Index: recipe_engine/doc.py |
diff --git a/recipe_engine/doc.py b/recipe_engine/doc.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..9f9baa94704fca4a702ac33684a7a2594fefdbe9 |
--- /dev/null |
+++ b/recipe_engine/doc.py |
@@ -0,0 +1,116 @@ |
+#!/usr/bin/env python |
+# Copyright 2013 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. |
+ |
+from __future__ import print_function |
+ |
+import collections |
+import inspect |
+import os |
+import sys |
+ |
+from . import loader |
+from . import run as recipe_run |
+from . import package |
+from . import recipe_api |
+ |
+def trim_doc(docstring): |
+ """From PEP 257""" |
+ if not docstring: |
+ return '' |
+ # Convert tabs to spaces (following the normal Python rules) |
+ # and split into a list of lines: |
+ lines = docstring.expandtabs().splitlines() |
+ # Determine minimum indentation (first line doesn't count): |
+ indent = sys.maxint |
+ for line in lines[1:]: |
+ stripped = line.lstrip() |
+ if stripped: |
+ indent = min(indent, len(line) - len(stripped)) |
+ # Remove indentation (first line is special): |
+ trimmed = [lines[0].strip()] |
+ if indent < sys.maxint: |
+ for line in lines[1:]: |
+ trimmed.append(line[indent:].rstrip()) |
+ # Strip off trailing and leading blank lines: |
+ while trimmed and not trimmed[-1]: |
+ trimmed.pop() |
+ while trimmed and not trimmed[0]: |
+ trimmed.pop(0) |
+ return trimmed |
+ |
+def member_iter(obj): |
+ for name in sorted(dir(obj)): |
+ if name[0] == '_' and name != '__call__': |
+ continue |
+ # Check class first to avoid calling property functions. |
+ if hasattr(obj.__class__, name): |
+ val = getattr(obj.__class__, name) |
+ if callable(val) or isinstance(val, property): |
+ yield name, val |
+ else: |
+ val = getattr(obj, name) |
+ if callable(val) or inspect.ismodule(val): |
+ yield name, val |
+ |
+def map_to_cool_name(typ): |
+ if typ is collections.Mapping: |
+ return 'Mapping' |
+ return typ |
+ |
+def p(indent_lvl, *args, **kwargs): |
+ sys.stdout.write(' '*indent_lvl) |
+ print(*args, **kwargs) |
+ |
+def pmethod(indent_lvl, name, obj): |
+ if isinstance(obj, property): |
+ name = '@'+name |
+ if obj.fset: |
+ name += '(r/w)' |
+ p(indent_lvl, name, '', end='') |
+ if obj.__doc__: |
+ lines = trim_doc(obj.__doc__) |
+ p(0, '--', lines[0]) |
+ else: |
+ p(0) |
+ |
+def main(package_deps): |
+ common_methods = set(k for k, v in member_iter(recipe_api.RecipeApi)) |
+ p(0, 'Common Methods -- %s' % os.path.splitext(recipe_api.__file__)[0]) |
+ for method in sorted(common_methods): |
+ pmethod(1, method, getattr(recipe_api.RecipeApi, method)) |
+ |
+ universe = loader.RecipeUniverse(package_deps) |
+ deps = universe.deps_from_spec( |
+ # TODO(luqui): This doesn't handle name scoping correctly (e.g. same-named |
+ # modules in different packages). |
+ { modpath: modpath.split('/')[-1] |
+ for modpath in universe.loop_over_recipe_modules() }) |
+ |
+ inst = loader.create_recipe_api( |
+ deps, recipe_run.RecipeEngine(None, {}, None)) |
+ |
+ for mod_name, mod in deps.iteritems(): |
+ p(0) |
+ p(0, "(%s) -- %s" % (mod_name, mod.__path__[0])) |
+ if mod.LOADED_DEPS: |
+ p(1, 'DEPS:', list(mod.LOADED_DEPS)) |
+ |
+ subinst = getattr(inst, mod_name) |
+ bases = set(subinst.__class__.__bases__) |
+ base_fns = set() |
+ for base in bases: |
+ for name, _ in inspect.getmembers(base): |
+ base_fns.add(name) |
+ for cool_base in bases - set((recipe_api.RecipeApi,)): |
+ p(1, 'behaves like %s' % map_to_cool_name(cool_base)) |
+ |
+ if mod.API.__doc__: |
+ for line in trim_doc(mod.API.__doc__): |
+ p(2, '"', line) |
+ |
+ for fn_name, obj in member_iter(subinst): |
+ if fn_name in base_fns: |
+ continue |
+ pmethod(1, fn_name, obj) |