# Copyright 2014 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 sys """Helper functions useful when writing scripts that are run from GN's exec_script function.""" class GNError(Exception): pass # Computes ASCII code of an element of encoded Python 2 str / Python 3 bytes. _Ord = ord if sys.version_info.major < 3 else lambda c: c def _TranslateToGnChars(s): for decoded_ch in s.encode('utf-8'): # str in Python 2, bytes in Python 3. code = _Ord(decoded_ch) # int if code in (34, 36, 92): # For '"', '$', or '\\'. yield '\\' + chr(code) elif 32 <= code < 127: yield chr(code) else: yield '$0x%02X' % code def ToGNString(value, pretty=False): """Returns a stringified GN equivalent of a Python value. Args: value: The Python value to convert. pretty: Whether to pretty print. If true, then non-empty lists are rendered recursively with one item per line, with indents. Otherwise lists are rendered without new line. Returns: The stringified GN equivalent to |value|. Raises: GNError: |value| cannot be printed to GN. """ if sys.version_info.major < 3: basestring_compat = basestring else: basestring_compat = str # Emits all output tokens without intervening whitespaces. def GenerateTokens(v, level): if isinstance(v, basestring_compat): yield '"' + ''.join(_TranslateToGnChars(v)) + '"' elif isinstance(v, bool): yield 'true' if v else 'false' elif isinstance(v, int): yield str(v) elif isinstance(v, list): yield '[' for i, item in enumerate(v): if i > 0: yield ',' for tok in GenerateTokens(item, level + 1): yield tok yield ']' elif isinstance(v, dict): if level > 0: yield '{' for key in sorted(v): if not isinstance(key, basestring_compat): raise GNError('Dictionary key is not a string.') if not key or key[0].isdigit() or not key.replace('_', '').isalnum(): raise GNError('Dictionary key is not a valid GN identifier.') yield key # No quotations. yield '=' for tok in GenerateTokens(v[key], level + 1): yield tok if level > 0: yield '}' else: # Not supporting float: Add only when needed. raise GNError('Unsupported type when printing to GN.') can_start = lambda tok: tok and tok not in ',}]=' can_end = lambda tok: tok and tok not in ',{[=' # Adds whitespaces, trying to keep everything (except dicts) in 1 line. def PlainGlue(gen): prev_tok = None for i, tok in enumerate(gen): if i > 0: if can_end(prev_tok) and can_start(tok): yield '\n' # New dict item. elif prev_tok == '[' and tok == ']': yield ' ' # Special case for []. elif tok != ',': yield ' ' yield tok prev_tok = tok # Adds whitespaces so non-empty lists can span multiple lines, with indent. def PrettyGlue(gen): prev_tok = None level = 0 for i, tok in enumerate(gen): if i > 0: if can_end(prev_tok) and can_start(tok): yield '\n' + ' ' * level # New dict item. elif tok == '=' or prev_tok in '=': yield ' ' # Separator before and after '=', on same line. if tok in ']}': level -= 1 # Exclude '[]' and '{}' cases. if int(prev_tok == '[') + int(tok == ']') == 1 or \ int(prev_tok == '{') + int(tok == '}') == 1: yield '\n' + ' ' * level yield tok if tok in '[{': level += 1 if tok == ',': yield '\n' + ' ' * level prev_tok = tok token_gen = GenerateTokens(value, 0) ret = ''.join((PrettyGlue if pretty else PlainGlue)(token_gen)) # Add terminating '\n' for dict |value| or multi-line output. if isinstance(value, dict) or '\n' in ret: return ret + '\n' return ret