Ejemplo n.º 1
0
    def test_generate_index(self):
        m = types.ModuleType('m')
        m.__file__ = __file__
        m.TestClass = TestClass
        m.test_function = test_function
        m.submodule = types.ModuleType('submodule')
        m.submodule.test_function = test_function

        generator = generate_lib.DocGenerator(
            root_title='test',
            py_modules=[('m', m)],
            code_url_prefix='https://tensorflow.org')

        parser_config = generator.run_extraction()

        docs = parser.generate_global_index(
            'TestLibrary',
            index=parser_config.index,
            reference_resolver=parser_config.reference_resolver)

        # Make sure duplicates and non-top-level symbols are in the index, but
        # methods and properties are not.
        self.assertNotIn('a_method', docs)
        self.assertNotIn('a_property', docs)
        self.assertIn('m.TestClass', docs)
        self.assertIn('m.TestClass.ChildClass', docs)
        self.assertIn('m.submodule.test_function', docs)
        self.assertIn('<code>m.submodule.test_function', docs)
Ejemplo n.º 2
0
    def test_generate_index(self):

        index = {
            'tf': test_module,
            'tf.TestModule': test_module,
            'tf.test_function': test_function,
            'tf.TestModule.test_function': test_function,
            'tf.TestModule.TestClass': TestClass,
            'tf.TestModule.TestClass.a_method': TestClass.a_method,
            'tf.TestModule.TestClass.a_property': TestClass.a_property,
            'tf.TestModule.TestClass.ChildClass': TestClass.ChildClass,
        }
        duplicate_of = {'tf.TestModule.test_function': 'tf.test_function'}

        visitor = DummyVisitor(index=index, duplicate_of=duplicate_of)

        reference_resolver = parser.ReferenceResolver.from_visitor(
            visitor=visitor, py_module_names=['tf'])

        docs = parser.generate_global_index(
            'TestLibrary', index=index, reference_resolver=reference_resolver)

        # Make sure duplicates and non-top-level symbols are in the index, but
        # methods and properties are not.
        self.assertNotIn('a_method', docs)
        self.assertNotIn('a_property', docs)
        self.assertIn('TestModule.TestClass', docs)
        self.assertIn('TestModule.TestClass.ChildClass', docs)
        self.assertIn('TestModule.test_function', docs)
        # Leading backtick to make sure it's included top-level.
        # This depends on formatting, but should be stable.
        self.assertIn('<code>tf.test_function', docs)
Ejemplo n.º 3
0
  def test_generate_index(self):

    index = {
        'tf': test_module,
        'tf.TestModule': test_module,
        'tf.test_function': test_function,
        'tf.TestModule.test_function': test_function,
        'tf.TestModule.TestClass': TestClass,
        'tf.TestModule.TestClass.a_method': TestClass.a_method,
        'tf.TestModule.TestClass.a_property': TestClass.a_property,
        'tf.TestModule.TestClass.ChildClass': TestClass.ChildClass,
    }
    duplicate_of = {'tf.TestModule.test_function': 'tf.test_function'}

    visitor = DummyVisitor(index=index, duplicate_of=duplicate_of)

    reference_resolver = parser.ReferenceResolver.from_visitor(
        visitor=visitor, py_module_names=['tf'])

    docs = parser.generate_global_index('TestLibrary', index=index,
                                        reference_resolver=reference_resolver)

    # Make sure duplicates and non-top-level symbols are in the index, but
    # methods and properties are not.
    self.assertNotIn('a_method', docs)
    self.assertNotIn('a_property', docs)
    self.assertIn('TestModule.TestClass', docs)
    self.assertIn('TestModule.TestClass.ChildClass', docs)
    self.assertIn('TestModule.test_function', docs)
    # Leading backtick to make sure it's included top-level.
    # This depends on formatting, but should be stable.
    self.assertIn('<code>tf.test_function', docs)
