some more documentation updates and minor code cleanups. Additionally True and true...
[jinja2.git] / docs / jinjaext.py
1 # -*- coding: utf-8 -*-
2 """
3     Jinja Documentation Extensions
4     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6     Support for automatically documenting filters and tests.
7
8     :copyright: Copyright 2008 by Armin Ronacher.
9     :license: BSD.
10 """
11 import os
12 import re
13 import inspect
14 import jinja2
15 from itertools import islice
16 from types import BuiltinFunctionType
17 from docutils import nodes
18 from docutils.statemachine import ViewList
19 from sphinx.ext.autodoc import prepare_docstring
20 from sphinx.application import TemplateBridge
21 from pygments.style import Style
22 from pygments.token import Keyword, Name, Comment, String, Error, \
23      Number, Operator, Generic
24 from jinja2 import Environment, FileSystemLoader
25
26
27 def parse_rst(state, content_offset, doc):
28     node = nodes.section()
29     # hack around title style bookkeeping
30     surrounding_title_styles = state.memo.title_styles
31     surrounding_section_level = state.memo.section_level
32     state.memo.title_styles = []
33     state.memo.section_level = 0
34     state.nested_parse(doc, content_offset, node, match_titles=1)
35     state.memo.title_styles = surrounding_title_styles
36     state.memo.section_level = surrounding_section_level
37     return node.children
38
39
40 class JinjaStyle(Style):
41     title = 'Jinja Style'
42     default_style = ""
43     styles = {
44         Comment:                    'italic #aaaaaa',
45         Comment.Preproc:            'noitalic #B11414',
46         Comment.Special:            'italic #505050',
47
48         Keyword:                    'bold #B80000',
49         Keyword.Type:               '#808080',
50
51         Operator.Word:              'bold #B80000',
52
53         Name.Builtin:               '#333333',
54         Name.Function:              '#333333',
55         Name.Class:                 'bold #333333',
56         Name.Namespace:             'bold #333333',
57         Name.Entity:                'bold #363636',
58         Name.Attribute:             '#686868',
59         Name.Tag:                   'bold #686868',
60         Name.Decorator:             '#686868',
61
62         String:                     '#AA891C',
63         Number:                     '#444444',
64
65         Generic.Heading:            'bold #000080',
66         Generic.Subheading:         'bold #800080',
67         Generic.Deleted:            '#aa0000',
68         Generic.Inserted:           '#00aa00',
69         Generic.Error:              '#aa0000',
70         Generic.Emph:               'italic',
71         Generic.Strong:             'bold',
72         Generic.Prompt:             '#555555',
73         Generic.Output:             '#888888',
74         Generic.Traceback:          '#aa0000',
75
76         Error:                      '#F00 bg:#FAA'
77     }
78
79
80 class Jinja2Bridge(TemplateBridge):
81
82     def init(self, builder):
83         path = builder.config.templates_path
84         self.env = Environment(loader=FileSystemLoader(path))
85
86     def render(self, template, context):
87         return self.env.get_template(template).render(context)
88
89
90 _sig_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*(\(.*?\))')
91
92
93 def format_function(name, aliases, func):
94     lines = inspect.getdoc(func).splitlines()
95     signature = '()'
96     if isinstance(func, BuiltinFunctionType):
97         match = _sig_re.match(lines[0])
98         if match is not None:
99             del lines[:1 + bool(lines and not lines[0])]
100             signature = match.group(1)
101     else:
102         try:
103             argspec = inspect.getargspec(func)
104             if getattr(func, 'environmentfilter', False) or \
105                getattr(func, 'contextfilter', False):
106                 del argspec[0][0]
107             signature = inspect.formatargspec(*argspec)
108         except:
109             pass
110     result = ['.. function:: %s%s' % (name, signature), '']
111     result.extend('    ' + line for line in lines)
112     if aliases:
113         result.extend(('', '    :aliases: %s' % ', '.join(
114                       '``%s``' % x for x in sorted(aliases))))
115     return result
116
117
118 def dump_functions(mapping):
119     def directive(dirname, arguments, options, content, lineno,
120                       content_offset, block_text, state, state_machine):
121         reverse_mapping = {}
122         for name, func in mapping.iteritems():
123             reverse_mapping.setdefault(func, []).append(name)
124         filters = []
125         for func, names in reverse_mapping.iteritems():
126             aliases = sorted(names, key=lambda x: len(x))
127             name = aliases.pop()
128             filters.append((name, aliases, func))
129         filters.sort()
130
131         result = ViewList()
132         for name, aliases, func in filters:
133             for item in format_function(name, aliases, func):
134                 result.append(item, '<jinjaext>')
135
136         node = nodes.paragraph()
137         state.nested_parse(result, content_offset, node)
138         return node.children
139     return directive
140
141
142 def jinja_changelog(dirname, arguments, options, content, lineno,
143                     content_offset, block_text, state, state_machine):
144     doc = ViewList()
145     changelog = file(os.path.join(os.path.dirname(jinja2.__file__), '..',
146                                   'CHANGES'))
147     try:
148         for line in islice(changelog, 3, None):
149             doc.append(line.rstrip(), '<jinjaext>')
150     finally:
151         changelog.close()
152     return parse_rst(state, content_offset, doc)
153
154
155 from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS
156 jinja_filters = dump_functions(DEFAULT_FILTERS)
157 jinja_tests = dump_functions(DEFAULT_TESTS)
158
159
160 def jinja_nodes(dirname, arguments, options, content, lineno,
161                 content_offset, block_text, state, state_machine):
162     from jinja2.nodes import Node
163     doc = ViewList()
164     def walk(node, indent):
165         p = ' ' * indent
166         sig = ', '.join(node.fields)
167         doc.append(p + '.. autoclass:: %s(%s)' % (node.__name__, sig), '')
168         if node.abstract:
169             members = []
170             for key, name in node.__dict__.iteritems():
171                 if not key.startswith('_') and \
172                    not hasattr(node.__base__, key) and callable(name):
173                     members.append(key)
174             if members:
175                 members.sort()
176                 doc.append('%s :members: %s' % (p, ', '.join(members)), '')
177         if node.__base__ != object:
178             doc.append('', '')
179             doc.append('%s :Node type: :class:`%s`' %
180                        (p, node.__base__.__name__), '')
181             # XXX: sphinx bug?  Expr gives a rst warning
182         doc.append('', '')
183         children = node.__subclasses__()
184         children.sort(key=lambda x: x.__name__.lower())
185         for child in children:
186             walk(child, indent)
187     walk(Node, 0)
188     return parse_rst(state, content_offset, doc)
189
190
191 def setup(app):
192     app.add_directive('jinjafilters', jinja_filters, 0, (0, 0, 0))
193     app.add_directive('jinjatests', jinja_tests, 0, (0, 0, 0))
194     app.add_directive('jinjachangelog', jinja_changelog, 0, (0, 0, 0))
195     app.add_directive('jinjanodes', jinja_nodes, 0, (0, 0, 0))