@@ -19,6 +19,9 @@ class to be recognized as such.
1919 ...
2020
2121 2. Use the family of emit*() functions to write to the output file.
22+
23+ The api attribute holds all information parsed from the specs, and should
24+ be used during generation.
2225 """
2326
2427 __metaclass__ = ABCMeta
@@ -39,8 +42,10 @@ def __init__(self, api, target_folder_path):
3942
4043 @abstractmethod
4144 def generate (self ):
42- """Subclasses should override this method. It's the entry point for
43- all code generation given the api description."""
45+ """
46+ Subclasses should override this method. It's the entry point that is
47+ invoked by the rest of the toolchain.
48+ """
4449 raise NotImplemented
4550
4651 @contextmanager
@@ -49,7 +54,7 @@ def output_to_relative_path(self, relative_path):
4954 Sets up generator so that all emits are directed towards the new file
5055 created at :param:`relative_path`.
5156
52- Clears output buffer on enter, and on exit.
57+ Clears the output buffer on enter and exit.
5358 """
5459 full_path = os .path .join (self .target_folder_path , relative_path )
5560 self ._logger .info ('Generating %s' , full_path )
@@ -59,14 +64,22 @@ def output_to_relative_path(self, relative_path):
5964 f .write ('' .join (self .output ))
6065 self .output = []
6166
67+ def output_buffer_to_string (self ):
68+ """Returns the contents of the output buffer as a string."""
69+ return '' .join (self .output )
70+
71+ def clear_output_buffer (self ):
72+ self .output = []
73+
6274 @contextmanager
6375 def indent (self , dent = None ):
6476 """
6577 For the duration of the context manager, indentation will be increased
6678 by dent. Dent is in units of spaces or tabs depending on the value of
67- the class variable tabs_for_indents.
79+ the class variable tabs_for_indents. If dent is None, indentation will
80+ increase by either four spaces or one tab.
6881 """
69- assert dent != 0 , 'Cannot specify relative indent of 0 '
82+ assert dent is None or dent > 0 , 'dent must be a whole number. '
7083 if dent is None :
7184 if self .tabs_for_indents :
7285 dent = 1
@@ -76,100 +89,83 @@ def indent(self, dent=None):
7689 yield
7790 self .cur_indent -= dent
7891
79- @contextmanager
80- def block (self , header = '' , dent = None , delim = ('{' ,'}' )):
81- if header :
82- self .emit_line ('{} {}' .format (header , delim [0 ]))
83- else :
84- self .emit_line (delim [0 ])
85-
86- with self .indent (dent ):
87- yield
88-
89- self .emit_line (delim [1 ])
90-
91- @contextmanager
92- def indent_to_cur_col (self ):
92+ def make_indent (self ):
9393 """
94- For the duration of the context manager, indentation will be set to the
95- current column marked by the "cursor". The cursor is what column of the
96- current line the next emit call would begin writing at .
94+ Returns a string representing the current indentation. Indents can be
95+ either spaces or tabs, depending on the value of the class variable
96+ tabs_for_indents .
9797 """
98- dent = 0
99- for s in self .output [::- 1 ]:
100- index = s .rfind ('\n ' )
101- if index == - 1 :
102- dent += len (s )
103- else :
104- dent += len (s ) - index - 1
105- break
106- dent_diff = dent - self .cur_indent
107- self .cur_indent += dent_diff
108- yield
109- self .cur_indent -= dent_diff
110-
111- def make_indent (self ):
112- """Returns a string representing an indent. Indents can be either
113- spaces or tabs, depending on the value of the class variable
114- tabs_for_indents."""
11598 if self .tabs_for_indents :
11699 return '\t ' * self .cur_indent
117100 else :
118101 return ' ' * self .cur_indent
119102
120- def emit (self , s ):
121- """Adds the input string to the output buffer."""
103+ def emit_raw (self , s ):
104+ """
105+ Adds the input string to the output buffer. The string must end in a
106+ newline. It may contain any number of newline characters. No
107+ indentation is generated.
108+ """
122109 self .lineno += s .count ('\n ' )
123110 self .output .append (s )
111+ if len (s ) > 0 and s [- 1 ] != '\n ' :
112+ raise AssertionError (
113+ 'Input string to emit_raw must end with a newline.' )
124114
125- def emit_indent (self ):
126- """Adds an indent into the output buffer."""
127- self .emit (self .make_indent ())
128-
129- def emit_line (self , s , trailing_newline = True ):
130- """Adds an indent, then the input string, and lastly a newline to the
131- output buffer. If you want the input string to potentially span across
132- multiple lines, see :func:`emit_string_wrap`."""
133- self .emit_indent ()
134- self .emit (s )
135- if trailing_newline :
136- self .emit ('\n ' )
137-
138- def emit_empty_line (self ):
139- """Adds a newline to the output buffer."""
140- self .emit ('\n ' )
115+ def emit (self , s = '' ):
116+ """
117+ Adds indentation, then the input string, and lastly a newline to the
118+ output buffer. If s is an empty string (default) then an empty line is
119+ created with no indentation.
120+ """
121+ assert isinstance (s , basestring ), 's must be a string type'
122+ assert '\n ' not in s , \
123+ 'String to emit cannot contain newline strings.'
124+ if s :
125+ self .emit_raw ('%s%s\n ' % (self .make_indent (), s ))
126+ else :
127+ self .emit_raw ('\n ' )
141128
142- def emit_wrapped_lines (self , s , prefix = '' , width = 80 , trailing_newline = True , first_line_prefix = True ):
129+ def emit_wrapped_text (self , s , initial_prefix = '' , subsequent_prefix = '' ,
130+ width = 80 , break_long_words = False , break_on_hyphens = False ):
143131 """
144- Adds the input string to the output buffer with wrapping.
132+ Adds the input string to the output buffer with indentation and
133+ wrapping. The wrapping is performed by the :func:`textwrap.fill` Python
134+ library function.
145135
146136 Args:
147- s: The input string to wrap.
148- prefix: The string to prepend to every line of the wrapped string.
149- Does not include indenting in the prefix as those are injected
150- automatically on every line.
151- width: The target width of each line including indentation and text.
152- """
153- indent = self .make_indent () + prefix
154- if first_line_prefix :
155- initial_indent = indent
156- else :
157- initial_indent = self .make_indent ()
158-
159- self .emit (textwrap .fill (s ,
160- initial_indent = initial_indent ,
161- subsequent_indent = indent ,
162- width = 80 ))
163- if trailing_newline :
164- self .emit ('\n ' )
137+ s (str): The input string to wrap.
138+ initial_prefix (str): The string to prepend to the first line of
139+ the wrapped string. Note that the current indentation is
140+ already added to each line.
141+ subsequent_prefix (str): The string to prepend to every line after
142+ the first. Note that the current indentation is already added
143+ to each line.
144+ width (int): The target width of each line including indentation
145+ and text.
146+ break_long_words (bool): Break words longer than width. If false,
147+ those words will not be broken, and some lines might be longer
148+ than width.
149+ break_on_hyphens (bool): Allow breaking hyphenated words. If true,
150+ wrapping will occur preferably on whitespaces and right after
151+ hyphens part of compound words.
152+ """
153+ indent = self .make_indent ()
154+ self .emit_raw (textwrap .fill (s ,
155+ initial_indent = indent + initial_prefix ,
156+ subsequent_indent = indent + subsequent_prefix ,
157+ width = width ,
158+ break_long_words = break_long_words ,
159+ break_on_hyphens = break_on_hyphens ,
160+ ) + '\n ' )
165161
166162class CodeGenerator (Generator ):
167163 """
168164 Extend this instead of :class:`Generator` when generating source code.
169165 Contains helper functions specific to code generation.
170166 """
171167
172- def _filter_out_none_valued_keys (self , d ):
168+ def filter_out_none_valued_keys (self , d ):
173169 """Given a dict, returns a new dict with all the same key/values except
174170 for keys that had values of None."""
175171 new_d = {}
@@ -178,45 +174,101 @@ def _filter_out_none_valued_keys(self, d):
178174 new_d [k ] = v
179175 return new_d
180176
181- def _generate_func_arg_list (self , args , compact = True ):
177+ def generate_multiline_list (self , items , before = '' , after = '' ,
178+ delim = ('(' , ')' ), compact = True , sep = ',' , skip_last_sep = False ):
182179 """
183- Given a list of arguments to a function, emits the args, one per line
184- with a trailing comma. The arguments are enclosed in parentheses making
185- this convenient way to create argument lists in function prototypes and
186- calls.
180+ Given a list of items, emits one item per line.
181+
182+ This is convenient for function prototypes and invocations, as well as
183+ for instantiating arrays, sets, and maps in some languages.
184+
185+ TODO(kelkabany): A generator that uses tabs cannot be used with this
186+ if compact is false.
187187
188188 Args:
189- args: List of strings where each string is an argument.
190- compact: In compact mode, the enclosing parentheses are on the same
191- lines as the first and last argument.
189+ items (list[str]): Should contain the items to generate a list of.
190+ before (str): The string to come before the list of items.
191+ after (str): The string to follow the list of items.
192+ delim (str, str): The first element is added immediately following
193+ `before`. The second element is added prior to `after`.
194+ compact (bool): In compact mode, the enclosing parentheses are on
195+ the same lines as the first and last list item.
196+ sep (str): The string that follows each list item when compact is
197+ true. If compact is false, the separator is omitted for the
198+ last item.
199+ skip_last_sep (bool): When compact is false, whether the last line
200+ should have a trailing separator. Ignored when compact is true.
192201 """
193- self .emit ('(' )
194- if len (args ) == 0 :
195- self .emit (')' )
202+ assert len (delim ) == 2 and isinstance (delim [0 ], str ) and \
203+ isinstance (delim [1 ], str ), 'delim must be a tuple of two strings.'
204+
205+ if len (items ) == 0 :
206+ self .emit (before + delim [0 ] + delim [1 ] + after )
196207 return
197- elif len (args ) == 1 :
198- self .emit (args [0 ])
199- self . emit ( ')' )
200- else :
201- if compact :
202- with self . indent_to_cur_col ():
203- args = args [:]
204- self . emit ( args . pop ( 0 ))
205- self . emit ( ',' )
206- self . emit_empty_line ()
207- for ( i , arg ) in enumerate ( args ):
208- if i == len ( args ) - 1 :
209- self .emit_line ( arg , trailing_newline = False )
210- else :
211- self .emit_line ( arg + ',' )
212- self . emit ( ')' )
208+ if len (items ) == 1 :
209+ self .emit (before + delim [0 ] + items [ 0 ] + delim [ 1 ] + after )
210+ return
211+
212+ if compact :
213+ self . emit ( before + delim [ 0 ] + items [ 0 ] + sep )
214+ def emit_list ( items ):
215+ items = items [ 1 :]
216+ for ( i , item ) in enumerate ( items ):
217+ if i == len ( items ) - 1 :
218+ self . emit ( item + delim [ 1 ] + after )
219+ else :
220+ self .emit ( item + sep )
221+ if before or delim [ 0 ] :
222+ with self .indent ( len ( before ) + len ( delim [ 0 ])):
223+ emit_list ( items )
213224 else :
214- self .emit_empty_line ()
215- with self .indent ():
216- for arg in args :
217- self .emit_line (arg + ',' )
218- self .emit_indent ()
219- self .emit (')' )
225+ emit_list (items )
226+ else :
227+ if before or delim [0 ]:
228+ self .emit (before + delim [0 ])
229+ with self .indent ():
230+ for (i , item ) in enumerate (items ):
231+ if i == len (items ) - 1 and skip_last_sep :
232+ self .emit (item )
233+ else :
234+ self .emit (item + sep )
235+ if delim [1 ] or after :
236+ self .emit (delim [1 ] + after )
237+ elif delim [1 ]:
238+ self .emit (delim [1 ])
239+
240+ @contextmanager
241+ def block (self , before = '' , after = '' , delim = ('{' ,'}' ), dent = None ):
242+ """
243+ A context manager that emits configurable lines before and after an
244+ indented block of text.
245+
246+ This is convenient for class and function definitions in some
247+ languages.
248+
249+ Args:
250+ before (str): The string to be output in the first line which is
251+ not indented..
252+ after (str): The string to be output in the last line which is
253+ not indented.
254+ delim (str, str): The first element is added immediately following
255+ `before` and a space. The second element is added prior to a
256+ space and then `after`.
257+ dent (int): The amount to indent the block. If none, the default
258+ indentation increment is used (four spaces or one tab).
259+ """
260+ assert len (delim ) == 2 and isinstance (delim [0 ], str ) and \
261+ isinstance (delim [1 ], str ), 'delim must be a tuple of two strings.'
262+
263+ if before :
264+ self .emit ('{} {}' .format (before , delim [0 ]))
265+ else :
266+ self .emit (delim [0 ])
267+
268+ with self .indent (dent ):
269+ yield
270+
271+ self .emit (delim [1 ] + after )
220272
221273class CodeGeneratorMonolingual (CodeGenerator ):
222274 """Identical to CodeGenerator, except that an additional attribute `lang`
0 commit comments