OLD | NEW |
1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import argparse | 5 import argparse |
6 import plistlib | 6 import plistlib |
7 import os | 7 import os |
8 import re | 8 import re |
9 import subprocess | 9 import subprocess |
10 import sys | 10 import sys |
11 import tempfile | 11 import tempfile |
12 import shlex | 12 import shlex |
13 | 13 |
14 | 14 |
15 # Xcode substitutes variables like ${PRODUCT_NAME} when compiling Info.plist. | 15 # Xcode substitutes variables like ${PRODUCT_NAME} when compiling Info.plist. |
16 # It also supports supports modifiers like :identifier or :rfc1034identifier. | 16 # It also supports supports modifiers like :identifier or :rfc1034identifier. |
17 # SUBST_RE matches a variable substitution pattern with an optional modifier, | 17 # SUBST_RE matches a variable substitution pattern with an optional modifier, |
18 # while IDENT_RE matches all characters that are not valid in an "identifier" | 18 # while IDENT_RE matches all characters that are not valid in an "identifier" |
19 # value (used when applying the modifier). | 19 # value (used when applying the modifier). |
20 SUBST_RE = re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}') | 20 SUBST_RE = re.compile(r'\$\{(?P<id>[^}]*?)(?P<modifier>:[^}]*)?\}') |
21 IDENT_RE = re.compile(r'[_/\s]') | 21 IDENT_RE = re.compile(r'[_/\s]') |
22 | 22 |
23 | 23 |
24 class ArgumentParser(argparse.ArgumentParser): | |
25 """Subclass of argparse.ArgumentParser to work with GN response files. | |
26 | |
27 GN response file writes all the arguments on a single line and assumes | |
28 that the python script uses shlext.split() to extract them. Since the | |
29 default ArgumentParser expects a single argument per line, we need to | |
30 provide a subclass to have the correct support for @{{response_file_name}}. | |
31 """ | |
32 | |
33 def convert_arg_line_to_args(self, arg_line): | |
34 return shlex.split(arg_line) | |
35 | |
36 | |
37 def InterpolateList(values, substitutions): | 24 def InterpolateList(values, substitutions): |
38 """Interpolates variable references into |value| using |substitutions|. | 25 """Interpolates variable references into |value| using |substitutions|. |
39 | 26 |
40 Inputs: | 27 Inputs: |
41 values: a list of values | 28 values: a list of values |
42 substitutions: a mapping of variable names to values | 29 substitutions: a mapping of variable names to values |
43 | 30 |
44 Returns: | 31 Returns: |
45 A new list of values with all variables references ${VARIABLE} replaced | 32 A new list of values with all variables references ${VARIABLE} replaced |
46 by their value in |substitutions| or None if any of the variable has no | 33 by their value in |substitutions| or None if any of the variable has no |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
175 if key in plist2: | 162 if key in plist2: |
176 value = plist2[key] | 163 value = plist2[key] |
177 else: | 164 else: |
178 value = plist1[key] | 165 value = plist1[key] |
179 if isinstance(value, dict): | 166 if isinstance(value, dict): |
180 value = MergePList(plist1.get(key, None), plist2.get(key, None)) | 167 value = MergePList(plist1.get(key, None), plist2.get(key, None)) |
181 result[key] = value | 168 result[key] = value |
182 return result | 169 return result |
183 | 170 |
184 | 171 |
185 def main(): | 172 class Action(object): |
186 parser = ArgumentParser( | 173 """Class implementing one action supported by the script.""" |
187 description='A script to generate iOS application Info.plist.', | 174 |
188 fromfile_prefix_chars='@') | 175 @classmethod |
189 parser.add_argument('-o', '--output', required=True, | 176 def Register(cls, subparsers): |
190 help='Path to output plist file.') | 177 parser = subparsers.add_parser(cls.name, help=cls.help) |
191 parser.add_argument('-s', '--subst', action='append', default=[], | 178 parser.set_defaults(func=cls._Execute) |
192 help='Substitution rule in the format "key=value".') | 179 cls._Register(parser) |
193 parser.add_argument('-f', '--format', required=True, | 180 |
194 help='Plist format (e.g. binary1, xml1) to output.') | 181 |
195 parser.add_argument('path', nargs="+", help='Path to input plist files.') | 182 class MergeAction(Action): |
| 183 """Class to merge multiple plist files.""" |
| 184 |
| 185 name = 'merge' |
| 186 help = 'merge multiple plist files' |
| 187 |
| 188 @staticmethod |
| 189 def _Register(parser): |
| 190 parser.add_argument( |
| 191 '-o', '--output', required=True, |
| 192 help='path to the output plist file') |
| 193 parser.add_argument( |
| 194 '-f', '--format', required=True, choices=('xml1', 'binary1', 'json'), |
| 195 help='format of the plist file to generate') |
| 196 parser.add_argument( |
| 197 'path', nargs="+", |
| 198 help='path to plist files to merge') |
| 199 |
| 200 @staticmethod |
| 201 def _Execute(args): |
| 202 data = {} |
| 203 for filename in args.path: |
| 204 data = MergePList(data, LoadPList(filename)) |
| 205 SavePList(args.output, args.format, data) |
| 206 |
| 207 |
| 208 class SubstituteAction(Action): |
| 209 """Class implementing the variable substitution in a plist file.""" |
| 210 |
| 211 name = 'substitute' |
| 212 help = 'perform pattern substitution in a plist file' |
| 213 |
| 214 @staticmethod |
| 215 def _Register(parser): |
| 216 parser.add_argument( |
| 217 '-o', '--output', required=True, |
| 218 help='path to the output plist file') |
| 219 parser.add_argument( |
| 220 '-t', '--template', required=True, |
| 221 help='path to the template file') |
| 222 parser.add_argument( |
| 223 '-s', '--substitution', action='append', default=[], |
| 224 help='substitution rule in the format key=value') |
| 225 parser.add_argument( |
| 226 '-f', '--format', required=True, choices=('xml1', 'binary1', 'json'), |
| 227 help='format of the plist file to generate') |
| 228 |
| 229 @staticmethod |
| 230 def _Execute(args): |
| 231 substitutions = {} |
| 232 for substitution in args.substitution: |
| 233 key, value = substitution.split('=', 1) |
| 234 substitutions[key] = value |
| 235 data = Interpolate(LoadPList(args.template), substitutions) |
| 236 SavePList(args.output, args.format, data) |
| 237 |
| 238 |
| 239 def Main(): |
| 240 parser = argparse.ArgumentParser(description='manipulate plist files') |
| 241 subparsers = parser.add_subparsers() |
| 242 |
| 243 for action in [MergeAction, SubstituteAction]: |
| 244 action.Register(subparsers) |
| 245 |
196 args = parser.parse_args() | 246 args = parser.parse_args() |
197 substitutions = {} | 247 args.func(args) |
198 for subst in args.subst: | 248 |
199 key, value = subst.split('=', 1) | |
200 substitutions[key] = value | |
201 data = {} | |
202 for filename in args.path: | |
203 data = MergePList(data, LoadPList(filename)) | |
204 data = Interpolate(data, substitutions) | |
205 SavePList(args.output, args.format, data) | |
206 return 0 | |
207 | 249 |
208 if __name__ == '__main__': | 250 if __name__ == '__main__': |
209 sys.exit(main()) | 251 sys.exit(Main()) |
OLD | NEW |