ClammingPy design

What this is

In short

A Python library is described twice: once in the source, where the author writes docstrings for the reader of the code, and once in a documentation site, written for the reader of the library. The second description is a copy of the first, and a copy drifts. This reads the source and writes the site, so that only one description exists.

What it produces is Markdown, and HTML built from that Markdown. The HTML is not a page of its own invention: it is written for Whakerexa, which is what makes the pages accessible.

Where it comes from

Documentation tools ask the source to be complete before they say anything about it: a missing type, an unknown field, a docstring written in a style the tool does not know, and the entry is dropped or the run fails. A library that grows is never in that state, and the author is left choosing between writing for the tool and writing for the reader.

The choice made here is the other one: flexibility rather than completeness. Whatever the docstring holds is documented, in the shape it was written. Nothing is required, and nothing is rejected.

What it does not do

It documents classes. A function written at module level and named in __all__ is skipped in silence: ClamsPack keeps only what passes inspect.isclass(), and ClammingClassParser raises a TypeError for anything else. This is a gap, not a decision, and it is written here as such.

It does not read inheritance: a class is documented with what its own source holds, and an inherited method appears in the page of the class that defines it. It does not resolve cross-references between classes, does not read type annotations to build a signature, and does not compare one version of a library to another.

It does not check the accessibility of what it writes. The HTML is built to be accessible, and it is Whakerexa that carries that property; nothing in the generator verifies it.

The needs

The problem, and what the tool is for

A Python library carries its own description: docstrings, written where the code is written, by whoever wrote it. Published documentation is a second description of the same thing, and two descriptions of one thing diverge — the second one being the one nobody rereads.

The tools that close that gap ask for something in exchange. They ask the docstring to be complete, and drop what is not; they ask it to be written in the markup they parse, and fail on the rest; and what they output is a site whose accessibility is whatever their default theme happens to give. An author who writes for the reader of the code has to write a second time for the tool.

The question this answers is therefore: how is a publishable documentation produced from the docstrings as they are already written — whatever their style, however incomplete — in more than one format, one of which is accessible, without asking the author to write anything twice?

What follows from that question, as objectives:

  • read the docstrings as they stand, and document whatever they hold;
  • accept the field styles that Python authors actually use, and the variants they write;
  • produce Markdown for what is read in a repository, and HTML for what is read on a site;
  • make the HTML accessible by construction rather than by inspection, which is what building it for Whakerexa buys;
  • document a whole library — modules, and the classes of each — in one run;
  • let prose written by hand into the generated pages, for what a docstring is not the place for;
  • remain a plain object-oriented Python library, so that what it does not do can be added to it.

What it is not: a replacement for the docstrings, a linter that judges them, or a site generator with a documentation feature. It documents; it does not rate what it documents.

The needs, expressed

Reading what is there. The first need is not a feature but a posture: the docstring is the author's, not the tool's. Whatever is written is documented, in the shape it was written, and what is not written raises nothing. A library halfway through its documentation effort is a normal library, and it is documented as it stands.

Understanding the conventions anyway. Reading what is there does not mean reading it flatly. The conventions Python authors use — the reST field lists, the Epydoc ones, the variants they write without thinking — carry meaning: a parameter, a returned value, an exception, an example. What is recognized is laid out as such. What is not recognized is kept, not dropped.

One run, one library. The unit of work is not the class. An author publishes a library, so the run takes a list of modules and writes everything: a page per class, an index per module, an index for the whole, and the links between them.

Two formats, one truth. A repository reads Markdown, a site reads HTML, and both are needed. What must not happen is two conversions saying two different things, so one conversion feeds both.

Accessibility as a function. The HTML output is not accessible as a matter of good will: the structure and the stylesheets come from Whakerexa, whose conformity to WCAG 2.1 is its own subject. What the generator owes is to produce the markup that framework expects, and nothing that undoes it.

Two axes of adjustment, not one. What a reader adjusts falls in two independent places. The first is the text itself: the contrast switch of Whakerexa raises the font size from 16 to 18 px, the line height from 1.2 to 1.5, the letter spacing from 0.04 to 0.12 rem, and thickens the outlines — it touches no colour but that of the links in light mode. The second is the palette: flat background, maximum ratio, no gradient, no rounded corner. A reader who needs the second is not served by the first, and the colours of the documentation are the same whether the contrast switch is on or off.

