Home | History | Annotate | Download | only in json_schema_compiler
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 class Code(object):
      6   """A convenience object for constructing code.
      7 
      8   Logically each object should be a block of code. All methods except |Render|
      9   and |IsEmpty| return self.
     10   """
     11   def __init__(self, indent_size=2, comment_length=80):
     12     self._code = []
     13     self._indent_level = 0
     14     self._indent_size = indent_size
     15     self._comment_length = comment_length
     16 
     17   def Append(self, line='', substitute=True, indent_level=None):
     18     """Appends a line of code at the current indent level or just a newline if
     19     line is not specified. Trailing whitespace is stripped.
     20 
     21     substitute: indicated whether this line should be affected by
     22     code.Substitute().
     23     """
     24     if indent_level is None:
     25       indent_level = self._indent_level
     26     self._code.append(Line(((' ' * indent_level) + line).rstrip(),
     27                       substitute=substitute))
     28     return self
     29 
     30   def IsEmpty(self):
     31     """Returns True if the Code object is empty.
     32     """
     33     return not bool(self._code)
     34 
     35   def Concat(self, obj):
     36     """Concatenate another Code object onto this one. Trailing whitespace is
     37     stripped.
     38 
     39     Appends the code at the current indent level. Will fail if there are any
     40     un-interpolated format specifiers eg %s, %(something)s which helps
     41     isolate any strings that haven't been substituted.
     42     """
     43     if not isinstance(obj, Code):
     44       raise TypeError(type(obj))
     45     assert self is not obj
     46     for line in obj._code:
     47       try:
     48         # line % () will fail if any substitution tokens are left in line
     49         if line.substitute:
     50           line.value %= ()
     51       except TypeError:
     52         raise TypeError('Unsubstituted value when concatting\n' + line.value)
     53       except ValueError:
     54         raise ValueError('Stray % character when concatting\n' + line.value)
     55       self.Append(line.value, line.substitute)
     56 
     57     return self
     58 
     59   def Cblock(self, code):
     60     """Concatenates another Code object |code| onto this one followed by a
     61     blank line, if |code| is non-empty."""
     62     if not code.IsEmpty():
     63       self.Concat(code).Append()
     64     return self
     65 
     66   def Sblock(self, line=None):
     67     """Starts a code block.
     68 
     69     Appends a line of code and then increases the indent level.
     70     """
     71     if line is not None:
     72       self.Append(line)
     73     self._indent_level += self._indent_size
     74     return self
     75 
     76   def Eblock(self, line=None):
     77     """Ends a code block by decreasing and then appending a line (or a blank
     78     line if not given).
     79     """
     80     # TODO(calamity): Decide if type checking is necessary
     81     #if not isinstance(line, basestring):
     82     #  raise TypeError
     83     self._indent_level -= self._indent_size
     84     if line is not None:
     85       self.Append(line)
     86     return self
     87 
     88   def Comment(self, comment, comment_prefix='// '):
     89     """Adds the given string as a comment.
     90 
     91     Will split the comment if it's too long. Use mainly for variable length
     92     comments. Otherwise just use code.Append('// ...') for comments.
     93 
     94     Unaffected by code.Substitute().
     95     """
     96     max_len = self._comment_length - self._indent_level - len(comment_prefix)
     97     while len(comment) >= max_len:
     98       line = comment[0:max_len]
     99       last_space = line.rfind(' ')
    100       if last_space != -1:
    101         line = line[0:last_space]
    102         comment = comment[last_space + 1:]
    103       else:
    104         comment = comment[max_len:]
    105       self.Append(comment_prefix + line, substitute=False)
    106     self.Append(comment_prefix + comment, substitute=False)
    107     return self
    108 
    109   def Substitute(self, d):
    110     """Goes through each line and interpolates using the given dict.
    111 
    112     Raises type error if passed something that isn't a dict
    113 
    114     Use for long pieces of code using interpolation with the same variables
    115     repeatedly. This will reduce code and allow for named placeholders which
    116     are more clear.
    117     """
    118     if not isinstance(d, dict):
    119       raise TypeError('Passed argument is not a dictionary: ' + d)
    120     for i, line in enumerate(self._code):
    121       if self._code[i].substitute:
    122         # Only need to check %s because arg is a dict and python will allow
    123         # '%s %(named)s' but just about nothing else
    124         if '%s' in self._code[i].value or '%r' in self._code[i].value:
    125           raise TypeError('"%s" or "%r" found in substitution. '
    126                           'Named arguments only. Use "%" to escape')
    127         self._code[i].value = line.value % d
    128         self._code[i].substitute = False
    129     return self
    130 
    131   def Render(self):
    132     """Renders Code as a string.
    133     """
    134     return '\n'.join([l.value for l in self._code])
    135 
    136 class Line(object):
    137   """A line of code.
    138   """
    139   def __init__(self, value, substitute=True):
    140     self.value = value
    141     self.substitute = substitute
    142