Beispiel #1
0
    def get_import_string(self) -> ImportString:
        """
        Get import string from a node.

        Returns:
            An absolute import string.
        """
        if self.source:
            return ImportString(self.source) + self.name

        return ImportString(self.name)
Beispiel #2
0
    def generate_modules(self) -> None:
        """
        Generate `<output>/README.md` file with title from `<root>/README.md` and `Modules`
        section that contains a Tree of all modules in the project.
        """
        self._logger.debug("Generating {}".format(
            self._root_path_finder.relative(self.md_modules.path)))
        with self.md_modules as md_modules:
            if not md_modules.title:
                md_modules.title = "{} {}".format(self._project_name,
                                                  self.MODULES_TITLE)

            autogenerated_marker = "> Auto-generated documentation modules index."
            subtitle_parts = [autogenerated_marker]
            if md_modules.subtitle:
                subtitle_parts.append(md_modules.subtitle)

            subtitle_parts.append("Full list of {} project modules.".format(
                md_modules.render_md_doc_link(self.md_index,
                                              title=self._project_name)))
            md_modules.subtitle = "\n\n".join(subtitle_parts)

            modules_toc_lines = self._build_modules_toc_lines(
                import_string=ImportString(""),
                max_depth=10,
                md_document=md_modules,
                start_level=1,
            )

            md_doc_link = md_modules.render_md_doc_link(self.md_index)
            modules_toc_lines.insert(
                0, md_modules.get_toc_line(md_doc_link, level=0))

            md_modules.toc_section = "\n".join(modules_toc_lines)
Beispiel #3
0
    def match(self, name: str) -> Optional[ImportString]:
        """
        Check if `name` matches or stats with a local name.

        Examples::

            import_node = ast.parse('from my_module import Name as LocalName')
            import_record = ImportRecord(import_node)

            import_record.match('LocalName')
            True

            import_record.match('LocalName.child')
            True

            import_record.match('OtherName')
            False

            import_record.match('LocalNameOther')
            False

        Returns:
            True if name is imported object itself on one of his children.
        """
        if name == self.local_name:
            return self.get_import_string()

        lookup = "{}.".format(self.local_name)
        if name.startswith(lookup):
            if self.source:
                trailing_import = name[len(lookup):]
                return ImportString("{}.{}".format(self.get_import_string(),
                                                   trailing_import))

        return None
Beispiel #4
0
    def test_generate_docs(self, PathFinderMock, MDDocumentMock,
                           ModuleRecordListMock, LoaderMock):
        source_path_mock = MagicMock()
        generator = Generator(
            project_name="test",
            input_path=Path("/input"),
            output_path=Path("/output"),
            source_paths=[source_path_mock],
        )

        MDDocumentMock().render_md_doc_link.return_value = "md_doc_link"
        MDDocumentMock().render_doc_link.return_value = "doc_link"

        module_record_mock = MagicMock()
        module_record_mock.title = "Title"
        module_record_mock.docstring = "Docstring"
        module_record_mock.import_string = ImportString("my.import.string")
        ModuleRecordListMock().__iter__ = MagicMock(
            return_value=iter([module_record_mock]))

        generator.generate_docs()

        LoaderMock.assert_called_with(output_path=Path("/output"),
                                      root_path=Path("/input"))
        PathFinderMock.assert_called_with(Path("/output"))
Beispiel #5
0
    def test_generate_doc(self, PathFinderMock, MDDocumentMock,
                          ModuleRecordListMock, LoaderMock):
        source_path_mock = MagicMock()
        generator = Generator(
            project_name="test",
            input_path=Path("/input"),
            output_path=Path("/output"),
            source_paths=[source_path_mock],
            raise_errors=True,
        )

        MDDocumentMock().render_md_doc_link.return_value = "md_doc_link"
        MDDocumentMock().render_doc_link.return_value = "doc_link"

        module_record_mock = MagicMock()
        module_record_mock.title = "Title"
        module_record_mock.docstring = "Docstring"
        module_record_mock.source_path = Path("/input/source.py")
        module_record_mock.import_string = ImportString("my.import.string")

        module_record_mock2 = MagicMock()
        module_record_mock2.title = "Title"
        module_record_mock2.docstring = "Docstring"
        module_record_mock2.source_path = Path("/input/source2.py")
        module_record_mock2.import_string = ImportString("my.import.string2")

        ModuleRecordListMock().__iter__ = MagicMock(
            return_value=iter([module_record_mock, module_record_mock2]))

        generator.generate_doc(Path("/input/source2.py"))

        LoaderMock.assert_called_with(output_path=Path("/output"),
                                      root_path=Path("/input"))
        PathFinderMock.assert_called_with(Path("/output"))

        ModuleRecordListMock().__iter__ = MagicMock(
            return_value=iter([module_record_mock]))
        with self.assertRaises(GeneratorError):
            generator.generate_doc(Path("/input/source2.py"))

        LoaderMock().parse_module_record.side_effect = ValueError(
            "loader_error")
        ModuleRecordListMock().__iter__ = MagicMock(
            return_value=iter([module_record_mock2]))
        with self.assertRaises(ValueError):
            generator.generate_doc(Path("/input/source2.py"))