Prose that is not a docstring. A library has things to say that no function docstring can hold: what it is for, how it is installed, where it comes from. That prose is written in Markdown, kept beside the code, and it belongs in the generated pages.

The pages belong to the documented software. Name, logo, address, copyright, favicon, language, and the paths where the statics live: the author sets them. The generator is not a brand.

Saying what was not done. A generator that silently omits is worse than one that fails: the author discovers the hole in the published site. What could not be documented is said during the run.

Costing nothing to the reader of the library. Whoever installs the documented library installs a library, not a documentation toolchain. What the generator needs, the generator needs — not its user's users.

Elicitation: the concepts

The needs above become, in the application, this closed set of concepts. They are what the code names, and what the rest of this file talks about.

  • Docstring: the text the author wrote under a class, a constructor or a function. The one source.
  • Field: a line of a docstring opening with : or @, carrying a name and a text. Recognized or not, it is a field.
  • Documented entity: what is known of one thing — a name, a list of argument names, a source code, a docstring. A class, a constructor and a function are all of them one of these.
  • Class: one documented entity, one constructor, and a set of functions, split by what their names say — public, private, protected, overload.
  • Module: the classes its __all__ declares, and the README found beside it.
  • Set of modules: what one run documents, and what the general index lists.
  • Output format: Markdown, or HTML built from it.
  • Page: one file of the HTML output — a class, the index of a module, the general index.
  • Theme: a whole stylesheet, holding the palette and nothing structural. Themes are exclusive: one is active, and activating another replaces it entirely.
  • Export options: what surrounds the documentation and belongs to the documented software rather than to its code.

Elicitation: the pathway

The needs are met along one path, and each step of it is a place where something can be given up without the next step failing:

Source → entities → Markdown → HTML → files. Reading the source gives entities; a docstring that says nothing gives an entity that says nothing. Converting gives Markdown; an unrecognized field is kept as prose. Rendering gives HTML; a missing renderer gives no HTML and a warning, and the Markdown produced before it stands. Writing gives files; the files that were written before an error are written.

Elicitation: what the needs left to settle

Collected as they are, the needs are neither precise nor compatible. What follows is what had to be settled before a single functional item could be written.

Words that had to be given a meaning
SaidTaken to mean
"a whole library"The classes named in the __all__ of each module given to the run. The public interface is what the author declared, not what the interpreter can reach.
"more than one format"Markdown and HTML, both from one conversion, so that they cannot say different things.
"accessible"HTML built for Whakerexa, whose conformity is stated there. The generator neither measures it nor checks it. Markdown carries no such claim.
"produced by a program"Run by the author before publishing, not computed when a page is read. A published page is as old as the last run, and that run overwrites the files it wrote before.
"prose written by hand"A README file found beside the code — beside the library for the general index, inside a module for the page of that module — inserted as it stands.
"what could not be documented is said"A warning in the log of the run. Nothing is written in the pages, and nothing stops.
Needs that could not both be served, and what was decided
TensionDecision
The author writes as they please, and the documentation is expected to state parameters, returns and exceptionsThe author wins. Nothing is required, nothing is invented: a page is worth what its docstring is worth. The conventions are made easy to follow, never mandatory.
Two formats, and nothing imposed on whoever installs the documented libraryMarkdown asks for the standard library alone; HTML asks for markdown2 and Pygments. They are optional: what is missing disables its format and warns.
The source code in the page, and a page one can find one's way inThe code is in the page and folded. It costs a click to whoever wants it, and nothing to whoever does not.
The identity of the documented software, and the accessibility of the pagesThe author governs what surrounds the documentation; the structure and the stylesheets are not theirs to change. Identity stops where conformity starts.
The colours of the documented software, and a palette of maximum legibilityThey cannot hold in one stylesheet: one carries an identity, the other renounces it. They become two exclusive themes, the first active by default, the second one switch away. Neither is a degraded version of the other.
A choice made once, and pages that are filesA theme chosen on one page holds for the next ones. Nothing being stored on a server, the choice travels in the address of the page, and the generator declares it rather than suffering it.

Elicitation: the inferred needs

Two things in the output were asked for by nobody. They come from watching what reading documentation actually costs.

