Skip to content

Commit

Permalink
Docs-Source link
Browse files Browse the repository at this point in the history
  • Loading branch information
daizutabi committed Jun 15, 2020
1 parent db5ff85 commit c74f4ab
Show file tree
Hide file tree
Showing 46 changed files with 313 additions and 249 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions docs/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h1, h2, h3 {
margin-bottom: 10px;
}
2 changes: 1 addition & 1 deletion docs/examples/google_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/numpy_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 3 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions docs/release.md
Original file line number Diff line number Diff line change
@@ -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)).
30 changes: 18 additions & 12 deletions docs/usage/inherit.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
6 changes: 2 additions & 4 deletions docs/usage/page.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
30 changes: 30 additions & 0 deletions examples/inherit_comment.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion mkapi/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 0 additions & 2 deletions mkapi/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
90 changes: 90 additions & 0 deletions mkapi/core/code.py
Original file line number Diff line number Diff line change
@@ -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<span class="c1"># __mkapi__:(.*?):(.*?)</span>')


def replace(html):
def func(match):
module, object = match.groups()
link = f'<span id="{object}"></span>'
link += f'<a class="mkapi-docs-link"'
link += f'href="../../{module}#{object}">DOCS</a>'
return link

return COMMENT_PATTERN.sub(func, html)


def get_code(name: str) -> Code:
module = get_module(name)
return Code(module)
7 changes: 5 additions & 2 deletions mkapi/core/docstring.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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)


Expand Down
6 changes: 0 additions & 6 deletions mkapi/core/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
21 changes: 14 additions & 7 deletions mkapi/core/page.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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))
Expand All @@ -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):
Expand Down
15 changes: 15 additions & 0 deletions mkapi/core/postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,25 @@ 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
if node.object.kind in ["class", "dataclass"]:
transform_class(node)
elif node.object.kind in ["module", "package"]:
transform_module(node, filters)
for x in node.walk():
sort(x)
Loading

0 comments on commit c74f4ab

Please sign in to comment.