Ticket #528: build_htmldocs-links_work-with_docs.diff

File build_htmldocs-links_work-with_docs.diff, 22.1 KB (added by mrts, 16 years ago)

Patch that implements a management command for adding docs, with documentation.

  • django/core/management/commands/htmldocs.py

     
     1"""
     2Command that converts Django documentation from ReST to HTML.
     3
     4Based 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
     13import os
     14import shutil
     15from optparse import make_option
     16from django.core.management import BaseCommand, CommandError
     17
     18CSS_FILE = "docs.css"
     19
     20class 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
     121def 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   
     129def 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
     136def 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
     174def 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/*
     2djangoproject.com by Wilson Miner (wilson@lawrence.com)
     3Copyright (c) 2005 Lawrence Journal-World. Please don't steal.
     4*/
     5
     6
     7/* SETUP */
     8
     9body { margin:0; padding:0; background:#092e20; color:white; }
     10body, 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
     16a {text-decoration: none;}
     17a img {border: none;}
     18a: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; }
     21a: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
     58h1,h2,h3 { margin-top:.8em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; }
     59h1 { font-size:218%; margin-top:.6em; margin-bottom:.6em; color:#092e20; line-height:1.1em; }
     60h2 { font-size:150%; margin-top:1em; margin-bottom:.2em; line-height:1.2em; color:#092e20; }
     61#homepage h2 { font-size:140%; }
     62h3 { font-size:125%; font-weight:bold; margin-bottom:.2em; color:#487858; }
     63h4 { font-size:100%; font-weight:bold; margin-bottom:-3px; margin-top:1.2em; text-transform:uppercase; letter-spacing:1px; }
     64h4 pre, h4 tt, h4 .literal { text-transform:none; }
     65h5 { font-size:1em; font-weight:bold; margin-top:1.5em; margin-bottom:3px; }
     66p, ul, dl { margin-top:.6em; margin-bottom:.8em; }
     67hr { color:#ccc; background-color:#ccc; height:1px; border:0; }
     68p.date { color:#487858; margin-top:-.2em; }
     69p.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; }
     76h2.deck { margin-top:-.5em !important; margin-bottom:.6em; color:#487858; }
     77ins { text-decoration: none; }
     78ins a { text-decoration: none; }
     79
     80/* LISTS */
     81
     82ul { padding-left:2em; }
     83ol { padding-left:30px; }
     84ul li { list-style-type:square; margin-bottom:.4em; }
     85ul ul { padding-left:1.2em; }
     86ul ul ul { padding-left:1em; }
     87ul.linklist, ul.toc { padding-left:0; }
     88ul.toc ul { margin-left:.6em; }
     89ul.toc ul li { list-style-type:square; }
     90ul.toc ul ul li { list-style-type:disc; }
     91ul.linklist li, ul.toc li { list-style-type:none; }
     92dt { font-weight:bold; margin-top:.5em; font-size:1.1em; }
     93dd { margin-bottom:.8em; }
     94
     95/*  RSS  */
     96
     97a.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; }
     99a.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; }
     114pre, .literal-block { font-size:medium; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: auto; }
     115dt .literal, table .literal { background:none; }
     116textarea.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; }
     132table.docutils { border-collapse:collapse; }
     133table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; }
     134table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:4px 2px;}
     135table.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; }
     160div.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%; }
     166h3 .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

     
    199199
    200200    django-admin.py flush --verbosity=2
    201201
     202htmldocs docdir outdir
     203----------------------
     204
     205Converts Django documentation to HTML. Uses the given Django documentation
     206directory ``docdir`` for reading documentation source and output directory
     207``outdir`` (creating it if necessary) for writing HTML files. Writes multiple
     208HTML files, i.e. one HTML file per documentation file by default.
     209
     210--single
     211~~~~~~~~~~~
     212
     213Creates a single combined HTML file. Not recommended, produces output that is
     214harder to navigate and more than 1.8 MB large.
     215
     216Example usage::
     217
     218    django-admin.py htmldocs trunk/docs django_docs --single
     219
    202220inspectdb
    203221---------
    204222
Back to Top