The source code, folded. A reader who doubts the prose goes to the source, and going there means leaving the page for the repository. Putting the source in the page answers that; putting it folded answers the reader who does not want it. The details element does both, and asks nothing of the author.

The table of contents, set aside. The same reasoning as the source code, one level up: a panel that is always shown takes a fifth of the width from whoever is reading a long docstring, and a documentation is read far more often than it is navigated. Folding it behind a button gives the width back and costs a click to whoever navigates. It is the author's choice rather than the reader's, because it decides the shape of the pages.

The table of contents, built when the page opens. A generated page knows its own headings, so nothing needs to write them a second time into a navigation panel. It is built in the reader's browser, from the page itself, and it cannot fall out of step with what it lists.

The needs, organized

Each item below is one thing the application does, stated so that it can be verified on its own. They are grouped by domain, and the numbering leaves room inside each group.

[010] Starting a generation

[011] The author can document one class, given the class object.
[012] The author can document one module, given the module object.
[013] The author can document a list of modules in a single run.
[014] The author can run the generator as a Python library or from the command line.
[015] The system returns the documentation as a string when no output path is given, and writes files when one is.

[100] The sources to be documented

[101] The author designates the modules to be documented.
[102] The system documents the classes named in the __all__ of a module.
[103] The system skips what is named there and is not a class.
[104] The system reports a module without __all__, documents it as empty, and goes on.
[105] The system refuses a built-in class, and a class whose source cannot be read.

[200] Reading the code

[201] The system reads the text of the source; it does not call what it documents.
[202] The system extracts the name and the docstring of the class.
[203] The system extracts the constructor: its arguments, its source, its docstring.
[204] The system extracts each function: its name, its arguments, its source, its docstring.
[205] The system keeps __init__ and __new__ out of the list of functions.
[206] The system classifies a function from its name alone: public, private, protected, overload.

[300] Reading the docstrings

[301] The system reads both field styles, :field: and @field:, in any case.
[302] The system normalizes the variants of a field name to one field.
[303] The system recognizes four fields as sections: parameters, returns, raises, example.
[304] The system reads :code: as an example.
[305] The system keeps an unrecognized field as an item of a description list.
[306] The system ignores type and rtype, the source annotations being the truth.
[307] The system opens an example section on a line starting with >>>, even when no field announced it.
[308] The system takes a first line ending with a dot as the short description.
[309] The system requires no field: an absent or empty docstring produces an entry all the same.

[400] The Markdown output

[401] The system produces the Markdown of a class, of a module, and of a set of modules.
[402] The Markdown output depends on nothing beyond the standard library.
[403] The recognized fields become headed sections.
[404] The source code is written in a fenced code block.

[500] The HTML output

[501] The system builds the HTML from the Markdown it produced.
[502] The system colours the source code it shows.
[503] The system folds the source code into a details element.
[504] The system disables the HTML output, with a warning, when a renderer is missing.
[505] The system writes the markup Whakerexa expects, and links its stylesheets and its loader.

[600] The files, and their organization

[601] The system writes one file per documented class.
[602] The system writes an index for each module.
[603] The system writes a general index for the set of modules.
[604] The system creates the output folder when it does not exist.
[605] Each page links to the previous and next class, to the previous and next module, and to the index.
[606] Each page builds its own table of contents from its headings when it is opened.
[607] The author chooses the table of contents to be a panel always shown, or one folded behind a button.

[700] The prose written by hand

[701] The author can have a README inserted into the general index.
[702] The system inserts the README found inside a module into the page of that module.
[703] The author can refuse the insertion of the READMEs.
[704] The system reports a README that cannot be read, and writes the page without it.

[800] The identity of the documented software

[801] The author sets the name, the copyright, the address, the logo, the favicon, the title and the language.
[802] The author sets the paths where the statics and Whakerexa are found.
[803] The author sets the colour mode the pages open in.
[804] The system refuses an option that is not of the expected type, and says which one.
[805] The author provides the theme the pages open with, which carries the colours of the documented software, and names its file.
[806] The system declares no theme when the author names none, and the pages are then what they were before themes existed.

[900] What the run reports

[901] The system reports a module without __all__.
[902] The system reports a class it cannot reach.
[903] The system reports a disabled output format, and what is missing for it.
[904] The system reports a README it could not read.
[905] No report of this kind stops the run.

[1000] Reading the result

