1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
TITLE="Andrea Corsini's Notes - A simple static website generator"
DESCRIPTION="I share and explain my simple static website generator, written in SHell scriptng from scratch."
---
<article>
<header>
<h2>A simple static website generator (sswg)</h2>
<time>June 3, 2022</time>
</header>
<nav class="toc">
TOC
</nav>
<p>Why bother creating another static website generator? There are so many
awesome projects out in the open source community, such as Jekyll, Hugo,
just to name few of them. Well, I wanted to have something dead simple, but
also flexible enough to be easily customized. So I wrote my own simple
static website generator (sswg) using SHell scripting.</p>
<p>This is possible because I don't need many features yet. In the future, I
could end up in switching to a proper generator. Anyway, for the time being I
can just use mine and share it here. Hopefully, it could be useful for other
folks that want to practice shell scripting, and/or want to implement their
website generator.</p>
<p>Here, you will find the original version and its rationale. Most likely,
the generator will change to cope with my website needs. You can find the
current version in my <a href="https://git.andreacorsini.xyz/sswg">git
repository</a>.</p>
<h3>Features</h3>
<p>At the time of writing, I am a simple person. I just want to:
<ul>
<li>Invoke the content generation with a simple command, such as <code>./sswg.sh</code>.</li>
<li>The generation should be <i>idempotent</i>.</li>
<li>Copy the header and footer templates in every page.</li>
<li>Support some simple macro substitution, to allow different title and description for each page.</li>
<li>Support the insertion of verbatim source code.</li>
</ul>
</p>
<p>Anything more than that is not necessary at the moment. For example, I
don't need Markdown or other languages to write pages, plain HTML is good
enough. In the future, it would be nice to have some extra features, like
automatic generation of RSS and Table of Content (TOC).</p>
<h3>Folder structure</h3>
<p>My sswg is just a tiny shell script to include in the website root. In this
way, it can be versioned along all the other website code. The script expect
to find a folder tree similar to</p>
<samp>
<b>├── _assets</b><br>
│ ├── style.css<br>
│ ├── favicon.ico<br>
│ ├── images<br>
│ │ └── picture.png<br>
│ └── rss.xml<br>
<b>├── _footer.t.html</b><br>
<b>├── _header.t.html</b><br>
<b>├── _pages</b><br>
│ ├── email.html<br>
│ ├── index.html<br>
│ ├── my-notes.html<br>
│ ├── notes<br>
│ │ └── a-simple-static-website-generator.html<br>
│ └── privacy-policy.html<br>
<b>└── sswg.sh</b>
</samp>
<p>Only the elements in <b>bold</b> are mandatory. The other files and folders
are there to showcase. Once the script is invoked with <code>./sswg.sh</code>,
sswg will regenerate the website to the output folder, named <code>_static</code>.
Any content of the direcorty <code>_asset</code> will be copied to the root
of <code>_static</code>. The content of template
files <code>_header.t.html</code> and <code>_footer.t.html</code> are preposed
and appended to each HTML page, respectively. The sswg will copy to the output
folder all the HTML files contained in <code>_page</code>, as well as
sub-directories and images.</p>
<p>The output folder <code>_static</code> for this example will result in</p>
<samp>
├── style.css<br>
├── favicon.ico<br>
├── images<br>
│ └── picture.png<br>
├── rss.xml<br>
├── email.html<br>
├── index.html<br>
├── my-notes.html<br>
├── notes<br>
│ └── a-simple-static-website-generator.html<br>
└── privacy-policy.html<br>
</samp>
<h3>Add a webpage</h3>
<p>Each HTML page in the directory <code>_page</code> only contains the main
content, while header and footer are shared in every page. Thus we don't need
to copy them every time.</p>
<p>So, adding a page is very easy. Save every new page within the
folder <code>_page</code>, or in one of its subfolders. Start with the first
two lines by defining the title and description for this page, by using the
macro T‍ITLE and D‍ESCRIPTION from sswg custom syntax. Then
separate the macro from the rest of the page with 3 dashes "<code>---</code>".
For example:</p>
|<pre>
|T‍ITLE="My new web page"
|D‍ESCRIPTION="This page contains information about..."
|---
|<h2>TI‍TLE</h2>
|# This is a sswg comment.
|<p>A page can contain whatever valid HTML code.</p>
|</pre>
# Test comment.
<p>The example also shows comments syntaxt. Every line which starts with a
hash mark # is considered as a source comment. Hence, it won't be copied in
the final static HTML page.</p>
<h3>Header and Footer templates</h3>
<p>Inside <code>_header.t.html</code> write the page content from the DOCTYPE
tag, to the navigation of your page, until the beginning of your main content.
Use the macros <code>TI‍TLE</code> and <code>D‍ESCRIPTION</code> for HTML title
and meta description. The generator will overwrite them with the value specified
within the HTML page. Here an example of header templete:</p>
|<pre>
|<!DOCTYPE html>
|<html>
| <head>
| <title>TI‍TLE</title>
| <meta name="description" content="DE‍SCRIPTION">
| <link rel="stylesheet" type="text/css" href="/css/style.css" media="screen">
| <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
| </head>
|
| <body>
| <header>
| <h1>Generic website title</h1>
| <div>
| <aside>I believe in personal digital freedom</aside>
| <nav>
| <a href="/index.html">Home</a> |
| <a href="/my-notes.html">My notes</a> |
| <a href="/email.html">Email</a>
| </nav>
| </div>
| </header>
| <main>
|</pre>
<p>Similarly to the header, <code>_footer.t.html</code> contains all the rest
of the website to be inserted at the end of each page. No macro substitution
is needed in this file. Here an example:</p>
|<pre>
| </main>
| <footer>
| <p><a href="/rss.xml"><img src="/images/feed.svg">Feed RSS</a>.
| <p>Copyright &copy; 2020-2021 Acme -
| <a href="/privacy-policy.html">Privacy policy</a></p>
| </footer>
| </body>
|</html>
|</pre>
<h3>Inserting verbatim source code</h3>
<p>I would like the HTML code assembled by sswg to maintain a proper
indentation. Therefore, the script takes care about indenting the page
contents between the header and footer templates. However, this is an issue
for the verbatim source code.</p>
<p>Indeed, any sort of white-spaces indentation added to the content of the
code block tags (<code><pre>...</pre></code>) is interpreted by
browsers as white-space characters of the verbatim code. Therefore, the
white-space characters will appear in the code blocks, resulting in unwanted
spaces.</p>
<p>To solve this issue, I decided to prepose every code block with a pipe
character |. The sswg script will take care in removing the pipe and
white-space characters. For example, the code</p>
|<pre>
| |<pre>
| |cd
| |ls -la
| |</pre>
|</pre>
<p>will be transformed into the HTML source code</p>
|<pre>
|<pre>
|cd
|ls -la
|</pre>
|</pre>
<h3>Code step by step</h3>
<p>By the time you will read this article, the code might have changed. So you
will find the current version in the
repository <a href="https://git.andreacorsini.xyz/sswg">git.andreacorsini.xyz/sswg</a>.</p>
<p>For what concern the early static website generator script, it starts by
setting constants for folders and template elements:</p>
|<pre>
|#!/bin/sh
|
|SSWG_OUTPUT_DIR="_static"
|SSWG_ASSETS_DIR="_assets"
|SSWG_PAGES_DIR="_pages"
|SSWG_HEADER_TEMPLATE="_header.t.html"
|SSWG_FOOTER_TEMPLATE="_footer.t.html"
|</pre>
<p>Second, the output folder <code>_static</code> is cleaned:</p>
|<pre>
|rm -rf "$SSWG_OUTPUT_DIR"
|mkdir "$SSWG_OUTPUT_DIR"
|</pre>
<p>So, be careful if you copied something inside it. It is wiser to copy
external files into <code>_assets</code>, as they will be automatically copied
back into <code>_static</code>:</p>
|<pre>
|cp -r "$SSWG_ASSETS_DIR"/* "$SSWG_OUTPUT_DIR"/.
|</pre>
<p>Then, we can finally generate each HTML page:</p>
|<pre>
|for page in $(find "$SSWG_PAGES_DIR" -iname '*.html' -o \
| -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png');
|do
| filename="$SSWG_OUTPUT_DIR/${page##$SSWG_PAGES_DIR/}"
| mkdir -p "`dirname $filename`"
|
| if [ "${filename##*.}" = "html" ]; then
|</pre>
<p>Prepose the header template:</p>
|<pre>
| cat "$SSWG_HEADER_TEMPLATE" >> "$filename"
|</pre>
<p>Indentation in the page content to match the header level:</p>
|<pre>
| cat "$page" | awk '
| BEGIN {print ""}
| FNR>3 {print " " $0}
| END {print ""}' >> "$filename"
|</pre>
<p>Append the footer template:</p>
|<pre>
| cat "$SSWG_FOOTER_TEMPLATE" >> "$filename"
|</pre>
<p>To perform the macro substitution, firstly shell-evaluate the first two
lines of the page. They are supposed to contain a shell-like declaration of the
variables TI‍TLE and DES‍CRIPTION. Secondly, each macro can be
substituted in the whole document.</p>
|<pre>
| eval `cat "$page" | awk 'FNR<3'`
| sed -i'' "s@TI‍TLE@$TIT‍LE@g" "$filename"
| sed -i'' "s@DES‍CRIPTION@$DES‍CRIPTION@g" "$filename"
|</pre>
<p>We can now remove comments and pipe + white-spaces:</p>
|<pre>
| sed -i'' "/^[ \t]*#/d" "$filename"
| sed -i'' "s/^[ \t]*|//g" "$filename"
|</pre>
<p>Continue with the rest of the script. If we are not reading an HTML file,
it could be either an image or a folder. Just copy it to its destination
in <code>_static</code>:</p>
|<pre>
| else
| cp $page $filename
| fi;
|done;
|</pre>
<hr>
<h3>Conclusion</h3>
<p>Static website generators are valid lightweight alternatives to more
complicate and larger content management systems (CMS), such as Wordpress.
They are normally simple to use, requires no databases, neither complicated
install procedures. Users are in control throught text files.</p>
<p>This post showed how to write a simple static website generator (sswg) to get our
web pages started. So far, the generator script has only bare-bones
functionalities, but it is simple enough to be extended with additional
features.</p>
<p>I hope you found this post useful and interesting. Don't hesitate to
contact me for any questions or comments.</p>
</article>
|