From c74f4ab5fe19f8808a986f9e9ecaaa7a382fa003 Mon Sep 17 00:00:00 2001 From: daizu Date: Tue, 16 Jun 2020 00:17:33 +0900 Subject: [PATCH] Docs-Source link --- README.md | 2 +- docs/custom.css | 3 + docs/examples/google_style.md | 2 +- docs/examples/numpy_style.md | 2 +- docs/index.md | 4 +- docs/release.md | 12 ++++ docs/usage/inherit.md | 30 +++++---- docs/usage/page.md | 6 +- examples/inherit_comment.py | 30 +++++++++ mkapi/__init__.py | 2 +- mkapi/core/base.py | 2 - mkapi/core/code.py | 90 ++++++++++++++++++++++++++ mkapi/core/docstring.py | 7 +- mkapi/core/module.py | 6 -- mkapi/core/page.py | 21 ++++-- mkapi/core/postprocess.py | 15 +++++ mkapi/core/renderer.py | 13 +++- mkapi/core/signature.py | 39 ++++++----- mkapi/core/source.py | 40 ------------ mkapi/core/structure.py | 25 ++++--- mkapi/plugins/api.py | 3 +- mkapi/plugins/mkdocs.py | 7 ++ mkapi/templates/code.jinja2 | 16 +++++ mkapi/templates/macros.jinja2 | 5 +- mkapi/templates/module.jinja2 | 4 +- mkapi/templates/object_div.jinja2 | 2 +- mkapi/templates/object_heading.jinja2 | 2 +- mkapi/theme/css/mkapi-common.css | 24 ++++++- mkdocs.yml | 4 ++ tests/core/test_core_module.py | 6 +- tests/core/test_core_node.py | 2 +- tests/core/test_core_postprocess.py | 10 +-- theme/docs/api/mkapi.core.base.md | 16 ----- theme/docs/api/mkapi.core.docstring.md | 24 ------- theme/docs/api/mkapi.core.inherit.md | 18 ------ theme/docs/api/mkapi.core.linker.md | 12 ---- theme/docs/api/mkapi.core.md | 2 - theme/docs/api/mkapi.core.module.md | 6 -- theme/docs/api/mkapi.core.node.md | 8 --- theme/docs/api/mkapi.core.object.md | 12 ---- theme/docs/api/mkapi.core.page.md | 4 -- theme/docs/api/mkapi.core.renderer.md | 4 -- theme/docs/api/mkapi.core.signature.md | 10 --- theme/docs/api/mkapi.core.tree.md | 4 -- theme/docs/api/mkapi.plugins.mkdocs.md | 4 -- theme/mkdocs.yml | 2 +- 46 files changed, 313 insertions(+), 249 deletions(-) create mode 100644 docs/custom.css create mode 100644 docs/release.md create mode 100644 examples/inherit_comment.py create mode 100644 mkapi/core/code.py delete mode 100644 mkapi/core/source.py create mode 100644 mkapi/templates/code.jinja2 delete mode 100644 theme/docs/api/mkapi.core.base.md delete mode 100644 theme/docs/api/mkapi.core.docstring.md delete mode 100644 theme/docs/api/mkapi.core.inherit.md delete mode 100644 theme/docs/api/mkapi.core.linker.md delete mode 100644 theme/docs/api/mkapi.core.md delete mode 100644 theme/docs/api/mkapi.core.module.md delete mode 100644 theme/docs/api/mkapi.core.node.md delete mode 100644 theme/docs/api/mkapi.core.object.md delete mode 100644 theme/docs/api/mkapi.core.page.md delete mode 100644 theme/docs/api/mkapi.core.renderer.md delete mode 100644 theme/docs/api/mkapi.core.signature.md delete mode 100644 theme/docs/api/mkapi.core.tree.md delete mode 100644 theme/docs/api/mkapi.plugins.mkdocs.md diff --git a/README.md b/README.md index f05ef768..ca747f08 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Features of MkApi are: * **Attribute inspection**: If you write attributes with description as comment in `__init__()`, Attributes section is automatically created. * **Docstring inheritance**: Docstring of a subclass can inherit parameters and attributes description from its superclasses. * **Page mode**: Comprehensive API documentation for your project, in which objects are linked to each other by type annotation. - +* **Bidirectional Link**: Using the Page mode, bidirectional links are created between documentation and source code. ## Installation diff --git a/docs/custom.css b/docs/custom.css new file mode 100644 index 00000000..543a6c4a --- /dev/null +++ b/docs/custom.css @@ -0,0 +1,3 @@ +h1, h2, h3 { + margin-bottom: 10px; +} diff --git a/docs/examples/google_style.md b/docs/examples/google_style.md index c270018f..8fbd8577 100644 --- a/docs/examples/google_style.md +++ b/docs/examples/google_style.md @@ -103,7 +103,7 @@ creates the documentation for the `ExampleClass` class. In this example, note that: * Type annotation using `typing` package (`List` and `Tuple` in this case) is converted into readable style. -* Attributes section is inserted with type and description. These information is collected from `__init__()` function. +* Attributes section is inserted with type and description. These information is collected from the source code. * Bound methods (`message` in this case) have a METHOD prefix instead of a FUNCTION prefix. * If callable objects have neither Returns nor Yields, the type appears on the object definition line like type annotation. * Propertes are moved to the Attributes section with [RO] or [RW] suffix that indicates whether the property is read-only or read-write. diff --git a/docs/examples/numpy_style.md b/docs/examples/numpy_style.md index 18cdff7c..5ec2edd7 100644 --- a/docs/examples/numpy_style.md +++ b/docs/examples/numpy_style.md @@ -104,7 +104,7 @@ creates the documentation for the `ExampleClass` class. In this example, note that: * Type annotation using `typing` package (`List` and `Tuple` in this case) is converted into readable style. -* Attributes section is inserted with type and description. These information is collected from `__init__()` function. +* Attributes section is inserted with type and description. These information is collected from the source code. * Bound methods (`message` in this case) have a METHOD prefix instead of a FUNCTION prefix. * If callable objects have neither Returns nor Yields, the type appears on the object definition line like type annotation. * Propertes are moved to the Attributes section with [RO] or [RW] suffix that indicates whether the property is read-only or read-write. diff --git a/docs/index.md b/docs/index.md index f4459ccd..9a0ca5f3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # MkApi Documentation -MkApi plugin for [MkDocs](https://www.mkdocs.org/) generates API documentation for Python code. MkApi supports two styles of docstrings: [Google](http://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) and [NumPy](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard). The [Napoleon package](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/index.html#) provides complete examples: +MkApi plugin for [MkDocs](https://www.mkdocs.org/) generates API documentation for Python code. MkApi supports two styles of docstrings: [Google](http://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) and [NumPy](https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard). The [Napoleon project](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/index.html#) provides complete examples: * [Example Google Style Python Docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html#example-google) * [Example NumPy Style Python Docstrings](https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html#example-numpy) @@ -13,6 +13,8 @@ Features of MkApi are: * **Attribute inspection**: If you write attributes with description as comment at module level or in `__init__()` of class, Attributes section is automatically created. * **Docstring inheritance**: Docstring of a subclass can inherit parameters and attributes description from its superclasses. * **Page mode**: Comprehensive API documentation for your project, in which objects are linked to each other by type annotation. +* **Bidirectional Link**: Using the Page mode, bidirectional links are created between documentation and source code. + ## Installation diff --git a/docs/release.md b/docs/release.md new file mode 100644 index 00000000..dfc84f8c --- /dev/null +++ b/docs/release.md @@ -0,0 +1,12 @@ +# Release Notes + +## Version 1.0.0 (2020-06-16) + +### Additions to Version 1.0.0 + +* Add support for NumPy docstring style ([#1](https://github.com/daizutabi/mkapi/issues/1)). +* Document only a specifc method from a class ([#5](https://github.com/daizutabi/mkapi/issues/5)). +* Add support for magic/dunder methods ([#7](https://github.com/daizutabi/mkapi/issues/7)). +* Include methods defined in different file ([#9](https://github.com/daizutabi/mkapi/issues/9)). +* Display inheritance of class ([#10](https://github.com/daizutabi/mkapi/issues/10)). +* Add `on_config` option to allow users to run custom script ([#11](https://github.com/daizutabi/mkapi/issues/11)). diff --git a/docs/usage/inherit.md b/docs/usage/inherit.md index c94b8c2e..1456d3fd 100644 --- a/docs/usage/inherit.md +++ b/docs/usage/inherit.md @@ -30,36 +30,31 @@ Taking a look at this example, you may notice that: ## Inheritance from Superclasses -Since the docstring of the superclass `Base` describes the `name`, the `Item` class can inherit its description with `inherit` filter. +Since the docstring of the superclass `Base` describes the `name`, the `Item` class can inherit its description. ~~~markdown -![mkapi](inherit.Item|inherit) +![mkapi](inherit.Item) ~~~ +![mkapi](inherit.Item) + By inheritance from superclasses, you don't need to write duplicated description. ## Inheritance from Signature -Using `strict` filter, MkApi adds missing parameters and attributes from the signature. Description is still empty but type is inspected. Note that `strict` filter invokes `inherit` filter at the same time. +Using `strict` filter, MkApi also adds parameters and attributes without description using its signature. Description is still empty but type is inspected. ~~~markdown ![mkapi](inherit.Item|strict) ~~~ +![mkapi](inherit.Item|strict) + Inheritance from signature has two benefits: * You can find parameters and attributes that wait for description. * Users can know their types at least if you use type annotation. -## Example of Strict Mode - -~~~markdown -![mkapi](inherit.Item|strict) -~~~ - - -![mkapi](inherit.Item|strict) - ## Inheritance in Page Mode Inheritance in [page mode](page.md) is straightforward. For example, @@ -69,3 +64,14 @@ nav: - index.md - API: mkapi/api/mkapi|upper|strict ~~~ + +## Inheritance in Dataclass + + +#File inherit_comment.py {%=/examples/inherit_comment.py%} + +~~~ +![mkapi](inherit_comment.Item) +~~~ + +![mkapi](inherit_comment.Item) diff --git a/docs/usage/page.md b/docs/usage/page.md index 6018c294..355abcd2 100644 --- a/docs/usage/page.md +++ b/docs/usage/page.md @@ -109,7 +109,7 @@ You can click the prefix (`mkapi.core.docstring`) or the function name (`section ### Link from Type -[Docstring](mkapi.core.base.Docstring) class of MkApi has an attribute `sections` that is a list of `Section` class instance like below: +[Docstring](mkapi.core.base.Docstring) class of MkApi has an attribute `sections` that is a list of `Section` class instance: ~~~python # Mimic code of Docstring class. @@ -125,8 +125,6 @@ class Docstring: type: str = "" ~~~ -Corresponding *real* documentation is like below: +Corresponding *real* documentation is displayed as below. Note that **Section** and **Type** are bold, which indicates that it is a link. Let's click. This link system using type annotation is useful to navigate users throughout the project documentation. ![mkapi](mkapi.core.base.Docstring) - -Note that **Section** and **Type** are bold, which indicates that it is a link. Let's click. This link system using type annotation is useful to navigate users throughout the project documentation. diff --git a/examples/inherit_comment.py b/examples/inherit_comment.py new file mode 100644 index 00000000..2213e9dd --- /dev/null +++ b/examples/inherit_comment.py @@ -0,0 +1,30 @@ +from dataclasses import dataclass + +from mkapi.core.base import Type + + +@dataclass +class Base: + """Base class.""" + + name: str #: Object name. + type: Type #: Object type. + + def set_name(self, name: str): + """Sets name. + + Args: + name: A New name. + """ + self.name = name + + +@dataclass +class Item(Base): + """Item class.""" + + markdown: str #: Object Markdown. + + def set_name(self, name: str): + """Sets name in upper case.""" + self.name = name.upper() diff --git a/mkapi/__init__.py b/mkapi/__init__.py index 3569fe2b..eb9ab4b7 100644 --- a/mkapi/__init__.py +++ b/mkapi/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.8.6" +__version__ = "1.0.0" from mkapi.core.module import get_module from mkapi.core.node import get_node diff --git a/mkapi/core/base.py b/mkapi/core/base.py index 62f8e744..999f34b5 100644 --- a/mkapi/core/base.py +++ b/mkapi/core/base.py @@ -418,8 +418,6 @@ def update(self, section: "Section", force: bool = False): >>> s1.items [Item('a', 'i'), Item('b', 'f'), Item('x', 'd')] """ - if section.name != self.name: - raise ValueError(f"Different name: {self.name} != {section.name}.") for item in section.items: self.set_item(item, force) diff --git a/mkapi/core/code.py b/mkapi/core/code.py new file mode 100644 index 00000000..7623906d --- /dev/null +++ b/mkapi/core/code.py @@ -0,0 +1,90 @@ +import re +from dataclasses import dataclass, field +from typing import List + +import markdown + +from mkapi.core.base import Base +from mkapi.core.module import Module, get_module + + +@dataclass(repr=False) +class Block(Base): + def __post_init__(self): + self.markdown = f"~~~python\n{self.markdown}\n~~~\n" + + def __iter__(self): + yield + + +@dataclass +class Code: + module: Module + markdown: str = field(default="", init=False) + html: str = field(default="", init=False) + level: int = field(default=1, init=False) + + def __post_init__(self): + sourcefile = self.module.sourcefile + with open(sourcefile, "r") as f: + source = f.read() + if not source: + return + + nodes = [] + linenos = [] + for node in self.module.node.walk(): + if node.sourcefile == sourcefile: + if node.lineno > 0 and node.lineno not in linenos: + nodes.append(node) + linenos.append(node.lineno) + module_id = self.module.object.id + i = 0 + lines = [] + for k, line in enumerate(source.split("\n")): + if i < len(linenos) and k == linenos[i]: + object_id = nodes[i].object.id + lines.append(f" # __mkapi__:{module_id}:{object_id}") + i += 1 + lines.append(" " + line) + source = "\n".join(lines) + self.markdown = f" :::python\n{source}\n" + html = markdown.markdown(self.markdown, extensions=["codehilite"]) + self.html = replace(html) + + def __repr__(self): + class_name = self.__class__.__name__ + return f"{class_name}({self.module.object.id!r}, num_objects={len(self.nodes)})" + + def get_markdown(self, level: int = 1) -> str: + """Returns a Markdown source for docstring of this object.""" + self.level = level + return f"# {self.module.object.id}" + + def set_html(self, html: str): + pass + + def get_html(self, filters: List[str] = None) -> str: + """Renders and returns HTML.""" + from mkapi.core.renderer import renderer + + return renderer.render_code(self, filters) # type:ignore + + +COMMENT_PATTERN = re.compile(r'\n# __mkapi__:(.*?):(.*?)') + + +def replace(html): + def func(match): + module, object = match.groups() + link = f'' + link += f'DOCS' + return link + + return COMMENT_PATTERN.sub(func, html) + + +def get_code(name: str) -> Code: + module = get_module(name) + return Code(module) diff --git a/mkapi/core/docstring.py b/mkapi/core/docstring.py index 512fce24..aa526fbe 100644 --- a/mkapi/core/docstring.py +++ b/mkapi/core/docstring.py @@ -2,6 +2,7 @@ import inspect import re +from dataclasses import is_dataclass from typing import Any, Iterator, List, Tuple from mkapi.core import preprocess @@ -228,8 +229,6 @@ def parse_source(doc: Docstring, obj: Any): s['Parameters'] """ signature = get_signature(obj) - if not hasattr(signature, "parameters"): - return name = "Parameters" section = signature[name] if name in doc: @@ -241,6 +240,10 @@ def parse_source(doc: Docstring, obj: Any): section = signature[name] if name not in doc and not section: return + if is_dataclass(obj) and 'Parameters' in doc: + for item in doc["Parameters"].items: + if item.name in section: + doc[name].set_item(item) doc[name].update(section) diff --git a/mkapi/core/module.py b/mkapi/core/module.py index 48e5290f..aa4cc1ac 100644 --- a/mkapi/core/module.py +++ b/mkapi/core/module.py @@ -58,12 +58,6 @@ def get_markdown(self, filters: List[str]) -> str: # type:ignore return renderer.render_module(self, filters) # type:ignore - def get_source(self, filters: List[str]) -> str: - """Returns a source for module.""" - from mkapi.core.source import get_source - - return get_source(self, filters) # type:ignore - def get_objects(obj) -> List[str]: obj_source_file = inspect.getsourcefile(obj) diff --git a/mkapi/core/page.py b/mkapi/core/page.py index dc2951d4..911dbeae 100644 --- a/mkapi/core/page.py +++ b/mkapi/core/page.py @@ -1,11 +1,12 @@ """This module provides a Page class that works with other converter.""" import re from dataclasses import InitVar, dataclass, field -from typing import Iterator, List +from typing import Iterator, List, Union from mkapi import utils from mkapi.core import postprocess from mkapi.core.base import Base, Section +from mkapi.core.code import Code, get_code from mkapi.core.inherit import inherit from mkapi.core.linker import resolve_link from mkapi.core.node import Node, get_node @@ -30,7 +31,7 @@ class Page: abs_src_path: str abs_api_paths: List[str] = field(default_factory=list, repr=False) markdown: str = field(init=False, repr=False) - nodes: List[Node] = field(default_factory=list, init=False, repr=False) + nodes: List[Union[Node, Code]] = field(default_factory=list, init=False, repr=False) def __post_init__(self, source): self.markdown = "\n\n".join(self.split(source)) @@ -53,12 +54,18 @@ def split(self, source: str) -> Iterator[str]: if markdown: yield self.resolve_link(markdown) heading, name = match.groups() + level = len(heading) name, filters = utils.split_filters(name) - node = get_node(name) - inherit(node) - postprocess.transform(node, filters) - self.nodes.append(node) - markdown = node.get_markdown(level=len(heading), callback=callback) + if "code" in filters: + code = get_code(name) + self.nodes.append(code) + markdown = code.get_markdown(level) + else: + node = get_node(name) + inherit(node) + postprocess.transform(node, filters) + self.nodes.append(node) + markdown = node.get_markdown(level, callback=callback) yield node_markdown(index, markdown, filters) cursor = end if cursor < len(source): diff --git a/mkapi/core/postprocess.py b/mkapi/core/postprocess.py index a10c0572..7d40ced8 100644 --- a/mkapi/core/postprocess.py +++ b/mkapi/core/postprocess.py @@ -89,6 +89,19 @@ def transform_module(node: Node, filters: Optional[List[str]] = None): node.members = [] +def sort(node: Node): + def clean(name: str) -> str: + if name.startswith("["): + name = name[1:] + return name + + doc = node.docstring + for section in doc.sections: + if section.name in ["Classes", "Parameters"]: + continue + section.items = sorted(section.items, key=lambda x: clean(x.name)) + + def transform(node: Node, filters: Optional[List[str]] = None): if node.docstring is None: return @@ -96,3 +109,5 @@ def transform(node: Node, filters: Optional[List[str]] = None): transform_class(node) elif node.object.kind in ["module", "package"]: transform_module(node, filters) + for x in node.walk(): + sort(x) diff --git a/mkapi/core/renderer.py b/mkapi/core/renderer.py index 6eb771d0..96f052be 100644 --- a/mkapi/core/renderer.py +++ b/mkapi/core/renderer.py @@ -11,6 +11,7 @@ import mkapi from mkapi.core import linker from mkapi.core.base import Docstring, Section +from mkapi.core.code import Code from mkapi.core.module import Module from mkapi.core.node import Node from mkapi.core.structure import Object @@ -104,7 +105,7 @@ def render_docstring(self, docstring: Docstring, filters: List[str] = None) -> s for section in docstring.sections: if section.items: valid = any(item.description for item in section.items) - if filters and 'strict' in filters or valid: + if filters and "strict" in filters or valid: section.html = self.render_section(section, filters) return template.render(docstring=docstring) @@ -121,7 +122,7 @@ def render_section(self, section: Section, filters: List[str] = None) -> str: else: return self.templates["args"].render(section=section, filters=filters) - def render_module(self, module: Module, filters: List[str]) -> str: + def render_module(self, module: Module, filters: List[str] = None) -> str: """Returns a rendered Markdown for Module. Args: @@ -134,6 +135,8 @@ def render_module(self, module: Module, filters: List[str]) -> str: will be converted into HTML by MkDocs. Then the HTML is rendered into HTML again by other functions in this module. """ + if filters is None: + filters = [] module_filter = "" if "upper" in filters: module_filter = "|upper" @@ -145,6 +148,12 @@ def render_module(self, module: Module, filters: List[str]) -> str: module=module, module_filter=module_filter, object_filter=object_filter ) + def render_code(self, code: Code, filters: List[str] = None) -> str: + if filters is None: + filters = [] + template = self.templates["code"] + return template.render(code=code, module=code.module, filters=filters) + #: Renderer instance that can be used globally. renderer: Renderer = Renderer() diff --git a/mkapi/core/signature.py b/mkapi/core/signature.py index 7f2a5ba9..4b97dcb6 100644 --- a/mkapi/core/signature.py +++ b/mkapi/core/signature.py @@ -30,15 +30,18 @@ class Signature: obj: Any = field(default=None, repr=False) signature: Optional[inspect.Signature] = field(default=None, init=False) - parameters: Section = field(init=False) + parameters: Section = field(default_factory=Section, init=False) defaults: Dict[str, Any] = field(default_factory=dict, init=False) - attributes: Section = field(init=False) + attributes: Section = field(default_factory=Section, init=False) returns: str = field(default="", init=False) yields: str = field(default="", init=False) def __post_init__(self): if self.obj is None: return + if inspect.ismodule(self.obj): + self.set_attributes() + return if not callable(self.obj): return try: @@ -56,7 +59,9 @@ def __post_init__(self): self.defaults[name] = default else: self.defaults[name] = f"{default!r}" - if not type.endswith(", optional"): + if not type: + type = 'optional' + elif not type.endswith(", optional"): type += ", optional" items.append(Item(name, Type(type))) self.parameters = Section("Parameters", items=items) @@ -158,7 +163,7 @@ def to_string(annotation, kind: str = "returns", obj=None) -> str: return str(annotation).replace("typing.", "").lower() origin = annotation.__origin__ if origin is Union: - return union(annotation) + return union(annotation, obj=obj) if origin is tuple: args = [to_string(x, obj=obj) for x in annotation.__args__] if args: @@ -178,12 +183,12 @@ def to_string(annotation, kind: str = "returns", obj=None) -> str: if len(annotation.__args__) == 0: return annotation.__origin__.__name__.lower() if len(annotation.__args__) == 1: - return a_of_b(annotation) + return a_of_b(annotation, obj=obj) else: - return to_string_args(annotation) + return to_string_args(annotation, obj=obj) -def a_of_b(annotation) -> str: +def a_of_b(annotation, obj=None) -> str: """Returns A of B style string. Args: @@ -206,13 +211,13 @@ def a_of_b(annotation) -> str: name = origin.__name__.lower() if type(annotation.__args__[0]) == TypeVar: return name - type_ = f"{name} of " + to_string(annotation.__args__[0]) + type_ = f"{name} of " + to_string(annotation.__args__[0], obj=obj) if type_.endswith(" of T"): return name return type_ -def union(annotation) -> str: +def union(annotation, obj=None) -> str: """Returns a string for union annotation. Args: @@ -235,19 +240,19 @@ def union(annotation) -> str: and hasattr(args[1], "__name__") and args[1].__name__ == "NoneType" ): - return to_string(args[0]) + ", optional" + return to_string(args[0], obj=obj) + ", optional" else: - args = [to_string(x) for x in args] + args = [to_string(x, obj=obj) for x in args] if all(" " not in arg for arg in args): if len(args) == 2: return " or ".join(args) else: return ", ".join(args[:-1]) + ", or " + args[-1] else: - return "Union(" + ", ".join(to_string(x) for x in args) + ")" + return "Union(" + ", ".join(to_string(x, obj=obj) for x in args) + ")" -def to_string_args(annotation) -> str: +def to_string_args(annotation, obj=None) -> str: """Returns a string for callable and generator annotation. Args: @@ -271,7 +276,7 @@ def to_string_args(annotation) -> str: """ def to_string_with_prefix(annotation, prefix=","): - s = to_string(annotation) + s = to_string(annotation, obj=obj) if s in ["NoneType", "any"]: return "" else: @@ -281,12 +286,12 @@ def to_string_with_prefix(annotation, prefix=","): name = annotation.__origin__.__name__.lower() if name == "callable": *args, returns = args - args = ", ".join(to_string(x) for x in args) + args = ", ".join(to_string(x, obj=obj) for x in args) returns = to_string_with_prefix(returns, ":") return f"{name}({args}{returns})" elif name == "generator": arg, sends, returns = args - arg = to_string(arg) + arg = to_string(arg, obj=obj) sends = to_string_with_prefix(sends) returns = to_string_with_prefix(returns) if not sends and returns: @@ -294,7 +299,7 @@ def to_string_with_prefix(annotation, prefix=","): return f"{name}({arg}{sends}{returns})" elif name == "asyncgenerator": arg, sends = args - arg = to_string(arg) + arg = to_string(arg, obj) sends = to_string_with_prefix(sends) return f"{name}({arg}{sends})" else: diff --git a/mkapi/core/source.py b/mkapi/core/source.py deleted file mode 100644 index 7a5c5f21..00000000 --- a/mkapi/core/source.py +++ /dev/null @@ -1,40 +0,0 @@ -import ast -import inspect -from typing import List - -import _ast -from mkapi.core.module import Module, get_module -from mkapi.core.object import get_object - - -def get_source(module: Module, filters: List[str] = None) -> str: - """Returns a source for module.""" - with open(module.sourcefile, "r") as f: - source = f.read().strip() - if not source: - return "" - # return f"[DOCS](../{module.object.id}.md#{module.object.id})\n\n~~~python\n{source}\n~~~\n" - return source - - -# module = get_module("mkapi.core.base") -# -# source = get_source(module) -# -# module.object.id -# module.objects -# -# obj = get_object(module.objects[0]) -# inspect.getsourcelines(obj) -# -# node = ast.parse(source) -# for x in ast.iter_child_nodes(node): -# if isinstance(x, _ast.ClassDef): -# break -# -# x.lineno -# ast.get_source_segment(source, x) -# x.end_lineno -# dir(x) -# for a in ast.walk(x): -# print(a) diff --git a/mkapi/core/structure.py b/mkapi/core/structure.py index 0a3f2f72..14c527d4 100644 --- a/mkapi/core/structure.py +++ b/mkapi/core/structure.py @@ -9,6 +9,8 @@ split_prefix_and_name) from mkapi.core.signature import Signature, get_signature +"a.b.c".rpartition(".") + @dataclass class Object(Base): @@ -28,11 +30,12 @@ class Object(Base): prefix: str = "" qualname: str = "" + kind: str = "" + signature: Signature = field(default_factory=Signature) + module: str = field(init=False) markdown: str = field(init=False) id: str = field(init=False) - kind: str = "" type: Type = field(default_factory=Type, init=False) - signature: Signature = field(default_factory=Signature) def __post_init__(self): from mkapi.core import linker @@ -40,6 +43,10 @@ def __post_init__(self): self.id = self.name if self.prefix: self.id = ".".join([self.prefix, self.name]) + if not self.qualname: + self.module = self.id + else: + self.module = self.id[: -len(self.qualname) - 1] if not self.markdown: name = linker.link(self.name, self.id) if self.prefix: @@ -121,14 +128,6 @@ def __getitem__(self, index: Union[int, str, List[str]]): return member raise IndexError - def __getattr__(self, name: str): - """Returns a member Tree instance whose name is equal to `name`. - """ - try: - return self[name] - except IndexError: - raise AttributeError - def __len__(self): return len(self.members) @@ -149,3 +148,9 @@ def get_members(self) -> List["Tree"]: def get_markdown(self) -> str: """Returns a Markdown source for docstring of self.""" raise NotImplementedError + + def walk(self): + """Yields all members.""" + yield self + for member in self.members: + yield from member.walk() diff --git a/mkapi/plugins/api.py b/mkapi/plugins/api.py index 502ace47..cce442fa 100644 --- a/mkapi/plugins/api.py +++ b/mkapi/plugins/api.py @@ -79,8 +79,9 @@ def create_page(path: str, module: Module, filters: List[str]): def create_source_page(path: str, module: Module, filters: List[str]): + filters_str = "|".join(filters) with open(path, "w") as f: - f.write(module.get_source(filters)) + f.write(f"# ![mkapi]({module.object.id}|code|{filters_str})") def rmtree(path: str): diff --git a/mkapi/plugins/mkdocs.py b/mkapi/plugins/mkdocs.py index a393587e..d3fd0497 100644 --- a/mkapi/plugins/mkdocs.py +++ b/mkapi/plugins/mkdocs.py @@ -104,6 +104,7 @@ def on_files(self, files, config): def on_page_markdown(self, markdown, page, config, files): """Converts Markdown source to intermidiate version.""" abs_src_path = page.file.abs_src_path + clean_page_title(page) page = Page(markdown, abs_src_path, self.abs_api_paths) self.pages[abs_src_path] = page return page.markdown @@ -136,3 +137,9 @@ def clear_prefix(toc): toc_item.title = toc_item.title.split(".")[-1] clear_prefix(toc_item.children) return + + +def clean_page_title(page): + title = page.title + if title.startswith("![mkapi]("): + page.title = title[9:-1].split("|")[0] diff --git a/mkapi/templates/code.jinja2 b/mkapi/templates/code.jinja2 new file mode 100644 index 00000000..6e1c07a9 --- /dev/null +++ b/mkapi/templates/code.jinja2 @@ -0,0 +1,16 @@ +{% from 'macros.jinja2' import object_body -%} + +{% set upper = 'upper' in filters -%} +{% set prefix_url = '../../' + module.object.prefix + '#' + module.object.prefix -%} +{% set name_url = '../../' + module.object.id + '#' + module.object.id -%} +
+ +SOURCE CODE +{{ object_body(module.object, '', '', 'span', upper) }} +DOCS + + +
+{{ code.html|safe }} +
+
diff --git a/mkapi/templates/macros.jinja2 b/mkapi/templates/macros.jinja2 index b5491613..5a1eb1cf 100644 --- a/mkapi/templates/macros.jinja2 +++ b/mkapi/templates/macros.jinja2 @@ -33,11 +33,12 @@ {%- endif %} {%- endmacro %} -{%- macro object_body(object, prefix_url, name_url, tag, upper) -%} +{%- macro object_body(object, prefix_url, name_url, tag, upper, filters) -%} {% if object.prefix and '.' not in object.qualname -%} <{{ tag }} class="mkapi-object-prefix">{{ object_prefix(object, prefix_url, upper) }} {%- endif -%} - <{{ tag }} class="mkapi-object-name">{{ object_name(object, name_url, upper) }}{{ object_signature(object.signature, tag) }} {{ object_type(object) }} + <{{ tag }} class="mkapi-object-name">{{ object_name(object, name_url, upper) }}{{ object_signature(object.signature, tag) }}{{ object_type(object) }} + {%- if 'sourcelink' in filters or 'link' in filters or 'apilink' in filters %}SOURCE{% endif %} {%- endmacro -%} {% macro object_member(name, url, signature) -%} diff --git a/mkapi/templates/module.jinja2 b/mkapi/templates/module.jinja2 index 973025b3..c3f9b7ac 100644 --- a/mkapi/templates/module.jinja2 +++ b/mkapi/templates/module.jinja2 @@ -3,9 +3,9 @@ {% if module.object.kind == 'module' -%} {% for node in module.node.members -%} {% if 'noheading' in object_filter -%} -![mkapi]({{ node.object.id }}{{ object_filter }}) +![mkapi]({{ node.object.id }}{{ object_filter }}|sourcelink) {% else -%} -## ![mkapi]({{ node.object.id }}{{ object_filter }}) +## ![mkapi]({{ node.object.id }}{{ object_filter }}|sourcelink) {% endif -%} {% endfor -%} {% else -%} diff --git a/mkapi/templates/object_div.jinja2 b/mkapi/templates/object_div.jinja2 index 369b35ce..ade53f2d 100644 --- a/mkapi/templates/object_div.jinja2 +++ b/mkapi/templates/object_div.jinja2 @@ -4,5 +4,5 @@ {% set tag = 'span' if 'plain' in filters else 'code' -%}
{{ object.kind|upper }}
-
{{ object_body(object, prefix_url, name_url, tag, upper) }}
+
{{ object_body(object, prefix_url, name_url, tag, upper, filters) }}
diff --git a/mkapi/templates/object_heading.jinja2 b/mkapi/templates/object_heading.jinja2 index a9ef8d3d..45f74339 100644 --- a/mkapi/templates/object_heading.jinja2 +++ b/mkapi/templates/object_heading.jinja2 @@ -4,5 +4,5 @@ {% set tag = 'span' if 'plain' in filters else 'code' -%} {{ object.kind|upper }} -{{ object_body(object, prefix_url, name_url, tag, upper) }} +{{ object_body(object, prefix_url, name_url, tag, upper, filters) }} diff --git a/mkapi/theme/css/mkapi-common.css b/mkapi/theme/css/mkapi-common.css index 46a08e41..48b3cf98 100644 --- a/mkapi/theme/css/mkapi-common.css +++ b/mkapi/theme/css/mkapi-common.css @@ -108,6 +108,7 @@ span.mkapi-object-signature { border: none; padding: 0px; margin: 0px; + font-size: 0.9rem; } /************************************************** @@ -120,7 +121,7 @@ span.mkapi-object-signature { h1.mkapi-object.plain ~ .mkapi-docstring, h2.mkapi-object.plain ~ .mkapi-docstring, h3.mkapi-object.plain ~ .mkapi-docstring { - margin-left: 0px; + margin-left: 10px; } .mkapi-section { @@ -399,3 +400,24 @@ code.mkapi-args-name::after { .mkapi-members .mkapi-node { margin-bottom: 0px; } + + +/***************************************************** + Source Code +*****************************************************/ +a.mkapi-src-link, a.mkapi-docs-link { + font-size: 0.7rem; + font-weight: normal; + margin-left: 10px; + padding: 1px 3px; + border: 1px solid #BEBEBE; + background: #F0F0F0; + border-radius: 5px; + color: #BEBEBE; +} + +a.mkapi-src-link:hover, a.mkapi-docs-link:hover { + border: 1px solid #FF9999; + background: #FFF0F0; + color: red; +} diff --git a/mkdocs.yml b/mkdocs.yml index 3179e934..9d68e2bb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,9 @@ theme: hljs_languages: - yaml +extra_css: + - custom.css + plugins: - search - pheasant: @@ -23,6 +26,7 @@ plugins: nav: - index.md + - release.md - Examples: - examples/google_style.md - examples/numpy_style.md diff --git a/tests/core/test_core_module.py b/tests/core/test_core_module.py index d32f3615..d51ce4aa 100644 --- a/tests/core/test_core_module.py +++ b/tests/core/test_core_module.py @@ -6,12 +6,12 @@ def test_get_module(): assert module.parent is None assert module.object.markdown == "[mkapi](!mkapi)" assert "core" in module - core = module.core + core = module['core'] assert core.parent is module assert core.object.markdown == "[mkapi](!mkapi).[core](!mkapi.core)" assert core.object.kind == "package" assert "base" in core - base = core.base + base = core['base'] assert base.parent is core assert base.object.markdown == "[mkapi.core](!mkapi.core).[base](!mkapi.core.base)" assert base.object.kind == "module" @@ -20,5 +20,5 @@ def test_get_module(): def test_repr(): module = get_module("mkapi.core.base") - s = "Module('mkapi.core.base', num_sections=1, num_members=0)" + s = "Module('mkapi.core.base', num_sections=2, num_members=0)" assert repr(module) == s diff --git a/tests/core/test_core_node.py b/tests/core/test_core_node.py index f460d44d..6c4b1bb7 100644 --- a/tests/core/test_core_node.py +++ b/tests/core/test_core_node.py @@ -97,4 +97,4 @@ def test_package(): def test_repr(): node = get_node("mkapi.core.base") - assert repr(node) == "Node('mkapi.core.base', num_sections=1, num_members=6)" + assert repr(node) == "Node('mkapi.core.base', num_sections=2, num_members=6)" diff --git a/tests/core/test_core_postprocess.py b/tests/core/test_core_postprocess.py index 6faf34ea..52626935 100644 --- a/tests/core/test_core_postprocess.py +++ b/tests/core/test_core_postprocess.py @@ -51,11 +51,11 @@ def test_transform_property(node): def test_get_type(node): assert P.get_type(node).name == "" - assert P.get_type(node.f).name == "str" - assert P.get_type(node.g).name == "int" - assert P.get_type(node.a).name == "(int, str)" - assert P.get_type(node.b).name == "str" - node.g.docstring.sections[1] + assert P.get_type(node['f']).name == "str" + assert P.get_type(node['g']).name == "int" + assert P.get_type(node['a']).name == "(int, str)" + assert P.get_type(node['b']).name == "str" + node['g'].docstring.sections[1] def test_transform_class(node): diff --git a/theme/docs/api/mkapi.core.base.md b/theme/docs/api/mkapi.core.base.md deleted file mode 100644 index 4d143d28..00000000 --- a/theme/docs/api/mkapi.core.base.md +++ /dev/null @@ -1,16 +0,0 @@ -# ![mkapi](mkapi.core.base|upper|link|plain) - - -## ![mkapi](mkapi.core.base.Base|) - -## ![mkapi](mkapi.core.base.Inline|) - -## ![mkapi](mkapi.core.base.Type|) - -## ![mkapi](mkapi.core.base.Item|) - -## ![mkapi](mkapi.core.base.Section|) - -## ![mkapi](mkapi.core.base.Docstring|) - -## ![mkapi](mkapi.core.base.Object|) diff --git a/theme/docs/api/mkapi.core.docstring.md b/theme/docs/api/mkapi.core.docstring.md deleted file mode 100644 index 55c39a08..00000000 --- a/theme/docs/api/mkapi.core.docstring.md +++ /dev/null @@ -1,24 +0,0 @@ -# ![mkapi](mkapi.core.docstring|upper|link|plain) - - -## ![mkapi](mkapi.core.docstring.section_heading|) - -## ![mkapi](mkapi.core.docstring.split_section|) - -## ![mkapi](mkapi.core.docstring.split_parameter|) - -## ![mkapi](mkapi.core.docstring.parse_parameter|) - -## ![mkapi](mkapi.core.docstring.parse_parameters|) - -## ![mkapi](mkapi.core.docstring.parse_returns|) - -## ![mkapi](mkapi.core.docstring.get_section|) - -## ![mkapi](mkapi.core.docstring.parse_bases|) - -## ![mkapi](mkapi.core.docstring.parse_property|) - -## ![mkapi](mkapi.core.docstring.parse_attribute|) - -## ![mkapi](mkapi.core.docstring.get_docstring|) diff --git a/theme/docs/api/mkapi.core.inherit.md b/theme/docs/api/mkapi.core.inherit.md deleted file mode 100644 index fba92adc..00000000 --- a/theme/docs/api/mkapi.core.inherit.md +++ /dev/null @@ -1,18 +0,0 @@ -# ![mkapi](mkapi.core.inherit|upper|link|plain) - - -## ![mkapi](mkapi.core.inherit.get_params|) - -## ![mkapi](mkapi.core.inherit.is_complete|) - -## ![mkapi](mkapi.core.inherit.inherit_base|) - -## ![mkapi](mkapi.core.inherit.inherit_signature|) - -## ![mkapi](mkapi.core.inherit.inherit_parameters|) - -## ![mkapi](mkapi.core.inherit.get_bases|) - -## ![mkapi](mkapi.core.inherit.inherit|) - -## ![mkapi](mkapi.core.inherit.inherit_by_filters|) diff --git a/theme/docs/api/mkapi.core.linker.md b/theme/docs/api/mkapi.core.linker.md deleted file mode 100644 index a44295c3..00000000 --- a/theme/docs/api/mkapi.core.linker.md +++ /dev/null @@ -1,12 +0,0 @@ -# ![mkapi](mkapi.core.linker|upper|link|plain) - - -## ![mkapi](mkapi.core.linker.link|) - -## ![mkapi](mkapi.core.linker.get_link|) - -## ![mkapi](mkapi.core.linker.resolve_link|) - -## ![mkapi](mkapi.core.linker.resolve_object|) - -## ![mkapi](mkapi.core.linker.replace_link|) diff --git a/theme/docs/api/mkapi.core.md b/theme/docs/api/mkapi.core.md deleted file mode 100644 index 6ac679d3..00000000 --- a/theme/docs/api/mkapi.core.md +++ /dev/null @@ -1,2 +0,0 @@ -# ![mkapi](mkapi.core|upper|link|plain) - diff --git a/theme/docs/api/mkapi.core.module.md b/theme/docs/api/mkapi.core.module.md deleted file mode 100644 index 54916722..00000000 --- a/theme/docs/api/mkapi.core.module.md +++ /dev/null @@ -1,6 +0,0 @@ -# ![mkapi](mkapi.core.module|upper|link|plain) - - -## ![mkapi](mkapi.core.module.Module|) - -## ![mkapi](mkapi.core.module.get_module|) diff --git a/theme/docs/api/mkapi.core.node.md b/theme/docs/api/mkapi.core.node.md deleted file mode 100644 index 64cbb8d2..00000000 --- a/theme/docs/api/mkapi.core.node.md +++ /dev/null @@ -1,8 +0,0 @@ -# ![mkapi](mkapi.core.node|upper|link|plain) - - -## ![mkapi](mkapi.core.node.Node|) - -## ![mkapi](mkapi.core.node.is_member|) - -## ![mkapi](mkapi.core.node.get_node|) diff --git a/theme/docs/api/mkapi.core.object.md b/theme/docs/api/mkapi.core.object.md deleted file mode 100644 index 094b071d..00000000 --- a/theme/docs/api/mkapi.core.object.md +++ /dev/null @@ -1,12 +0,0 @@ -# ![mkapi](mkapi.core.object|upper|link|plain) - - -## ![mkapi](mkapi.core.object.get_object|) - -## ![mkapi](mkapi.core.object.get_fullname|) - -## ![mkapi](mkapi.core.object.split_prefix_and_name|) - -## ![mkapi](mkapi.core.object.get_sourcefiles|) - -## ![mkapi](mkapi.core.object.from_object|) diff --git a/theme/docs/api/mkapi.core.page.md b/theme/docs/api/mkapi.core.page.md deleted file mode 100644 index 8574bfff..00000000 --- a/theme/docs/api/mkapi.core.page.md +++ /dev/null @@ -1,4 +0,0 @@ -# ![mkapi](mkapi.core.page|upper|link|plain) - - -## ![mkapi](mkapi.core.page.Page|) diff --git a/theme/docs/api/mkapi.core.renderer.md b/theme/docs/api/mkapi.core.renderer.md deleted file mode 100644 index 0e3d4fe7..00000000 --- a/theme/docs/api/mkapi.core.renderer.md +++ /dev/null @@ -1,4 +0,0 @@ -# ![mkapi](mkapi.core.renderer|upper|link|plain) - - -## ![mkapi](mkapi.core.renderer.Renderer|) diff --git a/theme/docs/api/mkapi.core.signature.md b/theme/docs/api/mkapi.core.signature.md deleted file mode 100644 index 6cf5c617..00000000 --- a/theme/docs/api/mkapi.core.signature.md +++ /dev/null @@ -1,10 +0,0 @@ -# ![mkapi](mkapi.core.signature|upper|link|plain) - - -## ![mkapi](mkapi.core.signature.Signature|) - -## ![mkapi](mkapi.core.signature.to_string|) - -## ![mkapi](mkapi.core.signature.a_of_b|) - -## ![mkapi](mkapi.core.signature.union|) diff --git a/theme/docs/api/mkapi.core.tree.md b/theme/docs/api/mkapi.core.tree.md deleted file mode 100644 index 6f0270e4..00000000 --- a/theme/docs/api/mkapi.core.tree.md +++ /dev/null @@ -1,4 +0,0 @@ -# ![mkapi](mkapi.core.tree|upper|link|plain) - - -## ![mkapi](mkapi.core.tree.Tree|) diff --git a/theme/docs/api/mkapi.plugins.mkdocs.md b/theme/docs/api/mkapi.plugins.mkdocs.md deleted file mode 100644 index 6ea9c422..00000000 --- a/theme/docs/api/mkapi.plugins.mkdocs.md +++ /dev/null @@ -1,4 +0,0 @@ -# ![mkapi](mkapi.plugins.mkdocs|upper|link|plain) - - -## ![mkapi](mkapi.plugins.mkdocs.MkapiPlugin|) diff --git a/theme/mkdocs.yml b/theme/mkdocs.yml index 0ac0d2e8..1f00abaf 100644 --- a/theme/mkdocs.yml +++ b/theme/mkdocs.yml @@ -1,6 +1,6 @@ site_name: Example -theme: ivory +theme: mkdocs plugins: - search