[1001] The pages are HTML-5 built for Whakerexa.
[1002] Each page offers the contrast and colour switches.
[1003] Each page opens with a link that skips to its main content.
[1004] Each page carries a stylesheet for printing.
[1005] Each page declares its language.
[1006] The reader can switch to a high-contrast theme: flat palette, maximum ratio, no gradient, no rounded corner.
[1007] A theme chosen on one page is still the one in force on the next.
[1008] Switching a theme leaves the text adjustments untouched, and adjusting the text leaves the theme untouched.

What is not met yet

The items above are what the application has to do; four of them it does not do, and two needs are met only in part. They are listed here rather than left for a reader to discover in the code.

Items not met, and what stands in the way
ItemState
[805], [1006], [1007], [1008]Not implemented. The pages hold their colours in the stylesheet of the documented software, which is linked statically: no theme is declared, none can be switched, and the high-contrast one of Whakerexa is unreachable from the documentation.
[806]Met by default, and it is what makes the rest safe to add: a documentation generated without a theme name is the documentation as it is produced today, link for link.
[102], [103]Met, with a silence. A function named in __all__ is skipped because it is not a class, and no report of [900] covers that case: the author is not told.
[204]Met for the functions a class defines. An inherited method is documented in the page of the class that defines it, so the page of a class does not state everything that can be called on it.

Decisions

The source is read, not run

The structure of a class is taken from its text: inspect.getsource() gives the source, textwrap.dedent() makes it parsable wherever it was written, and ast walks it. Names, arguments, source and docstrings all come from that tree.

The module itself is imported — ClamsPack needs __all__ and importlib to reach the classes it names — but nothing of what is documented is called. What a decorator would return, what a metaclass would build, what a property computes: none of it is asked, so none of it is in the page.

Markdown is the pivot

A docstring is converted once, into Markdown. The HTML is that Markdown given to markdown2, and the source code is given to Pygments. Two formats, one conversion: what is read of a docstring cannot differ between them, because it is read once.

The consequence is accepted: what Markdown cannot express does not reach the HTML either.

What a field is worth

A line opening with : or @ is a candidate field. Its name is normalized — lowercased, then mapped through the table of variants — and four names become sections of the page: param, return, raise and example. A candidate whose name is none of these is not an error: it is written as an item of a description list, which is what a reader would take it for.

A line opening with >>> opens an example section on its own, for the author who wrote the example and forgot to announce it.

The first line, twice

When the first line of a docstring ends with a dot, it is the short description: it is written in italics at the head of the entry, and for a class it also becomes the description of the page, cut to 160 characters — the length a search engine shows.

Four kinds of functions

Functions are grouped by what their name says of them, and the vocabulary is the one of this project:

How a function is classified, from its name alone
NameSection
namePublic functions
_namePrivate functions
__nameProtected functions
__name__Overloads

__init__ and __new__ are not in any of them: the constructor has a section of its own, before the functions.

A missing dependency disables a format

ClamUtils holds the import of markdown2 in a try: on failure it keeps the message and leaves the converter at None. Every conversion asks that value first, warns, and returns an empty string. Markdown output needs nothing but the standard library, so a library without markdown2 is still documented — in one format instead of two.

The classes

What carries the data

Data classes
ClassWhat it holds
ClamInfoOne documented entity: a name, a list of argument names, a source code, a docstring. Nothing is interpreted here.
ClamInfoMarkdownThe same entity seen in Markdown. It owns the conversion rules: fields, variants, types, lists, examples.
ExportOptionsWhat surrounds the documentation: software name, copyright, URL, icon, favicon, title, language, paths to the statics, neighbouring pages for the navigation, and the shape of the table of contents.

What does the work

Working classes
ClassWhat it does
ClammingClassParserTurns one class into three things: the class itself, its constructor, and a dictionary of its functions — all of them ClamInfo.
ClamsClassTurns a parsed class into a page content, in Markdown or in HTML. It is where the four kinds of functions are separated.
ClamsPackOne module: the classes named in its __all__, its README, its index, and the export of one file per class.
ClamsModulesA list of modules: the index that links them, and the export of them all.
ClamUtilsWhat the others need and none of them owns: reaching a class from its name, converting Markdown, colouring source code.

How this was designed

The way, and why it is written here

