blob: 78aacf864281050068ffb3f48c23ca3d9100372e [file] [log] [blame]
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +02001# -*- 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"""
Armin Ronacher9d472df2008-05-04 19:56:34 +020011import os
Armin Ronacher157531b2008-04-28 16:14:03 +020012import re
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +020013import inspect
Armin Ronacher9d472df2008-05-04 19:56:34 +020014import jinja2
15from itertools import islice
Armin Ronacher157531b2008-04-28 16:14:03 +020016from types import BuiltinFunctionType
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +020017from docutils import nodes
18from docutils.statemachine import ViewList
19from sphinx.ext.autodoc import prepare_docstring
Armin Ronacher9d472df2008-05-04 19:56:34 +020020from sphinx.application import TemplateBridge
Armin Ronacher157531b2008-04-28 16:14:03 +020021from pygments.style import Style
22from pygments.token import Keyword, Name, Comment, String, Error, \
23 Number, Operator, Generic
Armin Ronacher9d472df2008-05-04 19:56:34 +020024from jinja2 import Environment, FileSystemLoader
Armin Ronacher157531b2008-04-28 16:14:03 +020025
26
27class JinjaStyle(Style):
28 title = 'Jinja Style'
29 default_style = ""
30 styles = {
31 Comment: 'italic #aaaaaa',
32 Comment.Preproc: 'noitalic #B11414',
33 Comment.Special: 'italic #505050',
34
35 Keyword: 'bold #B80000',
36 Keyword.Type: '#808080',
37
38 Operator.Word: '#333333',
39
40 Name.Builtin: '#333333',
41 Name.Function: '#333333',
42 Name.Class: 'bold #333333',
43 Name.Namespace: 'bold #333333',
44 Name.Entity: 'bold #363636',
45 Name.Attribute: '#686868',
46 Name.Tag: 'bold #686868',
47 Name.Decorator: '#686868',
48
Armin Ronacher7259c762008-04-30 13:03:59 +020049 String: '#AA891C',
Armin Ronacherb2a36aa2008-04-28 19:57:40 +020050 Number: '#444444',
Armin Ronacher157531b2008-04-28 16:14:03 +020051
52 Generic.Heading: 'bold #000080',
53 Generic.Subheading: 'bold #800080',
54 Generic.Deleted: '#aa0000',
55 Generic.Inserted: '#00aa00',
56 Generic.Error: '#aa0000',
57 Generic.Emph: 'italic',
58 Generic.Strong: 'bold',
59 Generic.Prompt: '#555555',
60 Generic.Output: '#888888',
61 Generic.Traceback: '#aa0000',
62
63 Error: '#F00 bg:#FAA'
64 }
65
Armin Ronacher9d472df2008-05-04 19:56:34 +020066
67class Jinja2Bridge(TemplateBridge):
68
69 def init(self, builder):
70 path = builder.config.templates_path
71 self.env = Environment(loader=FileSystemLoader(path))
72
73 def render(self, template, context):
74 return self.env.get_template(template).render(context)
75
76
Armin Ronacher157531b2008-04-28 16:14:03 +020077_sig_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*(\(.*?\))')
78
79
80def format_function(name, aliases, func):
81 lines = inspect.getdoc(func).splitlines()
82 signature = '()'
83 if isinstance(func, BuiltinFunctionType):
84 match = _sig_re.match(lines[0])
85 if match is not None:
86 del lines[:1 + bool(lines and not lines[0])]
87 signature = match.group(1)
88 else:
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +020089 try:
Armin Ronacher157531b2008-04-28 16:14:03 +020090 argspec = inspect.getargspec(func)
91 if getattr(func, 'environmentfilter', False) or \
92 getattr(func, 'contextfilter', False):
93 del argspec[0][0]
94 signature = inspect.formatargspec(*argspec)
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +020095 except:
Armin Ronacher157531b2008-04-28 16:14:03 +020096 pass
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +020097 result = ['.. function:: %s%s' % (name, signature), '']
Armin Ronacher157531b2008-04-28 16:14:03 +020098 result.extend(' ' + line for line in lines)
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +020099 if aliases:
100 result.extend(('', ' :aliases: %s' % ', '.join(
101 '``%s``' % x for x in sorted(aliases))))
102 return result
103
104
Armin Ronacher157531b2008-04-28 16:14:03 +0200105def dump_functions(mapping):
106 def directive(dirname, arguments, options, content, lineno,
107 content_offset, block_text, state, state_machine):
108 reverse_mapping = {}
109 for name, func in mapping.iteritems():
110 reverse_mapping.setdefault(func, []).append(name)
111 filters = []
112 for func, names in reverse_mapping.iteritems():
113 aliases = sorted(names, key=lambda x: len(x))
114 name = aliases.pop()
115 filters.append((name, aliases, func))
116 filters.sort()
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +0200117
Armin Ronacher157531b2008-04-28 16:14:03 +0200118 result = ViewList()
119 for name, aliases, func in filters:
120 for item in format_function(name, aliases, func):
121 result.append(item, '<jinjaext>')
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +0200122
Armin Ronacher157531b2008-04-28 16:14:03 +0200123 node = nodes.paragraph()
124 state.nested_parse(result, content_offset, node)
125 return node.children
126 return directive
127
128
Armin Ronacher9d472df2008-05-04 19:56:34 +0200129def jinja_changelog(dirname, arguments, options, content, lineno,
130 content_offset, block_text, state, state_machine):
131 doc = ViewList()
132 changelog = file(os.path.join(os.path.dirname(jinja2.__file__), '..',
133 'CHANGES'))
134 try:
135 for line in islice(changelog, 3, None):
136 doc.append(line.rstrip(), '<jinjaext>')
137 finally:
138 changelog.close()
139 node = nodes.section()
140 # hack around title style bookkeeping
141 surrounding_title_styles = state.memo.title_styles
142 surrounding_section_level = state.memo.section_level
143 state.memo.title_styles = []
144 state.memo.section_level = 0
145 state.nested_parse(doc, content_offset, node, match_titles=1)
146 state.memo.title_styles = surrounding_title_styles
147 state.memo.section_level = surrounding_section_level
148 return node.children
149
150
Armin Ronacher157531b2008-04-28 16:14:03 +0200151from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS
152jinja_filters = dump_functions(DEFAULT_FILTERS)
153jinja_tests = dump_functions(DEFAULT_TESTS)
Armin Ronacher3c8b7ad2008-04-28 13:52:21 +0200154
155
156def setup(app):
Armin Ronacher157531b2008-04-28 16:14:03 +0200157 app.add_directive('jinjafilters', jinja_filters, 0, (0, 0, 0))
158 app.add_directive('jinjatests', jinja_tests, 0, (0, 0, 0))
Armin Ronacher9d472df2008-05-04 19:56:34 +0200159 app.add_directive('jinjachangelog', jinja_changelog, 0, (0, 0, 0))