Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(516)

Side by Side Diff: recipe_engine/doc.py

Issue 2856003002: [doc] fix doc to work in all known repos. (Closed)
Patch Set: add comments Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « recipe_engine/config.py ('k') | recipe_engine/recipe_api.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2013 The LUCI Authors. All rights reserved. 2 # Copyright 2013 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file. 4 # that can be found in the LICENSE file.
5 5
6 from __future__ import print_function, absolute_import 6 from __future__ import print_function, absolute_import
7 7
8 import ast 8 import ast
9 import inspect 9 import inspect
10 import json 10 import json
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
66 astunparse.Unparser(node, buf) 66 astunparse.Unparser(node, buf)
67 return buf.getvalue() 67 return buf.getvalue()
68 68
69 69
70 def _find_value_of(mod_ast, target): 70 def _find_value_of(mod_ast, target):
71 """Looks for an assignment to `target`, returning the assignment value ast 71 """Looks for an assignment to `target`, returning the assignment value ast
72 node and the line number of the assignment. 72 node and the line number of the assignment.
73 """ 73 """
74 for node in mod_ast.body: 74 for node in mod_ast.body:
75 if isinstance(node, ast.Assign): 75 if isinstance(node, ast.Assign):
76 if len(node.targets) == 1 and node.targets[0].id == target: 76 if (len(node.targets) == 1 and
77 isinstance(node.targets[0], ast.Name) and
78 node.targets[0].id == target):
77 return node.value, node.lineno 79 return node.value, node.lineno
78 return None, None 80 return None, None
79 81
80 82
81 def _expand_mock_imports(*mock_imports): 83 def _expand_mock_imports(*mock_imports):
82 """Returns an expanded set of mock imports. 84 """Returns an expanded set of mock imports.
83 85
84 mock_imports is expected to be a dict which looks like: 86 mock_imports is expected to be a dict which looks like:
85 { 87 {
86 "absolute.module.name": SomeObject, 88 "absolute.module.name": SomeObject,
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
119 expanded_imports[dotted_name+'.'+name] = getattr(obj, name) 121 expanded_imports[dotted_name+'.'+name] = getattr(obj, name)
120 for i in range(len(toks)-1, 0, -1): 122 for i in range(len(toks)-1, 0, -1):
121 partial = '.'.join(toks[:i]) 123 partial = '.'.join(toks[:i])
122 cur_obj = expanded_imports.setdefault(partial, expando()) 124 cur_obj = expanded_imports.setdefault(partial, expando())
123 if not isinstance(cur_obj, expando): 125 if not isinstance(cur_obj, expando):
124 raise ValueError('nested mock imports! %r', partial) 126 raise ValueError('nested mock imports! %r', partial)
125 setattr(cur_obj, toks[i], expanded_imports[partial+'.'+toks[i]]) 127 setattr(cur_obj, toks[i], expanded_imports[partial+'.'+toks[i]])
126 128
127 return expanded_imports 129 return expanded_imports
128 130
131 ALL_IMPORTS = {} # used in doc_test to ensure everything is actually importable
132 KNOWN_OBJECTS = {}
129 133
130 _decorator_imports = { 134 _decorator_imports = {
131 'recipe_engine.util.returns_placeholder': util.returns_placeholder, 135 'recipe_engine.util.returns_placeholder': util.returns_placeholder,
132 'recipe_engine.recipe_api.non_step': recipe_api.non_step, 136 'recipe_engine.recipe_api.non_step': recipe_api.non_step,
133 'recipe_engine.recipe_api.infer_composite_step': ( 137 'recipe_engine.recipe_api.infer_composite_step': (
134 recipe_api.infer_composite_step) 138 recipe_api.infer_composite_step)
135 } 139 }
140 KNOWN_OBJECTS.update(_decorator_imports)
136 141
137 _config_imports = { 142 _config_imports = {
138 'recipe_engine.config.ConfigGroup': config.ConfigGroup, 143 'recipe_engine.config.ConfigGroup': config.ConfigGroup,
139 'recipe_engine.config.ConfigList': config.ConfigList, 144 'recipe_engine.config.ConfigList': config.ConfigList,
140 'recipe_engine.config.Set': config.Set, 145 'recipe_engine.config.Set': config.Set,
141 'recipe_engine.config.Dict': config.Dict, 146 'recipe_engine.config.Dict': config.Dict,
142 'recipe_engine.config.List': config.List, 147 'recipe_engine.config.List': config.List,
143 'recipe_engine.config.Single': config.Single, 148 'recipe_engine.config.Single': config.Single,
144 'recipe_engine.config.Static': config.Static, 149 'recipe_engine.config.Static': config.Static,
145 'recipe_engine.config.Enum': config.Enum, 150 'recipe_engine.config.Enum': config.Enum,
146 } 151 }
152 KNOWN_OBJECTS.update(_config_imports)
147 153
148 _placeholder_imports = { 154 _placeholder_imports = {
149 'recipe_engine.util.OutputPlaceholder': util.OutputPlaceholder, 155 'recipe_engine.util.OutputPlaceholder': util.OutputPlaceholder,
150 'recipe_engine.util.InputPlaceholder': util.InputPlaceholder, 156 'recipe_engine.util.InputPlaceholder': util.InputPlaceholder,
151 'recipe_engine.util.Placeholder': util.Placeholder, 157 'recipe_engine.util.Placeholder': util.Placeholder,
152 } 158 }
159 KNOWN_OBJECTS.update(_placeholder_imports)
153 160
154 MOCK_IMPORTS_PARAMETERS = _expand_mock_imports({ 161 _property_imports = {
155 'recipe_engine.recipe_api.Property': recipe_api.Property, 162 'recipe_engine.recipe_api.Property': recipe_api.Property,
156 }, _config_imports) 163 }
164 KNOWN_OBJECTS.update(_property_imports)
157 165
158 MOCK_IMPORTS_RETURN_SCHEMA = _expand_mock_imports({ 166 _return_schema_imports = {
159 'recipe_engine.config.ReturnSchema': config.ReturnSchema, 167 'recipe_engine.config.ReturnSchema': config.ReturnSchema,
160 'recipe_engine.config.ConfigGroupSchema': config.ConfigGroupSchema, 168 'recipe_engine.config.ConfigGroupSchema': config.ConfigGroupSchema,
161 }, _config_imports) 169 }
170 KNOWN_OBJECTS.update(_return_schema_imports)
162 171
163 MOCK_IMPORTS_RECIPE = _expand_mock_imports({ 172 _util_imports = {
164 'recipe_engine.types.freeze': types.freeze, 173 'recipe_engine.types.freeze': types.freeze,
165 }, _decorator_imports, _placeholder_imports) 174 }
175 KNOWN_OBJECTS.update(_util_imports)
166 176
167 MOCK_IMPORTS_MODULE = _expand_mock_imports({ 177 _recipe_api_class_imports = {
168 'recipe_engine.recipe_api.RecipeApi': recipe_api.RecipeApi, 178 'recipe_engine.recipe_api.RecipeApi': recipe_api.RecipeApi,
169 'recipe_engine.recipe_api.RecipeApiPlain': recipe_api.RecipeApiPlain, 179 'recipe_engine.recipe_api.RecipeApiPlain': recipe_api.RecipeApiPlain,
170 }, _decorator_imports, _placeholder_imports) 180 }
171 181 KNOWN_OBJECTS.update(_recipe_api_class_imports)
172 ALL_IMPORTS = {}
173 ALL_IMPORTS.update(MOCK_IMPORTS_PARAMETERS)
174 ALL_IMPORTS.update(MOCK_IMPORTS_RETURN_SCHEMA)
175 ALL_IMPORTS.update(MOCK_IMPORTS_RECIPE)
176 ALL_IMPORTS.update(MOCK_IMPORTS_MODULE)
177 182
178 183
179 def _parse_mock_imports(mod_ast, expanded_imports): 184 def _parse_mock_imports(mod_ast, expanded_imports):
180 """Parses a module ast node for import statements and resolves them against 185 """Parses a module ast node for import statements and resolves them against
181 expanded_imports (such as you might get from _expand_mock_imports). 186 expanded_imports (such as you might get from _expand_mock_imports).
182 187
183 If an import is not recognized, it is omitted from the returned dictionary. 188 If an import is not recognized, it is omitted from the returned dictionary.
184 189
185 Returns a dictionary suitable for eval'ing a statement in mod_ast, with 190 Returns a dictionary suitable for eval'ing a statement in mod_ast, with
186 symbols from mod_ast's imports resolved to real objects, as per 191 symbols from mod_ast's imports resolved to real objects, as per
(...skipping 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 relpath=relpath, 275 relpath=relpath,
271 lineno=lineno, 276 lineno=lineno,
272 ) 277 )
273 spec = uv.normalize_deps_spec(ast.literal_eval(_unparse(DEPS))) 278 spec = uv.normalize_deps_spec(ast.literal_eval(_unparse(DEPS)))
274 for pkg, mod_name in sorted(spec.itervalues()): 279 for pkg, mod_name in sorted(spec.itervalues()):
275 ret.module_links.add(package=pkg.name, name=mod_name) 280 ret.module_links.add(package=pkg.name, name=mod_name)
276 281
277 return ret 282 return ret
278 283
279 284
285 def extract_jsonish_assignments(mod_ast):
286 """This extracts all single assignments where the target is a name, and the
287 value is a simple 'jsonish' statement (aka python literal).
288
289 The result is returned as a dictionary of name to the decoded literal.
290
291 Example:
292 Foo = "hello"
293 Bar = [1, 2, "something"]
294 Other, Things = range(2) # not single assignment
295 Bogus = object() # not a python literal
296 # returns: {"Foo": "hello", "Bar": [1, 2, "something"]}
297 """
298 ret = {}
299 for node in mod_ast.body:
300 if not isinstance(node, ast.Assign):
301 continue
302 if len(node.targets) != 1:
303 continue
304 if not isinstance(node.targets[0], ast.Name):
305 continue
306 try:
307 ret[node.targets[0].id] = ast.literal_eval(node.value)
308 except (KeyError, ValueError):
309 pass
310 return ret
311
312
280 def parse_parameter(param): 313 def parse_parameter(param):
281 assert isinstance(param, recipe_api.Property), type(param) 314 assert isinstance(param, recipe_api.Property), type(param)
282 default = None 315 default = None
283 if param._default is not recipe_api.PROPERTY_SENTINEL: 316 if param._default is not recipe_api.PROPERTY_SENTINEL:
284 default = json.dumps(param._default) 317 default = json.dumps(param._default)
285 318
286 return doc.Doc.Parameter( 319 return doc.Doc.Parameter(
287 docstring=param.help, 320 docstring=param.help,
288 kind=param.kind.schema_proto() if param.kind else None, 321 kind=param.kind.schema_proto() if param.kind else None,
289 default_json=default) 322 default_json=default)
290 323
291 324
325 MOCK_IMPORTS_PARAMETERS = _expand_mock_imports(
326 _property_imports, _config_imports)
327 ALL_IMPORTS.update(MOCK_IMPORTS_PARAMETERS)
328
329
292 def parse_parameters(mod_ast, relpath): 330 def parse_parameters(mod_ast, relpath):
293 parameters, lineno = _find_value_of(mod_ast, 'PROPERTIES') 331 parameters, lineno = _find_value_of(mod_ast, 'PROPERTIES')
294 if not parameters: 332 if not parameters:
295 return None 333 return None
296 334
297 imports = _parse_mock_imports(mod_ast, MOCK_IMPORTS_PARAMETERS) 335 imports = _parse_mock_imports(mod_ast, MOCK_IMPORTS_PARAMETERS)
336 imports.update(extract_jsonish_assignments(mod_ast))
298 data = eval(_unparse(parameters), imports) 337 data = eval(_unparse(parameters), imports)
299 if not data: 338 if not data:
300 return None 339 return None
301 340
302 for k, v in sorted(data.iteritems()): 341 for k, v in sorted(data.iteritems()):
303 data[k] = parse_parameter(v) 342 data[k] = parse_parameter(v)
304 343
305 return doc.Doc.Parameters(relpath=relpath, lineno=lineno, parameters=data) 344 return doc.Doc.Parameters(relpath=relpath, lineno=lineno, parameters=data)
306 345
307 346
308 def parse_func(func_node, relpath, imports): 347 def parse_func(func_node, relpath, imports):
309 ret = doc.Doc.Func( 348 ret = doc.Doc.Func(
310 name=func_node.name, 349 name=func_node.name,
311 relpath=relpath, 350 relpath=relpath,
312 lineno=func_node.lineno, 351 lineno=func_node.lineno,
313 docstring=ast.get_docstring(func_node) or '', 352 docstring=ast.get_docstring(func_node) or '',
314 ) 353 )
315 354
316 for exp in func_node.decorator_list: 355 for exp in func_node.decorator_list:
317 item = _apply_imports_to_unparsed_expression(exp, imports) 356 item = _apply_imports_to_unparsed_expression(exp, imports)
318 if isinstance(item, str): 357 if isinstance(item, str):
319 ret.decorators.add(generic=item) 358 ret.decorators.add(generic=item)
320 else: 359 else:
321 ret.decorators.add(known=item.__module__+'.'+item.__name__) 360 ret.decorators.add(known=item.__module__+'.'+item.__name__)
322 361
323 ret.signature = _unparse(func_node.args).strip() 362 ret.signature = _unparse(func_node.args).strip()
324 return ret 363 return ret
325 364
326 365
366 MOCK_IMPORTS_RETURN_SCHEMA = _expand_mock_imports(
367 _return_schema_imports, _config_imports)
368 ALL_IMPORTS.update(MOCK_IMPORTS_RETURN_SCHEMA)
369
370
327 def parse_return_schema(mod_ast, relpath): 371 def parse_return_schema(mod_ast, relpath):
328 imports = _parse_mock_imports(mod_ast, MOCK_IMPORTS_RETURN_SCHEMA) 372 imports = _parse_mock_imports(mod_ast, MOCK_IMPORTS_RETURN_SCHEMA)
329 schema, lineno = _find_value_of(mod_ast, 'RETURN_SCHEMA') 373 schema, lineno = _find_value_of(mod_ast, 'RETURN_SCHEMA')
330 if not schema: 374 if not schema:
331 return None 375 return None
332 schema = eval(_unparse(schema), imports) 376 schema = eval(_unparse(schema), imports)
333 if not schema: 377 if not schema:
334 return None 378 return None
335 return doc.Doc.ReturnSchema(relpath=relpath, lineno=lineno, 379 return doc.Doc.ReturnSchema(relpath=relpath, lineno=lineno,
336 schema=schema.schema_proto()) 380 schema=schema.schema_proto())
337 381
338 382
383 MOCK_IMPORTS_RECIPE = _expand_mock_imports(
384 _util_imports, _decorator_imports, _placeholder_imports)
385 ALL_IMPORTS.update(MOCK_IMPORTS_RECIPE)
386
387
339 def parse_recipe(uv, base_dir, relpath, recipe_name): 388 def parse_recipe(uv, base_dir, relpath, recipe_name):
340 recipe = _grab_ast(base_dir, relpath) 389 recipe = _grab_ast(base_dir, relpath)
341 if not recipe: 390 if not recipe:
342 return None 391 return None
343 classes, funcs = _extract_classes_funcs(recipe, relpath, MOCK_IMPORTS_RECIPE) 392 classes, funcs = _extract_classes_funcs(recipe, relpath, MOCK_IMPORTS_RECIPE)
344 funcs.pop('GenTests', None) 393 funcs.pop('GenTests', None)
345 394
346 # TODO(iannucci): parse RequireClients 395 # TODO(iannucci): parse RequireClients
347 396
348 return doc.Doc.Recipe( 397 return doc.Doc.Recipe(
349 name=recipe_name, 398 name=recipe_name,
350 relpath=relpath, 399 relpath=relpath,
351 docstring=ast.get_docstring(recipe) or '', 400 docstring=ast.get_docstring(recipe) or '',
352 deps=parse_deps(uv, recipe, relpath), 401 deps=parse_deps(uv, recipe, relpath),
353 parameters=parse_parameters(recipe, relpath), 402 parameters=parse_parameters(recipe, relpath),
354 return_schema=parse_return_schema(recipe, relpath), 403 return_schema=parse_return_schema(recipe, relpath),
355 classes=classes, 404 classes=classes,
356 funcs=funcs, 405 funcs=funcs,
357 ) 406 )
358 407
359 408
409 MOCK_IMPORTS_MODULE = _expand_mock_imports(
410 _recipe_api_class_imports, _decorator_imports, _placeholder_imports)
411 ALL_IMPORTS.update(MOCK_IMPORTS_MODULE)
412
413
360 def parse_module(uv, base_dir, relpath, mod_name): 414 def parse_module(uv, base_dir, relpath, mod_name):
361 native_relpath = _to_native(relpath) 415 native_relpath = _to_native(relpath)
362 416
363 api_relpath = relpath + '/api.py' 417 api_relpath = relpath + '/api.py'
364 api = _grab_ast(base_dir, _to_native(api_relpath)) 418 api = _grab_ast(base_dir, _to_native(api_relpath))
365 if not api: 419 if not api:
366 return None 420 return None
367 421
368 init_relpath = os.path.join(native_relpath, '__init__.py') 422 init_relpath = os.path.join(native_relpath, '__init__.py')
369 init = _grab_ast(base_dir, init_relpath) 423 init = _grab_ast(base_dir, init_relpath)
370 if not init: 424 if not init:
371 return None 425 return None
372 426
373 imports = _parse_mock_imports(api, MOCK_IMPORTS_MODULE) 427 imports = _parse_mock_imports(api, MOCK_IMPORTS_MODULE)
374 classes, funcs = _extract_classes_funcs(api, api_relpath, imports) 428 classes, funcs = _extract_classes_funcs(api, api_relpath, imports)
375 429
376 api_class = None 430 api_class = None
377 for name, val in sorted(classes.iteritems()): 431 for name, val in sorted(classes.iteritems()):
378 if any(b.known in KNOWN_RECIPE_API_BASES for b in val.bases): 432 if any(b.known in _recipe_api_class_imports for b in val.bases):
379 api_class = classes.pop(name) 433 api_class = classes.pop(name)
380 break 434 break
381 if not api_class: 435 if not api_class:
382 LOGGER.error('could not determine main RecipeApi class: %r', relpath) 436 LOGGER.error('could not determine main RecipeApi class: %r', relpath)
383 return None 437 return None
384 438
385 # TODO(iannucci): bundle_extra_paths.txt 439 # TODO(iannucci): bundle_extra_paths.txt
386 440
387 return doc.Doc.Module( 441 return doc.Doc.Module(
388 name=mod_name, 442 name=mod_name,
(...skipping 24 matching lines...) Expand all
413 467
414 for recipe_path, recipe_name in uv.loop_over_recipes(): 468 for recipe_path, recipe_name in uv.loop_over_recipes():
415 relpath = posixpath.relpath(recipe_path, base_dir) 469 relpath = posixpath.relpath(recipe_path, base_dir)
416 recipe = parse_recipe(uv, base_dir, relpath, recipe_name) 470 recipe = parse_recipe(uv, base_dir, relpath, recipe_name)
417 if recipe: 471 if recipe:
418 ret.recipes[recipe_name].CopyFrom(recipe) 472 ret.recipes[recipe_name].CopyFrom(recipe)
419 473
420 return ret 474 return ret
421 475
422 476
423 KNOWN_RECIPE_API_BASES = {
424 'recipe_engine.recipe_api.RecipeApi': recipe_api.RecipeApi,
425 'recipe_engine.recipe_api.RecipeApiPlain': recipe_api.RecipeApiPlain,
426 }
427
428
429 KNOWN_OBJECTS = {
430 'recipe_engine.recipe_api.non_step': recipe_api.non_step,
431 'recipe_engine.recipe_api.infer_composite_step': (
432 recipe_api.infer_composite_step),
433
434 'recipe_engine.util.returns_placeholder': util.returns_placeholder,
435 }
436 KNOWN_OBJECTS.update(KNOWN_RECIPE_API_BASES)
437
438
439 RECIPE_ENGINE_URL = 'https://github.com/luci/recipes-py' 477 RECIPE_ENGINE_URL = 'https://github.com/luci/recipes-py'
440 478
441 479
442 def _set_known_objects(base): 480 def _set_known_objects(base):
443 source_cache = {} 481 source_cache = {}
444 482
483 def _add_it(key, fname, target):
484 relpath = os.path.relpath(fname, RECIPE_ENGINE_BASE)
485 for node in source_cache[fname].body:
486 if isinstance(node, ast.ClassDef) and node.name == target:
487 # This is a class definition in the form of:
488 # def Target(...)
489 base.known_objects[key].klass.CopyFrom(parse_class(node, relpath, {}))
490 return
491 elif isinstance(node, ast.FunctionDef) and node.name == target:
492 # This is a function definition in the form of:
493 # def Target(...)
494 base.known_objects[key].func.CopyFrom(parse_func(node, relpath, {}))
495 return
496 elif isinstance(node, ast.Assign) and node.targets[0].id == target:
497 # This is an alias in the form of:
498 # Target = RealImplementation
499 _add_it(key, fname, node.value.id)
500 return
501
502 raise ValueError('could not find %r in %r' % (key, relpath))
503
445 for k, v in KNOWN_OBJECTS.iteritems(): 504 for k, v in KNOWN_OBJECTS.iteritems():
446 base.known_objects[k].url = RECIPE_ENGINE_URL 505 base.known_objects[k].url = RECIPE_ENGINE_URL
447 _, target = k.rsplit('.', 1) 506 _, target = k.rsplit('.', 1)
448 fname = inspect.getsourcefile(v) 507 fname = inspect.getsourcefile(v)
449 if fname not in source_cache: 508 if fname not in source_cache:
450 # we load and cache the whole source file so that ast.parse gets the right 509 # we load and cache the whole source file so that ast.parse gets the right
451 # line numbers for all the definitions. 510 # line numbers for all the definitions.
452 source_lines, _ = inspect.findsource(v) 511 source_lines, _ = inspect.findsource(v)
453 source_cache[fname] = ast.parse(''.join(source_lines), fname) 512 source_cache[fname] = ast.parse(''.join(source_lines), fname)
454 513
455 relpath = os.path.relpath(fname, RECIPE_ENGINE_BASE) 514 _add_it(k, fname, target)
456 for node in source_cache[fname].body:
457 if isinstance(node, ast.ClassDef) and node.name == target:
458 base.known_objects[k].klass.CopyFrom(parse_class(node, relpath, {}))
459 break
460 elif isinstance(node, ast.FunctionDef) and node.name == target:
461 base.known_objects[k].func.CopyFrom(parse_func(node, relpath, {}))
462 break
463 else:
464 raise ValueError('could not find %r in %r' % (k, relpath))
465 515
466 516
467 def add_subparser(parser): 517 def add_subparser(parser):
468 doc_kinds=('binarypb', 'jsonpb', 'textpb', 'markdown(github)', 518 doc_kinds=('binarypb', 'jsonpb', 'textpb', 'markdown(github)',
469 'markdown(gitiles)') 519 'markdown(gitiles)')
470 helpstr = ( 520 helpstr = (
471 'List all known modules reachable from the current package, with their ' 521 'List all known modules reachable from the current package, with their '
472 'documentation.' 522 'documentation.'
473 ) 523 )
474 doc_p = parser.add_parser( 524 doc_p = parser.add_parser(
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
508 elif args.kind == 'binarypb': 558 elif args.kind == 'binarypb':
509 sys.stdout.write(node.SerializeToString()) 559 sys.stdout.write(node.SerializeToString())
510 elif args.kind == 'textpb': 560 elif args.kind == 'textpb':
511 sys.stdout.write(textpb.MessageToString(node)) 561 sys.stdout.write(textpb.MessageToString(node))
512 elif args.kind == 'markdown-github': 562 elif args.kind == 'markdown-github':
513 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITHUB), node) 563 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITHUB), node)
514 elif args.kind == 'markdown-gitiles': 564 elif args.kind == 'markdown-gitiles':
515 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITILES), node) 565 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITILES), node)
516 else: 566 else:
517 raise NotImplementedError('--kind=%s' % args.kind) 567 raise NotImplementedError('--kind=%s' % args.kind)
OLDNEW
« no previous file with comments | « recipe_engine/config.py ('k') | recipe_engine/recipe_api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698