| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """ |
| Merges multiple Apple Property List files (.plist) and perform variables |
| substitutions $(VARIABLE) in the Property List string values. |
| """ |
| |
| import argparse |
| import json |
| import re |
| import subprocess |
| import sys |
| |
| |
| # Pattern representing a variable to substitue in a string value. |
| VARIABLE_PATTERN = re.compile(r'\$\(([^)]*)\)') |
| |
| |
| def GetCommandOutput(command): |
| """Returns the output of `command` as a string.""" |
| return subprocess.check_output(command, encoding='utf-8') |
| |
| |
| def LoadPlist(plist_path): |
| """Loads Apple Property List file at |plist_path|.""" |
| return json.loads( |
| GetCommandOutput(['plutil', '-convert', 'json', '-o', '-', plist_path])) |
| |
| |
| def SavePlist(plist_path, content, format): |
| """Saves |content| as Apple Property List in |format| at |plist_path|.""" |
| proc = subprocess.Popen( |
| ['plutil', '-convert', format, '-o', plist_path, '-'], |
| stdin=subprocess.PIPE) |
| output, _ = proc.communicate(json.dumps(content).encode('utf-8')) |
| if proc.returncode: |
| raise subprocess.CalledProcessError( |
| proc.returncode, |
| ['plutil', '-convert', format, '-o', plist_path, '-'], |
| output) |
| |
| |
| def MergeObjects(obj1, obj2): |
| """Merges two objects (either dictionary, list, string or numbers).""" |
| if type(obj1) != type(obj2): |
| return obj2 |
| |
| if isinstance(obj2, dict): |
| result = dict(obj1) |
| for key in obj2: |
| value1 = obj1.get(key, None) |
| value2 = obj2.get(key, None) |
| result[key] = MergeObjects(value1, value2) |
| return result |
| |
| if isinstance(obj2, list): |
| return obj1 + obj2 |
| |
| return obj2 |
| |
| |
| def MergePlists(plist_paths): |
| """Loads and merges all Apple Property List files at |plist_paths|.""" |
| plist = {} |
| for plist_path in plist_paths: |
| plist = MergeObjects(plist, LoadPlist(plist_path)) |
| return plist |
| |
| |
| def PerformSubstitutions(plist, substitutions): |
| """Performs variables substitutions in |plist| given by |substitutions|.""" |
| if isinstance(plist, dict): |
| result = dict(plist) |
| for key in plist: |
| result[key] = PerformSubstitutions(plist[key], substitutions) |
| return result |
| |
| if isinstance(plist, list): |
| return [ PerformSubstitutions(item, substitutions) for item in plist ] |
| |
| if isinstance(plist, str): |
| result = plist |
| while True: |
| match = VARIABLE_PATTERN.search(result) |
| if not match: |
| break |
| |
| extent = match.span() |
| expand = substitutions[match.group(1)] |
| result = result[:extent[0]] + expand + result[extent[1]:] |
| return result |
| |
| return plist |
| |
| |
| def PerformSubstitutionsFrom(plist, substitutions_path): |
| """Performs variable substitutions in |plist| from |substitutions_path|.""" |
| with open(substitutions_path) as substitutions_file: |
| return PerformSubstitutions(plist, json.load(substitutions_file)) |
| |
| |
| def ParseArgs(argv): |
| """Parses command line arguments.""" |
| parser = argparse.ArgumentParser( |
| description=__doc__, |
| formatter_class=argparse.ArgumentDefaultsHelpFormatter) |
| |
| parser.add_argument( |
| '-s', '--substitutions', |
| help='path to a JSON file containing variable substitutions') |
| parser.add_argument( |
| '-f', '--format', default='json', choices=('json', 'binary1', 'xml1'), |
| help='format of the generated file') |
| parser.add_argument( |
| '-o', '--output', default='-', |
| help='path to the result; - means stdout') |
| parser.add_argument( |
| 'inputs', nargs='+', |
| help='path of the input files to merge') |
| |
| return parser.parse_args(argv) |
| |
| |
| def main(argv): |
| args = ParseArgs(argv) |
| |
| data = MergePlists(args.inputs) |
| if args.substitutions: |
| data = PerformSubstitutionsFrom( |
| data, args.substitutions) |
| |
| SavePlist(args.output, data, args.format) |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |