OLD | NEW |
(Empty) | |
| 1 # Copyright (c) 2013 The Native Client 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 import codecs |
| 10 import os |
| 11 import string |
| 12 from docutils import nodes |
| 13 from docutils.parsers.rst import Directive, directives |
| 14 from sphinx.util.osutil import ensuredir |
| 15 from sphinx.builders.html import StandaloneHTMLBuilder |
| 16 from sphinx.writers.html import HTMLWriter |
| 17 from sphinx.writers.html import SmartyPantsHTMLTranslator as HTMLTranslator |
| 18 |
| 19 |
| 20 # TODO(eliben): it may be interesting to use an actual Sphinx template here at |
| 21 # some point. |
| 22 PAGE_TEMPLATE = string.Template(r''' |
| 23 ${devsite_prefix} |
| 24 <html devsite> |
| 25 <head> |
| 26 <title>${doc_title}</title> |
| 27 <meta name="project_path" value="/native-client/_project.yaml" /> |
| 28 <meta name="book_path" value="/native-client/_book.yaml" /> |
| 29 <link href="/native-client/css/local_extensions.css" rel="stylesheet" type="
text/css"/> |
| 30 ${nonprod_css} |
| 31 <style type="text/css"> |
| 32 .nested-def {list-style-type: none; margin-bottom: 0.3em;} |
| 33 .maxheight {height: 200px;} |
| 34 </style> |
| 35 </head> |
| 36 <body> |
| 37 ${devsite_butterbar} |
| 38 |
| 39 ${doc_body} |
| 40 |
| 41 </body> |
| 42 </html> |
| 43 '''.lstrip()) |
| 44 |
| 45 DEVSITE_PREFIX = r''' |
| 46 {% setvar pepperversion %}pepper28{% endsetvar %} |
| 47 {% include "native-client/_local_variables.html" %} |
| 48 '''.lstrip() |
| 49 |
| 50 DEVSITE_BUTTERBAR = '{{butterbar}}' |
| 51 |
| 52 # We want the non-production-mode HTML to resemble the real one, so this points |
| 53 # to a copied version of the devsite CSS that we'll keep locally. It's for |
| 54 # testing purposes only. |
| 55 NONPROD_CSS = '<link href="/_static/css/local_extensions.css"'\ |
| 56 'rel="stylesheet" type="text/css"/>' |
| 57 |
| 58 |
| 59 class DevsiteHTMLTranslator(HTMLTranslator): |
| 60 """ Custom HTML translator for devsite output. |
| 61 |
| 62 Hooked into the HTML builder by setting the html_translator_class |
| 63 option in conf.py |
| 64 """ |
| 65 def __init__(self, builder, *args, **kwds): |
| 66 # HTMLTranslator is an old-style Python class, so 'super' doesn't work: use |
| 67 # direct parent invocation. |
| 68 HTMLTranslator.__init__(self, builder, *args, **kwds) |
| 69 |
| 70 self.within_ignored_h1 = False |
| 71 self.within_toc = False |
| 72 |
| 73 def visit_bullet_list(self, node): |
| 74 if self.within_toc: |
| 75 # Within a TOC, devsite wants <ol> |
| 76 self.body.append(self.starttag(node, 'ol')) |
| 77 else: |
| 78 # Use our own class attribute for <ul>. Don't care about compacted lists. |
| 79 self.body.append(self.starttag(node, 'ul', **{'class': 'small-gap'})) |
| 80 |
| 81 def depart_bullet_list(self, node): |
| 82 if self.within_toc: |
| 83 self.body.append('</ol>\n') |
| 84 else: |
| 85 # Override to not pop anything from context |
| 86 self.body.append('</ul>\n') |
| 87 |
| 88 def visit_literal_block(self, node): |
| 89 # We don't use Sphinx's buildin pygments integration for code highlighting, |
| 90 # because the devsite requires special <pre> tags for that and handles the |
| 91 # highlighting on its own. |
| 92 attrs = {'class': 'prettyprint'} if node.get('prettyprint', 1) else {} |
| 93 self.body.append(self.starttag(node, 'pre', **attrs)) |
| 94 |
| 95 def depart_literal_block(self, node): |
| 96 self.body.append('\n</pre>\n') |
| 97 |
| 98 def visit_paragraph(self, node): |
| 99 # Don't generate <p>s within the table of contents |
| 100 if not self.within_toc: |
| 101 HTMLTranslator.visit_paragraph(self, node) |
| 102 |
| 103 def depart_paragraph(self, node): |
| 104 if not self.within_toc: |
| 105 HTMLTranslator.depart_paragraph(self, node) |
| 106 |
| 107 def visit_section(self, node): |
| 108 # devsite needs <section> instead of <div class='section'> |
| 109 self.section_level += 1 |
| 110 self.body.append(self.starttag(node, 'section')) |
| 111 |
| 112 def depart_section(self, node): |
| 113 self.section_level -= 1 |
| 114 self.body.append('</section>') |
| 115 |
| 116 def visit_image(self, node): |
| 117 # Paths to images in .rst sources should be absolute. This visitor does the |
| 118 # required transformation for the path to be correct in the final HTML. |
| 119 if self.builder.devsite_production_mode: |
| 120 node['uri'] = '/native-client/' + node['uri'] |
| 121 HTMLTranslator.visit_image(self, node) |
| 122 |
| 123 def visit_title(self, node): |
| 124 # Why this? |
| 125 # |
| 126 # Sphinx insists on inserting a <h1>Page Title</h1> into the page, but this |
| 127 # is not how the devsite wants it. The devsite inserts the page title on |
| 128 # its own, the the extra h1 is duplication. |
| 129 # |
| 130 # Killing the doctree title node in a custom transform doesn't work, because |
| 131 # Sphinx explicitly looks for it when writing a document. So instead we rig |
| 132 # the HTML produced. |
| 133 # |
| 134 # When a title node is visited, and this is the h1-to-be, we ignore it and |
| 135 # also set a flag that tells visit_Text not to print the actual text of the |
| 136 # header. |
| 137 |
| 138 # The h1 node is in the section whose parent is the document itself. Other |
| 139 # sections have this top-section as their parent. |
| 140 if (node.parent and node.parent.parent and |
| 141 isinstance(node.parent.parent, nodes.document)): |
| 142 # Internal flag. Also, nothing is pushed to the context. Our depart_title |
| 143 # doesn't pop anything when this flag is set. |
| 144 self.within_ignored_h1 = True |
| 145 else: |
| 146 HTMLTranslator.visit_title(self, node) |
| 147 |
| 148 def depart_title(self, node): |
| 149 if not self.within_ignored_h1: |
| 150 HTMLTranslator.depart_title(self, node) |
| 151 self.within_ignored_h1 = False |
| 152 |
| 153 def visit_Text(self, node): |
| 154 if not self.within_ignored_h1: |
| 155 HTMLTranslator.visit_Text(self, node) |
| 156 |
| 157 def visit_topic(self, node): |
| 158 if 'contents' in node['classes']: |
| 159 # Detect a TOC: this requires special treatment for devsite. |
| 160 self.within_toc = True |
| 161 # Emit <nav> manually and not through starttage because we don't want |
| 162 # additional class components to be added |
| 163 self.body.append('\n<nav class="inline-toc">') |
| 164 |
| 165 def depart_topic(self, node): |
| 166 if self.within_toc: |
| 167 self.within_toc = False |
| 168 self.body.append('</nav>\n') |
| 169 |
| 170 def write_colspecs(self): |
| 171 # Override this method from docutils to do nothing. We don't need those |
| 172 # pesky <col width=NN /> tags in our markup. |
| 173 pass |
| 174 |
| 175 def visit_admonition(self, node, name=''): |
| 176 self.body.append(self.starttag(node, 'aside', CLASS=node.get('class', ''))) |
| 177 |
| 178 def depart_admonition(self, node=''): |
| 179 self.body.append('\n</aside>\n') |
| 180 |
| 181 def unknown_visit(self, node): |
| 182 raise NotImplementedError('Unknown node: ' + node.__class__.__name__) |
| 183 |
| 184 |
| 185 class DevsiteBuilder(StandaloneHTMLBuilder): |
| 186 """ Builder for the NaCl devsite HTML output. |
| 187 |
| 188 Loosely based on the code of Sphinx's standard SerializingHTMLBuilder. |
| 189 """ |
| 190 name = 'devsite' |
| 191 out_suffix = '.html' |
| 192 link_suffix = '.html' |
| 193 |
| 194 # Disable the addition of "pi"-permalinks to each section header |
| 195 add_permalinks = False |
| 196 |
| 197 def init(self): |
| 198 self.devsite_production_mode = int(self.config.devsite_production_mode) == 1 |
| 199 print "----> Devsite builder with production mode = %d" % ( |
| 200 self.devsite_production_mode,) |
| 201 self.config_hash = '' |
| 202 self.tags_hash = '' |
| 203 self.theme = None # no theme necessary |
| 204 self.templates = None # no template bridge necessary |
| 205 self.init_translator_class() |
| 206 self.init_highlighter() |
| 207 |
| 208 def get_target_uri(self, docname, typ=None): |
| 209 if self.devsite_production_mode: |
| 210 # TODO(eliben): testrst here will have to be replaced with |
| 211 # {{pepperversion}} |
| 212 return '/native-client/testrst/%s' % docname |
| 213 else: |
| 214 return docname + self.link_suffix |
| 215 |
| 216 def handle_page(self, pagename, ctx, templatename='page.html', |
| 217 outfilename=None, event_arg=None): |
| 218 ctx['current_page_name'] = pagename |
| 219 |
| 220 if not outfilename: |
| 221 outfilename = os.path.join(self.outdir, |
| 222 pagename + self.out_suffix) |
| 223 |
| 224 # Emit an event to Sphinx |
| 225 self.app.emit('html-page-context', pagename, templatename, |
| 226 ctx, event_arg) |
| 227 |
| 228 ensuredir(os.path.dirname(outfilename)) |
| 229 self._dump_context(ctx, outfilename) |
| 230 |
| 231 def _dump_context(self, context, filename): |
| 232 """ Do the actual dumping of the page to the file. context is a dict. Some |
| 233 important fields: |
| 234 body - document contents |
| 235 title |
| 236 current_page_name |
| 237 Some special pages (genindex, etc.) may not have some of the fields, so |
| 238 fetch them conservatively. |
| 239 """ |
| 240 if not 'body' in context: |
| 241 return |
| 242 # codecs.open is the fast Python 2.x way of emulating the encoding= argument |
| 243 # in Python 3's builtin open. |
| 244 with codecs.open(filename, 'w', encoding='utf-8') as f: |
| 245 f.write(PAGE_TEMPLATE.substitute( |
| 246 doc_title=context.get('title', ''), |
| 247 doc_body=context.get('body'), |
| 248 nonprod_css=self._conditional_nonprod(NONPROD_CSS), |
| 249 devsite_prefix=self._conditional_devsite(DEVSITE_PREFIX), |
| 250 devsite_butterbar=self._conditional_devsite(DEVSITE_BUTTERBAR))) |
| 251 |
| 252 def _conditional_devsite(self, s): |
| 253 return s if self.devsite_production_mode else '' |
| 254 |
| 255 def _conditional_nonprod(self, s): |
| 256 return s if not self.devsite_production_mode else '' |
| 257 |
| 258 |
| 259 class NaclCodeDirective(Directive): |
| 260 """ Custom "naclcode" directive for code snippets. To keep it under our |
| 261 control. |
| 262 """ |
| 263 has_content = True |
| 264 required_arguments = 0 |
| 265 optional_arguments = 1 |
| 266 option_spec = { |
| 267 'prettyprint': int, |
| 268 } |
| 269 |
| 270 def run(self): |
| 271 code = u'\n'.join(self.content) |
| 272 literal = nodes.literal_block(code, code) |
| 273 literal['prettyprint'] = self.options.get('prettyprint', 1) |
| 274 return [literal] |
| 275 |
| 276 |
| 277 def setup(app): |
| 278 """ Extension registration hook. |
| 279 """ |
| 280 app.add_directive('naclcode', NaclCodeDirective) |
| 281 app.add_builder(DevsiteBuilder) |
| 282 |
| 283 # "Production mode" for local testing vs. on-server documentation. |
| 284 app.add_config_value('devsite_production_mode', default='1', rebuild='html') |
| 285 |
| 286 |
OLD | NEW |