1*333d2b36SAndroid Build Coastguard Worker#!/usr/bin/env python 2*333d2b36SAndroid Build Coastguard Worker# 3*333d2b36SAndroid Build Coastguard Worker# Copyright (C) 2019 The Android Open Source Project 4*333d2b36SAndroid Build Coastguard Worker# 5*333d2b36SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*333d2b36SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*333d2b36SAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*333d2b36SAndroid Build Coastguard Worker# 9*333d2b36SAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*333d2b36SAndroid Build Coastguard Worker# 11*333d2b36SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*333d2b36SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*333d2b36SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*333d2b36SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*333d2b36SAndroid Build Coastguard Worker# limitations under the License. 16*333d2b36SAndroid Build Coastguard Worker 17*333d2b36SAndroid Build Coastguard Worker 18*333d2b36SAndroid Build Coastguard Workerimport argparse 19*333d2b36SAndroid Build Coastguard Workerimport collections 20*333d2b36SAndroid Build Coastguard Workerimport json 21*333d2b36SAndroid Build Coastguard Workerimport sys 22*333d2b36SAndroid Build Coastguard Worker 23*333d2b36SAndroid Build Coastguard Workerdef follow_path(obj, path): 24*333d2b36SAndroid Build Coastguard Worker cur = obj 25*333d2b36SAndroid Build Coastguard Worker last_key = None 26*333d2b36SAndroid Build Coastguard Worker for key in path.split('.'): 27*333d2b36SAndroid Build Coastguard Worker if last_key: 28*333d2b36SAndroid Build Coastguard Worker if last_key not in cur: 29*333d2b36SAndroid Build Coastguard Worker return None,None 30*333d2b36SAndroid Build Coastguard Worker cur = cur[last_key] 31*333d2b36SAndroid Build Coastguard Worker last_key = key 32*333d2b36SAndroid Build Coastguard Worker if last_key not in cur: 33*333d2b36SAndroid Build Coastguard Worker return None,None 34*333d2b36SAndroid Build Coastguard Worker return cur, last_key 35*333d2b36SAndroid Build Coastguard Worker 36*333d2b36SAndroid Build Coastguard Worker 37*333d2b36SAndroid Build Coastguard Workerdef ensure_path(obj, path): 38*333d2b36SAndroid Build Coastguard Worker cur = obj 39*333d2b36SAndroid Build Coastguard Worker last_key = None 40*333d2b36SAndroid Build Coastguard Worker for key in path.split('.'): 41*333d2b36SAndroid Build Coastguard Worker if last_key: 42*333d2b36SAndroid Build Coastguard Worker if last_key not in cur: 43*333d2b36SAndroid Build Coastguard Worker cur[last_key] = dict() 44*333d2b36SAndroid Build Coastguard Worker cur = cur[last_key] 45*333d2b36SAndroid Build Coastguard Worker last_key = key 46*333d2b36SAndroid Build Coastguard Worker return cur, last_key 47*333d2b36SAndroid Build Coastguard Worker 48*333d2b36SAndroid Build Coastguard Worker 49*333d2b36SAndroid Build Coastguard Workerclass SetValue(str): 50*333d2b36SAndroid Build Coastguard Worker def apply(self, obj, val): 51*333d2b36SAndroid Build Coastguard Worker cur, key = ensure_path(obj, self) 52*333d2b36SAndroid Build Coastguard Worker cur[key] = val 53*333d2b36SAndroid Build Coastguard Worker 54*333d2b36SAndroid Build Coastguard Worker 55*333d2b36SAndroid Build Coastguard Workerclass Replace(str): 56*333d2b36SAndroid Build Coastguard Worker def apply(self, obj, val): 57*333d2b36SAndroid Build Coastguard Worker cur, key = follow_path(obj, self) 58*333d2b36SAndroid Build Coastguard Worker if cur: 59*333d2b36SAndroid Build Coastguard Worker cur[key] = val 60*333d2b36SAndroid Build Coastguard Worker 61*333d2b36SAndroid Build Coastguard Worker 62*333d2b36SAndroid Build Coastguard Workerclass ReplaceIfEqual(str): 63*333d2b36SAndroid Build Coastguard Worker def apply(self, obj, old_val, new_val): 64*333d2b36SAndroid Build Coastguard Worker cur, key = follow_path(obj, self) 65*333d2b36SAndroid Build Coastguard Worker if cur and cur[key] == int(old_val): 66*333d2b36SAndroid Build Coastguard Worker cur[key] = new_val 67*333d2b36SAndroid Build Coastguard Worker 68*333d2b36SAndroid Build Coastguard Worker 69*333d2b36SAndroid Build Coastguard Workerclass Remove(str): 70*333d2b36SAndroid Build Coastguard Worker def apply(self, obj): 71*333d2b36SAndroid Build Coastguard Worker cur, key = follow_path(obj, self) 72*333d2b36SAndroid Build Coastguard Worker if cur: 73*333d2b36SAndroid Build Coastguard Worker del cur[key] 74*333d2b36SAndroid Build Coastguard Worker 75*333d2b36SAndroid Build Coastguard Worker 76*333d2b36SAndroid Build Coastguard Workerclass AppendList(str): 77*333d2b36SAndroid Build Coastguard Worker def apply(self, obj, *args): 78*333d2b36SAndroid Build Coastguard Worker cur, key = ensure_path(obj, self) 79*333d2b36SAndroid Build Coastguard Worker if key not in cur: 80*333d2b36SAndroid Build Coastguard Worker cur[key] = list() 81*333d2b36SAndroid Build Coastguard Worker if not isinstance(cur[key], list): 82*333d2b36SAndroid Build Coastguard Worker raise ValueError(self + " should be a array.") 83*333d2b36SAndroid Build Coastguard Worker cur[key].extend(args) 84*333d2b36SAndroid Build Coastguard Worker 85*333d2b36SAndroid Build Coastguard Worker# A JSONDecoder that supports line comments start with // 86*333d2b36SAndroid Build Coastguard Workerclass JSONWithCommentsDecoder(json.JSONDecoder): 87*333d2b36SAndroid Build Coastguard Worker def __init__(self, **kw): 88*333d2b36SAndroid Build Coastguard Worker super().__init__(**kw) 89*333d2b36SAndroid Build Coastguard Worker 90*333d2b36SAndroid Build Coastguard Worker def decode(self, s: str): 91*333d2b36SAndroid Build Coastguard Worker s = '\n'.join(l for l in s.split('\n') if not l.lstrip(' ').startswith('//')) 92*333d2b36SAndroid Build Coastguard Worker return super().decode(s) 93*333d2b36SAndroid Build Coastguard Worker 94*333d2b36SAndroid Build Coastguard Workerdef main(): 95*333d2b36SAndroid Build Coastguard Worker parser = argparse.ArgumentParser() 96*333d2b36SAndroid Build Coastguard Worker parser.add_argument('-o', '--out', 97*333d2b36SAndroid Build Coastguard Worker help='write result to a file. If omitted, print to stdout', 98*333d2b36SAndroid Build Coastguard Worker metavar='output', 99*333d2b36SAndroid Build Coastguard Worker action='store') 100*333d2b36SAndroid Build Coastguard Worker parser.add_argument('input', nargs='?', help='JSON file') 101*333d2b36SAndroid Build Coastguard Worker parser.add_argument("-v", "--value", type=SetValue, 102*333d2b36SAndroid Build Coastguard Worker help='set value of the key specified by path. If path doesn\'t exist, creates new one.', 103*333d2b36SAndroid Build Coastguard Worker metavar=('path', 'value'), 104*333d2b36SAndroid Build Coastguard Worker nargs=2, dest='patch', default=[], action='append') 105*333d2b36SAndroid Build Coastguard Worker parser.add_argument("-s", "--replace", type=Replace, 106*333d2b36SAndroid Build Coastguard Worker help='replace value of the key specified by path. If path doesn\'t exist, no op.', 107*333d2b36SAndroid Build Coastguard Worker metavar=('path', 'value'), 108*333d2b36SAndroid Build Coastguard Worker nargs=2, dest='patch', action='append') 109*333d2b36SAndroid Build Coastguard Worker parser.add_argument("-se", "--replace-if-equal", type=ReplaceIfEqual, 110*333d2b36SAndroid Build Coastguard Worker help='replace value of the key specified by path to new_value if it\'s equal to old_value.' + 111*333d2b36SAndroid Build Coastguard Worker 'If path doesn\'t exist or the value is not equal to old_value, no op.', 112*333d2b36SAndroid Build Coastguard Worker metavar=('path', 'old_value', 'new_value'), 113*333d2b36SAndroid Build Coastguard Worker nargs=3, dest='patch', action='append') 114*333d2b36SAndroid Build Coastguard Worker parser.add_argument("-r", "--remove", type=Remove, 115*333d2b36SAndroid Build Coastguard Worker help='remove the key specified by path. If path doesn\'t exist, no op.', 116*333d2b36SAndroid Build Coastguard Worker metavar='path', 117*333d2b36SAndroid Build Coastguard Worker nargs=1, dest='patch', action='append') 118*333d2b36SAndroid Build Coastguard Worker parser.add_argument("-a", "--append_list", type=AppendList, 119*333d2b36SAndroid Build Coastguard Worker help='append values to the list specified by path. If path doesn\'t exist, creates new list for it.', 120*333d2b36SAndroid Build Coastguard Worker metavar=('path', 'value'), 121*333d2b36SAndroid Build Coastguard Worker nargs='+', dest='patch', default=[], action='append') 122*333d2b36SAndroid Build Coastguard Worker args = parser.parse_args() 123*333d2b36SAndroid Build Coastguard Worker 124*333d2b36SAndroid Build Coastguard Worker if args.input: 125*333d2b36SAndroid Build Coastguard Worker with open(args.input) as f: 126*333d2b36SAndroid Build Coastguard Worker obj = json.load(f, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder) 127*333d2b36SAndroid Build Coastguard Worker else: 128*333d2b36SAndroid Build Coastguard Worker obj = json.load(sys.stdin, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder) 129*333d2b36SAndroid Build Coastguard Worker 130*333d2b36SAndroid Build Coastguard Worker for p in args.patch: 131*333d2b36SAndroid Build Coastguard Worker p[0].apply(obj, *p[1:]) 132*333d2b36SAndroid Build Coastguard Worker 133*333d2b36SAndroid Build Coastguard Worker if args.out: 134*333d2b36SAndroid Build Coastguard Worker with open(args.out, "w") as f: 135*333d2b36SAndroid Build Coastguard Worker json.dump(obj, f, indent=2, separators=(',', ': ')) 136*333d2b36SAndroid Build Coastguard Worker f.write('\n') 137*333d2b36SAndroid Build Coastguard Worker else: 138*333d2b36SAndroid Build Coastguard Worker print(json.dumps(obj, indent=2, separators=(',', ': '))) 139*333d2b36SAndroid Build Coastguard Worker 140*333d2b36SAndroid Build Coastguard Worker 141*333d2b36SAndroid Build Coastguard Workerif __name__ == '__main__': 142*333d2b36SAndroid Build Coastguard Worker main() 143