Commits

Anonymous committed 1a36b11

Clean URLs opt-in; get(id) in templates; more...

Clean URLs are now optional, and disabled by default.

join_url() is now a little smarter. Consecutive slashes are reduced to one,
and the .html extension can be omitted by specifying ext=False.

ContentPage.posted defaults to None, rather than .date

New archive page attributes:
month
year

New template variables:
get(id) -- get a Page by id (e.g. get(year).url)
join_url(*, ext=True)
years -- sorted list of all years in which content was posted

Comments (0)

Files changed (22)

example-site/content/about.text

 title: About
 
-Made with [pilcrow](http://github.com/inky/pilcrow).
+[Pilcrow](http://github.com/inky/pilcrow) is a static site generator, tailored
+for the author's persnickety needs.
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

example-site/content/index.text

 template: home
 
-[About this site](/about)
+A simple static site generator.
+
+Work in progress...

example-site/content/lorem-ipsum.text

 title: Lorem ipsum
-date: 6 Dec 2009, 22:44 GMT
+date: 7 Dec 2009, 21:21 Z
+tags:
+  - lorem ipsum
+  - typography
+  - filler
 
-Lorem ipsum.
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ultrices
+tempor tincidunt. Mauris sit amet ante augue, eu pulvinar lorem. Nam eleifend
+sodales consectetur. Sed eget mi ac nunc ultrices egestas a vitae nibh.
+Maecenas mattis pharetra lorem. Fusce faucibus lacus nec tellus interdum nec
+cursus ante consequat.
+
+Aliquam sed nisi mauris.
+
+Nulla sit amet lacus at massa pharetra mattis at eu tortor. Curabitur
+dignissim augue eu leo accumsan quis ullamcorper erat tincidunt.
+
+Cras enim diam, cursus id condimentum ac, gravida et quam. Suspendisse
+ultricies libero quis quam facilisis blandit. Nunc adipiscing dolor et magna
+tincidunt venenatis. Ut vel magna et neque fringilla porttitor. Nam suscipit
+consectetur justo eget rutrum. Morbi eu eros nec nunc molestie blandit.
+Suspendisse lectus mi, sollicitudin at cursus non, congue at tortor.
+
+Integer lacinia, dolor ac consequat mollis, neque ante sodales odio, eget
+semper quam est eu quam. Quisque et nisl sit amet urna condimentum gravida.
+Nam euismod ante at orci blandit pharetra.

example-site/content/turritopsis-nutricula.text

+title: Turritopsis nutricula
+date: 24 NOV 09
+tags: wikipedia, immortality, wtf
+
+From [Wikipedia](http://en.wikipedia.org/wiki/Turritopsis_nutricula):
+
+> Turritopsis nutricula is a hydrozoan with a life cycle in which it reverts to
+> the polyp stage after becoming sexually mature. It is the only known case of
+> a metazoan capable of reverting completely to a sexually immature, colonial
+> stage after having reached sexual maturity as a solitary stage. It does this
+> through the cell development process of transdifferentiation. Theoretically,
+> this cycle can repeat indefinitely, rendering it biologically immortal until
+> its nerve center is removed from the rest of the body.

example-site/content/whats-new.text

 title: What's new in Python 2.6
 date: October 1 2008
+posted: 2009-12-06 22:44 UTC
+tags: python
 
 This article explains the new features in Python 2.6, released on October 1
 2008. The release schedule is described in [PEP 361][pep361].

example-site/deploy/.htaccess

 
 <IfModule mod_rewrite.c>
     RewriteEngine On
-    RewriteBase /
+    RewriteBase /pilcrow/
 
     # rewrite /page.html to /page
     RewriteCond %{REQUEST_FILENAME}.html -f

example-site/deploy/2008.html

 <html>
 <head>
   <meta charset="utf-8">
-  <title>Example Site: 2008</title>
-  <link rel="stylesheet" href="/style.css">
+  <title>Pilcrow: 2008</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
 </head>
 <body>
   <div id="page">
 
     <div id="header">
       
-    <p><a href="/">Example Site</a></p>
+    <p><a href="/pilcrow/">Pilcrow</a></p>
 
     </div>
 
     <div id="body">
       
 
+<h1>2008</h1>
+
 <ul>
     <li>
-      <a href="/2008/whats-new">What's new in Python 2.6</a>
+      <a href="/pilcrow/2008/whats-new.html">What's new in Python 2.6</a>
       (1 Oct)
     </li>
 </ul>
 
+
+
     </div>
 
     <div id="footer">
       
-  <hr>
-  <p><a href="/">Home</a></p>
+  <p><a href="/pilcrow/">Home</a>
+      <span class="sep">|</span>
+        <span class="selected">2008</span>
+      <span class="sep">|</span>
+        <a href="/pilcrow/2009.html">2009</a>
+  </p>
 
     </div>
 

example-site/deploy/2008/whats-new.html

 <html>
 <head>
   <meta charset="utf-8">
-  <title>Example Site: What's new in Python 2.6</title>
-  <link rel="stylesheet" href="/style.css">
+  <title>Pilcrow: What's new in Python 2.6</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
 </head>
 <body>
   <div id="page">
 
     <div id="header">
       
-    <p><a href="/">Example Site</a></p>
+    <p><a href="/pilcrow/">Pilcrow</a></p>
 
     </div>
 
 
 <p class="time" title="2008-10-01T00:00:00">
    1 October 2008
+    (posted  6 December 2009)
 </p>
 
 <p>This article explains the new features in Python 2.6, released on October 1
 
     <div id="footer">
       
-  <hr>
-  <p><a href="/">Home</a></p>
+  <p>
+
+    <a href="/pilcrow/2008.html">2008</a>
+
+  </p>
 
     </div>
 

example-site/deploy/2009.html

 <html>
 <head>
   <meta charset="utf-8">
-  <title>Example Site: 2009</title>
-  <link rel="stylesheet" href="/style.css">
+  <title>Pilcrow: 2009</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
 </head>
 <body>
   <div id="page">
 
     <div id="header">
       
-    <p><a href="/">Example Site</a></p>
+    <p><a href="/pilcrow/">Pilcrow</a></p>
 
     </div>
 
     <div id="body">
       
 
+<h1>2009</h1>
+
 <ul>
     <li>
-      <a href="/2009/lorem-ipsum">Lorem ipsum</a>
-      (6 Dec)
+      <a href="/pilcrow/2009/turritopsis-nutricula.html">Turritopsis nutricula</a>
+      (24 Nov)
+    </li>
+    <li>
+      <a href="/pilcrow/2009/lorem-ipsum.html">Lorem ipsum</a>
+      (7 Dec)
     </li>
 </ul>
 
+
+
     </div>
 
     <div id="footer">
       
-  <hr>
-  <p><a href="/">Home</a></p>
+  <p><a href="/pilcrow/">Home</a>
+      <span class="sep">|</span>
+        <a href="/pilcrow/2008.html">2008</a>
+      <span class="sep">|</span>
+        <span class="selected">2009</span>
+  </p>
 
     </div>
 

example-site/deploy/2009/lorem-ipsum.html

 <html>
 <head>
   <meta charset="utf-8">
-  <title>Example Site: Lorem ipsum</title>
-  <link rel="stylesheet" href="/style.css">
+  <title>Pilcrow: Lorem ipsum</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
 </head>
 <body>
   <div id="page">
 
     <div id="header">
       
-    <p><a href="/">Example Site</a></p>
+    <p><a href="/pilcrow/">Pilcrow</a></p>
 
     </div>
 
 
 <h1>Lorem ipsum</h1>
 
-<p class="time" title="2009-12-06T22:44:00+00:00">
-   6 December 2009
+<p class="time" title="2009-12-07T21:21:00+00:00">
+   7 December 2009
 </p>
 
-<p>Lorem ipsum.</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer ultrices
+tempor tincidunt. Mauris sit amet ante augue, eu pulvinar lorem. Nam eleifend
+sodales consectetur. Sed eget mi ac nunc ultrices egestas a vitae nibh.
+Maecenas mattis pharetra lorem. Fusce faucibus lacus nec tellus interdum nec
+cursus ante consequat.</p>
+<p>Aliquam sed nisi mauris.</p>
+<p>Nulla sit amet lacus at massa pharetra mattis at eu tortor. Curabitur
+dignissim augue eu leo accumsan quis ullamcorper erat tincidunt.</p>
+<p>Cras enim diam, cursus id condimentum ac, gravida et quam. Suspendisse
+ultricies libero quis quam facilisis blandit. Nunc adipiscing dolor et magna
+tincidunt venenatis. Ut vel magna et neque fringilla porttitor. Nam suscipit
+consectetur justo eget rutrum. Morbi eu eros nec nunc molestie blandit.
+Suspendisse lectus mi, sollicitudin at cursus non, congue at tortor.</p>
+<p>Integer lacinia, dolor ac consequat mollis, neque ante sodales odio, eget
+semper quam est eu quam. Quisque et nisl sit amet urna condimentum gravida.
+Nam euismod ante at orci blandit pharetra.</p>
 
 
 
 
     <div id="footer">
       
-  <hr>
-  <p><a href="/">Home</a></p>
+  <p>
+      <a href="/pilcrow/2009/turritopsis-nutricula.html">&larr; Turritopsis nutricula</a>
+      <span class="sep">|</span>
+
+    <a href="/pilcrow/2009.html">2009</a>
+
+  </p>
 
     </div>
 

example-site/deploy/2009/turritopsis-nutricula.html

+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Pilcrow: Turritopsis nutricula</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
+</head>
+<body>
+  <div id="page">
+
+    <div id="header">
+      
+    <p><a href="/pilcrow/">Pilcrow</a></p>
+
+    </div>
+
+    <div id="body">
+      
+
+<h1>Turritopsis nutricula</h1>
+
+<p class="time" title="2009-11-24T00:00:00">
+  24 November 2009
+</p>
+
+<p>From <a href="http://en.wikipedia.org/wiki/Turritopsis_nutricula">Wikipedia</a>:</p>
+<blockquote>
+<p>Turritopsis nutricula is a hydrozoan with a life cycle in which it reverts to
+the polyp stage after becoming sexually mature. It is the only known case of
+a metazoan capable of reverting completely to a sexually immature, colonial
+stage after having reached sexual maturity as a solitary stage. It does this
+through the cell development process of transdifferentiation. Theoretically,
+this cycle can repeat indefinitely, rendering it biologically immortal until
+its nerve center is removed from the rest of the body.</p>
+</blockquote>
+
+
+
+    </div>
+
+    <div id="footer">
+      
+  <p>
+
+    <a href="/pilcrow/2009.html">2009</a>
+
+      <span class="sep">|</span>
+      <a href="/pilcrow/2009/lorem-ipsum.html">Lorem ipsum &rarr;</a>
+  </p>
+
+    </div>
+
+  </div>
+</body>
+</html>

example-site/deploy/about.html

 <html>
 <head>
   <meta charset="utf-8">
-  <title>Example Site: About</title>
-  <link rel="stylesheet" href="/style.css">
+  <title>Pilcrow: About</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
 </head>
 <body>
   <div id="page">
 
     <div id="header">
       
-    <p><a href="/">Example Site</a></p>
+    <p><a href="/pilcrow/">Pilcrow</a></p>
 
     </div>
 
 
 <h1>About</h1>
 
-<p>Made with <a href="http://github.com/inky/pilcrow">pilcrow</a>.</p>
+<p><a href="http://github.com/inky/pilcrow">Pilcrow</a> is a static site generator, tailored
+for the author's persnickety needs.</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
 
     </div>
 
     <div id="footer">
       
-  <hr>
-  <p><a href="/">Home</a></p>
+  <p><a href="/pilcrow/">Home</a></p>
 
     </div>
 

example-site/deploy/index.html

 <html>
 <head>
   <meta charset="utf-8">
-  <title>Example Site</title>
-  <link rel="stylesheet" href="/style.css">
+  <title>Pilcrow</title>
+  <link rel="stylesheet" href="/pilcrow/style.css">
 </head>
 <body>
   <div id="page">
 
     <div id="header">
       
-    <h1><a href="/">Example Site</a></h1>
+    <h1><a href="/pilcrow/">Pilcrow</a></h1>
 
     </div>
 
     <div id="body">
       
 
+<p>A simple static site generator.</p>
+<p>Work in progress...</p>
+
+<h2>Sample Posts</h2>
+
 <ul>
-    <li>
-      <a href="/2009/lorem-ipsum">Lorem ipsum</a>
+    <li><a href="/pilcrow/2009/lorem-ipsum.html">Lorem ipsum</a>
+      (2009-Dec-07)
+    <li><a href="/pilcrow/2008/whats-new.html">What's new in Python 2.6</a>
       (2009-Dec-06)
-    </li>
-    <li>
-      <a href="/2008/whats-new">What's new in Python 2.6</a>
-      (2008-Oct-01)
-    </li>
+    <li><a href="/pilcrow/2009/turritopsis-nutricula.html">Turritopsis nutricula</a>
+      (2009-Nov-24)
 </ul>
 
-<p><a href="/about">About this site</a></p>
+<h2>Links</h2>
+
+<ul>
+  <li><a href="/pilcrow/about.html">About</a>
+  <li><a href="http://github.com/inky/pilcrow">Code on GitHub</a>
+</ul>
 
     </div>
 
-    <div id="footer">
-      
-  <hr>
-  <p><a href="/">Home</a></p>
-
-    </div>
 
   </div>
 </body>

example-site/deploy/style.css

 body {
-  background: #eeeeee;
-  color: #000000;
+  background: #161616;
+  color: #eeeeee;
   font: 1em/1.5 'Helvetica Neue', Helvetica, Arial, sans-serif;
   margin: 3em;
+  text-shadow: #0a0a0a 0 1px 1px;
 }
 #page {
-  background: #ffffff;
-  border: 4px double #999999;
+  background: #222222;
+  border: 2px solid #333333;
   max-width: 36em;
   padding: 2em 3em;
 }
+#header h1 { margin-top: 0; }
+#header p { margin-top: 0; }
 #footer {
-  border-top: 3px solid #cccccc;
+  border-top: 3px solid #3b3b3b;
   margin-top: 3em;
 }
-#footer hr { display: none; }
+#footer .sep { color: #777777; }
 p.time {
-  color: #666666;
+  color: #777777;
   font-size: 0.9em;
   text-transform: uppercase;
 }
+a:link {
+  color: #eebb22;
+  font-weight: bold;
+  text-decoration: none;
+}
+a:visited {
+  color: #eebb22;
+  font-weight: bold;
+  text-decoration: none;
+}
+a:hover { color: #ffee55; }

example-site/files/.htaccess

 
 <IfModule mod_rewrite.c>
     RewriteEngine On
-    RewriteBase /
+    RewriteBase /pilcrow/
 
     # rewrite /page.html to /page
     RewriteCond %{REQUEST_FILENAME}.html -f

example-site/files/style.less

-@background: #fff;
-@text: #000;
+@background: #222;
+@text: #eee;
+@link: #eb2;
 
 body {
-    background: @background - #111;
+    background: @background / 1.5;
     color: @text;
     font: 1em/1.5 'Helvetica Neue', Helvetica, Arial, sans-serif;
     margin: 3em;
+    text-shadow: (@background - #181818) 0 1px 1px;
 }
 
 #page {
     background: @background;
-    border: 4px double (@background - #666);
+    border: 2px solid (@background * 1.5);
     max-width: 36em;
     padding: 2em 3em;
 }
 
+#header {
+    h1, p { margin-top: 0; }
+}
+
 #footer {
-    border-top: 3px solid (@background - #333);
+    border-top: 3px solid (@text / 4);
     margin-top: 3em;
-    hr { display: none; }
+    .sep { color: @text / 2; }
 }
 
 p.time {
-    color: #666;
+    color: @text / 2;
     font-size: 0.9em;
     text-transform: uppercase;
 }
+
+a:link, a:visited {
+    color: @link;
+    font-weight: bold;
+    text-decoration: none;
+}
+a:hover {
+    color: @link + #333;
+}

example-site/site.yml

-site_title: Example Site
+site_title: Pilcrow
 title_format: "%(site_title)s: %(title)s"
-root: /
-domain: "http://localhost/"
+root: /pilcrow/
+domain: "http://inky.github.com/"
+clean_urls: no

example-site/templates/_base.html

       ${next.body()}
     </div>
 
+    % if id != 'index':
     <div id="footer">
       ${self.footer()}
     </div>
+    % endif
 
   </div>
 </body>
 </%def>
 
 <%def name="footer()">
-  <hr>
   <p><a href="${root}">Home</a></p>
 </%def>

example-site/templates/archive_year.html

 <%inherit file="_base.html"/>
 
+% if title:
+<h1>${title}</h1>
+% endif
+
 <ul>
   % for entry in entries:
     <li>
     </li>
   % endfor
 </ul>
+
+<%def name="footer()">
+  <p><a href="${root}">Home</a>
+  % for y in years:
+      <span class="sep">|</span>
+  %   if year == y:
+        <span class="selected">${y}</span>
+  %   else:
+        <a href="${get(y).url}">${y}</a>
+  %   endif
+  % endfor
+  </p>
+</%def>

example-site/templates/entry.html

 
 <p class="time" title="${date.isoformat()}">
   ${date.strftime('%e %B %Y')}
+  % if posted:
+    (posted ${posted.strftime('%e %B %Y')})
+  % endif
 </p>
 
 ${content}
 
+<%def name="footer()">
+  <p>
+    % if prevpost:
+      <a href="${prevpost.url}">&larr; ${prevpost.title or 'Untitled'}</a>
+      <span class="sep">|</span>
+    % endif
 
-% if prevpost or nextpost:
-<p>
-  % if prevpost:
-  <a href="${prevpost.url}">&larr; ${prevpost.title or 'Untitled'}</a>
-  % endif
-  % if nextpost:
-  <a href="${nextpost.url}">${nextpost.title or 'Untitled'} &rarr;</a>
-  % endif
-</p>
-% endif
+    <a href="${get(date.year).url}">${date.year}</a>
+
+    % if nextpost:
+      <span class="sep">|</span>
+      <a href="${nextpost.url}">${nextpost.title or 'Untitled'} &rarr;</a>
+    % endif
+  </p>
+</%def>

example-site/templates/home.html

 <%inherit file="_base.html"/>
 
+${content}
+
+<h2>Sample Posts</h2>
+
 <ul>
   % for page in pages(5):
-    <li>
-      <a href="${page.url}">${page.title or 'Untitled'}</a>
-      (${page.date.strftime('%Y-%b-%d')})
-    </li>
+    <li><a href="${page.url}">${page.title or 'Untitled'}</a>
+      (${ (page.posted or page.date).strftime('%Y-%b-%d') })
   % endfor
 </ul>
 
-${content}
+<h2>Links</h2>
+
+<ul>
+  <li><a href="${get('about').url}">About</a>
+  <li><a href="http://github.com/inky/pilcrow">Code on GitHub</a>
+</ul>
     '.less': lambda s, d: run_or_die('lessc %s %s' % (s, d)),
 }
 
-CONTEXT = {}
+CONTEXT = {
+    'clean_urls': False,
+}
 
 alphanum = lambda s: re.sub('[^A-Za-z0-9]', '', s)
 filemtime = lambda f: datetime.fromtimestamp(os.fstat(f.fileno()).st_mtime)
     tags = is_str(obj) and obj.split(',' in obj and ',' or None) or obj
     return tuple(filter(bool, (alphanum(tag) for tag in tags)))
 
-def join_url(*parts):
-    return '/'.join(str(s) for s in parts if s)
+def join_url(*parts, **kwargs):
+    ext = (kwargs.get('ext', 1) and not CONTEXT['clean_urls']) and '.html' or ''
+    return re.sub('//+', '/', '/'.join(str(s) for s in parts if s)) + ext
 
 def mkdir(d):
     try: os.mkdir(d)
         self.update(kwargs)
 
     def __getattr__(self, name):
-        if name == 'id' and self[name] == 'index':
-            return ''
         return self[name]
 
     @property
     def url(self):
-        return CONTEXT['root'] + self.id
+        id = self.id
+        return join_url(CONTEXT['root'], id != 'index' and id)
 
 class ContentPage(Page):
     NORM = {
             self[key] = self.NORM.get(key, identity)(val)
         if self.date:
             self.update({
-                'id': join_url(self.date.year, id),
+                'id': join_url(self.date.year, id, ext=False),
                 'template': self.template or 'entry',
+                'posted': self.get('posted', None),
                 'month_name': self.date.strftime('%B'),
                 'prevpost': None,
                 'nextpost': None,
             })
-            if 'posted' not in self:
-                self['posted'] = self.date
 
         def _summary(m):
             summary = m.group(2).strip()
 class ArchivePage(Page):
 
     def __init__(self, entries, year, month=0):
-        id = join_url(year, month and '%02d' % month)
+        id = join_url(year, month and '%02d' % month, ext=False)
         Page.__init__(self, id, {
             'entries': entries,
+            'year': year,
+            'month': month,
             'template': 'archive_%s' % (month and 'month' or 'year'),
             'title': month and datetime(year, month, 1).strftime('%B %Y') or year,
         })
             die('duplicate page id: %s' % page.id)
         self.pages[page.id] = page
 
+    def __getitem__(self, id):
+        return self.pages[id]
+
     def all(self):
         return self.pages.values()
 
                 context['head_title'] = context['title_format'] % context
             try:
                 html = template.render_unicode(**context).strip()
-                with open(path.join(DEPLOY_DIR, page['id']) + '.html', 'w') as f:
+                with open(path.join(DEPLOY_DIR, page.id) + '.html', 'w') as f:
                     f.write(html.encode('utf-8'))
             except NameError:
                 die('template error: undefined variable in', template.filename)
 
+CONTEXT.update({
+    'join_url': join_url,
+})
+
 def build(site_path, clean=False):
     try: os.chdir(site_path)
     except OSError: die('invalid path:', site_path)
 
     global CONTEXT
     with open(CONFIG_FILE) as f:
-        CONTEXT = yaml.load(f)
+        CONTEXT.update(yaml.load(f))
 
     if clean:
         shutil.rmtree(deploy_path, ignore_errors=True)
         return tuple(results)[:limit]
 
     CONTEXT.update({
+        'get': lambda id: pages[str(id)],
         'pages': select,
         'domain': CONTEXT['domain'].rstrip('/'),
         'root': '/' + CONTEXT.get('root', '').lstrip('/'),
         'head_title': CONTEXT.get('site_title', ''),
+        'years': sorted(years.keys()),
         'default_template': CONTEXT.get('default_template', 'page'),
     })
-
     try: pages.render()
     except MakoException as e: die('template error:', e)