Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / doc / _sphinxext / chromesite_builder.py
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.
4
5 #
6 # This is a Sphinx extension.
7 #
8
9 from __future__ import print_function
10 import codecs
11 from collections import namedtuple, OrderedDict
12 import os
13 import string
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
21
22 # PEPPER_VERSION = "31"
23
24 # TODO(eliben): it may be interesting to use an actual Sphinx template here at
25 # some point.
26 PAGE_TEMPLATE = string.Template(r'''
27 {{+bindTo:partials.${doc_template}}}
28
29 ${doc_body}
30
31 {{/partials.${doc_template}}}
32 '''.lstrip())
33
34
35 # Path to the top-level YAML table-of-contents file for the chromesite
36 BOOK_TOC_TEMPLATE = '_book_template.yaml'
37
38
39 class ChromesiteHTMLTranslator(HTMLTranslator):
40   """ Custom HTML translator for chromesite output.
41
42       Hooked into the HTML builder by setting the html_translator_class
43       option in conf.py
44
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.
50   """
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)
55
56     self.within_toc = False
57
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'}))
61
62   def depart_bullet_list(self, node):
63     # Override to not pop anything from context
64     self.body.append('</ul>\n')
65
66   def visit_literal(self, node):
67     # Don't insert "smart" quotes here
68     self.no_smarty += 1
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>
71     # tags for them.
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=''))
75
76   def depart_literal(self, node):
77     self.no_smarty -= 1
78     self.body.append('</code>')
79
80   def visit_literal_block(self, node):
81     # Don't insert "smart" quotes here
82     self.no_smarty += 1
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))
88
89   def depart_literal_block(self, node):
90     self.no_smarty -= 1
91     self.body.append('\n</pre>\n')
92
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'][:]
99
100     HTMLTranslator.visit_title(self, node)
101
102
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'))
108
109   def depart_section(self, node):
110     if self.section_level == 1:
111       self.body.append('</section>')
112     self.section_level -= 1
113
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)
120
121   def visit_reference(self, node):
122     # In "kill_internal_links" mode, we don't emit the actual links for internal
123     # nodes.
124     if self.builder.chromesite_kill_internal_links and node.get('internal'):
125       pass
126     else:
127       HTMLTranslator.visit_reference(self, node)
128
129   def depart_reference(self, node):
130     if self.builder.chromesite_kill_internal_links and node.get('internal'):
131       pass
132     else:
133       HTMLTranslator.depart_reference(self, node)
134
135   def visit_topic(self, node):
136     if 'contents' in node['classes']:
137       # TODO(binji):
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
140       # here.
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))
145     else:
146       HTMLTranslator.visit_topic(self, node)
147
148   def depart_topic(self, node):
149     if self.within_toc:
150       self.body.append('\n</div>')
151     else:
152       HTMLTranslator.visit_topic(self, node)
153
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.
157     pass
158
159   def visit_admonition(self, node, name=''):
160     self.body.append(self.starttag(node, 'aside', CLASS=node.get('class', '')))
161
162   def depart_admonition(self, node=''):
163     self.body.append('\n</aside>\n')
164
165   def unknown_visit(self, node):
166     raise NotImplementedError('Unknown node: ' + node.__class__.__name__)
167
168
169 class ChromesiteBuilder(StandaloneHTMLBuilder):
170   """ Builder for the NaCl chromesite HTML output.
171
172       Loosely based on the code of Sphinx's standard SerializingHTMLBuilder.
173   """
174   name = 'chromesite'
175   out_suffix = '.html'
176   link_suffix = '.html'
177
178   # Disable the addition of "pi"-permalinks to each section header
179   add_permalinks = False
180
181   def init(self):
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 = ''
188     self.tags_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()
193
194   def finish(self):
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))
205     self.info()
206
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()
211     pass
212
213   def get_production_url(self, url):
214     # if not self.chromesite_production_mode:
215     #   return url
216
217     return '/native-client/%s' % url
218
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
222     # else:
223     #   return docname + self.link_suffix
224
225   def handle_page(self, pagename, ctx, templatename='page.html',
226                   outfilename=None, event_arg=None):
227     ctx['current_page_name'] = pagename
228
229     if not outfilename:
230       outfilename = os.path.join(self.outdir,
231                                  pagename + self.out_suffix)
232
233     # Emit an event to Sphinx
234     self.app.emit('html-page-context', pagename, templatename,
235                   ctx, event_arg)
236
237     ensuredir(os.path.dirname(outfilename))
238     self._dump_context(ctx, outfilename)
239
240   def _dump_context(self, context, filename):
241     """ Do the actual dumping of the page to the file. context is a dict. Some
242         important fields:
243           body - document contents
244           title
245           current_page_name
246         Some special pages (genindex, etc.) may not have some of the fields, so
247         fetch them conservatively.
248     """
249     if not 'body' in context:
250       return
251
252     template = context.get('meta', {}).get('template', 'standard_nacl_article')
253     title = context.get('title', '')
254     body = context.get('body', '')
255
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,
261         doc_title=title,
262         doc_body=body))
263
264   def _conditional_chromesite(self, s):
265     # return s if self.chromesite_production_mode else ''
266     return s
267
268   def _conditional_nonprod(self, s):
269     # return s if not self.chromesite_production_mode else ''
270     return ''
271
272
273 class NaclCodeDirective(Directive):
274   """ Custom "naclcode" directive for code snippets. To keep it under our
275       control.
276   """
277   has_content = True
278   required_arguments = 0
279   optional_arguments = 1
280   option_spec = {
281       'prettyprint': int,
282   }
283
284   def run(self):
285     code = u'\n'.join(self.content)
286     literal = nodes.literal_block(code, code)
287     literal['prettyprint'] = self.options.get('prettyprint', 1)
288     return [literal]
289
290 def setup(app):
291   """ Extension registration hook.
292   """
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
298   import urllib2
299   linkcheck.HeadRequest = urllib2.Request
300
301   app.add_directive('naclcode', NaclCodeDirective)
302   app.add_builder(ChromesiteBuilder)
303
304   # "Production mode" for local testing vs. on-server documentation.
305   app.add_config_value('chromesite_kill_internal_links', default='0',
306                        rebuild='html')