The models below are those of Merise, kept in the order the method gives them: who exchanges what, what the data are made of, which operations exist, where and when they happen, how the data are laid out, where they live, and where the code goes. A generator has no database, so two of the models say less than they would elsewhere; they are kept all the same, because what they would hold is what a reader looks for when something is to be changed.

MCC: who exchanges what

Actors and flows
FromToWhat
Author of the libraryGeneratorThe source code, with its docstrings, and the list of modules to document
Author of the libraryGeneratorThe export options: identity of the software, paths, shape of the pages
GeneratorFile systemOne index, one page per module, one page per class, in Markdown and in HTML
GeneratorAuthor of the libraryWarnings: a module without __all__, a class that cannot be reached, a disabled format
ReaderPagesReading, navigation from page to page, opening of the folded source code, opening of the table of contents when it is set aside

The data handled, and the values they take

Data handled by the application
DatumValues
moduleAn importable Python module. Documented through the names of its __all__.
nameA string: the name of a class, of a constructor, of a function.
argsA list of strings: the argument names, in the order of the definition.
sourceA string: the source code, as ast writes it back.
docstringA string, or None when the entity has none.
fieldAfter normalization, one of {param, return, raise, example}, or any other name, which is kept as prose.
field variantsreturnsreturn; raises, catch, exceptraise; codeexample. type and rtype are dropped.
kind of function{public, private, protected, overload}, decided by the name alone.
output format{Markdown, HTML}. HTML exists only when its renderers are installed.
short descriptionA string of 160 characters at most: the class name, then the first line of its docstring.
colour mode{light, dark}. Light is the default and carries no class on the HTML root element.
css_themeA string: the name of the theme file of the documented software, in statics. Empty by default, which declares no theme at all.
active theme{the theme of the documented software, the high-contrast one of Whakerexa}. One at a time, held by the href of a single link element, and carried from page to page in the address. Nothing is active when css_theme is empty.
langA BCP 47 tag. en by default.
statics, wexa_staticsPaths, relative to the output folder.
aside_tocA boolean. False by default: the table of contents is a panel always shown.
readmeA boolean. True by default: the READMEs found are inserted.
software, copyright, url, icon, favicon, titleStrings, empty by default but for the favicon.

MCD: what the data are made of

Entities and associations
EntityAttributesAssociation
Documented entity (ClamInfo)name, args, source, docstring
Classits own entity, one constructor, n functions1 class → 1 constructor, 1 class → 0..n functions
Modulename, README1 module → 1..n classes
Set of modules1 set → 1..n modules
Export optionsidentity, paths, neighbours, shape of the ToC1 export → the whole set

A documented entity is the only thing the generator stores. Everything above it — class, module, set — is a way of grouping those entities and of deciding which file they are written to.

MCT: the operations

The operations, and what triggers them
No.OperationTriggerResult
O1Parse a classA class object is givenThree ClamInfo groups: the class, its constructor, its functions
O2Convert a docstringA ClamInfo is asked for its MarkdownMarkdown: short description, sections of parameters, returns, raises, examples
O3Write a class pageA parsed class is asked for its contentMarkdown, or HTML with the source code folded in a details element
O4Write a moduleA module and its export options are givenAn index of the module, and one file per class
O5Write a set of modulesA list of modules and the export options are givenThe general index, then O4 for each module
O6Switch the themeThe reader asks for it, on any pageThe active stylesheet is replaced, and the choice is carried to the pages reached from there
O7Open or close the table of contentsThe reader asks for it, when the author set it asideThe panel is shown or hidden, and it is out of reach — for the keyboard as for a screen reader — as long as it is hidden

MOT: where, when, and by whom

Organization of the operations
OperationWhenWhereBy whom
O1 to O5Before a release, when the docstrings have changedOn the author's machineThe author, through makedoc.py or main.py
ReadingAny time afterwardsIn a browser, from a file or a serverThe reader
Building of the table of contentsWhen a page is openedIn the reader's browserThe Book class of Whakerexa
O6, switching the themeWhen the reader asks for itIn the reader's browserThe ThemeManager class of Whakerexa
O7, opening the table of contentsWhen the reader asks for it, and only if the author set it asideIn the reader's browserThe Book class of Whakerexa
Adjusting the textWhen the reader asks for itIn the reader's browserThe accessibility manager of Whakerexa

