Use tf. for all symbols.

Change base_dir to exclude tensorflow/.

Improve the doc generator's handling of defaults in argument lists. Use an index to look up the preferred name for objects that are passed around as defaults which has a canonical TensorFlow name. Use ast and codegen to reproduce what was in the actual code for everything else.

Also simplify @{} replace logic.
Change: 146750326
This commit is contained in:
Martin Wicke 2017-02-06 22:19:07 -08:00 committed by TensorFlower Gardener
parent 8b5a8df5e3
commit 3414c1ebab
9 changed files with 215 additions and 120 deletions

View File

@ -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.

View File

@ -52,6 +52,7 @@ py_test(
"parser_test.py",
],
srcs_version = "PY2AND3",
tags = ["manual"],
deps = [
":parser",
"//tensorflow/python:platform_test",

View File

@ -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

View File

@ -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()

View File

@ -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',

View File

@ -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__':

View File

@ -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.')

View File

@ -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, '']

View File

@ -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