Beispiel #6
0
 def test_build_children(self, ModuleAnalyzerMock):
     node = MagicMock()
     node.name = "name"
     node.body = ["body"]
     node.mock_add_spec(ast.Module)
     record = ModuleRecord(node)
     record.import_string = ImportString("my_module")
     record.name = "class_node"
     class_node = MagicMock()
     class_node.name = "ClassNode"
     method_node = MagicMock()
     method_node.name = "class_method"
     method_node.body = ["class_method"]
     method_node.mock_add_spec(ast.FunctionDef)
     class_node.body = [method_node]
     class_node.decorator_list = []
     class_node.bases = []
     class_node.mock_add_spec(ast.ClassDef)
     function_node = MagicMock()
     function_node.decorator_list = []
     function_node.name = "function_node"
     function_node.body = ["function_body"]
     function_node.mock_add_spec(ast.FunctionDef)
     attribute_node = MagicMock()
     attribute_target = MagicMock()
     attribute_target.id = "attribute_target"
     attribute_target.mock_add_spec(ast.Name)
     attribute_node.targets = [attribute_target]
     attribute_node.value = "attribute_value"
     attribute_node.mock_add_spec(ast.Assign)
     import_node = MagicMock()
     import_name = MagicMock()
     import_name.name = "import_name"
     import_name_2 = MagicMock()
     import_name_2.name = "import_name_2"
     import_node.names = [import_name, import_name_2]
     import_node.mock_add_spec(ast.Import)
     ModuleAnalyzerMock().class_nodes = [class_node]
     ModuleAnalyzerMock().function_nodes = [function_node]
     ModuleAnalyzerMock().attribute_nodes = [attribute_node]
     ModuleAnalyzerMock().import_nodes = [import_node]
     self.assertIsNone(record.build_children())
     self.assertEqual(record.title, "ClassNode")
     self.assertEqual(record.class_records[0].node, class_node)
     self.assertEqual(record.function_records[0].node, function_node)
     self.assertEqual(record.attribute_records[0].node, attribute_node)
     self.assertEqual(record.import_records[0].node, import_node)
     self.assertEqual(record.import_records[0].name, "import_name")
     self.assertEqual(record.import_records[1].node, import_node)
     self.assertEqual(record.import_records[1].name, "import_name_2")
     self.assertEqual(record.class_records[0].import_string.value,
                      "my_module.ClassNode")
     self.assertEqual(
         record.class_records[0].method_records[0].import_string.value,
         "my_module.ClassNode.class_method",
     )
     self.assertEqual(record.function_records[0].import_string.value,
                      "my_module.function_node")
Beispiel #7
0
 def __init__(self, node: ast.AST) -> None:
     self.docstring = ""
     self.import_string = ImportString("")
     self.node = node
     self.name = self.node.__class__.__name__
     self.title = ""
     self.is_method = False
     self.attribute_records: List[AttributeRecord] = []
     self.parsed = False
     self._line_number: Optional[int] = None
Beispiel #8
0
 def test_startswith(self):
     self.assertTrue(
         ImportString("parent.parent2.value").startswith(
             ImportString("parent")))
     self.assertTrue(
         ImportString("parent.parent2.value").startswith(
             ImportString("parent.parent2")))
     self.assertFalse(
         ImportString("parent.parent2.value").startswith(
             ImportString("parent2")))
     self.assertFalse(
         ImportString("parent.parent2.value").startswith(
             ImportString("parent.parent2value")))
Beispiel #9
0
 def __init__(self, node: ast.Module) -> None:
     super().__init__(node)
     self.all_names: List[str] = []
     self.class_records: List[ClassRecord] = []
     self.function_records: List[FunctionRecord] = []
     self.import_records: List[ImportRecord] = []
     self.source_path = Path("")
     self.source_lines: List[str] = []
     self.name = "module"
     self.title = ""
     self.import_string = ImportString("")
     self.import_string_map: Dict[ImportString, NodeRecord] = {}
     self.docstring = self._get_docstring()
Beispiel #10
0
 def __init__(self, node):
     # type: (ast.Module) -> None
     super(ModuleRecord, self).__init__(node)
     self.all_names = []  # type: List[Text]
     self.class_records = []  # type: List[ClassRecord]
     self.function_records = []  # type: List[FunctionRecord]
     self.import_records = []  # type: List[ImportRecord]
     self.source_path = Path("")
     self.source_lines = []  # type: List[Text]
     self.name = "module"
     self.title = ""
     self.import_string = ImportString("")
     self.import_string_map = {}  # type: Dict[ImportString, NodeRecord]
     self.docstring = self._get_docstring()
