Source

poole / README

  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
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
345
346
347
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
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
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
<!-- -*- markdown -*- -->

<!-- ====== Flattr Poole at http://flattr.com/thing/142425/Poole ========= -->
<p style="float: right;"><a href="http://flattr.com/thing/142425/Poole"
target="_blank"><img src="http://api.flattr.com/button/flattr-badge-large.png"
alt="Flattr this" title="Flattr this" border="0" /></a></p>
<!-- ===================================================================== -->

# Poole

**Create simple websites fast, now.**

Poole is an easy to use [Markdown][] driven static website generator. You write
the content of your pages in Markdown and Poole creates a nice and simple site
with a navigation menu. You don't need to learn a template or preprocessing
engine.

Though Poole is made for simple sites, it has basic support for content
generation by embedding Python code in page source files. This is a dirty merge
of content and logic but for simple sites it's a pragmatic way to get things
done fast and easy -- if you know Python you're ready to start.

See what you get: [example site](http://obensonne.bitbucket.org/).

## Requirements

**You** should know Markdown and optionally Python if you want to use Poole's
dirty content generation capability.

**Your system** should have installed Python  2.5 and python-markdown. Poole
has been tested on Linux but should also work on other Unix systems and Windows
(in theory, [report an issue][issues] if it fails).

[markdown]: http://daringfireball.net/projects/markdown/

## Getting Started

Clone oder download ([zip][zip], [tgz][tgz]) the repository and then put
*poole.py* to your *PATH*:

    $ hg clone http://bitbucket.org/obensonne/poole/ /some/where/poole
    $ export PATH=$PATH:/some/where/poole

**TIP**: You might want to add the last command to your `~/.bashrc`.

Create and build a site project:

    $ mkdir /path/to/site/project
    $ cd /path/to/site/project
    $ poole.py --init
    $ poole.py --build
    $ poole.py --serve

Done. You've just created a website! Browse <http://localhost:8080/> and watch
the example pages which have been created during initialization.

To write your own pages, use the example pages in the *input* folder as a
starting point.

Run `poole.py --build` whenever you've made some changes in the *input* folder.

[zip]: http://bitbucket.org/obensonne/poole/get/tip.zip
[tgz]: http://bitbucket.org/obensonne/poole/get/tip.tar.gz

## How It Works

Poole takes files from a project's `input` directory and copies them to the
`output` directory. In this process files ending with *md*, *mkd*, *mdown* or
*markdown* get converted to HTML using the project's `page.html` as a skeleton.

Additionally Poole expands any macros used in a page. Don't care about that for
now ..

When running `poole.py --build` in a Poole project, an input directory like
this:

    |- input
        |- index.md
        |- news.mkd
        |- foo.mdown
        |- images
            |- bar.png

will result in an output folder like that:

    |- output
        |- index.html
        |- news.html
        |- foo.html
        |- images
            |- bar.png

## Page Layout

Every Poole page is based on the skeleton file `page.html`. Hence adjusting the
site layout means adjusting `page.html` and extending or replacing its CCS file
`input/poole.css`.

The only thing you should keep in `page.html` are the embedded
{{\_\_content\_\_}} and {{\_\_encoding\_\_}} expressions.  Below is an almost
minimal `page.html` file. It does not look nice but it's a clean starting point
to build your own layout from scratch.

Minimal `page.html`:

    <html>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset={{ __encoding__ }}" />
      </head>
      <body>
        {{ __content __ }}
      </body>
    </html>

It's easy to apply one of the numerous free CSS templates out there to a Poole
site. For more information read [this blog post with step-by-step
instructions][pimp].

[pimp]: http://obensonne.bitbucket.org/blog/20091122-using-a-free-css-templates-in-poole.html

## Content Generation

Poole allows you to embed Python code in your pages to *generate* content:

`input/some-page.md`:

    Here is normal text in *markdown* flavor.
    {%
    print "hello poole"
    %}
    Did you know? The sum of 2 and 2 is {{ 2 + 2 }}.

This example demonstrates 2 ways to embed Python code, either as statements or
as expressions:

  1. Everything between `{%` and `%}` are *statements* and whatever is printed
     to *stdout* during their execution is going to be part of the final HTML
     page.
  2. Everything between `{{` and `}}` are *expressions* and their evaluation is
     going to be part of the final page.
 
**TIP**: Instead of the outer curly brackets `{` and `}` you can also use
`<!--` and `-->` to prevent syntax highlighting markdown editors from getting
confused by the Python code.

**TIP:** To print the code surrounding tags literally, simply escape the
opening tag with a backslash.

[hyde]: http://ringce.com/hyde

### Outsource complex or frequently used code

To keep embedded code short and compact or to reuse it in several pages, it can
be outsourced into a file called `macros.py` in a project's root folder (where
the `page.html` file is located). Every public attribute in `macros.py` is
available within embedded Python code blocks:

