luv

Workshop wiki

asdf.lisp

wiki/asdf.lisp

system luv-wiki · 30 definitions · on GitHub

The wiki as an ASDF system.

ASDF is a build system for Lisp, but its model is general: components have input files, operations have outputs, and the plan is a dependency graph over (operation . component) actions. cffi-toolchain uses that generality to compile C files; here we use it for Org pages.

An org-file is a static-file component (so the ordinary compile-op is a no-op) whose LOAD-OP reads the page into a document and remembers it on the component. "Loading the wiki" therefore means having the corpus as Lisp objects in the image, ready for the figure index or an inspector.

render-op is a downward, selfward operation: rendering the system renders every page; rendering a page first loads it, and also loads the whole system, because mentions and backlinks cross pages. It writes HTML into the site directory rather than the fasl cache, so its output-files asks not to be translated. A static-file such as an image is simply copied. With :BUILD-OPERATION set, (asdf:make :luv-wiki-site) builds the site.

in-package#:luv.wiki
defclassorg-file
asdf:static-file
type:initform"org"
document:initformnil:accessororg-file-document:documentation"The DOCUMENT read by the last LOAD-OP, or NIL."
:documentation

One Org page of the wiki as an ASDF component.

defclassrender-op
:documentation

Render a wiki system, page, or asset into the static site.

defmethodasdf:selfward-operation'asdf:load-op
defgenericsite-output-directory
system
:documentation

Where render-op writes the site for system.

:method
systemasdf:system
asdf:system-relative-pathnamesystem"build/wiki/"

Loading: an Org page becomes a document.

defmethodasdf:perform
oasdf:load-op
setf
org-file-documentc
read-org-file
asdf:component-pathnamec
:name
asdf:component-namec
defmethodasdf:perform
oasdf:load-source-op
asdf:perform
asdf:make-operation'asdf:load-op
c
defunsystem-org-files
system

All org-file components of system, in system order.

let
files'
labels
walk
component
typecasecomponent
org-file
pushcomponentfiles
asdf:parent-component
mapc#'walk
asdf:component-childrencomponent
walksystem
nreversefiles
defunsystem-documents
system

The DOCUMENTs of system's loaded org files.

removenil
mapcar#'org-file-document
defparameter*code-systems*'
"luv""luvcraft""mcluv""luft""luv-wiki""luv-wiki-site"

Primary names of the systems whose source files the site reads for definitions and figure mentions. luv-wiki is among them so the site shows how it renders itself; its tests fabricate figure IDs, which is why dangling-code-mentions passes over test systems.

defuncode-source-components

Alist of (pathname . system-name) for the cl-source-file components of every registered system whose primary name is in *CODE-SYSTEMS*, without loading anything.

let
files'
dolist
name
asdf:registered-systems
when
member
asdf:primary-system-namename
*code-systems*:test#'string=
labels
walk
component
typecasecomponent
asdf:cl-source-file
let
pathname
asdf:component-pathnamecomponent
unless
assocpathnamefiles:test#'equal
push
conspathnamename
files
asdf:parent-component
mapc#'walk
asdf:component-childrencomponent
walk
asdf:find-systemname
sortfiles#'string<:key
lambda
entry
namestring
carentry
defclasssystem-entry
name:initarg:name:accessorsystem-entry-name
description:initarg:description:initformnil:accessorsystem-entry-description
depends-on:initarg:depends-on:initform'
:accessorsystem-entry-depends-on:documentation"Names of the code systems this one depends on."
files:initarg:files:initform'
:accessorsystem-entry-files:documentation"SOURCE-FILEs of this system's components."
:documentation

One of the code systems, for the source index.

defuncode-systems
source-files

SYSTEM-ENTRYs for the registered code systems, in dependency order: a system comes after everything it depends on, ties broken by name, and each source-file of source-files attached to its system.

let*
names
remove-if-not
lambda
n
member
asdf:primary-system-namen
*code-systems*:test#'string=
asdf:registered-systems
entries
mapcar
lambda
name
let
system
asdf:registered-systemname
make-instance'system-entry:namename:description
asdf:system-descriptionsystem
:depends-on
sort
remove-if-not
lambda
d
memberdnames:test#'string=
mapcar
lambda
d
string-downcase
if
conspd
d
asdf:system-depends-onsystem
#'string<
:files
removenamesource-files:key#'source-file-system-name:test-not#'string=

Alphabetical, but test systems after the rest so the topological pass places them behind their subjects.

sort
copy-listnames
lambda
ab
let
ta
and
search"/test"a
t
tb
and
search"/test"b
t
if
eqtatb
string<ab
tb
ordered'

