Home | History | Annotate | Download | only in docs
      1 # Copyright 2015 The TensorFlow Authors. All Rights Reserved.
      2 #
      3 # Licensed under the Apache License, Version 2.0 (the "License");
      4 # you may not use this file except in compliance with the License.
      5 # You may obtain a copy of the License at
      6 #
      7 #     http://www.apache.org/licenses/LICENSE-2.0
      8 #
      9 # Unless required by applicable law or agreed to in writing, software
     10 # distributed under the License is distributed on an "AS IS" BASIS,
     11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 # See the License for the specific language governing permissions and
     13 # limitations under the License.
     14 # ==============================================================================
     15 """Turn Python docstrings into Markdown for TensorFlow documentation."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 import ast
     22 import collections
     23 import functools
     24 import itertools
     25 import json
     26 import os
     27 import re
     28 
     29 import astor
     30 import six
     31 
     32 from google.protobuf.message import Message as ProtoMessage
     33 from tensorflow.python.platform import tf_logging as logging
     34 from tensorflow.python.util import tf_inspect
     35 from tensorflow.tools.docs import doc_controls
     36 
     37 
     38 def is_free_function(py_object, full_name, index):
     39   """Check if input is a free function (and not a class- or static method).
     40 
     41   Args:
     42     py_object: The the object in question.
     43     full_name: The full name of the object, like `tf.module.symbol`.
     44     index: The {full_name:py_object} dictionary for the public API.
     45 
     46   Returns:
     47     True if the obeject is a stand-alone function, and not part of a class
     48     definition.
     49   """
     50   if not tf_inspect.isfunction(py_object):
     51     return False
     52 
     53   parent_name = full_name.rsplit('.', 1)[0]
     54   if tf_inspect.isclass(index[parent_name]):
     55     return False
     56 
     57   return True
     58 
     59 
     60 # A regular expression capturing a python identifier.
     61 IDENTIFIER_RE = r'[a-zA-Z_]\w*'
     62 
     63 
     64 class TFDocsError(Exception):
     65   pass
     66 
     67 
     68 class _Errors(object):
     69   """A collection of errors."""
     70 
     71   def __init__(self):
     72     self._errors = []
     73 
     74   def log_all(self):
     75     """Log all the collected errors to the standard error."""
     76     template = 'ERROR:\n    output file name: %s\n    %s\n\n'
     77 
     78     for full_name, message in self._errors:
     79       logging.warn(template, full_name, message)
     80 
     81   def append(self, full_name, message):
     82     """Add an error to the collection.
     83 
     84     Args:
     85       full_name: The path to the file in which the error occurred.
     86       message: The message to display with the error.
     87     """
     88     self._errors.append((full_name, message))
     89 
     90   def __len__(self):
     91     return len(self._errors)
     92 
     93   def __eq__(self, other):
     94     if not isinstance(other, _Errors):
     95       return False
     96     return self._errors == other._errors  # pylint: disable=protected-access
     97 
     98 
     99 def documentation_path(full_name, is_fragment=False):
    100   """Returns the file path for the documentation for the given API symbol.
    101 
    102   Given the fully qualified name of a library symbol, compute the path to which
    103   to write the documentation for that symbol (relative to a base directory).
    104   Documentation files are organized into directories that mirror the python
    105   module/class structure.
    106 
    107   Args:
    108     full_name: Fully qualified name of a library symbol.
    109     is_fragment: If `False` produce a direct markdown link (`tf.a.b.c` -->
    110       `tf/a/b/c.md`). If `True` produce fragment link, `tf.a.b.c` -->
    111       `tf/a/b.md#c`
    112   Returns:
    113     The file path to which to write the documentation for `full_name`.
    114   """
    115   parts = full_name.split('.')
    116   if is_fragment:
    117     parts, fragment = parts[:-1], parts[-1]
    118 
    119   result = os.path.join(*parts) + '.md'
    120 
    121   if is_fragment:
    122     result = result + '#' + fragment
    123 
    124   return result
    125 
    126 
    127 def _get_raw_docstring(py_object):
    128   """Get the docs for a given python object.
    129 
    130   Args:
    131     py_object: A python object to retrieve the docs for (class, function/method,
    132       or module).
    133 
    134   Returns:
    135     The docstring, or the empty string if no docstring was found.
    136   """
    137   # For object instances, tf_inspect.getdoc does give us the docstring of their
    138   # type, which is not what we want. Only return the docstring if it is useful.
    139   if (tf_inspect.isclass(py_object) or tf_inspect.ismethod(py_object) or
    140       tf_inspect.isfunction(py_object) or tf_inspect.ismodule(py_object) or
    141       isinstance(py_object, property)):
    142     return tf_inspect.getdoc(py_object) or ''
    143   else:
    144     return ''
    145 
    146 
    147 # A regular expression for capturing a @{symbol} reference.
    148 SYMBOL_REFERENCE_RE = re.compile(
    149     r"""
    150     # Start with a literal "@{".
    151     @\{
    152       # Group at least 1 symbol, not "}".
    153       ([^}]+)
    154     # Followed by a closing "}"
    155     \}
    156     """,
    157     flags=re.VERBOSE)
    158 
    159 AUTO_REFERENCE_RE = re.compile(r'`([a-zA-Z0-9_.]+?)`')
    160 
    161 
    162 class ReferenceResolver(object):
    163   """Class for replacing @{...} references with Markdown links.
    164 
    165   Attributes:
    166     current_doc_full_name: A string (or None) indicating the name of the
    167       document currently being processed, so errors can reference the broken
    168       doc.
    169   """
    170 
    171   def __init__(self, duplicate_of, doc_index, is_fragment, py_module_names):
    172     """Initializes a Reference Resolver.
    173 
    174     Args:
    175       duplicate_of: A map from duplicate names to preferred names of API
    176         symbols.
    177       doc_index: A `dict` mapping symbol name strings to objects with `url`
    178         and `title` fields. Used to resolve @{$doc} references in docstrings.
    179       is_fragment: A map from full names to bool for each symbol. If True the
    180         object lives at a page fragment `tf.a.b.c` --> `tf/a/b#c`. If False
    181         object has a page to itself: `tf.a.b.c` --> `tf/a/b/c`.
    182       py_module_names: A list of string names of Python modules.
    183     """
    184     self._duplicate_of = duplicate_of
    185     self._doc_index = doc_index
    186     self._is_fragment = is_fragment
    187     self._all_names = set(is_fragment.keys())
    188     self._py_module_names = py_module_names
    189 
    190     self.current_doc_full_name = None
    191     self._errors = _Errors()
    192 
    193   def add_error(self, message):
    194     self._errors.append(self.current_doc_full_name, message)
    195 
    196   def log_errors(self):
    197     self._errors.log_all()
    198 
    199   def num_errors(self):
    200     return len(self._errors)
    201 
    202   @classmethod
    203   def from_visitor(cls, visitor, doc_index, **kwargs):
    204     """A factory function for building a ReferenceResolver from a visitor.
    205 
    206     Args:
    207       visitor: an instance of `DocGeneratorVisitor`
    208       doc_index: a dictionary mapping document names to references objects with
    209         "title" and "url" fields
    210       **kwargs: all remaining args are passed to the constructor
    211     Returns:
    212       an instance of `ReferenceResolver` ()
    213     """
    214     is_fragment = {}
    215     for name, obj in visitor.index.items():
    216       has_page = (
    217           tf_inspect.isclass(obj) or tf_inspect.ismodule(obj) or
    218           is_free_function(obj, name, visitor.index))
    219 
    220       is_fragment[name] = not has_page
    221 
    222     return cls(
    223         duplicate_of=visitor.duplicate_of,
    224         doc_index=doc_index,
    225         is_fragment=is_fragment,
    226         **kwargs)
    227 
    228   @classmethod
    229   def from_json_file(cls, filepath, doc_index):
    230     with open(filepath) as f:
    231       json_dict = json.load(f)
    232 
    233     return cls(doc_index=doc_index, **json_dict)
    234 
    235   def to_json_file(self, filepath):
    236     """Converts the RefenceResolver to json and writes it to the specified file.
    237 
    238     Args:
    239       filepath: The file path to write the json to.
    240     """
    241     try:
    242       os.makedirs(os.path.dirname(filepath))
    243     except OSError:
    244       pass
    245     json_dict = {}
    246     for key, value in self.__dict__.items():
    247       # Drop these two fields. `_doc_index` is not serializable. `_all_names` is
    248       # generated by the constructor.
    249       if key in ('_doc_index', '_all_names',
    250                  '_errors', 'current_doc_full_name'):
    251         continue
    252 
    253       # Strip off any leading underscores on field names as these are not
    254       # recognized by the constructor.
    255       json_dict[key.lstrip('_')] = value
    256 
    257     with open(filepath, 'w') as f:
    258       json.dump(json_dict, f, indent=2, sort_keys=True)
    259 
    260   def replace_references(self, string, relative_path_to_root):
    261     """Replace "@{symbol}" references with links to symbol's documentation page.
    262 
    263     This functions finds all occurrences of "@{symbol}" in `string`
    264     and replaces them with markdown links to the documentation page
    265     for "symbol".
    266 
    267     `relative_path_to_root` is the relative path from the document
    268     that contains the "@{symbol}" reference to the root of the API
    269     documentation that is linked to. If the containing page is part of
    270     the same API docset, `relative_path_to_root` can be set to
    271     `os.path.dirname(documentation_path(name))`, where `name` is the
    272     python name of the object whose documentation page the reference
    273     lives on.
    274 
    275     Args:
    276       string: A string in which "@{symbol}" references should be replaced.
    277       relative_path_to_root: The relative path from the containing document to
    278         the root of the API documentation that is being linked to.
    279 
    280     Returns:
    281       `string`, with "@{symbol}" references replaced by Markdown links.
    282     """
    283 
    284     def strict_one_ref(match):
    285       try:
    286         return self._one_ref(match, relative_path_to_root)
    287       except TFDocsError as e:
    288         self.add_error(e.message)
    289         return 'BAD_LINK'
    290 
    291     string = re.sub(SYMBOL_REFERENCE_RE, strict_one_ref, string)
    292 
    293     def sloppy_one_ref(match):
    294       try:
    295         return self._one_ref(match, relative_path_to_root)
    296       except TFDocsError:
    297         return match.group(0)
    298 
    299     string = re.sub(AUTO_REFERENCE_RE, sloppy_one_ref, string)
    300 
    301     return string
    302 
    303   def python_link(self, link_text, ref_full_name, relative_path_to_root,
    304                   code_ref=True):
    305     """Resolve a "@{python symbol}" reference to a Markdown link.
    306 
    307     This will pick the canonical location for duplicate symbols.  The
    308     input to this function should already be stripped of the '@' and
    309     '{}'.  This function returns a Markdown link. If `code_ref` is
    310     true, it is assumed that this is a code reference, so the link
    311     text will be rendered as code (using backticks).
    312     `link_text` should refer to a library symbol, starting with 'tf.'.
    313 
    314     Args:
    315       link_text: The text of the Markdown link.
    316       ref_full_name: The fully qualified name of the symbol to link to.
    317       relative_path_to_root: The relative path from the location of the current
    318         document to the root of the API documentation.
    319       code_ref: If true (the default), put `link_text` in `...`.
    320 
    321     Returns:
    322       A markdown link to the documentation page of `ref_full_name`.
    323     """
    324     url = self.reference_to_url(ref_full_name, relative_path_to_root)
    325 
    326     if code_ref:
    327       link_text = link_text.join(['<code>', '</code>'])
    328     else:
    329       link_text = self._link_text_to_html(link_text)
    330 
    331     return '<a href="{}">{}</a>'.format(url, link_text)
    332 
    333   @staticmethod
    334   def _link_text_to_html(link_text):
    335     code_re = '`(.*?)`'
    336     return re.sub(code_re, r'<code>\1</code>', link_text)
    337 
    338   def py_master_name(self, full_name):
    339     """Return the master name for a Python symbol name."""
    340     return self._duplicate_of.get(full_name, full_name)
    341 
    342   def reference_to_url(self, ref_full_name, relative_path_to_root):
    343     """Resolve a "@{python symbol}" reference to a relative path.
    344 
    345     The input to this function should already be stripped of the '@'
    346     and '{}', and its output is only the link, not the full Markdown.
    347 
    348     If `ref_full_name` is the name of a class member, method, or property, the
    349     link will point to the page of the containing class, and it will include the
    350     method name as an anchor. For example, `tf.module.MyClass.my_method` will be
    351     translated into a link to
    352     `os.join.path(relative_path_to_root, 'tf/module/MyClass.md#my_method')`.
    353 
    354     Args:
    355       ref_full_name: The fully qualified name of the symbol to link to.
    356       relative_path_to_root: The relative path from the location of the current
    357         document to the root of the API documentation.
    358 
    359     Returns:
    360       A relative path that links from the documentation page of `from_full_name`
    361       to the documentation page of `ref_full_name`.
    362 
    363     Raises:
    364       RuntimeError: If `ref_full_name` is not documented.
    365       TFDocsError: If the @{} syntax cannot be decoded.
    366     """
    367     master_name = self._duplicate_of.get(ref_full_name, ref_full_name)
    368 
    369     # Check whether this link exists
    370     if master_name not in self._all_names:
    371       raise TFDocsError(
    372           'Cannot make link to "%s": Not in index.' % master_name)
    373 
    374     ref_path = documentation_path(master_name, self._is_fragment[master_name])
    375     return os.path.join(relative_path_to_root, ref_path)
    376 
    377   def _one_ref(self, match, relative_path_to_root):
    378     """Return a link for a single "@{symbol}" reference."""
    379     string = match.group(1)
    380 
    381     # Look for link text after $.
    382     dollar = string.rfind('$')
    383     if dollar > 0:  # Ignore $ in first character
    384       link_text = string[dollar + 1:]
    385       string = string[:dollar]
    386       manual_link_text = True
    387     else:
    388       link_text = string
    389       manual_link_text = False
    390 
    391     # Handle different types of references.
    392     if string.startswith('$'):  # Doc reference
    393       return self._doc_link(string, link_text, manual_link_text,
    394                             relative_path_to_root)
    395 
    396     elif string.startswith('tensorflow::'):
    397       # C++ symbol
    398       return self._cc_link(string, link_text, manual_link_text,
    399                            relative_path_to_root)
    400 
    401     else:
    402       is_python = False
    403       for py_module_name in self._py_module_names:
    404         if string == py_module_name or string.startswith(py_module_name + '.'):
    405           is_python = True
    406           break
    407       if is_python:  # Python symbol
    408         return self.python_link(
    409             link_text,
    410             string,
    411             relative_path_to_root,
    412             code_ref=not manual_link_text)
    413 
    414     # Error!
    415     raise TFDocsError('Did not understand "%s"' % match.group(0),
    416                       'BROKEN_LINK')
    417 
    418   def _doc_link(self, string, link_text, manual_link_text,
    419                 relative_path_to_root):
    420     """Generate a link for a @{$...} reference."""
    421     string = string[1:]  # remove leading $
    422 
    423     # If string has a #, split that part into `hash_tag`
    424     hash_pos = string.find('#')
    425     if hash_pos > -1:
    426       hash_tag = string[hash_pos:]
    427       string = string[:hash_pos]
    428     else:
    429       hash_tag = ''
    430 
    431     if string in self._doc_index:
    432       if not manual_link_text: link_text = self._doc_index[string].title
    433       url = os.path.normpath(os.path.join(
    434           relative_path_to_root, '../..', self._doc_index[string].url))
    435       link_text = self._link_text_to_html(link_text)
    436       return '<a href="{}{}">{}</a>'.format(url, hash_tag, link_text)
    437 
    438     return self._doc_missing(string, hash_tag, link_text, manual_link_text,
    439                              relative_path_to_root)
    440 
    441   def _doc_missing(self, string, unused_hash_tag, unused_link_text,
    442                    unused_manual_link_text, unused_relative_path_to_root):
    443     """Generate an error for unrecognized @{$...} references."""
    444     raise TFDocsError('Unknown Document "%s"' % string)
    445 
    446   def _cc_link(self, string, link_text, unused_manual_link_text,
    447                relative_path_to_root):
    448     """Generate a link for a @{tensorflow::...} reference."""
    449     # TODO(josh11b): Fix this hard-coding of paths.
    450     if string == 'tensorflow::ClientSession':
    451       ret = 'class/tensorflow/client-session.md'
    452     elif string == 'tensorflow::Scope':
    453       ret = 'class/tensorflow/scope.md'
    454     elif string == 'tensorflow::Status':
    455       ret = 'class/tensorflow/status.md'
    456     elif string == 'tensorflow::Tensor':
    457       ret = 'class/tensorflow/tensor.md'
    458     elif string == 'tensorflow::ops::Const':
    459       ret = 'namespace/tensorflow/ops.md#const'
    460     else:
    461       raise TFDocsError('C++ reference not understood: "%s"' % string)
    462 
    463     # relative_path_to_root gets you to api_docs/python, we go from there
    464     # to api_docs/cc, and then add ret.
    465     cc_relative_path = os.path.normpath(os.path.join(
    466         relative_path_to_root, '../cc', ret))
    467 
    468     return '<a href="{}"><code>{}</code></a>'.format(cc_relative_path,
    469                                                      link_text)
    470 
    471 
    472 # TODO(aselle): Collect these into a big list for all modules and functions
    473 # and make a rosetta stone page.
    474 def _handle_compatibility(doc):
    475   """Parse and remove compatibility blocks from the main docstring.
    476 
    477   Args:
    478     doc: The docstring that contains compatibility notes"
    479 
    480   Returns:
    481     a tuple of the modified doc string and a hash that maps from compatibility
    482     note type to the text of the note.
    483   """
    484   compatibility_notes = {}
    485   match_compatibility = re.compile(r'[ \t]*@compatibility\((\w+)\)\s*\n'
    486                                    r'((?:[^@\n]*\n)+)'
    487                                    r'\s*@end_compatibility')
    488   for f in match_compatibility.finditer(doc):
    489     compatibility_notes[f.group(1)] = f.group(2)
    490   return match_compatibility.subn(r'', doc)[0], compatibility_notes
    491 
    492 
    493 def _gen_pairs(items):
    494   """Given an list of items [a,b,a,b...], generate pairs [(a,b),(a,b)...].
    495 
    496   Args:
    497     items: A list of items (length must be even)
    498 
    499   Yields:
    500     The original items, in pairs
    501   """
    502   assert len(items) % 2 == 0
    503   items = iter(items)
    504   while True:
    505     try:
    506       yield next(items), next(items)
    507     except StopIteration:
    508       return
    509 
    510 
    511 class _FunctionDetail(
    512     collections.namedtuple('_FunctionDetail', ['keyword', 'header', 'items'])):
    513   """A simple class to contain function details.
    514 
    515   Composed of a "keyword", a possibly empty "header" string, and a possibly
    516   empty
    517   list of key-value pair "items".
    518   """
    519   __slots__ = []
    520 
    521   def __str__(self):
    522     """Return the original string that represents the function detail."""
    523     parts = [self.keyword + ':\n']
    524     parts.append(self.header)
    525     for key, value in self.items:
    526       parts.append('  ' + key + ': ')
    527       parts.append(value)
    528 
    529     return ''.join(parts)
    530 
    531 
    532 def _parse_function_details(docstring):
    533   r"""Given a docstring, split off the header and parse the function details.
    534 
    535   For example the docstring of tf.nn.relu:
    536 
    537   '''Computes rectified linear: `max(features, 0)`.
    538 
    539   Args:
    540     features: A `Tensor`. Must be one of the following types: `float32`,
    541       `float64`, `int32`, `int64`, `uint8`, `int16`, `int8`, `uint16`,
    542       `half`.
    543     name: A name for the operation (optional).
    544 
    545   Returns:
    546     A `Tensor`. Has the same type as `features`.
    547   '''
    548 
    549   This is parsed, and returned as:
    550 
    551   ```
    552   ('Computes rectified linear: `max(features, 0)`.\n\n', [
    553       _FunctionDetail(
    554           keyword='Args',
    555           header='',
    556           items=[
    557               ('features', ' A `Tensor`. Must be ...'),
    558               ('name', ' A name for the operation (optional).\n\n')]),
    559       _FunctionDetail(
    560           keyword='Returns',
    561           header='  A `Tensor`. Has the same type as `features`.',
    562           items=[])
    563   ])
    564   ```
    565 
    566   Args:
    567     docstring: The docstring to parse
    568 
    569   Returns:
    570     A (header, function_details) pair, where header is a string and
    571     function_details is a (possibly empty) list of `_FunctionDetail` objects.
    572   """
    573 
    574   detail_keywords = '|'.join([
    575       'Args', 'Arguments', 'Fields', 'Returns', 'Yields', 'Raises', 'Attributes'
    576   ])
    577   tag_re = re.compile('(?<=\n)(' + detail_keywords + '):\n', re.MULTILINE)
    578   parts = tag_re.split(docstring)
    579 
    580   # The first part is the main docstring
    581   docstring = parts[0]
    582 
    583   # Everything else alternates keyword-content
    584   pairs = list(_gen_pairs(parts[1:]))
    585 
    586   function_details = []
    587   item_re = re.compile(r'^   ? ?(\*?\*?\w[\w.]*?\s*):\s', re.MULTILINE)
    588 
    589   for keyword, content in pairs:
    590     content = item_re.split(content)
    591     header = content[0]
    592     items = list(_gen_pairs(content[1:]))
    593 
    594     function_details.append(_FunctionDetail(keyword, header, items))
    595 
    596   return docstring, function_details
    597 
    598 
    599 _DocstringInfo = collections.namedtuple('_DocstringInfo', [
    600     'brief', 'docstring', 'function_details', 'compatibility'
    601 ])
    602 
    603 
    604 def _parse_md_docstring(py_object, relative_path_to_root, reference_resolver):
    605   """Parse the object's docstring and return a `_DocstringInfo`.
    606 
    607   This function clears @@'s from the docstring, and replaces @{} references
    608   with markdown links.
    609 
    610   For links within the same set of docs, the `relative_path_to_root` for a
    611   docstring on the page for `full_name` can be set to:
    612 
    613   ```python
    614   relative_path_to_root = os.path.relpath(
    615     path='.', start=os.path.dirname(documentation_path(full_name)) or '.')
    616   ```
    617 
    618   Args:
    619     py_object: A python object to retrieve the docs for (class, function/method,
    620       or module).
    621     relative_path_to_root: The relative path from the location of the current
    622       document to the root of the Python API documentation. This is used to
    623       compute links for "@{symbol}" references.
    624     reference_resolver: An instance of ReferenceResolver.
    625 
    626   Returns:
    627     A _DocstringInfo object, all fields will be empty if no docstring was found.
    628   """
    629   # TODO(wicke): If this is a partial, use the .func docstring and add a note.
    630   raw_docstring = _get_raw_docstring(py_object)
    631 
    632   raw_docstring = reference_resolver.replace_references(
    633       raw_docstring, relative_path_to_root)
    634 
    635   atat_re = re.compile(r' *@@[a-zA-Z_.0-9]+ *$')
    636   raw_docstring = '\n'.join(
    637       line for line in raw_docstring.split('\n') if not atat_re.match(line))
    638 
    639   docstring, compatibility = _handle_compatibility(raw_docstring)
    640   docstring, function_details = _parse_function_details(docstring)
    641 
    642   if 'Generated by: tensorflow/tools/api/generator' in docstring:
    643     docstring = ''
    644 
    645   return _DocstringInfo(
    646       docstring.split('\n')[0], docstring, function_details, compatibility)
    647 
    648 
    649 def _get_arg_spec(func):
    650   """Extracts signature information from a function or functools.partial object.
    651 
    652   For functions, uses `tf_inspect.getfullargspec`. For `functools.partial`
    653   objects, corrects the signature of the underlying function to take into
    654   account the removed arguments.
    655 
    656   Args:
    657     func: A function whose signature to extract.
    658 
    659   Returns:
    660     An `FullArgSpec` namedtuple `(args, varargs, varkw, defaults, etc.)`,
    661     as returned by `tf_inspect.getfullargspec`.
    662   """
    663   # getfullargspec does not work for functools.partial objects directly.
    664   if isinstance(func, functools.partial):
    665     argspec = tf_inspect.getfullargspec(func.func)
    666     # Remove the args from the original function that have been used up.
    667     first_default_arg = (
    668         len(argspec.args or []) - len(argspec.defaults or []))
    669     partial_args = len(func.args)
    670     argspec_args = []
    671 
    672     if argspec.args:
    673       argspec_args = list(argspec.args[partial_args:])
    674 
    675     argspec_defaults = list(argspec.defaults or ())
    676     if argspec.defaults and partial_args > first_default_arg:
    677       argspec_defaults = list(argspec.defaults[partial_args-first_default_arg:])
    678 
    679     first_default_arg = max(0, first_default_arg - partial_args)
    680     for kwarg in (func.keywords or []):
    681       if kwarg in (argspec.args or []):
    682         i = argspec_args.index(kwarg)
    683         argspec_args.pop(i)
    684         if i >= first_default_arg:
    685           argspec_defaults.pop(i-first_default_arg)
    686         else:
    687           first_default_arg -= 1
    688     return tf_inspect.FullArgSpec(
    689         args=argspec_args,
    690         varargs=argspec.varargs,
    691         varkw=argspec.varkw,
    692         defaults=tuple(argspec_defaults),
    693         kwonlyargs=[],
    694         kwonlydefaults=None,
    695         annotations={})
    696   else:  # Regular function or method, getargspec will work fine.
    697     return tf_inspect.getfullargspec(func)
    698 
    699 
    700 def _remove_first_line_indent(string):
    701   indent = len(re.match(r'^\s*', string).group(0))
    702   return '\n'.join([line[indent:] for line in string.split('\n')])
    703 
    704 
    705 PAREN_NUMBER_RE = re.compile(r'^\(([0-9.e-]+)\)')
    706 
    707 
    708 def _generate_signature(func, reverse_index):
    709   """Given a function, returns a list of strings representing its args.
    710 
    711   This function produces a list of strings representing the arguments to a
    712   python function. It uses tf_inspect.getfullargspec, which
    713   does not generalize well to Python 3.x, which is more flexible in how *args
    714   and **kwargs are handled. This is not a problem in TF, since we have to remain
    715   compatible to Python 2.7 anyway.
    716 
    717   This function uses `__name__` for callables if it is available. This can lead
    718   to poor results for functools.partial and other callable objects.
    719 
    720   The returned string is Python code, so if it is included in a Markdown
    721   document, it should be typeset as code (using backticks), or escaped.
    722 
    723   Args:
    724     func: A function, method, or functools.partial to extract the signature for.
    725     reverse_index: A map from object ids to canonical full names to use.
    726 
    727   Returns:
    728     A list of strings representing the argument signature of `func` as python
    729     code.
    730   """
    731 
    732   args_list = []
    733 
    734   argspec = _get_arg_spec(func)
    735   first_arg_with_default = (
    736       len(argspec.args or []) - len(argspec.defaults or []))
    737 
    738   # Python documentation skips `self` when printing method signatures.
    739   # Note we cannot test for ismethod here since unbound methods do not register
    740   # as methods (in Python 3).
    741   first_arg = 1 if 'self' in argspec.args[:1] else 0
    742 
    743   # Add all args without defaults.
    744   for arg in argspec.args[first_arg:first_arg_with_default]:
    745     args_list.append(arg)
    746 
    747   # Add all args with defaults.
    748   if argspec.defaults:
    749     try:
    750       source = _remove_first_line_indent(tf_inspect.getsource(func))
    751       func_ast = ast.parse(source)
    752       ast_defaults = func_ast.body[0].args.defaults
    753     except IOError:  # If this is a builtin, getsource fails with IOError
    754       # If we cannot get the source, assume the AST would be equal to the repr
    755       # of the defaults.
    756       ast_defaults = [None] * len(argspec.defaults)
    757 
    758     for arg, default, ast_default in zip(
    759         argspec.args[first_arg_with_default:], argspec.defaults, ast_defaults):
    760       if id(default) in reverse_index:
    761         default_text = reverse_index[id(default)]
    762       elif ast_default is not None:
    763         default_text = (
    764             astor.to_source(ast_default).rstrip('\n').replace('\t', '\\t')
    765             .replace('\n', '\\n').replace('"""', "'"))
    766         default_text = PAREN_NUMBER_RE.sub('\\1', default_text)
    767 
    768         if default_text != repr(default):
    769           # This may be an internal name. If so, handle the ones we know about.
    770           # TODO(wicke): This should be replaced with a lookup in the index.
    771           # TODO(wicke): (replace first ident with tf., check if in index)
    772           internal_names = {
    773               'ops.GraphKeys': 'tf.GraphKeys',
    774               '_ops.GraphKeys': 'tf.GraphKeys',
    775               'init_ops.zeros_initializer': 'tf.zeros_initializer',
    776               'init_ops.ones_initializer': 'tf.ones_initializer',
    777               'saver_pb2.SaverDef': 'tf.train.SaverDef',
    778           }
    779           full_name_re = '^%s(.%s)+' % (IDENTIFIER_RE, IDENTIFIER_RE)
    780           match = re.match(full_name_re, default_text)
    781           if match:
    782             lookup_text = default_text
    783             for internal_name, public_name in six.iteritems(internal_names):
    784               if match.group(0).startswith(internal_name):
    785                 lookup_text = public_name + default_text[len(internal_name):]
    786                 break
    787             if default_text is lookup_text:
    788               logging.warn(
    789                   'WARNING: Using default arg, failed lookup: %s, repr: %r',
    790                   default_text, default)
    791             else:
    792               default_text = lookup_text
    793       else:
    794         default_text = repr(default)
    795 
    796       args_list.append('%s=%s' % (arg, default_text))
    797 
    798   # Add *args and *kwargs.
    799   if argspec.varargs:
    800     args_list.append('*' + argspec.varargs)
    801   if argspec.varkw:
    802     args_list.append('**' + argspec.varkw)
    803 
    804   return args_list
    805 
    806 
    807 def _get_guides_markdown(duplicate_names, guide_index, relative_path):
    808   all_guides = []
    809   for name in duplicate_names:
    810     all_guides.extend(guide_index.get(name, []))
    811   if not all_guides: return ''
    812   prefix = '../' * (relative_path.count('/') + 3)
    813   links = sorted(set([guide_ref.make_md_link(prefix)
    814                       for guide_ref in all_guides]))
    815   return 'See the guide%s: %s\n\n' % (
    816       's' if len(links) > 1 else '', ', '.join(links))
    817 
    818 
    819 def _get_defining_class(py_class, name):
    820   for cls in tf_inspect.getmro(py_class):
    821     if name in cls.__dict__:
    822       return cls
    823   return None
    824 
    825 
    826 class _LinkInfo(
    827     collections.namedtuple(
    828         '_LinkInfo', ['short_name', 'full_name', 'obj', 'doc', 'url'])):
    829 
    830   __slots__ = []
    831 
    832   def is_link(self):
    833     return True
    834 
    835 
    836 class _OtherMemberInfo(
    837     collections.namedtuple('_OtherMemberInfo',
    838                            ['short_name', 'full_name', 'obj', 'doc'])):
    839 
    840   __slots__ = []
    841 
    842   def is_link(self):
    843     return False
    844 
    845 
    846 _PropertyInfo = collections.namedtuple(
    847     '_PropertyInfo', ['short_name', 'full_name', 'obj', 'doc'])
    848 
    849 _MethodInfo = collections.namedtuple('_MethodInfo', [
    850     'short_name', 'full_name', 'obj', 'doc', 'signature', 'decorators'
    851 ])
    852 
    853 
    854 class _FunctionPageInfo(object):
    855   """Collects docs For a function Page."""
    856 
    857   def __init__(self, full_name):
    858     self._full_name = full_name
    859     self._defined_in = None
    860     self._aliases = None
    861     self._doc = None
    862     self._guides = None
    863 
    864     self._signature = None
    865     self._decorators = []
    866 
    867   def for_function(self):
    868     return True
    869 
    870   def for_class(self):
    871     return False
    872 
    873   def for_module(self):
    874     return False
    875 
    876   @property
    877   def full_name(self):
    878     return self._full_name
    879 
    880   @property
    881   def short_name(self):
    882     return self._full_name.split('.')[-1]
    883 
    884   @property
    885   def defined_in(self):
    886     return self._defined_in
    887 
    888   def set_defined_in(self, defined_in):
    889     assert self.defined_in is None
    890     self._defined_in = defined_in
    891 
    892   @property
    893   def aliases(self):
    894     return self._aliases
    895 
    896   def set_aliases(self, aliases):
    897     assert self.aliases is None
    898     self._aliases = aliases
    899 
    900   @property
    901   def doc(self):
    902     return self._doc
    903 
    904   def set_doc(self, doc):
    905     assert self.doc is None
    906     self._doc = doc
    907 
    908   @property
    909   def guides(self):
    910     return self._guides
    911 
    912   def set_guides(self, guides):
    913     assert self.guides is None
    914     self._guides = guides
    915 
    916   @property
    917   def signature(self):
    918     return self._signature
    919 
    920   def set_signature(self, function, reverse_index):
    921     """Attach the function's signature.
    922 
    923     Args:
    924       function: The python function being documented.
    925       reverse_index: A map from object ids in the index to full names.
    926     """
    927 
    928     assert self.signature is None
    929     self._signature = _generate_signature(function, reverse_index)
    930 
    931   @property
    932   def decorators(self):
    933     return list(self._decorators)
    934 
    935   def add_decorator(self, dec):
    936     self._decorators.append(dec)
    937 
    938   def get_metadata_html(self):
    939     return _Metadata(self.full_name).build_html()
    940 
    941 
    942 class _ClassPageInfo(object):
    943   """Collects docs for a class page.
    944 
    945   Attributes:
    946     full_name: The fully qualified name of the object at the master
    947       location. Aka `master_name`. For example: `tf.nn.sigmoid`.
    948     short_name: The last component of the `full_name`. For example: `sigmoid`.
    949     defined_in: The path to the file where this object is defined.
    950     aliases: The list of all fully qualified names for the locations where the
    951       object is visible in the public api. This includes the master location.
    952     doc: A `_DocstringInfo` object representing the object's docstring (can be
    953       created with `_parse_md_docstring`).
    954     guides: A markdown string, of back links pointing to the api_guides that
    955       reference this object.
    956     bases: A list of `_LinkInfo` objects pointing to the docs for the parent
    957       classes.
    958     properties: A list of `_PropertyInfo` objects documenting the class'
    959       properties (attributes that use `@property`).
    960     methods: A list of `_MethodInfo` objects documenting the class' methods.
    961     classes: A list of `_LinkInfo` objects pointing to docs for any nested
    962       classes.
    963     other_members: A list of `_OtherMemberInfo` objects documenting any other
    964       object's defined inside the class object (mostly enum style fields).
    965   """
    966 
    967   def __init__(self, full_name):
    968     self._full_name = full_name
    969     self._defined_in = None
    970     self._aliases = None
    971     self._doc = None
    972     self._guides = None
    973     self._namedtuplefields = None
    974 
    975     self._bases = None
    976     self._properties = []
    977     self._methods = []
    978     self._classes = []
    979     self._other_members = []
    980 
    981   def for_function(self):
    982     """Returns true if this object documents a function."""
    983     return False
    984 
    985   def for_class(self):
    986     """Returns true if this object documents a class."""
    987     return True
    988 
    989   def for_module(self):
    990     """Returns true if this object documents a module."""
    991     return False
    992 
    993   @property
    994   def full_name(self):
    995     """Returns the documented object's fully qualified name."""
    996     return self._full_name
    997 
    998   @property
    999   def short_name(self):
   1000     """Returns the documented object's short name."""
   1001     return self._full_name.split('.')[-1]
   1002 
   1003   @property
   1004   def defined_in(self):
   1005     """Returns the path to the file where the documented object is defined."""
   1006     return self._defined_in
   1007 
   1008   def set_defined_in(self, defined_in):
   1009     """Sets the `defined_in` path."""
   1010     assert self.defined_in is None
   1011     self._defined_in = defined_in
   1012 
   1013   @property
   1014   def aliases(self):
   1015     """Returns a list of all full names for the documented object."""
   1016     return self._aliases
   1017 
   1018   def set_aliases(self, aliases):
   1019     """Sets the `aliases` list.
   1020 
   1021     Args:
   1022       aliases: A list of strings. Containing all the object's full names.
   1023     """
   1024     assert self.aliases is None
   1025     self._aliases = aliases
   1026 
   1027   @property
   1028   def doc(self):
   1029     """Returns a `_DocstringInfo` created from the object's docstring."""
   1030     return self._doc
   1031 
   1032   def set_doc(self, doc):
   1033     """Sets the `doc` field.
   1034 
   1035     Args:
   1036       doc: An instance of `_DocstringInfo`.
   1037     """
   1038     assert self.doc is None
   1039     self._doc = doc
   1040 
   1041   @property
   1042   def guides(self):
   1043     """Returns a markdown string containing backlinks to relevant api_guides."""
   1044     return self._guides
   1045 
   1046   def set_guides(self, guides):
   1047     """Sets the `guides` field.
   1048 
   1049     Args:
   1050       guides: A markdown string containing backlinks to all the api_guides that
   1051         link to the documented object.
   1052     """
   1053     assert self.guides is None
   1054     self._guides = guides
   1055 
   1056   @property
   1057   def namedtuplefields(self):
   1058     return self._namedtuplefields
   1059 
   1060   def set_namedtuplefields(self, py_class):
   1061     if issubclass(py_class, tuple):
   1062       if all(
   1063           hasattr(py_class, attr)
   1064           for attr in ('_asdict', '_fields', '_make', '_replace')):
   1065         self._namedtuplefields = py_class._fields
   1066 
   1067   @property
   1068   def bases(self):
   1069     """Returns a list of `_LinkInfo` objects pointing to the class' parents."""
   1070     return self._bases
   1071 
   1072   def _set_bases(self, relative_path, parser_config):
   1073     """Builds the `bases` attribute, to document this class' parent-classes.
   1074 
   1075     This method sets the `bases` to a list of `_LinkInfo` objects point to the
   1076     doc pages for the class' parents.
   1077 
   1078     Args:
   1079       relative_path: The relative path from the doc this object describes to
   1080         the documentation root.
   1081       parser_config: An instance of `ParserConfig`.
   1082     """
   1083     bases = []
   1084     obj = parser_config.py_name_to_object(self.full_name)
   1085     for base in obj.__bases__:
   1086       base_full_name = parser_config.reverse_index.get(id(base), None)
   1087       if base_full_name is None:
   1088         continue
   1089       base_doc = _parse_md_docstring(base, relative_path,
   1090                                      parser_config.reference_resolver)
   1091       base_url = parser_config.reference_resolver.reference_to_url(
   1092           base_full_name, relative_path)
   1093 
   1094       link_info = _LinkInfo(short_name=base_full_name.split('.')[-1],
   1095                             full_name=base_full_name, obj=base,
   1096                             doc=base_doc, url=base_url)
   1097       bases.append(link_info)
   1098 
   1099     self._bases = bases
   1100 
   1101   @property
   1102   def properties(self):
   1103     """Returns a list of `_PropertyInfo` describing the class' properties."""
   1104     props_dict = {prop.short_name: prop for prop in self._properties}
   1105     props = []
   1106     if self.namedtuplefields:
   1107       for field in self.namedtuplefields:
   1108         props.append(props_dict.pop(field))
   1109 
   1110     props.extend(sorted(props_dict.values()))
   1111 
   1112     return props
   1113 
   1114   def _add_property(self, short_name, full_name, obj, doc):
   1115     """Adds a `_PropertyInfo` entry to the `properties` list.
   1116 
   1117     Args:
   1118       short_name: The property's short name.
   1119       full_name: The property's fully qualified name.
   1120       obj: The property object itself
   1121       doc: The property's parsed docstring, a `_DocstringInfo`.
   1122     """
   1123     # Hide useless namedtuple docs-trings
   1124     if re.match('Alias for field number [0-9]+', doc.docstring):
   1125       doc = doc._replace(docstring='', brief='')
   1126     property_info = _PropertyInfo(short_name, full_name, obj, doc)
   1127     self._properties.append(property_info)
   1128 
   1129   @property
   1130   def methods(self):
   1131     """Returns a list of `_MethodInfo` describing the class' methods."""
   1132     return self._methods
   1133 
   1134   def _add_method(self, short_name, full_name, obj, doc, signature, decorators):
   1135     """Adds a `_MethodInfo` entry to the `methods` list.
   1136 
   1137     Args:
   1138       short_name: The method's short name.
   1139       full_name: The method's fully qualified name.
   1140       obj: The method object itself
   1141       doc: The method's parsed docstring, a `_DocstringInfo`
   1142       signature: The method's parsed signature (see: `_generate_signature`)
   1143       decorators: A list of strings describing the decorators that should be
   1144         mentioned on the object's docs page.
   1145     """
   1146 
   1147     method_info = _MethodInfo(short_name, full_name, obj, doc, signature,
   1148                               decorators)
   1149 
   1150     self._methods.append(method_info)
   1151 
   1152   @property
   1153   def classes(self):
   1154     """Returns a list of `_LinkInfo` pointing to any nested classes."""
   1155     return self._classes
   1156 
   1157   def get_metadata_html(self):
   1158     meta_data = _Metadata(self.full_name)
   1159     for item in itertools.chain(self.classes, self.properties, self.methods,
   1160                                 self.other_members):
   1161       meta_data.append(item)
   1162 
   1163     return meta_data.build_html()
   1164 
   1165   def _add_class(self, short_name, full_name, obj, doc, url):
   1166     """Adds a `_LinkInfo` for a nested class to `classes` list.
   1167 
   1168     Args:
   1169       short_name: The class' short name.
   1170       full_name: The class' fully qualified name.
   1171       obj: The class object itself
   1172       doc: The class' parsed docstring, a `_DocstringInfo`
   1173       url: A url pointing to where the nested class is documented.
   1174     """
   1175     page_info = _LinkInfo(short_name, full_name, obj, doc, url)
   1176 
   1177     self._classes.append(page_info)
   1178 
   1179   @property
   1180   def other_members(self):
   1181     """Returns a list of `_OtherMemberInfo` describing any other contents."""
   1182     return self._other_members
   1183 
   1184   def _add_other_member(self, short_name, full_name, obj, doc):
   1185     """Adds an `_OtherMemberInfo` entry to the `other_members` list.
   1186 
   1187     Args:
   1188       short_name: The class' short name.
   1189       full_name: The class' fully qualified name.
   1190       obj: The class object itself
   1191       doc: The class' parsed docstring, a `_DocstringInfo`
   1192     """
   1193     other_member_info = _OtherMemberInfo(short_name, full_name, obj, doc)
   1194     self._other_members.append(other_member_info)
   1195 
   1196   def collect_docs_for_class(self, py_class, parser_config):
   1197     """Collects information necessary specifically for a class's doc page.
   1198 
   1199     Mainly, this is details about the class's members.
   1200 
   1201     Args:
   1202       py_class: The class object being documented
   1203       parser_config: An instance of ParserConfig.
   1204     """
   1205     self.set_namedtuplefields(py_class)
   1206     doc_path = documentation_path(self.full_name)
   1207     relative_path = os.path.relpath(
   1208         path='.', start=os.path.dirname(doc_path) or '.')
   1209 
   1210     self._set_bases(relative_path, parser_config)
   1211 
   1212     for short_name in parser_config.tree[self.full_name]:
   1213       # Remove builtin members that we never want to document.
   1214       if short_name in [
   1215           '__class__', '__base__', '__weakref__', '__doc__', '__module__',
   1216           '__dict__', '__abstractmethods__', '__slots__', '__getnewargs__',
   1217           '__str__', '__repr__', '__hash__', '__reduce__'
   1218       ]:
   1219         continue
   1220 
   1221       child_name = '.'.join([self.full_name, short_name])
   1222       child = parser_config.py_name_to_object(child_name)
   1223 
   1224       # Don't document anything that is defined in object or by protobuf.
   1225       defining_class = _get_defining_class(py_class, short_name)
   1226       if defining_class in [object, type, tuple, BaseException, Exception]:
   1227         continue
   1228 
   1229       # The following condition excludes most protobuf-defined symbols.
   1230       if (defining_class and
   1231           defining_class.__name__ in ['CMessage', 'Message', 'MessageMeta']):
   1232         continue
   1233       # TODO(markdaoust): Add a note in child docs showing the defining class.
   1234 
   1235       if doc_controls.should_skip_class_attr(py_class, short_name):
   1236         continue
   1237 
   1238       child_doc = _parse_md_docstring(child, relative_path,
   1239                                       parser_config.reference_resolver)
   1240 
   1241       if isinstance(child, property):
   1242         self._add_property(short_name, child_name, child, child_doc)
   1243 
   1244       elif tf_inspect.isclass(child):
   1245         if defining_class is None:
   1246           continue
   1247         url = parser_config.reference_resolver.reference_to_url(
   1248             child_name, relative_path)
   1249         self._add_class(short_name, child_name, child, child_doc, url)
   1250 
   1251       elif (tf_inspect.ismethod(child) or tf_inspect.isfunction(child) or
   1252             tf_inspect.isroutine(child)):
   1253         if defining_class is None:
   1254           continue
   1255 
   1256         # Omit methods defined by namedtuple.
   1257         original_method = defining_class.__dict__[short_name]
   1258         if (hasattr(original_method, '__module__') and
   1259             (original_method.__module__ or '').startswith('namedtuple')):
   1260           continue
   1261 
   1262         # Some methods are often overridden without documentation. Because it's
   1263         # obvious what they do, don't include them in the docs if there's no
   1264         # docstring.
   1265         if not child_doc.brief.strip() and short_name in [
   1266             '__del__', '__copy__'
   1267         ]:
   1268           continue
   1269 
   1270         try:
   1271           child_signature = _generate_signature(child,
   1272                                                 parser_config.reverse_index)
   1273         except TypeError:
   1274           # If this is a (dynamically created) slot wrapper, tf_inspect will
   1275           # raise typeerror when trying to get to the code. Ignore such
   1276           # functions.
   1277           continue
   1278 
   1279         child_decorators = []
   1280         try:
   1281           if isinstance(py_class.__dict__[short_name], classmethod):
   1282             child_decorators.append('classmethod')
   1283         except KeyError:
   1284           pass
   1285 
   1286         try:
   1287           if isinstance(py_class.__dict__[short_name], staticmethod):
   1288             child_decorators.append('staticmethod')
   1289         except KeyError:
   1290           pass
   1291 
   1292         self._add_method(short_name, child_name, child, child_doc,
   1293                          child_signature, child_decorators)
   1294       else:
   1295         # Exclude members defined by protobuf that are useless
   1296         if issubclass(py_class, ProtoMessage):
   1297           if (short_name.endswith('_FIELD_NUMBER') or
   1298               short_name in ['__slots__', 'DESCRIPTOR']):
   1299             continue
   1300 
   1301         # TODO(wicke): We may want to also remember the object itself.
   1302         self._add_other_member(short_name, child_name, child, child_doc)
   1303 
   1304 
   1305 class _ModulePageInfo(object):
   1306   """Collects docs for a module page."""
   1307 
   1308   def __init__(self, full_name):
   1309     self._full_name = full_name
   1310     self._defined_in = None
   1311     self._aliases = None
   1312     self._doc = None
   1313     self._guides = None
   1314 
   1315     self._modules = []
   1316     self._classes = []
   1317     self._functions = []
   1318     self._other_members = []
   1319 
   1320   def for_function(self):
   1321     return False
   1322 
   1323   def for_class(self):
   1324     return False
   1325 
   1326   def for_module(self):
   1327     return True
   1328 
   1329   @property
   1330   def full_name(self):
   1331     return self._full_name
   1332 
   1333   @property
   1334   def short_name(self):
   1335     return self._full_name.split('.')[-1]
   1336 
   1337   @property
   1338   def defined_in(self):
   1339     return self._defined_in
   1340 
   1341   def set_defined_in(self, defined_in):
   1342     assert self.defined_in is None
   1343     self._defined_in = defined_in
   1344 
   1345   @property
   1346   def aliases(self):
   1347     return self._aliases
   1348 
   1349   def set_aliases(self, aliases):
   1350     assert self.aliases is None
   1351     self._aliases = aliases
   1352 
   1353   @property
   1354   def doc(self):
   1355     return self._doc
   1356 
   1357   def set_doc(self, doc):
   1358     assert self.doc is None
   1359     self._doc = doc
   1360 
   1361   @property
   1362   def guides(self):
   1363     return self._guides
   1364 
   1365   def set_guides(self, guides):
   1366     assert self.guides is None
   1367     self._guides = guides
   1368 
   1369   @property
   1370   def modules(self):
   1371     return self._modules
   1372 
   1373   def _add_module(self, short_name, full_name, obj, doc, url):
   1374     self._modules.append(_LinkInfo(short_name, full_name, obj, doc, url))
   1375 
   1376   @property
   1377   def classes(self):
   1378     return self._classes
   1379 
   1380   def _add_class(self, short_name, full_name, obj, doc, url):
   1381     self._classes.append(_LinkInfo(short_name, full_name, obj, doc, url))
   1382 
   1383   @property
   1384   def functions(self):
   1385     return self._functions
   1386 
   1387   def _add_function(self, short_name, full_name, obj, doc, url):
   1388     self._functions.append(_LinkInfo(short_name, full_name, obj, doc, url))
   1389 
   1390   @property
   1391   def other_members(self):
   1392     return self._other_members
   1393 
   1394   def _add_other_member(self, short_name, full_name, obj, doc):
   1395     self._other_members.append(
   1396         _OtherMemberInfo(short_name, full_name, obj, doc))
   1397 
   1398   def get_metadata_html(self):
   1399     meta_data = _Metadata(self.full_name)
   1400 
   1401     # Objects with their own pages are not added to the matadata list for the
   1402     # module, the module only has a link to the object page. No docs.
   1403     for item in self.other_members:
   1404       meta_data.append(item)
   1405 
   1406     return meta_data.build_html()
   1407 
   1408   def collect_docs_for_module(self, parser_config):
   1409     """Collect information necessary specifically for a module's doc page.
   1410 
   1411     Mainly this is information about the members of the module.
   1412 
   1413     Args:
   1414       parser_config: An instance of ParserConfig.
   1415     """
   1416     relative_path = os.path.relpath(
   1417         path='.',
   1418         start=os.path.dirname(documentation_path(self.full_name)) or '.')
   1419 
   1420     member_names = parser_config.tree.get(self.full_name, [])
   1421     for name in member_names:
   1422 
   1423       if name in ['__builtins__', '__doc__', '__file__',
   1424                   '__name__', '__path__', '__package__',
   1425                   '__cached__', '__loader__', '__spec__']:
   1426         continue
   1427 
   1428       member_full_name = self.full_name + '.' + name if self.full_name else name
   1429       member = parser_config.py_name_to_object(member_full_name)
   1430 
   1431       member_doc = _parse_md_docstring(member, relative_path,
   1432                                        parser_config.reference_resolver)
   1433 
   1434       url = parser_config.reference_resolver.reference_to_url(
   1435           member_full_name, relative_path)
   1436 
   1437       if tf_inspect.ismodule(member):
   1438         self._add_module(name, member_full_name, member, member_doc, url)
   1439 
   1440       elif tf_inspect.isclass(member):
   1441         self._add_class(name, member_full_name, member, member_doc, url)
   1442 
   1443       elif tf_inspect.isfunction(member):
   1444         self._add_function(name, member_full_name, member, member_doc, url)
   1445 
   1446       else:
   1447         self._add_other_member(name, member_full_name, member, member_doc)
   1448 
   1449 
   1450 class ParserConfig(object):
   1451   """Stores all indexes required to parse the docs."""
   1452 
   1453   def __init__(self, reference_resolver, duplicates, duplicate_of, tree, index,
   1454                reverse_index, guide_index, base_dir):
   1455     """Object with the common config for docs_for_object() calls.
   1456 
   1457     Args:
   1458       reference_resolver: An instance of ReferenceResolver.
   1459       duplicates: A `dict` mapping fully qualified names to a set of all
   1460         aliases of this name. This is used to automatically generate a list of
   1461         all aliases for each name.
   1462       duplicate_of: A map from duplicate names to preferred names of API
   1463         symbols.
   1464       tree: A `dict` mapping a fully qualified name to the names of all its
   1465         members. Used to populate the members section of a class or module page.
   1466       index: A `dict` mapping full names to objects.
   1467       reverse_index: A `dict` mapping object ids to full names.
   1468 
   1469       guide_index: A `dict` mapping symbol name strings to objects with a
   1470         `make_md_link()` method.
   1471 
   1472       base_dir: A base path that is stripped from file locations written to the
   1473         docs.
   1474     """
   1475     self.reference_resolver = reference_resolver
   1476     self.duplicates = duplicates
   1477     self.duplicate_of = duplicate_of
   1478     self.tree = tree
   1479     self.reverse_index = reverse_index
   1480     self.index = index
   1481     self.guide_index = guide_index
   1482     self.base_dir = base_dir
   1483     self.defined_in_prefix = 'tensorflow/'
   1484     self.code_url_prefix = (
   1485         '/code/stable/tensorflow/')  # pylint: disable=line-too-long
   1486 
   1487   def py_name_to_object(self, full_name):
   1488     """Return the Python object for a Python symbol name."""
   1489     return self.index[full_name]
   1490 
   1491 
   1492 def docs_for_object(full_name, py_object, parser_config):
   1493   """Return a PageInfo object describing a given object from the TF API.
   1494 
   1495   This function uses _parse_md_docstring to parse the docs pertaining to
   1496   `object`.
   1497 
   1498   This function resolves '@{symbol}' references in the docstrings into links to
   1499   the appropriate location. It also adds a list of alternative names for the
   1500   symbol automatically.
   1501 
   1502   It assumes that the docs for each object live in a file given by
   1503   `documentation_path`, and that relative links to files within the
   1504   documentation are resolvable.
   1505 
   1506   Args:
   1507     full_name: The fully qualified name of the symbol to be
   1508       documented.
   1509     py_object: The Python object to be documented. Its documentation is sourced
   1510       from `py_object`'s docstring.
   1511     parser_config: A ParserConfig object.
   1512 
   1513   Returns:
   1514     Either a `_FunctionPageInfo`, `_ClassPageInfo`, or a `_ModulePageInfo`
   1515     depending on the type of the python object being documented.
   1516 
   1517   Raises:
   1518     RuntimeError: If an object is encountered for which we don't know how
   1519       to make docs.
   1520   """
   1521 
   1522   # Which other aliases exist for the object referenced by full_name?
   1523   master_name = parser_config.reference_resolver.py_master_name(full_name)
   1524   duplicate_names = parser_config.duplicates.get(master_name, [full_name])
   1525 
   1526   # TODO(wicke): Once other pieces are ready, enable this also for partials.
   1527   if (tf_inspect.ismethod(py_object) or tf_inspect.isfunction(py_object) or
   1528       # Some methods in classes from extensions come in as routines.
   1529       tf_inspect.isroutine(py_object)):
   1530     page_info = _FunctionPageInfo(master_name)
   1531     page_info.set_signature(py_object, parser_config.reverse_index)
   1532 
   1533   elif tf_inspect.isclass(py_object):
   1534     page_info = _ClassPageInfo(master_name)
   1535     page_info.collect_docs_for_class(py_object, parser_config)
   1536 
   1537   elif tf_inspect.ismodule(py_object):
   1538     page_info = _ModulePageInfo(master_name)
   1539     page_info.collect_docs_for_module(parser_config)
   1540 
   1541   else:
   1542     raise RuntimeError('Cannot make docs for object %s: %r' % (full_name,
   1543                                                                py_object))
   1544 
   1545   relative_path = os.path.relpath(
   1546       path='.', start=os.path.dirname(documentation_path(full_name)) or '.')
   1547 
   1548   page_info.set_doc(_parse_md_docstring(
   1549       py_object, relative_path, parser_config.reference_resolver))
   1550 
   1551   page_info.set_aliases(duplicate_names)
   1552 
   1553   page_info.set_guides(_get_guides_markdown(
   1554       duplicate_names, parser_config.guide_index, relative_path))
   1555 
   1556   page_info.set_defined_in(_get_defined_in(py_object, parser_config))
   1557 
   1558   return page_info
   1559 
   1560 
   1561 class _PythonBuiltin(object):
   1562   """This class indicated that the object in question is a python builtin.
   1563 
   1564   This can be used for the `defined_in` slot of the `PageInfo` objects.
   1565   """
   1566 
   1567   def is_builtin(self):
   1568     return True
   1569 
   1570   def is_python_file(self):
   1571     return False
   1572 
   1573   def is_generated_file(self):
   1574     return False
   1575 
   1576   def __str__(self):
   1577     return 'This is an alias for a Python built-in.\n\n'
   1578 
   1579 
   1580 class _PythonFile(object):
   1581   """This class indicates that the object is defined in a regular python file.
   1582 
   1583   This can be used for the `defined_in` slot of the `PageInfo` objects.
   1584   """
   1585 
   1586   def __init__(self, path, parser_config):
   1587     self.path = path
   1588     self.path_prefix = parser_config.defined_in_prefix
   1589     self.code_url_prefix = parser_config.code_url_prefix
   1590 
   1591   def is_builtin(self):
   1592     return False
   1593 
   1594   def is_python_file(self):
   1595     return True
   1596 
   1597   def is_generated_file(self):
   1598     return False
   1599 
   1600   def __str__(self):
   1601     return 'Defined in [`{prefix}{path}`]({code_prefix}{path}).\n\n'.format(
   1602         path=self.path, prefix=self.path_prefix,
   1603         code_prefix=self.code_url_prefix)
   1604 
   1605 
   1606 class _ProtoFile(object):
   1607   """This class indicates that the object is defined in a .proto file.
   1608 
   1609   This can be used for the `defined_in` slot of the `PageInfo` objects.
   1610   """
   1611 
   1612   def __init__(self, path, parser_config):
   1613     self.path = path
   1614     self.path_prefix = parser_config.defined_in_prefix
   1615     self.code_url_prefix = parser_config.code_url_prefix
   1616 
   1617   def is_builtin(self):
   1618     return False
   1619 
   1620   def is_python_file(self):
   1621     return False
   1622 
   1623   def is_generated_file(self):
   1624     return False
   1625 
   1626   def __str__(self):
   1627     return 'Defined in [`{prefix}{path}`]({code_prefix}{path}).\n\n'.format(
   1628         path=self.path, prefix=self.path_prefix,
   1629         code_prefix=self.code_url_prefix)
   1630 
   1631 
   1632 class _GeneratedFile(object):
   1633   """This class indicates that the object is defined in a generated python file.
   1634 
   1635   Generated files should not be linked to directly.
   1636 
   1637   This can be used for the `defined_in` slot of the `PageInfo` objects.
   1638   """
   1639 
   1640   def __init__(self, path, parser_config):
   1641     self.path = path
   1642     self.path_prefix = parser_config.defined_in_prefix
   1643 
   1644   def is_builtin(self):
   1645     return False
   1646 
   1647   def is_python_file(self):
   1648     return False
   1649 
   1650   def is_generated_file(self):
   1651     return True
   1652 
   1653   def __str__(self):
   1654     return 'Defined in generated file: `%s%s`.\n\n' % (self.path_prefix,
   1655                                                        self.path)
   1656 
   1657 
   1658 def _get_defined_in(py_object, parser_config):
   1659   """Returns a description of where the passed in python object was defined.
   1660 
   1661   Args:
   1662     py_object: The Python object.
   1663     parser_config: A ParserConfig object.
   1664 
   1665   Returns:
   1666     Either a `_PythonBuiltin`, `_PythonFile`, or a `_GeneratedFile`
   1667   """
   1668   # Every page gets a note about where this object is defined
   1669   # TODO(wicke): If py_object is decorated, get the decorated object instead.
   1670   # TODO(wicke): Only use decorators that support this in TF.
   1671 
   1672   try:
   1673     path = os.path.relpath(path=tf_inspect.getfile(py_object),
   1674                            start=parser_config.base_dir)
   1675   except TypeError:  # getfile throws TypeError if py_object is a builtin.
   1676     return _PythonBuiltin()
   1677 
   1678   # TODO(wicke): If this is a generated file, link to the source instead.
   1679   # TODO(wicke): Move all generated files to a generated/ directory.
   1680   # TODO(wicke): And make their source file predictable from the file name.
   1681 
   1682   # In case this is compiled, point to the original
   1683   if path.endswith('.pyc'):
   1684     path = path[:-1]
   1685 
   1686   # Never include links outside this code base.
   1687   if path.startswith('..') or re.search(r'\b_api\b', path):
   1688     return None
   1689 
   1690   if re.match(r'.*/gen_[^/]*\.py$', path):
   1691     return _GeneratedFile(path, parser_config)
   1692   if 'genfiles' in path or 'tools/api/generator' in path:
   1693     return _GeneratedFile(path, parser_config)
   1694   elif re.match(r'.*_pb2\.py$', path):
   1695     # The _pb2.py files all appear right next to their defining .proto file.
   1696     return _ProtoFile(path[:-7] + '.proto', parser_config)
   1697   else:
   1698     return _PythonFile(path, parser_config)
   1699 
   1700 
   1701 # TODO(markdaoust): This should just parse, pretty_docs should generate the md.
   1702 def generate_global_index(library_name, index, reference_resolver):
   1703   """Given a dict of full names to python objects, generate an index page.
   1704 
   1705   The index page generated contains a list of links for all symbols in `index`
   1706   that have their own documentation page.
   1707 
   1708   Args:
   1709     library_name: The name for the documented library to use in the title.
   1710     index: A dict mapping full names to python objects.
   1711     reference_resolver: An instance of ReferenceResolver.
   1712 
   1713   Returns:
   1714     A string containing an index page as Markdown.
   1715   """
   1716   symbol_links = []
   1717   for full_name, py_object in six.iteritems(index):
   1718     if (tf_inspect.ismodule(py_object) or tf_inspect.isfunction(py_object) or
   1719         tf_inspect.isclass(py_object)):
   1720       # In Python 3, unbound methods are functions, so eliminate those.
   1721       if tf_inspect.isfunction(py_object):
   1722         if full_name.count('.') == 0:
   1723           parent_name = ''
   1724         else:
   1725           parent_name = full_name[:full_name.rfind('.')]
   1726         if parent_name in index and tf_inspect.isclass(index[parent_name]):
   1727           # Skip methods (=functions with class parents).
   1728           continue
   1729       symbol_links.append((
   1730           full_name, reference_resolver.python_link(full_name, full_name, '.')))
   1731 
   1732   lines = ['# All symbols in %s' % library_name, '']
   1733   for _, link in sorted(symbol_links, key=lambda x: x[0]):
   1734     lines.append('*  %s' % link)
   1735 
   1736   # TODO(markdaoust): use a _ModulePageInfo -> prety_docs.build_md_page()
   1737   return '\n'.join(lines)
   1738 
   1739 
   1740 class _Metadata(object):
   1741   """A class for building a page's Metadata block.
   1742 
   1743   Attributes:
   1744     name: The name of the page being described by the Metadata block.
   1745     version: The source version.
   1746   """
   1747 
   1748   def __init__(self, name, version='Stable'):
   1749     """Creates a Metadata builder.
   1750 
   1751     Args:
   1752       name: The name of the page being described by the Metadata block.
   1753       version: The source version.
   1754     """
   1755     self.name = name
   1756     self.version = version
   1757     self._content = []
   1758 
   1759   def append(self, item):
   1760     """Adds an item from the page to the Metadata block.
   1761 
   1762     Args:
   1763       item: The parsed page section to add.
   1764     """
   1765     self._content.append(item.short_name)
   1766 
   1767   def build_html(self):
   1768     """Returns the Metadata block as an Html string."""
   1769     schema = 'http://developers.google.com/ReferenceObject'
   1770     parts = ['<div itemscope itemtype="%s">' % schema]
   1771 
   1772     parts.append('<meta itemprop="name" content="%s" />' % self.name)
   1773     parts.append('<meta itemprop="path" content="%s" />' % self.version)
   1774     for item in self._content:
   1775       parts.append('<meta itemprop="property" content="%s"/>' % item)
   1776 
   1777     parts.extend(['</div>', ''])
   1778 
   1779     return '\n'.join(parts)
   1780