`macros.py`:

    from datetime import date
    def today():
        return date.today().strftime("%B %d, %Y")

    author = "Popeye"

`input/some-page.md`:

    This site has been updated on {{ today() }} by {{ author }}.

### Working with pages

Next to stuff defined in `macros.py` the objects `page` and `pages` are
available in embedded Python code. The first one is a dictionary describing the
page in which the code is embedded. The second one is a list of *all* pages in
the project.

The following attributes are always set in a page dictionary:

  * **title:** The page's title, by default its filename without extension
    (setting alternatives is described in the next section).
  * **fname:** Path to the page's source file, e.g.
    `/path/to/project/input/stuff/news.md`.
  * **url:** The page's relative URL, e.g. for a source page
    `input/stuff/news.md` this is `stuff/news.html`.

The example `page.html` file in a freshly initialized site project uses a
page's *title* attribute:

    ...
    <div id="header">
         <h1>a poole site</h1>
         <h2>{{ page["title"] }}</h2>
    </div>
    ...

**TIP:** All items in a page dictionary are exposed as attributes, i.e.
`page["foobar"]` is identical to `page.foobar`. Dictionary access is useful if
an item may not be set, e.g.: `page.get("foobar", "...")`. 

#### Setting page attributes

Page attributes can be set at the top of a page's source file, in [Python's
configuration file style][pyconf]. They are delimited from the page's content
by a line with 3 or more dashes.

`input/stuff/news.md`:

    title: Hot News
    foobar: King Kong
    ---
    Here are some news about {{ page.foobar }}.
    Did I say {% print(page.foobar) %}?

That way you can also set a page's title explicitly, instead of using the file
name. Other useful attributes to set are *description* and *keywords*, which
get used by the default `page.html` file to set HTML meta tags. Here it comes
in handy to set *default* page attributes in the `macros.py` file:

`macros.py`:

    page = { "description": "some stuff", "keywords": "stuff" }

That way you can safely use the *description* and *keywords* attributes without
bothering if they are really defined in every page.

[pyconf]: http://docs.python.org/library/configparser.html

#### Accessing page objects in the macros module

The objects `pages` and `page` are also available within `macros.py`. That
means you can define them as dummys and reference them in `macros.py`. Poole
updates them when loading the `macros` module.

`macros.py`:

    page = {} # you can also set defaults here, see previous section
    pages = []

    def something():
        # when executing this, the page and pages objects above are up-to-date
        print page["title"]

### Options and paths

