README is now informative to help you get started with wrigit
[wrigit.git] / wrigit.cgi
blob2221b2b98f22857dcc475ca57ef4cdb13062ede0
1 #!/usr/bin/env python
2 # -*- mode: python -*-
4 import os, time, datetime
5 from os.path import join, abspath
6 from optparse import OptionParser
8 from docutils import core, nodes
9 from docutils.parsers import rst
10 from docutils.writers.html4css1 import Writer, HTMLTranslator
12 from mako.template import Template
13 from mako.lookup import TemplateLookup
15 from pygments import highlight
16 from pygments.lexers import get_lexer_by_name, TextLexer
17 from pygments.formatters import HtmlFormatter
20 # Program options
21 # Start with --help to get help
23 oparser = OptionParser()
24 oparser.set_defaults(**dict(
25 rstdir = './example/',
26 templatedir = './example/',
27 baseurl = 'http://localhost:8080',
28 title = 'example.com',
29 regen = False,
31 disqus_forum = 'tst' # DEBUG
33 oparser.add_option('-r', '--rstdir', dest='rstdir',
34 help='Directory where source .rst files are found')
35 oparser.add_option('-t', '--templatedir', dest='templatedir',
36 help='Directory where mako templates can be found')
37 oparser.add_option('-b', '--baseurl', dest='baseurl',
38 help='The base URL of the site')
39 oparser.add_option('-T', '--title', dest='title',
40 help='Site title that goes into <title>')
41 oparser.add_option('', '--regen', dest='regen', action="store_true",
42 help='Regenerate the whole site (all files)')
43 oparser.add_option('-d', '--disqus-forum', dest='disqus_forum',
44 help='The name of the disqus forum. For example, if http://foo.disqus.com is the forum, then do --disqus-forum=foo')
46 (options, args) = oparser.parse_args()
47 options.rstdir = abspath(options.rstdir)
48 options.templatedir = abspath(options.templatedir)
49 print 'using options: %s' % options
52 # Pygments syntax-highlighting for ReST
53 # adapted from,
54 # http://dev.pocoo.org/projects/pygments/browser/external/rst-directive.py
56 INLINESTYLES = False
57 DEFAULT = HtmlFormatter(noclasses=INLINESTYLES)
58 # Add name -> formatter pairs for every variant you want to use
59 VARIANTS = {
60 # 'linenos': HtmlFormatter(noclasses=INLINESTYLES, linenos=True),
63 def pygments_directive(name, arguments, options, content, lineno,
64 content_offset, block_text, state, state_machine):
65 try:
66 lexer = get_lexer_by_name(arguments[0])
67 except ValueError:
68 # no lexer found - use the text one instead of an exception
69 lexer = TextLexer()
70 # take an arbitrary option if more than one is given
71 formatter = options and VARIANTS[options.keys()[0]] or DEFAULT
72 parsed = highlight(u'\n'.join(content), lexer, formatter)
73 return [nodes.raw('', parsed, format='html')]
75 pygments_directive.arguments = (1, 0, 1)
76 pygments_directive.content = 1
77 pygments_directive.options = dict([(key, directives.flag) for key in VARIANTS])
79 rst.directives.register_directive('sourcecode', pygments_directive)
82 def htmlpath(rstfile):
83 """Return the .html path for the given .rst path"""
84 assert rstfile.endswith('.rst'), '<%s> is not a rst path' % rstfile
85 return rstfile[:-4] + '.html'
87 class Page(object):
88 """A rst page"""
90 def __init__(self, rstfile):
91 self.rstfile = rstfile
92 writer = Writer()
93 writer.translator_class = DocInfoProxy # see comment for `DocInfoProxy`
95 parts = core.publish_parts(
96 source=open(rstfile).read(),
97 source_path=rstfile, writer=writer)
98 self.title, self.fragment = parts['title'], parts['fragment']
99 self.meta = DocInfoProxy.ITEMS.copy()
101 def date(self):
102 if '|' in self.meta['date']:
103 format = '%Y-%m-%d|%H:%M'
104 else:
105 format = '%Y-%m-%d'
107 return datetime.datetime(
108 *time.strptime(self.meta['date'],
109 format)[0:6])
111 def tags(self):
112 tags = self.meta.get('tags', '').split()
113 tags.sort()
114 return tags
116 def url(self):
117 path_info = htmlpath(self.rstfile[len(options.rstdir):])
118 return options.baseurl + path_info
120 def commentable(self):
121 return False
123 class BlogPost(Page):
125 def commentable(self):
126 return True
128 def __cmp__(self, other):
129 return cmp(self.date(), other.date())
131 def __repr__(self):
132 return '<BlogPost "%s" on %s>' % (self.title, self.date())
135 # This is a temporary hack - to extract the 'custom fields' from
136 # docutil's `docinfo` node.
138 # `HTMLTranslator` happens to process the docinfo tree and so we can steal
139 # the key,value pairs of custom fields from the `visit_field_name` function.
140 class DocInfoProxy(HTMLTranslator):
142 ITEMS = {}
144 def __init__(self, *args, **kwargs):
145 HTMLTranslator.__init__(self, *args, **kwargs)
146 DocInfoProxy.ITEMS = {}
147 self.__items = {}
149 # Custom fields are processed here (Tags and so on)
150 def visit_field_name(self, node):
151 key = node.astext().lower()
152 value = node.parent.children[1].astext() # from sibling
153 # print 'meta[%s] = {%s}' % (key, value)
154 DocInfoProxy.ITEMS[key] = value
155 return HTMLTranslator.visit_field_name(self, node)
157 # Standard fields are processed here (Date, Author and so on)
158 def visit_docinfo_item(self, node, name, meta=1):
159 key = name.lower()
160 value = node.parent.children[0].astext() # from sibling
161 # print '*meta[%s] = {%s}' % (key, value)
162 DocInfoProxy.ITEMS[key] = value
163 return HTMLTranslator.visit_docinfo_item(self, node, name, meta)
166 # gnu make like functinality
167 def last_modified(filename):
168 return os.stat(filename).st_mtime
170 def make(target, deps, content_generator, *args, **kwargs):
171 if options.regen or \
172 True in [last_modified(target) < last_modified(d) for d in deps]:
173 # requires update
174 print '>', target
175 content = content_generator(*args, **kwargs)
176 open(target, 'w').write(content)
179 def rstfiles(directory):
180 for dir, subdirs, files in os.walk(directory):
181 for file in files:
182 if file.endswith('.rst'):
183 yield abspath(join(dir, file))
185 def mako():
186 templates = TemplateLookup(directories=[options.templatedir])
187 def render(name, **kwargs):
188 return templates.get_template(name).render(**kwargs)
189 return render
190 mako = mako()
193 if __name__ == '__main__':
194 blog_posts = []
195 blog_path = abspath(join(options.rstdir, 'blog'))
196 for f in rstfiles(options.rstdir):
197 if blog_path in f:
198 page = BlogPost(f)
199 blog_posts.append(page)
200 else:
201 page = Page(f)
203 make(htmlpath(f), [f],
204 mako, 'page.mako', **locals())
206 # generate blog index
207 blog_posts.sort()
208 blog_posts.reverse()
209 make(join(options.rstdir, 'blog', 'index.html'),
210 [p.rstfile for p in blog_posts],
211 mako, 'blog/index.mako', **locals())