diff options
-rw-r--r-- | COPYING | 8 | ||||
-rw-r--r-- | README.md | 276 |
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 @@ | |||
1 | Copyright (C) 2023 Case Duckworth <acdw@acdw.net> | ||
2 | |||
3 | Everyone is permitted to do whatever with this software, without | ||
4 | limitation. This software comes without any warranty whatsoever, | ||
5 | but 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 | ||
5 | generator. I've written a couple of these over the years, and I think I've | ||
6 | finally 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 | ||
11 | commands on the files in a given folder and produces output. It has | ||
12 | command-line switches as well as subcommands. By default, `vienna` simply | ||
13 | builds 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 | |||
40 | Pretty much any SSG is a mapping from input files to output files, with various | ||
41 | amounts of processing along the way. Usually, the input files are in some | ||
42 | text-ish format like markdown, the output files are in html, and processing | ||
43 | includes templating, static file copying, and other such mixins. | ||
44 | |||
45 | `vienna` is similar, except it uses a mostly-html input language and uses POSIX | ||
46 | shell as its templating and extension language. I chose these because they | ||
47 | require the smallest number of dependencies, and because shell scripting is fun! | ||
48 | [citation required] | ||
49 | |||
50 | But more about the markup later. Let's talk about the structure `vienna` | ||
51 | expects 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 | |||
65 | The content files of your site are all of the ones *not* beginning with a dot, | ||
66 | while all of `vienna`'s are the ones that *are*. In UNIX-like environments, | ||
67 | this makes them "hidden," so you can focus on your content by default. However, | ||
68 | your content is not something I can talk about in this README, because you still | ||
69 | have 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 | ||
74 | configuration file. By default, this file is `.vienna.sh` in your site folder, | ||
75 | but you can make it anything you want by passing `-c <config>` on the command | ||
76 | line or by setting the `$VIENNA_CONFIG` environment variable (more on the | ||
77 | command line and environment variables below). | ||
78 | |||
79 | Because the configuration file is written in the same language as `vienna` | ||
80 | itself, you can redefine any variable *or function* that's in `vienna`, fully | ||
81 | customizing 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 | ||
86 | when 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 | ||
100 | nothing. `vienna init` defines a basic `publish` function in `.vienna.sh`. | ||
101 | - `preview`: How to preview a site locally. By default, this function does | ||
102 | nothing. `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 | ||
104 | sort files at all. | ||
105 | - `index_item`: How to build each item in an index. The default outputs `<li><a | ||
106 | href=[LINK]>TITLE</a></li>`, where TITLE is the page's title and LINK is a link | ||
107 | to 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 | ||
111 | default 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 | ||
123 | the site. They can define new functions, export variables, or do whatever, so | ||
124 | make sure you know what's in these files! | ||
125 | |||
126 | ## content | ||
127 | |||
128 | Now for the stuff *you're* in charge of: your content. `vienna` takes a flat- | ||
129 | or no-hierarchy approach to sites. Your site will just be one flat folder full | ||
130 | of pages, though each page will be its own folder with an `index.html` for nicer | ||
131 | urls. 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 | |||
136 | By default, files matching the pattern `*.htm` will be processed by `vienna` and | ||
137 | turned into finished pages. `.htm` was chosen because it'll be picked up as | ||
138 | html in most text editors (for the format they're written in, see the **phtml** | ||
139 | section below), but it's also *unfinished html* (get it?!). | ||
140 | |||
141 | You can change this—you can change anything with `vienna`—but I'll | ||
142 | leave that as an exercise for you to figure out. | ||
143 | |||
144 | ### static files | ||
145 | |||
146 | Every other non-hidden file in the `vienna` folder will be copied as-is to the | ||
147 | output folder. | ||
148 | |||
149 | ## `phtml`: *pretty much* html | ||
150 | |||
151 | While lightweight markup languages like Markdown are nice (I'm using it to write | ||
152 | this README, for example), I realized that for my blogging needs it's not really | ||
153 | necessary. html is *pretty much* good enough for authoring, if I'm being | ||
154 | honest—it's just a little too verbose for fully-fluent drafting. | ||
155 | |||
156 | Thus, `phtml` was born. It's a function in the `vienna` source that consists of | ||
157 | one sed call: | ||
158 | |||
159 | ``` | ||
160 | sed -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\&#g # Replace & with & | ||
166 | s#([^\\])<#\1\<#g # Replace < with < | ||
167 | s#([^\\])>#\1\>#g # Replace > with > | ||
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 | |||
176 | To clarify the comments above: `phtml` allows a writer to leave out `<p>` tags, | ||
177 | which I consider are the most annoying parts of html. Paragraphs that don't | ||
178 | begin with `<` are wrapped in `<p>` tags, and html reserved characters `<`, `>`, | ||
179 | and `&` are turned into entities. | ||
180 | |||
181 | You can still write html, of course—either by backslash-escaping the | ||
182 | reserved characters or by starting a paragraph with an html tag (really, the | ||
183 | character `<`). Those paragraphs are passed through unprocessed. | ||
184 | |||
185 | This 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, | ||
187 | I'll have to escape the tags or wrap the whole thing in html!* While you'd be | ||
188 | right about that, I use plaintext paragraphs enough that it's worth it.n | ||
189 | |||
190 | ## `expand`: templating with here-docs | ||
191 | |||
192 | Here-docs are some of the most useful structures in any programming language, | ||
193 | and in shell they can be especially powerful. I first came across using | ||
194 | here-docs for templating on some Github repo I've since lost, but there are many | ||
195 | other projects that do something similar. `expand` is pretty minimal, and uses | ||
196 | here-docs and a little bit of escaping to provide lots of expressive power. | ||
197 | |||
198 | A template that looks like this: | ||
199 | |||
200 | ``` | ||
201 | Donuts now cost $$${CURRENT_DONUT_COST}, a $$(donut_perc_delta)% | ||
202 | $$(if [ $$(donut_perc_delta) -ge 0 ] | ||
203 | then echo increase | ||
204 | else echo decrease | ||
205 | ) from last week. | ||
206 | ``` | ||
207 | |||
208 | will 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 | ``` | ||
212 | Donuts now cost $1.99, a 5% | ||
213 | increase | ||
214 | from last week. | ||
215 | ``` | ||
216 | |||
217 | Single `$` and <code>\`</code> characters are escaped with backslashes so they | ||
218 | won'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 | ||
220 | pair of dollar signs to introduce a variable or function, not the first pair. | ||
221 | |||
222 | Other than that, `expand` simply `eval`s the templates given on the command line | ||
223 | as here-docs. Pages to be templated are passed into the function's standard | ||
224 | input, 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 | ||
229 | files. 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` | ||
239 | function. So in templates, you can access the title with `$$(meta title)`, or | ||
240 | the 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 | |||
247 | Of course, you can always rewrite `phtml` or `expand` to suit your needs, or | ||
248 | rewrite `filters` to use other processers like `markdown`, `asciidoc`, or | ||
249 | whatever you desire. However, for the "vanilla" experience, `vienna` includes a | ||
250 | variable that tweaks the behavior of `phtml`: `$PHTML_OPTIONS`. | ||
251 | |||
252 | `$PHTML_OPTIONS` can be one or more of the following values, separated by | ||
253 | spaces: | ||
254 | |||
255 | - `expand`: Run `expand` on the content after it's processed by `phtml`. | ||
256 | - `entities`: Convert `&`, `<`, and `>` into html entities. | ||
257 | |||
258 | The default is `expand entities`. | ||
259 | |||
260 | ## using plugins | ||
261 | |||
262 | `vienna` also supports the use of plugins, which are shell scripts sourced | ||
263 | before building the site. Plugins are placed in `$VIENNA_PLUGINDIR`, or | ||
264 | `.plugins` by default, and must end in `.sh` to be sourced. | ||
265 | |||
266 | You can see the `plugins` directory of this repo for some example plugins that I | ||
267 | found useful. | ||
268 | |||
269 | ## contributing | ||
270 | |||
271 | Comments, bug reports, and merge requests are welcome! [Send me an | ||
272 | email](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. | ||