1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
6 # This is a Sphinx extension.
9 from __future__
import print_function
11 from collections
import namedtuple
, OrderedDict
14 from docutils
import nodes
15 from docutils
.parsers
.rst
import Directive
, directives
16 from sphinx
.util
.osutil
import ensuredir
17 from sphinx
.builders
.html
import StandaloneHTMLBuilder
18 from sphinx
.writers
.html
import HTMLWriter
19 from sphinx
.writers
.html
import SmartyPantsHTMLTranslator
as HTMLTranslator
20 from sphinx
.util
.console
import bold
22 # PEPPER_VERSION = "31"
24 # TODO(eliben): it may be interesting to use an actual Sphinx template here at
26 PAGE_TEMPLATE
= string
.Template(r
'''
27 {{+bindTo:partials.${doc_template}}}
31 {{/partials.${doc_template}}}
35 # Path to the top-level YAML table-of-contents file for the chromesite
36 BOOK_TOC_TEMPLATE
= '_book_template.yaml'
39 class ChromesiteHTMLTranslator(HTMLTranslator
):
40 """ Custom HTML translator for chromesite output.
42 Hooked into the HTML builder by setting the html_translator_class
45 HTMLTranslator is provided by Sphinx. We're actually using
46 SmartyPantsHTMLTranslator to use its quote and dash-formatting
47 capabilities. It's a subclass of the HTMLTranslator provided by docutils,
48 with Sphinx-specific features added. Here we provide chromesite-specific
49 behavior by overriding some of the visiting methods.
51 def __init__(self
, builder
, *args
, **kwds
):
52 # HTMLTranslator is an old-style Python class, so 'super' doesn't work: use
53 # direct parent invocation.
54 HTMLTranslator
.__init
__(self
, builder
, *args
, **kwds
)
56 self
.within_toc
= False
58 def visit_bullet_list(self
, node
):
59 # Use our own class attribute for <ul>. Don't care about compacted lists.
60 self
.body
.append(self
.starttag(node
, 'ul', **{'class': 'small-gap'}))
62 def depart_bullet_list(self
, node
):
63 # Override to not pop anything from context
64 self
.body
.append('</ul>\n')
66 def visit_literal(self
, node
):
67 # Don't insert "smart" quotes here
69 # Sphinx emits <tt></tt> for literals (``like this``), with <span> per word
70 # to protect against wrapping, etc. We're required to emit plain <code>
72 # Emit a simple <code> tag without enabling "protect_literal_text" mode,
73 # so Sphinx's visit_Text doesn't mess with the contents.
74 self
.body
.append(self
.starttag(node
, 'code', suffix
=''))
76 def depart_literal(self
, node
):
78 self
.body
.append('</code>')
80 def visit_literal_block(self
, node
):
81 # Don't insert "smart" quotes here
83 # We don't use Sphinx's buildin pygments integration for code highlighting,
84 # because the chromesite requires special <pre> tags for that and handles
85 # the highlighting on its own.
86 attrs
= {'class': 'prettyprint'} if node
.get('prettyprint', 1) else {}
87 self
.body
.append(self
.starttag(node
, 'pre', **attrs
))
89 def depart_literal_block(self
, node
):
91 self
.body
.append('\n</pre>\n')
93 def visit_title(self
, node
):
94 if isinstance(node
.parent
, nodes
.section
):
95 # Steal the id from the parent. This is used in chromesite to handle the
96 # auto-generated navbar and permalinks.
97 if node
.parent
.hasattr('ids'):
98 node
['ids'] = node
.parent
['ids'][:]
100 HTMLTranslator
.visit_title(self
, node
)
103 def visit_section(self
, node
):
104 # chromesite needs <section> instead of <div class='section'>
105 self
.section_level
+= 1
106 if self
.section_level
== 1:
107 self
.body
.append(self
.starttag(node
, 'section'))
109 def depart_section(self
, node
):
110 if self
.section_level
== 1:
111 self
.body
.append('</section>')
112 self
.section_level
-= 1
114 def visit_image(self
, node
):
115 # Paths to images in .rst sources should be absolute. This visitor does the
116 # required transformation for the path to be correct in the final HTML.
117 # if self.builder.chromesite_production_mode:
118 node
['uri'] = self
.builder
.get_production_url(node
['uri'])
119 HTMLTranslator
.visit_image(self
, node
)
121 def visit_reference(self
, node
):
122 # In "kill_internal_links" mode, we don't emit the actual links for internal
124 if self
.builder
.chromesite_kill_internal_links
and node
.get('internal'):
127 HTMLTranslator
.visit_reference(self
, node
)
129 def depart_reference(self
, node
):
130 if self
.builder
.chromesite_kill_internal_links
and node
.get('internal'):
133 HTMLTranslator
.depart_reference(self
, node
)
135 def visit_topic(self
, node
):
136 if 'contents' in node
['classes']:
138 # Detect a TOC: we want to hide these from chromesite, but still keep
139 # them in devsite. An easy hack is to add display: none to the element
141 # When we remove devsite support, we can remove this hack.
142 self
.within_toc
= True
143 attrs
= {'style': 'display: none'}
144 self
.body
.append(self
.starttag(node
, 'div', **attrs
))
146 HTMLTranslator
.visit_topic(self
, node
)
148 def depart_topic(self
, node
):
150 self
.body
.append('\n</div>')
152 HTMLTranslator
.visit_topic(self
, node
)
154 def write_colspecs(self
):
155 # Override this method from docutils to do nothing. We don't need those
156 # pesky <col width=NN /> tags in our markup.
159 def visit_admonition(self
, node
, name
=''):
160 self
.body
.append(self
.starttag(node
, 'aside', CLASS
=node
.get('class', '')))
162 def depart_admonition(self
, node
=''):
163 self
.body
.append('\n</aside>\n')
165 def unknown_visit(self
, node
):
166 raise NotImplementedError('Unknown node: ' + node
.__class
__.__name
__)
169 class ChromesiteBuilder(StandaloneHTMLBuilder
):
170 """ Builder for the NaCl chromesite HTML output.
172 Loosely based on the code of Sphinx's standard SerializingHTMLBuilder.
176 link_suffix
= '.html'
178 # Disable the addition of "pi"-permalinks to each section header
179 add_permalinks
= False
182 self
.config
.html_translator_class
= \
183 'chromesite_builder.ChromesiteHTMLTranslator'
184 self
.chromesite_kill_internal_links
= \
185 int(self
.config
.chromesite_kill_internal_links
) == 1
186 self
.info("----> Chromesite builder")
187 self
.config_hash
= ''
189 self
.theme
= None # no theme necessary
190 self
.templates
= None # no template bridge necessary
191 self
.init_translator_class()
192 self
.init_highlighter()
195 super(ChromesiteBuilder
, self
).finish()
196 # if self.chromesite_production_mode:
197 # # We decided to keep the manual _book.yaml for now;
198 # # The code for auto-generating YAML TOCs from index.rst was removed in
199 # # https://codereview.chromium.org/57923006/
200 # self.info(bold('generating YAML table-of-contents... '))
201 # subs = { 'version': PEPPER_VERSION }
202 # with open(os.path.join(self.env.srcdir, '_book.yaml')) as in_f:
203 # with open(os.path.join(self.outdir, '_book.yaml'), 'w') as out_f:
204 # out_f.write(string.Template(in_f.read()).substitute(subs))
207 def dump_inventory(self
):
208 # We don't want an inventory file when building for chromesite
209 # if not self.chromesite_production_mode:
210 # super(ChromesiteBuilder, self).dump_inventory()
213 def get_production_url(self
, url
):
214 # if not self.chromesite_production_mode:
217 return '/native-client/%s' % url
219 def get_target_uri(self
, docname
, typ
=None):
220 # if self.chromesite_production_mode:
221 return self
.get_production_url(docname
) + self
.link_suffix
223 # return docname + self.link_suffix
225 def handle_page(self
, pagename
, ctx
, templatename
='page.html',
226 outfilename
=None, event_arg
=None):
227 ctx
['current_page_name'] = pagename
230 outfilename
= os
.path
.join(self
.outdir
,
231 pagename
+ self
.out_suffix
)
233 # Emit an event to Sphinx
234 self
.app
.emit('html-page-context', pagename
, templatename
,
237 ensuredir(os
.path
.dirname(outfilename
))
238 self
._dump
_context
(ctx
, outfilename
)
240 def _dump_context(self
, context
, filename
):
241 """ Do the actual dumping of the page to the file. context is a dict. Some
243 body - document contents
246 Some special pages (genindex, etc.) may not have some of the fields, so
247 fetch them conservatively.
249 if not 'body' in context
:
252 template
= context
.get('meta', {}).get('template', 'standard_nacl_article')
253 title
= context
.get('title', '')
254 body
= context
.get('body', '')
256 # codecs.open is the fast Python 2.x way of emulating the encoding= argument
257 # in Python 3's builtin open.
258 with codecs
.open(filename
, 'w', encoding
='utf-8') as f
:
259 f
.write(PAGE_TEMPLATE
.substitute(
260 doc_template
=template
,
264 def _conditional_chromesite(self
, s
):
265 # return s if self.chromesite_production_mode else ''
268 def _conditional_nonprod(self
, s
):
269 # return s if not self.chromesite_production_mode else ''
273 class NaclCodeDirective(Directive
):
274 """ Custom "naclcode" directive for code snippets. To keep it under our
278 required_arguments
= 0
279 optional_arguments
= 1
285 code
= u
'\n'.join(self
.content
)
286 literal
= nodes
.literal_block(code
, code
)
287 literal
['prettyprint'] = self
.options
.get('prettyprint', 1)
291 """ Extension registration hook.
293 # linkcheck issues HEAD requests to save time, but some Google properties
294 # reject them and we get spurious 405 responses. Monkey-patch sphinx to
295 # just use normal GET requests.
296 # See: https://bitbucket.org/birkenfeld/sphinx/issue/1292/
297 from sphinx
.builders
import linkcheck
299 linkcheck
.HeadRequest
= urllib2
.Request
301 app
.add_directive('naclcode', NaclCodeDirective
)
302 app
.add_builder(ChromesiteBuilder
)
304 # "Production mode" for local testing vs. on-server documentation.
305 app
.add_config_value('chromesite_kill_internal_links', default
='0',