| #!/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() |