Add API documentation

dev
Andrew Cassidy 3 years ago
parent 5cc815d8b6
commit f085f318b3

@ -1,10 +1,10 @@
{% extends "!layout.html" %}
{% block extrahead %}
<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="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="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="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="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="128x128" href="/_static/icon-128.png">
<link rel="icon" type="image/png" sizes="256x256" href="/_static/icon-256.png">
{{ super() }}
{% endblock %}

@ -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),
}

@ -11,7 +11,7 @@ Welcome to Yaclog's documentation!
:includehidden:
:caption: Contents:
Changelog <changelog>
API Reference <reference/index.rst>
.. toctree::
: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
# 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<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',
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
"""

@ -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 <https://www.gnu.org/licenses/>.
import re
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)')
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

@ -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
#

Loading…
Cancel
Save