#!/usr/bin/env python # # Copyright (C) 2019 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import argparse import collections import json import sys def follow_path(obj, path): cur = obj last_key = None for key in path.split('.'): if last_key: if last_key not in cur: return None,None cur = cur[last_key] last_key = key if last_key not in cur: return None,None return cur, last_key def ensure_path(obj, path): cur = obj last_key = None for key in path.split('.'): if last_key: if last_key not in cur: cur[last_key] = dict() cur = cur[last_key] last_key = key return cur, last_key class SetValue(str): def apply(self, obj, val): cur, key = ensure_path(obj, self) cur[key] = val class Replace(str): def apply(self, obj, val): cur, key = follow_path(obj, self) if cur: cur[key] = val class ReplaceIfEqual(str): def apply(self, obj, old_val, new_val): cur, key = follow_path(obj, self) if cur and cur[key] == int(old_val): cur[key] = new_val class Remove(str): def apply(self, obj): cur, key = follow_path(obj, self) if cur: del cur[key] class AppendList(str): def apply(self, obj, *args): cur, key = ensure_path(obj, self) if key not in cur: cur[key] = list() if not isinstance(cur[key], list): raise ValueError(self + " should be a array.") cur[key].extend(args) # A JSONDecoder that supports line comments start with // class JSONWithCommentsDecoder(json.JSONDecoder): def __init__(self, **kw): super().__init__(**kw) def decode(self, s: str): s = '\n'.join(l for l in s.split('\n') if not l.lstrip(' ').startswith('//')) return super().decode(s) def main(): parser = argparse.ArgumentParser() parser.add_argument('-o', '--out', help='write result to a file. If omitted, print to stdout', metavar='output', action='store') parser.add_argument('input', nargs='?', help='JSON file') parser.add_argument("-v", "--value", type=SetValue, help='set value of the key specified by path. If path doesn\'t exist, creates new one.', metavar=('path', 'value'), nargs=2, dest='patch', default=[], action='append') parser.add_argument("-s", "--replace", type=Replace, help='replace value of the key specified by path. If path doesn\'t exist, no op.', metavar=('path', 'value'), nargs=2, dest='patch', action='append') parser.add_argument("-se", "--replace-if-equal", type=ReplaceIfEqual, help='replace value of the key specified by path to new_value if it\'s equal to old_value.' + 'If path doesn\'t exist or the value is not equal to old_value, no op.', metavar=('path', 'old_value', 'new_value'), nargs=3, dest='patch', action='append') parser.add_argument("-r", "--remove", type=Remove, help='remove the key specified by path. If path doesn\'t exist, no op.', metavar='path', nargs=1, dest='patch', action='append') parser.add_argument("-a", "--append_list", type=AppendList, help='append values to the list specified by path. If path doesn\'t exist, creates new list for it.', metavar=('path', 'value'), nargs='+', dest='patch', default=[], action='append') args = parser.parse_args() if args.input: with open(args.input) as f: obj = json.load(f, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder) else: obj = json.load(sys.stdin, object_pairs_hook=collections.OrderedDict, cls=JSONWithCommentsDecoder) for p in args.patch: p[0].apply(obj, *p[1:]) if args.out: with open(args.out, "w") as f: json.dump(obj, f, indent=2, separators=(',', ': ')) f.write('\n') else: print(json.dumps(obj, indent=2, separators=(',', ': '))) if __name__ == '__main__': main()