Similarly to `page` and `pages` the following objects are available within
embedded Python code and within the *macros* module:

  * **options:** The command line arguments given to Poole as parsed by
    [Python's optparse module][pyopts]. For instance the base URL can be
    retrieved by `options.base_url`.
  * **project:** Path to the project's root directory.
  * **input:** Path to the project's input directory.
  * **output:** Path to the project's output directory.

[pyopts]: http://docs.python.org/library/optparse.html

### Character encodings

In case you use non-ASCII characters, check the *encoding* options of Poole. In
most cases working with non-ASCII strings should work straight forward if the
options are set properly (default is *UTF-8*).

However, be aware that page variables defined within page source files and
derived from a page's file name internally are handled as Python *unicode*
objects. That means if you want to refer to non-ASCII page variable names and
values form within embedded Python code or from `macros.py`, make sure to use
*unicode* strings to reference them.

### Howtos

#### Navigation menu

Have a look into the `page.html` file in a freshly initialized Poole project.

#### List of blog posts

If you want to write some blog posts, you probably would like to have a page
listing all or the latest blog posts. This is easy if you set certain page
attributes in every blog post page:

`input/brain-on-mongs.md`:

    title: blog
    post: This is your brain on mongs
    date: 2010-03-01
    ---

    # {{ page.post }}

    Posted on {{ page.date }}

    My hero is full of keyboards. Get nonsense at <http://automeme.net/>

`input/blog.md`:

    This is my blog.

    # My posts

    {%
    from datetime import datetime
    posts = [p for p in pages if "post" in p] # get all blog post pages
    posts.sort(key=lambda p: p.get("date"), reverse=True) # sort post pages by date
    for p in posts:
        date = datetime.strptime(p["date"], "%Y-%m-%d").strftime("%B %d, %Y")
        print "  * **[%s](%s)** - %s" % (p.post, p.url, date) # markdown list item
    %}

Feel free to adjust this to your needs.

**TIP:** Instead of setting the post title and date as page attributes, you can
encode them in the page's file name using a structure like
`page-title.YYYY-MM-DD.post-title.md`. For instance for the file name
`blog.2010-03-01.This_is_your_brain_on_mongs.md` Poole would automatically set
the page attributes which has been set manually in the example above.

To see this example in action, have a look into the example pages in a freshly
initialized Poole project.

#### Google sitemap file

To generate a Google sitemap.xml file, put this into the project's `macros.py`
file:

    # -----------------------------------------------------------------------------
    # generate sitemap.xml
    # -----------------------------------------------------------------------------

    from datetime import datetime
    import os.path

    _SITEMAP = """<?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    %s
    </urlset>
    """

    _SITEMAP_URL = """
    <url>
        <loc>%s/%s</loc>
        <lastmod>%s</lastmod>
        <changefreq>%s</changefreq>
        <priority>%s</priority>
    </url>
    """

    def hook_preconvert_sitemap():
        """Generate Google sitemap.xml file."""
        date = datetime.strftime(datetime.now(), "%Y-%m-%d")
        urls = []
        for p in pages:
            urls.append(_SITEMAP_URL % (options.base_url.rstrip('/'), p.url, date,
                        p.get("changefreq", "monthly"), p.get("priority", "0.8")))
        fname = os.path.join(options.project, "output", "sitemap.xml")
        fp = open(fname, 'w')
        fp.write(_SITEMAP % "".join(urls))
        fp.close()

You probably want to adjust the default values for *changefreq* and *priority*.

**Info:** Every function in `macros.py` whose name starts with
`hook_preconvert_` or `hook_postconvert_` is executed exactly once per project
build -- either before or after converting pages from markdown to HTML. In
post-convert hooks the HTML content of a page (yet without header and footer)
can be accessed with `page.html`. This is useful to generate full-content RSS
feeds.

#### RSS feed for blog posts

To generate an RSS feed for blog posts put this into the project's `macros.py`
file and adjust for your site:

    # -----------------------------------------------------------------------------
    # generate rss feed
    # -----------------------------------------------------------------------------

    import email.utils
    import os.path
    import time

    _RSS = """<?xml version="1.0"?>
    <rss version="2.0">
    <channel>
    <title>%s</title>
    <link>%s</link>
    <description>%s</description>
    <language>en-us</language>
    <pubDate>%s</pubDate>
    <lastBuildDate>%s</lastBuildDate>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>Poole</generator>
    %s
    </channel>
    </rss>
    """

    _RSS_ITEM = """
    <item>
        <title>%s</title>
        <link>%s</link>
        <description>%s</description>
        <pubDate>%s</pubDate>
        <guid>%s</guid>
    </item>
    """

    def hook_postconvert_rss():
        items = []
        posts = [p for p in pages if "post" in p] # get all blog post pages
        posts.sort(key=lambda p: p.date, reverse=True)
        for p in posts:
            title = p.post
            link = "%s/%s" % (options.base_url.rstrip("/"), p.url)
            desc = p.get("description", "")
            date = time.mktime(time.strptime("%s 12" % p.date, "%Y-%m-%d %H"))
            date = email.utils.formatdate(date)
            items.append(_RSS_ITEM % (title, link, desc, date, link))

        items = "".join(items)

        # --- CHANGE THIS --- #
        title = "Maximum volume yields maximum moustaches"
        link = "%s/blog.html" % options.base_url.rstrip("/")
        desc = "My name is dragonforce. You killed my dragons. Prepare to scream."
        date = email.utils.formatdate()

        rss = _RSS % (title, link, desc, date, date, items)

        fp = open(os.path.join(output, "rss.xml"), 'w')
        fp.write(rss)
        fp.close()

#### Convert CleverCSS to CSS

To convert all [CleverCSS][] files in the `input` directory to regular CSS
files in the `output` directory, put this into the project's `macros.py`
(assuming you use `.ccss` as file name extension):

    # -----------------------------------------------------------------------------
    # convert clevercss files to css
    # -----------------------------------------------------------------------------

    import clevercss
    import glob
    import os.path
    
    def hook_preconvert_ccss(): # pre- or post-convert hook, doesn't matter
        for ccss in glob.glob(os.path.join(input, "**.ccss")):
            css = ccss[len(input):].lstrip("/")
            css = "%s.css" % os.path.splitext(css)[0]
            css = os.path.join(output, css)
            fpi = open(ccss)
            fpo = open(css, 'w')
            fpo.write(clevercss.convert(fpi.read()))
            fpi.close()
            fpo.close()

To prevent the original *CCSS* files from getting copied to the output
directory, use Poole's *--ignore* option:
`poole.py --build --ignore '^\.|~$|\.ccss$'`.

[clevercss]: http://sandbox.pocoo.org/clevercss/

## Feedback

Please use the [issue tracker][issues].

[issues]: http://bitbucket.org/obensonne/poole/issues/