From f085f318b3a2c6f209736789947aeff6b7a4a466 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Tue, 4 May 2021 21:01:30 -0700 Subject: [PATCH] Add API documentation --- docs/_templates/layout.html | 12 ++++++------ docs/conf.py | 38 +++++++++++++++++++++++++++++++----- docs/index.rst | 2 +- docs/reference/changelog.rst | 5 +++++ docs/reference/index.rst | 8 ++++++++ docs/reference/markdown.rst | 5 +++++ docs/reference/version.rst | 5 +++++ yaclog/changelog.py | 35 +++++++++++++++++++-------------- yaclog/markdown.py | 28 ++++++++++++++++++-------- yaclog/version.py | 5 +++++ 10 files changed, 108 insertions(+), 35 deletions(-) create mode 100644 docs/reference/changelog.rst create mode 100644 docs/reference/index.rst create mode 100644 docs/reference/markdown.rst create mode 100644 docs/reference/version.rst diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index b9c6754..8435f03 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -1,10 +1,10 @@ {% extends "!layout.html" %} {% block extrahead %} - - - - - - + + + + + + {{ super() }} {% endblock %} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 086680a..2ddee8f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,16 +4,18 @@ # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html +from pkg_resources import get_distribution + # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) -from pkg_resources import get_distribution +import os +import sys + +sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- @@ -29,7 +31,10 @@ version = '.'.join(release.split('.')[:3]) # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'myst_parser', 'sphinx_rtd_theme' + 'myst_parser', + 'sphinx_rtd_theme', + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', ] # Add any paths that contain templates here, relative to this directory. @@ -40,6 +45,8 @@ templates_path = ['_templates'] # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +default_role = 'py:obj' + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for @@ -54,3 +61,24 @@ html_favicon = 'favicon.ico' # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] html_css_files = ['css/custom.css'] + +# -- Options for Autodoc ----------------------------------------------------- + +add_module_names = False +autodoc_docstring_signature = True +autoclass_content = 'both' + +autodoc_default_options = { + 'member-order': 'bysource', + 'undoc-members': True, +} + +# -- Options for Intersphinx ------------------------------------------------- + +# This config value contains the locations and names of other projects that +# should be linked to in this documentation. + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'packaging': ('https://packaging.pypa.io/en/latest/', None), +} diff --git a/docs/index.rst b/docs/index.rst index 609940d..edc6a6a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ Welcome to Yaclog's documentation! :includehidden: :caption: Contents: - Changelog + API Reference .. toctree:: :maxdepth: 1 diff --git a/docs/reference/changelog.rst b/docs/reference/changelog.rst new file mode 100644 index 0000000..2f66447 --- /dev/null +++ b/docs/reference/changelog.rst @@ -0,0 +1,5 @@ +Changelog Module +================ + +.. automodule:: yaclog.changelog + :members: \ No newline at end of file diff --git a/docs/reference/index.rst b/docs/reference/index.rst new file mode 100644 index 0000000..c74a015 --- /dev/null +++ b/docs/reference/index.rst @@ -0,0 +1,8 @@ +API Reference +============= + +.. toctree:: + :maxdepth: 1 + :glob: + + * \ No newline at end of file diff --git a/docs/reference/markdown.rst b/docs/reference/markdown.rst new file mode 100644 index 0000000..c8b48d2 --- /dev/null +++ b/docs/reference/markdown.rst @@ -0,0 +1,5 @@ +Markdown Module +============== + +.. automodule:: yaclog.markdown + :members: \ No newline at end of file diff --git a/docs/reference/version.rst b/docs/reference/version.rst new file mode 100644 index 0000000..c722d3e --- /dev/null +++ b/docs/reference/version.rst @@ -0,0 +1,5 @@ +Version Module +============== + +.. automodule:: yaclog.version + :members: \ No newline at end of file diff --git a/yaclog/changelog.py b/yaclog/changelog.py index 56082d6..668aa7f 100644 --- a/yaclog/changelog.py +++ b/yaclog/changelog.py @@ -1,3 +1,8 @@ +""" +Contains the `Changelog` class that represents a parsed changelog file that can be read from and written to +disk as markdown, as well as the `VersionEntry` class that represents a single version within that changelog +""" + # yaclog: yet another changelog tool # Copyright (c) 2021. Andrew Cassidy # @@ -32,23 +37,21 @@ default_header = '# Changelog\n\nAll notable changes to this project will be doc class VersionEntry: """Holds a single version entry in a :py:class:`Changelog`""" - header_regex = re.compile( # THE LANGUAGE OF THE GODS + _header_regex = re.compile( # THE LANGUAGE OF THE GODS r"##\s+(?P.*?)(?:\s+-)?(?:\s+(?P\d{4}-\d{2}-\d{2}))?(?P(?:\s+\[[^]]*?])*)\s*$") - tag_regex = re.compile(r'\[(?P[^]]*?)]') + _tag_regex = re.compile(r'\[(?P[^]]*?)]') def __init__(self, name: str = 'Unreleased', date: Optional[datetime.date] = None, tags: Optional[List[str]] = None, link: Optional[str] = None, link_id: Optional[str] = None, line_no: Optional[int] = None): """ - Create a new version entry - :param str name: The version's name - :param date: When the version was released + :param Optional[datetime.date] date: When the version was released :param tags: The version's tags :param link: The version's URL :param link_id: The version's link ID - :param line_no What line in the original file the version starts on + :param line_no: What line in the original file the version starts on """ self.name: str = name @@ -67,7 +70,7 @@ class VersionEntry: """The version's link ID, uses the version name by default when writing""" self.line_no: Optional[int] = line_no - """What line the version occurs at in the file, or None if the version was not read from a file. + """What line the version occurs at in the file, or `None` if the version was not read from a file. This is not guaranteed to be correct after the changelog has been modified, and it has no effect on the written file""" @@ -86,7 +89,7 @@ class VersionEntry: """ version = cls(line_no=line_no) - match = cls.header_regex.match(header) + match = cls._header_regex.match(header) assert match, f'failed to parse version header: "{header}"' version.name, version.link, version.link_id = markdown.strip_link(match['name']) @@ -98,7 +101,7 @@ class VersionEntry: return cls(name=header.lstrip('#').strip(), line_no=line_no) if match['tags']: - version.tags = [m['tag'].upper() for m in cls.tag_regex.finditer(match['tags'])] + version.tags = [m['tag'].upper() for m in cls._tag_regex.finditer(match['tags'])] return version @@ -200,11 +203,13 @@ class VersionEntry: return contents @property - def released(self): + def released(self) -> bool: + """Returns true if a PEP440 version number is present in the version name, and has no prerelease segments""" return yaclog.version.is_release(self.name) @property def version(self): + """Returns the PEP440 version number from the version name, or `None` if none is found""" return yaclog.version.extract_version(self.name)[0] def __str__(self) -> str: @@ -216,7 +221,7 @@ class Changelog: def __init__(self, path=None, header: str = default_header): """ - Create a new changelog object. Contents will be automatically read from disk if the file exists + Contents will be automatically read from disk if the file exists :param path: The changelog's path on disk :param header: The header at the top of the changelog to use if the file does not exist @@ -230,7 +235,7 @@ class Changelog: self.versions: List[VersionEntry] = [] """A list of versions in the changelog""" - self.links = {} + self.links: Dict[str, str] = {} """Link IDs at the end of the changelog""" if path and os.path.exists(path): @@ -339,12 +344,12 @@ class Changelog: Get the current version from the changelog :param released: if the returned version should be a released version, - an unreleased version, or ``None`` to return the most recent + an unreleased version, or `None` to return the most recent :param new_version: if a new version should be created if none exist. :param new_version_name: The name of the version to create if there are no matches and ``new_version`` is True. :return: The current version matching the criteria, - or None if ``new_version`` is disabled and none are found. + or `None` if ``new_version`` is disabled and none are found. """ # return the first version that matches `released` @@ -365,7 +370,7 @@ class Changelog: """ Get a version from the changelog - :param name: The name of the version to get, or ``None`` to return the most recent + :param name: The name of the version to get, or `None` to return the most recent :return: The first version with the selected name """ diff --git a/yaclog/markdown.py b/yaclog/markdown.py index d854490..9a6672f 100644 --- a/yaclog/markdown.py +++ b/yaclog/markdown.py @@ -1,3 +1,7 @@ +""" +Tools for parsing and manipulating markdown, including a very basic tokenizer. +""" + # yaclog: yet another changelog tool # Copyright (c) 2021. Andrew Cassidy # @@ -13,6 +17,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . + import re from typing import List @@ -31,23 +36,23 @@ setext_h1_replace_regex = re.compile(r'(?<=\n)(?P
[^\n]+?)\n=+[ \t]*(?=\n setext_h2_replace_regex = re.compile(r'(?<=\n)(?P
[^\n]+?)\n-+[ \t]*(?=\n)') -def strip_link(token): +def strip_link(text): """ - Parses and removes any links from the token + Parses and removes any links from the input string - :param token: An input token which may be a markdown link, either literal or an ID - :return: A tuple of (name, url, id) + :param text: An input string which may be a markdown link, either literal or an ID + :return: A tuple of (name, url, id). If the input is not a link, it is returned verbatim as the name. """ - if link_lit := link_lit_regex.fullmatch(token): + if link_lit := link_lit_regex.fullmatch(text): # in the form [name](link) return link_lit['text'], link_lit['link'], None - if link_def := link_def_regex.fullmatch(token): + if link_def := link_def_regex.fullmatch(text): # in the form [name][id] where id is hopefully linked somewhere else in the document return link_def['text'], None, link_def['link_id'].lower() - return token, None, None + return text, None, None def join(segments: List[str]) -> str: @@ -76,10 +81,17 @@ def join(segments: List[str]) -> str: class Token: + """A single tokenized block of markdown, consisting of one or more lines of text.""" + def __init__(self, line_no: int, lines: List[str], kind: str): self.line_no = line_no + """Which line this block appears on in the original file""" + self.lines = lines + """The lines of text making up this block""" + self.kind = kind + """What kind of token this is. One of ``h[1-6]``, ``p``, ``li`` or ``code``""" def __str__(self): return f'{self.kind}: {self.lines}' @@ -93,7 +105,7 @@ def tokenize(text: str): (Headers, top-level list items, links, code blocks, paragraphs). :param text: input text to tokenize - :return: A list of tokens + :return: A list of tokens and a dictionary of links """ # convert setext-style headers diff --git a/yaclog/version.py b/yaclog/version.py index 0b87bb0..9439995 100644 --- a/yaclog/version.py +++ b/yaclog/version.py @@ -1,3 +1,8 @@ +""" +Various helper functions for analyzing and manipulating PEP440 version numbers, +meant to augment the `packaging.version` module. +""" + # yaclog: yet another changelog tool # Copyright (c) 2021. Andrew Cassidy #