Add API documentation

dev
Andrew Cassidy 3 years ago
parent 5cc815d8b6
commit f085f318b3

@ -1,10 +1,10 @@
{% extends "!layout.html" %} {% extends "!layout.html" %}
{% block extrahead %} {% block extrahead %}
<link rel="icon" type="image/png" sizes="16x16" href="_static/icon-16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/_static/icon-16.png">
<link rel="icon" type="image/png" sizes="32x32" href="_static/icon-32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/_static/icon-32.png">
<link rel="icon" type="image/png" sizes="48x48" href="_static/icon-48.png"> <link rel="icon" type="image/png" sizes="48x48" href="/_static/icon-48.png">
<link rel="icon" type="image/png" sizes="64x64" href="_static/icon-64.png"> <link rel="icon" type="image/png" sizes="64x64" href="/_static/icon-64.png">
<link rel="icon" type="image/png" sizes="128x128" href="_static/icon-128.png"> <link rel="icon" type="image/png" sizes="128x128" href="/_static/icon-128.png">
<link rel="icon" type="image/png" sizes="256x256" href="_static/icon-256.png"> <link rel="icon" type="image/png" sizes="256x256" href="/_static/icon-256.png">
{{ super() }} {{ super() }}
{% endblock %} {% endblock %}

@ -4,16 +4,18 @@
# list see the documentation: # list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html # https://www.sphinx-doc.org/en/master/usage/configuration.html
from pkg_resources import get_distribution
# -- Path setup -------------------------------------------------------------- # -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory, # 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 # 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. # documentation root, use os.path.abspath to make it absolute, like shown here.
# #
# import os import os
# import sys import sys
# sys.path.insert(0, os.path.abspath('.'))
from pkg_resources import get_distribution sys.path.insert(0, os.path.abspath('..'))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
@ -29,7 +31,10 @@ version = '.'.join(release.split('.')[:3])
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ 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. # 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. # This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
default_role = 'py:obj'
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # 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". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ['_static']
html_css_files = ['css/custom.css'] 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),
}

@ -11,7 +11,7 @@ Welcome to Yaclog's documentation!
:includehidden: :includehidden:
:caption: Contents: :caption: Contents:
Changelog <changelog> API Reference <reference/index.rst>
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1

@ -0,0 +1,5 @@
Changelog Module
================
.. automodule:: yaclog.changelog
:members:

@ -0,0 +1,8 @@
API Reference
=============
.. toctree::
:maxdepth: 1
:glob:
*

@ -0,0 +1,5 @@
Markdown Module
==============
.. automodule:: yaclog.markdown
:members:

@ -0,0 +1,5 @@
Version Module
==============
.. automodule:: yaclog.version
:members:

