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