README.md 11.6 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
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
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>
```

179 180
This will generate a `site/blog.html` page. You can override the slug by
setting the `blog_slug` configuration variable (more on that later).
181 182 183 184 185 186 187 188 189 190 191 192

`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

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

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

216 217 218 219 220 221 222 223 224 225 226
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
227 228 229 230
the article template, uppercased and prepended by `SITE_`. You can set a
`blog_slug` (e.g. `index`) to change where the main blog page will be
generated. `url` can be set to `filesystem` for development (it will generate
paths to the output directory).
231

232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
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
```

253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
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
273
│   ├── blog.html
274 275 276 277 278 279
│   ├── index.html
│   ├── my-article.html
│   └── style.css
├── static
│   └── style.css
├── templates
280 281
│   ├── article.html
│   └── blog.html
282 283 284 285
├── configuration.yml
└── index.html
```

286 287 288 289 290 291
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
292
$ boop.py
293 294 295 296
$ cat site/foo/index.html
<html><body>My page</body></html>
```

297 298
Please note since the index page is considered as a "normal" page, this
template **also applies to the index page**.
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 342 343 344
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>
```

345 346 347 348 349 350
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.
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
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()`.

395 396 397
Series are accessible through the `blog` template with the `SERIES` variable. A
serie is just a basic page.

398 399 400 401 402 403
To finish, you can create a template specific to series. For that, you'll have
to set a `template: serie` variable in your `serie.html` files. The series have
access to two additional variables: `ARTICLES` (such in the `blog` template but
they are sorted from the older to the newer) and `FEED` which represent the
Atom feed associated to your serie. You can call `FEED.url()` to create a link
to your feed.
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 445 446 447 448 449 450 451 452 453
## 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>
```

454 455 456 457 458 459
## Tests

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

```console
460
$ python3 -m doctest -vf */*.py
461
```