@ -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 # yaclog: yet another changelog tool
# Copyright (c) 2021. Andrew Cassidy # Copyright (c) 2021. Andrew Cassidy
# #
@ -32,23 +37,21 @@ default_header = '# Changelog\n\nAll notable changes to this project will be doc
class VersionEntry: class VersionEntry:
"""Holds a single version entry in a :py:class:`Changelog`""" """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<name>.*?)(?:\s+-)?(?:\s+(?P<date>\d{4}-\d{2}-\d{2}))?(?P<tags>(?:\s+\[[^]]*?])*)\s*$") r"##\s+(?P<name>.*?)(?:\s+-)?(?:\s+(?P<date>\d{4}-\d{2}-\d{2}))?(?P<tags>(?:\s+\[[^]]*?])*)\s*$")
tag_regex = re.compile(r'\[(?P<tag>[^]]*?)]') _tag_regex = re.compile(r'\[(?P<tag>[^]]*?)]')
def __init__(self, name: str = 'Unreleased', def __init__(self, name: str = 'Unreleased',
date: Optional[datetime.date] = None, tags: Optional[List[str]] = None, date: Optional[datetime.date] = None, tags: Optional[List[str]] = None,
link: Optional[str] = None, link_id: Optional[str] = None, line_no: Optional[int] = 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 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 tags: The version's tags
:param link: The version's URL :param link: The version's URL
:param link_id: The version's link ID :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 self.name: str = name
@ -67,7 +70,7 @@ class VersionEntry:
"""The version's link ID, uses the version name by default when writing""" """The version's link ID, uses the version name by default when writing"""
self.line_no: Optional[int] = line_no 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, This is not guaranteed to be correct after the changelog has been modified,
and it has no effect on the written file""" and it has no effect on the written file"""
@ -86,7 +89,7 @@ class VersionEntry:
""" """
version = cls(line_no=line_no) 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}"' assert match, f'failed to parse version header: "{header}"'
version.name, version.link, version.link_id = markdown.strip_link(match['name']) 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) return cls(name=header.lstrip('#').strip(), line_no=line_no)
if match['tags']: 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 return version
@ -200,11 +203,13 @@ class VersionEntry:
return contents return contents
@property @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) return yaclog.version.is_release(self.name)
@property @property
def version(self): 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] return yaclog.version.extract_version(self.name)[0]
def __str__(self) -> str: def __str__(self) -> str:
@ -216,7 +221,7 @@ class Changelog:
def __init__(self, path=None, header: str = default_header): 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 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 :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] = [] self.versions: List[VersionEntry] = []
"""A list of versions in the changelog""" """A list of versions in the changelog"""
self.links = {} self.links: Dict[str, str] = {}
"""Link IDs at the end of the changelog""" """Link IDs at the end of the changelog"""
if path and os.path.exists(path): if path and os.path.exists(path):
@ -339,12 +344,12 @@ class Changelog:
Get the current version from the changelog Get the current version from the changelog
:param released: if the returned version should be a released version, :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: if a new version should be created if none exist.
:param new_version_name: The name of the version to create if there :param new_version_name: The name of the version to create if there
are no matches and ``new_version`` is True. are no matches and ``new_version`` is True.
:return: The current version matching the criteria, :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` # return the first version that matches `released`
@ -365,7 +370,7 @@ class Changelog:
""" """
Get a version from the 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 :return: The first version with the selected name
""" """

@ -1,3 +1,7 @@
"""
Tools for parsing and manipulating markdown, including a very basic tokenizer.
"""
# yaclog: yet another changelog tool # yaclog: yet another changelog tool
# Copyright (c) 2021. Andrew Cassidy # Copyright (c) 2021. Andrew Cassidy
# #
@ -13,6 +17,7 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import re import re
from typing import List from typing import List
@ -31,23 +36,23 @@ setext_h1_replace_regex = re.compile(r'(?<=\n)(?P<header>[^\n]+?)\n=+[ \t]*(?=\n
setext_h2_replace_regex = re.compile(r'(?<=\n)(?P<header>[^\n]+?)\n-+[ \t]*(?=\n)') setext_h2_replace_regex = re.compile(r'(?<=\n)(?P<header>[^\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 :param text: An input string which may be a markdown link, either literal or an ID
:return: A tuple of (name, url, 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) # in the form [name](link)
return link_lit['text'], link_lit['link'], None 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 # 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 link_def['text'], None, link_def['link_id'].lower()
return token, None, None return text, None, None
def join(segments: List[str]) -> str: def join(segments: List[str]) -> str:
@ -76,10 +81,17 @@ def join(segments: List[str]) -> str:
class Token: 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): def __init__(self, line_no: int, lines: List[str], kind: str):
self.line_no = line_no self.line_no = line_no
"""Which line this block appears on in the original file"""
self.lines = lines self.lines = lines
"""The lines of text making up this block"""
self.kind = kind self.kind = kind
"""What kind of token this is. One of ``h[1-6]``, ``p``, ``li`` or ``code``"""
def __str__(self): def __str__(self):
return f'{self.kind}: {self.lines}' return f'{self.kind}: {self.lines}'
@ -93,7 +105,7 @@ def tokenize(text: str):
(Headers, top-level list items, links, code blocks, paragraphs). (Headers, top-level list items, links, code blocks, paragraphs).
:param text: input text to tokenize :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 # convert setext-style headers

@ -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 # yaclog: yet another changelog tool
# Copyright (c) 2021. Andrew Cassidy # Copyright (c) 2021. Andrew Cassidy
# #

Loading…
Cancel
Save