2
2
"""
3
3
Sphinx plugins for Django documentation.
4
4
"""
5
+ import os
6
+ import re
5
7
6
- import docutils .nodes
7
- import docutils .transforms
8
- import sphinx
9
- import sphinx .addnodes
10
- import sphinx .builder
11
- import sphinx .directives
12
- import sphinx .environment
13
- import sphinx .htmlwriter
8
+ from docutils import nodes , transforms
9
+ try :
10
+ import json
11
+ except ImportError :
12
+ try :
13
+ import simplejson as json
14
+ except ImportError :
15
+ try :
16
+ from django .utils import simplejson as json
17
+ except ImportError :
18
+ json = None
19
+
20
+ from sphinx import addnodes , roles , __version__ as sphinx_ver
21
+ from sphinx .builders .html import StandaloneHTMLBuilder
22
+ from sphinx .writers .html import SmartyPantsHTMLTranslator
23
+ from sphinx .util .console import bold
24
+ from sphinx .util .compat import Directive
14
25
15
- def populate_index_to_rebuilds (app , doctree ):
16
- app .builder .env .files_to_rebuild ['index' ] = set ([])
26
+ # RE for option descriptions without a '--' prefix
27
+ simple_option_desc_re = re .compile (
28
+ r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)' )
17
29
18
30
def setup (app ):
19
- app .connect ('doctree-read' , populate_index_to_rebuilds )
20
31
app .add_crossref_type (
21
32
directivename = "setting" ,
22
33
rolename = "setting" ,
@@ -47,79 +58,93 @@ def setup(app):
47
58
directivename = "django-admin-option" ,
48
59
rolename = "djadminopt" ,
49
60
indextemplate = u"pair: %s; django-admin コマンドラインオプション" ,
50
- parse_node = lambda env , sig , signode : sphinx . directives . parse_option_desc ( signode , sig ) ,
61
+ parse_node = parse_django_adminopt_node ,
51
62
)
52
- app .add_transform (SuppressBlockquotes )
53
-
54
- # Monkeypatch PickleHTMLBuilder so that it doesn't die in Sphinx 0.4.2
55
- if sphinx .__version__ == '0.4.2' :
56
- monkeypatch_pickle_builder ()
57
-
58
- class SuppressBlockquotes (docutils .transforms .Transform ):
59
- """
60
- Remove the default blockquotes that encase indented list, tables, etc.
61
- """
62
- default_priority = 300
63
-
64
- suppress_blockquote_child_nodes = (
65
- docutils .nodes .bullet_list ,
66
- docutils .nodes .enumerated_list ,
67
- docutils .nodes .definition_list ,
68
- docutils .nodes .literal_block ,
69
- docutils .nodes .doctest_block ,
70
- docutils .nodes .line_block ,
71
- docutils .nodes .table
72
- )
73
-
74
- def apply (self ):
75
- for node in self .document .traverse (docutils .nodes .block_quote ):
76
- if len (node .children ) == 1 and isinstance (node .children [0 ], self .suppress_blockquote_child_nodes ):
77
- node .replace_self (node .children [0 ])
63
+ app .add_config_value ('django_next_version' , '0.0' , True )
64
+ app .add_directive ('versionadded' , VersionDirective )
65
+ app .add_directive ('versionchanged' , VersionDirective )
66
+ app .add_builder (DjangoStandaloneHTMLBuilder )
67
+
78
68
79
- class DjangoHTMLTranslator (sphinx .htmlwriter .SmartyPantsHTMLTranslator ):
69
+ class VersionDirective (Directive ):
70
+ has_content = True
71
+ required_arguments = 1
72
+ optional_arguments = 1
73
+ final_argument_whitespace = True
74
+ option_spec = {}
75
+
76
+ def run (self ):
77
+ env = self .state .document .settings .env
78
+ arg0 = self .arguments [0 ]
79
+ is_nextversion = env .config .django_next_version == arg0
80
+ ret = []
81
+ node = addnodes .versionmodified ()
82
+ ret .append (node )
83
+ if not is_nextversion :
84
+ if len (self .arguments ) == 1 :
85
+ linktext = u'リリースノートを参照してください </releases/%s>' % (arg0 )
86
+ xrefs = roles .XRefRole ()('doc' , linktext , linktext , self .lineno , self .state )
87
+ node .extend (xrefs [0 ])
88
+ node ['version' ] = arg0
89
+ else :
90
+ node ['version' ] = "Development version"
91
+ node ['type' ] = self .name
92
+ if len (self .arguments ) == 2 :
93
+ inodes , messages = self .state .inline_text (self .arguments [1 ], self .lineno + 1 )
94
+ node .extend (inodes )
95
+ if self .content :
96
+ self .state .nested_parse (self .content , self .content_offset , node )
97
+ ret = ret + messages
98
+ env .note_versionchange (node ['type' ], node ['version' ], node , self .lineno )
99
+ return ret
100
+
101
+
102
+ class DjangoHTMLTranslator (SmartyPantsHTMLTranslator ):
80
103
"""
81
104
Django-specific reST to HTML tweaks.
82
105
"""
83
106
84
107
# Don't use border=1, which docutils does by default.
85
108
def visit_table (self , node ):
109
+ self ._table_row_index = 0 # Needed by Sphinx
86
110
self .body .append (self .starttag (node , 'table' , CLASS = 'docutils' ))
87
-
111
+
88
112
# <big>? Really?
89
113
def visit_desc_parameterlist (self , node ):
90
114
self .body .append ('(' )
91
115
self .first_param = 1
92
-
116
+ self .param_separator = node .child_text_separator
117
+
93
118
def depart_desc_parameterlist (self , node ):
94
119
self .body .append (')' )
95
- pass
96
-
97
- #
98
- # Don't apply smartypants to literal blocks
99
- #
100
- def visit_literal_block (self , node ):
101
- self .no_smarty += 1
102
- sphinx . htmlwriter . SmartyPantsHTMLTranslator .visit_literal_block (self , node )
103
-
104
- def depart_literal_block (self , node ):
105
- sphinx . htmlwriter . SmartyPantsHTMLTranslator .depart_literal_block (self , node )
106
- self .no_smarty -= 1
107
-
120
+
121
+ if sphinx_ver < '1.0.8' :
122
+ #
123
+ # Don't apply smartypants to literal blocks
124
+ #
125
+ def visit_literal_block (self , node ):
126
+ self .no_smarty += 1
127
+ SmartyPantsHTMLTranslator .visit_literal_block (self , node )
128
+
129
+ def depart_literal_block (self , node ):
130
+ SmartyPantsHTMLTranslator .depart_literal_block (self , node )
131
+ self .no_smarty -= 1
132
+
108
133
#
109
- # Turn the "new in version" stuff (versoinadded /versionchanged) into a
134
+ # Turn the "new in version" stuff (versionadded /versionchanged) into a
110
135
# better callout -- the Sphinx default is just a little span,
111
136
# which is a bit less obvious that I'd like.
112
137
#
113
- # FIXME: these messages are all hardcoded in English. We need to chanage
138
+ # FIXME: these messages are all hardcoded in English. We need to change
114
139
# that to accomodate other language docs, but I can't work out how to make
115
- # that work and I think it'll require Sphinx 0.5 anyway .
140
+ # that work.
116
141
#
117
142
version_text = {
118
143
'deprecated' : u'Django %s で撤廃されました' ,
119
144
'versionchanged' : u'Django %s で変更されました' ,
120
145
'versionadded' : u'Django %s で新たに登場しました' ,
121
146
}
122
-
147
+
123
148
def visit_versionmodified (self , node ):
124
149
self .body .append (
125
150
self .starttag (node , 'div' , CLASS = node ['type' ])
@@ -129,71 +154,77 @@ def visit_versionmodified(self, node):
129
154
len (node ) and ":" or "."
130
155
)
131
156
self .body .append ('<span class="title">%s</span> ' % title )
132
-
157
+
133
158
def depart_versionmodified (self , node ):
134
159
self .body .append ("</div>\n " )
135
-
160
+
136
161
# Give each section a unique ID -- nice for custom CSS hooks
137
- # This is different on docutils 0.5 vs. 0.4...
138
-
139
- # The docutils 0.4 override.
140
- if hasattr (sphinx .htmlwriter .SmartyPantsHTMLTranslator , 'start_tag_with_title' ):
141
- def start_tag_with_title (self , node , tagname , ** atts ):
142
- node = {
143
- 'classes' : node .get ('classes' , []),
144
- 'ids' : ['s-%s' % i for i in node .get ('ids' , [])]
145
- }
146
- return self .starttag (node , tagname , ** atts )
147
-
148
- # The docutils 0.5 override.
149
- else :
150
- def visit_section (self , node ):
151
- old_ids = node .get ('ids' , [])
152
- node ['ids' ] = ['s-' + i for i in old_ids ]
153
- sphinx .htmlwriter .SmartyPantsHTMLTranslator .visit_section (self , node )
154
- node ['ids' ] = old_ids
162
+ def visit_section (self , node ):
163
+ old_ids = node .get ('ids' , [])
164
+ node ['ids' ] = ['s-' + i for i in old_ids ]
165
+ node ['ids' ].extend (old_ids )
166
+ SmartyPantsHTMLTranslator .visit_section (self , node )
167
+ node ['ids' ] = old_ids
155
168
156
169
def parse_django_admin_node (env , sig , signode ):
157
170
command = sig .split (' ' )[0 ]
158
171
env ._django_curr_admin_command = command
159
172
title = "django-admin.py %s" % sig
160
- signode += sphinx . addnodes .desc_name (title , title )
173
+ signode += addnodes .desc_name (title , title )
161
174
return sig
162
175
163
- def monkeypatch_pickle_builder ():
164
- import shutil
165
- from os import path
166
- try :
167
- import cPickle as pickle
168
- except ImportError :
169
- import pickle
170
- from sphinx .util .console import bold
171
-
172
- def handle_finish (self ):
173
- # dump the global context
174
- outfilename = path .join (self .outdir , 'globalcontext.pickle' )
175
- f = open (outfilename , 'wb' )
176
- try :
177
- pickle .dump (self .globalcontext , f , 2 )
178
- finally :
179
- f .close ()
176
+ def parse_django_adminopt_node (env , sig , signode ):
177
+ """A copy of sphinx.directives.CmdoptionDesc.parse_signature()"""
178
+ from sphinx .domains .std import option_desc_re
179
+ count = 0
180
+ firstname = ''
181
+ for m in option_desc_re .finditer (sig ):
182
+ optname , args = m .groups ()
183
+ if count :
184
+ signode += addnodes .desc_addname (', ' , ', ' )
185
+ signode += addnodes .desc_name (optname , optname )
186
+ signode += addnodes .desc_addname (args , args )
187
+ if not count :
188
+ firstname = optname
189
+ count += 1
190
+ if not count :
191
+ for m in simple_option_desc_re .finditer (sig ):
192
+ optname , args = m .groups ()
193
+ if count :
194
+ signode += addnodes .desc_addname (', ' , ', ' )
195
+ signode += addnodes .desc_name (optname , optname )
196
+ signode += addnodes .desc_addname (args , args )
197
+ if not count :
198
+ firstname = optname
199
+ count += 1
200
+ if not firstname :
201
+ raise ValueError
202
+ return firstname
180
203
181
- self .info (bold ('dumping search index...' ))
182
- self .indexer .prune (self .env .all_docs )
183
- f = open (path .join (self .outdir , 'searchindex.pickle' ), 'wb' )
184
- try :
185
- self .indexer .dump (f , 'pickle' )
186
- finally :
187
- f .close ()
188
204
189
- # copy the environment file from the doctree dir to the output dir
190
- # as needed by the web app
191
- shutil . copyfile ( path . join ( self . doctreedir , sphinx . builder . ENV_PICKLE_FILENAME ),
192
- path . join ( self . outdir , sphinx . builder . ENV_PICKLE_FILENAME ))
205
+ class DjangoStandaloneHTMLBuilder ( StandaloneHTMLBuilder ):
206
+ """
207
+ Subclass to add some extra things we need.
208
+ """
193
209
194
- # touch 'last build' file, used by the web application to determine
195
- # when to reload its environment and clear the cache
196
- open (path .join (self .outdir , sphinx .builder .LAST_BUILD_FILENAME ), 'w' ).close ()
210
+ name = 'djangohtml'
197
211
198
- sphinx .builder .PickleHTMLBuilder .handle_finish = handle_finish
199
-
212
+ def finish (self ):
213
+ super (DjangoStandaloneHTMLBuilder , self ).finish ()
214
+ if json is None :
215
+ self .warn ("cannot create templatebuiltins.js due to missing simplejson dependency" )
216
+ return
217
+ self .info (bold ("writing templatebuiltins.js..." ))
218
+ xrefs = self .env .domaindata ["std" ]["objects" ]
219
+ templatebuiltins = {
220
+ "ttags" : [n for ((t , n ), (l , a )) in xrefs .items ()
221
+ if t == "templatetag" and l == "ref/templates/builtins" ],
222
+ "tfilters" : [n for ((t , n ), (l , a )) in xrefs .items ()
223
+ if t == "templatefilter" and l == "ref/templates/builtins" ],
224
+ }
225
+ outfilename = os .path .join (self .outdir , "templatebuiltins.js" )
226
+ f = open (outfilename , 'wb' )
227
+ f .write ('var django_template_builtins = ' )
228
+ json .dump (templatebuiltins , f )
229
+ f .write (';\n ' )
230
+ f .close ();
0 commit comments