e067410da3c3 draft — Oben Sonne 11 months ago
BitBucket wiki pages are now in the in-repo docs folder
4 files changed, 552 insertions(+), 3 deletions(-)

M README.md
A => docs/macros.md
A => docs/recipes.md
A => docs/sites.md
M README.md +3 -3
@@ 15,7 15,7 @@ done fast and easy -- if you know Python
 Check the list of [sites built with Poole][examples] (and feel free to add
 yours).
 
-[examples]: https://bitbucket.org/obensonne/poole/wiki/Home
+[examples]: ./docs/sites.md
 
 [markdown]: http://daringfireball.net/projects/markdown/
 [pymd]: https://pypi.python.org/pypi/Markdown

          
@@ 49,7 49,7 @@ Next to the *minimal* theme, there are s
 Run `poole --build` whenever you've made some changes in the *input* folder.
 
 [tgz]: https://hg.sr.ht/~obensonne/poole/archive/tip.tar.gz
-[themes]: https://bitbucket.org/obensonne/poole/wiki/Themes
+[themes]: ./docs/themes.md
 
 ## How It Works
 

          
@@ 349,7 349,7 @@ You can do some pretty fancy and useful 
 the macros module, for instance generate a list of blog posts or create an RSS
 file. Check out the [example recipes][recipes].
 
-[recipes]: https://bitbucket.org/obensonne/poole/wiki/Recipes
+[recipes]: ./docs/recipes.md
 
 ## Feedback
 

          
