Select Git revision
ssg.texi 11.38 KiB
\input texinfo
@settitle Static Site Generator
@copying
Copyright @copyright{} 2022 Hugo Hörnquist
@end copying
@dircategory Individual utilities
@direntry
* SSG: (ssg). Static site generator
@end direntry
@titlepage
@title Static Site Generator
@author Hugo Hörnquist
@page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@contents
@ifnottex
@node Top
@top SSG
@end ifnottex
@node User Manual
@chapter User manual
This part of the manual is for ``end users'' of the application. It
documents general concepts which are useful to know to best utilize
the software, along with how to configure a page properly.
@node Concepts
@section Concepts
@subsection Sites
@cindex site
@cindex sites
(The source code) of a complete website, represented by a directory
containing (at least) a config file, a templates directory, and a
pages directory.
This directory will be added to guile's load-path during building.
@subsection Widgets
@cindex widget
@cindex widgets
A procedure which expands into a fragment of the site.
A simple widget could for example be @code{[[copyright]]}, which
expands into the copyright symbol @copyright{}
So
@example
<div>[[copyright]] Hugo Hörnquist</div>
@end example
Would expand into
@example
<div>@copyright{} Hugo Hörnquist</div>
@end example
@subsection Templates
@cindex templates
@cindex template
HTML-fragments, really similar to pages
@subsection Entries
@cindex entry
@cindex entries
News entries. Treated separately from regular pages since a unique
page needs to be generated for each, but they also need to be
propagated in feeds.
@subsection Pages
@cindex page
@cindex pages
Regular webpages
@node Site Structure
@section Site Structure
@table @asis
@item config
General (static) configuration file for site. Contains items such as
global title, and default template.
@item entries
@cindex entrie
A flat directory of all (blog) entries on the page. File names don't
matter here.
@item pages.scm
@cindex pages
@cindex pages.scm
Dynamic configuration file for pages which are too dynamic to go
through the general page-template system (see below).
@item pages
@cindex pages
``Static'' pages which should be run through all processing
steps. Each file maps directly to a file in the output directory, and
local directory structure will be preserved.
@item static
@cindex static
Files which should be copied verbatim to the output
directory. Directory structure will be preserved.
@item templates
@cindex templates
Template files usable by ``pages''
Should (probably) be a HTML file, which SHALL NOT have any headers. It
can however use any widgets, and SHOULD include the widgets
@code{page-title} and @code{page-body} at appropriate places.
@item widgets
@cindex widgets
Guile modules for introducing extra modules.
@item assets
@cindex assets
Files used by some widgets. Really similar to static, but has an
override hierarchy.
Each widget should document which assets it uses.
@end table
@node Widgets
@section Widgets
All entries @emph{must} return sxml
Each widget @emph{must} be defined in its own module, placed in
@code{(widget @var{widget-name})}. The module @emph{must} export the
binding @code{widget}.
If a piece of HTML needs to be return verbatim (which for example is
used in the text/html content type handler, since we then don't need
to parse it) can be done by wrapping the output in a procedure which
displays the string to @code{(current-output-port)}. For example
@example
(xml->sxml `(div (lambda () (display "<span>Test</span>"))))
@end example
Would produce
@example
<div><span>Test</span></div>
@end example
@subsection Widget calling conventions
[[widget-name arguments go here]]
@node Built in Widgets
@subsection Built in Widgets
@c TODO describe how to find built in widgets
@node Building your own Widgets
@subsection Building your own Widgets
Create a module @code{(widget @var{widget-name})}, which @emph{must}
export @code{widget}, and @emph{should} export @code{doc}.
@subsubsection @code{widget}
@c TODO input
and outputs an SXML HTML fragment
@c TODO can widgets include other widgets?
@subsubsection @code{doc}
A string, which @emph{must} be valid Texinfo, and should not include a heading.
@node Content Types
@section Content Types
Each content type should be in its own moudle, named
@code{(content-type @var{content-type})}, for example
@code{(content-type text html)} for ``text/html''.
Each content-type module @emph{must} export @code{body->html}, which
should take a string of the expected content type, a ``sxml'' tree
mapping to HTML, but which also can contain @code{*WIDGET*} nodes.
The content-type's widget generator should output
@example
`(*WIDGET* ,string)
@end example
The central widget processor the parses @var{string} as a space
delimited list of either numbers, ``key=number'' pairs, ``key=string''
pairs, or plain strings. @var{string} @emph{must} start with the
widgets name
@example
"key=value 10 k2=9 hello"
@end example
Would be parsed as
@example
'(#:key "value"
10
#:k2 9
"hello")
@end example
@c TODO by whom, and how, are widgets handled?
@subsection text/html
@subsection text/plain
@subsection application/x-texinfo
@c https://www.sitepoint.com/mime-types-complete-list/
@subsection text/vimwiki
@node Data flow
@section Data Flow
@subsection Config is loaded
The config file is loaded, and basic validation is done (such as
checking if the site is tested for this version of the software).
The site directory is also added to the load path.
@c TODO possibly change this to @em{site}/module
@subsection All pages are added to the file set
Each input file in @code{entries/}, @code{static/}, @code{pages/},
and @code{pages.scm} is one by one added to the file set.
At this stage, page content (to be parsed), and templates are loaded
from disk and built into an AST, each found widget is initialized (but
@emph{not} realized. Pages can't directly reference other pages, while
widgets can.
The file set should be considered read only after this stage.
@subsection Widgets are realized
Each widget is realized. Widgets can reference the complete file set,
since this stage happens after the completion of the file set.
The actuall realization should happen inside the producing procedures
for pages, meaning that after this is done all pages exists and the
whole process is done.
@node Function Reference
@chapter Function Reference
@node file-set
@section file-set
A file set is a collection of filenames and procedures for generating
the contents of those files. Most of SSG works by inserting entries
into a file set, and then realizing it (see below) as a final step.
@deftp {Scheme Record} file-set
@defun make-file-set
Return a fresh file-set object.
@end defun
@defun file-set? x
Is the given object a file set?
@end defun
@end deftp
@defun file-set-add! file-set path file-producer [#:cookie=(random (expt 2 64))] [#:on-collision <procedure (path, cookie) -> '()]
Add an entry to the file set.
@c alias FilePath = String
@c file-set-add! :: FilePath, (Promise (FilePath → IO ())) → State FileSet
@var{path} is a path relative the file sets root. The files parents
will automatically be created in the output set.
@var{file-producer} should be a promise returning a procedure which
takes a destination inside the target directory tree. It should create
that file, with whatever content it sees fit.
The procedure is forced after the collision check is done.
@var{on-collision} is a procedure which is called if a file with
@var{path} already exists in the file set. It's called with the path,
along with the cookie which was supplied when the other item was added
to the set, along with the cookie for the item currently being
added. The default handler prints a warning and overrides the
target. The cookie is to allow an idempotent producer to be invoked
multiple times, while still failing on collisions.
In example (1) below a warning would be emitted every time the widget
was used. Example (2) solves this by setting a cookie. However, if
another module tries to override our logo (example (3)) a warning
would still be emitted. A pre-generated UUID is recomended as a cookie.
@footnote{Note that cookies don't actually give security against
malicious content, since the attacker could just look at your source
and ``borrow'' your cookie. It does however solve unintentional conflicts.}
@example
;; our-widget.scm
(file-set-add! fs "logo.png" produce-logo) ;; (1)
(file-set-add! fs "logo.png" produce-logo #:cookie "RANDOM_STRING") ;; (2)
;; evil-widget.scm
(file-set-add! fs "logo.png" produce-logo #:cookie "ANOTHER_COOKIE") ;; (3)
@end example
@var{cookie} The cookie as described for @var{on-collision}
(above). Can be anything which @code{equal?} works sensible for.
@end defun
@defun file-set-keys file-set
Return all registered files in the file set.
@end defun
@defun realize-file-set file-set destination
Creates all files in @var{file-set} under directory @var{destination}.
@end defun
@node lib
@section lib
@defun accumulate proc default lst
Builds a list by applying @var{proc} to the "current" element of
@var{lst}, along with the result of the last application of
@var{proc}. Seeded with the first element of @var{lst}.
Return @var{default} if @var{lst} is empty.
@end defun
@defun flip f
Flip returns a new procedure, which takes the same number of arguments
as @var{f}, but applies the arguments reversed.
@end defun
@defun read-key-value port
Read HTTP-like key-value pairs from port until a blank line, or end of
file.
HTTP-like here meaning a free-text key (excluding colon and space),
followed by a colon, and then any text until end of line.
Leading whitespace is trimmed from both key and value. No whitespace
may be between the key and the colon.
@end defun
@defun read-entry filename
Read a blog entry from the file pointed at by @var{filename}.
Either returns two values: the key-value headers as read by
@code{read-key-value}, and the body. Or just the body if the file
didn't start with headers.
@c Headers are detected by checking if the first line contains a colon.
@c Headers end on the first blank line.
@end defun
@defun parser-for content-type
Returns a procedure which formats an entry body into HTML.
@var{content-type} should either be a string on the form
of a MIME type, containing a type and a subtime delimited by a slash
(such as ``text/html''), or @code{#f} to default to ``text/plain''.
This string is re-interpreted as a list of symbols, and looked up in
@code{`(content-type ,@@symbols)}
@end defun
@defun link-or-copy from to
Utility function which first attempts to create @var{to} as a hard
link to @var{from}, and if that fails copies the data instead.
@end defun
@defun symlink-if-not-exists from to
Creates a symlink at @var{to} containing @var{from}. If @var{to}
already is a symlink pointing to @var{from} then do nothing, otherwise
throw the same error as @code{symlink} would have done.
@end defun
@node mkdir
@section mkdir
@defun mkdir-p path
Creates @var{path}, and all its parents. If the destination already
exists nothing is node. See also the -P flag of mkdir(1).
@end defun
@node ssg
@section ssg
@defun generate-site page-dir output-dir
Primary procedure of entire library. Reads a page from @var{page-dir},
and publishes it to @var{output-dir}.
@pxref{Site Structure}
@end defun
@defvr {Parameter} current-page
Will be set to the local path of the target file when realizing
templates (and widgets).
@end defvr
@node Index
@unnumbered Index
@printindex cp
@printindex fn
@printindex ky
@printindex pg
@printindex tp
@printindex vr
@bye