about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--COPYING8
-rw-r--r--README.md276
2 files changed, 284 insertions, 0 deletions
diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3be757f --- /dev/null +++ b/COPYING
@@ -0,0 +1,8 @@
1Copyright (C) 2023 Case Duckworth <acdw@acdw.net>
2
3Everyone is permitted to do whatever with this software, without
4limitation. This software comes without any warranty whatsoever,
5but with two pieces of advice:
6
7- Don't hurt yourself.
8- Make good choices.
diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e53c51 --- /dev/null +++ b/README.md
@@ -0,0 +1,276 @@
1# vienna
2## a tiny, tasty ssg
3
4`Vienna` is my (current) Platonic Ideal of a simple and extensible static site
5generator. I've written a couple of these over the years, and I think I've
6finally got something that I really like. Anyway, lemme tell you how it works.
7
8## `vienna` invocation
9
10`vienna` has a similar command-line interface to `make`; that is, it runs
11commands on the files in a given folder and produces output. It has
12command-line switches as well as subcommands. By default, `vienna` simply
13builds the pages in the current directory into the output directory.
14
15### command-line arguments
16
17- `-r URL`: Use URL as the root url for the site. The default is the useless
18 https://example.com/, since you really should set this either on the command
19 line, in the config file, or in an environment variable.
20- `-C DIRECTORY`: Build the site from pages in DIRECTORY, instead of the current
21 directory.
22- `-o DIRECTORY`: Build the site to DIRECTORY, instead of the default `out`.
23- `-c FILE`: Use FILE as the configuration file, instead of `.vienna.sh`.
24- `-h`: Show a usage note and exit.
25- `-q`: Disable any logging.
26
27### subcommands
28
29- `init`: Initialize a site by creating a `.vienna.sh` in the site directory.
30- `clean`: Delete the output directory and temporary files before building.
31- `preview`: Preview the website on your local machine. By default, `vienna`
32 doesn't define a `preview` function, so you'll want to write your own. I've
33 included one in the `.vienna.sh` made with `vienna init` to get you started.
34- `publish`: Publish the website to your production server. By default,
35 `vienna` doesn't define a `publish` function, though I do include a sample in
36 the default `.vienna.sh` made with `vienna init`.
37
38## file layout
39
40Pretty much any SSG is a mapping from input files to output files, with various
41amounts of processing along the way. Usually, the input files are in some
42text-ish format like markdown, the output files are in html, and processing
43includes templating, static file copying, and other such mixins.
44
45`vienna` is similar, except it uses a mostly-html input language and uses POSIX
46shell as its templating and extension language. I chose these because they
47require the smallest number of dependencies, and because shell scripting is fun!
48[citation required]
49
50But more about the markup later. Let's talk about the structure `vienna`
51expects your files to be in:
52
53```
54/
55/some-page.htm
56/some-other-page.htm
57[...]
58/.vienna.sh
59/.page.tmpl.htm
60/.index.tmpl.htm
61/.feed.tmpl.htm
62/.plugins/[...]
63```
64
65The content files of your site are all of the ones *not* beginning with a dot,
66while all of `vienna`'s are the ones that *are*. In UNIX-like environments,
67this makes them "hidden," so you can focus on your content by default. However,
68your content is not something I can talk about in this README, because you still
69have to write it! Let's talk about `vienna`'s files next.
70
71### .vienna.sh
72
73`vienna` is a POSIX shell script. Before it builds any pages, it reads a
74configuration file. By default, this file is `.vienna.sh` in your site folder,
75but you can make it anything you want by passing `-c <config>` on the command
76line or by setting the `$VIENNA_CONFIG` environment variable (more on the
77command line and environment variables below).
78
79Because the configuration file is written in the same language as `vienna`
80itself, you can redefine any variable *or function* that's in `vienna`, fully
81customizing your experience. The main knobs you'll want to turn include
82
83#### variables
84
85- `$DOMAIN`: The domain (and, well, protocol) of your published website. Used
86when building indexes and feeds. *Default: `https://www.example.com`*
87- `$OUTDIR`: The output directory where `vienna` will put the built site.
88*Default: `out`*
89- `$PLUGINDIR`: The directory where plugins can be found and sourced by
90`vienna`. *Default: `.plugins`*
91- `$PAGE_TEMPLATE`: The template to use for pages. *Default: `.page.tmpl.htm`*
92- `$INDEX_TEMPLATE`: The template to use for an index. *Default:
93 `.index.tmpl.htm`*
94- `$FEED_TEMPLATE`: The template to use for an RSS feed. *Default:
95 `.feed.tmpl.htm`*
96
97#### functions
98
99- `publish`: How to publish a finished site. By default, this function does
100nothing. `vienna init` defines a basic `publish` function in `.vienna.sh`.
101- `preview`: How to preview a site locally. By default, this function does
102nothing. `vienna init` defines a basic `preview` function in `.vienna.sh`.
103- `sort_items`: How to sort items when generating a list. By default, don't
104sort files at all.
105- `index_item`: How to build each item in an index. The default outputs `<li><a
106href=[LINK]>TITLE</a></li>`, where TITLE is the page's title and LINK is a link
107to the published page.
108- `feed_item`: How to build each item in a feed. The default outputs an RSS
109`<item>` tag suitable for inclusion in an RSS 2.0 feed.
110- `filters`: UNIX pipes that each page is run through as it's being built. The
111default just uses `phtml`, but in my personal site I also pipe it through
112`expand` so I can use shell expansion as well.
113
114### templates
115
116- `.page.tmpl.htm`: The template for individual pages.
117- `.index.tmpl.htm`: The template for the root index.html.
118- `.feed.tmpl.htm`: The template for the RSS feed.
119
120### plugins
121
122- `.plugins/*.sh`: Files matching this pattern will be sourced before building
123the site. They can define new functions, export variables, or do whatever, so
124make sure you know what's in these files!
125
126## content
127
128Now for the stuff *you're* in charge of: your content. `vienna` takes a flat-
129or no-hierarchy approach to sites. Your site will just be one flat folder full
130of pages, though each page will be its own folder with an `index.html` for nicer
131urls. This might not be for you, and that's okay! You can change it
132(complicated) or use another ssg (probably easier). It's up to you.
133
134### pages
135
136By default, files matching the pattern `*.htm` will be processed by `vienna` and
137turned into finished pages. `.htm` was chosen because it'll be picked up as
138html in most text editors (for the format they're written in, see the **phtml**
139section below), but it's also *unfinished html* (get it?!).
140
141You can change this&mdash;you can change anything with `vienna`&mdash;but I'll
142leave that as an exercise for you to figure out.
143
144### static files
145
146Every other non-hidden file in the `vienna` folder will be copied as-is to the
147output folder.
148
149## `phtml`: *pretty much* html
150
151While lightweight markup languages like Markdown are nice (I'm using it to write
152this README, for example), I realized that for my blogging needs it's not really
153necessary. html is *pretty much* good enough for authoring, if I'm being
154honest&mdash;it's just a little too verbose for fully-fluent drafting.
155
156Thus, `phtml` was born. It's a function in the `vienna` source that consists of
157one sed call:
158
159```
160sed -E '
161 /./ {H;$!d}; x # Hold lines til empty, then exchange to pattern
162 s#^[ \n\t]+([^<].*)#\1# # Replace non-HTML paragraph with itself
163 t par; b end # If successful, branch to :par; else :end
164 :par
165 s#([^\\])&#\1\&amp;#g # Replace & with &amp;
166 s#([^\\])<#\1\&lt;#g # Replace < with &lt;
167 s#([^\\])>#\1\&gt;#g # Replace > with &gt;
168 s#\\([&<>])#\1#g # Replace \-escaped &,<,> with raw
169 s#.*#<p>&</p># # Wrap the pattern space with <p> tags
170 :end # [:par falls through to :end]
171 s#^[ \n\t]+## # Remove leading whitespace
172 $!a # Add a final newline unless last line
173'
174```
175
176To clarify the comments above: `phtml` allows a writer to leave out `<p>` tags,
177which I consider are the most annoying parts of html. Paragraphs that don't
178begin with `<` are wrapped in `<p>` tags, and html reserved characters `<`, `>`,
179and `&` are turned into entities.
180
181You can still write html, of course&mdash;either by backslash-escaping the
182reserved characters or by starting a paragraph with an html tag (really, the
183character `<`). Those paragraphs are passed through unprocessed.
184
185This rule may seem as though it negates the benefits of leaving out `<p>` tags.
186*After all*, you might think, *if I want to add a link or even emphasize text,
187I'll have to escape the tags or wrap the whole thing in html!* While you'd be
188right about that, I use plaintext paragraphs enough that it's worth it.n
189
190## `expand`: templating with here-docs
191
192Here-docs are some of the most useful structures in any programming language,
193and in shell they can be especially powerful. I first came across using
194here-docs for templating on some Github repo I've since lost, but there are many
195other projects that do something similar. `expand` is pretty minimal, and uses
196here-docs and a little bit of escaping to provide lots of expressive power.
197
198A template that looks like this:
199
200```
201Donuts now cost $$${CURRENT_DONUT_COST}, a $$(donut_perc_delta)%
202$$(if [ $$(donut_perc_delta) -ge 0 ]
203then echo increase
204else echo decrease
205) from last week.
206```
207
208will turn into this, given that donuts currently cost $1.99 and $1.89 last week
209(`donut_perc_delta` truncates percentages to two digits):
210
211```
212Donuts now cost $1.99, a 5%
213increase
214from last week.
215```
216
217Single `$` and <code>\`</code> characters are escaped with backslashes so they
218won't be expanded by the shell. Two `$$` are converted to one `$`, and three
219`$$$` in a row are converted to `\$$$`, since usually you'll want the second
220pair of dollar signs to introduce a variable or function, not the first pair.
221
222Other than that, `expand` simply `eval`s the templates given on the command line
223as here-docs. Pages to be templated are passed into the function's standard
224input, and everything just works. It's pretty cool!
225
226### page metadata
227
228`vienna` also supports colon-separated metadata in html comments in source
229files. I put something like the following at the tops of my pages:
230
231```
232<!--
233 title: My really cool page
234 date: 2023-01-01
235-->
236```
237
238`vienna` processes these as simple key-value pairs, accessible with the `meta`
239function. So in templates, you can access the title with `$$(meta title)`, or
240the value of a key called `foo` with `$$(meta foo)`.
241
242`vienna` provides convenience functions `title` for the page `title` and
243`pubdate` for its `date`.
244
245## tweaking the behavior of phtml and expand (without re-writing the functions)
246
247Of course, you can always rewrite `phtml` or `expand` to suit your needs, or
248rewrite `filters` to use other processers like `markdown`, `asciidoc`, or
249whatever you desire. However, for the "vanilla" experience, `vienna` includes a
250variable that tweaks the behavior of `phtml`: `$PHTML_OPTIONS`.
251
252`$PHTML_OPTIONS` can be one or more of the following values, separated by
253spaces:
254
255- `expand`: Run `expand` on the content after it's processed by `phtml`.
256- `entities`: Convert `&`, `<`, and `>` into html entities.
257
258The default is `expand entities`.
259
260## using plugins
261
262`vienna` also supports the use of plugins, which are shell scripts sourced
263before building the site. Plugins are placed in `$VIENNA_PLUGINDIR`, or
264`.plugins` by default, and must end in `.sh` to be sourced.
265
266You can see the `plugins` directory of this repo for some example plugins that I
267found useful.
268
269## contributing
270
271Comments, bug reports, and merge requests are welcome! [Send me an
272email](mailto:vienna@code.acdw.net) or [@ me on Mastodon](https://tilde.zone/@acdw).
273
274## license
275
276`vienna` is licensed under the Good Choices License. See COPYING for details.