A => docs/macros.md +152 -0
@@ 0,0 1,152 @@ 
+Macros
+------
+
+Though poole is made for simple websites you can add a thin layer of
+smartness to a site using macros.
+
+**Note:** If you end up using macros a lot and everywhere, you should
+have a look at more powerful site generator\'s like Jekyll or Hyde which
+realize the idea of processing logic much better and with a more clear
+distinction between logic, content and layout.
+
+### Macros as variables
+
+You can use macros to avoid writing some repetitive content again and
+again. Consider the following as the content of a page, let\'s say
+*some-page.md*, in your project\'s *input* folder:
+
+**some-page.md**:
+
+    #!text
+    # book_title: Fool
+    # book_author: Moore
+
+    Books
+    -----
+
+    My current favorite book is {{ book_title }} by {{ book_author }}.
+    ...
+    That is why I love *{{book_title}}*.
+
+At the beginning it defines 2 macros which are used later using *{{
+macro-name }}*. Macros defined within a page are only valid in the scope
+of that page. If you want to reference your currently favored book on
+other pages, you should define it as a *global* macro.
+
+To define global macros, create a file *macros.py* in the same folder
+where *page.html* is located and set your macros there:
+
+**macros.py**:
+
+    #!python
+    book_title = Fool
+    book_author = Moore
+
+Now you can reference these macro in every page.
+
+What about a *today* macro, specifying the site build day:
+
+**macros.py:**
+
+    #!python
+    import datetime
+    today = datetime.datetime.now().strftime("%Y-%m-%d")
+
+### Overriding global macros in pages
+
+A good use case for a global macro defined in *macors.py* is to set a
+description or some keywords for your site which can then be referenced
+in the *page.html* file, e.g.:
+
+**macros.py:**
+
+    #!python
+    # ...
+    description = "a site about boo"
+    keywords = "foo, bar"
+    # ...
+
+**page.html:**
+
+    #!html
+    <!-- ... -->
+    <meta name="description" content="{{ description }}">
+    <meta name="keywords" content="{{ keywords }}">
+    <!-- ... -->
+
+For individual pages you can override these settings, for instance:
+
+**some-page.md:**
+
+    #!text
+    # keywords: foo, baz
+    ...
+
+Page macro definitions override global macro definitions in *macros.py*!
+
+### Dynamically generated content
+
+In *macros.py* you can define functions which then can be referenced as
+macros. Here\'s a simple and useless example:
+
+**macros.py**:
+
+    #!python
+    def asum(pages, page, a="0", b="1"):
+        return int(a) + int(b)
+
+**some-page.md:**
+
+    #!text
+    ...
+    The sum of 1 and 5 is {{ asum a=1 b=5 }}.
+    ...
+
+This will be replaced by, suprise, *6*.
+
+Macro function in must have at least 2 parameters:
+
+1.  *pages*: a list of all pages in the site processed by *poole*
+2.  *page*: the current page where this macro is used
+
+Additional parameters must be declared as keword arguments.
+
+The objects in *pages* as well as *page* itself are Page objects which
+have the following public fields:
+
+-   *name*: name of the page (either the file name without extension or
+    the value of the `name` macro, if defined within the page\'s source
+    file
+-   *macros*: a dictionary of all macros defined for the page (including
+    global macros defined in *macro.py*)
+-   *url*: URL to link to that page
+
+Here is a more complex example, the built-in *menu* macro used in the
+default *page.html/ file to display a navigation menu:*
+
+    #!python
+    def menu(pages, page, tag="span", current="current"):
+        """Compile an HTML list of pages to appear as a navigation menu.
+
+        Any page which has a macro {{{menu_pos}}} defined is included. Menu
+        positions are sorted by the integer values of {{{menu_pos}}} (smallest
+        first).
+
+        The current page's //tag// element is assigned the CSS class //current//.
+
+        """
+        menu_pages = [p for p in self.pages if "menu_pos" in p.macros]
+        menu_pages.sort(key=lambda p: int(p.macros["menu_pos"]))
+
+        html = ''
+        for p in menu_pages:
+            style = p == self.__page and (' class="%s"' % current) or ''
+            html += '<%s%s><a href="%s">%s</a></%s>' % (tag, style, p.url, p.name, tag)
+        return html
+
+You can write your own *menu* macro in *macros.py*, if you don\'t like
+the built-in one.
+
+### Limitations
+
+Macros are not nestable.

          
A => docs/recipes.md +368 -0
@@ 0,0 1,368 @@ 
+Recipes
+=======
+
+\* Fancy things you can do with Poole. *
+
+[Navigation Menu](Recipes#!navigation-menu) ·
+[Breadcrumb Navigation](Recipes#!breadcrumb-navigation) ·
+[List of Blog Posts](Recipes#!list-of-blog-posts) ·
+[Google Sitemap File](Recipes#!google-sitemap-file) ·
+[RSS Feed for Blog Posts](Recipes#!rss-feed-for-blog-posts) ·
+[Multiple Languages Support](Recipes#!multiple-languages-support) ·
+[Link File Size](Recipes#!link-file-size)
+
+Feel free to add yours!
+
+------------------------------------------------------------------------
+
+Navigation Menu
+---------------
+
+Have a look into the `page.html` file in a freshly initialized Poole
+project.
+
+------------------------------------------------------------------------
+
+Breadcrumb Navigation
+---------------------
+
+To add breadcrumb navigation, put this into the project\'s `macros.py`
+file:
+
+    #!python
+    def breadcrumb():
+        parents = {p.title: (p.url, p.get('parent')) for p in pages}
+        title = page.title
+        output = hx(title)
+        while parents[title][1] is not None:
+            title = parents[title][1]
+            url = parents[title][0]
+            output = '<a href="%s">%s</a> &gt; %s' % (url, hx(title), output)
+        return output
+
+For each page that has a parent, set the page attribute `parent` to the
+`title` of the parent page. The breadcrumb trail can then be included by
+specifying `{{ breadcrumb() }}` in your `page.html` (or elsewhere).
+
+------------------------------------------------------------------------
+
+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:
+
+    #!python
+    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:
+
+    #!python
+    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()
+
+------------------------------------------------------------------------
+
+Multiple languages support
+--------------------------
+
+To make your website available in several languages, put this into the
+project\'s `macros.py` file:
+
+    #!python
+
+    import re
+    import itertools
+
+
+    def hook_preconvert_multilang():
+        MKD_PATT = r'\.(?:md|mkd|mdown|markdown)$'
+        _re_lang = re.compile(r'^[\s+]?lang[\s+]?[:=]((?:.|\n )*)', re.MULTILINE)
+        vpages = [] # Set of all virtual pages
+        for p in pages:
+            current_lang = "en" # Default language
+            langs = [] # List of languages for the current page
+            page_vpages = {} # Set of virtual pages for the current page
+            text_lang = re.split(_re_lang, p.source)
+            text_grouped = dict(zip([current_lang,] + \
+                                            [lang.strip() for lang in text_lang[1::2]], \
+                                            text_lang[::2]))
+
+            for lang, text in text_grouped.iteritems():
+                spath = p.fname.split(os.path.sep)
+                langs.append(lang)
+                filename = re.sub(MKD_PATT, ".%s\g<0>" % lang, p.fname).split(os.path.sep)[-1]
+                vp = Page(filename, virtual=text)
+                # Copy real page attributes to the virtual page
+                for attr in p:
+                    if not vp.has_key(attr):
+                        vp[attr] = p[attr]
+                # Define a title in the proper language
+                vp["title"] = p["title_%s" % lang] \
+                                        if p.has_key("title_%s" % lang) \
+                                        else p["title"]
+                # Keep track of the current lang of the virtual page
+                vp["lang"] = lang
+                # Fix post name if exists
+                if vp.has_key("post"):
+                    vp["post"] = vp["post"][:-len(lang) - 1]
+                page_vpages[lang] = vp
+
+            # Each virtual page has to know about its sister vpages
+            for lang, vpage in page_vpages.iteritems():
+                vpage["lang_links"] = dict([(l, v["url"]) for l, v in page_vpages.iteritems()])
+                vpage["other_lang"] = langs # set other langs and link
+
+            vpages += page_vpages.values()
+
+        pages[:] = vpages
+
+Then make the following modifications in `page.html`:
+
+    #!python
+
+    mpages = [p for p in pages if "menu-position" in p]
+
+becomes
+
+    #!python
+
+    mpages = [p for p in pages if "menu-position" in p and p.has_key("lang") and p["lang"] == page["lang"]]
+
+Add the language list by adding this code in `page.html`, for example at
+the end of the div menu:
+
+    #!html
+         <div id="lang">
+             <!--%
+                 print " | ".join(["<span><a href='%s'>%s</a></span>" % \
+                          (url, lang) for lang, url in page["lang_links"].iteritems()])
+             %-->
+          </div>
+
+Adjust the `poole.css` file by adding something like:
+
+    #!css
+    div#lang {
+        float:right;
+        text-align:right;
+        color: white;
+    }
+
+Finally, if you want to show blog pages of the current language only,
+replace:
+
+    #!python
+
+    posts = [p for p in pages if "post" in p] # get all blog post pages
+
+with
+
+    #!python
+
+    posts = [p for p in pages if "post" in p if p.lang == page.lang] # get all blog post pages
+
+in `blog.md` (or whatever your blog file is).
+
+### Usage
+
+The directive `lang: lang_name` (where `lang_name` can be any language
+code, typically according to
+[ISO 639-1](http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes))
+separate different languages of the same page. The attribute
+`title_lang_name` can be used to translate the title page (which may be
+displayed in the menu). Example:
+
+`input/stuff/news.md:`
+
+        title: Hot News
+        title_fr: Nouvelles
+        foobar: King Kong
+        ---
+        Here are some news about {{ page.foobar }}.
+        Did I say {% print(page.foobar) %}?
+
+        lang: fr
+
+        Voici quelques nouvelles a propos de {{ page.foobar }}.
+        Ai-je bien dit {% print(page.foobar) %} ?
+
+The first block will always take the default language (which can be
+changed in the hook above).
+
+------------------------------------------------------------------------
+
+Link File Size
+--------------
+
+For people with slow internet access, or simply to inform the visitor
+about the size of a downloadable file on your poole web site, you can
+use the following postconvert hook:
+
+    #!python
+    def hook_postconvert_size():
+        file_ext = '|'.join(['pdf', 'eps', 'ps'])
+        def matched_link(matchobj):
+        try:
+            # We assume a relative link to a document in the output directory of poole.
+            size = os.path.getsize(os.path.join("output", matchobj.group(1)))
+            return  "<a href=\"%s\">%s</a>&nbsp;(%d KiB)" % (matchobj.group(1), \
+                                     matchobj.group(3), \
+                                     size // 1024)
+        except:
+            print "Unable to estimate file size for %s" % matchobj.group(1)
+            return '<a href=\"%s\">%s</a>' % (matchobj.group(1), \
+                              matchobj.group(3))
+
+        _re_url = '<a href=\"(.*?\.(%s))\">(.*?)<\/a>' % file_ext
+        for p in pages:
+        p.html = re.sub(_re_url, matched_link, p.html)
+
+It will add the file size in KiB, right after the link, for the file
+extensions specified in the second line.

          
A => docs/sites.md +29 -0
@@ 0,0 1,29 @@ 
+Websites built with Poole
+-------------------------
+
+-   [twentyweeks.com](https://twentyweeks.com)
+-   [bertjwregeer.com](http://bertjwregeer.com/index.html)
+-   [chistoe-nebo.info](http://www.chistoe-nebo.info/)
+-   [evanchen.cc](http://web.evanchen.cc/)
+-   [hpi.uni-potsdam.de/giese/events/2011/seams2011](http://www.hpi.uni-potsdam.de/giese/events/2011/seams2011/index.html)
+-   [i-for-change.co.uk](http://www.i-for-change.co.uk)
+-   [jcby.com](http://jcby.com/index.html)
+-   [land.umonkey.net](http://land.umonkey.net/)
+-   <http://adiultra.github.io/lynx/index.html>
+-   [ mechatronics3d.com](http://www.mechatronics3d.com)
+-   [monitoring-plugins.org](http://www.monitoring-plugins.org/)
+-   [obensonne.bitbucket.org](http://obensonne.bitbucket.org/)
+-   [paulbarker.me.uk](http://www.paulbarker.me.uk)
+-   [profgra.org/lycee](http://profgra.org/lycee/)
+-   [rpedroso.github.com/sharme](http://rpedroso.github.com/sharme)
+-   [serge.liyun.free.fr/serge](http://serge.liyun.free.fr/serge/index.html)
+-   [taecilla.github.io](http://taecilla.github.io/)
+-   [thpani.at](http://thpani.at/)
+-   [translation.baham.co](https://translation.baham.co)
+-   [ultimatehurl.com](http://ultimatehurl.com/index.html)
+-   [ece.mcgill.ca/\~\~tnorth](http://www.ece.mcgill.ca/~tnorth/)
+-   [xythobuz.de](http://xythobuz.de)
+
+*This list is in alphabetical order. Feel free to add your site (or
+remove it if the addition by someone else is not your will).*
+