README.md 11.1 KB
Newer Older
1
2
# Boop! – a simple "low-tech" static site generator, for fun.

3
4
5
6
**Important note:** Boop! is under development, it is probably full of bugs.
Also, it is a pet project and isn't communautary (for the moment at least). Use
it at your own risks.

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
## Installation

In this guide, we assume you are using Linux.

First, you should clone the Boop! repository and install its dependencies.
Boop! requires Python 3.6 to work (mainly because of our use of the `f-strings`).

```console
$ git clone https://framagit.org/marien.fressinaud/boop.git
$ cd boop
$ pip3 install -r requirements.txt
$ cd ..
```

Then, add the path to your `.bashrc`:

```console
$ echo export PATH="$(pwd)/boop:\$PATH" >> ~/.bashrc
```

And source it so it considers the change:

```console
$ source ~/.bashrc
```

33
34
## Usage

35
First, create a project and execute the Boop! command:
36
37
38

```console
$ mkdir my-website && cd my-website
39
$ echo '<h1>Welcome!</h1>' > index.html
40
$ boop.py
41
42
43
Boop!
```

44
45
Now, you should have your "production-ready" website under a `./site` folder
that you can upload on your server.
46
47
48
49

```console
$ tree
.
50
51
52
├── site
│   └── index.html
└── index.html
53
54
```

55
56
57
Well OK, Boop! basically just copied the `index.html` file under the `site`
directory. Let's write an article then.

58
For that, you'll need a template file under a `./templates` folder:
59
60

```console
61
$ mkdir templates
berumuron's avatar
berumuron committed
62
$ echo '<html><body>{{ ARTICLE_CONTENT }}</body></html>' > templates/article.html
63
64
```

65
Then, you can create a Markdown file under a `./articles` folder:
66
67

```console
68
69
$ mkdir articles
$ echo '# My first article' > articles/my-article.md
70
$ boop.py
71
72
$ tree
.
73
├── articles
74
75
│   └── my-article.md
├── site
76
77
│   ├── feeds
│   │   └── all.atom.xml
78
79
│   ├── index.html
│   ├── my-article.html
80
├── templates
81
82
│   └── article.html
└── index.html
83
84
$ echo site/my-article.html
<html><body><h1>My first article</h1></body></html>
85
```
86

87
88
89
Please note that `./articles` folder only accepts Markdown files (i.e.
extension is `.md`).

