blob: 6b6b0ac7d8ad87f1f53a979f0d3f43ee2d1fa7b3 [file] [log] [blame]
Armin Ronacher37a88512007-03-02 20:42:18 +01001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3"""
4 Generate Jinja Documentation
5 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6
7 Generates a bunch of html files containing the documentation.
8
9 :copyright: 2006-2007 by Armin Ronacher, Georg Brandl.
10 :license: BSD, see LICENSE for more details.
11"""
12import os
13import sys
14sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
15import re
16import inspect
17from datetime import datetime
18from cgi import escape
19
20from docutils import nodes
21from docutils.parsers.rst import directives
22from docutils.core import publish_parts
23from docutils.writers import html4css1
24
25from jinja import Environment
26
27from pygments import highlight
28from pygments.lexers import get_lexer_by_name
29from pygments.formatters import HtmlFormatter
30
31def generate_list_of_filters():
32 from jinja.filters import FILTERS
33 result = []
34
35 filters = {}
36 for name, f in FILTERS.iteritems():
37 if not f in filters:
38 filters[f] = ([name], inspect.getdoc(f))
39 else:
40 filters[f][0].append(name)
41 for names, _ in filters.itervalues():
42 names.sort(key=lambda x: -len(x))
43
44 for names, doc in sorted(filters.values(), key=lambda x: x[0][0].lower()):
45 name = names[0]
46 if len(names) > 1:
47 aliases = '\n\n :Aliases: %s\n' % ', '.join(names[1:])
48 else:
49 aliases = ''
50
51 doclines = []
52 for line in doc.splitlines():
53 doclines.append(' ' + line)
54 doc = '\n'.join(doclines)
55 result.append('`%s`\n%s%s' % (name, doc, aliases))
56
57 return '\n'.join(result)
58
59def generate_list_of_tests():
60 from jinja.tests import TESTS
61 result = []
62
63 tests = {}
64 for name, f in TESTS.iteritems():
65 if not f in tests:
66 tests[f] = ([name], inspect.getdoc(f))
67 else:
68 tests[f][0].append(name)
69 for names, _ in tests.itervalues():
70 names.sort(key=lambda x: -len(x))
71
72 for names, doc in sorted(tests.values(), key=lambda x: x[0][0].lower()):
73 name = names[0]
74 if len(names) > 1:
75 aliases = '\n\n :Aliases: %s\n' % ', '.join(names[1:])
76 else:
77 aliases = ''
78
79 doclines = []
80 for line in doc.splitlines():
81 doclines.append(' ' + line)
82 doc = '\n'.join(doclines)
83 result.append('`%s`\n%s%s' % (name, doc, aliases))
84
85 return '\n'.join(result)
86
87def generate_list_of_loaders():
88 from jinja import loaders as loader_module
89
90 result = []
91 loaders = []
92 for item in loader_module.__all__:
93 loaders.append(getattr(loader_module, item))
94 loaders.sort(key=lambda x: x.__name__.lower())
95
96 for loader in loaders:
97 doclines = []
98 for line in inspect.getdoc(loader).splitlines():
99 doclines.append(' ' + line)
100 result.append('`%s`\n%s' % (loader.__name__, '\n'.join(doclines)))
101
102 return '\n\n'.join(result)
103
Armin Ronacherdb69d0a2007-06-02 01:35:53 +0200104def generate_list_of_baseloaders():
105 from jinja import loaders as loader_module
106
107 result = []
108 loaders = []
109 for item in dir(loader_module):
110 obj = getattr(loader_module, item)
111 try:
112 if issubclass(obj, loader_module.BaseLoader) and \
113 obj.__name__ != 'BaseLoader' and \
114 obj.__name__ not in loader_module.__all__:
115 loaders.append(obj)
116 except TypeError:
117 pass
118 loaders.sort(key=lambda x: x.__name__.lower())
119
120 for loader in loaders:
121 doclines = []
122 for line in inspect.getdoc(loader).splitlines():
123 doclines.append(' ' + line)
124 result.append('`%s`\n%s' % (loader.__name__, '\n'.join(doclines)))
125
126 return '\n\n'.join(result)
127
Armin Ronacher5a8e4972007-04-05 11:21:38 +0200128def generate_environment_doc():
129 from jinja.environment import Environment
130 return '%s\n\n%s' % (
131 inspect.getdoc(Environment),
132 inspect.getdoc(Environment.__init__)
133 )
134
Armin Ronacher37a88512007-03-02 20:42:18 +0100135e = Environment()
136
137PYGMENTS_FORMATTER = HtmlFormatter(style='pastie', cssclass='syntax')
138
139LIST_OF_FILTERS = generate_list_of_filters()
140LIST_OF_TESTS = generate_list_of_tests()
141LIST_OF_LOADERS = generate_list_of_loaders()
Armin Ronacherdb69d0a2007-06-02 01:35:53 +0200142LIST_OF_BASELOADERS = generate_list_of_baseloaders()
Armin Ronacher5a8e4972007-04-05 11:21:38 +0200143ENVIRONMENT_DOC = generate_environment_doc()
Armin Ronachera38b3122007-04-15 00:49:13 +0200144CHANGELOG = file(os.path.join(os.path.dirname(__file__), os.pardir, 'CHANGES'))\
145 .read().decode('utf-8')
Armin Ronacher37a88512007-03-02 20:42:18 +0100146
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100147FULL_TEMPLATE = e.from_string('''\
Armin Ronacher37a88512007-03-02 20:42:18 +0100148<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
149 "http://www.w3.org/TR/html4/strict.dtd">
150<html>
151<head>
152 <title>{{ title }} &mdash; Jinja Documentation</title>
153 <meta http-equiv="content-type" content="text/html; charset=utf-8">
154 <link rel="stylesheet" href="style.css" type="text/css">
155 <style type="text/css">
156 {{ style|e }}
157 </style>
158</head>
159<body>
160 <div id="content">
161 {% if file_id == 'index' %}
162 <div id="jinjalogo"></div>
163 <h2 class="subheading plain">{{ title }}</h2>
164 {% else %}
165 <h1 class="heading"><span>Jinja</span></h1>
166 <h2 class="subheading">{{ title }}</h2>
167 {% endif %}
168 {% if file_id != 'index' or toc %}
169 <div id="toc">
170 <h2>Navigation</h2>
171 <ul>
172 <li><a href="index.html">back to index</a></li>
173 </ul>
174 {% if toc %}
175 <h2>Contents</h2>
176 <ul class="contents">
177 {% for key, value in toc %}
178 <li><a href="{{ key }}">{{ value }}</a></li>
179 {% endfor %}
180 </ul>
181 {% endif %}
182 </div>
183 {% endif %}
184 <div id="contentwrapper">
185 {{ body }}
186 </div>
187 </div>
188</body>
189<!-- generated on: {{ generation_date }}
190 file id: {{ file_id }} -->
191</html>\
192''')
193
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100194PREPROC_TEMPLATE = e.from_string('''\
195<!-- TITLE -->{{ title }}<!-- ENDTITLE -->
196<!-- TOC -->{% for key, value in toc %}<li><a href="{{
197 key }}">{{ value }}</a></li>{% endfor %}<!-- ENDTOC -->
198<!-- BODY -->{{ body }}<!-- ENDBODY -->\
199''')
200
Armin Ronacher37a88512007-03-02 20:42:18 +0100201def pygments_directive(name, arguments, options, content, lineno,
202 content_offset, block_text, state, state_machine):
203 try:
204 lexer = get_lexer_by_name(arguments[0])
205 except ValueError:
206 # no lexer found
207 lexer = get_lexer_by_name('text')
208 parsed = highlight(u'\n'.join(content), lexer, PYGMENTS_FORMATTER)
209 return [nodes.raw('', parsed, format="html")]
210pygments_directive.arguments = (1, 0, 1)
211pygments_directive.content = 1
212directives.register_directive('sourcecode', pygments_directive)
213
214
215def create_translator(link_style):
216 class Translator(html4css1.HTMLTranslator):
217 def visit_reference(self, node):
218 refuri = node.get('refuri')
219 if refuri is not None and '/' not in refuri and refuri.endswith('.txt'):
220 node['refuri'] = link_style(refuri[:-4])
221 html4css1.HTMLTranslator.visit_reference(self, node)
222 return Translator
223
224
225class DocumentationWriter(html4css1.Writer):
226
227 def __init__(self, link_style):
228 html4css1.Writer.__init__(self)
229 self.translator_class = create_translator(link_style)
230
231 def translate(self):
232 html4css1.Writer.translate(self)
233 # generate table of contents
234 contents = self.build_contents(self.document)
235 contents_doc = self.document.copy()
236 contents_doc.children = contents
237 contents_visitor = self.translator_class(contents_doc)
238 contents_doc.walkabout(contents_visitor)
239 self.parts['toc'] = self._generated_toc
240
241 def build_contents(self, node, level=0):
242 sections = []
243 i = len(node) - 1
244 while i >= 0 and isinstance(node[i], nodes.section):
245 sections.append(node[i])
246 i -= 1
247 sections.reverse()
248 toc = []
249 for section in sections:
250 try:
251 reference = nodes.reference('', '', refid=section['ids'][0], *section[0])
252 except IndexError:
253 continue
254 ref_id = reference['refid']
255 text = escape(reference.astext().encode('utf-8'))
256 toc.append((ref_id, text))
257
258 self._generated_toc = [('#%s' % href, caption) for href, caption in toc]
259 # no further processing
260 return []
261
262
263def generate_documentation(data, link_style):
264 writer = DocumentationWriter(link_style)
265 data = data.replace('[[list_of_filters]]', LIST_OF_FILTERS)\
266 .replace('[[list_of_tests]]', LIST_OF_TESTS)\
Armin Ronacher5a8e4972007-04-05 11:21:38 +0200267 .replace('[[list_of_loaders]]', LIST_OF_LOADERS)\
Armin Ronacherdb69d0a2007-06-02 01:35:53 +0200268 .replace('[[list_of_baseloaders]]', LIST_OF_BASELOADERS)\
Armin Ronachera38b3122007-04-15 00:49:13 +0200269 .replace('[[environment_doc]]', ENVIRONMENT_DOC)\
270 .replace('[[changelog]]', CHANGELOG)
Armin Ronacher37a88512007-03-02 20:42:18 +0100271 parts = publish_parts(
272 data,
273 writer=writer,
274 settings_overrides={
Armin Ronacher21580912007-04-17 17:13:10 +0200275 'initial_header_level': 2,
Armin Ronacher37a88512007-03-02 20:42:18 +0100276 'field_name_limit': 50,
277 }
278 )
279 return {
280 'title': parts['title'].encode('utf-8'),
281 'body': parts['body'].encode('utf-8'),
282 'toc': parts['toc']
283 }
284
285
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100286def handle_file(filename, fp, dst, preproc):
Armin Ronacher37a88512007-03-02 20:42:18 +0100287 now = datetime.now()
288 title = os.path.basename(filename)[:-4]
Armin Ronacherccf284b2007-05-21 16:44:26 +0200289 content = fp.read().decode('utf-8')
Armin Ronacher1f1823c2007-04-05 19:15:11 +0200290 suffix = not preproc and '.html' or ''
291 parts = generate_documentation(content, (lambda x: './%s%s' % (x, suffix)))
Armin Ronacher37a88512007-03-02 20:42:18 +0100292 result = file(os.path.join(dst, title + '.html'), 'w')
293 c = dict(parts)
294 c['style'] = PYGMENTS_FORMATTER.get_style_defs('.syntax')
295 c['generation_date'] = now
296 c['file_id'] = title
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100297 if preproc:
298 tmpl = PREPROC_TEMPLATE
299 else:
300 tmpl = FULL_TEMPLATE
301 result.write(tmpl.render(c).encode('utf-8'))
Armin Ronacher37a88512007-03-02 20:42:18 +0100302 result.close()
303
304
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100305def run(dst, preproc, sources=(), handle_file=handle_file):
Armin Ronacher37a88512007-03-02 20:42:18 +0100306 path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src'))
307 if not sources:
308 sources = [os.path.join(path, fn) for fn in os.listdir(path)]
309 for fn in sources:
310 if not os.path.isfile(fn):
311 continue
312 print 'Processing %s' % fn
313 f = open(fn)
314 try:
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100315 handle_file(fn, f, dst, preproc)
Armin Ronacher37a88512007-03-02 20:42:18 +0100316 finally:
317 f.close()
318
319
Armin Ronacher9356b7b2007-03-13 22:29:01 +0100320def main(dst='build/', preproc=False, *sources):
321 run(os.path.realpath(dst), str(preproc).lower() == 'true', sources)
Armin Ronacher37a88512007-03-02 20:42:18 +0100322
323
324if __name__ == '__main__':
325 main(*sys.argv[1:])