Ticket #528: build_htmldocs-links_work-with_docs.diff
File build_htmldocs-links_work-with_docs.diff, 22.1 KB (added by , 17 years ago) |
---|
-
django/core/management/commands/htmldocs.py
1 """ 2 Command that converts Django documentation from ReST to HTML. 3 4 Based on django_website/apps/docs/builder.py. 5 """ 6 7 # TODO: 8 # 1. fix inter-page links 9 # 2. fetch external images and place them to img directory, fix image links 10 # 3. add images used in CSS (in notes etc) 11 # 4. add PDF support, take inspiration from http://code.google.com/p/rst2pdf/ 12 13 import os 14 import shutil 15 from optparse import make_option 16 from django.core.management import BaseCommand, CommandError 17 18 CSS_FILE = "docs.css" 19 20 class Command(BaseCommand): 21 option_list = BaseCommand.option_list + ( 22 make_option('--single', action='store_true', dest='single', 23 default=False, help = "Write a single combined HTML file. " 24 "Not recommended, produces output that is harder to navigate."), 25 ) 26 help = ("Converts Django documentation to HTML. Uses the given Django " 27 "documentation directory for reading documentation source and " 28 "output directory for writing HTML files. Creates the output " 29 "directory if it does not exist.") 30 args = "[Django documentation directory] [output directory]" 31 32 requires_model_validation = False 33 can_import_settings = False 34 35 def handle(self, *paths, **options): 36 try: 37 from docutils.core import publish_parts 38 except ImportError: 39 raise CommandError("The docutils module is required to run " 40 "this command.") 41 42 if len(paths) != 2: 43 raise CommandError("Please provide exactly two arguments in the " 44 "following order: %s." % self.args) 45 46 source, dest = paths 47 if not os.path.exists(dest): 48 try: 49 os.mkdir(dest) 50 except OSError, e: 51 raise CommandError("Failed to create directory '%s'. " 52 "The error was '%s'." % (dest, e)) 53 for p in source, dest: 54 if not os.path.isdir(p): 55 raise CommandError("'%s' is not a directory." % p) 56 57 css_path = os.path.join(source, "css", CSS_FILE) 58 if not os.path.exists(css_path): 59 raise CommandError("'%s' does not appear to be a Django " 60 "documentation directory (stylesheet not found)" % source) 61 try: 62 shutil.copy(css_path, dest) 63 except IOError, e: 64 raise CommandError("Unable to write to directory '%s'. " 65 "The error was '%s'." % (dest, e)) 66 67 opt_single = options.get("single", False) 68 69 files = [f for f in os.listdir(source) if f.endswith('.txt')] 70 files.sort() 71 72 if opt_single: 73 docs = [] 74 index = [] 75 for file in files: 76 out = file[:-4] + ".html" 77 print "Converting '%s'... " % out, 78 doc = publish_parts(open(os.path.join(source, file)).read(), 79 writer=get_django_html_writer(opt_single), 80 settings_overrides={'initial_header_level': 2}) 81 print "done" 82 try: 83 index.append([out, doc['title']]) 84 except KeyError: 85 index.append([out, 'UNKNOWN (%s)' % file]) 86 87 if opt_single: 88 doc['id'] = out 89 docs.append(doc) 90 else: 91 out_path = os.path.join(dest, out) 92 print "Writing '%s'... " % out_path, 93 open(out_path, 'w').write(render_doc(doc)) 94 print "done" 95 96 # write out either index or the single combined page 97 doc = None 98 if opt_single: 99 doc = { 100 'title' : "Documentation", 101 'toc' : get_single_toc(index), 102 'body' : get_single_body(docs) 103 } 104 else: 105 doc = { 106 'title' : "Documentation index", 107 'toc' : "<p>This is the automatically genrated documentation " 108 "index. The automatic documentation is a basic " 109 "reference, more resources are available on the " 110 '<a href="http://www.djangoproject.com/documentation/">' 111 "Django website</a>.</p>", 112 'body' : '<ul>%s</ul>' % \ 113 '\n'.join(['<li><a href="%s">%s</a></li>' \ 114 % (href, title) for href, title in index ]) 115 } 116 print "Writing index... ", 117 open(os.path.join(dest, "index.html"), 'w').write(render_doc(doc)) 118 print "done" 119 print "All done" 120 121 def get_single_toc(index): 122 """ 123 Creates the table of contents for single-document output. 124 """ 125 toc = '<ul class="toc">%s</ul>' 126 line = '<li><a class="reference internal" href="#%s">%s</a></li>' 127 return toc % '\n'.join([ line % (name, title) for name, title in index]) 128 129 def get_single_body(docs): 130 """ 131 Joins individual documents into a single document. 132 """ 133 chunk = '<h1 id="%(id)s">%(title)s</h1>\n%(body)s' 134 return '\n'.join([chunk % doc for doc in docs]) 135 136 def render_doc(doc): 137 """ 138 Renders the document with a HTML template. 139 """ 140 return ("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 141 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 142 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 143 <head> 144 <meta http-equiv="Content-type" content="text/html; charset=utf-8" /> 145 <meta http-equiv="Content-Language" content="en-us" /> 146 <title>%(title)s | Django Documentation</title> 147 <link href="docs.css" rel="stylesheet" type="text/css" media="screen" /> 148 </head> 149 <body id="documentation" class="default"> 150 <div id="container"> 151 <div id="header"> 152 <ul id="nav-global"> 153 <li id="nav-homepage"><a href="index.html">Documentation index</a></li> 154 </ul> 155 </div> 156 <div id="billboard"><h2><a href="index.html" style="color: white; height:40px; padding-top:20px; text-indent:22px;">Django documentation</a></h2></div> 157 <div id="columnwrap"> 158 <div id="content-main"> 159 <h1>%(title)s</h1> 160 %(body)s 161 </div> 162 <div id="content-related" class="sidebar"> 163 <h2>Contents</h2> 164 %(toc)s 165 </div> 166 </div> 167 <div id="footer"> 168 <p><a href="index.html"><< Back to documentation index</a></p> 169 </div> 170 </div> 171 </body> 172 </html>""" % doc).encode('utf-8') 173 174 def get_django_html_writer(is_single): 175 """ 176 Returns the Django HTML writer instance. Note that we need to define the 177 classes inside a function to avoid triggering an import error when 178 docutils is unavailable. 179 """ 180 from docutils import nodes 181 from docutils.writers import html4css1 182 import re 183 184 class DjangoHTMLTranslator(html4css1.HTMLTranslator): 185 """ 186 reST -> HTML translator subclass that outputs Django-specific markup. 187 """ 188 189 # Prevent name attributes from being generated 190 named_tags = [] 191 192 def __init__(self, document): 193 html4css1.HTMLTranslator.__init__(self, document) 194 self._in_literal = 0 195 196 # Remove the default border=1 from <table> 197 def visit_table(self, node): 198 self.body.append(self.starttag(node, 'table', CLASS='docutils')) 199 200 # No smartypants conversion 201 202 # Avoid <blockquote>s around merely indented nodes. 203 # Adapted from 204 # http://thread.gmane.org/gmane.text.docutils.user/742/focus=804 205 206 _suppress_blockquote_child_nodes = ( 207 nodes.bullet_list, nodes.enumerated_list, nodes.definition_list, 208 nodes.literal_block, nodes.doctest_block, nodes.line_block, 209 nodes.table 210 ) 211 def _bq_is_valid(self, node): 212 return len(node.children) != 1 \ 213 or not isinstance(node.children[0], 214 self._suppress_blockquote_child_nodes) 215 216 def visit_block_quote(self, node): 217 if self._bq_is_valid(node): 218 html4css1.HTMLTranslator.visit_block_quote(self, node) 219 220 def depart_block_quote(self, node): 221 if self._bq_is_valid(node): 222 html4css1.HTMLTranslator.depart_block_quote(self, node) 223 224 # Fix inter-page links 225 226 class MultiPageTranslator(DjangoHTMLTranslator): 227 def visit_reference(self, node, regex=re.compile(r'^../([-\w]+)/')): 228 if node.has_key('refuri') and regex.match(node['refuri']): 229 node['refuri'] = regex.sub(r'\1.html', node['refuri']) 230 html4css1.HTMLTranslator.visit_reference(self, node) 231 232 class SinglePageTranslator(DjangoHTMLTranslator): 233 # Destroys ../foo/#bar style anchors (replacing them with #foo), 234 # but we can live with that 235 def visit_reference(self, node, regex=re.compile(r'^../([-\w]+)/.*')): 236 if node.has_key('refuri') and regex.match(node['refuri']): 237 node['refuri'] = regex.sub(r'#\1.html', node['refuri']) 238 html4css1.HTMLTranslator.visit_reference(self, node) 239 240 class DjangoHTMLWriter(html4css1.Writer): 241 """ 242 HTML writer that adds a "toc" key to the set of doc parts. 243 """ 244 def __init__(self): 245 html4css1.Writer.__init__(self) 246 if is_single: 247 self.translator_class = SinglePageTranslator 248 else: 249 self.translator_class = MultiPageTranslator 250 251 def translate(self): 252 # build the document 253 html4css1.Writer.translate(self) 254 255 # build the contents 256 contents = self.build_contents(self.document) 257 contents_doc = self.document.copy() 258 contents_doc.children = contents 259 contents_visitor = self.translator_class(contents_doc) 260 contents_doc.walkabout(contents_visitor) 261 self.parts['toc'] = "<ul class='toc'>%s</ul>" \ 262 % ''.join(contents_visitor.fragment) 263 264 def build_contents(self, node, level=0): 265 level += 1 266 sections = [] 267 i = len(node) - 1 268 while i >= 0 and isinstance(node[i], nodes.section): 269 sections.append(node[i]) 270 i -= 1 271 sections.reverse() 272 entries = [] 273 autonum = 0 274 depth = 4 # XXX FIXME 275 for section in sections: 276 title = section[0] 277 entrytext = title 278 try: 279 reference = nodes.reference('', '', 280 refid=section['ids'][0], *entrytext) 281 except IndexError: 282 continue 283 ref_id = self.document.set_id(reference) 284 entry = nodes.paragraph('', '', reference) 285 item = nodes.list_item('', entry) 286 if level < depth: 287 subsects = self.build_contents(section, level) 288 item += subsects 289 entries.append(item) 290 if entries: 291 contents = nodes.bullet_list('', *entries) 292 return contents 293 else: 294 return [] 295 296 return DjangoHTMLWriter() -
docs/css/docs.css
1 /* 2 djangoproject.com by Wilson Miner (wilson@lawrence.com) 3 Copyright (c) 2005 Lawrence Journal-World. Please don't steal. 4 */ 5 6 7 /* SETUP */ 8 9 body { margin:0; padding:0; background:#092e20; color:white; } 10 body, th, td { font:12px/1.4em Verdana,sans-serif; } 11 #container { position:relative; min-width:55em; max-width:100em; } 12 #homepage #container { max-width:100em; } 13 14 /* LINKS */ 15 16 a {text-decoration: none;} 17 a img {border: none;} 18 a:link, a:visited { color:#ffc757; } 19 #content-main a:link, #content-main a:visited { color:#ab5603; text-decoration:underline; } 20 #content-secondary a:link, #content-secondary a:visited { color:#ffc757; text-decoration:none; } 21 a:hover { color:#ffe761; } 22 #content-main a:hover { background-color:#E0FFB8; color:#234f32; text-decoration:none; } 23 #content-secondary a:hover { color:#ffe761; background:none; } 24 #content-main h2 a, #content-main h3 a { text-decoration:none !important; } 25 26 /* HEADER */ 27 28 #header { position:relative; height:6.5em; background:#092e20; } 29 #header h1#logo { margin:0; width:111px; height:41px; position:absolute; bottom:10px; left:25px; } 30 31 /* NAV */ 32 33 #nav-global { position:absolute; margin:0; bottom:0; right:0; font-family:"Trebuchet MS",sans-serif; white-space:nowrap; } 34 #nav-global li { display:block; float:left; list-style-type:none; margin:0; padding:0; } 35 #nav-global a { display:block; float:left; padding:5em 16px 10px 16px; background:#092e20; } 36 #nav-global a:hover { color:white; background:#234f32; } 37 #homepage #nav-homepage a, #overview #nav-overview a, #download #nav-download a, #documentation #nav-documentation a, #weblog #nav-weblog a, #community #nav-community a, #blogroll #nav-blogroll a, #code #nav-code a { color:white; background:#092e20 url(../img/site/nav_bg.gif) bottom repeat-x; } 38 39 /* COLUMNS */ 40 41 #columnwrap { background:#234f32; padding-bottom:10px; } 42 #subwrap { background:#326342; width:73%; float:left; padding-bottom:10px; } 43 #content-main { float:left; width:70%; background:white; color:black; padding-bottom:10px; } 44 #generic #content-main, #code #content-main { width:100%; } 45 #content-main * { margin-left:22px; margin-right:24px; } 46 #content-main * * { margin-left:0; margin-right:0; } 47 .sidebar { font-size:92%; } 48 .sidebar * { margin-left:14px; margin-right:14px; } 49 .sidebar * * { margin-left:0; margin-right:0; } 50 #content-extra { float:right; width:27%; } 51 #content-related { float:right; width:30%;} 52 #content-secondary { clear:both; background:#487858; margin-left:0; margin-right:0; margin-top:15px; margin-bottom:-10px; padding:10px 24px; color:white; } 53 .subcol-primary, .subcol-secondary { width:40%; float:left; padding-bottom:1.2em; } 54 .subcol-primary { margin-right:1%; } 55 56 /* CONTENT */ 57 58 h1,h2,h3 { margin-top:.8em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } 59 h1 { font-size:218%; margin-top:.6em; margin-bottom:.6em; color:#092e20; line-height:1.1em; } 60 h2 { font-size:150%; margin-top:1em; margin-bottom:.2em; line-height:1.2em; color:#092e20; } 61 #homepage h2 { font-size:140%; } 62 h3 { font-size:125%; font-weight:bold; margin-bottom:.2em; color:#487858; } 63 h4 { font-size:100%; font-weight:bold; margin-bottom:-3px; margin-top:1.2em; text-transform:uppercase; letter-spacing:1px; } 64 h4 pre, h4 tt, h4 .literal { text-transform:none; } 65 h5 { font-size:1em; font-weight:bold; margin-top:1.5em; margin-bottom:3px; } 66 p, ul, dl { margin-top:.6em; margin-bottom:.8em; } 67 hr { color:#ccc; background-color:#ccc; height:1px; border:0; } 68 p.date { color:#487858; margin-top:-.2em; } 69 p.more { margin-top:-.4em; } 70 .sidebar p.date { color:#90ba9e; } 71 #content-secondary h2, .sidebar h2 { color:white; } 72 #content-secondary h3, .sidebar h3 { color:#9aef3f; } 73 #content-secondary h2:first-child { margin-top:.6em; } 74 .sidebar h2:first-child { margin-top:.8em; } 75 #content-main h2, #content-main h3 { margin-top:1.2em; } 76 h2.deck { margin-top:-.5em !important; margin-bottom:.6em; color:#487858; } 77 ins { text-decoration: none; } 78 ins a { text-decoration: none; } 79 80 /* LISTS */ 81 82 ul { padding-left:2em; } 83 ol { padding-left:30px; } 84 ul li { list-style-type:square; margin-bottom:.4em; } 85 ul ul { padding-left:1.2em; } 86 ul ul ul { padding-left:1em; } 87 ul.linklist, ul.toc { padding-left:0; } 88 ul.toc ul { margin-left:.6em; } 89 ul.toc ul li { list-style-type:square; } 90 ul.toc ul ul li { list-style-type:disc; } 91 ul.linklist li, ul.toc li { list-style-type:none; } 92 dt { font-weight:bold; margin-top:.5em; font-size:1.1em; } 93 dd { margin-bottom:.8em; } 94 95 /* RSS */ 96 97 a.rss { font:bold 10px Verdana, sans-serif; padding:0 .2em; border: 1px solid; text-decoration:none; background:#f60;color: #fff; border-color:#ffc8a4 #7d3302 #3f1a01 #ff9a57; margin:0 3px; vertical-align:middle; } 98 #content-main a.rss { color:#fff; text-decoration:none; } 99 a.rss:hover, a.rss:link, a.rss:visited { color:#fff; text-decoration:none; } 100 101 /* BLOCKQUOTES */ 102 103 #weblog blockquote { padding-left:0.8em; padding-right:1em; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; } 104 .sidebar blockquote { margin-top:1.5em; margin-bottom:1.5em; } 105 .sidebar blockquote p { font:italic 175%/1.2em "Trebuchet MS",sans-serif; color:#94da3a; } 106 .sidebar blockquote cite { display:block; font-style:normal; line-height:1.2em; margin-top:-.8em; color:#94da3a; } 107 .sidebar cite strong { font-weight:normal; color:white; } 108 109 /* CODE BLOCKS */ 110 111 .literal { white-space:nowrap; } 112 .literal, .literal-block { color:#234f32; } 113 .sidebar .literal { color:white; background:transparent; font-size:11px; } 114 pre, .literal-block { font-size:medium; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: auto; } 115 dt .literal, table .literal { background:none; } 116 textarea.codedump { font-size:10px; color:#234f32; width:100%; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; padding: .3em .4em; } 117 118 /* NOTES & ADMONITIONS */ 119 120 .note, .admonition, .caution { padding:.8em 1em .8em; margin: 1em 0; border:1px solid #94da3a; } 121 .admonition-title { font-weight:bold; margin-top:0 !important; margin-bottom:0 !important;} 122 .admonition .last { margin-bottom:0 !important; } 123 .admonition-philosophy { padding-left:65px; background:url(../img/doc/icons/docicons-philosophy.gif) .8em .8em no-repeat;} 124 .admonition-note, .caution { padding-left:65px; background:url(../img/doc/icons/docicons-note.gif) .8em .8em no-repeat;} 125 .admonition-behind-the-scenes { padding-left:65px; background:url(../img/doc/icons/docicons-behindscenes.gif) .8em .8em no-repeat;} 126 127 /* DOCS */ 128 129 #documentation h2, #documentation h3, #documentation h4 { margin-top:1.4em; } 130 #documentation dd { margin-left:1em; } 131 #content-main table { color:#000; } 132 table.docutils { border-collapse:collapse; } 133 table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; } 134 table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:4px 2px;} 135 table.docutils td p { margin-top:0; margin-bottom:.5em; } 136 #documentation #content-related .literal { background:transparent !important; } 137 138 /* BILLBOARDS */ 139 140 #billboard { background:#94da3a url(../img/site/bbdsm_bg.gif) repeat-x; border-bottom:6px solid #092e20; } 141 #billboard h2 { margin:0; } 142 #generic #billboard { display:none; } 143 #homepage #billboard { background-image: url(../img/site/bbd_bg.gif); } 144 #homepage #billboard h2 { margin:0; text-indent:-5000px; height:80px; width:633px; background:url(../img/site/bbd_homepage.gif) no-repeat; } 145 #overview #billboard h2 { margin:0; text-indent:-5000px; height:60px; width:203px; background:url(../img/site/bbd_overview.gif) no-repeat; } 146 #download #billboard h2 { margin:0; text-indent:-5000px; height:60px; width:203px; background:url(../img/site/bbd_download.gif) no-repeat; } 147 #documentation #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_documentation.gif) no-repeat; } 148 #weblog #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_weblog.gif) no-repeat; } 149 #community #billboard h2 { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_community.gif) no-repeat; } 150 #blogroll #billboard h2 { display:block; margin:0; text-indent:-5000px; height:60px; width:168px; background:url(../img/site/bbd_blogroll.gif) no-repeat; } 151 #code #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:184px; background:url(../img/site/bbd_code.gif) no-repeat; } 152 153 /* FOOTER */ 154 155 #footer { clear:both; color:#487858; padding:10px 20px; font-size:90%; } 156 157 /* COMMENTS */ 158 159 .comment { margin:15px 0; } 160 div.comment p { margin-left:1em; } 161 #weblog div.comment p.date { margin-bottom:.2em; color:#94da3a; } 162 163 /* MISC */ 164 165 .small { font-size:90%; } 166 h3 .small { font-size:80%; } 167 .quiet { font-weight:normal; } 168 .clear { clear:both; } 169 #content-main .quiet { color:#487858; } 170 #content-secondary .quiet { color:#90ba9e; } 171 172 /* CLEARFIX KLUDGE */ 173 174 #columnwrap:after { 175 content: "."; 176 display: block; 177 height: 0; 178 clear: both; 179 visibility: hidden; 180 } 181 #columnwrap { display: inline-block; } 182 183 /* Hides from IE-mac \*/ 184 * html #columnwrap { height: 1%; } 185 #columnwrap { display: block; } 186 /* End hide from IE-mac */ 187 188 #subwrap:after { 189 content: "."; 190 display: block; 191 height: 0; 192 clear: both; 193 visibility: hidden; 194 } 195 #subwrap { display: inline-block; } 196 197 /* Hides from IE-mac \*/ 198 * html #subwrap { height: 1%; } 199 #subwrap { display: block; } 200 /* End hide from IE-mac */om IE-mac */ -
docs/django-admin.txt
199 199 200 200 django-admin.py flush --verbosity=2 201 201 202 htmldocs docdir outdir 203 ---------------------- 204 205 Converts Django documentation to HTML. Uses the given Django documentation 206 directory ``docdir`` for reading documentation source and output directory 207 ``outdir`` (creating it if necessary) for writing HTML files. Writes multiple 208 HTML files, i.e. one HTML file per documentation file by default. 209 210 --single 211 ~~~~~~~~~~~ 212 213 Creates a single combined HTML file. Not recommended, produces output that is 214 harder to navigate and more than 1.8 MB large. 215 216 Example usage:: 217 218 django-admin.py htmldocs trunk/docs django_docs --single 219 202 220 inspectdb 203 221 --------- 204 222