mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
227 lines
7.2 KiB
Python
227 lines
7.2 KiB
Python
# Copyright 2016 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.
|
|
|
|
import argparse
|
|
import plistlib
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import shlex
|
|
|
|
|
|
# Xcode substitutes variables like ${PRODUCT_NAME} or $(PRODUCT_NAME) when
|
|
# compiling Info.plist. It also supports supports modifiers like :identifier
|
|
# or :rfc1034identifier. SUBSTITUTION_REGEXP_LIST is a list of regular
|
|
# expressions matching a variable substitution pattern with an optional
|
|
# modifier, while INVALID_CHARACTER_REGEXP matches all characters that are
|
|
# not valid in an "identifier" value (used when applying the modifier).
|
|
INVALID_CHARACTER_REGEXP = re.compile(r'[_/\s]')
|
|
SUBSTITUTION_REGEXP_LIST = (
|
|
re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}'),
|
|
re.compile(r'\$\((?P<id>[^}]*?)(?P<modifier>:[^}]*)?\)'),
|
|
)
|
|
|
|
|
|
class SubstitutionError(Exception):
|
|
def __init__(self, key):
|
|
super(SubstitutionError, self).__init__()
|
|
self.key = key
|
|
|
|
def __str__(self):
|
|
return "SubstitutionError: {}".format(self.key)
|
|
|
|
|
|
def InterpolateString(value, substitutions):
|
|
"""Interpolates variable references into |value| using |substitutions|.
|
|
|
|
Inputs:
|
|
value: a string
|
|
substitutions: a mapping of variable names to values
|
|
|
|
Returns:
|
|
A new string with all variables references ${VARIABLES} replaced by their
|
|
value in |substitutions|. Raises SubstitutionError if a variable has no
|
|
substitution.
|
|
"""
|
|
def repl(match):
|
|
variable = match.group('id')
|
|
if variable not in substitutions:
|
|
raise SubstitutionError(variable)
|
|
# Some values need to be identifier and thus the variables references may
|
|
# contains :modifier attributes to indicate how they should be converted
|
|
# to identifiers ("identifier" replaces all invalid characters by '_' and
|
|
# "rfc1034identifier" replaces them by "-" to make valid URI too).
|
|
modifier = match.group('modifier')
|
|
if modifier == ':identifier':
|
|
return INVALID_CHARACTER_REGEXP.sub('_', substitutions[variable])
|
|
elif modifier == ':rfc1034identifier':
|
|
return INVALID_CHARACTER_REGEXP.sub('-', substitutions[variable])
|
|
else:
|
|
return substitutions[variable]
|
|
for substitution_regexp in SUBSTITUTION_REGEXP_LIST:
|
|
value = substitution_regexp.sub(repl, value)
|
|
return value
|
|
|
|
|
|
def Interpolate(value, substitutions):
|
|
"""Interpolates variable references into |value| using |substitutions|.
|
|
|
|
Inputs:
|
|
value: a value, can be a dictionary, list, string or other
|
|
substitutions: a mapping of variable names to values
|
|
|
|
Returns:
|
|
A new value with all variables references ${VARIABLES} replaced by their
|
|
value in |substitutions|. Raises SubstitutionError if a variable has no
|
|
substitution.
|
|
"""
|
|
if isinstance(value, dict):
|
|
return {k: Interpolate(v, substitutions) for k, v in value.iteritems()}
|
|
if isinstance(value, list):
|
|
return [Interpolate(v, substitutions) for v in value]
|
|
if isinstance(value, str):
|
|
return InterpolateString(value, substitutions)
|
|
return value
|
|
|
|
|
|
def LoadPList(path):
|
|
"""Loads Plist at |path| and returns it as a dictionary."""
|
|
fd, name = tempfile.mkstemp()
|
|
try:
|
|
subprocess.check_call(['plutil', '-convert', 'xml1', '-o', name, path])
|
|
with os.fdopen(fd, 'r') as f:
|
|
return plistlib.readPlist(f)
|
|
finally:
|
|
os.unlink(name)
|
|
|
|
|
|
def SavePList(path, format, data):
|
|
"""Saves |data| as a Plist to |path| in the specified |format|."""
|
|
fd, name = tempfile.mkstemp()
|
|
try:
|
|
# "plutil" does not replace the destination file but update it in place,
|
|
# so if more than one hardlink points to destination all of them will be
|
|
# modified. This is not what is expected, so delete destination file if
|
|
# it does exist.
|
|
if os.path.exists(path):
|
|
os.unlink(path)
|
|
with os.fdopen(fd, 'w') as f:
|
|
plistlib.writePlist(data, f)
|
|
subprocess.check_call(['plutil', '-convert', format, '-o', path, name])
|
|
finally:
|
|
os.unlink(name)
|
|
|
|
|
|
def MergePList(plist1, plist2):
|
|
"""Merges |plist1| with |plist2| recursively.
|
|
|
|
Creates a new dictionary representing a Property List (.plist) files by
|
|
merging the two dictionary |plist1| and |plist2| recursively (only for
|
|
dictionary values). List value will be concatenated.
|
|
|
|
Args:
|
|
plist1: a dictionary representing a Property List (.plist) file
|
|
plist2: a dictionary representing a Property List (.plist) file
|
|
|
|
Returns:
|
|
A new dictionary representing a Property List (.plist) file by merging
|
|
|plist1| with |plist2|. If any value is a dictionary, they are merged
|
|
recursively, otherwise |plist2| value is used. If values are list, they
|
|
are concatenated.
|
|
"""
|
|
result = plist1.copy()
|
|
for key, value in plist2.iteritems():
|
|
if isinstance(value, dict):
|
|
old_value = result.get(key)
|
|
if isinstance(old_value, dict):
|
|
value = MergePList(old_value, value)
|
|
if isinstance(value, list):
|
|
value = plist1.get(key, []) + plist2.get(key, [])
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
class Action(object):
|
|
"""Class implementing one action supported by the script."""
|
|
|
|
@classmethod
|
|
def Register(cls, subparsers):
|
|
parser = subparsers.add_parser(cls.name, help=cls.help)
|
|
parser.set_defaults(func=cls._Execute)
|
|
cls._Register(parser)
|
|
|
|
|
|
class MergeAction(Action):
|
|
"""Class to merge multiple plist files."""
|
|
|
|
name = 'merge'
|
|
help = 'merge multiple plist files'
|
|
|
|
@staticmethod
|
|
def _Register(parser):
|
|
parser.add_argument(
|
|
'-o', '--output', required=True,
|
|
help='path to the output plist file')
|
|
parser.add_argument(
|
|
'-f', '--format', required=True, choices=('xml1', 'binary1', 'json'),
|
|
help='format of the plist file to generate')
|
|
parser.add_argument(
|
|
'path', nargs="+",
|
|
help='path to plist files to merge')
|
|
|
|
@staticmethod
|
|
def _Execute(args):
|
|
data = {}
|
|
for filename in args.path:
|
|
data = MergePList(data, LoadPList(filename))
|
|
SavePList(args.output, args.format, data)
|
|
|
|
|
|
class SubstituteAction(Action):
|
|
"""Class implementing the variable substitution in a plist file."""
|
|
|
|
name = 'substitute'
|
|
help = 'perform pattern substitution in a plist file'
|
|
|
|
@staticmethod
|
|
def _Register(parser):
|
|
parser.add_argument(
|
|
'-o', '--output', required=True,
|
|
help='path to the output plist file')
|
|
parser.add_argument(
|
|
'-t', '--template', required=True,
|
|
help='path to the template file')
|
|
parser.add_argument(
|
|
'-s', '--substitution', action='append', default=[],
|
|
help='substitution rule in the format key=value')
|
|
parser.add_argument(
|
|
'-f', '--format', required=True, choices=('xml1', 'binary1', 'json'),
|
|
help='format of the plist file to generate')
|
|
|
|
@staticmethod
|
|
def _Execute(args):
|
|
substitutions = {}
|
|
for substitution in args.substitution:
|
|
key, value = substitution.split('=', 1)
|
|
substitutions[key] = value
|
|
data = Interpolate(LoadPList(args.template), substitutions)
|
|
SavePList(args.output, args.format, data)
|
|
|
|
|
|
def Main():
|
|
parser = argparse.ArgumentParser(description='manipulate plist files')
|
|
subparsers = parser.add_subparsers()
|
|
|
|
for action in [MergeAction, SubstituteAction]:
|
|
action.Register(subparsers)
|
|
|
|
args = parser.parse_args()
|
|
args.func(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(Main())
|