chai2
boring static site generator
chai2 can generate simple, paginated image galleries. It allows tagging images, and generates sub-sites for all tags. It's not really opinionated, and easily extensible. Without source code hacking, it allows for quite a lot of customization in the configuration file. For the lack of a better choice, the configuration language is the same as implementation language (it's not yaml thank you for asking though).
design choices
There's not much one can write about the design choices of 200 lines of code, yet I'll try.
chai2 tries to be as much out of the way as possible. After initial configuration,
adding images is as simple as executing the generator with source image and it's tags as arguments.
It tries to mimic prahou's imgram
in what it gives to the user by default. I think the design of imgram is good. As in, having the same script
be the interface to adding images and the actual site generator is great. I've adapted the idea to my needs
(sexp configuration file, more lambdas, more intellectual masturbation with combinators - you name it!).
The only thing that left me uneasy looking at how imgram works was how data was stored. As far as i can tell,
pre-generated HTML pieces are stored in a data directory. This is probably done for speed,
and because the korn shell (or any login shell people really use) is not good at reading/writing
structured data such as posts ↔ tags relations. I'm not willing to get proven wrong.
I know it's possible and probably also very ugly.
The database solution I've decided to pick is a fasl-encoded association list storing key-value pairs of file-id → metadata. metadata is an association list with keys including, but not limited to:
- filename: A string holding the value of image's file name on the server.
- filename-minimized: A string holding the name of the minimized file. This is shown as the picture's thumbnail.
- timestamp: An integer holding the posix timestamp of a given file.
- tags: A list of symbols holding tags of a given file.
I've decided to store the database as a fasl-encoded association list, and not a finite function so it would be easier to read it with other software. Because a virtual machine is not needed to unpack fasl encoded lists, it's much easier to interpret a database that uses alists instead of finite functions.
configuration
By default, configuration is stored in chai.cfg.
This configuration file should contain a single (define-config ...)
expression. The format is as follows:
(define-config
key1 => value1
key2 => value2
...)
Every value after the `=>' token will be evaluated in the *toplevel* as an Owl Lisp expression. The values may be of any lisp type. Most config keys have sane defaults. Some config keys require Owl functions as values, they definitely have defaults. Values you can set in the configuration file are as follows:
| key | description |
|---|---|
| id-length | How long a picture's randomly generated id will be. A number, preferably. |
| database | Location of the database. Relative to PWD. A string, preferably. |
| www-root | Location of the public WWW root. Relative to PWD. A string, preferably. |
| host | Your webpage's hostname with the protocol. This will be used while generating rss. Examples include "https://example.com", "http://i-dislike-tls.net/here/are/images". A string, preferably. |
| items-per-page | How many pictures should a single page hold. A number, preferably. |
| rss-description | Description of the RSS feed. A string, preferably. |
| css | Linked css file. This will be inserted to the generated page as a <link> element, so it should be relative to www-root. Note that tag directories will link this file as well, so it should be an absolute path, for example "/styles.css", or "/static/something.css". A string, preferably. |
| rss | Linked rss file that will be generated every rebuild. Relative to www-root. Should follow same guidelines as css. A string, preferably. |
| name | Webpage's name. A string, preferably. |
| default-tags | List of default tags that will be set, when an image is not given any tags. Preferably a list of symbols. |
| display-time-function | A function of posix timestamp -> robusta HTML expression. This should render the posix timestamp in a desired format. |
| html-heading | A function of title -> robusta HTML expression. This should render the title in a desired format. |
| minimize-image-function | Function of filename -> filename. This function may minimize a file filename and return the new file name. It should not remove the old file. The new (returned) filename will be served as the thumbnail. |
REPL
chai2 includes a REPL used for editing the database. You can enter the
REPL by using the -e or --edit flag. You will be greeted by a very
non-user-friendly prompt:
?
This is the toplevel, it accepts commands as follows:
- help: shows available commands
- list: lists all images
- tags: lists all tags
- tag tag-name: lists all images under tag `tag-name'
- rebuild: same as
chai --rebuild - pick id: selects image with id id for editing and enters edit mode
- quit: exits
The edit mode queues edits for applying with the save command. If you choose not to commit the changes, just use the quit command. The remove command is not queued, but it does ask for confirmation. All commands accepted by the edit mode:
- help: shows available commands
- set-tags tag1 tag2 ...: sets the tag list to
(tag1 tag2 ...) - tags: lists tags of currently picked image
- set-timestamp timeval: sets the timestamp of picked image to
timeval - timestamp: prints timestamp of picked image
- remove: removes the currently picked image from the db
- save: saves changes and quits
- quit: quits without saving changes
Note that the REPL will not remove image files from the disk. It only
operates on the database. Images removed from the database will still be
accessible, just not listed.
chai2 will not rebuild your website automatically after applying changes
to the database. Use the -r, --rebuild flag or the rebuild command
to rebuild it manually.
running chai2
Get the latest source code from github
; git clone https://github.com/krzysckh/chai2.git
; cd chai2
After running make, possibly after configuring OL to your location of
Owl Lisp, it will spit out a chai binary in current working directory.
Save it somewhere safe. Good places include
- right next to your gallery
- /usr/local/bin
Note that chai2 loads the configuration file on demand; every run.
It may be specified with the -c, or --config-file flag.
To add an image to your site, run
chai image-filename.extension tag1 tag2 .... Note a single image can
use many tags as well as none. If no tags are specified, chai2 will
automatically set them to default-tags defined in the config.
By default, this is (unknown) (- a list of single tag, called unknown).
To rebuild the entire page, without adding additional images,
run chai with the -r, or --rebuild flag.
This will ensure all pages are up to date with the database.
This should not be needed unless you touch the database with external software.
bonus content
touching the database with external software
unrambling the session was left as an exercise to the reader
fuji# ol
You see a prompt.
> (define db (fasl-load "chai.db" #f))
;; Defined db
> (define img (assoc 'jylqqggm db))
;; Defined img
> img
'(jylqqggm (filename . "jylqqggm.jpg")
(filename-minimized . "jylqqggm.jpg-small.jpg")
(timestamp . 1785152592) (tags tatry))
> (define img* (led img 4 (C append '(panopticon))))
;; Defined img*
> img*
'(jylqqggm (filename . "jylqqggm.jpg")
(filename-minimized . "jylqqggm.jpg-small.jpg")
(timestamp . 1785152592) (tags tatry panopticon))
> (define db* (filter (B not (B (C eq? 'jylqqggm) car)) db))
;; Defined db*
> (define db** (cons img* db*))
;; Defined db**
> (fasl-save db** "chai.db")
#true
> ,quit
fuji#
touching the database with the REPL
fuji# ./chai -e
? tag raj
[yehzzdym] (slovensky raj) https://img.krzysckh.org/yehzzdym.jpg-small.jpg
[uifnirjm] (slovensky raj) https://img.krzysckh.org/uifnirjm.jpg-small.jpg
[nuvpndom] (slovensky raj) https://img.krzysckh.org/nuvpndom.jpg-small.jpg
[uglevdkm] (slovensky raj) https://img.krzysckh.org/uglevdkm.jpg-small.jpg
[bzzduqlm] (slovensky raj) https://img.krzysckh.org/bzzduqlm.jpg-small.jpg
[mksozrym] (slovensky raj) https://img.krzysckh.org/mksozrym.jpg-small.jpg
[utnexfjm] (slovensky raj) https://img.krzysckh.org/utnexfjm.jpg-small.jpg
? pick bzzduqlm
bzzduqlm? tags
(slovensky raj)
bzzduqlm? set-tags slovensky raj panopticon
ok
bzzduqlm? tags
(slovensky raj panopticon)
bzzduqlm? save
saved
? rebuild
? quit
bye
fuji#
configuration for img.krzysckh.org
(define-config
host => "https://img.krzysckh.org"
name => "img.krzysckh.org"
rss-description => "from img.krzysckh.org"
www-root => "public/"
id-length => 8
database => "chai.db"
rss => "/images.rss"
css => "/style.css"
items-per-page => 20
default-tags => '(panopticon)
minimize-image-function => (λ (image)
(lets ((new-name (str image "-small.jpg")))
(system (list "exiftool" "-GPS:all=" "-overwrite_original" image))
(system (list "convert" (str image) "-resize" "640" new-name))
new-name))
display-time-function => (λ (timestamp)
`((div (class . "timestamp")) (h4 ,timestamp)))
html-heading => (λ (tag-name)
`(h1 ((a (href . "/")) "/") " " ,tag-name))
)
a screenshot
llms were not used. spelling mistakes are my own.
- kpm