The pages are written once and served as files: what happens at the reader's request happens in their browser and changes nothing on disk. Two of these three operations are theirs to ask for, and the third — the table of contents — is asked for by nobody.

MLD: the structures

There is no database. What the tables of a logical model would hold is held, for the time of one run, by these structures:

Structures, and what plays the part of a key
StructureContentKey
ClamInfoname, args, source, docstringThe name, within its class
fct_clamsA dictionary of ClamInfoThe function name
The four lists of ClamsClassPublic, private, protected, overloadsOrder of appearance in the source
ClamsPackA list of ClamsClassThe class name, which is also the file name

MPD: where each thing exists

Physical model
ThingWhere it existsHow long
The parsed entitiesIn memoryThe time of one run
The pages<out>/<Class>.html, <out>/<module>.html, <out>/index.htmlUntil the next run overwrites them
The Markdown<out>/<module>.mdUntil the next run overwrites them
The statics of the author<out>/statics/Written by hand, never by the generator
The themesThe one of the documented software in <out>/statics/, the high-contrast one in the stylesheets of WhakerexaWritten by hand, never by the generator
Whakerexa<out>/Whakerexa-current/wexa_statics/Unpacked by hand, never by the generator

MOpT: where the code goes

One responsibility, one module
FileResponsibility
claminfo.pyHold what was found, and nothing else
claminfomd.pyKnow the docstring conventions, and them alone
classparser.pyRead a class from its source
clamsclass.pyWrite one class, in two formats
clamspack.pyWrite one module and its files
clamsmodules.pyWrite a set of modules and its index
exportoptions.pyHold what surrounds the documentation, and produce the head, header, nav and footer
clamutils.pyReach a class, convert Markdown, colour source code

UML: the sequence of one export

What happens when ClamsModules.html_export_packages() is called:

ClamsModules            ClamsPack             ClamsClass          ClammingClassParser
     |                       |                     |                       |
     |-- html_export_index ->|                     |                       |
     |   (index.html)        |                     |                       |
     |                       |                     |                       |
     |-- html_export_clams ->|                     |                       |
     |                       |-- for each class -->|                       |
     |                       |                     |-- parse ------------->|
     |                       |                     |<-- ClamInfo x n ------|
     |                       |                     |                       |
     |                       |                     |-- markdown / html --> (ClamInfoMarkdown)
     |                       |<-- content ---------|                       |
     |                       |                     |                       |
     |                       |-- ExportOptions: head, header, nav, footer   |
     |                       |-- write <Class>.html                         |
     |<-- list of files -----|                     |                       |

The export ends there. Nothing of this library runs again afterwards: the objects that act on a page being read belong to Whakerexa.

UML: the classes

Attributes are private throughout — the language mangles them, and what is readable is read through an operation. Everything below is what the code declares.

+--------------------------------+          +-------------------------------+
|          ClamInfo              |          |      ClamInfoMarkdown         |
+--------------------------------+          +-------------------------------+
| - name: str                    |<>--1..1--| - clam: ClamInfo              |
| - args: list                   |          +-------------------------------+
| - source: str                  |          | + name(): str                 |
| - docstring: str               |          | + args(): list                |
+--------------------------------+          | + source(): str               |
| + get_name() / set_name()      |          | + docstring(): str            |
| + get_args() / set_args()      |          | + convert_name(): str         |
| + get_source() / set_source()  |          | + convert_source(): str       |
| + get_docstring()              |          | + convert_docstring(): str    |
| + set_docstring()              |          +-------------------------------+
+--------------------------------+

+--------------------------------+          +-------------------------------+
|     ClammingClassParser        |          |          ClamsClass           |
+--------------------------------+          +-------------------------------+
| - obj: type                    |--1..1---<| - utils: ClamUtils            |
| - obj_src: str                 |          | - info_class_name: str        |
| - obj_clams: ClamInfo          |          | - info_constructor: ClamInfo  |
| - init_clams: ClamInfo         |          | - info_public_fcts: list      |
| - fct_clams: dict              |          | - info_private_fcts: list     |
+--------------------------------+          | - info_protected_fcts: list   |
| + get_obj_clams(): ClamInfo    |          | - info_overloads: list        |
| + get_init_clams(): ClamInfo   |          +-------------------------------+
| + get_fct_clams(): dict        |          | + get_name(): str             |
+--------------------------------+          | + get_short_description(): str|
                                            | + markdown(): str             |
                                            | + html(): str                 |
                                            +-------------------------------+

