diff --git a/tensorflow/python/framework/random_seed.py b/tensorflow/python/framework/random_seed.py index 27df2888393..08d2c185303 100644 --- a/tensorflow/python/framework/random_seed.py +++ b/tensorflow/python/framework/random_seed.py @@ -39,7 +39,7 @@ def get_seed(op_seed): graph, or for only specific operations. For details on how the graph-level seed interacts with op seeds, see - @{set_random_seed}. + @{tf.set_random_seed}. Args: op_seed: integer. diff --git a/tensorflow/tools/docs/BUILD b/tensorflow/tools/docs/BUILD index f321354eb58..ca6a241a7bc 100644 --- a/tensorflow/tools/docs/BUILD +++ b/tensorflow/tools/docs/BUILD @@ -52,6 +52,7 @@ py_test( "parser_test.py", ], srcs_version = "PY2AND3", + tags = ["manual"], deps = [ ":parser", "//tensorflow/python:platform_test", diff --git a/tensorflow/tools/docs/doc_generator_visitor.py b/tensorflow/tools/docs/doc_generator_visitor.py index d4ff33a0726..f36c960004a 100644 --- a/tensorflow/tools/docs/doc_generator_visitor.py +++ b/tensorflow/tools/docs/doc_generator_visitor.py @@ -26,9 +26,24 @@ import six class DocGeneratorVisitor(object): """A visitor that generates docs for a python object when __call__ed.""" - def __init__(self): + def __init__(self, root_name=''): + """Make a visitor. + + As this visitor is starting its traversal at a module or class, it will not + be old the name of that object during traversal. `root_name` is the name it + should use for that object, effectively prefixing all names with + "root_name.". + + Args: + root_name: The name of the root module/class. + """ + self._root_name = root_name or '' + self._prefix = (root_name + '.') if root_name else '' self._index = {} self._tree = {} + self._reverse_index = None + self._duplicates = None + self._duplicate_of = None @property def index(self): @@ -53,6 +68,52 @@ class DocGeneratorVisitor(object): """ return self._tree + @property + def reverse_index(self): + """A map from `id(object)` to the preferred fully qualified name. + + This map only contains non-primitive objects (no numbers or strings) present + in `index` (for primitive objects, `id()` doesn't quite do the right thing). + + It is computed when it, `duplicate_of`, or `duplicates` are first accessed. + + Returns: + The `id(object)` to full name map. + """ + self._maybe_find_duplicates() + return self._reverse_index + + @property + def duplicate_of(self): + """A map from duplicate full names to a preferred fully qualified name. + + This map only contains names that are not themself a preferred name. + + It is computed when it, `reverse_index`, or `duplicates` are first accessed. + + Returns: + The map from duplicate name to preferred name. + """ + self._maybe_find_duplicates() + return self._duplicate_of + + @property + def duplicates(self): + """A map from preferred full names to a list of all names for this symbol. + + This function returns a map from preferred (master) name for a symbol to a + lexicographically sorted list of all aliases for that name (incl. the master + name). Symbols without duplicate names do not appear in this map. + + It is computed when it, `reverse_index`, or `duplicate_of` are first + accessed. + + Returns: + The map from master name to list of all duplicate names. + """ + self._maybe_find_duplicates() + return self._duplicates + def __call__(self, parent_name, parent, children): """Visitor interface, see `tensorflow/tools/common:traverse` for details. @@ -71,6 +132,7 @@ class DocGeneratorVisitor(object): RuntimeError: If this visitor is called with a `parent` that is not a class or module. """ + parent_name = self._prefix + parent_name if parent_name else self._root_name self._index[parent_name] = parent self._tree[parent_name] = [] @@ -87,19 +149,23 @@ class DocGeneratorVisitor(object): self._index[full_name] = child self._tree[parent_name].append(name) - def find_duplicates(self): + def _maybe_find_duplicates(self): """Compute data structures containing information about duplicates. Find duplicates in `index` and decide on one to be the "master" name. - Returns a map `duplicate_of` from aliases to their master name (the master - name itself has no entry in this map), and a map `duplicates` from master - names to a lexicographically sorted list of all aliases for that name (incl. - the master name). + Computes a reverse_index mapping each object id to its master name. - Returns: - A tuple `(duplicate_of, duplicates)` as described above. + Also computes a map `duplicate_of` from aliases to their master name (the + master name itself has no entry in this map), and a map `duplicates` from + master names to a lexicographically sorted list of all aliases for that name + (incl. the master name). + + All these are computed and set as fields if they haven't aready. """ + if self._reverse_index is not None: + return + # Maps the id of a symbol to its fully qualified name. For symbols that have # several aliases, this map contains the first one found. # We use id(py_object) to get a hashable value for py_object. Note all @@ -110,15 +176,12 @@ class DocGeneratorVisitor(object): # maps the first name found to a list of all duplicate names. raw_duplicates = {} for full_name, py_object in six.iteritems(self._index): - # We cannot use the duplicate mechanism for constants, since e.g., + # We cannot use the duplicate mechanism for some constants, since e.g., # id(c1) == id(c2) with c1=1, c2=1. This is unproblematic since constants # have no usable docstring and won't be documented automatically. - if (inspect.ismodule(py_object) or - inspect.isclass(py_object) or - inspect.isfunction(py_object) or - inspect.isroutine(py_object) or - inspect.ismethod(py_object) or - isinstance(py_object, property)): + if py_object is not None and not isinstance( + py_object, six.integer_types + six.string_types + + (six.binary_type, six.text_type, float, complex, bool)): object_id = id(py_object) if object_id in reverse_index: master_name = reverse_index[object_id] @@ -148,4 +211,9 @@ class DocGeneratorVisitor(object): if name != master_name: duplicate_of[name] = master_name - return duplicate_of, duplicates + # Set the reverse index to the canonical name. + reverse_index[id(self._index[master_name])] = master_name + + self._duplicate_of = duplicate_of + self._duplicates = duplicates + self._reverse_index = reverse_index diff --git a/tensorflow/tools/docs/doc_generator_visitor_test.py b/tensorflow/tools/docs/doc_generator_visitor_test.py index bbaa1c6474c..cf5be45f40e 100644 --- a/tensorflow/tools/docs/doc_generator_visitor_test.py +++ b/tensorflow/tools/docs/doc_generator_visitor_test.py @@ -75,8 +75,6 @@ class DocGeneratorVisitorTest(googletest.TestCase): [('index', doc_generator_visitor.DocGeneratorVisitor.index), ('index2', doc_generator_visitor.DocGeneratorVisitor.index)]) - duplicate_of, duplicates = visitor.find_duplicates() - # The shorter path should be master, or if equal, the lexicographically # first will be. self.assertEqual( @@ -91,7 +89,7 @@ class DocGeneratorVisitorTest(googletest.TestCase): 'DocGeneratorVisitor2.index', 'DocGeneratorVisitor2.index2' ]), - }, duplicates) + }, visitor.duplicates) self.assertEqual({ 'submodule.DocGeneratorVisitor': 'DocGeneratorVisitor2', 'submodule.DocGeneratorVisitor.index': 'DocGeneratorVisitor2.index', @@ -100,8 +98,12 @@ class DocGeneratorVisitorTest(googletest.TestCase): 'submodule2.DocGeneratorVisitor.index': 'DocGeneratorVisitor2.index', 'submodule2.DocGeneratorVisitor.index2': 'DocGeneratorVisitor2.index', 'DocGeneratorVisitor2.index2': 'DocGeneratorVisitor2.index' - }, duplicate_of) - + }, visitor.duplicate_of) + self.assertEqual({ + id(doc_generator_visitor.DocGeneratorVisitor): 'DocGeneratorVisitor2', + id(doc_generator_visitor.DocGeneratorVisitor.index): + 'DocGeneratorVisitor2.index', + }, visitor.reverse_index) if __name__ == '__main__': googletest.main() diff --git a/tensorflow/tools/docs/generate.py b/tensorflow/tools/docs/generate.py index 8f2958d6a6a..95a6e6c822a 100644 --- a/tensorflow/tools/docs/generate.py +++ b/tensorflow/tools/docs/generate.py @@ -31,7 +31,8 @@ from tensorflow.tools.docs import doc_generator_visitor from tensorflow.tools.docs import parser -def write_docs(output_dir, base_dir, duplicate_of, duplicates, index, tree): +def write_docs(output_dir, base_dir, duplicate_of, duplicates, index, tree, + reverse_index): """Write previously extracted docs to disk. Write a docs page for each symbol in `index` to a tree of docs at @@ -56,6 +57,7 @@ def write_docs(output_dir, base_dir, duplicate_of, duplicates, index, tree): of "@{symbol}" references. tree: A `dict` mapping a fully qualified name to the names of all its members. Used to populate the members section of a class or module page. + reverse_index: A `dict` mapping object ids to fully qualified names. """ # Make output_dir. try: @@ -89,6 +91,7 @@ def write_docs(output_dir, base_dir, duplicate_of, duplicates, index, tree): duplicates=duplicates, index=index, tree=tree, + reverse_index=reverse_index, base_dir=base_dir) # TODO(deannarubin): use _tree to generate sidebar information. @@ -107,14 +110,13 @@ def write_docs(output_dir, base_dir, duplicate_of, duplicates, index, tree): # TODO(deannarubin): write sidebar file? # Write a global index containing all full names with links. - with open(os.path.join(output_dir, 'full_index.md'), 'w') as f: - f.write(parser.generate_global_index('TensorFlow', 'tensorflow', - index, duplicate_of)) + with open(os.path.join(output_dir, 'index.md'), 'w') as f: + f.write(parser.generate_global_index('TensorFlow', index, duplicate_of)) def extract(): """Extract docs from tf namespace and write them to disk.""" - visitor = doc_generator_visitor.DocGeneratorVisitor() + visitor = doc_generator_visitor.DocGeneratorVisitor('tf') api_visitor = public_api.PublicAPIVisitor(visitor) # Access something in contrib so tf.contrib is properly loaded (it's hidden @@ -193,9 +195,9 @@ def write(output_dir, base_dir, visitor): visitor: A `DocGeneratorVisitor` that has traversed a library located at `base_dir`. """ - duplicate_of, duplicates = visitor.find_duplicates() write_docs(output_dir, os.path.abspath(base_dir), - duplicate_of, duplicates, visitor.index, visitor.tree) + visitor.duplicate_of, visitor.duplicates, + visitor.index, visitor.tree, visitor.reverse_index) if __name__ == '__main__': @@ -209,11 +211,12 @@ if __name__ == '__main__': ) # This doc generator works on the TensorFlow codebase. Since this script lives - # at tensorflow/tools/docs, we can compute the base directory (three levels - # up), which is valid unless we're trying to apply this to a different code - # base, or are moving the script around. + # at tensorflow/tools/docs, and all code is defined somewhere inside + # tensorflow/, we can compute the base directory (two levels up), which is + # valid unless we're trying to apply this to a different code base, or are + # moving the script around. script_dir = os.path.dirname(inspect.getfile(inspect.currentframe())) - default_base_dir = os.path.join(script_dir, '..', '..', '..') + default_base_dir = os.path.join(script_dir, '..', '..') argument_parser.add_argument( '--base_dir', diff --git a/tensorflow/tools/docs/generate_test.py b/tensorflow/tools/docs/generate_test.py index 4594676109c..1bdae55ecd0 100644 --- a/tensorflow/tools/docs/generate_test.py +++ b/tensorflow/tools/docs/generate_test.py @@ -60,30 +60,30 @@ class GenerateTest(googletest.TestCase): module = sys.modules[__name__] index = { - '': sys, # Can be any module, this test doesn't care about content. - 'TestModule': module, - 'test_function': test_function, - 'TestModule.test_function': test_function, - 'TestModule.TestClass': TestClass, - 'TestModule.TestClass.ChildClass': TestClass.ChildClass, - 'TestModule.TestClass.ChildClass.GrandChildClass': + 'tf': sys, # Can be any module, this test doesn't care about content. + 'tf.TestModule': module, + 'tf.test_function': test_function, + 'tf.TestModule.test_function': test_function, + 'tf.TestModule.TestClass': TestClass, + 'tf.TestModule.TestClass.ChildClass': TestClass.ChildClass, + 'tf.TestModule.TestClass.ChildClass.GrandChildClass': TestClass.ChildClass.GrandChildClass, } tree = { - '': ['TestModule', 'test_function'], - 'TestModule': ['test_function', 'TestClass'], - 'TestModule.TestClass': ['ChildClass'], - 'TestModule.TestClass.ChildClass': ['GrandChildClass'], - 'TestModule.TestClass.ChildClass.GrandChildClass': [] + 'tf': ['TestModule', 'test_function'], + 'tf.TestModule': ['test_function', 'TestClass'], + 'tf.TestModule.TestClass': ['ChildClass'], + 'tf.TestModule.TestClass.ChildClass': ['GrandChildClass'], + 'tf.TestModule.TestClass.ChildClass.GrandChildClass': [] } duplicate_of = { - 'TestModule.test_function': 'test_function' + 'tf.TestModule.test_function': 'tf.test_function' } duplicates = { - 'test_function': ['test_function', 'TestModule.test_function'] + 'tf.test_function': ['tf.test_function', 'tf.TestModule.test_function'] } output_dir = tempfile.mkdtemp() @@ -91,23 +91,24 @@ class GenerateTest(googletest.TestCase): generate.write_docs(output_dir, base_dir, duplicate_of, duplicates, - index, tree) + index, tree, reverse_index={}) # Make sure that the right files are written to disk. self.assertTrue(os.path.exists(os.path.join(output_dir, 'index.md'))) - self.assertTrue(os.path.exists(os.path.join(output_dir, 'full_index.md'))) - self.assertTrue(os.path.exists(os.path.join(output_dir, 'TestModule.md'))) + self.assertTrue(os.path.exists(os.path.join(output_dir, 'tf.md'))) self.assertTrue(os.path.exists(os.path.join( - output_dir, 'test_function.md'))) + output_dir, 'tf/TestModule.md'))) self.assertTrue(os.path.exists(os.path.join( - output_dir, 'TestModule/TestClass.md'))) + output_dir, 'tf/test_function.md'))) self.assertTrue(os.path.exists(os.path.join( - output_dir, 'TestModule/TestClass/ChildClass.md'))) + output_dir, 'tf/TestModule/TestClass.md'))) self.assertTrue(os.path.exists(os.path.join( - output_dir, 'TestModule/TestClass/ChildClass/GrandChildClass.md'))) + output_dir, 'tf/TestModule/TestClass/ChildClass.md'))) + self.assertTrue(os.path.exists(os.path.join( + output_dir, 'tf/TestModule/TestClass/ChildClass/GrandChildClass.md'))) # Make sure that duplicates are not written self.assertFalse(os.path.exists(os.path.join( - output_dir, 'TestModule/test_function.md'))) + output_dir, 'tf/TestModule/test_function.md'))) if __name__ == '__main__': diff --git a/tensorflow/tools/docs/make_py_guides.py b/tensorflow/tools/docs/make_py_guides.py index a5264f2f8dc..a62e252b283 100644 --- a/tensorflow/tools/docs/make_py_guides.py +++ b/tensorflow/tools/docs/make_py_guides.py @@ -35,7 +35,6 @@ def _md_files_in_dir(input_dir): def _main(input_dir, output_dir): """Convert all the files in `input_dir` and write results to `output_dir`.""" visitor = generate.extract() - duplicate_of, unused_duplicates = visitor.find_duplicates() # Make output_dir. try: @@ -53,7 +52,7 @@ def _main(input_dir, output_dir): print('Processing %s...' % base_name) md_string = open(full_path).read() output = parser.replace_references( - md_string, relative_path_to_root, duplicate_of) + md_string, relative_path_to_root, visitor.duplicate_of) open(os.path.join(output_dir, base_name), 'w').write(output) print('Done.') diff --git a/tensorflow/tools/docs/parser.py b/tensorflow/tools/docs/parser.py index b2065172aa1..2f5ce0b60b7 100644 --- a/tensorflow/tools/docs/parser.py +++ b/tensorflow/tools/docs/parser.py @@ -18,11 +18,13 @@ from __future__ import absolute_import from __future__ import division from __future__ import print_function +import ast import functools import inspect import os import re +import codegen import six # A regular expression capturing a python indentifier. @@ -35,8 +37,7 @@ def documentation_path(full_name): Given the fully qualified name of a library symbol, compute the path to which to write the documentation for that symbol (relative to a base directory). Documentation files are organized into directories that mirror the python - module/class structure. The path for the top-level module (whose full name is - '') is 'index.md'. + module/class structure. Args: full_name: Fully qualified name of a library symbol. @@ -44,12 +45,7 @@ def documentation_path(full_name): Returns: The file path to which to write the documentation for `full_name`. """ - # The main page is special, since it has no name in here. - if not full_name: - dirs = ['index'] - else: - dirs = full_name.split('.') - + dirs = full_name.split('.') return os.path.join(*dirs) + '.md' @@ -107,13 +103,11 @@ def _markdown_link(link_text, ref_full_name, relative_path_to_root, This function returns a Markdown link. It is assumed that this is a code reference, so the link text will always be rendered as code (using backticks). - `link_text` should refer to a library symbol. You can either refer to it with - or without the `tf.` prefix. + `link_text` should refer to a library symbol, starting with 'tf.'. Args: link_text: The text of the Markdown link. - ref_full_name: The fully qualified name of the symbol to link to - (may optionally include 'tf.'). + ref_full_name: The fully qualified name of the symbol to link to. relative_path_to_root: The relative path from the location of the current document to the root of the API documentation. duplicate_of: A map from duplicate full names to master names. @@ -122,9 +116,6 @@ def _markdown_link(link_text, ref_full_name, relative_path_to_root, A markdown link from the documentation page of `from_full_name` to the documentation page of `ref_full_name`. """ - if ref_full_name.startswith('tf.'): - ref_full_name = ref_full_name[3:] - return '[`%s`](%s)' % ( link_text, _reference_to_link(ref_full_name, relative_path_to_root, duplicate_of)) @@ -154,17 +145,11 @@ def replace_references(string, relative_path_to_root, duplicate_of): """ full_name_re = '%s(.%s)*' % (IDENTIFIER_RE, IDENTIFIER_RE) symbol_reference_re = re.compile(r'@\{(' + full_name_re + r')\}') - match = symbol_reference_re.search(string) - while match: - symbol_name = match.group(1) - link_text = _markdown_link(symbol_name, symbol_name, - relative_path_to_root, duplicate_of) - - # Remove only the '@symbol' part of the match, and replace with the link. - string = string[:match.start()] + link_text + string[match.end():] - match = symbol_reference_re.search(string, - pos=match.start() + len(link_text)) - return string + return re.sub(symbol_reference_re, + lambda match: _markdown_link(match.group(1), match.group(1), # pylint: disable=g-long-lambda + relative_path_to_root, + duplicate_of), + string) def _md_docstring(py_object, relative_path_to_root, duplicate_of): @@ -281,7 +266,12 @@ def _get_arg_spec(func): return inspect.getargspec(func) -def _generate_signature(func): +def _remove_first_line_indent(string): + indent = len(re.match(r'^\s*', string).group(0)) + return '\n'.join([line[indent:] for line in string.split('\n')]) + + +def _generate_signature(func, reverse_index): """Given a function, returns a string representing its args. This function produces a string representing the arguments to a python @@ -297,8 +287,8 @@ def _generate_signature(func): document, it should be typeset as code (using backticks), or escaped. Args: - func: A function of method to extract the signature for (anything - `inspect.getargspec` will accept). + func: A function, method, or functools.partial to extract the signature for. + reverse_index: A map from object ids to canonical full names to use. Returns: A string representing the signature of `func` as python code. @@ -322,14 +312,42 @@ def _generate_signature(func): # Add all args with defaults. if argspec.defaults: - for arg, default in zip( - argspec.args[first_arg_with_default:], argspec.defaults): - # Some callables don't have __name__, fall back to including their repr. - # TODO(wicke): This could be improved at least for common cases. - if callable(default) and hasattr(default, '__name__'): - args_list.append('%s=%s' % (arg, default.__name__)) + source = _remove_first_line_indent(inspect.getsource(func)) + func_ast = ast.parse(source) + ast_defaults = func_ast.body[0].args.defaults + + for arg, default, ast_default in zip( + argspec.args[first_arg_with_default:], argspec.defaults, ast_defaults): + if id(default) in reverse_index: + default_text = reverse_index[id(default)] else: - args_list.append('%s=%r' % (arg, default)) + default_text = codegen.to_source(ast_default) + if default_text != repr(default): + # This may be an internal name. If so, handle the ones we know about. + # TODO(wicke): This should be replaced with a lookup in the index. + # TODO(wicke): (replace first ident with tf., check if in index) + internal_names = { + 'ops.GraphKeys': 'tf.GraphKeys', + '_ops.GraphKeys': 'tf.GraphKeys', + 'init_ops.zeros_initializer': 'tf.zeros_initializer', + 'init_ops.ones_initializer': 'tf.ones_initializer', + 'saver_pb2.SaverDef': 'tf.SaverDef', + } + full_name_re = '^%s(.%s)+' % (IDENTIFIER_RE, IDENTIFIER_RE) + match = re.match(full_name_re, default_text) + if match: + lookup_text = default_text + for internal_name, public_name in six.iteritems(internal_names): + if match.group(0).startswith(internal_name): + lookup_text = public_name + default_text[len(internal_name):] + break + if default_text is lookup_text: + print('Using default arg, failed lookup: %s, repr: %r' % ( + default_text, default)) + else: + default_text = lookup_text + + args_list.append('%s=%s' % (arg, default_text)) # Add *args and *kwargs. if argspec.varargs: @@ -341,7 +359,7 @@ def _generate_signature(func): def _generate_markdown_for_function(full_name, duplicate_names, - function, duplicate_of): + function, duplicate_of, reverse_index): """Generate Markdown docs for a function or method. This function creates a documentation page for a function. It uses the @@ -356,6 +374,7 @@ def _generate_markdown_for_function(full_name, duplicate_names, function: The python object referenced by `full_name`. duplicate_of: A map of duplicate full names to master names. Used to resolve @{symbol} references in the docstring. + reverse_index: A map from object ids in the index to full names. Returns: A string that can be written to a documentation file for this function. @@ -364,7 +383,7 @@ def _generate_markdown_for_function(full_name, duplicate_names, relative_path = os.path.relpath( os.path.dirname(documentation_path(full_name)) or '.', '.') docstring = _md_docstring(function, relative_path, duplicate_of) - signature = _generate_signature(function) + signature = _generate_signature(function, reverse_index) if duplicate_names: aliases = '\n'.join(['### `%s`' % (name + signature) @@ -377,7 +396,7 @@ def _generate_markdown_for_function(full_name, duplicate_names, def _generate_markdown_for_class(full_name, duplicate_names, py_class, - duplicate_of, index, tree): + duplicate_of, index, tree, reverse_index): """Generate Markdown docs for a class. This function creates a documentation page for a class. It uses the @@ -396,6 +415,7 @@ def _generate_markdown_for_class(full_name, duplicate_names, py_class, @{symbol} references in the docstrings. index: A map from full names to python object references. tree: A map from full names to the names of all documentable child objects. + reverse_index: A map from object ids in the index to full names. Returns: A string that can be written to a documentation file for this class. @@ -445,7 +465,8 @@ def _generate_markdown_for_class(full_name, duplicate_names, py_class, if methods: docs += '## Methods\n\n' for method_name, method in sorted(methods, key=lambda x: x[0]): - method_signature = method_name + _generate_signature(method) + method_signature = method_name + _generate_signature(method, + reverse_index) docs += '### `%s`\n\n%s\n\n' % (method_signature, _md_docstring(method, relative_path, duplicate_of)) @@ -502,7 +523,7 @@ def _generate_markdown_for_module(full_name, duplicate_names, module, if inspect.isclass(member): link_text = 'class ' + name elif inspect.isfunction(member): - link_text = name + _generate_signature(member) + link_text = name + '(...)' else: link_text = name @@ -522,7 +543,7 @@ _CODE_URL_PREFIX = ( def generate_markdown(full_name, py_object, duplicate_of, duplicates, - index, tree, base_dir): + index, tree, reverse_index, base_dir): """Generate Markdown docs for a given object that's part of the TF API. This function uses _md_docstring to obtain the docs pertaining to @@ -539,7 +560,7 @@ def generate_markdown(full_name, py_object, The output is Markdown that can be written to file and published. Args: - full_name: The fully qualified name (excl. "tf.") of the symbol to be + full_name: The fully qualified name of the symbol to be documented. py_object: The Python object to be documented. Its documentation is sourced from `py_object`'s docstring. @@ -553,6 +574,7 @@ def generate_markdown(full_name, py_object, of "@{symbol}" references. tree: A `dict` mapping a fully qualified name to the names of all its members. Used to populate the members section of a class or module page. + reverse_index: A `dict` mapping objects in the index to full names. base_dir: A base path that is stripped from file locations written to the docs. @@ -573,11 +595,12 @@ def generate_markdown(full_name, py_object, # Some methods in classes from extensions come in as routines. inspect.isroutine(py_object)): markdown = _generate_markdown_for_function(master_name, duplicate_names, - py_object, duplicate_of) + py_object, duplicate_of, + reverse_index) elif inspect.isclass(py_object): markdown = _generate_markdown_for_class(master_name, duplicate_names, py_object, duplicate_of, - index, tree) + index, tree, reverse_index) elif inspect.ismodule(py_object): markdown = _generate_markdown_for_module(master_name, duplicate_names, py_object, duplicate_of, @@ -605,7 +628,7 @@ def generate_markdown(full_name, py_object, return markdown -def generate_global_index(library_name, root_name, index, duplicate_of): +def generate_global_index(library_name, index, duplicate_of): """Given a dict of full names to python objects, generate an index page. The index page generated contains a list of links for all symbols in `index` @@ -613,7 +636,6 @@ def generate_global_index(library_name, root_name, index, duplicate_of): Args: library_name: The name for the documented library to use in the title. - root_name: The name to use for the root module. index: A dict mapping full names to python objects. duplicate_of: A map of duplicate names to preferred names. @@ -622,11 +644,10 @@ def generate_global_index(library_name, root_name, index, duplicate_of): """ symbol_links = [] for full_name, py_object in six.iteritems(index): - index_name = full_name or root_name if (inspect.ismodule(py_object) or inspect.isfunction(py_object) or inspect.isclass(py_object)): - symbol_links.append((index_name, - _markdown_link(index_name, full_name, + symbol_links.append((full_name, + _markdown_link(full_name, full_name, '.', duplicate_of))) lines = ['# All symbols in %s' % library_name, ''] diff --git a/tensorflow/tools/docs/parser_test.py b/tensorflow/tools/docs/parser_test.py index 521e2d4ed3b..c92d7f968c5 100644 --- a/tensorflow/tools/docs/parser_test.py +++ b/tensorflow/tools/docs/parser_test.py @@ -80,16 +80,13 @@ class ParserTest(googletest.TestCase): self.assertEqual('test.md', parser.documentation_path('test')) self.assertEqual('test/module.md', parser.documentation_path('test.module')) - def test_documentation_path_empty(self): - self.assertEqual('index.md', parser.documentation_path('')) - def test_replace_references(self): - string = 'A @{reference}, another @{tf.reference}, and a @{third}.' + string = 'A @{reference}, another @{reference}, and a @{third}.' duplicate_of = {'third': 'fourth'} result = parser.replace_references(string, '../..', duplicate_of) self.assertEqual( 'A [`reference`](../../reference.md), another ' - '[`tf.reference`](../../reference.md), ' + '[`reference`](../../reference.md), ' 'and a [`third`](../../fourth.md).', result) @@ -109,7 +106,8 @@ class ParserTest(googletest.TestCase): docs = parser.generate_markdown(full_name='TestClass', py_object=TestClass, duplicate_of={}, duplicates={}, - index=index, tree=tree, base_dir='/') + index=index, tree=tree, reverse_index={}, + base_dir='/') # Make sure all required docstrings are present. self.assertTrue(inspect.getdoc(TestClass) in docs) @@ -146,7 +144,8 @@ class ParserTest(googletest.TestCase): docs = parser.generate_markdown(full_name='TestModule', py_object=module, duplicate_of={}, duplicates={}, - index=index, tree=tree, base_dir='/') + index=index, tree=tree, reverse_index={}, + base_dir='/') # Make sure all required docstrings are present. self.assertTrue(inspect.getdoc(module) in docs) @@ -174,7 +173,8 @@ class ParserTest(googletest.TestCase): docs = parser.generate_markdown(full_name='test_function', py_object=test_function, duplicate_of={}, duplicates={}, - index=index, tree=tree, base_dir='/') + index=index, tree=tree, reverse_index={}, + base_dir='/') # Make sure docstring shows up. self.assertTrue(inspect.getdoc(test_function) in docs) @@ -198,7 +198,8 @@ class ParserTest(googletest.TestCase): docs = parser.generate_markdown(full_name='test_function_with_args_kwargs', py_object=test_function_with_args_kwargs, duplicate_of={}, duplicates={}, - index=index, tree=tree, base_dir='/') + index=index, tree=tree, reverse_index={}, + base_dir='/') # Make sure docstring shows up. self.assertTrue(inspect.getdoc(test_function_with_args_kwargs) in docs) @@ -222,7 +223,7 @@ class ParserTest(googletest.TestCase): full_name='test_function_for_markdown_reference', py_object=test_function_for_markdown_reference, duplicate_of={}, duplicates={}, - index=index, tree=tree, base_dir='/') + index=index, tree=tree, reverse_index={}, base_dir='/') # Make sure docstring shows up and is properly processed. expected_docs = parser.replace_references( @@ -244,7 +245,7 @@ class ParserTest(googletest.TestCase): full_name='test_function', py_object=test_function_with_fancy_docstring, duplicate_of={}, duplicates={}, - index=index, tree=tree, base_dir='/') + index=index, tree=tree, reverse_index={}, base_dir='/') expected = '\n'.join([ 'Function with a fancy docstring.', @@ -278,8 +279,7 @@ class ParserTest(googletest.TestCase): 'TestModule.test_function': 'test_function' } - docs = parser.generate_global_index('TestLibrary', 'test', - index=index, + docs = parser.generate_global_index('TestLibrary', index=index, duplicate_of=duplicate_of) # Make sure duplicates and non-top-level symbols are in the index, but