xref: /aosp_15_r20/build/soong/scripts/jsonmodify.py (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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