+--------------------------------+          +-------------------------------+
|          ClamsPack             |          |         ClamsModules          |
+--------------------------------+          +-------------------------------+
| - name: str                    |>--1..*---| - clams_packs: list           |
| - pack: module                 |          +-------------------------------+
| - clams: list<ClamsClass>      |          | + markdown_export_packages()  |
+--------------------------------+          | + html_export_index(): str    |
| + get_name(): str              |          | + html_export_packages(): list|
| + get_readme(): str            |          +-------------------------------+
| + markdown(): str              |
| + html(): str                  |          +-------------------------------+
| + html_index(): str            |          |          ClamUtils            |
| + html_export_clams(): list    |          +-------------------------------+
+--------------------------------+          | + markdowner: Markdown        |
              |                             | + lexer: PythonLexer          |
              | uses 1..1                   | + formatter: HtmlFormatter    |
              v                             +-------------------------------+
+--------------------------------+          | + get_class(): Any            |
|        ExportOptions           |          | + markdown_convert(): str     |
+--------------------------------+          | + markdown_to_html(): str     |
| - software, copyright, url     |          | + source_to_html(): str       |
| - icon, favicon, title, lang   |          +-------------------------------+
| - theme, statics, wexa_statics |
| - readme: bool                 |
| - aside_toc: bool              |
| - next/prev class and module   |
+--------------------------------+
| + get_root_class(): str        |
| + get_head(): str              |
| + get_header(): str            |
| + get_nav(): str               |
| + get_footer(): str            |
+--------------------------------+

Two aggregations carry the whole structure: a set of modules holds its modules, a module holds its classes. The parser and the options are used, not held: a ClamsClass is built from a parser it does not keep alive, and the same ExportOptions instance travels through every export of a run, its neighbouring-page fields being rewritten between two files.

UML: the sequence when a page is read

Same objects whichever way they were loaded — modules over HTTP, bundle over file://.

Reader        Browser         Book       ThemeManager   AccessibilityManager
  |              |              |              |              |
  |-- open ----->|              |              |              |
  |              |-- create --->|              |              |
  |              |              |-- read the headings         |
  |              |              |-- build the table of contents
  |              |              |  [ if the table is set aside ]
  |              |              |-- make the panel out of reach
  |              |              |-- add the button that opens it
  |              |              |              |              |
  |              |-- create ------------------>|              |
  |              |              |     register clamming       |
  |              |              |     register highcontrast   |
  |              |              |     read the address        |
  |              |              |     activate a theme        |
  |              |              |              |              |
  |              |-- create ----------------------------------|
  |              |              |              |     read the address
  |              |              |              |     apply the adjustments
  |              |              |              |              |
  |-- switch the theme ------------------------>|             |
  |              |<-- replace the stylesheet ---|              |
  |              |                                            |
  |-- adjust the text ------------------------------------->  |
  |              |<-- set the class on the root element -------|
  |                                                            |
  |-- open the table of contents -->|            |             |
  |              |<-- show the panel, and put it in reach -----|

UML: the states of the presentation

The presentation of a page is one composite state with two orthogonal regions: one holds the palette, the other the text. A transition in one region leaves the other where it was, which is what makes the four combinations reachable.

+-- Presentation ------------------------------------------------+
|                                                                |
|  region: palette   (exists only if a theme is declared)        |
|    (o)--> [ of the software ] -- switch --> [ high contrast ]   |
|                ^                                    |          |
|                +------------- switch ---------------+          |
|                                                                |
| ---------------------------------------------------------------|
|                                                                |
|  region: text                                                  |
|    (o)--> [ as written ] --- switch contrast ---> [ adjusted ]  |
|                ^                                       |       |
|                +--------- switch contrast -------------+       |
|                                                                |
| ---------------------------------------------------------------|
|                                                                |
|  region: table of contents  (set aside by the author)          |
|    (o)--> [ closed ] ------- press the button ---> [ open ]     |
|                ^                                       |       |
|                +--------- press the button ------------+       |
|                                                                |
|    a table of contents that is not set aside has no region:    |
|    it is shown, and there is nothing to reach                  |
|                                                                |
+----------------------------------------------------------------+