Ejemplo n.º 4
0
def write_docs(output_dir,
               parser_config,
               yaml_toc,
               root_title='TensorFlow',
               search_hints=True,
               site_path=''):
    """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `parser.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
    # Make output_dir.
    if not os.path.isabs(output_dir):
        raise ValueError("'output_dir' must be an absolute path.\n"
                         "    output_dir='%s'" % output_dir)

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # These dictionaries are used for table-of-contents generation below
    # They will contain, after the for-loop below::
    #  - module name(string):classes and functions the module contains(list)
    module_children = {}
    #  - symbol name(string):pathname (string)
    symbol_to_file = {}

    # Collect redirects for an api _redirects.yaml file.
    redirects = []

    # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
    for full_name, py_object in six.iteritems(parser_config.index):
        parser_config.reference_resolver.current_doc_full_name = full_name

        if full_name in parser_config.duplicate_of:
            continue

        # Methods and some routines are documented only as part of their class.
        if not (tf_inspect.ismodule(py_object)
                or tf_inspect.isclass(py_object) or parser.is_free_function(
                    py_object, full_name, parser_config.index)):
            continue

        sitepath = os.path.join('api_docs/python',
                                parser.documentation_path(full_name)[:-3])

        # For TOC, we need to store a mapping from full_name to the file
        # we're generating
        symbol_to_file[full_name] = sitepath

        # For a module, remember the module for the table-of-contents
        if tf_inspect.ismodule(py_object):
            if full_name in parser_config.tree:
                module_children.setdefault(full_name, [])

        # For something else that's documented,
        # figure out what module it lives in
        else:
            subname = str(full_name)
            while True:
                subname = subname[:subname.rindex('.')]
                if tf_inspect.ismodule(parser_config.index[subname]):
                    module_name = parser_config.duplicate_of.get(
                        subname, subname)
                    module_children.setdefault(module_name,
                                               []).append(full_name)
                    break

        # Generate docs for `py_object`, resolving references.
        page_info = parser.docs_for_object(full_name, py_object, parser_config)

        path = os.path.join(output_dir, parser.documentation_path(full_name))
        directory = os.path.dirname(path)
        try:
            if not os.path.exists(directory):
                os.makedirs(directory)
            # This function returns raw bytes in PY2 or unicode in PY3.
            if search_hints:
                content = [page_info.get_metadata_html()]
            else:
                content = ['']

            content.append(pretty_docs.build_md_page(page_info))
            text = '\n'.join(content)
            if six.PY3:
                text = text.encode('utf-8')
            with open(path, 'wb') as f:
                f.write(text)
        except OSError:
            raise OSError('Cannot write documentation for %s to %s' %
                          (full_name, directory))

        duplicates = parser_config.duplicates.get(full_name, [])
        if not duplicates:
            continue

        duplicates = [item for item in duplicates if item != full_name]

        for dup in duplicates:
            from_path = os.path.join(site_path, 'api_docs/python',
                                     dup.replace('.', '/'))
            to_path = os.path.join(site_path, 'api_docs/python',
                                   full_name.replace('.', '/'))
            redirects.append(
                (os.path.join('/', from_path), os.path.join('/', to_path)))

    if redirects:
        redirects = sorted(redirects)
        template = ('- from: {}\n' '  to: {}\n')
        redirects = [template.format(f, t) for f, t in redirects]
        api_redirects_path = os.path.join(output_dir, '_redirects.yaml')
        with open(api_redirects_path, 'w') as redirect_file:
            redirect_file.write('redirects:\n')
            redirect_file.write(''.join(redirects))

    if yaml_toc:
        # Generate table of contents

        # Put modules in alphabetical order, case-insensitive
        modules = sorted(module_children.keys(), key=lambda a: a.upper())

        leftnav_path = os.path.join(output_dir, '_toc.yaml')
        with open(leftnav_path, 'w') as f:

            # Generate header
            f.write(
                '# Automatically generated file; please do not edit\ntoc:\n')
            for module in modules:
                indent_num = module.count('.')
                # Don't list `tf.submodule` inside `tf`
                indent_num = max(indent_num, 1)
                indent = '  ' * indent_num

                if indent_num > 1:
                    # tf.contrib.baysflow.entropy will be under
                    #   tf.contrib->baysflow->entropy
                    title = module.split('.')[-1]
                else:
                    title = module

                header = [
                    '- title: ' + title, '  section:', '  - title: Overview',
                    '    path: ' +
                    os.path.join('/', site_path, symbol_to_file[module])
                ]
                header = ''.join([indent + line + '\n' for line in header])
                f.write(header)

                symbols_in_module = module_children.get(module, [])
                # Sort case-insensitive, if equal sort case sensitive (upper first)
                symbols_in_module.sort(key=lambda a: (a.upper(), a))

                for full_name in symbols_in_module:
                    item = [
                        '  - title: ' + full_name[len(module) + 1:],
                        '    path: ' +
                        os.path.join('/', site_path, symbol_to_file[full_name])
                    ]
                    item = ''.join([indent + line + '\n' for line in item])
                    f.write(item)

    # Write a global index containing all full names with links.
    with open(os.path.join(output_dir, 'index.md'), 'w') as f:
        f.write(
            parser.generate_global_index(root_title, parser_config.index,
                                         parser_config.reference_resolver))
Ejemplo n.º 5
0
def write_docs(
    *,
    output_dir: Union[str, pathlib.Path],
    parser_config: parser.ParserConfig,
    yaml_toc: bool,
    root_module_name: str,
    root_title: str = 'TensorFlow',
    search_hints: bool = True,
    site_path: str = 'api_docs/python',
    gen_redirects: bool = True,
    table_view: bool = True,
    gen_report: bool = False,
):
    """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `parser.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_module_name: (str) the name of the root module (`tf` for tensorflow).
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.
    gen_redirects: Bool which decides whether to generate _redirects.yaml file
      or not.
    table_view: If True, `Args`, `Returns`, `Raises` or `Attributes` will be
      converted to a tabular format while generating markdown. If False, they
      will be converted to a markdown List view.
    gen_report: If True, a report for the library is generated by linting the
      docstrings of its public API symbols.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
    output_dir = pathlib.Path(output_dir)
    site_path = pathlib.Path('/', site_path)

    # Make output_dir.
    if not output_dir.is_absolute():
        raise ValueError("'output_dir' must be an absolute path.\n"
                         f"    output_dir='{output_dir}'")
    output_dir.mkdir(parents=True, exist_ok=True)

    # These dictionaries are used for table-of-contents generation below
    # They will contain, after the for-loop below::
    #  - module name(string):classes and functions the module contains(list)
    module_children = {}

    # Collect redirects for an api _redirects.yaml file.
    redirects = []

    if gen_report:
        api_report_obj = utils.ApiReport()

    # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
    for full_name in sorted(parser_config.index.keys(),
                            key=lambda k: k.lower()):
        py_object = parser_config.index[full_name]

        if full_name in parser_config.duplicate_of:
            continue

        # Methods constants are only documented only as part of their parent's page.
        if parser_config.reference_resolver.is_fragment(full_name):
            continue

        # Remove the extension from the path.
        docpath, _ = os.path.splitext(parser.documentation_path(full_name))

        # For a module, remember the module for the table-of-contents
        if inspect.ismodule(py_object):
            if full_name in parser_config.tree:
                mod_obj = Module(module=full_name,
                                 py_object=py_object,
                                 path=str(site_path / docpath))
                module_children[full_name] = mod_obj
        # For something else that's documented,
        # figure out what module it lives in
        else:
            subname = str(full_name)
            while True:
                subname = subname[:subname.rindex('.')]
                if inspect.ismodule(parser_config.index[subname]):
                    module_name = parser_config.duplicate_of.get(
                        subname, subname)
                    child_mod = ModuleChild(name=full_name,
                                            py_object=py_object,
                                            parent=module_name,
                                            path=str(site_path / docpath))
                    module_children[module_name].add_children(child_mod)
                    break

        # Generate docs for `py_object`, resolving references.
        try:
            page_info = parser.docs_for_object(full_name, py_object,
                                               parser_config)
        except:
            raise ValueError(
                f'Failed to generate docs for symbol: `{full_name}`')

        if gen_report and not full_name.startswith(
            ('tf.compat.v', 'tf.keras.backend')):
            api_report_obj.fill_metrics(page_info)
            continue

        path = output_dir / parser.documentation_path(full_name)
        try:
            path.parent.mkdir(exist_ok=True, parents=True)
            # This function returns unicode in PY3.
            hidden = doc_controls.should_hide_from_search(page_info.py_object)
            brief_no_backticks = page_info.doc.brief.replace('`', '').strip()
            content = []
            if brief_no_backticks:
                content.append(f'description: {brief_no_backticks}\n')

            if search_hints and not hidden:
                content.append(page_info.get_metadata_html())
            else:
                content.append('robots: noindex\n')

            content.append(pretty_docs.build_md_page(page_info, table_view))
            text = '\n'.join(content)
            path.write_text(text, encoding='utf-8')
        except OSError:
            raise OSError('Cannot write documentation for '
                          f'{full_name} to {path.parent}')

        duplicates = parser_config.duplicates.get(full_name, [])
        if not duplicates:
            continue

        duplicates = [item for item in duplicates if item != full_name]

        if gen_redirects:
            for dup in duplicates:
                from_path = site_path / dup.replace('.', '/')
                to_path = site_path / full_name.replace('.', '/')
                redirects.append({'from': str(from_path), 'to': str(to_path)})

    if gen_report:
        serialized_proto = api_report_obj.api_report.SerializeToString()
        raw_proto = output_dir / 'api_report.pb'
        raw_proto.write_bytes(serialized_proto)
        return

    if yaml_toc:
        toc_gen = GenerateToc(module_children)
        toc_dict = toc_gen.generate()

        # Replace the overview path *only* for 'TensorFlow' to
        # `/api_docs/python/tf_overview`. This will be redirected to
        # `/api_docs/python/tf`.
        toc_values = toc_dict['toc'][0]
        if toc_values['title'] == 'tf':
            section = toc_values['section'][0]
            section['path'] = str(site_path / 'tf_overview')

        leftnav_toc = output_dir / root_module_name / '_toc.yaml'
        with open(leftnav_toc, 'w') as toc_file:
            yaml.dump(toc_dict, toc_file, default_flow_style=False)

    if redirects and gen_redirects:
        if yaml_toc and toc_values['title'] == 'tf':
            redirects.append({
                'from': str(site_path / 'tf_overview'),
                'to': str(site_path / 'tf'),
            })
        redirects_dict = {
            'redirects': sorted(redirects,
                                key=lambda redirect: redirect['from'])
        }

        api_redirects_path = output_dir / root_module_name / '_redirects.yaml'
        with open(api_redirects_path, 'w') as redirect_file:
            yaml.dump(redirects_dict, redirect_file, default_flow_style=False)

    # Write a global index containing all full names with links.
    with open(output_dir / root_module_name / 'all_symbols.md', 'w') as f:
        global_index = parser.generate_global_index(
            root_title, parser_config.index, parser_config.reference_resolver)
        if not search_hints:
            global_index = 'robots: noindex\n' + global_index
        f.write(global_index)
