PAX Manual
Table of Contents
- 1 Introduction
- 2 Emacs Setup
- 3 Links and Systems
- 4 Background
- 5 Basics
- 6 PAX Locatives
- 7 Navigating Sources in Emacs
- 8 Generating Documentation
- 8.1 The
documentFunction - 8.2 The mgl-pax/document ASDF System
- 8.3 Browsing Live Documentation
- 8.4 Markdown Support
- 8.5 Codification
- 8.6 Linking to Code
- 8.7 Linking to the Hyperspec
- 8.8 Linking to Sections
- 8.9 Miscellaneous Variables
- 8.10 Utilities for Generating Documentation
- 8.11 Overview of Escaping
- 8.12 Output Details
- 8.13 Documentation Generation Implementation Notes
- 8.1 The
- 9 Transcripts
- 10 Writing Extensions
[in package MGL-PAX with nicknames PAX]
1 Introduction
What if documentation really lived in the code?
Docstrings are already there. If some narrative glued them together,
we'd be able develop and explore the code along with the
documentation due to their physical proximity. The main tool that
PAX provides for this is defsection:
(defsection @foo-random-manual (:title "Foo Random manual")
"Foo Random is a random number generator library."
(foo-random-state class)
(uniform-random function)
(@foo-random-examples section))
Like this one, sections can have docstrings and
references to
definitions (e.g. (uniform-random function)). These docstrings and
references are the glue. To support interactive development, PAX
See Emacs Setup.
Beyond interactive workflows, Generating Documentation from sections and all the referenced items in Markdown or HTML format is also implemented.
With the simplistic tools provided, one may emphasize the narrative as with Literate Programming, but documentation is generated from code, not vice versa, and there is no support for chunking.
Code is first, code must look pretty, documentation is code.
Docstrings
PAX automatically recognizes and marks up code with
backticks and links code to their definitions.
Take, for instance, SBCL's abort function, whose docstring is
written in the usual style, uppercasing names of symbols:
(docstring #'abort)
=> "Transfer control to a restart named ABORT, signalling a CONTROL-ERROR if
none exists."
Note how in the generated documentation, abort is set with a
monospace font, while control-error is autolinked:
[function] ABORT &OPTIONAL CONDITION
Transfer control to a restart named
abort, signalling acontrol-errorif none exists.
In the following transcript, the above output is rendered from the raw markdown:
(document #'abort :format :markdown)
.. - [function] **ABORT** *&OPTIONAL CONDITION*
..
.. Transfer control to a restart named `ABORT`, signalling a [`CONTROL-ERROR`][7c2c] if
.. none exists.
..
.. [7c2c]: http://www.lispworks.com/documentation/HyperSpec/Body/e_contro.htm "CONTROL-ERROR (MGL-PAX:CLHS CONDITION)"
..
A Complete Example
Here is an example of how it all works together:
(mgl-pax:define-package :foo-random
(:documentation "This package provides various utilities for random.
See FOO-RANDOM:@FOO-RANDOM-MANUAL.")
(:use #:common-lisp #:mgl-pax))
(in-package :foo-random)
(defsection @foo-random-manual (:title "Foo Random manual")
"FOO-RANDOM is a random number generator library inspired by CL:RANDOM.
Functions such as UNIFORM-RANDOM use *FOO-STATE* and have a
:RANDOM-STATE keyword arg."
(foo-random-state class)
(state (reader foo-random-state))
"Hey we can also print states!"
(print-object (method () (foo-random-state t)))
(*foo-state* variable)
(gaussian-random function)
(uniform-random function)
;; This is a subsection.
(@foo-random-examples section))
(defclass foo-random-state ()
((state :reader state)))
(defmethod print-object ((object foo-random-state) stream)
(print-unreadable-object (object stream :type t)))
(defvar *foo-state* (make-instance 'foo-random-state)
"Much like *RANDOM-STATE* but uses the FOO algorithm.")
(defun uniform-random (limit &key (random-state *foo-state*))
"Return a random number from the between 0 and LIMIT (exclusive)
uniform distribution."
nil)
(defun gaussian-random (stddev &key (random-state *foo-state*))
"Return a random number from a zero mean normal distribution with
STDDEV."
nil)
(defsection @foo-random-examples (:title "Examples")
"Let's see the transcript of a real session of someone working
with FOO:
```cl-transcript
(values (princ :hello) (list 1 2))
.. HELLO
=> :HELLO
=> (1 2)
(make-instance 'foo-random-state)
==> #<FOO-RANDOM-STATE >
```")
Note how (variable *foo-state*) in the defsection form both
exports *foo-state* and includes its documentation in
@foo-random-manual. The symbols variable and
function are just two instances of locatives,
which are used in defsection to refer to definitions tied to
symbols.
(document @foo-random-manual) generates fancy markdown or HTML
output with automatic markup and autolinks uppercase words
found in docstrings, numbers sections, and creates a table of
contents.
One can even generate documentation for different but related libraries at the same time with the output going to different files but with cross-page links being automatically added for symbols mentioned in docstrings. In fact, this is what PAX World does. See Generating Documentation for some convenience functions to cover the most common cases.
The transcript in the code block tagged with
cl-transcript is automatically checked for up-to-dateness when
documentation is generated.
2 Emacs Setup
Load src/mgl-pax.el in Emacs, and maybe set up some key bindings.
If you installed PAX with Quicklisp, the location of mgl-pax.el
may change with updates, and you may want to copy the current
version of mgl-pax.el to a stable location:
(mgl-pax:install-pax-elisp "~/quicklisp/")
Then, assuming the Elisp file is in the quicklisp directory, add
something like this to your .emacs:
(load "~/quicklisp/mgl-pax.el")
(mgl-pax-hijack-slime-doc-keys)
(global-set-key (kbd "C-.") 'mgl-pax-document)
(global-set-key (kbd "s-x t") 'mgl-pax-transcribe-last-expression)
(global-set-key (kbd "s-x r") 'mgl-pax-retranscribe-region)
For Browsing Live Documentation, mgl-pax-browser-function can be
customized in Elisp. To browse within Emacs, choose
w3m-browse-url (see
w3m), and make
sure both the w3m binary and the w3m Emacs package are installed. On
Debian, simply install the w3m-el package. With other browser
functions, a HUNCHENTOOT web server is started.
See Navigating Sources in Emacs, Generating Documentation and Transcribing with Emacs for how to use the relevant features.
[function] install-pax-elisp target-dir
Copy
mgl-pax.eldistributed with this package totarget-dir.
3 Links and Systems
Here is the official repository and the HTML documentation for the latest version.
PAX is built on top of the DRef library (bundled in the same repository). See DRef's HTML documentation
3.1 The mgl-pax ASDF System
- Version: 0.3.0
- Description: Documentation system, browser, generator.
- Long Description: The set of dependencies of the MGL-PAX system is
kept light, and its heavier dependencies are autoloaded via ASDF
when the relevant functionality is accessed. See the
mgl-pax/navigate,mgl-pax/document,mgl-pax/transcribeandmgl-pax/fullsystems. To keep deployed code small, client systems should declare an ASDF dependency on this system, never on the others, which are intended for autoloading and interactive use. - Licence: MIT, see COPYING.
- Author: Gábor Melis
- Mailto: mega@retes.hu
- Homepage: http://melisgl.github.io/mgl-pax
- Bug tracker: https://github.com/melisgl/mgl-pax/issues
- Source control: GIT
3.2 The mgl-pax/full ASDF System
- Description: MGL-PAX with all features preloaded except MGL-PAX/WEB.
- Long Description: Do not declare a dependency on this system. It is autoloaded.
- Licence: MIT, see COPYING.
- Author: Gábor Melis
- Mailto: mega@retes.hu
4 Background
As a user, I frequently run into documentation that's incomplete
and out of date, so I tend to stay in the editor and explore the
code by jumping around with SLIME's m-. (slime-edit-definition).
As a library author, I spend a great deal of time polishing code but
precious little writing documentation.
In fact, I rarely write anything more comprehensive than docstrings for exported stuff. Writing docstrings feels easier than writing a separate user manual, and they are always close at hand during development. The drawback of this style is that users of the library have to piece the big picture together themselves.
That's easy to solve, I thought, let's just put all the narrative that holds docstrings together in the code and be a bit like a Literate Programmer turned inside out. The original prototype, which did almost everything I wanted, was this:
(defmacro defsection (name docstring)
`(defun ,name () ,docstring))
Armed with this defsection, I soon found myself
organizing code following the flow of user-level documentation and
relegated comments to implementation details entirely. However, some
parts of defsection docstrings were just listings of
all the functions, macros and variables related to the narrative,
and this list was repeated in the defpackage form complete with
little comments that were like section names. A clear violation of
OAOO, one of them had to go, so defsection got a list
of symbols to export.
That was great, but soon I found that the listing of symbols is
ambiguous if, for example, a function, a compiler macro and a class
were named by the same symbol. This did not concern exporting, of
course, but it didn't help readability. Distractingly, on such
symbols, M-. was popping up selection dialogs. There were two
birds to kill, and the symbol got accompanied by a type, which was
later generalized into the concept of locatives:
(defsection @introduction ()
"A single line for one man ..."
(foo class)
(bar function))
After a bit of elisp hacking, M-. was smart enough to
disambiguate based on the locative found in the vicinity of the
symbol, and everything was good for a while.
Then, I realized that sections could refer to other sections if
there were a section locative. Going down that path, I soon began to
feel the urge to generate pretty documentation as all the necessary
information was available in the defsection forms. The design
constraint imposed on documentation generation was that following
the typical style of upcasing symbols in docstrings, there should be
no need to explicitly mark up links: if M-. works, then the
documentation generator shall also be able figure out what's being
referred to.
I settled on markdown as a reasonably non-intrusive format, and a
few thousand lines later PAX was born. Since then, locatives and
references were factored out into the DRef
library to let PAX focus on M-. and documentation.
5 Basics
Now let's examine the most important pieces.
[macro] defsection name (&key (package '*package*) (readtable '*readtable*) (export t) title link-title-to (discard-documentation-p *discard-documentation-p*)) &body entries
Define a documentation section and maybe export referenced symbols. A bit behind the scenes, a global variable with
nameis defined and is bound to asectionobject. By convention, section names start with the character@. See Introduction for an example.Entries
entriesconsists of docstrings and references in any order. Docstrings are arbitrary strings in markdown format.References are
xrefs given in the form(name locative). For example,(foo function)refers to the functionfoo,(@bar section)says that@baris a subsection of this one.(baz (method () (t t t)))refers to the default method of the three argument generic functionbaz.(foo function)is equivalent to(foo (function)). See the DRef Introduction for more.The same name may occur in multiple references, typically with different locatives, but this is not required.
The references are not
located until documentation is generated, so they may refer to things yet to be defined.Exporting
If
exportis true (the default),nameand the names of references amongentrieswhich aresymbols are candidates for exporting. A candidate symbol is exported ifit is accessible in
package, andthere is a reference to it in the section being defined which is approved by
exportable-reference-p.
See
define-packageif you use the export feature. The idea with confounding documentation and exporting is to force documentation of all exported symbols.Misc
titleis a string containing markdown ornil. If non-nil, it determines the text of the heading in the generated output.link-title-tois a reference given as an(name locative)pair ornil, to which the heading will link when generating HTML. If not specified, the heading will link to its own anchor.When
discard-documentation-p(defaults to*discard-documentation-p*) is true,entrieswill not be recorded to save memory.
[variable] *discard-documentation-p* nil
The default value of
defsection'sdiscard-documentation-pargument. One may want to set*discard-documentation-p*to true before building a binary application.
[macro] define-package package &rest options
This is like
cl:defpackagebut silences warnings and errors signalled when the redefined package is at variance with the current state of the package. Typically this situation occurs when symbols are exported by callingexport(as is the case withdefsection) as opposed to adding:exportforms to thedefpackageform and the package definition is subsequently reevaluated. See the section on package variance in the SBCL manual.The bottom line is that if you rely on
defsectionto do the exporting, then you'd better usedefine-package.
[macro] define-glossary-term name (&key title url (discard-documentation-p *discard-documentation-p*)) &optional docstring
Define a global variable with
name, and set it to aglossary-termobject.title,urlanddocstringare markdown strings ornil. Glossary terms aredocumented in the lightweight bullet + locative + name/title style. See the glossary entry name for an example.When a glossary term is linked to in documentation, its
titlewill be the link text instead of the name of the symbol (as withsections).Glossary entries with a non-
nilurlare like external links: they are linked to theirurlin the generated documentation. These offer a more reliable alternative to using markdown reference links and are usually not included insections.When
discard-documentation-p(defaults to*discard-documentation-p*) is true,docstringwill not be recorded to save memory.
5.1 Parsing
When Navigating Sources in Emacs or Generating Documentation, references are parsed from the buffer content or docstrings, respectively. In either case, names are extracted from words and then turned into DRef names to form DRef references maybe with locatives found next to the word.
-
A word is a string from which we want to extract a name. When Navigating, the word is
slime-symbol-at-point. When Generating Documentation, it is a non-empty string between whitespace characters in a docstring.
-
A name is a string that denotes a possible DRef name (e.g. an
internedsymbol, the name of apackageor anasdf:system). Names are constructed from words by trimming some prefixes and suffixes. For a given word, multiple candidate names are considered in the following order.The entire word.
Trimming the characters `#<{;"'`` from the left of the word.
Trimming the characters `,;:.>}"'`` from the right of the word.
Trimming both of the previous two at the same time.
From the result of 4., further removing some plural markers.
From the result of 4., further removing non-uppercase prefixes and suffixes.
For example, when
m-.is pressed while point is overnonREADable., the last word of the sentenceIt may be nonREADable., the following names are considered until one is found with a definition:The entire word,
"nonREADable.".Trimming left does not produce a new word.
Trimming right removes the dot and gives
"nonREADable".Trimming both is the same as trimming right.
No plural markers are found.
The lowercase prefix and suffix is removed around the uppercase core, giving
"READ". This has a definition, which `M-.' will visit.
The exact rules for steps 5. and 6. are the following.
If a word ends with what looks like a plural marker (case-insensitive), then a name is created by removing it. For example, from the word
busesthe plural markeresis removed to produce the namebus. The list of plural markers considered iss(e.g.cars),es(e.g.buses),ses(e.g.gasses),zes(e.g.fezzes), andren(e.g.children).From a codifiable word, a name is created by removing the prefix before the first and the suffix after the last uppercase character if they contain at least one lowercase character.
6 PAX Locatives
To the Locative Types defined by DRef, PAX adds a few of its own.
-
Refers to a
sectiondefined bydefsection.sectionisexportable-locative-type-pbut not exported by default (seeexportable-reference-p).
-
Refers to a
glossary-termdefined bydefine-glossary-term.glossary-termisexportable-locative-type-pbut not exported by default (seeexportable-reference-p).
-
Refers to a symbol in a non-specific context. Useful for preventing autolinking. For example, if there is a function called
foothen`FOO`will be linked (if
*document-link-code*) to its definition. However,[`FOO`][dislocated]will not be. With a dislocated locative,
locatealways fails with alocate-errorcondition. Also see Preventing Autolinking.dislocatedreferences do notresolve.
-
An alias for
dislocated, so that one can refer to an argument of a macro without accidentally linking to a class that has the same name as that argument. In the following example,formatmay link tocl:format(if we generated documentation for it):"See FORMAT in DOCUMENT."Since
argumentis a locative, we can prevent that linking by writing:"See the FORMAT argument of DOCUMENT."argumentreferences do notresolve.
[locative] include source &key line-prefix header footer header-nl footer-nl
This pseudolocative refers to a region of a file.
sourcecan be astringor apathname, in which case the whole file is being pointed to, or it can explicitly supplystart,endlocatives.includeis typically used to include non-lisp files in the documentation (say markdown or Elisp as in the next example) or regions of Lisp source files. This can reduce clutter and duplication.(defsection @example-section () (mgl-pax.el (include #.(asdf:system-relative-pathname :mgl-pax "src/mgl-pax.el") :header-nl "```elisp" :footer-nl "```")) (foo-example (include (:start (dref-ext:make-source-location function) :end (dref-ext:source-location-p function)) :header-nl "```" :footer-nl "```")))In the above example, when documentation is generated, the entire
src/mgl-pax.elfile is included in the markdown output surrounded by the strings given asheader-nlandfooter-nl. The documentation offoo-examplewill be the region of the file from thesource-locationof thestartreference (inclusive) to thesource-locationof theendreference (exclusive). If only one ofstartandendis specified, then they default to the beginning and end of the file, respectively.Since
startandendare literal references, pressingM-.onpax.elwill open thesrc/mgl-pax.elfile and put the cursor on its first character.M-.onfoo-examplewill go to the source location of thefoofunction.With the
lambdalocative, one can specify positions in arbitrary files as in, for example, Emacs Setup for Browsing.sourceis either an absolute pathname designator or a list matching the destructuring lambda list(&key start end), wherestartandendmust benilor(<name> <locative>)lists (not evaluated) like adefsectionentry. Theirsource-locations constitute the bounds of the region of the file to be included. Note that the file of the source location ofstartandendmust be the same. Ifsourceis a pathname designator, then it must be absolute so that the locative is context independent.If specified,
line-prefixis a string that's prepended to each line included in the documentation. For example, a string of four spaces makes markdown think it's a code block.headerandfooter, if non-nil, are printed before the included string.header-nlandfooter-nl, if non-nil, are printed between twofresh-linecalls.
includeis notexportable-locative-type-p, andincludereferences do notresolve.
-
docstringis a pseudolocative for including the parse tree of the markdowndocstringof a definition in the parse tree of a docstring when generating documentation. It has no source location information and only works as an explicit link. This construct is intended to allow docstrings to live closer to their implementation, which typically involves a non-exported definition.(defun div2 (x) "X must be [even* type][docstring]." (/ x 2)) (deftype even* () "an even integer" '(satisfies evenp)) (document #'div2) .. - [function] DIV2 X .. .. X must be an even integer. ..There is no way to
locatedocstrings, so nothing toresolveeither.
[locative] go (name locative)
Redirect to a definition in the context of the reference designated by
nameandlocative. This pseudolocative is intended for things that have no explicit global definition.As an example, consider this part of a hypothetical documentation of CLOS:
(defsection @clos () (defmethod macro) (call-next-method (go (defmethod macro))))The
goreference exports the symbolcall-next-methodand also produces a terse redirection message in the documentation.gobehaves as described below.A
goreferenceresolves to whatnamewithlocativeresolves to:(resolve (dref 'xxx '(go (print function)))) ==> #<FUNCTION PRINT>The
docstringof agoreference isnil.source-location(thusM-.) returns the source location of the embedded reference:(equal (source-location (dref 'xxx '(go (print function)))) (source-location (dref 'print 'function))) => T
[locative] clhs &optional nested-locative
Refers to sections or definitions in the Common Lisp Hyperspec. These have no source location so
M-.will not work. What works is linking in documentation, including Browsing Live Documentation. The generated links are relative to*document-hyperspec-root*and work even if*document-link-to-hyperspec*isnil.definitions: These are typically unnecessary as
documentwill produce the same link for e.g.PPRINT,[PPRINT][function], or[pprint][]if*document-link-to-hyperspec*is non-niland thepprintfunction in the running Lisp is not beingdocumented. When Browsing Live Documentation, a slight difference is that everything is beingdocumented, so using theclhslink bypasses the page with the definition in the running Lisp.glossary terms (case-insensitive):
[lambda list][(clhs glossary-term)](lambda list)
issues:
[ISSUE:AREF-1D][clhs](ISSUE:AREF-1D)[ISSUE:AREF-1D][(clhs section)](ISSUE:AREF-1D)
issue summaries: These render as (SUMMARY:CHARACTER-PROPOSAL:2-6-5):
[SUMMARY:CHARACTER-PROPOSAL:2-6-5][clhs][SUMMARY:CHARACTER-PROPOSAL:2-6-5][(clhs section)]
Since these summary ids are not particularly reader friendly, the alternative form of the Specified Locative may be used:
[see this][SUMMARY:CHARACTER-PROPOSAL:2-6-5 (clhs section)](see this)
sections:
by section number:
[3.4][clhs]or[3.4][(clhs section)](3.4)by section title (case-insensitive, substring match):
[lambda lists][clhs]or[lambda lists][(clhs section)](lambda lists)by filename:
[03_d][clhs]or[03_d][(clhs section)](03_d)
As the above examples show, the
nested-locativeargument of theclhslocative may be omitted. In that case, definitions, glossary terms, issues, issue summaries, and sections are considered in that order. Sections are considered last because a substring of a section title can be matched by chance easily.All examples so far used explicit links. Autolinking also works if the name is marked up as code or is codified (e.g. in
COS clhs(cosclhs).As mentioned above,
m-.does not do anything overclhsreferences. Slightly more usefully, the live documentation browser understandsclhslinks so one can enter inputs like3.4 clhs,"lambda list" clhsorerror (clhs function).clhsreferences do notresolve.
7 Navigating Sources in Emacs
Integration into SLIME's m-. (slime-edit-definition) allows
one to visit the source-location of a definition(0 1).
The definition is either determined from the buffer content at point
or is prompted. If prompted, then the format is <name> <locative>,
where the locative may be omitted to recover stock Slime behaviour.
When determining the definition from the buffer contents,
slime-symbol-at-point is parsed as a word, then candidate
locatives are looked for before and after that word. Thus, if a
locative is the previous or the next expression around the symbol of
interest, then M-. will go straight to the definition which
corresponds to the locative. If that fails, M-. will try to find
the definitions in the normal way, which may involve popping up an
xref buffer and letting the user interactively select one of
possible definitions.
In the following examples, when the cursor is on one of the
characters of foo or just after foo, pressing M-. will visit
the definition of function foo:
function foo
foo function
(function foo)
(foo function)
In particular, references in a defsection form are in (name
locative) format so M-. will work just fine there.
Just like vanilla M-., this works in comments and docstrings. In
the next example, pressing M-. on foo will visit foo's
default method:
;; See RESOLVE* (method () (dref)) for how this all works.
With a prefix argument (C-u M-.), one can enter a symbol plus a
locative separated by whitespace to preselect one of the
possibilities.
The M-. extensions can be enabled by loading src/mgl-pax.el.
See Emacs Setup. In addition, the Elisp command
mgl-pax-edit-parent-section visits the source location of the
section containing the definition with point in it. See
Browsing Live Documentation.
7.1 The mgl-pax/navigate ASDF System
- Description: Slime
M-.support for MGL-PAX. - Long Description: Autoloaded by Slime's
M-.whensrc/pax.elis loaded. See Navigating Sources in Emacs. - Licence: MIT, see COPYING.
- Author: Gábor Melis
- Mailto: mega@retes.hu
8 Generating Documentation
8.1 The document Function
[function] document documentable &key (stream t) pages (format :plain)
Write
documentableinformattostreamdiverting some output topages.formatis a 3BMD output format (currently one of:markdown,:htmland:plain).streammay be astreamobject,tornilas withcl:format.To look up the documentation of the
documentfunction itself:(document #'document)The same with fancy markup:
(document #'document :format :markdown)To document a
section:(document @pax-manual)To generate the documentation for separate libraries with automatic cross-links:
(document (list pax::@pax-manual dref::@dref-manual) :format :markdown)See Utilities for Generating Documentation for more.
Definitions that do not define a first-class object are supported via DRef:
(document (dref:locate 'foo 'type))There are quite a few special variables that affect how output is generated, see Codification, Linking to Code, Linking to Sections, and Miscellaneous Variables.
For the details, see the following sections, starting with Documentables. Also see Writing Extensions and
document-object*.
8.1.1 Documentables
The
documentableargument may be adrefor anything else that islocateable. This includes non-drefxrefs and first-class objects such asfunctions.If
documentableis a string, then it is processed like a docstring indefsection. That is, with docstring sanitization, Codification, and linking (see Linking to Code, Linking to the Hyperspec).Finally,
documentablemay be a nested list oflocateable objects and docstrings. The structure of the list is unimportant. The objects in it are documented in depth-first order.
8.1.2 Return Values
If pages are nil, then document - like cl:format - returns a
string (when stream is nil) else nil.
If pages, then a list of output designators are returned, one for
each non-empty page (to which some output has been written), which
are determined as follows.
The string itself if the output was to a string.
The stream if the output was to a stream.
The pathname of the file if the output was to a file.
If the default page given by the stream argument of document was
written to, then its output designator is the first element of the
returned list. The rest of the designators correspond to the
non-empty pages in the pages argument of document in that order.
8.1.3 Pages
The pages argument of document is to create multi-page documents
by routing some of the generated output to files, strings or
streams. pages is a list of page specification elements. A page spec
is a property list with keys :objects, :output,
:uri-fragment, :source-uri-fn, :header-fn and :footer-fn. objects is
a list of objects (references are allowed but not required) whose
documentation is to be sent to :output.
Documentation is initially sent to a default stream (the stream
argument of document), but output is redirected if the thing being
currently documented is the :object of a page-spec.
:output can be a number things:
If it's
nil, then output will be collected in a string.If it's
t, then output will be sent to*standard-output*.If it's a stream, then output will be sent to that stream.
If it's a list whose first element is a string or a pathname, then output will be sent to the file denoted by that and the rest of the elements of the list are passed on to
cl:open. One extra keyword argument is:ensure-directories-exist. If it's true,ensure-directories-existwill be called on the pathname before it's opened.
Note that even if pages is specified, stream acts as a catch all,
absorbing the generated documentation for references not claimed by
any pages.
:header-fn, if not nil, is a function of a single stream argument,
which is called just before the first write to the page. Since
:format :html only generates HTML fragments, this makes it possible
to print arbitrary headers, typically setting the title, CSS
stylesheet, or charset.
:footer-fn is similar to :header-fn, but it's called after the last
write to the page. For HTML, it typically just closes the body.
:uri-fragment is a string such as "doc/manual.html" that specifies
where the page will be deployed on a webserver. It defines how links
between pages will look. If it's not specified and :output refers to
a file, then it defaults to the name of the file. If :uri-fragment
is nil, then no links will be made to or from that page.
Finally, :source-uri-fn is a function of a single, reference
argument. If it returns a value other than nil, then it must be a
string representing an URI. If format is :html and
*document-mark-up-signatures* is true, then the locative as
displayed in the signature will be a link to this uri. See
make-git-source-uri-fn.
pages may look something like this:
`((;; The section about SECTIONs and everything below it ...
:objects (, @sections)
;; ... is so boring that it's not worth the disk space, so
;; send it to a string.
:output (nil)
;; Explicitly tell other pages not to link to these guys.
:uri-fragment nil)
;; Send the @EXTENSION-API section and everything reachable
;; from it ...
(:objects (, @extension-api)
;; ... to build/tmp/pax-extension-api.html.
:output "build/tmp/pax-extension-api.html"
;; However, on the web server html files will be at this
;; location relative to some common root, so override the
;; default:
:uri-fragment "doc/dev/pax-extension-api.html"
;; Set html page title, stylesheet, charset.
:header-fn 'write-html-header
;; Just close the body.
:footer-fn 'write-html-footer)
;; Catch references that were not reachable from the above. It
;; is important for this page spec to be last.
(:objects (, @pax-manual)
:output "build/tmp/manual.html"
;; Links from the extension api page to the manual page will
;; be to ../user/pax-manual#<anchor>, while links going to
;; the opposite direction will be to
;; ../dev/pax-extension-api.html#<anchor>.
:uri-fragment "doc/user/pax-manual.html"
:header-fn 'write-html-header
:footer-fn 'write-html-footer))
8.1.4 Package and Readtable
While generating documentation, symbols may be read (e.g. from
docstrings) and printed. What values of *package* and *readtable*
are used is determined separately for each definition being
documented.
If the values of
*package*and*readtable*in effect at the time of definition were captured (e.g. bydefine-locative-typeanddefsection), then they are used.Else, if the definition has a Home Section (see below), then the home section's
section-packageandsection-readtableare used.Else, if the definition has an argument list, then the package of the first argument that's not external in any package is used.
Else, if the definition is named by a symbol, then its
symbol-packageis used, and*readtable*is set to the standard readtable(named-readtables:find-readtable :common-lisp).Else,
*package*is set to thecl-userpackage and*readtable*to the standard readtable.
The values thus determined come into effect after the name itself is printed, for printing of the arglist and the docstring.
CL-USER> (pax:document #'foo)
- [function] FOO <!> X Y &KEY (ERRORP T)
Do something with X and Y.
In the above, the <!> marks the place where *package* and
*readtable* are bound.
Home Section
The home section of an object is a section that contains the
object's definition in its section-entries or nil. In the
overwhelming majority of cases there should be at most one
containing section.
If there are multiple containing sections, the following apply.
If the name of the definition is a non-keyword symbol, only those containing sections are considered whose package is closest to the
symbol-packageof the name, where closest is defined as having the longest common prefix between the twopackage-names.If there are multiple sections with equally long matches or the name is not a non-keyword symbol, then it's undefined which one is the home section.
For example, (mgl-pax:document function) is an entry in the
MGL-PAX::@BASICS section. Unless another section that contains
it is defined in the MGL-PAX package, the home section is guaranteed
to be MGL-PAX::@BASICS because the symbol-packages of
mgl-pax:document and MGL-PAX::@BASICS are the same (hence their
common prefix is maximally long).
This scheme would also work, for example, if the home package
of document were mgl-pax/impl, and it were reexported from
mgl-pax because the only way to externally change the home package
would be to define a containing section in a package like
mgl-pax/imp.
Thus, relying on the package system makes it possible to find the intended home section of a definition among multiple containing sections with high probability. However, for names which are not symbols, there is no package system to advantage of.
[variable] *document-normalize-packages* t
Whether to print
[in package <package-name>]in the documentation when the package changes.
8.2 The mgl-pax/document ASDF System
- Description: Documentation generation support for MGL-PAX.
- Long Description: Do not declare a dependency on this system. It is autoloaded. See Generating Documentation.
- Licence: MIT, see COPYING.
- Author: Gábor Melis
- Mailto: mega@retes.hu
8.3 Browsing Live Documentation
Documentation can be browsed live in Emacs or with an external
browser. HTML documentation, complete with Codification and
links, is generated from docstrings of all kinds
of Lisp definitions and PAX sections.
If Emacs Setup has been done, the Elisp function mgl-pax-document
generates and displays documentation as a single HTML page. For
example, to view the documentation of this very section, one can do:
M-x mgl-pax-document
View Documentation of: pax::@browsing-live-documentation
If the empty string is entered, and there is no existing w3m buffer or w3m is not used, then sections registered in PAX World are listed. If there is a w3m buffer, then entering the empty string displays that buffer.
If we enter function instead, then a disambiguation page will be
shown with the documentation of the function class and the function
locative. One may then follow the links on the page to navigate to a
page with the documentation the desired definition. If you are
browsing live documentation right now, then the disambiguation page
is like this: function(0 1). In offline documentation, multiple links
are shown instead as described in Ambiguous Unspecified Locative.
Alternatively, a locative may be entered as part of the
argument to mgl-pax-document as in function class, which gives
this result. Finally, the definition of defsection
in the context of a single-page PAX Manual can be
viewed by
entering pax::@pax-manual#pax:defsection pax:macro.
In interactive use, mgl-pax-document defaults to documenting
slime-symbol-at-point, possibly with a nearby locative the same
way as in Navigating Sources in Emacs. The convenience function
mgl-pax-document-current-definition documents the definition with
point in it.
8.3.1 PAX URLs
A PAX URL consists of a REFERENCE and an optional fragment
part:
URL = [REFERENCE] ["#" FRAGMENT]
where REFERENCE names either
a complete reference (e.g.
"pax:section class"),or the name of a reference (e.g.
"pax:section"), which possibly makes what to document ambiguous.
8.3.2 Apropos
The Elisp functions mgl-pax-apropos, mgl-pax-apropos-all, and
mgl-pax-apropos-package can display the results of
dref:dref-apropos in the live documentation browser. These parallel the functionality of
slime-apropos, slime-apropos-all, and slime-apropos-package.
dref:dref-apropos itself is similar to cl:apropos-list, but it
supports more flexible matching – e.g. filtering by
locative types – and returns DRef
references.
The returned references are presented in two groups: those with non-symbol and those with symbol names. The non-symbol group is sorted by locative type then by name. The symbol group is sorted by name then by locative type.
8.3.3 Emacs Setup for Browsing
Make sure Emacs Setup has been done. In particular, set
mgl-pax-browser-function to choose between browsing documentation
with w3m in an
Emacs buffer, or with an external browser plus a web server in the
Lisp image.
In Emacs Setup, (mgl-pax-hijack-slime-doc-keys) was evaluated,
which handles the common case of binding keys. The Elisp definition
is reproduced here for its docstring.
(defun mgl-pax-hijack-slime-doc-keys ()
"Make the following changes to `slime-doc-map' (assuming it's
bound to `C-c C-d').
- `C-c C-d a': `mgl-pax-apropos' (replaces `slime-apropos')
- `C-c C-d z': `mgl-pax-aproposa-all' (replaces `slime-apropos-all')
- `C-c C-d p': `mgl-pax-apropos-package' (replaces `slime-apropos-package')
- `C-c C-d d': `mgl-pax-document' (replaces `slime-describe-symbol')
- `C-c C-d f': `mgl-pax-document' (replaces `slime-describe-function')
- `C-c C-d c': `mgl-pax-current-definition-toggle-view'
Also, regardless of whether `w3m' is available, add this:
- `C-c C-d u': `mgl-pax-edit-parent-section'
In addition, because it can be almost as useful as `M-.', one may
want to give `mgl-pax-document' a more convenient binding such as
`C-.' or `s-.' if you have a Super key. For example, to bind
`C-.' in all Slime buffers:
(slime-bind-keys slime-parent-map nil '((\"C-.\" mgl-pax-document)))
To bind `C-.' globally:
(global-set-key (kbd \"C-.\") 'mgl-pax-document)"
...)
8.3.4 Browsing with w3m
With w3m's default key bindings, moving the cursor between links involves
tab and s-tab (or <up> and <down>). ret and <right>
follow a link, while b and <left> go back in history.
In addition, the following PAX-specific key bindings are available:
m-.visits the source location of the definition corresponding to the link under the point.Invoking
mgl-pax-documenton a section title link will show the documentation of that section on its own page.nmoves to the next PAX definition on the page.pmoves to the previous PAX definition on the page.ufollows the firstUp:link (to the first containingsection) if any.uis likeubut positions the cursor at the top of the page.vvisits the source location of the current definition (the one under the cursor or the first one above it).vvisits the source location of the first definition on the page.
8.3.5 Browsing with Other Browsers
When the value of the Elisp variable mgl-pax-browser-function
is not w3m-browse-url, requests are served via a web server
started in the running Lisp, and documentation is most likely
displayed in a separate browser window .
By default, mgl-pax-browser-function is nil, which makes PAX use
browse-url-browser-function. You may want to customize the related
browse-url-new-window-flag or, for Chrome, set
browse-url-chrome-arguments to ("--new-window").
In the browser, clicking on the locative on the left of the
object (e.g. in - [function] PRINT) will raise and focus the Emacs
window (if Emacs is not in text mode, and also subject to window
manager focus stealing settings), then go to the corresponding
source location. For sections, clicking on the lambda link will do
the same (see *document-fancy-html-navigation*).
Finally, note that the urls exposed by the web server are subject to
change.
[variable] *browse-html-style* :charter
The HTML style to use for browsing live documentation. Affects only non-w3m browsers. See
*document-html-default-style*for the possible values.
8.4 Markdown Support
The markdown in docstrings is processed with the 3BMD library.
8.4.1 Markdown in Docstrings
Docstrings can be indented in any of the usual styles. PAX normalizes indentation by stripping the longest run of leading spaces common to all non-blank lines except the first. The following two docstrings are equivalent:
(defun foo () "This is indented differently") (defun foo () "This is indented differently")When Browsing Live Documentation, the page displayed can be of, say, a single function within what would constitute the offline documentation of a library. Because markdown reference link definitions, for example
[Daring Fireball]: http://daringfireball.net/can be defined anywhere, they wouldn't be resolvable in that case, their use is discouraged. Currently, only reflink definitions in the vicinity of their uses are resolvable. This is left intentionally vague because the specifics are subject to change.
See
define-glossary-termfor a better alternative to markdown reference links.
Docstrings of definitions which do not have a Home Section and are
not sections themselves are assumed to have been written with no
knowledge of PAX and to conform to markdown only by accident. These
docstrings are thus sanitized more aggressively.
Indentation of what looks like blocks of Lisp code is rounded up to a multiple of 4. More precisely, non-zero indented lines between blank lines or the docstring boundaries are reindented if the first non-space character of the first line is an
(or a;character.Special HTML characters
<&are escaped.Furthermore, to reduce the chance of inadvertently introducing a markdown heading, if a line starts with a string of
#characters, then the first one is automatically escaped. Thus, the following two docstrings are equivalent:The characters #\Space, #\Tab and #Return are in the whitespace group. The characters #\Space, #\Tab and \#Return are in the whitespace group.
8.4.2 Syntax Highlighting
For syntax highlighting, Github's fenced code blocks markdown extension to mark up code blocks with triple backticks is enabled so all you need to do is write:
```elisp
(defun foo ())
```
to get syntactically marked up HTML output. Copy src/style.css
from PAX and you are set. The language tag, elisp in this example,
is optional and defaults to common-lisp.
See the documentation of 3BMD and Colorize for the details.
8.4.3 MathJax
Displaying pretty mathematics in TeX format is supported via
MathJax. It can be done inline with $ like this:
$\int_0^\infty e^{-x^2} dx=\frac{\sqrt{\pi}}{2}$
which is displayed as $\int_0^\infty e^{-x^2}
dx=\frac{\sqrt{\pi}}{2}$, or it can be delimited by $$ like this:
$$\int_0^\infty e^{-x^2} dx=\frac{\sqrt{\pi}}{2}$$
to get: $$\int_0^\infty e^{-x^2} dx=\frac{\sqrt{\pi}}{2}$$
MathJax will leave code blocks (including those inline with
backticks) alone. Outside code blocks, escape $ by prefixing it
with a backslash to scare MathJax off.
Escaping all those backslashes in TeX fragments embedded in Lisp strings can be a pain. Pythonic String Reader can help with that.
8.5 Codification
[variable] *document-uppercase-is-code* t
When true, interesting names extracted from codifiable words are assumed to be code as if they were marked up with backticks. For example, this docstring
"T PRINT CLASSes SECTION *PACKAGE* MGL-PAX ASDF CaMeL Capital"is equivalent to this:
"`T` `PRINT` `CLASS`es `SECTION` `*PACKAGE*` `MGL-PAX` `ASDF` CaMel Capital"and renders as
tprintclassessectionmgl-paxasdfCaMel Capitalwhere the links are added due to
*document-link-code*.To suppress codification, add a backslash to the beginning of the a codifiable word or right after the leading
*if it would otherwise be parsed as markdown emphasis:"\\SECTION *\\PACKAGE*"The number of backslashes is doubled above because that's how the example looks in a docstring. Note that the backslash is discarded even if
*document-uppercase-is-code*is false.
-
A word is codifiable iff
it has a single uppercase character (e.g. it's
t) and no lowercase characters at all, orthere is more than one uppercase character and no lowercase characters between them (e.g.
classes, nonreadable,class-names but notClassesoraTe.
-
A name is interesting iff
it names a symbol external to its package, or
it is at least 3 characters long and names an interned symbol, or
it names a local reference.
[variable] *document-downcase-uppercase-code* nil
If true, then all Markdown inline code (that is,
stuff between backticks, including those found if*document-uppercase-is-code*) which has no lowercase characters is downcased in the output. Characters of literal strings in the code may be of any case. If this variable is:only-in-markupand the output format does not support markup (e.g. it's:plain), then no downcasing is performed. For example,`(PRINT "Hello")`is downcased to
`(print "Hello")`because it only contains uppercase characters outside the string. However,
`MiXed "RESULTS"`is not altered because it has lowercase characters.
If the first two characters are backslashes, then no downcasing is performed, in addition to Preventing Autolinking. Use this to mark inline code that's not Lisp.
Press `\\M-.` in Emacs.
8.6 Linking to Code
In this section, we describe all ways of linking to code
available when *document-uppercase-is-code* is true.
Note that invoking M-. on the
name of any of the following links will disambiguate based the
textual context, determining the locative. In a nutshell, if M-.
works without popping up a list of choices, then the documentation
will contain a single link.
[variable] *document-link-code* t
Enable the various forms of links in docstrings described in Linking to Code. See the following sections for a description of how to use linking.
8.6.1 Specified Locative
The following examples all render as document.
[DOCUMENT][function](name + locative, explicit link)DOCUMENT function(name + locative, autolink)function DOCUMENT(locative + name, autolink)
The Markdown link definition (i.e. function between the second
set of brackets above) needs no backticks to mark it as code.
Here and below, the name (document) is uppercased, and we rely
on *document-uppercase-is-code* being true. Alternatively, the name
could be explicitly marked up as code with a pair of backticks, and
then its character case would likely not matter (subject to
readtable-case).
The link text in the above examples is document. To override it,
this form may be used:
[see this][document function](title + name + locative, explicit link) renders as: see this.
8.6.2 Unspecified Locative
When only an name is provided without a locative, all
definitions of the name are considered as possible link targets.
Then, definitions that are not symbol-based (i.e. whose
xref-name is not a symbol) are filtered out to prevent
unrelated packages and asdf:systems from cluttering the
documentation without the control provided by importing symbols.
To further reduce clutter, if the definitions include a
generic-function locative, then all references with locative-type
method, accessor, reader and
writer are removed to avoid linking to a possibly large
number of methods.
Furthermore, filter out all references with locative-type
locative if there are references with other locative-types.
Unambiguous Unspecified Locative
In the following examples, although no locative is specified,
document names a single name being documented, so they all
render as document.
[document][](name, explicit link),document(name, autolink).
To override the title:
[see this][document](title + name, explicit link) renders as: see this.
Ambiguous Unspecified Locative
These examples all render as section, linking to both
definitions of the name section, the class and the
locative. Note that the rendered output is a single link to a
disambiguation page when Browsing Live Documentation, while
multiple, numbered links are generated in offline documentation.
[section][](name, explicit link)section(name, autolink)
To override the title:
[see this][section](title + name, explicit link) renders as: see this.
8.6.3 Explicit and Autolinking
The examples in the previous sections are marked with explicit link or autolink. Explicit links are those with a Markdown reference link spelled out explicitly, while autolinks are those without.
8.6.4 Preventing Autolinking
In the common case, when *document-uppercase-is-code* is true,
prefixing an uppercase word with a backslash prevents it from being
codified and thus also prevents autolinking form kicking in. For example,
\DOCUMENT
renders as DOCUMENT. If it should be marked up as code but not autolinked, the backslash must be within backticks like this:
`\DOCUMENT`
This renders as document. Alternatively, the dislocated or the
argument locative may be used as in [DOCUMENT][dislocated].
8.6.5 Unresolvable Links
[condition] unresolvable-reflink warning
When
documentencounters an explicit link such as[NONEXISTENT][function]that looks like a PAX construct but cannot be resolved, it signals andunresolvable-reflinkwarning.If the
output-reflinkrestart is invoked, then no warning is printed and the markdown link is left unchanged.muffle-warning(01) is equivalent tooutput-reflink.If the
output-labelrestart is invoked, then no warning is printed and the markdown link is replaced by its label. For example,[NONEXISTENT][function]becomesnonexistent.If the warning is not handled, then it is printed to
*error-output*. Otherwise, it behaves asoutput-reflink.
[function] output-reflink &optional condition
Invoke the
output-reflinkrestart. Seeunresolvable-reflink.
[function] output-label &optional condition
Invoke the
output-labelrestart. Seeunresolvable-reflink.
8.6.6 Suppressed Links
Autolinking of code (i.e.
of something like foo) is suppressed if it would create a link
that was already made within the same docstring. In the following
docstring, only the first foo will be turned into a link.
"`FOO` is safe. `FOO` is great."
However, explicit links (when a locative was specified or
found near the name) are never suppressed. In the following, in
both docstrings, both occurrences foo produce links.
"`FOO` is safe. [`FOO`][macro] is great."
"`FOO` is safe. Macro `FOO` is great."
As an exception, links with specified
and unambiguous
locatives to sections and glossary-terms always produce a link to
allow their titles to be displayed properly.
Finally, autolinking to t or
nil is suppressed (see *document-link-to-hyperspec*).
8.6.7 Local References
To declutter the generated output by reducing the number of
links, the so-called local references (e.g. references to the very
definition for which documentation is being generated) are treated
specially. In the following example, there are local references to
the function foo and its arguments, so none of them get turned into
links:
(defun foo (arg1 arg2)
"FOO takes two arguments: ARG1 and ARG2."
t)
If linking was desired, one could use a Specified Locative (e.g.
[FOO][function] or FOO function), which results in a single
link. An explicit link with an unspecified locative like [foo][]
generates links to all references involving the foo symbol except
the local ones.
The exact rules for local references are as follows:
Unless a locative is specified, no autolinking is performed for names for which there are local references. For example,
foodoes not get any links if there is any local reference with the same name.With a locative specified (e.g. in the explicit link
[FOO][function]or in the textthe FOO function), a single link is made irrespective of any local references.Explicit links with an unspecified locative (e.g.
[foo][]) are linked to all non-local references.
8.7 Linking to the Hyperspec
[variable] *document-link-to-hyperspec* t
If true, link symbols found in code to the Common Lisp Hyperspec unless there is a definition in the running Lisp that is being
documented.Locatives work as expected (see
*document-link-code*):find-iflinks tofind-if,functionlinks tofunction(01), and[FUNCTION][type]links tofunction.Autolinking to
tandnilis suppressed. If desired, use[t][](that links tot(01)) or[T][constant](that links tot).Note that linking explicitly with the
clhslocative is not subject to the value of this variable.
[variable] *document-hyperspec-root* "http://www.lispworks.com/documentation/HyperSpec/"
A URL of the Common Lisp Hyperspec. The default value is the canonical location. When invoked from Emacs, the Elisp variable
common-lisp-hyperspec-rootis in effect.
8.8 Linking to Sections
The following variables control how to generate section numbering, table of contents and navigation links.
[variable] *document-link-sections* t
When true, HTML anchors are generated before the headings (e.g. of sections), which allows the table of contents to contain links and also code-like references to sections (like
@foo-manual) to be translated to links with the section title being the name of the link.
[variable] *document-max-numbering-level* 3
A non-negative integer. In their hierarchy, sections on levels less than this value get numbered in the format of
3.1.2. Setting it to 0 turns numbering off.
[variable] *document-max-table-of-contents-level* 3
An integer that determines the depth of the table of contents.
If negative, then no table of contents is generated.
If non-negative, and there are multiple top-level sections on a page, then they are listed at the top of the page.
If positive, then for each top-level section a table of contents is printed after its heading, which includes a nested tree of section titles whose depth is limited by this value.
If
*document-link-sections*is true, then the tables will link to the sections.
[variable] *document-text-navigation* nil
If true, then before each heading a line is printed with links to the previous, parent and next section. Needs
*document-link-sections*to be on to work.
[variable] *document-fancy-html-navigation* t
If true and the output format is HTML, then headings get a navigation component that consists of links to the previous, parent, next section, a self-link, and a link to the definition in the source code if available (see
:source-uri-fnindocument). This component is normally hidden, it is visible only when the mouse is over the heading. Needs*document-link-sections*to be on to work.
8.9 Miscellaneous Variables
[variable] *document-url-versions* (2 1)
A list of versions of PAX URL formats to support in the generated documentation. The first in the list is used to generate links.
PAX emits HTML anchors before the documentation of
sections (see Linking to Sections) and other things (see Linking to Code). For the functionfoo, in the current version (version 2), the anchor is<a id="MGL-PAX:FOO%20FUNCTION">, and its URL will end with#MGL-PAX:FOO%20FUNCTION.Note that to make the URL independent of whether a symbol is internal or external to their
symbol-package, single colon is printed where a double colon would be expected. Package and symbol names are both printed verbatim except for escaping colons and spaces with a backslash. For exported symbols with no funny characters, this coincides with howprin1would print the symbol, while having the benefit of making the URL independent of the Lisp printer's escaping strategy and producing human-readable output for mixed-case symbols. No such promises are made for non-ASCII characters, and their URLs may change in future versions. Locatives are printed withprin1.Version 1 is based on the more strict HTML4 standard and the id of
foois"x-28MGL-PAX-3A-3AFOO-20FUNCTION-29". This is supported by Github-flavoured Markdown. Version 2 has minimal clutter and is obviously preferred. However, in order not to break external links, by default, both anchors are generated.Let's understand the generated Markdown.
(defun foo (x)) (document #'foo :format :markdown) => ("<a id=\"x-28MGL-PAX-3AFOO-20FUNCTION-29\"></a> <a id=\"MGL-PAX:FOO%20FUNCTION\"></a> - [function] **FOO** *X* ") (let ((*document-url-versions* '(1))) (document #'foo :format :markdown)) => ("<a id=\"x-28MGL-PAX-3AFOO-20FUNCTION-29\"></a> - [function] **FOO** *X* ")
[variable] *document-min-link-hash-length* 4
Recall that markdown reference style links (like
[label][id]) are used for linking to sections and code. It is desirable to have ids that are short to maintain legibility of the generated markdown, but also stable to reduce the spurious diffs in the generated documentation, which can be a pain in a version control system.Clearly, there is a tradeoff here. This variable controls how many characters of the MD5 sum of the full link id (the reference as a string) are retained. If collisions are found due to the low number of characters, then the length of the hash of the colliding reference is increased.
This variable has no effect on the HTML generated from markdown, but it can make markdown output more readable.
[variable] *document-mark-up-signatures* t
When true, some things such as function names and arglists are rendered as bold and italic. In
:htmloutput, locative types become links to sources (if:source-uri-fnis provided, seedocument), and the symbol becomes a self-link for your permalinking pleasure.For example, a reference is rendered in markdown roughly as:
- [function] foo x yWith this option on, the above becomes:
- [function] **foo** *x y*Also, in HTML
**foo**will be a link to that very entry and[function]may turn into a link to sources.
[variable] *document-base-url* nil
When
*document-base-url*is non-nil, this is prepended to all Markdown relativeurls. It must be a validurlwithout no query and fragment parts (that is, "http://lisp.org/doc/" but not "http://lisp.org/doc?a=1" or "http://lisp.org/doc#fragment"). Note that intra-page links using onlyurlfragments (e.g. and explicit HTML links (e.g.<a href="...">) in Markdown are not affected.
8.10 Utilities for Generating Documentation
Two convenience functions are provided to serve the common case of having an ASDF system with some readmes and a directory with for the HTML documentation and the default CSS stylesheet.
[function] update-asdf-system-readmes object asdf-system &key (url-versions '(1)) (formats '(:markdown))
Convenience function to generate up to two readme files in the directory holding the
asdf-systemdefinition.objectis passed on todocument.If
:markdownis informats, thenREADME.mdis generated with anchors, links, inline code, and other markup added. Not necessarily the easiest on the eye in an editor but looks good on github.If
:plainis informats, thenREADMEis generated, which is optimized for reading in text format. It has less cluttery markup and no autolinking.Example usage:
(update-asdf-system-readmes @pax-manual :mgl-pax :formats '(:markdown :plain))Note that
*document-url-versions*is bound tourl-versions, which defaults to using the uglier, version 1 style ofurlfor the sake of github.
8.10.1 HTML Output
[function] update-asdf-system-html-docs sections asdf-system &key pages (target-dir (asdf/system:system-relative-pathname asdf-system "doc/")) (update-css-p t) (style *document-html-default-style*)
Generate pretty HTML documentation for a single ASDF system, possibly linking to github. If
update-css-p, copy thestylefiles totarget-dir(see*document-html-default-style*).Example usage:
(update-asdf-system-html-docs @pax-manual :mgl-pax)The same, linking to the sources on github:
(update-asdf-system-html-docs @pax-manual :mgl-pax :pages `((:objects (,mgl-pax::@pax-manual) :source-uri-fn ,(make-git-source-uri-fn :mgl-pax "https://github.com/melisgl/mgl-pax"))))
See the following variables, which control HTML generation.
[variable] *document-html-default-style* :default
The HTML style to use. It's either
styleis either:defaultor:charter. The:defaultCSS stylesheet relies on the default fonts (sans-serif, serif, monospace), while:charterbundles some fonts for a more controlled look.The value of this variable affects the default style of
update-asdf-system-html-docs. If you change this variable, you may need to do a hard refresh in the browser (oftenC-<f5>). See*browse-html-style*for how to control the style used for Browsing Live Documentation.
[variable] *document-html-max-navigation-table-of-contents-level* nil
nilor a non-negative integer. If non-nil, it overrides*document-max-numbering-level*in the dynamic HTML table of contents on the left of the page.
[variable] *document-html-head* nil
Stuff to be included in the
<head>of the generated HTML.
[variable] *document-html-sidebar* nil
Stuff to be included in the HTML sidebar.
If
nil, a default sidebar is generated, with*document-html-top-blocks-of-links*, followed by the dynamic table of contents, and*document-html-bottom-blocks-of-links*.If a
string(01), then it is written to the HTML output as is without any escaping.If a function designator, then it is called with a single argument, the HTML stream, where it must write the output.
[variable] *document-html-top-blocks-of-links* nil
A list of blocks of links to be displayed on the sidebar on the left, above the table of contents. A block is of the form
(&key title id links), wheretitlewill be displayed at the top of the block in a HTMLdivwithidfollowed by the links.linksis a list of(uri label)elements, whereurimaybe a string or an object beingdocumented or areferencethereof.
[variable] *document-html-bottom-blocks-of-links* nil
Like
*document-html-top-blocks-of-links*, only it is displayed below the table of contents.
8.10.2 Github Workflow
It is generally recommended to commit generated readmes (see
update-asdf-system-readmes), so that users have something to read
without reading the code and sites like github can display them.
HTML documentation can also be committed, but there is an issue with
that: when linking to the sources (see make-git-source-uri-fn), the
commit id is in the link. This means that code changes need to be
committed first, and only then can HTML documentation be regenerated
and committed in a followup commit.
The second issue is that github is not very good at serving HTML files from the repository itself (and http://htmlpreview.github.io chokes on links to the sources).
The recommended workflow is to use
gh-pages, which can be made relatively
painless with the git worktree command. The gist of it is to make
the doc/ directory a checkout of the branch named gh-pages.
There is a good
description
of this general process. Two commits are needed still, but it is
somewhat less painful.
This way the HTML documentation will be available at
http://<username>.github.io/<repo-name>
It is probably a good idea to add sections like the Links and Systems section to allow jumping between the repository and the gh-pages site.
[function] make-github-source-uri-fn asdf-system github-uri &key git-version
This function is a backward-compatibility wrapper around
make-git-source-uri-fn, which supersedesmake-github-source-uri-fn. All arguments are passed on tomake-git-source-uri-fn, leavinguri-format-stringat its default, which is suitable for github.
[function] make-git-source-uri-fn asdf-system git-forge-uri &key git-version (uri-format-string "~A/blob/~A/~A#L~S")
Return a function suitable as
:source-uri-fnof a page spec (see thepagesargument ofdocument). The function looks at the source location of the object passed to it, and if the location is found, the path is made relative to the toplevel directory of the git checkout containing the file of theasdf-systemand finally an URI pointing to your git forge (such as github) is returned. A warning is signalled whenever the source location lookup fails or if the source location points to a directory not below the directory ofasdf-system.If
git-forge-uriis"https://github.com/melisgl/mgl-pax/"andgit-versionis"master", then the returned URI may look like this:https://github.com/melisgl/mgl-pax/blob/master/src/pax-early.lisp#L12If
git-versionisnil, then an attempt is made to determine to current commit id from the.gitin the directory holdingasdf-system. If no.gitdirectory is found, then no links to the git forge will be generated.uri-format-stringis acl:formatcontrol string for four arguments:git-forge-uri,git-version,the relative path to the file of the source location of the reference,
and the line number.
The default value of
uri-format-stringis for github. If using a non-standard git forge, such as Sourcehut or Gitlab, simply pass a suitableuri-format-stringmatching the URI scheme of your forge.
8.10.3 PAX World
PAX World is a registry of documents, which can generate cross-linked HTML documentation pages for all the registered documents. There is an official PAX World.
[function] register-doc-in-pax-world name sections page-specs
Register
sectionsandpage-specsundername(a symbol) in PAX World. By default,update-pax-worldgenerates documentation for all of these.sectionsandpage-specsmust be lists ofsections andpage-specs (SEEdocument) or designators of function of no arguments that return such lists.
For example, this is how PAX registers itself:
(defun pax-sections ()
(list @pax-manual))
(defun pax-pages ()
`((:objects ,(pax-sections)
:source-uri-fn ,(make-git-source-uri-fn
:mgl-pax
"https://github.com/melisgl/mgl-pax"))))
(register-doc-in-pax-world :pax 'pax-sections 'pax-pages)
[function] update-pax-world &key (docs *registered-pax-world-docs*) dir update-css-p (style *document-html-default-style*)
Generate HTML documentation for all
docs. Files are created indir((asdf:system-relative-pathname :mgl-pax "world/")by default ifdirisnil).docsis a list of entries of the form (namesectionspage-specs). The default fordocsis all the sections and pages registered withregister-doc-in-pax-world.In the absence of
:header-fn:footer-fn,:output, every spec inpage-specsis augmented with HTML headers, footers and output location specifications (based on the name of the section).If necessary a default page spec is created for every section.
8.11 Overview of Escaping
Let's recap how escaping Codification, downcasing, and Linking to Code works.
One backslash in front of a word turns codification off. Use this to prevent codification words such as DOCUMENT, which is all uppercase hence codifiable, and it names an exported symbol hence it is interesting.
One backslash right after an opening backtick turns autolinking off.
Two backslashes right after an opening backtick turns autolinking and downcasing off. Use this for things that are not Lisp code but which need to be in a monospace font.
In the following examples capital C/D/A letters mark the presence,
and a/b/c the absence of codification, downcasing, and autolinking
assuming all these features are enabled by
*document-uppercase-is-code*, *document-downcase-uppercase-code*,
and *document-link-code*.
DOCUMENT => [`document`][1234] (CDA)
\DOCUMENT => DOCUMENT (cda)
`\DOCUMENT` => `document` (CDa)
`\\DOCUMENT` => `DOCUMENT` (CdA)
[DOCUMENT][] => [`document`][1234] (CDA)
[\DOCUMENT][] => [DOCUMENT][1234] (cdA)
[`\DOCUMENT`][] => [`document`][1234] (CDA) *
[`\\DOCUMENT`][] => [`DOCUMENT`][1234] (CdA)
[DOCUMENT][dislocated] => `document` (CDa)
Note that in the example marked with *, the single backslash,
that would normally turn autolinking off, is ignored because it is
in an explicit link.
8.12 Output Details
By default, drefs are documented in the following format.
- [<locative-type>] <name> <arglist>
<docstring>
The line with the bullet is printed with documenting-reference. The
docstring is processed with document-docstring while
Local References established with with-dislocated-names are in
effect for all variables locally bound in a definition with arglist,
and *package* is bound to the second return value of docstring.
With this default format, PAX supports all locative types, but for some Locative Types defined in DRef and the PAX Locatives, special provisions have been made.
- For definitions with a
variableorconstantlocative, their initform is printed as their arglist. The initform is theinitformargument of the locative if provided, or the global symbol value of their name. If noinitformis provided, and the symbol is globally unbound, then no arglist is printed.
When the printed initform is too long, it is truncated.
Depending of what the
setflocative refers to, thearglistof the setf expander, setf function, or the method signature is printed as with themethodlocative.For definitions with a
methodlocative, the arglist printed is the method signature, which consists of the locative'squalifiersandspecializersappended.For definitions with an
accessor,readerorwriterlocative, the class on which they are specialized is printed as their arglist.For definitions with a
structure-accessorlocative, the arglist printed is the locative'sclass-nameargument if provided.For definitions with a
classlocative, the arglist printed is the list of immediate superclasses withstandard-object,conditionand non-exported symbols omitted.For definitions with a
asdf:systemlocative, their most important slots are printed as an unnumbered list.When a definition with the
sectionlocative is being documented, a new (sub)section is opened (seewith-heading), within which documentation for its each of itssection-entriesis generated. A fresh line is printed after all entries except the last.For definitions with a
glossary-termlocative, no arglist is printed, and if non-nil,glossary-term-titleis printed as name.For definitions with a
golocative, itslocative-argsare printed as its arglist, along with a redirection message.See the
includelocative.For definitions with a
clhslocative, thelocative-argsare printed as the arglist. There is no docstring.For definitions with an
unknownlocative, thelocative-argsare printed as the arglist. There is no docstring.
8.13 Documentation Generation Implementation Notes
Documentation Generation is supported on ABCL, AllegroCL, CLISP,
CCL, CMUCL, ECL and SBCL, but their outputs may differ due to the
lack of some introspective capability. SBCL generates complete
output. see arglist, docstring and source-location for
implementation notes.
In addition, CLISP does not support the ambiguous case of PAX URLs
for Browsing Live Documentation because the current implementation
relies on Swank to list definitions of symbols (as variable,
function, etc), and that simply doesn't work.
9 Transcripts
What are transcripts for? When writing a tutorial, one often wants to include a REPL session with maybe a few defuns and a couple of forms whose output or return values are shown. Also, in a function's docstring an example call with concrete arguments and return values speaks volumes. A transcript is a text that looks like a REPL session, but which has a light markup for printed output and return values, while no markup (i.e. prompt) for Lisp forms. PAX transcripts may include output and return values of all forms, or only selected ones. In either case, the transcript itself can be easily generated from the source code.
The main worry associated with including examples in the
documentation is that they tend to get out-of-sync with the code.
This is solved by being able to parse back and update transcripts.
In fact, this is exactly what happens during documentation
generation with PAX. Code sections tagged with cl-transcript are
retranscribed and checked for consistency (that is, no difference in
output or return values). If the consistency check fails, an error
is signalled that includes a reference to the object being
documented.
Going beyond documentation, transcript consistency checks can be used for writing simple tests in a very readable form. For example:
(+ 1 2)
=> 3
(values (princ :hello) (list 1 2))
.. HELLO
=> :HELLO
=> (1 2)
All in all, transcripts are a handy tool especially when combined with the Emacs support to regenerate them and with PYTHONIC-STRING-READER's triple-quoted strings, that allow one to work with nested strings with less noise. The triple-quote syntax can be enabled with:
(in-readtable pythonic-string-syntax)
9.1 The mgl-pax/transcribe ASDF System
- Description: Transcription support for MGL-PAX.
- Long Description: Do not declare a dependency on this system.
It is autoloaded by
mgl-pax:transcribeand by the Emacs integration (see Transcripts). - Licence: MIT, see COPYING.
- Author: Gábor Melis
- Mailto: mega@retes.hu
9.2 Transcribing with Emacs
Typical transcript usage from within Emacs is simple: add a Lisp
form to a docstring or comment at any indentation level. Move the
cursor right after the end of the form as if you were to evaluate it
with C-x C-e. The cursor is marked by #\^:
This is part of a docstring.
```cl-transcript
(values (princ :hello) (list 1 2))^
```
Note that the use of fenced code blocks with the language tag
cl-transcript is only to tell PAX to perform consistency checks at
documentation generation time.
Now invoke the Elisp function mgl-pax-transcribe where the cursor
is, and the fenced code block from the docstring becomes:
(values (princ :hello) (list 1 2))
.. HELLO
=> :HELLO
=> (1 2)
^
Then you change the printed message and add a comment to the second return value:
(values (princ :hello-world) (list 1 2))
.. HELLO
=> :HELLO
=> (1
;; This value is arbitrary.
2)
When generating the documentation you get a
transcription-consistency-error because the printed output and the
first return value changed, so you regenerate the documentation by
marking the region of bounded by #\| and the cursor at #\^ in
the example:
|(values (princ :hello-world) (list 1 2))
.. HELLO
=> :HELLO
=> (1
;; This value is arbitrary.
2)
^
then invoke the Elisp function mgl-pax-retranscribe-region to get:
(values (princ :hello-world) (list 1 2))
.. HELLO-WORLD
=> :HELLO-WORLD
=> (1
;; This value is arbitrary.
2)
^
Note how the indentation and the comment of (1 2) were left alone,
but the output and the first return value got updated.
Alternatively, C-u 1 mgl-pax-transcribe will emit commented markup:
(values (princ :hello) (list 1 2))
;.. HELLO
;=> :HELLO
;=> (1 2)
C-u 0 mgl-pax-retranscribe-region will turn commented into
non-commented markup. In general, the numeric prefix argument is the
index of the syntax to be used in *transcribe-syntaxes*. Without a
prefix argument, mgl-pax-retranscribe-region will not change the
markup style.
Finally, not only do both functions work at any indentation level but in comments too:
;;;; (values (princ :hello) (list 1 2))
;;;; .. HELLO
;;;; => :HELLO
;;;; => (1 2)
The dynamic environment of the transcription is determined by the
:dynenv argument of the enclosing cl-transcript code block (see
Controlling the Dynamic Environment).
Transcription support in Emacs can be enabled by loading
src/mgl-pax.el. See Emacs Setup.
9.3 Transcript API
[function] transcribe input output &key update-only (include-no-output update-only) (include-no-value update-only) (echo t) (check-consistency *transcribe-check-consistency*) default-syntax (input-syntaxes *transcribe-syntaxes*) (output-syntaxes *transcribe-syntaxes*) dynenv
Read forms from
inputand write them (iffecho) tooutputfollowed by any output and return values produced by callingevalon the form.inputcan be a stream or a string, whileoutputcan be a stream ornil, in which case output goes into a string. The return value is theoutputstream or the string that was constructed. Sincetranscribeevaluates arbitrary code anyway, forms are read with*read-eval*t.Go up to Transcribing with Emacs for nice examples. A more mind-bending one is this:
(transcribe "(princ 42) " nil) => "(princ 42) .. 42 => 42 "However, the above may be a bit confusing since this documentation uses
transcribemarkup syntax in this very example, so let's do it differently. If we have a file with these contents:(values (princ 42) (list 1 2))it is transcribed to:
(values (princ 42) (list 1 2)) .. 42 => 42 => (1 2)Output to all standard streams is captured and printed with the
:outputprefix (".."). The return values above are printed with the:readableprefix ("=>"). Note how these prefixes are always printed on a new line to facilitate parsing.Updating
transcribeis able to parse its own output. If we transcribe the previous output above, we get it back exactly. However, if we remove all output markers, leave only a placeholder value marker and pass:update-onlytwith source:(values (princ 42) (list 1 2)) =>we get this:
(values (princ 42) (list 1 2)) => 42 => (1 2)With
update-only, the printed output of a form is transcribed only if there were output markers in the source. Similarly, withupdate-only, return values are transcribed only if there were value markers in the source.No Output/Values
If the form produces no output or returns no values, then whether or not output and values are transcribed is controlled by
include-no-outputandinclude-no-value, respectively. By default, neither is on so:(values) .. =>is transcribed to
(values)With
update-onlytrue, we probably wouldn't like to lose those markers since they were put there for a reason. Hence, withupdate-only,include-no-outputandinclude-no-valuedefault to true. So, withupdate-onlythe above example is transcribed to:(values) .. => ; No valuewhere the last line is the
:no-valueprefix.Consistency Checks
If
check-consistencyis true, thentranscribesignals a continuabletranscription-output-consistency-errorwhenever a form's output as a string is different from what was ininput, provided thatinputcontained the output. Similarly, for values, a continuabletranscription-values-consistency-erroris signalled if a value read from the source does not print as the as the value returned byeval. This allows readable values to be hand-indented without failing consistency checks:(list 1 2) => ;; This is commented, too. (1 ;; Funny indent. 2)See Transcript Consistency Checking for the full picture.
Unreadable Values
The above scheme involves
read, so consistency of unreadable values cannot be treated the same. In fact, unreadable values must even be printed differently for transcribe to be able to read them back:(defclass some-class () ()) (defmethod print-object ((obj some-class) stream) (print-unreadable-object (obj stream :type t) (format stream \"~%~%end\"))) (make-instance 'some-class) ==> #<SOME-CLASS --> --> end>where
"==>"is the:unreadableprefix and"-->"is the:unreadable-continuationprefix. As with outputs, a consistency check between an unreadable value from the source and the value fromevalis performed withstring=by default. That is, the value fromevalis printed to a string and compared to the source value. Hence, any change to unreadable values will break consistency checks. This is most troublesome with instances of classes with the defaultprint-objectmethod printing the memory address. See Finer-Grained Consistency Checks.Errors
If an
errorcondition is signalled, the error is printed to the output and no values are returned.(progn (print "hello") (error "no greeting")) .. .. "hello" .. debugger invoked on SIMPLE-ERROR: .. no greetingTo keep the textual representation somewhat likely to be portable, the printing is done with
(format t "#<~S ~S>" (type-of error) (princ-to-string error)).simple-conditions are formatted to strings withsimple-condition-format-controlandsimple-condition-format-arguments.Syntaxes
Finally, a transcript may employ different syntaxes for the output and values of different forms. When
inputis read, the syntax for each form is determined by trying to match all prefixes from all syntaxes ininput-syntaxesagainst a line. If there are no output or values for a form ininput, then the syntax remains undetermined.When
outputis written, the prefixes to be used are looked up indefault-syntaxofoutput-syntaxes, ifdefault-syntaxis notnil. Ifdefault-syntaxisnil, then the syntax used by the same form in theinputis used or (if that could not be determined) the syntax of the previous form. If there was no previous form, then the first syntax ifoutput-syntaxesis used.To produce a transcript that's executable Lisp code, use
:default-syntax:commented-1:(make-instance 'some-class) ;==> #<SOME-CLASS ;--> ;--> end> (list 1 2) ;=> (1 ;-> 2)To translate the above to uncommented syntax, use
:default-syntax:default. Ifdefault-syntaxisnil(the default), the same syntax will be used in the output as in the input as much as possible.Dynamic Environment
If
dynenvis non-nil, then it must be a function that establishes the dynamic environment in which transcription shall take place. It is called with a single argument: a thunk (a function of no arguments). See Controlling the Dynamic Environment for an example.
[variable] *transcribe-check-consistency* nil
The default value of
transcribe'scheck-consistencyargument.
[variable] *transcribe-syntaxes* ((:default (:output "..") (:no-value "=> ; No value") (:readable "=>") (:unreadable "==>") (:unreadable-continuation "-->")) (:commented-1 (:output ";..") (:no-value ";=> ; No value") (:readable ";=>") (:readable-continuation ";->") (:unreadable ";==>") (:unreadable-continuation ";-->")) (:commented-2 (:output ";;..") (:no-value ";;=> ; No value") (:readable ";;=>") (:readable-continuation ";;->") (:unreadable ";;==>") (:unreadable-continuation ";;-->")))
The default syntaxes used by
transcribefor reading and writing lines containing output and values of an evaluated form.A syntax is a list of of the form
(syntax-id &rest prefixes)whereprefixesis a list of(prefix-id prefix-string)elements. For example the syntax:commented-1looks like this:(:commented-1 (:output ";..") (:no-value ";=> No value") (:readable ";=>") (:readable-continuation ";->") (:unreadable ";==>") (:unreadable-continuation ";-->"))All of the above prefixes must be defined for every syntax except for
:readable-continuation. If that's missing (as in the:defaultsyntax), then the following value is read withreadand printed withprin1(hence no need to mark up the following lines).When writing, an extra space is added automatically if the line to be prefixed is not empty. Similarly, the first space following the prefix is discarded when reading.
See
transcribefor how the actual syntax to be used is selected.
[condition] transcription-error error
Represents syntactic errors in the
sourceargument oftranscribeand also serves as the superclass oftranscription-consistency-error.
[condition] transcription-consistency-error transcription-error
A common superclass for
transcription-output-consistency-errorandtranscription-values-consistency-error.
[condition] transcription-output-consistency-error transcription-consistency-error
Signalled (with
cerror) bytranscribewhen invoked with:check-consistencyand the output of a form is not the same as what was parsed.
[condition] transcription-values-consistency-error transcription-consistency-error
Signalled (with
cerror) bytranscribewhen invoked with:check-consistencyand the values of a form are inconsistent with their parsed representation.
9.4 Transcript Consistency Checking
The main use case for consistency checking is detecting out-of-date examples in documentation, although using it for writing tests is also a possibility. Here, we focus on the former.
When a markdown code block tagged cl-transcript is processed
during Generating Documentation, the code in it is replaced with
the output of with (transcribe <code> nil :update-only t
:check-consistency t). Suppose we have the following example of the
function greet, that prints hello and returns 7.
```cl-transcript
(greet)
.. hello
=> 7
```
Now, if we change greet to print or return something else, a
transcription-consistency-error will be signalled during
documentation generation. Then we may fix the documentation or
continue from the error.
By default, comparisons of previous to current output, readable and
unreadable return values are performed with string=, equal, and
string=, respectively, which is great in the simple case.
Non-determinism aside, exact matching becomes brittle as soon as the
notoriously unportable pretty printer is used or when unreadable
objects are printed with their #<> syntax, especially when
print-unreadable-object is used with :identity t.
9.4.1 Finer-Grained Consistency Checks
To get around this problem, consistency checking of output,
readable and unreadable values can be customized individually by
supplying transcribe with a check-consistency argument
like ((:output <output-check>) (:readable
<readable-check>) (:unreadable <unreadable-check>)). In this case,
<output-check> may be nil, t, or a function designator.
If it's
nilor there is no:outputentry in the list, then the output is not checked for consistency.If it's
t, then the outputs are compared with the default,string=.If it's a function designator, then it's called with two strings and must return whether they are consistent with each other.
The case of <readable-check> and <unreadable-check> is similar.
Code blocks tagged cl-transcript can take arguments, which they
pass on to transcribe. The following shows how to check only the
output.
```cl-transcript (:check-consistency ((:output t)))
(error "Oh, no.")
.. debugger invoked on SIMPLE-ERROR:
.. Oh, no.
(make-condition 'simple-error)
==> #<SIMPLE-ERROR {1008A81533}>
9.4.2 Controlling the Dynamic Environment
The dynamic environment in which forms in the transcript are
evaluated can be controlled via the :dynenv argument of
cl-transcript.
```cl-transcript (:dynenv my-transcript)
...
```
In this case, instead of calling transcribe directly, the call will
be wrapped in a function of no arguments and passed to the function
my-transcript, which establishes the desired dynamic environment
and calls its argument. The following definition of my-transcript
simply packages up oft-used settings to transcribe.
(defun my-transcript (fn)
(let ((*transcribe-check-consistency*
'((:output my-transcript-output=)
(:readable equal)
(:unreadable nil))))
(funcall fn)))
(defun my-transcript-output= (string1 string2)
(string= (my-transcript-normalize-output string1)
(my-transcript-normalize-output string2)))
(defun my-transcript-normalize-output (string)
(squeeze-whitespace (delete-trailing-whitespace (delete-comments string))))
A more involved solution could rebind global variables set in transcripts, unintern symbols created or even create a temporary package for evaluation.
9.4.3 Utilities for Consistency Checking
[function] squeeze-whitespace string
Replace consecutive whitespace characters with a single space in
string. This is useful to undo the effects of pretty printing when building comparison functions fortranscribe.
[function] delete-trailing-whitespace string
Delete whitespace characters after the last non-whitespace character in each line in
string.
[function] delete-comments string &key (pattern ";")
For each line in
stringdelete the rest of the line after and including the first occurrence ofpattern. On changed lines, delete trailing whitespace too. This function does not parsestringas Lisp forms, hence all occurrences ofpattern(even those seemingly in string literals) are recognized as comments.Let's define a comparison function:
(defun string=/no-comments (string1 string2) (string= (delete-comments string1) (delete-comments string2)))And use it to check consistency of output:
```cl-transcript (:check-consistency ((:output string=/no-comments))) (format t "hello~%world") .. hello ; This is the first line. .. world ; This is the second line. ```Just to make sure the above example works, here it is without being quoted.
(format t "hello~%world") .. hello ; This is the first line. .. world ; This is the second line.
10 Writing Extensions
10.1 Adding New Locatives
Once everything in Adding New Locatives has been done, there are only a couple of PAX generic functions left to extend.
[generic-function] document-object* object stream
Write
objectin*format*tostream. Specialize this on a subclass ofdrefif that subclass is notresolveable, else on the type of object it resolves to. This function is for extension only. Don't call it directly.
[generic-function] exportable-reference-p package symbol locative-type locative-args
Return true iff
symbolis to be exported frompackagewhen it occurs in adefsectionin a reference withlocative-typeandlocative-args.symbolis accessible inpackage.The default method calls
exportable-locative-type-pwithlocative-typeand ignores the other arguments.By default,
sections andglossary-terms are not exported although they areexportable-locative-type-p. To export symbols naming sections from MGL-PAX, the following method could be added:(defmethod exportable-reference-p ((package (eql (find-package 'mgl-pax))) symbol (locative-type (eql 'section)) locative-args) t)
[generic-function] exportable-locative-type-p locative-type
Return true iff symbols in references with
locative-typeare to be exported by default when they occur in adefsection. The default method returnst, while the methods forsection,glossary-term,package,asdf:system,methodandincludereturnnil.This function is called by the default method of
exportable-reference-pto decide what symbolsdefsectionshall export when itsexportargument is true.
Also note that due to the Home Section logic, especially for
locative types with string names, dref-ext:docstring* should
probably return a non-nil package.
10.2 Locative Aliases
define-locative-alias can be used to help M-. and autolinking
disambiguate references based on the context of a name as described
on Parsing and also in Specified Locative.
The following example shows how to make docstrings read more naturally by defining an alias.
(defclass my-string ()
())
(defgeneric my-string (obj)
(:documentation "Convert OBJ to MY-STRING."))
;;; This version of FOO has a harder to read docstring because
;;; it needs to disambiguate the MY-STRING reference.
(defun foo (x)
"FOO takes and argument X, a [MY-STRING][class] object.")
;;; Define OBJECT as an alias for the CLASS locative.
(define-locative-alias object class)
;;; Note how no explicit link is needed anymore.
(defun foo (x)
"FOO takes an argument X, a MY-CLASS object.")
Similary, defining the indefinite articles as aliases of the class
locative can reduce the need for explicit linking.
(define-locative-alias a class)
(define-locative-alias an class)
Since these are unlikely to be universally helpful, make sure not to
export the symbols a and an.
10.3 Extending document
For all definitions that it encounters, document calls
document-object* to generate documentation. The following utilities
are for writing new document-object* methods, which emit markdown.
-
Bound by
documentto itsformatargument, this allows markdown output to depend on the output format.
[macro] with-heading (stream object title &key link-title-to) &body body
Write a markdown heading with
titletostream. Nestedwith-headings produce nested headings. If*document-link-sections*, generate anchors based on the definition ofobject.link-title-tobehaves like thelink-title-toargument ofdefsection.
[macro] documenting-reference (stream &key reference name package readtable arglist) &body body
Write
referencetostreamas described in*document-mark-up-signatures*, and establishreferenceas a local reference for the processing ofbody.referencedefaults to the reference beingdocumented.namedefaults to(xref-name reference)and is printed after thelocative-type.*package*and*readtable*are bound topackageandreadtablefor the duration of printing thearglistand the processing ofbody. If either isnil, then a default value is computed as described in Package and Readtable.If
arglistisnil, then it is not printed.If
arglistis a list, then it is must be a lambda list and is printed without the outermost parens and with the package names removed from the argument names.If
arglistis a string, then it must be valid markdown.It is not allowed to have
with-headingwithin the dynamic extent ofbody.
[macro] with-dislocated-names names &body body
For each name in
names, establish a local reference with thedislocatedlocative, which prevents autolinking.
[function] document-docstring docstring stream &key (indentation " ") exclude-first-line-p (paragraphp t)
Write
docstringtostream, sanitizing the markdown from it, performing Codification and Linking to Code, finally prefixing each line withindentation. The prefix is not added to the first line ifexclude-first-line-p. Ifparagraphp, then add a newline before and after the output.
[function] escape-markdown string &key (escape-inline t) (escape-html t) (escape-block t)
Backslash escape markdown constructs in
string.If
escape-inline, then escape*_`[]characters.If
escape-html, then escape<&characters.If
escape-block, then escape whatever is necessary to avoid starting a new markdown block (e.g. a paragraph, heading, etc).
[function] prin1-to-markdown object &key (escape-inline t) (escape-html t) (escape-block t)
Like
prin1-to-string, but bind*print-case*depending on*document-downcase-uppercase-code*and*format*, andescape-markdown.
10.4 Sections
section objects rarely need to be dissected since
defsection and document cover most needs. However, it is plausible
that one wants to subclass them and maybe redefine how they are
presented.
-
defsectionstores itsname,title,package,readtableandentriesarguments insectionobjects.
[reader] section-name section (:name)
The name of the global variable whose value is this
sectionobject.
[reader] section-package section (:package)
*package*will be bound to this package when generating documentation for this section.
[reader] section-readtable section (:readtable)
*readtable*will be bound to this when generating documentation for this section.
[reader] section-title section (:title)
A markdown string or
nil. Used in generated documentation.
- [function] section-link-title-to section
[function] section-entries section
A list of markdown docstrings and
xrefs in the order they occurred indefsection.
10.5 Glossary Terms
glossary-term objects rarely need to be dissected since
define-glossary-term and document cover most needs. However, it is
plausible that one wants to subclass them and maybe redefine how
they are presented.
[reader] glossary-term-name glossary-term (:name)
The name of the global variable whose value is this
glossary-termobject.
[reader] glossary-term-title glossary-term (:title)
A markdown string or
nil. Used in generated documentation (see Output Details).
[reader] glossary-term-url glossary-term (:url)
A string or
nil.