Neither state is a degraded form of another. The third region is the one that does not survive a page: a table of contents opens closed on every page, since nothing carries its state and nothing should — a panel left open on the previous page would hide the beginning of the next one. The two others keep their state across pages the same way — in the address, under wexa_theme for the palette, wexa_contrast and wexa_color for the text and the colour mode — because pages that are files have nowhere else to keep them.

Rules that hold everywhere

The error policy

Three things can go wrong, and each kind has one answer. The line between them is the one this whole file is built on: what the author of the generator got wrong stops; what the author of the documented library did not write does not.

What is raised, what is reported, what is passed over
KindAnswerWhy the line is there
A call that cannot mean anything — an option given a value of the wrong type, an object that is not a classRaises. TypeError, at the moment of the call.It is a programming error in whoever drives the generator. Continuing would document something nobody asked for.
Something expected of the documented library and not found — a module without __all__, a class that cannot be reached, a README that cannot be readReported in the log. The run goes on, and produces the rest.A library is documented as it stands. A hole in one module is not a reason to publish nothing.
Something the author simply did not write — no docstring, no field, no constructorNothing. Neither a raise nor a report.It is not an anomaly. It is the normal state of a library halfway through, and reporting it would be the generator judging the source.
A missing renderer — markdown2, PygmentsReported once, then the format returns empty content. Markdown is unaffected.An optional dependency is optional. What can still be produced is produced.

Nothing is caught and hidden: the two exceptions the library raises are its own, and everything it swallows is written to the log before being swallowed.

Invariants

What holds at every point of a run, whatever was given to it:

Invariants, and where they come from
InvariantWhat upholds it
A documented entity is never half filled. Its four fields exist from its creation; the absent ones are an empty string, an empty list, or None — never a missing attribute.The constructor of the data class, which takes all four and defaults the rest.
A refused option leaves the object as it was. Nothing is half assigned.Each setter checks the type before assigning, and raises before touching anything.
What is documented is never executed. No decorator runs, no property is computed, no metaclass builds.The structure is read from the syntax tree of the source, never from the live object.
One function appears in one section, and in one only.The four categories are decided from the name alone, and their conditions are mutually exclusive.
Markdown and HTML never say two different things about one docstring.There is one conversion. The HTML is built from its result, not from a second reading.
Nothing written to a docstring is lost. What is not recognized is carried through as prose.The conversion has no discarding branch: an unknown field becomes a description list item.
An entity that is documented twice is documented identically.Nothing in the conversion depends on the order, on what was converted before, or on the state of the object doing it.

What a run guarantees

A run writes files one after another, and nothing undoes what it wrote.

Guarantees of an export
GuaranteeWhat it means for the author
What was written before an error stays written.An interrupted run leaves a partial documentation, not an empty folder. The files it produced are complete files.
A run overwrites; it never merges.A class removed from a library leaves its page behind. Removing what a run no longer produces is the author's, not the generator's.
The output folder is created if it is missing.Naming a folder is enough; preparing it is not asked for.
Nothing is written when no path is given.The same objects produce a string for whoever wants to place it themselves.
The statics and the framework are never written by the generator.What the author put in the output folder stays as it is, run after run.

What is tested, and where

Where the tests are

Test files
FileWhat it covers
test_claminfo.pyThe data class, and the conversion rules of ClamInfoMarkdown: fields, variants, types, lists, examples.
test_classparser.pyReading a class: its docstring, its constructor, its functions.
test_clamspack.pyOne module: its index, its exported files, the sections without numbering, and the two formats of ClamsClass.
test_clamsmodules.pyA set of modules: the general index, with and without a README.
test_exportoptions.pyThe options, the head, and the two shapes of the table of contents.
test_clamutils.pyReaching a class from its name, and the conversions.

What no test covers

Nothing verifies what a browser does with the pages: the table of contents built at load time, the folded source code, the navigation from page to page, the contrast and colour switches. Nothing verifies the accessibility of the result.

Nothing covers the documentation of functions written outside a class, because nothing implements it.

ClamsClass and ClamInfoMarkdown have no test file of their own: they are covered from the tests of the classes that use them, which leaves their own errors — a docstring at None, a class without a constructor — verified only where another class happens to produce them.