Ejemplo n.º 6
0
def write_docs(output_dir,
               parser_config,
               yaml_toc,
               root_title='TensorFlow',
               search_hints=True,
               site_path='api_docs/python'):
    """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `parser.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
    # Make output_dir.
    if not os.path.isabs(output_dir):
        raise ValueError("'output_dir' must be an absolute path.\n"
                         "    output_dir='%s'" % output_dir)

    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # These dictionaries are used for table-of-contents generation below
    # They will contain, after the for-loop below::
    #  - module name(string):classes and functions the module contains(list)
    module_children = {}

    # Collect redirects for an api _redirects.yaml file.
    redirects = []

    # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
    for full_name in sorted(parser_config.index.keys(),
                            key=lambda k: k.lower()):
        py_object = parser_config.index[full_name]

        if full_name in parser_config.duplicate_of:
            continue

        # Methods and some routines are documented only as part of their class.
        if not (tf_inspect.ismodule(py_object)
                or tf_inspect.isclass(py_object) or parser.is_free_function(
                    py_object, full_name, parser_config.index)):
            continue

        # Remove the extension from the path.
        docpath, _ = os.path.splitext(parser.documentation_path(full_name))

        # For a module, remember the module for the table-of-contents
        if tf_inspect.ismodule(py_object):
            if full_name in parser_config.tree:
                mod_obj = Module(module=full_name,
                                 py_object=py_object,
                                 path=os.path.join('/', site_path, docpath))
                module_children[full_name] = mod_obj
        # For something else that's documented,
        # figure out what module it lives in
        else:
            subname = str(full_name)
            while True:
                subname = subname[:subname.rindex('.')]
                if tf_inspect.ismodule(parser_config.index[subname]):
                    module_name = parser_config.duplicate_of.get(
                        subname, subname)
                    child_mod = ModuleChild(name=full_name,
                                            py_object=py_object,
                                            parent=module_name,
                                            path=os.path.join(
                                                '/', site_path, docpath))
                    module_children[module_name].add_children(child_mod)
                    break

        # Generate docs for `py_object`, resolving references.
        try:
            page_info = parser.docs_for_object(full_name, py_object,
                                               parser_config)
        except:
            raise ValueError(
                'Failed to generate docs for symbol: `{}`'.format(full_name))

        path = os.path.join(output_dir, parser.documentation_path(full_name))
        directory = os.path.dirname(path)
        try:
            if not os.path.exists(directory):
                os.makedirs(directory)
            # This function returns raw bytes in PY2 or unicode in PY3.
            if search_hints:
                content = [page_info.get_metadata_html()]
            else:
                content = ['']

            content.append(pretty_docs.build_md_page(page_info))
            text = '\n'.join(content)
            if six.PY3:
                text = text.encode('utf-8')
            with open(path, 'wb') as f:
                f.write(text)
        except OSError:
            raise OSError('Cannot write documentation for %s to %s' %
                          (full_name, directory))

        duplicates = parser_config.duplicates.get(full_name, [])
        if not duplicates:
            continue

        duplicates = [item for item in duplicates if item != full_name]

        for dup in duplicates:
            from_path = os.path.join(site_path, dup.replace('.', '/'))
            to_path = os.path.join(site_path, full_name.replace('.', '/'))
            redirects.append({
                'from': os.path.join('/', from_path),
                'to': os.path.join('/', to_path)
            })

    if redirects:
        redirects_dict = {
            'redirects': sorted(redirects,
                                key=lambda redirect: redirect['from'])
        }

        api_redirects_path = os.path.join(output_dir, '_redirects.yaml')
        with open(api_redirects_path, 'w') as redirect_file:
            yaml.dump(redirects_dict, redirect_file, default_flow_style=False)

    if yaml_toc:
        toc_gen = GenerateToc(module_children)
        toc_dict = toc_gen.generate()

        leftnav_toc = os.path.join(output_dir, '_toc.yaml')
        with open(leftnav_toc, 'w') as toc_file:
            yaml.dump(toc_dict, toc_file, default_flow_style=False)

    # Write a global index containing all full names with links.
    with open(os.path.join(output_dir, 'index.md'), 'w') as f:
        f.write(
            parser.generate_global_index(root_title, parser_config.index,
                                         parser_config.reference_resolver))
Ejemplo n.º 7
0
def write_docs(
    *,
    output_dir: Union[str, pathlib.Path],
    parser_config: config.ParserConfig,
    yaml_toc: Union[bool, Type[toc_lib.TocBuilder]],
    root_module_name: str,
    root_title: str = 'TensorFlow',
    search_hints: bool = True,
    site_path: str = 'api_docs/python',
    gen_redirects: bool = True,
    gen_report: bool = True,
    extra_docs: Optional[Dict[int, str]] = None,
    page_builder_classes: Optional[docs_for_object.PageBuilderDict] = None,
):
    """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `config.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_module_name: (str) the name of the root module (`tf` for tensorflow).
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.
    gen_redirects: Bool which decides whether to generate _redirects.yaml file
      or not.
    gen_report: If True, a report for the library is generated by linting the
      docstrings of its public API symbols.
    extra_docs: To add docs for a particular object instance set it's __doc__
      attribute. For some classes (list, tuple, etc) __doc__ is not writable.
      Pass those docs like: `extra_docs={id(obj): "docs"}`
    page_builder_classes: A optional dict of `{ObjectType:Type[PageInfo]}` for
        overriding the default page builder classes.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
    output_dir = pathlib.Path(output_dir)
    site_path = pathlib.Path('/', site_path)

    # Make output_dir.
    if not output_dir.is_absolute():
        raise ValueError("'output_dir' must be an absolute path.\n"
                         f"    output_dir='{output_dir}'")
    output_dir.mkdir(parents=True, exist_ok=True)

    # Collect redirects for an api _redirects.yaml file.
    redirects = []

    api_report = None
    if gen_report:
        api_report = utils.ApiReport()

    # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
    num_docs_output = 0
    for api_node in parser_config.api_tree.iter_nodes():
        full_name = api_node.full_name

        if api_node.output_type() is api_node.OutputType.FRAGMENT:
            continue

        # Generate docs for `py_object`, resolving references.
        try:
            page_info = docs_for_object.docs_for_object(
                api_node=api_node,
                parser_config=parser_config,
                extra_docs=extra_docs,
                search_hints=search_hints,
                page_builder_classes=page_builder_classes)

            if api_report is not None and not full_name.startswith(
                ('tf.compat.v', 'tf.keras.backend', 'tf.numpy',
                 'tf.experimental.numpy')):
                api_report.fill_metrics(page_info)
        except Exception as e:
            raise ValueError(
                f'Failed to generate docs for symbol: `{full_name}`') from e

        path = output_dir / parser.documentation_path(full_name)

        try:
            path.parent.mkdir(exist_ok=True, parents=True)
            path.write_text(page_info.page_text, encoding='utf-8')
            num_docs_output += 1
        except OSError as e:
            raise OSError('Cannot write documentation for '
                          f'{full_name} to {path.parent}') from e

        duplicates = parser_config.duplicates.get(full_name, [])
        if not duplicates:
            continue

        duplicates = [item for item in duplicates if item != full_name]

        if gen_redirects:
            for dup in duplicates:
                from_path = site_path / dup.replace('.', '/')
                to_path = site_path / full_name.replace('.', '/')
                redirects.append({'from': str(from_path), 'to': str(to_path)})

    if api_report is not None:
        api_report.write(output_dir / root_module_name / 'api_report.pb')

    if num_docs_output <= 1:
        raise ValueError(
            'The `DocGenerator` failed to generate any docs. Verify '
            'your arguments (`base_dir` and `callbacks`). '
            'Everything you want documented should be within '
            '`base_dir`.')

    if yaml_toc:
        if isinstance(yaml_toc, bool):
            yaml_toc = toc_lib.FlatModulesTocBuilder
        toc = yaml_toc(site_path).build(parser_config.api_tree)

        toc_path = output_dir / root_module_name / '_toc.yaml'
        toc.write(toc_path)

    if redirects and gen_redirects:
        redirects_dict = {
            'redirects': sorted(redirects,
                                key=lambda redirect: redirect['from'])
        }

        api_redirects_path = output_dir / root_module_name / '_redirects.yaml'
        with open(api_redirects_path, 'w') as redirect_file:
            yaml.dump(redirects_dict, redirect_file, default_flow_style=False)

    # Write a global index containing all full names with links.
    with open(output_dir / root_module_name / 'all_symbols.md', 'w') as f:
        global_index = parser.generate_global_index(
            root_title, parser_config.index, parser_config.reference_resolver)
        if not search_hints:
            global_index = 'robots: noindex\n' + global_index
        f.write(global_index)
Ejemplo n.º 8
0
def write_docs(
    *,
    output_dir: Union[str, pathlib.Path],
    parser_config: config.ParserConfig,
    yaml_toc: bool,
    root_module_name: str,
    root_title: str = 'TensorFlow',
    search_hints: bool = True,
    site_path: str = 'api_docs/python',
    gen_redirects: bool = True,
    gen_report: bool = True,
    extra_docs: Optional[Dict[int, str]] = None,
    page_builder_classes: Optional[docs_for_object.PageBuilderDict] = None,
):
    """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `config.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_module_name: (str) the name of the root module (`tf` for tensorflow).
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.
    gen_redirects: Bool which decides whether to generate _redirects.yaml file
      or not.
    gen_report: If True, a report for the library is generated by linting the
      docstrings of its public API symbols.
    extra_docs: To add docs for a particular object instance set it's __doc__
      attribute. For some classes (list, tuple, etc) __doc__ is not writable.
      Pass those docs like: `extra_docs={id(obj): "docs"}`
    page_builder_classes: A optional dict of `{ObjectType:Type[PageInfo]}` for
        overriding the default page builder classes.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
    output_dir = pathlib.Path(output_dir)
    site_path = pathlib.Path('/', site_path)

    # Make output_dir.
    if not output_dir.is_absolute():
        raise ValueError("'output_dir' must be an absolute path.\n"
                         f"    output_dir='{output_dir}'")
    output_dir.mkdir(parents=True, exist_ok=True)

    # These dictionaries are used for table-of-contents generation below
    # They will contain, after the for-loop below::
    #  - module name(string):classes and functions the module contains(list)
    module_children = {}

    # Collect redirects for an api _redirects.yaml file.
    redirects = []

    if gen_report:
        api_report_obj = utils.ApiReport()

    # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
    num_docs_output = 0
    for full_name in sorted(parser_config.index.keys(),
                            key=lambda k: k.lower()):
        py_object = parser_config.index[full_name]

        if full_name in parser_config.duplicate_of:
            continue

        # Methods constants are only documented only as part of their parent's page.
        if parser_config.reference_resolver.is_fragment(full_name):
            continue

        # Remove the extension from the path.
        docpath, _ = os.path.splitext(parser.documentation_path(full_name))

        # For a module, remember the module for the table-of-contents
        if inspect.ismodule(py_object):
            if full_name in parser_config.tree:
                mod_obj = Module(module=full_name,
                                 py_object=py_object,
                                 path=str(site_path / docpath))
                module_children[full_name] = mod_obj
        # For something else that's documented,
        # figure out what module it lives in
        else:
            subname = str(full_name)
            while True:
                subname = subname[:subname.rindex('.')]
                if inspect.ismodule(parser_config.index[subname]):
                    module_name = parser_config.duplicate_of.get(
                        subname, subname)
                    child_mod = ModuleChild(name=full_name,
                                            py_object=py_object,
                                            parent=module_name,
                                            path=str(site_path / docpath))
                    module_children[module_name].add_children(child_mod)
                    break

        # Generate docs for `py_object`, resolving references.
        try:
            page_info = docs_for_object.docs_for_object(
                full_name=full_name,
                py_object=py_object,
                parser_config=parser_config,
                extra_docs=extra_docs,
                search_hints=search_hints,
                page_builder_classes=page_builder_classes)

            if gen_report and not full_name.startswith(
                ('tf.compat.v', 'tf.keras.backend', 'tf.numpy',
                 'tf.experimental.numpy')):
                api_report_obj.fill_metrics(page_info)
        except Exception as e:
            raise ValueError(
                f'Failed to generate docs for symbol: `{full_name}`') from e

        path = output_dir / parser.documentation_path(full_name)

        text = page_info.build()

        try:
            path.parent.mkdir(exist_ok=True, parents=True)
            path.write_text(text, encoding='utf-8')
            num_docs_output += 1
        except OSError:
            raise OSError('Cannot write documentation for '
                          f'{full_name} to {path.parent}')

        duplicates = parser_config.duplicates.get(full_name, [])
        if not duplicates:
            continue

        duplicates = [item for item in duplicates if item != full_name]

        if gen_redirects:
            for dup in duplicates:
                from_path = site_path / dup.replace('.', '/')
                to_path = site_path / full_name.replace('.', '/')
                redirects.append({'from': str(from_path), 'to': str(to_path)})

    if gen_report:
        serialized_proto = api_report_obj.api_report.SerializeToString()
        raw_proto = output_dir / root_module_name / 'api_report.pb'
        raw_proto.write_bytes(serialized_proto)

    if num_docs_output <= 1:
        raise ValueError(
            'The `DocGenerator` failed to generate any docs. Verify '
            'your arguments (`base_dir` and `callbacks`). '
            'Everything you want documented should be within '
            '`base_dir`.')

    if yaml_toc:
        toc_gen = GenerateToc(module_children)
        toc_dict = toc_gen.generate()

        # Replace the overview path *only* for 'TensorFlow' to
        # `/api_docs/python/tf_overview`. This will be redirected to
        # `/api_docs/python/tf`.
        toc_values = toc_dict['toc'][0]
        if toc_values['title'] == 'tf':
            section = toc_values['section'][0]
            section['path'] = str(site_path / 'tf_overview')

        leftnav_toc = output_dir / root_module_name / '_toc.yaml'
        with open(leftnav_toc, 'w') as toc_file:
            yaml.dump(toc_dict, toc_file, default_flow_style=False)

    if redirects and gen_redirects:
        if yaml_toc and toc_values['title'] == 'tf':
            redirects.append({
                'from': str(site_path / 'tf_overview'),
                'to': str(site_path / 'tf'),
            })
        redirects_dict = {
            'redirects': sorted(redirects,
                                key=lambda redirect: redirect['from'])
        }

        api_redirects_path = output_dir / root_module_name / '_redirects.yaml'
        with open(api_redirects_path, 'w') as redirect_file:
            yaml.dump(redirects_dict, redirect_file, default_flow_style=False)

    # Write a global index containing all full names with links.
    with open(output_dir / root_module_name / 'all_symbols.md', 'w') as f:
        global_index = parser.generate_global_index(
            root_title, parser_config.index, parser_config.reference_resolver)
        if not search_hints:
            global_index = 'robots: noindex\n' + global_index
        f.write(global_index)
