Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 Loading... | |
| 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 base.known_objects[key].klass.CopyFrom(parse_class(node, relpath, {})) | |
| 488 return | |
| 489 elif isinstance(node, ast.FunctionDef) and node.name == target: | |
| 490 base.known_objects[key].func.CopyFrom(parse_func(node, relpath, {})) | |
| 491 return | |
| 492 elif isinstance(node, ast.Assign) and node.targets[0].id == target: | |
| 493 # This is an alias in the form of: | |
| 494 # Target = RealImplementation | |
|
dnj
2017/05/03 00:25:31
Actually this was real nice. It took a few minutes
iannucci
2017/05/03 01:09:30
Done.
| |
| 495 _add_it(key, fname, node.value.id) | |
| 496 return | |
| 497 | |
| 498 raise ValueError('could not find %r in %r' % (key, relpath)) | |
| 499 | |
| 445 for k, v in KNOWN_OBJECTS.iteritems(): | 500 for k, v in KNOWN_OBJECTS.iteritems(): |
| 446 base.known_objects[k].url = RECIPE_ENGINE_URL | 501 base.known_objects[k].url = RECIPE_ENGINE_URL |
| 447 _, target = k.rsplit('.', 1) | 502 _, target = k.rsplit('.', 1) |
| 448 fname = inspect.getsourcefile(v) | 503 fname = inspect.getsourcefile(v) |
| 449 if fname not in source_cache: | 504 if fname not in source_cache: |
| 450 # we load and cache the whole source file so that ast.parse gets the right | 505 # we load and cache the whole source file so that ast.parse gets the right |
| 451 # line numbers for all the definitions. | 506 # line numbers for all the definitions. |
| 452 source_lines, _ = inspect.findsource(v) | 507 source_lines, _ = inspect.findsource(v) |
| 453 source_cache[fname] = ast.parse(''.join(source_lines), fname) | 508 source_cache[fname] = ast.parse(''.join(source_lines), fname) |
| 454 | 509 |
| 455 relpath = os.path.relpath(fname, RECIPE_ENGINE_BASE) | 510 _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 | 511 |
| 466 | 512 |
| 467 def add_subparser(parser): | 513 def add_subparser(parser): |
| 468 doc_kinds=('binarypb', 'jsonpb', 'textpb', 'markdown(github)', | 514 doc_kinds=('binarypb', 'jsonpb', 'textpb', 'markdown(github)', |
| 469 'markdown(gitiles)') | 515 'markdown(gitiles)') |
| 470 helpstr = ( | 516 helpstr = ( |
| 471 'List all known modules reachable from the current package, with their ' | 517 'List all known modules reachable from the current package, with their ' |
| 472 'documentation.' | 518 'documentation.' |
| 473 ) | 519 ) |
| 474 doc_p = parser.add_parser( | 520 doc_p = parser.add_parser( |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 508 elif args.kind == 'binarypb': | 554 elif args.kind == 'binarypb': |
| 509 sys.stdout.write(node.SerializeToString()) | 555 sys.stdout.write(node.SerializeToString()) |
| 510 elif args.kind == 'textpb': | 556 elif args.kind == 'textpb': |
| 511 sys.stdout.write(textpb.MessageToString(node)) | 557 sys.stdout.write(textpb.MessageToString(node)) |
| 512 elif args.kind == 'markdown-github': | 558 elif args.kind == 'markdown-github': |
| 513 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITHUB), node) | 559 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITHUB), node) |
| 514 elif args.kind == 'markdown-gitiles': | 560 elif args.kind == 'markdown-gitiles': |
| 515 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITILES), node) | 561 doc_markdown.Emit(doc_markdown.Printer(doc_markdown.GITILES), node) |
| 516 else: | 562 else: |
| 517 raise NotImplementedError('--kind=%s' % args.kind) | 563 raise NotImplementedError('--kind=%s' % args.kind) |
| OLD | NEW |