90
91
92
As you can see, it also generates automatically [an Atom feed](https://en.wikipedia.org/wiki/Atom_(Web_standard))
under `./site/feeds/all.atom.xml`.

93
94
95
96
97
98
You can define meta variables in the Markdown file which will be accessible
then in the template. Variables are uppercased and prepended by `ARTICLE_`. For
example, for the following Markdown file:

```markdown
---
99
title: An article
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
author: Marien
---

This is my article.
```

And the HTML template:

```html
<html>
    <head>
        <title>{{ ARTICLE_TITLE }}</title>
    </head>
    <body>
        <h1>{{ ARTICLE_TITLE }}</h1>
        <p>By {{ ARTICLE_AUTHOR }}</p>

        {{ ARTICLE_CONTENT }}
    </body>
</html>
```

It will produce the following file:

```html
<html>
    <head>
127
        <title>An article</title>
128
129
    </head>
    <body>
130
        <h1>An article</h1>
131
132
133
134
135
136
137
138
139
140
141
        <p>By Marien</p>

        <p>This is my article.</p>
    </body>
</html>
```

For now, you can use any valid Python expression between `{{ }}`, you have
access to the variables defined in the meta header, the Python builtin
functions and the `datetime` module.

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
The following meta variables have specific significance, especially in the Atom
feed:

- `slug` (defines the final URL of the article)
- `title`
- `author`
- `date` (the date of publication)
- `update` (the date of the last update)

It is highly recommended to always specify `title` and `date` (or default
values will be generated). The default value of `slug` is the name of the file
without its extension. A default value for `author` can be set in the
`configuration.yml` file. `date` and `update` must follow this format:
`YYYY-MM-DD HH:MM`.

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
Then, you'll probably want to have a page listing all the articles of your
blog. For that, you'll need to create a `templates/blog.html` file. This
template has access to a `ARTICLES` variable:

```html
<html>
    <head>
        <title>Blog</title>
    </head>
    <body>
        <ul>
            {% for article in ARTICLES %}
                <li>
                    <a href="{{ article.url() }}">{{ article.title() }}</a>
                    ({{ article.date().strftime("%d %b %Y") }})
                </div>
            {% endfor %}
        </ul>
    </body>
</html>
```

This will generate a `site/blog.html` page.

`Article` model has several public methods:

- `slug()`: the name of the generated file, without its extension
- `title()`: the title of the article
- `author()`: the author of the article
- `url()`: the full URL of the article
- `uuid()`: a unique identifier, based on the URL
- `content()`: the article's content in HTML
- `date()`: the publication date
- `update()`: the last update date

192
The root folder can contain a `static` folder as well. Its content will be
193
194
195
196
197
copied under the `./site` folder:

```console
$ tree
.
198
├── articles
199
200
│   └── my-article.md
├── site
201
202
│   ├── feeds
│   │   └── all.atom.xml
203
│   ├── blog.html
204
205
206
│   ├── index.html
│   ├── my-article.html
│   └── style.css
207
208
├── static
│   └── style.css
209
├── templates
210
211
│   ├── article.html
│   └── blog.html
212
213
214
└── index.html
```

berumuron's avatar
berumuron committed
215
216
217
218
219
220
221
222
223
224
225
Finally, you might want to create a `./configuration.yml` file. Four
information are used by the Atom feed, so you might want to declare them:

```yaml
url: http://my-website.org/
title: My website
author: Dale Cooper
timezone: Europe/Paris
```

You can declare as many variables as you want, they all will be accessible in
226
227
the article template, uppercased and prepended by `SITE_`. `url` can be set to
`filesystem` for development (it will generate paths to the output directory).
berumuron's avatar
berumuron committed
228

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
If you want to use a different configuration in development, you can specify
different options under a `development` section:

```yaml
url: http://my-website.org/
title: My website
author: Dale Cooper
timezone: Europe/Paris

development:
  url: filesystem
```

By default, `boop.py` command generates the website with production
configuration. To use the development one you should pass the `--development`
argument:

```console
$ boop.py --development
```

berumuron's avatar
berumuron committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
You might find yourself limited by the article-only system. If you want to
create additional pages, just put HTML files under a `pages` folder. The
directory structure will be preserved.

```console
$ mkdir -p pages/foo
$ echo '<html><body>My page</body></html>' > pages/foo/index.html
$ boop.py
$ tree
.
├── articles
│   └── my-article.md
├── pages
│   └── foo
│       └── index.html
├── site
│   ├── feeds
│   │   └── all.atom.xml
│   ├── foo
│   │   └── index.html
270
│   ├── blog.html
berumuron's avatar
berumuron committed
271
272
273
274
275
276
│   ├── index.html
│   ├── my-article.html
│   └── style.css
├── static
│   └── style.css
├── templates
277
278
│   ├── article.html
│   └── blog.html
berumuron's avatar
berumuron committed
279
280
281
282
├── configuration.yml
└── index.html
```

berumuron's avatar
berumuron committed
283
284
285
286
287
288
You can also define a generic page template in the `templates/page.html` file.
The file's content is accessible through the `PAGE_CONTENT` variable.

```console
$ echo '<html><body>{{ PAGE_CONTENT }}</body></html>' > templates/page.html
$ echo 'My page' > pages/foo/index.html
289
$ boop.py
berumuron's avatar
berumuron committed
290
291
292
293
$ cat site/foo/index.html
<html><body>My page</body></html>
```

294
295
Please note since the index page is considered as a "normal" page, this
template **also applies to the index page**.
berumuron's avatar
berumuron committed
296

297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
You can define Yaml variables in the pages that will be used in the template
then. The variables are uppercased and prepended by `PAGE_`. For instance, in a
`pages/my-page.html` file:

```html
---
title: My page
---
<div>
    This is the content of my page
</div>
```

And in the `templates/page.html` file:

```html
<html>
    <head>
        <title>{{ PAGE_TITLE }}</title>
    </head>
    <body>
        <h1>{{ PAGE_TITLE }}</h1>

        {{ PAGE_CONTENT }}
    </body>
</html>
```

This will generate the `site/my_page.html` file:

```html
<html>
    <head>
        <title>My page</title>
    </head>
    <body>
        <h1>My page</h1>

        <div>
            This is the content of my page
        </div>
    </body>
</html>
```

342
343
344
345
346
347
Please note if you make use of a variable in the template, it must be defined
in all pages then (for example, we must add a `title` variable to all the pages
we created until now).

You can also override the default template by setting a `template` variable in
the Yaml header of pages and articles.
348

349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
Last feature is the "serie" one. You might want to regroup several articles
because they are related. First, create a new folder under `articles`:

```console
$ mkdir articles/my-serie
```

Then, let's write two articles with the following format:

```markdown
---
title: First article of the serie
author: Enora
date: 2019-02-24 17:00
---

This is my first article.
```

If you call Boop!, you'll notice nothing different than before. Just note for
the moment that the articles are not generated under a `my-serie` folder.

To transform this folder into a serie, you'll have to create a `serie.html`
file. This is a normal page (so it is generated with the `page` template),
except that it is located under the `articles/` folder. So, write a
`articles/my-serie/serie.html`:

```html
---
title: My serie
---

<div>
    This is the main page of my serie!
</div>
```

Now, if you call the `boop.py` command, you'll notice two things: a new page
`site/serie/my-serie.html` and a new feed `site/feeds/my-serie.atom.xml`. The
feed obviously contains only the articles from the serie. You can also access
the serie object from the article template with `ARTICLE_SERIE`. The two most
interesting functions to use on this object are `title()` and `url()`.

Please note for the moment, you cannot access the list of serie's articles in
the `page` template nor from the `ARTICLE_SERIE` object so you'll have to
create the links to the articles manually.

396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
## Template syntax

As explained above, you can create templates under the `./templates` folder.
These templates are basic HTML files, with a pinch of special syntax.

To output the value of a variable, we already saw how to do:

```html
<p>
    {{ ARTICLE_CONTENT }}
</p>
```

Any valid Python expression is accepted here. For instance, you might want to
display the article publication date in a human format:

```html
<div class="article-pubdate">
    {{ datetime.strptime(ARTICLE_DATE, "%Y-%m-%d %H:%M").strftime("%a %d %B %Y")
</div>
```

Yes, it's a bit complex for now. Hopefully, you will not have to call this very
often. Improvement is planned.

Then, you might want to display content conditionnaly with a `if` statement:

```html
<head>
    {% if PAGE_TITLE %}
        <title>{{ PAGE_TITLE }} - {{ SITE_TITLE }}</title>
    {% else %}
        <title>{{ SITE_TITLE }}</title>
    {% endif %}
</head>
```

`elif` statement is also supported the same way as `if`.

Finally, the `for` statement gives you the possibility to generate lists:

```html
<ul>
    {% for author in ARTICLE_AUTHORS %}
        <li>{{ author }}</li>
    {% enfor %}
</ul>
```

445
446
447
448
449
450
## Tests

There are some tests (i.e. doctests) that can be run to check that everything
works smoothly. They can be executed with:

```console
451
$ python3 -m doctest -vf */*.py
452
```