Ejemplo n.º 9
0
def write_docs(output_dir,
               parser_config,
               yaml_toc,
               root_title='TensorFlow',
               search_hints=True,
               site_path=''):
  """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `parser.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
  # Make output_dir.
  if not os.path.isabs(output_dir):
    raise ValueError("'output_dir' must be an absolute path.\n"
                     "    output_dir='%s'" % output_dir)

  if not os.path.exists(output_dir):
    os.makedirs(output_dir)

  # These dictionaries are used for table-of-contents generation below
  # They will contain, after the for-loop below::
  #  - module name(string):classes and functions the module contains(list)
  module_children = {}
  #  - symbol name(string):pathname (string)
  symbol_to_file = {}

  # Collect redirects for an api _redirects.yaml file.
  redirects = []

  # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
  for full_name, py_object in six.iteritems(parser_config.index):
    parser_config.reference_resolver.current_doc_full_name = full_name

    if full_name in parser_config.duplicate_of:
      continue

    # Methods and some routines are documented only as part of their class.
    if not (tf_inspect.ismodule(py_object) or tf_inspect.isclass(py_object) or
            parser.is_free_function(py_object, full_name, parser_config.index)):
      continue

    sitepath = os.path.join('api_docs/python',
                            parser.documentation_path(full_name)[:-3])

    # For TOC, we need to store a mapping from full_name to the file
    # we're generating
    symbol_to_file[full_name] = sitepath

    # For a module, remember the module for the table-of-contents
    if tf_inspect.ismodule(py_object):
      if full_name in parser_config.tree:
        module_children.setdefault(full_name, [])

    # For something else that's documented,
    # figure out what module it lives in
    else:
      subname = str(full_name)
      while True:
        subname = subname[:subname.rindex('.')]
        if tf_inspect.ismodule(parser_config.index[subname]):
          module_name = parser_config.duplicate_of.get(subname, subname)
          module_children.setdefault(module_name, []).append(full_name)
          break

    # Generate docs for `py_object`, resolving references.
    page_info = parser.docs_for_object(full_name, py_object, parser_config)

    path = os.path.join(output_dir, parser.documentation_path(full_name))
    directory = os.path.dirname(path)
    try:
      if not os.path.exists(directory):
        os.makedirs(directory)
      # This function returns raw bytes in PY2 or unicode in PY3.
      if search_hints:
        content = [page_info.get_metadata_html()]
      else:
        content = ['']

      content.append(pretty_docs.build_md_page(page_info))
      text = '\n'.join(content)
      if six.PY3:
        text = text.encode('utf-8')
      with open(path, 'wb') as f:
        f.write(text)
    except OSError:
      raise OSError(
          'Cannot write documentation for %s to %s' % (full_name, directory))

    duplicates = parser_config.duplicates.get(full_name, [])
    if not duplicates:
      continue

    duplicates = [item for item in duplicates if item != full_name]

    for dup in duplicates:
      from_path = os.path.join(site_path, 'api_docs/python',
                               dup.replace('.', '/'))
      to_path = os.path.join(site_path, 'api_docs/python',
                             full_name.replace('.', '/'))
      redirects.append((
          os.path.join('/', from_path),
          os.path.join('/', to_path)))

  if redirects:
    redirects = sorted(redirects)
    template = ('- from: {}\n'
                '  to: {}\n')
    redirects = [template.format(f, t) for f, t in redirects]
    api_redirects_path = os.path.join(output_dir, '_redirects.yaml')
    with open(api_redirects_path, 'w') as redirect_file:
      redirect_file.write('redirects:\n')
      redirect_file.write(''.join(redirects))

  if yaml_toc:
    # Generate table of contents

    # Put modules in alphabetical order, case-insensitive
    modules = sorted(module_children.keys(), key=lambda a: a.upper())

    leftnav_path = os.path.join(output_dir, '_toc.yaml')
    with open(leftnav_path, 'w') as f:

      # Generate header
      f.write('# Automatically generated file; please do not edit\ntoc:\n')
      for module in modules:
        indent_num = module.count('.')
        # Don't list `tf.submodule` inside `tf`
        indent_num = max(indent_num, 1)
        indent = '  '*indent_num

        if indent_num > 1:
          # tf.contrib.baysflow.entropy will be under
          #   tf.contrib->baysflow->entropy
          title = module.split('.')[-1]
        else:
          title = module

        header = [
            '- title: ' + title, '  section:', '  - title: Overview',
            '    path: ' + os.path.join('/', site_path, symbol_to_file[module])
        ]
        header = ''.join([indent+line+'\n' for line in header])
        f.write(header)

        symbols_in_module = module_children.get(module, [])
        # Sort case-insensitive, if equal sort case sensitive (upper first)
        symbols_in_module.sort(key=lambda a: (a.upper(), a))

        for full_name in symbols_in_module:
          item = [
              '  - title: ' + full_name[len(module) + 1:], '    path: ' +
              os.path.join('/', site_path, symbol_to_file[full_name])
          ]
          item = ''.join([indent+line+'\n' for line in item])
          f.write(item)

  # Write a global index containing all full names with links.
  with open(os.path.join(output_dir, 'index.md'), 'w') as f:
    f.write(
        parser.generate_global_index(root_title, parser_config.index,
                                     parser_config.reference_resolver))
Ejemplo n.º 10
0
def write_docs(output_dir,
               parser_config,
               yaml_toc,
               root_title='TensorFlow',
               search_hints=True,
               site_path='api_docs/python'):
    """Write previously extracted docs to disk.

  Write a docs page for each symbol included in the indices of parser_config to
  a tree of docs at `output_dir`.

  Symbols with multiple aliases will have only one page written about
  them, which is referenced for all aliases.

  Args:
    output_dir: Directory to write documentation markdown files to. Will be
      created if it doesn't exist.
    parser_config: A `parser.ParserConfig` object, containing all the necessary
      indices.
    yaml_toc: Set to `True` to generate a "_toc.yaml" file.
    root_title: The title name for the root level index.md.
    search_hints: (bool) include meta-data search hints at the top of each
      output file.
    site_path: The output path relative to the site root. Used in the
      `_toc.yaml` and `_redirects.yaml` files.

  Raises:
    ValueError: if `output_dir` is not an absolute path
  """
    output_dir = pathlib.Path(output_dir)
    site_path = pathlib.Path('/', site_path)

    # Make output_dir.
    if not output_dir.is_absolute():
        raise ValueError("'output_dir' must be an absolute path.\n"
                         f"    output_dir='{output_dir}'")
    output_dir.mkdir(parents=True, exist_ok=True)

    # These dictionaries are used for table-of-contents generation below
    # They will contain, after the for-loop below::
    #  - module name(string):classes and functions the module contains(list)
    module_children = {}

    # Collect redirects for an api _redirects.yaml file.
    redirects = []

    # Parse and write Markdown pages, resolving cross-links (`tf.symbol`).
    for full_name in sorted(parser_config.index.keys(),
                            key=lambda k: k.lower()):
        py_object = parser_config.index[full_name]

        if full_name in parser_config.duplicate_of:
            continue

        # Methods and some routines are documented only as part of their class.
        if not (inspect.ismodule(py_object)
                or inspect.isclass(py_object) or parser.is_free_function(
                    py_object, full_name, parser_config.index)):
            continue

        # Remove the extension from the path.
        docpath, _ = os.path.splitext(parser.documentation_path(full_name))

        # For a module, remember the module for the table-of-contents
        if inspect.ismodule(py_object):
            if full_name in parser_config.tree:
                mod_obj = Module(module=full_name,
                                 py_object=py_object,
                                 path=str(site_path / docpath))
                module_children[full_name] = mod_obj
        # For something else that's documented,
        # figure out what module it lives in
        else:
            subname = str(full_name)
            while True:
                subname = subname[:subname.rindex('.')]
                if inspect.ismodule(parser_config.index[subname]):
                    module_name = parser_config.duplicate_of.get(
                        subname, subname)
                    child_mod = ModuleChild(name=full_name,
                                            py_object=py_object,
                                            parent=module_name,
                                            path=str(site_path / docpath))
                    module_children[module_name].add_children(child_mod)
                    break

        # Generate docs for `py_object`, resolving references.
        try:
            page_info = parser.docs_for_object(full_name, py_object,
                                               parser_config)
        except:
            raise ValueError(
                f'Failed to generate docs for symbol: `{full_name}`')

        path = output_dir / parser.documentation_path(full_name)
        try:
            path.parent.mkdir(exist_ok=True, parents=True)
            # This function returns unicode in PY3.
            hidden = doc_controls.should_hide_from_search(page_info.py_object)
            if search_hints and not hidden:
                content = [page_info.get_metadata_html()]
            else:
                content = ['<meta name="robots" content="noindex">\n']

            content.append(pretty_docs.build_md_page(page_info))
            text = '\n'.join(content)
            path.write_text(text, encoding='utf-8')
        except OSError:
            raise OSError('Cannot write documentation for '
                          f'{full_name} to {path.parent}')

        duplicates = parser_config.duplicates.get(full_name, [])
        if not duplicates:
            continue

        duplicates = [item for item in duplicates if item != full_name]

        for dup in duplicates:
            from_path = site_path / dup.replace('.', '/')
            to_path = site_path / full_name.replace('.', '/')
            redirects.append({'from': str(from_path), 'to': str(to_path)})

    if yaml_toc:
        toc_gen = GenerateToc(module_children)
        toc_dict = toc_gen.generate()

        # Replace the overview path *only* for 'TensorFlow' to
        # `/api_docs/python/tf_overview`. This will be redirected to
        # `/api_docs/python/tf`.
        toc_values = toc_dict['toc'][0]
        if toc_values['title'] == 'tf':
            section = toc_values['section'][0]
            section['path'] = str(site_path / 'tf_overview')

        leftnav_toc = output_dir / '_toc.yaml'
        with open(leftnav_toc, 'w') as toc_file:
            yaml.dump(toc_dict, toc_file, default_flow_style=False)

    if redirects:
        if yaml_toc and toc_values['title'] == 'tf':
            redirects.append({
                'from': str(site_path / 'tf_overview'),
                'to': str(site_path / 'tf'),
            })
        redirects_dict = {
            'redirects': sorted(redirects,
                                key=lambda redirect: redirect['from'])
        }

        api_redirects_path = output_dir / '_redirects.yaml'
        with open(api_redirects_path, 'w') as redirect_file:
            yaml.dump(redirects_dict, redirect_file, default_flow_style=False)

    # Write a global index containing all full names with links.
    with open(output_dir / 'index.md', 'w') as f:
        f.write(
            parser.generate_global_index(root_title, parser_config.index,
                                         parser_config.reference_resolver))