Topological order: repeatedly take the first entry whose dependencies are all placed.

loopwhileentriesdo
let
next
or
find-if
lambda
e
every
lambda
d
finddordered:key#'system-entry-name:test#'string=
system-entry-depends-one
entries
firstentries
pushnextordered
setfentries
removenextentries
nreverseordered
defunarglists-file
system

The introspected operator lambda lists the renderer reads, if present.

let
pathname
asdf:system-relative-pathnamesystem"wiki/arglists.sexp"
and
probe-filepathname
listpathname
defvar*source-cache*
make-hash-table:test'equal

Pathname namestring -> (write-date . source-file), so an unchanged file is not read again within one image.

defuncode-sources
&key
root
uiop:getcwd

The SOURCE-FILEs of the code systems, reading a file only when it changed.

loopfor
pathname.system-name
inforkey=
namestringpathname
fordate=
file-write-datepathname
forcached=
gethashkey*source-cache*
collect
if
andcached
eql
carcached
date
cdrcached
let
file
read-source-filepathname:relative-path
namestring
uiop:enough-pathnamepathnameroot
:system-namesystem-name
setf
gethashkey*source-cache*
consdatefile
file
defuncode-definitions

The DEFINITIONs of all code source files.

loopforfileinappend
source-file-definitionsfile
defunsystem-site
system
let
root
asdf:system-source-directorysystem
load-arglists
merge-pathnames"wiki/arglists.sexp"root
let
sources
make-site:source-filessources:systems:source-directoryroot

Rendering

defmethodasdf:component-depends-on

A page's rendering depends on the whole system being loaded, since mentions and backlinks resolve across pages.

`
asdf:load-op,
asdf:component-systemc
,@
call-next-method
defmethodasdf:input-files

Every page is an input to every page: a new figure or mention anywhere can change the links and backlinks rendered here; so is every source file, whose definitions may reference this page's figures.

append
mapcar#'asdf:component-pathname
system-org-files
asdf:component-systemc
arglists-file
asdf:component-systemc
defmethodasdf:output-files
values
list
merge-pathnames
make-pathname:name
asdf:component-namec
:type"html"
site-output-directory
asdf:component-systemc
t
defmethodasdf:perform
let*
system
asdf:component-systemc
document
org-file-documentc
output
asdf:output-fileoc
defmethodasdf:input-files
casdf:static-file
list
asdf:component-pathnamec
defmethodasdf:output-files
casdf:static-file

An asset keeps its path relative to its module, so wiki/images/x.png lands at images/x.png in the site.

values
list
merge-pathnames
uiop:enough-pathname
asdf:component-pathnamec
asdf:component-pathname
asdf:component-parentc
site-output-directory
asdf:component-systemc
t
defmethodasdf:perform
casdf:static-file
let
output
asdf:output-fileoc
ensure-directories-existoutput
uiop:copy-file
asdf:component-pathnamec
output

The system itself contributes the figures index.

defunstyle-source-files

The Lisp files the stylesheet is compiled from, so editing a style re-renders the site.

mapcar
lambda
name
asdf:system-relative-pathname:luv-wikiname
'
"wiki/css.lisp""wiki/style.lisp"
defmethodasdf:input-files
sasdf:system
defmethodasdf:output-files
sasdf:system

The stylesheet, the figures index, the source index, and one page per source file.

let
root
asdf:system-source-directorys
values
list*
merge-pathnames"style.css"directory
merge-pathnames"pages.html"directory
merge-pathnames"work.html"directory
merge-pathnames"source.html"directory
loopforpathnameincollect
merge-pathnames
concatenate'string"source/"
namestring
uiop:enough-pathnamepathnameroot
".html"
directory
t
defmethodasdf:perform
sasdf:system
let
let
write-html-file
merge-pathnames"pages.html"directory
write-html-file
merge-pathnames"work.html"directory
write-html-file
merge-pathnames"source.html"directory
dolist
file
site-source-filessite
write-html-file
merge-pathnamesdirectory
let
whendangling
warn"Dangling figure mentions:~{~% ~A: ~{~A~^ ~}~}"
loopforindanglingcollect
document-namedocument
collectids
let
whendangling
warn"Dangling figure mentions in code:~{~% ~A: ~{~A~^ ~}~}"
loopforindanglingcollect
formatnil"~A ~A (~A:~D)"
definition-kinddefinition
definition-namedefinition
definition-linedefinition
collectids

A defsystem form names these classes as strings that ASDF reads in its own package, e.g. :default-component-class "luv.wiki:org-file" and :build-operation "luv.wiki:render-op", so we need not intern anything into the ASDF package.