Beispiel #11
0
    def get_module_record(self, source_path):
        # type: (Path) -> Optional[ModuleRecord]
        """
        Build `ModuleRecord` for given `source_path`.

        Arguments:
            source_path -- Absolute path to source file.

        Returns:
            A new `ModuleRecord` instance or None if there is ntohing to import.

        Raises:
            LoaderError -- If python source cannot be loaded.
        """
        if not (source_path.parent / "__init__.py").exists():
            return None

        if source_path.name == "__init__.py" and source_path.parent == self._root_path:
            return None

        import_string = self.get_import_string(source_path)
        docstring_parts = []

        try:
            module_record = ModuleRecord.create_from_source(
                source_path, ImportString(import_string)
            )
            module_record.build_children()
        except Exception as e:
            raise LoaderError(
                "{} while loading {}: {}".format(e.__class__.__name__, source_path, e)
            )

        if module_record.docstring:
            docstring_parts.append(module_record.docstring)

        if source_path.name == "__init__.py":
            readme_md_path = source_path.parent / "README.md"
            if readme_md_path.exists():
                docstring_parts.append(readme_md_path.read_text())

        docstring = "\n\n".join(docstring_parts)
        title, docstring = extract_md_title(docstring)
        if title:
            module_record.title = title
        module_record.docstring = docstring

        return module_record
Beispiel #12
0
    def find_module_record(self, import_string: ImportString) -> Optional[ModuleRecord]:
        """
        Find `ModuleRecord` by it's import string.

        Arguments:
            import_string -- Object import string.

        Returns:
            Found `NodeRecord` instance or None.
        """
        while True:
            module_record = self.import_string_map.get(import_string)
            if module_record:
                return module_record

            if import_string.is_top_level():
                break

            import_string = import_string.parent

        return None
Beispiel #13
0
    def _replace_links(
        self,
        module_record: ModuleRecord,
        record: NodeRecord,
        md_document: MDDocument,
        docstring: str,
    ) -> str:
        parent_import_string = None
        if not record.import_string.is_top_level():
            parent_import_string = record.import_string.parent

        for match in self._short_link_re.findall(docstring):
            related_record_name = match.replace("`", "")
            related_import_string = None
            related_record = None
            target_path = md_document.path

            # find record in parent
            if parent_import_string:
                related_import_string = parent_import_string + related_record_name
                if related_import_string != record.import_string:
                    related_record = module_record.find_record(
                        related_import_string)

            # find record in module
            if not related_record:
                related_import_string = module_record.import_string + related_record_name
                related_record = module_record.find_record(
                    related_import_string)

            # find record globally
            if not related_record:
                related_import_string = ImportString(related_record_name)
                related_module_record = self._module_records.find_module_record(
                    related_import_string)
                if related_module_record:
                    related_record = related_module_record.find_record(
                        related_import_string)
                    target_path = self._loader.get_output_path(
                        related_module_record.source_path)

            if not related_record:
                continue

            if related_record.import_string.startswith(record.import_string):
                continue

            title = related_record.title
            anchor = md_document.get_anchor(related_record.title)
            if isinstance(related_record, AttributeRecord):
                parent_related_record = module_record.find_record(
                    related_record.import_string.parent)
                if parent_related_record:
                    anchor = md_document.get_anchor(
                        parent_related_record.title)

            link = md_document.render_doc_link(title,
                                               anchor=anchor,
                                               target_path=target_path)
            docstring = docstring.replace(match, link)
            self._logger.debug("Adding local link '{}' to '{}'".format(
                title, record.title))
        return docstring
Beispiel #14
0
    def test_init(self):
        self.assertEqual(ImportString("value").value, "value")
        self.assertEqual(str(ImportString("value")), "value")
        self.assertTrue(hash(ImportString("value")))
        self.assertTrue(ImportString("value"))
        self.assertFalse(ImportString(""))
        self.assertEqual((ImportString("value") + "add").value, "value.add")
        self.assertEqual((ImportString("") + "add").value, "add")
        self.assertEqual(ImportString("value"), ImportString("value"))
        self.assertEqual(ImportString("value"), "value")
        self.assertNotEqual(ImportString("value"), ImportString("value1"))
        self.assertNotEqual(ImportString("value"), "value1")
        self.assertNotEqual(ImportString("value"), b"value")
        self.assertEqual(
            ImportString("parent.parent2.value").parent.value,
            "parent.parent2")

        with self.assertRaises(ImportStringError):
            _ = ImportString("value").parent
Beispiel #15
0
 def test_is_top_level(self):
     self.assertTrue(ImportString("value").is_top_level())
     self.assertTrue(ImportString("").is_top_level())
     self.assertFalse(ImportString("parent.value").is_top_level())