|
2 | 2 | # Use of this source code is governed by a BSD-style license that can be
|
3 | 3 | # found in the LICENSE file.
|
4 | 4 |
|
| 5 | +import sys |
| 6 | + |
5 | 7 | """Helper functions useful when writing scripts that are run from GN's
|
6 | 8 | exec_script function."""
|
7 | 9 |
|
8 |
| -class GNException(Exception): |
| 10 | +class GNError(Exception): |
9 | 11 | pass
|
10 | 12 |
|
11 | 13 |
|
12 |
| -def ToGNString(value, allow_dicts = True): |
13 |
| - """Prints the given value to stdout. |
| 14 | +# Computes ASCII code of an element of encoded Python 2 str / Python 3 bytes. |
| 15 | +_Ord = ord if sys.version_info.major < 3 else lambda c: c |
| 16 | + |
| 17 | + |
| 18 | +def _TranslateToGnChars(s): |
| 19 | + for decoded_ch in s.encode('utf-8'): # str in Python 2, bytes in Python 3. |
| 20 | + code = _Ord(decoded_ch) # int |
| 21 | + if code in (34, 36, 92): # For '"', '$', or '\\'. |
| 22 | + yield '\\' + chr(code) |
| 23 | + elif 32 <= code < 127: |
| 24 | + yield chr(code) |
| 25 | + else: |
| 26 | + yield '$0x%02X' % code |
| 27 | + |
| 28 | + |
| 29 | +def ToGNString(value, pretty=False): |
| 30 | + """Returns a stringified GN equivalent of a Python value. |
| 31 | +
|
| 32 | + Args: |
| 33 | + value: The Python value to convert. |
| 34 | + pretty: Whether to pretty print. If true, then non-empty lists are rendered |
| 35 | + recursively with one item per line, with indents. Otherwise lists are |
| 36 | + rendered without new line. |
| 37 | + Returns: |
| 38 | + The stringified GN equivalent to |value|. |
| 39 | +
|
| 40 | + Raises: |
| 41 | + GNError: |value| cannot be printed to GN. |
| 42 | + """ |
| 43 | + |
| 44 | + if sys.version_info.major < 3: |
| 45 | + basestring_compat = basestring |
| 46 | + else: |
| 47 | + basestring_compat = str |
| 48 | + |
| 49 | + # Emits all output tokens without intervening whitespaces. |
| 50 | + def GenerateTokens(v, level): |
| 51 | + if isinstance(v, basestring_compat): |
| 52 | + yield '"' + ''.join(_TranslateToGnChars(v)) + '"' |
| 53 | + |
| 54 | + elif isinstance(v, bool): |
| 55 | + yield 'true' if v else 'false' |
| 56 | + |
| 57 | + elif isinstance(v, int): |
| 58 | + yield str(v) |
| 59 | + |
| 60 | + elif isinstance(v, list): |
| 61 | + yield '[' |
| 62 | + for i, item in enumerate(v): |
| 63 | + if i > 0: |
| 64 | + yield ',' |
| 65 | + for tok in GenerateTokens(item, level + 1): |
| 66 | + yield tok |
| 67 | + yield ']' |
| 68 | + |
| 69 | + elif isinstance(v, dict): |
| 70 | + if level > 0: |
| 71 | + yield '{' |
| 72 | + for key in sorted(v): |
| 73 | + if not isinstance(key, basestring_compat): |
| 74 | + raise GNError('Dictionary key is not a string.') |
| 75 | + if not key or key[0].isdigit() or not key.replace('_', '').isalnum(): |
| 76 | + raise GNError('Dictionary key is not a valid GN identifier.') |
| 77 | + yield key # No quotations. |
| 78 | + yield '=' |
| 79 | + for tok in GenerateTokens(v[key], level + 1): |
| 80 | + yield tok |
| 81 | + if level > 0: |
| 82 | + yield '}' |
| 83 | + |
| 84 | + else: # Not supporting float: Add only when needed. |
| 85 | + raise GNError('Unsupported type when printing to GN.') |
14 | 86 |
|
15 |
| - allow_dicts indicates if this function will allow converting dictionaries |
16 |
| - to GN scopes. This is only possible at the top level, you can't nest a |
17 |
| - GN scope in a list, so this should be set to False for recursive calls.""" |
18 |
| - if isinstance(value, str) or isinstance(value, str): |
19 |
| - if value.find('\n') >= 0: |
20 |
| - raise GNException("Trying to print a string with a newline in it.") |
21 |
| - return '"' + value.replace('"', '\\"') + '"' |
| 87 | + can_start = lambda tok: tok and tok not in ',}]=' |
| 88 | + can_end = lambda tok: tok and tok not in ',{[=' |
22 | 89 |
|
23 |
| - if isinstance(value, list): |
24 |
| - return '[ %s ]' % ', '.join(ToGNString(v) for v in value) |
| 90 | + # Adds whitespaces, trying to keep everything (except dicts) in 1 line. |
| 91 | + def PlainGlue(gen): |
| 92 | + prev_tok = None |
| 93 | + for i, tok in enumerate(gen): |
| 94 | + if i > 0: |
| 95 | + if can_end(prev_tok) and can_start(tok): |
| 96 | + yield '\n' # New dict item. |
| 97 | + elif prev_tok == '[' and tok == ']': |
| 98 | + yield ' ' # Special case for []. |
| 99 | + elif tok != ',': |
| 100 | + yield ' ' |
| 101 | + yield tok |
| 102 | + prev_tok = tok |
25 | 103 |
|
26 |
| - if isinstance(value, dict): |
27 |
| - if not allow_dicts: |
28 |
| - raise GNException("Attempting to recursively print a dictionary.") |
29 |
| - result = "" |
30 |
| - for key in value: |
31 |
| - if not isinstance(key, str): |
32 |
| - raise GNException("Dictionary key is not a string.") |
33 |
| - result += "%s = %s\n" % (key, ToGNString(value[key], False)) |
34 |
| - return result |
| 104 | + # Adds whitespaces so non-empty lists can span multiple lines, with indent. |
| 105 | + def PrettyGlue(gen): |
| 106 | + prev_tok = None |
| 107 | + level = 0 |
| 108 | + for i, tok in enumerate(gen): |
| 109 | + if i > 0: |
| 110 | + if can_end(prev_tok) and can_start(tok): |
| 111 | + yield '\n' + ' ' * level # New dict item. |
| 112 | + elif tok == '=' or prev_tok in '=': |
| 113 | + yield ' ' # Separator before and after '=', on same line. |
| 114 | + if tok in ']}': |
| 115 | + level -= 1 |
| 116 | + # Exclude '[]' and '{}' cases. |
| 117 | + if int(prev_tok == '[') + int(tok == ']') == 1 or \ |
| 118 | + int(prev_tok == '{') + int(tok == '}') == 1: |
| 119 | + yield '\n' + ' ' * level |
| 120 | + yield tok |
| 121 | + if tok in '[{': |
| 122 | + level += 1 |
| 123 | + if tok == ',': |
| 124 | + yield '\n' + ' ' * level |
| 125 | + prev_tok = tok |
35 | 126 |
|
36 |
| - if isinstance(value, int): |
37 |
| - return str(value) |
| 127 | + token_gen = GenerateTokens(value, 0) |
| 128 | + ret = ''.join((PrettyGlue if pretty else PlainGlue)(token_gen)) |
| 129 | + # Add terminating '\n' for dict |value| or multi-line output. |
| 130 | + if isinstance(value, dict) or '\n' in ret: |
| 131 | + return ret + '\n' |
| 132 | + return ret |
38 | 133 |
|
39 |
| - raise GNException("Unsupported type %s (value %s) when printing to GN." % (type(value), value)) |
|
0 commit comments