blob: f13ce0077500ea87a7896eaf4e9d344477c73bf4 [file] [log] [blame] [edit]
# 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:]))