Index: django/core/management/commands/htmldocs.py
===================================================================
--- django/core/management/commands/htmldocs.py (revision 0)
+++ django/core/management/commands/htmldocs.py (revision 0)
@@ -0,0 +1,296 @@
+"""
+Command that converts Django documentation from ReST to HTML.
+
+Based on django_website/apps/docs/builder.py.
+"""
+
+# TODO:
+# 1. fix inter-page links
+# 2. fetch external images and place them to img directory, fix image links
+# 3. add images used in CSS (in notes etc)
+# 4. add PDF support, take inspiration from http://code.google.com/p/rst2pdf/
+
+import os
+import shutil
+from optparse import make_option
+from django.core.management import BaseCommand, CommandError
+
+CSS_FILE = "docs.css"
+
+class Command(BaseCommand):
+ option_list = BaseCommand.option_list + (
+ make_option('--single', action='store_true', dest='single',
+ default=False, help = "Write a single combined HTML file. "
+ "Not recommended, produces output that is harder to navigate."),
+ )
+ help = ("Converts Django documentation to HTML. Uses the given Django "
+ "documentation directory for reading documentation source and "
+ "output directory for writing HTML files. Creates the output "
+ "directory if it does not exist.")
+ args = "[Django documentation directory] [output directory]"
+
+ requires_model_validation = False
+ can_import_settings = False
+
+ def handle(self, *paths, **options):
+ try:
+ from docutils.core import publish_parts
+ except ImportError:
+ raise CommandError("The docutils module is required to run "
+ "this command.")
+
+ if len(paths) != 2:
+ raise CommandError("Please provide exactly two arguments in the "
+ "following order: %s." % self.args)
+
+ source, dest = paths
+ if not os.path.exists(dest):
+ try:
+ os.mkdir(dest)
+ except OSError, e:
+ raise CommandError("Failed to create directory '%s'. "
+ "The error was '%s'." % (dest, e))
+ for p in source, dest:
+ if not os.path.isdir(p):
+ raise CommandError("'%s' is not a directory." % p)
+
+ css_path = os.path.join(source, "css", CSS_FILE)
+ if not os.path.exists(css_path):
+ raise CommandError("'%s' does not appear to be a Django "
+ "documentation directory (stylesheet not found)" % source)
+ try:
+ shutil.copy(css_path, dest)
+ except IOError, e:
+ raise CommandError("Unable to write to directory '%s'. "
+ "The error was '%s'." % (dest, e))
+
+ opt_single = options.get("single", False)
+
+ files = [f for f in os.listdir(source) if f.endswith('.txt')]
+ files.sort()
+
+ if opt_single:
+ docs = []
+ index = []
+ for file in files:
+ out = file[:-4] + ".html"
+ print "Converting '%s'... " % out,
+ doc = publish_parts(open(os.path.join(source, file)).read(),
+ writer=get_django_html_writer(opt_single),
+ settings_overrides={'initial_header_level': 2})
+ print "done"
+ try:
+ index.append([out, doc['title']])
+ except KeyError:
+ index.append([out, 'UNKNOWN (%s)' % file])
+
+ if opt_single:
+ doc['id'] = out
+ docs.append(doc)
+ else:
+ out_path = os.path.join(dest, out)
+ print "Writing '%s'... " % out_path,
+ open(out_path, 'w').write(render_doc(doc))
+ print "done"
+
+ # write out either index or the single combined page
+ doc = None
+ if opt_single:
+ doc = {
+ 'title' : "Documentation",
+ 'toc' : get_single_toc(index),
+ 'body' : get_single_body(docs)
+ }
+ else:
+ doc = {
+ 'title' : "Documentation index",
+ 'toc' : "
This is the automatically genrated documentation "
+ "index. The automatic documentation is a basic "
+ "reference, more resources are available on the "
+ ''
+ "Django website .
",
+ 'body' : '' % \
+ '\n'.join(['%s ' \
+ % (href, title) for href, title in index ])
+ }
+ print "Writing index... ",
+ open(os.path.join(dest, "index.html"), 'w').write(render_doc(doc))
+ print "done"
+ print "All done"
+
+def get_single_toc(index):
+ """
+ Creates the table of contents for single-document output.
+ """
+ toc = ''
+ line = '%s '
+ return toc % '\n'.join([ line % (name, title) for name, title in index])
+
+def get_single_body(docs):
+ """
+ Joins individual documents into a single document.
+ """
+ chunk = '%(title)s \n%(body)s'
+ return '\n'.join([chunk % doc for doc in docs])
+
+def render_doc(doc):
+ """
+ Renders the document with a HTML template.
+ """
+ return ("""
+
+
+
+
+ %(title)s | Django Documentation
+
+
+
+
+
+
+
+
+
%(title)s
+ %(body)s
+
+
+
+
+
+
+""" % doc).encode('utf-8')
+
+def get_django_html_writer(is_single):
+ """
+ Returns the Django HTML writer instance. Note that we need to define the
+ classes inside a function to avoid triggering an import error when
+ docutils is unavailable.
+ """
+ from docutils import nodes
+ from docutils.writers import html4css1
+ import re
+
+ class DjangoHTMLTranslator(html4css1.HTMLTranslator):
+ """
+ reST -> HTML translator subclass that outputs Django-specific markup.
+ """
+
+ # Prevent name attributes from being generated
+ named_tags = []
+
+ def __init__(self, document):
+ html4css1.HTMLTranslator.__init__(self, document)
+ self._in_literal = 0
+
+ # Remove the default border=1 from
+ def visit_table(self, node):
+ self.body.append(self.starttag(node, 'table', CLASS='docutils'))
+
+ # No smartypants conversion
+
+ # Avoid s around merely indented nodes.
+ # Adapted from
+ # http://thread.gmane.org/gmane.text.docutils.user/742/focus=804
+
+ _suppress_blockquote_child_nodes = (
+ nodes.bullet_list, nodes.enumerated_list, nodes.definition_list,
+ nodes.literal_block, nodes.doctest_block, nodes.line_block,
+ nodes.table
+ )
+ def _bq_is_valid(self, node):
+ return len(node.children) != 1 \
+ or not isinstance(node.children[0],
+ self._suppress_blockquote_child_nodes)
+
+ def visit_block_quote(self, node):
+ if self._bq_is_valid(node):
+ html4css1.HTMLTranslator.visit_block_quote(self, node)
+
+ def depart_block_quote(self, node):
+ if self._bq_is_valid(node):
+ html4css1.HTMLTranslator.depart_block_quote(self, node)
+
+ # Fix inter-page links
+
+ class MultiPageTranslator(DjangoHTMLTranslator):
+ def visit_reference(self, node, regex=re.compile(r'^../([-\w]+)/')):
+ if node.has_key('refuri') and regex.match(node['refuri']):
+ node['refuri'] = regex.sub(r'\1.html', node['refuri'])
+ html4css1.HTMLTranslator.visit_reference(self, node)
+
+ class SinglePageTranslator(DjangoHTMLTranslator):
+ # Destroys ../foo/#bar style anchors (replacing them with #foo),
+ # but we can live with that
+ def visit_reference(self, node, regex=re.compile(r'^../([-\w]+)/.*')):
+ if node.has_key('refuri') and regex.match(node['refuri']):
+ node['refuri'] = regex.sub(r'#\1.html', node['refuri'])
+ html4css1.HTMLTranslator.visit_reference(self, node)
+
+ class DjangoHTMLWriter(html4css1.Writer):
+ """
+ HTML writer that adds a "toc" key to the set of doc parts.
+ """
+ def __init__(self):
+ html4css1.Writer.__init__(self)
+ if is_single:
+ self.translator_class = SinglePageTranslator
+ else:
+ self.translator_class = MultiPageTranslator
+
+ def translate(self):
+ # build the document
+ html4css1.Writer.translate(self)
+
+ # build the contents
+ contents = self.build_contents(self.document)
+ contents_doc = self.document.copy()
+ contents_doc.children = contents
+ contents_visitor = self.translator_class(contents_doc)
+ contents_doc.walkabout(contents_visitor)
+ self.parts['toc'] = "" \
+ % ''.join(contents_visitor.fragment)
+
+ def build_contents(self, node, level=0):
+ level += 1
+ sections = []
+ i = len(node) - 1
+ while i >= 0 and isinstance(node[i], nodes.section):
+ sections.append(node[i])
+ i -= 1
+ sections.reverse()
+ entries = []
+ autonum = 0
+ depth = 4 # XXX FIXME
+ for section in sections:
+ title = section[0]
+ entrytext = title
+ try:
+ reference = nodes.reference('', '',
+ refid=section['ids'][0], *entrytext)
+ except IndexError:
+ continue
+ ref_id = self.document.set_id(reference)
+ entry = nodes.paragraph('', '', reference)
+ item = nodes.list_item('', entry)
+ if level < depth:
+ subsects = self.build_contents(section, level)
+ item += subsects
+ entries.append(item)
+ if entries:
+ contents = nodes.bullet_list('', *entries)
+ return contents
+ else:
+ return []
+
+ return DjangoHTMLWriter()
Index: docs/css/docs.css
===================================================================
--- docs/css/docs.css (revision 0)
+++ docs/css/docs.css (revision 0)
@@ -0,0 +1,200 @@
+/*
+djangoproject.com by Wilson Miner (wilson@lawrence.com)
+Copyright (c) 2005 Lawrence Journal-World. Please don't steal.
+*/
+
+
+/* SETUP */
+
+body { margin:0; padding:0; background:#092e20; color:white; }
+body, th, td { font:12px/1.4em Verdana,sans-serif; }
+#container { position:relative; min-width:55em; max-width:100em; }
+#homepage #container { max-width:100em; }
+
+/* LINKS */
+
+a {text-decoration: none;}
+a img {border: none;}
+a:link, a:visited { color:#ffc757; }
+#content-main a:link, #content-main a:visited { color:#ab5603; text-decoration:underline; }
+#content-secondary a:link, #content-secondary a:visited { color:#ffc757; text-decoration:none; }
+a:hover { color:#ffe761; }
+#content-main a:hover { background-color:#E0FFB8; color:#234f32; text-decoration:none; }
+#content-secondary a:hover { color:#ffe761; background:none; }
+#content-main h2 a, #content-main h3 a { text-decoration:none !important; }
+
+/* HEADER */
+
+#header { position:relative; height:6.5em; background:#092e20; }
+#header h1#logo { margin:0; width:111px; height:41px; position:absolute; bottom:10px; left:25px; }
+
+/* NAV */
+
+#nav-global { position:absolute; margin:0; bottom:0; right:0; font-family:"Trebuchet MS",sans-serif; white-space:nowrap; }
+#nav-global li { display:block; float:left; list-style-type:none; margin:0; padding:0; }
+#nav-global a { display:block; float:left; padding:5em 16px 10px 16px; background:#092e20; }
+#nav-global a:hover { color:white; background:#234f32; }
+#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; }
+
+/* COLUMNS */
+
+#columnwrap { background:#234f32; padding-bottom:10px; }
+#subwrap { background:#326342; width:73%; float:left; padding-bottom:10px; }
+#content-main { float:left; width:70%; background:white; color:black; padding-bottom:10px; }
+#generic #content-main, #code #content-main { width:100%; }
+#content-main * { margin-left:22px; margin-right:24px; }
+#content-main * * { margin-left:0; margin-right:0; }
+.sidebar { font-size:92%; }
+.sidebar * { margin-left:14px; margin-right:14px; }
+.sidebar * * { margin-left:0; margin-right:0; }
+#content-extra { float:right; width:27%; }
+#content-related { float:right; width:30%;}
+#content-secondary { clear:both; background:#487858; margin-left:0; margin-right:0; margin-top:15px; margin-bottom:-10px; padding:10px 24px; color:white; }
+.subcol-primary, .subcol-secondary { width:40%; float:left; padding-bottom:1.2em; }
+.subcol-primary { margin-right:1%; }
+
+/* CONTENT */
+
+h1,h2,h3 { margin-top:.8em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; }
+h1 { font-size:218%; margin-top:.6em; margin-bottom:.6em; color:#092e20; line-height:1.1em; }
+h2 { font-size:150%; margin-top:1em; margin-bottom:.2em; line-height:1.2em; color:#092e20; }
+#homepage h2 { font-size:140%; }
+h3 { font-size:125%; font-weight:bold; margin-bottom:.2em; color:#487858; }
+h4 { font-size:100%; font-weight:bold; margin-bottom:-3px; margin-top:1.2em; text-transform:uppercase; letter-spacing:1px; }
+h4 pre, h4 tt, h4 .literal { text-transform:none; }
+h5 { font-size:1em; font-weight:bold; margin-top:1.5em; margin-bottom:3px; }
+p, ul, dl { margin-top:.6em; margin-bottom:.8em; }
+hr { color:#ccc; background-color:#ccc; height:1px; border:0; }
+p.date { color:#487858; margin-top:-.2em; }
+p.more { margin-top:-.4em; }
+.sidebar p.date { color:#90ba9e; }
+#content-secondary h2, .sidebar h2 { color:white; }
+#content-secondary h3, .sidebar h3 { color:#9aef3f; }
+#content-secondary h2:first-child { margin-top:.6em; }
+.sidebar h2:first-child { margin-top:.8em; }
+#content-main h2, #content-main h3 { margin-top:1.2em; }
+h2.deck { margin-top:-.5em !important; margin-bottom:.6em; color:#487858; }
+ins { text-decoration: none; }
+ins a { text-decoration: none; }
+
+/* LISTS */
+
+ul { padding-left:2em; }
+ol { padding-left:30px; }
+ul li { list-style-type:square; margin-bottom:.4em; }
+ul ul { padding-left:1.2em; }
+ul ul ul { padding-left:1em; }
+ul.linklist, ul.toc { padding-left:0; }
+ul.toc ul { margin-left:.6em; }
+ul.toc ul li { list-style-type:square; }
+ul.toc ul ul li { list-style-type:disc; }
+ul.linklist li, ul.toc li { list-style-type:none; }
+dt { font-weight:bold; margin-top:.5em; font-size:1.1em; }
+dd { margin-bottom:.8em; }
+
+/* RSS */
+
+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; }
+#content-main a.rss { color:#fff; text-decoration:none; }
+a.rss:hover, a.rss:link, a.rss:visited { color:#fff; text-decoration:none; }
+
+/* BLOCKQUOTES */
+
+#weblog blockquote { padding-left:0.8em; padding-right:1em; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; }
+.sidebar blockquote { margin-top:1.5em; margin-bottom:1.5em; }
+.sidebar blockquote p { font:italic 175%/1.2em "Trebuchet MS",sans-serif; color:#94da3a; }
+.sidebar blockquote cite { display:block; font-style:normal; line-height:1.2em; margin-top:-.8em; color:#94da3a; }
+.sidebar cite strong { font-weight:normal; color:white; }
+
+/* CODE BLOCKS */
+
+.literal { white-space:nowrap; }
+.literal, .literal-block { color:#234f32; }
+.sidebar .literal { color:white; background:transparent; font-size:11px; }
+pre, .literal-block { font-size:medium; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: auto; }
+dt .literal, table .literal { background:none; }
+textarea.codedump { font-size:10px; color:#234f32; width:100%; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; padding: .3em .4em; }
+
+/* NOTES & ADMONITIONS */
+
+.note, .admonition, .caution { padding:.8em 1em .8em; margin: 1em 0; border:1px solid #94da3a; }
+.admonition-title { font-weight:bold; margin-top:0 !important; margin-bottom:0 !important;}
+.admonition .last { margin-bottom:0 !important; }
+.admonition-philosophy { padding-left:65px; background:url(../img/doc/icons/docicons-philosophy.gif) .8em .8em no-repeat;}
+.admonition-note, .caution { padding-left:65px; background:url(../img/doc/icons/docicons-note.gif) .8em .8em no-repeat;}
+.admonition-behind-the-scenes { padding-left:65px; background:url(../img/doc/icons/docicons-behindscenes.gif) .8em .8em no-repeat;}
+
+/* DOCS */
+
+#documentation h2, #documentation h3, #documentation h4 { margin-top:1.4em; }
+#documentation dd { margin-left:1em; }
+#content-main table { color:#000; }
+table.docutils { border-collapse:collapse; }
+table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; }
+table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:4px 2px;}
+table.docutils td p { margin-top:0; margin-bottom:.5em; }
+#documentation #content-related .literal { background:transparent !important; }
+
+/* BILLBOARDS */
+
+#billboard { background:#94da3a url(../img/site/bbdsm_bg.gif) repeat-x; border-bottom:6px solid #092e20; }
+#billboard h2 { margin:0; }
+#generic #billboard { display:none; }
+#homepage #billboard { background-image: url(../img/site/bbd_bg.gif); }
+#homepage #billboard h2 { margin:0; text-indent:-5000px; height:80px; width:633px; background:url(../img/site/bbd_homepage.gif) no-repeat; }
+#overview #billboard h2 { margin:0; text-indent:-5000px; height:60px; width:203px; background:url(../img/site/bbd_overview.gif) no-repeat; }
+#download #billboard h2 { margin:0; text-indent:-5000px; height:60px; width:203px; background:url(../img/site/bbd_download.gif) no-repeat; }
+#documentation #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_documentation.gif) no-repeat; }
+#weblog #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_weblog.gif) no-repeat; }
+#community #billboard h2 { display:block; margin:0; text-indent:-5000px; height:60px; width:226px; background:url(../img/site/bbd_community.gif) no-repeat; }
+#blogroll #billboard h2 { display:block; margin:0; text-indent:-5000px; height:60px; width:168px; background:url(../img/site/bbd_blogroll.gif) no-repeat; }
+#code #billboard h2 a { display:block; margin:0; text-indent:-5000px; height:60px; width:184px; background:url(../img/site/bbd_code.gif) no-repeat; }
+
+/* FOOTER */
+
+#footer { clear:both; color:#487858; padding:10px 20px; font-size:90%; }
+
+/* COMMENTS */
+
+.comment { margin:15px 0; }
+div.comment p { margin-left:1em; }
+#weblog div.comment p.date { margin-bottom:.2em; color:#94da3a; }
+
+/* MISC */
+
+.small { font-size:90%; }
+h3 .small { font-size:80%; }
+.quiet { font-weight:normal; }
+.clear { clear:both; }
+#content-main .quiet { color:#487858; }
+#content-secondary .quiet { color:#90ba9e; }
+
+/* CLEARFIX KLUDGE */
+
+#columnwrap:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+#columnwrap { display: inline-block; }
+
+/* Hides from IE-mac \*/
+* html #columnwrap { height: 1%; }
+#columnwrap { display: block; }
+/* End hide from IE-mac */
+
+#subwrap:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+#subwrap { display: inline-block; }
+
+/* Hides from IE-mac \*/
+* html #subwrap { height: 1%; }
+#subwrap { display: block; }
+/* End hide from IE-mac */om IE-mac */
Index: docs/django-admin.txt
===================================================================
--- docs/django-admin.txt (revision 7321)
+++ docs/django-admin.txt (working copy)
@@ -199,6 +199,24 @@
django-admin.py flush --verbosity=2
+htmldocs docdir outdir
+----------------------
+
+Converts Django documentation to HTML. Uses the given Django documentation
+directory ``docdir`` for reading documentation source and output directory
+``outdir`` (creating it if necessary) for writing HTML files. Writes multiple
+HTML files, i.e. one HTML file per documentation file by default.
+
+--single
+~~~~~~~~~~~
+
+Creates a single combined HTML file. Not recommended, produces output that is
+harder to navigate and more than 1.8 MB large.
+
+Example usage::
+
+ django-admin.py htmldocs trunk/docs django_docs --single
+
inspectdb
---------