A few refactorings in Sphinx.

This commit is contained in:
Georg Brandl 2008-01-16 20:27:25 +00:00
parent 1fbdc410b7
commit b09e628b0f
56 changed files with 631 additions and 667 deletions

View File

@ -3,7 +3,7 @@
Sphinx Sphinx
~~~~~~ ~~~~~~
The Python documentation toolchain. The Sphinx documentation toolchain.
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
@ -14,8 +14,8 @@ import getopt
from os import path from os import path
from cStringIO import StringIO from cStringIO import StringIO
from .builder import builders from sphinx.builder import builders
from .util.console import nocolor from sphinx.util.console import nocolor
__version__ = '$Revision: 5369 $' __version__ = '$Revision: 5369 $'
@ -31,7 +31,6 @@ options: -b <builder> -- builder to use (one of %s)
-E -- don't use a saved environment, always read all files -E -- don't use a saved environment, always read all files
-d <path> -- path for the cached environment and doctree files -d <path> -- path for the cached environment and doctree files
(default outdir/.doctrees) (default outdir/.doctrees)
-O <option[=value]> -- give option to to the builder (-O help for list)
-D <setting=value> -- override a setting in sourcedir/conf.py -D <setting=value> -- override a setting in sourcedir/conf.py
-N -- do not do colored output -N -- do not do colored output
-q -- no output on stdout, just warnings on stderr -q -- no output on stdout, just warnings on stderr
@ -44,7 +43,7 @@ modi:
def main(argv): def main(argv):
try: try:
opts, args = getopt.getopt(argv[1:], 'ab:d:O:D:NEqP') opts, args = getopt.getopt(argv[1:], 'ab:d:D:NEqP')
srcdirname = path.abspath(args[0]) srcdirname = path.abspath(args[0])
if not path.isdir(srcdirname): if not path.isdir(srcdirname):
print >>sys.stderr, 'Error: Cannot find source directory.' print >>sys.stderr, 'Error: Cannot find source directory.'
@ -70,9 +69,8 @@ def main(argv):
return 1 return 1
builder = all_files = None builder = all_files = None
opt_help = freshenv = use_pdb = False freshenv = use_pdb = False
status = sys.stdout status = sys.stdout
options = {}
confoverrides = {} confoverrides = {}
doctreedir = path.join(outdirname, '.doctrees') doctreedir = path.join(outdirname, '.doctrees')
for opt, val in opts: for opt, val in opts:
@ -88,18 +86,6 @@ def main(argv):
all_files = True all_files = True
elif opt == '-d': elif opt == '-d':
doctreedir = val doctreedir = val
elif opt == '-O':
if val == 'help':
opt_help = True
continue
if '=' in val:
key, val = val.split('=')
try:
val = int(val)
except: pass
else:
key, val = val, True
options[key] = val
elif opt == '-D': elif opt == '-D':
key, val = val.split('=') key, val = val.split('=')
try: try:
@ -125,14 +111,8 @@ def main(argv):
builderobj = builders[builder] builderobj = builders[builder]
if opt_help:
print 'Options recognized by the %s builder:' % builder
for optname, description in builderobj.option_spec.iteritems():
print ' * %s: %s' % (optname, description)
return 0
try: try:
builderobj = builderobj(srcdirname, outdirname, doctreedir, options, builderobj = builderobj(srcdirname, outdirname, doctreedir,
status_stream=status, status_stream=status,
warning_stream=sys.stderr, warning_stream=sys.stderr,
confoverrides=confoverrides, confoverrides=confoverrides,
@ -146,7 +126,8 @@ def main(argv):
except: except:
if not use_pdb: if not use_pdb:
raise raise
import pdb import pdb, traceback
traceback.print_exc()
pdb.post_mortem(sys.exc_info()[2]) pdb.post_mortem(sys.exc_info()[2])

View File

@ -8,11 +8,41 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import absolute_import
import sys import sys
import codecs
from os import path from os import path
sys.path.insert(0, path.dirname(__file__)) sys.path.insert(0, path.dirname(__file__))
from jinja import Environment, FileSystemLoader from jinja import Environment
from jinja.loaders import BaseLoader
from jinja.exceptions import TemplateNotFound
class SphinxFileSystemLoader(BaseLoader):
"""
A loader that loads templates either relative to one of a list of given
paths, or from an absolute path.
"""
def __init__(self, paths):
self.searchpaths = map(path.abspath, paths)
def get_source(self, environment, name, parent):
name = name.replace('/', path.sep)
if path.isabs(name):
if not path.exists(name):
raise TemplateNotFound(name)
filename = name
else:
for searchpath in self.searchpaths:
if path.exists(path.join(searchpath, name)):
filename = path.join(searchpath, name)
break
else:
raise TemplateNotFound(name)
f = codecs.open(filename, 'r', environment.template_charset)
try:
return f.read()
finally:
f.close()

View File

@ -3,6 +3,8 @@
sphinx.addnodes sphinx.addnodes
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
Additional docutils nodes.
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """

View File

@ -8,7 +8,6 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import os import os
import sys import sys
@ -21,26 +20,27 @@ import cStringIO as StringIO
from os import path from os import path
from cgi import escape from cgi import escape
from docutils import nodes
from docutils.io import StringOutput, FileOutput, DocTreeInput from docutils.io import StringOutput, FileOutput, DocTreeInput
from docutils.core import publish_parts from docutils.core import publish_parts
from docutils.utils import new_document from docutils.utils import new_document
from docutils.readers import doctree from docutils.readers import doctree
from docutils.frontend import OptionParser from docutils.frontend import OptionParser
from .util import (get_matching_files, attrdict, status_iterator, ensuredir, from sphinx import addnodes
get_category, relative_uri, os_path, SEP) from sphinx.util import (get_matching_files, attrdict, status_iterator,
from .htmlhelp import build_hhx ensuredir, relative_uri, os_path, SEP)
from .patchlevel import get_version_info, get_sys_version_info from sphinx.htmlhelp import build_hhx
from .htmlwriter import HTMLWriter from sphinx.patchlevel import get_version_info, get_sys_version_info
from .latexwriter import LaTeXWriter from sphinx.htmlwriter import HTMLWriter
from .environment import BuildEnvironment, NoUri from sphinx.latexwriter import LaTeXWriter
from .highlighting import pygments, highlight_block, get_stylesheet from sphinx.environment import BuildEnvironment, NoUri
from .util.console import bold, purple, green from sphinx.highlighting import pygments, highlight_block, get_stylesheet
from sphinx.util.console import bold, purple, green
from . import addnodes
# side effect: registers roles and directives # side effect: registers roles and directives
from . import roles from sphinx import roles
from . import directives from sphinx import directives
ENV_PICKLE_FILENAME = 'environment.pickle' ENV_PICKLE_FILENAME = 'environment.pickle'
LAST_BUILD_FILENAME = 'last_build' LAST_BUILD_FILENAME = 'last_build'
@ -53,33 +53,18 @@ class relpath_to(object):
self.builder = builder self.builder = builder
def __call__(self, otheruri, resource=False): def __call__(self, otheruri, resource=False):
if not resource: if not resource:
otheruri = self.builder.get_target_uri(otheruri) otheruri = self.builder.get_target_uri(otheruri + '.rst')
return relative_uri(self.baseuri, otheruri) return relative_uri(self.baseuri, otheruri)
class collect_env_warnings(object):
def __init__(self, builder):
self.builder = builder
self.warnings = []
def __enter__(self):
self.builder.env.set_warnfunc(self.warnings.append)
def __exit__(self, *args):
self.builder.env.set_warnfunc(self.builder.warn)
for warning in self.warnings:
self.builder.warn(warning)
class Builder(object): class Builder(object):
""" """
Builds target formats from the reST sources. Builds target formats from the reST sources.
""" """
option_spec = {}
def __init__(self, srcdirname, outdirname, doctreedirname, def __init__(self, srcdirname, outdirname, doctreedirname,
options, confoverrides=None, env=None, confoverrides=None, env=None, freshenv=False,
status_stream=None, warning_stream=None, status_stream=None, warning_stream=None):
freshenv=False):
self.srcdir = srcdirname self.srcdir = srcdirname
self.outdir = outdirname self.outdir = outdirname
self.doctreedir = doctreedirname self.doctreedir = doctreedirname
@ -87,9 +72,6 @@ class Builder(object):
os.mkdir(doctreedirname) os.mkdir(doctreedirname)
self.freshenv = freshenv self.freshenv = freshenv
self.options = attrdict(options)
self.validate_options()
self.status_stream = status_stream or sys.stdout self.status_stream = status_stream or sys.stdout
self.warning_stream = warning_stream or sys.stderr self.warning_stream = warning_stream or sys.stderr
@ -97,7 +79,12 @@ class Builder(object):
self.env = env self.env = env
self.config = {} self.config = {}
execfile(path.join(srcdirname, 'conf.py'), self.config) olddir = os.getcwd()
try:
os.chdir(srcdirname)
execfile(path.join(srcdirname, 'conf.py'), self.config)
finally:
os.chdir(olddir)
# remove potentially pickling-problematic values # remove potentially pickling-problematic values
del self.config['__builtins__'] del self.config['__builtins__']
for key, val in self.config.items(): for key, val in self.config.items():
@ -105,31 +92,23 @@ class Builder(object):
del self.config[key] del self.config[key]
if confoverrides: if confoverrides:
self.config.update(confoverrides) self.config.update(confoverrides)
# replace version info if 'auto' # replace version info if '<auto>'
if self.config['version'] == 'auto' or self.config['release'] == 'auto': if self.config['version'] == '<auto>' or self.config['release'] == '<auto>':
try: try:
version, release = get_version_info(srcdirname) version, release = get_version_info(srcdirname)
except (IOError, OSError): except (IOError, OSError):
version, release = get_sys_version_info() version, release = get_sys_version_info()
self.warn('Can\'t get version info from Include/patchlevel.h, ' self.warn('Can\'t get version info from Include/patchlevel.h, '
'using version of this interpreter (%s).' % release) 'using version of this interpreter (%s).' % release)
if self.config['version'] == 'auto': if self.config['version'] == '<auto>':
self.config['version'] = version self.config['version'] = version
if self.config['release'] == 'auto': if self.config['release'] == '<auto>':
self.config['release'] = release self.config['release'] = release
self.init() self.init()
# helper methods # helper methods
def validate_options(self):
for option in self.options:
if option not in self.option_spec:
raise ValueError('Got unexpected option %s' % option)
for option in self.option_spec:
if option not in self.options:
self.options[option] = False
def msg(self, message='', nonl=False, nobold=False): def msg(self, message='', nonl=False, nobold=False):
if not nobold: message = bold(message) if not nobold: message = bold(message)
if nonl: if nonl:
@ -214,16 +193,20 @@ class Builder(object):
updated_filenames = [] updated_filenames = []
# while reading, collect all warnings from docutils # while reading, collect all warnings from docutils
with collect_env_warnings(self): warnings = []
self.msg('reading, updating environment:', nonl=1) self.env.set_warnfunc(warnings.append)
iterator = self.env.update(self.config) self.msg('reading, updating environment:', nonl=1)
self.msg(iterator.next(), nonl=1, nobold=1) iterator = self.env.update(self.config)
for filename in iterator: self.msg(iterator.next(), nonl=1, nobold=1)
if not updated_filenames: for filename in iterator:
self.msg('') if not updated_filenames:
updated_filenames.append(filename) self.msg('')
self.msg(purple(filename), nonl=1, nobold=1) updated_filenames.append(filename)
self.msg() self.msg(purple(filename), nonl=1, nobold=1)
self.msg()
for warning in warnings:
self.warn(warning)
self.env.set_warnfunc(self.warn)
if updated_filenames: if updated_filenames:
# save the environment # save the environment
@ -260,12 +243,16 @@ class Builder(object):
self.prepare_writing(filenames) self.prepare_writing(filenames)
# write target files # write target files
with collect_env_warnings(self): warnings = []
self.msg('writing output...') self.env.set_warnfunc(warnings.append)
for filename in status_iterator(sorted(filenames), green, self.msg('writing output...')
stream=self.status_stream): for filename in status_iterator(sorted(filenames), green,
doctree = self.env.get_and_resolve_doctree(filename, self) stream=self.status_stream):
self.write_file(filename, doctree) doctree = self.env.get_and_resolve_doctree(filename, self)
self.write_file(filename, doctree)
for warning in warnings:
self.warn(warning)
self.env.set_warnfunc(self.warn)
def prepare_writing(self, filenames): def prepare_writing(self, filenames):
raise NotImplementedError raise NotImplementedError
@ -288,22 +275,28 @@ class StandaloneHTMLBuilder(Builder):
def init(self): def init(self):
"""Load templates.""" """Load templates."""
# lazily import this, maybe other builders won't need it # lazily import this, maybe other builders won't need it
from ._jinja import Environment, FileSystemLoader from sphinx._jinja import Environment, SphinxFileSystemLoader
# load templates # load templates
self.templates = {} self.templates = {}
templates_path = path.join(path.dirname(__file__), 'templates') templates_path = path.join(path.dirname(__file__), 'templates')
jinja_env = Environment(loader=FileSystemLoader(templates_path), self.jinja_env = Environment(loader=SphinxFileSystemLoader([templates_path]),
# disable traceback, more likely that something in the # disable traceback, more likely that something
# application is broken than in the templates # in the application is broken than in the templates
friendly_traceback=False) friendly_traceback=False)
# pre-load built-in templates
for fname in os.listdir(templates_path): for fname in os.listdir(templates_path):
if fname.endswith('.html'): if fname.endswith('.html'):
self.templates[fname[:-5]] = jinja_env.get_template(fname) self.templates[fname] = self.jinja_env.get_template(fname)
def get_template(self, name):
if name in self.templates:
return self.templates[name]
return self.jinja_env.get_template(name)
def render_partial(self, node): def render_partial(self, node):
"""Utility: Render a lone doctree node.""" """Utility: Render a lone doctree node."""
doc = new_document('foo') doc = new_document('<partial node>')
doc.append(node) doc.append(node)
return publish_parts( return publish_parts(
doc, doc,
@ -314,7 +307,7 @@ class StandaloneHTMLBuilder(Builder):
) )
def prepare_writing(self, filenames): def prepare_writing(self, filenames):
from .search import IndexBuilder from sphinx.search import IndexBuilder
self.indexer = IndexBuilder() self.indexer = IndexBuilder()
self.load_indexer(filenames) self.load_indexer(filenames)
self.docwriter = HTMLWriter(self) self.docwriter = HTMLWriter(self)
@ -331,13 +324,15 @@ class StandaloneHTMLBuilder(Builder):
self.last_updated = None self.last_updated = None
self.globalcontext = dict( self.globalcontext = dict(
last_updated = self.last_updated, project = self.config.get('project', 'Python'),
builder = self.name, copyright = self.config.get('copyright', ''),
release = self.config['release'], release = self.config['release'],
version = self.config['version'], version = self.config['version'],
last_updated = self.last_updated,
builder = self.name,
parents = [], parents = [],
len = len,
titles = {}, titles = {},
len = len, # the built-in
) )
def write_file(self, filename, doctree): def write_file(self, filename, doctree):
@ -375,7 +370,6 @@ class StandaloneHTMLBuilder(Builder):
context = dict( context = dict(
title = title, title = title,
sourcename = sourcename, sourcename = sourcename,
pathto = relpath_to(self, self.get_target_uri(filename)),
body = self.docwriter.parts['fragment'], body = self.docwriter.parts['fragment'],
toc = self.render_partial(self.env.get_toc_for(filename))['fragment'], toc = self.render_partial(self.env.get_toc_for(filename))['fragment'],
# only display a TOC if there's more than one item to show # only display a TOC if there's more than one item to show
@ -386,7 +380,7 @@ class StandaloneHTMLBuilder(Builder):
) )
self.index_file(filename, doctree, title) self.index_file(filename, doctree, title)
self.handle_file(filename, context) self.handle_page(filename[:-4], context)
def finish(self): def finish(self):
self.msg('writing additional files...') self.msg('writing additional files...')
@ -402,10 +396,8 @@ class StandaloneHTMLBuilder(Builder):
genindexcontext = dict( genindexcontext = dict(
genindexentries = self.env.index, genindexentries = self.env.index,
genindexcounts = indexcounts, genindexcounts = indexcounts,
current_page_name = 'genindex',
pathto = relpath_to(self, self.get_target_uri('genindex.rst')),
) )
self.handle_file('genindex.rst', genindexcontext, 'genindex') self.handle_page('genindex', genindexcontext, 'genindex.html')
# the global module index # the global module index
@ -422,12 +414,12 @@ class StandaloneHTMLBuilder(Builder):
cg = 0 # collapse group cg = 0 # collapse group
fl = '' # first letter fl = '' # first letter
for mn, (fn, sy, pl, dep) in modules: for mn, (fn, sy, pl, dep) in modules:
pl = pl.split(', ') if pl else [] pl = pl and pl.split(', ') or []
platforms.update(pl) platforms.update(pl)
if fl != mn[0].lower() and mn[0] != '_': if fl != mn[0].lower() and mn[0] != '_':
modindexentries.append(['', False, 0, False, modindexentries.append(['', False, 0, False,
mn[0].upper(), '', [], False]) mn[0].upper(), '', [], False])
tn = mn.partition('.')[0] tn = mn.split('.')[0]
if tn != mn: if tn != mn:
# submodule # submodule
if pmn == tn: if pmn == tn:
@ -447,32 +439,22 @@ class StandaloneHTMLBuilder(Builder):
modindexcontext = dict( modindexcontext = dict(
modindexentries = modindexentries, modindexentries = modindexentries,
platforms = platforms, platforms = platforms,
current_page_name = 'modindex',
pathto = relpath_to(self, self.get_target_uri('modindex.rst')),
) )
self.handle_file('modindex.rst', modindexcontext, 'modindex') self.handle_page('modindex', modindexcontext, 'modindex.html')
# the download page
downloadcontext = dict(
pathto = relpath_to(self, self.get_target_uri('download.rst')),
current_page_name = 'download',
download_base_url = self.config['html_download_base_url'],
)
self.handle_file('download.rst', downloadcontext, 'download')
# the index page
indexcontext = dict(
pathto = relpath_to(self, self.get_target_uri('index.rst')),
current_page_name = 'index',
)
self.handle_file('index.rst', indexcontext, 'index')
# the search page # the search page
searchcontext = dict( self.handle_page('search', {}, 'search.html')
pathto = relpath_to(self, self.get_target_uri('search.rst')),
current_page_name = 'search', # additional pages from conf.py
) for pagename, template in self.config.get('html_additional_pages', {}).items():
self.handle_file('search.rst', searchcontext, 'search') template = path.join(self.srcdir, template)
self.handle_page(pagename, {}, template)
# the index page
indextemplate = self.config.get('html_index')
if indextemplate:
indextemplate = path.join(self.srcdir, indextemplate)
self.handle_page('index', {'indextemplate': indextemplate}, 'index.html')
# copy style files # copy style files
self.msg('copying style files...') self.msg('copying style files...')
@ -512,8 +494,11 @@ class StandaloneHTMLBuilder(Builder):
def load_indexer(self, filenames): def load_indexer(self, filenames):
try: try:
with open(path.join(self.outdir, 'searchindex.json'), 'r') as f: f = open(path.join(self.outdir, 'searchindex.json'), 'r')
try:
self.indexer.load(f, 'json') self.indexer.load(f, 'json')
finally:
f.close()
except (IOError, OSError): except (IOError, OSError):
pass pass
# delete all entries for files that will be rebuilt # delete all entries for files that will be rebuilt
@ -522,32 +507,43 @@ class StandaloneHTMLBuilder(Builder):
def index_file(self, filename, doctree, title): def index_file(self, filename, doctree, title):
# only index pages with title # only index pages with title
if self.indexer is not None and title: if self.indexer is not None and title:
category = get_category(filename) self.indexer.feed(self.get_target_uri(filename)[:-5], # strip '.html'
if category is not None: title, doctree)
self.indexer.feed(self.get_target_uri(filename)[:-5], # strip '.html'
category, title, doctree)
def handle_file(self, filename, context, templatename='page'): def handle_page(self, pagename, context, templatename='page.html'):
ctx = self.globalcontext.copy() ctx = self.globalcontext.copy()
ctx['current_page_name'] = pagename
ctx['pathto'] = relpath_to(self, self.get_target_uri(pagename+'.rst'))
ctx['hasdoc'] = lambda name: name+'.rst' in self.env.all_files
sidebarfile = self.config.get('html_sidebars', {}).get(pagename)
if sidebarfile:
ctx['customsidebar'] = path.join(self.srcdir, sidebarfile)
ctx.update(context) ctx.update(context)
output = self.templates[templatename].render(ctx)
outfilename = path.join(self.outdir, os_path(filename)[:-4] + '.html') output = self.get_template(templatename).render(ctx)
outfilename = path.join(self.outdir, os_path(pagename) + '.html')
ensuredir(path.dirname(outfilename)) # normally different from self.outdir ensuredir(path.dirname(outfilename)) # normally different from self.outdir
try: try:
with codecs.open(outfilename, 'w', 'utf-8') as fp: f = codecs.open(outfilename, 'w', 'utf-8')
fp.write(output) try:
f.write(output)
finally:
f.close()
except (IOError, OSError), err: except (IOError, OSError), err:
self.warn("Error writing file %s: %s" % (outfilename, err)) self.warn("Error writing file %s: %s" % (outfilename, err))
if self.copysource and context.get('sourcename'): if self.copysource and context.get('sourcename'):
# copy the source file for the "show source" link # copy the source file for the "show source" link
shutil.copyfile(path.join(self.srcdir, os_path(filename)), shutil.copyfile(path.join(self.srcdir, os_path(pagename+'.rst')),
path.join(self.outdir, os_path(context['sourcename']))) path.join(self.outdir, os_path(context['sourcename'])))
def handle_finish(self): def handle_finish(self):
self.msg('dumping search index...') self.msg('dumping search index...')
self.indexer.prune([self.get_target_uri(fn)[:-5] for fn in self.env.all_files]) self.indexer.prune([self.get_target_uri(fn)[:-5] for fn in self.env.all_files])
with open(path.join(self.outdir, 'searchindex.json'), 'w') as f: f = open(path.join(self.outdir, 'searchindex.json'), 'w')
try:
self.indexer.dump(f, 'json') self.indexer.dump(f, 'json')
finally:
f.close()
class WebHTMLBuilder(StandaloneHTMLBuilder): class WebHTMLBuilder(StandaloneHTMLBuilder):
@ -581,44 +577,57 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
def load_indexer(self, filenames): def load_indexer(self, filenames):
try: try:
with open(path.join(self.outdir, 'searchindex.pickle'), 'r') as f: f = open(path.join(self.outdir, 'searchindex.pickle'), 'r')
try:
self.indexer.load(f, 'pickle') self.indexer.load(f, 'pickle')
finally:
f.close()
except (IOError, OSError): except (IOError, OSError):
pass pass
# delete all entries for files that will be rebuilt # delete all entries for files that will be rebuilt
self.indexer.prune(set(self.env.all_files) - set(filenames)) self.indexer.prune(set(self.env.all_files) - set(filenames))
def index_file(self, filename, doctree, title): def index_file(self, filename, doctree, title):
# only index pages with title and category # only index pages with title
if self.indexer is not None and title: if self.indexer is not None and title:
category = get_category(filename) self.indexer.feed(filename, title, doctree)
if category is not None:
self.indexer.feed(filename, category, title, doctree)
def handle_file(self, filename, context, templatename='page'): def handle_page(self, pagename, context, templatename='page.html'):
outfilename = path.join(self.outdir, os_path(filename)[:-4] + '.fpickle') context['current_page_name'] = pagename
sidebarfile = self.config.get('html_sidebars', {}).get(pagename, '')
if sidebarfile:
context['customsidebar'] = path.join(self.srcdir, sidebarfile)
outfilename = path.join(self.outdir, os_path(pagename) + '.fpickle')
ensuredir(path.dirname(outfilename)) ensuredir(path.dirname(outfilename))
context.pop('pathto', None) # can't be pickled f = open(outfilename, 'wb')
with file(outfilename, 'wb') as fp: try:
pickle.dump(context, fp, 2) pickle.dump(context, f, 2)
finally:
f.close()
# if there is a source file, copy the source file for the "show source" link # if there is a source file, copy the source file for the "show source" link
if context.get('sourcename'): if context.get('sourcename'):
source_name = path.join(self.outdir, 'sources', source_name = path.join(self.outdir, 'sources',
os_path(context['sourcename'])) os_path(context['sourcename']))
ensuredir(path.dirname(source_name)) ensuredir(path.dirname(source_name))
shutil.copyfile(path.join(self.srcdir, os_path(filename)), source_name) shutil.copyfile(path.join(self.srcdir, os_path(pagename)+'.rst'), source_name)
def handle_finish(self): def handle_finish(self):
# dump the global context # dump the global context
outfilename = path.join(self.outdir, 'globalcontext.pickle') outfilename = path.join(self.outdir, 'globalcontext.pickle')
with file(outfilename, 'wb') as fp: f = open(outfilename, 'wb')
pickle.dump(self.globalcontext, fp, 2) try:
pickle.dump(self.globalcontext, f, 2)
finally:
f.close()
self.msg('dumping search index...') self.msg('dumping search index...')
self.indexer.prune(self.env.all_files) self.indexer.prune(self.env.all_files)
with open(path.join(self.outdir, 'searchindex.pickle'), 'wb') as f: f = open(path.join(self.outdir, 'searchindex.pickle'), 'wb')
try:
self.indexer.dump(f, 'pickle') self.indexer.dump(f, 'pickle')
finally:
f.close()
# copy the environment file from the doctree dir to the output dir # copy the environment file from the doctree dir to the output dir
# as needed by the web app # as needed by the web app
@ -641,15 +650,11 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder):
""" """
name = 'htmlhelp' name = 'htmlhelp'
option_spec = {
'outname': 'Output file base name (default "pydoc")'
}
# don't copy the reST source # don't copy the reST source
copysource = False copysource = False
def handle_finish(self): def handle_finish(self):
build_hhx(self, self.outdir, self.options.get('outname') or 'pydoc') build_hhx(self, self.outdir, self.config.get('htmlhelp_basename', 'pydoc'))
class LaTeXBuilder(Builder): class LaTeXBuilder(Builder):
@ -660,6 +665,17 @@ class LaTeXBuilder(Builder):
def init(self): def init(self):
self.filenames = [] self.filenames = []
self.document_data = map(list, self.config.get('latex_documents', ()))
# assign subdirs to titles
self.titles = []
for entry in self.document_data:
# replace version with real version
entry[0] = entry[0].replace('<auto>', self.config['version'])
sourcename = entry[0]
if sourcename.endswith('/index.rst'):
sourcename = sourcename[:-9]
self.titles.append((sourcename, entry[2]))
def get_outdated_files(self): def get_outdated_files(self):
return 'all documents' # for now return 'all documents' # for now
@ -674,49 +690,42 @@ class LaTeXBuilder(Builder):
else: else:
return '' return ''
def get_document_data(self):
# Python specific...
for toplevel in ["c-api", "distutils", "documenting", "extending",
"install", "reference", "tutorial", "using", "library"]:
yield (toplevel + SEP + 'index.rst', toplevel+'.tex', 'manual')
yield ('whatsnew' + SEP + self.config['version'] + '.rst',
'whatsnew.tex', 'howto')
for howto in [fn for fn in self.env.all_files
if fn.startswith('howto'+SEP)
and not fn.endswith('index.rst')]:
yield (howto, 'howto-'+howto[6:-4]+'.tex', 'howto')
def write(self, *ignored): def write(self, *ignored):
# first, assemble the "special" docs that are in every PDF # first, assemble the "appendix" docs that are in every PDF
specials = [] appendices = []
for fname in ["glossary", "about", "license", "copyright"]: for fname in self.config.get('latex_appendices', []):
specials.append(self.env.get_doctree(fname+".rst")) appendices.append(self.env.get_doctree(fname))
docwriter = LaTeXWriter(self) docwriter = LaTeXWriter(self)
docsettings = OptionParser( docsettings = OptionParser(
defaults=self.env.settings, defaults=self.env.settings,
components=(docwriter,)).get_default_values() components=(docwriter,)).get_default_values()
for sourcename, targetname, docclass in self.get_document_data(): if not self.document_data:
self.warn('No "latex_documents" config setting found; no documents '
'will be written.')
for sourcename, targetname, title, author, docclass in self.document_data:
destination = FileOutput( destination = FileOutput(
destination_path=path.join(self.outdir, targetname), destination_path=path.join(self.outdir, targetname),
encoding='utf-8') encoding='utf-8')
print "processing", targetname + "...", print "processing", targetname + "...",
doctree = self.assemble_doctree( doctree = self.assemble_doctree(
sourcename, specials=(docclass == 'manual') and specials or []) sourcename, appendices=(docclass == 'manual') and appendices or [])
print "writing...", print "writing...",
doctree.settings = docsettings doctree.settings = docsettings
doctree.settings.author = author
doctree.settings.filename = sourcename doctree.settings.filename = sourcename
doctree.settings.docclass = docclass doctree.settings.docclass = docclass
output = docwriter.write(doctree, destination) output = docwriter.write(doctree, destination)
print "done" print "done"
def assemble_doctree(self, indexfile, specials): def assemble_doctree(self, indexfile, appendices):
self.filenames = set([indexfile, 'glossary.rst', 'about.rst', self.filenames = set([indexfile, 'glossary.rst', 'about.rst',
'license.rst', 'copyright.rst']) 'license.rst', 'copyright.rst'])
print green(indexfile), print green(indexfile),
def process_tree(filename, tree): def process_tree(filename, tree):
#tree = tree.deepcopy() XXX tree = tree.deepcopy()
for toctreenode in tree.traverse(addnodes.toctree): for toctreenode in tree.traverse(addnodes.toctree):
newnodes = [] newnodes = []
includefiles = map(str, toctreenode['includefiles']) includefiles = map(str, toctreenode['includefiles'])
@ -734,11 +743,25 @@ class LaTeXBuilder(Builder):
toctreenode.parent.replace(toctreenode, newnodes) toctreenode.parent.replace(toctreenode, newnodes)
return tree return tree
largetree = process_tree(indexfile, self.env.get_doctree(indexfile)) largetree = process_tree(indexfile, self.env.get_doctree(indexfile))
largetree.extend(specials) largetree.extend(appendices)
print print
print "resolving references..." print "resolving references..."
# XXX problem here: :ref:s to distant tex files
self.env.resolve_references(largetree, indexfile, self) self.env.resolve_references(largetree, indexfile, self)
# resolve :ref:s to distant tex files -- we can't add a cross-reference,
# but append the document name
for pendingnode in largetree.traverse(addnodes.pending_xref):
filename = pendingnode['reffilename']
sectname = pendingnode['refsectname']
newnodes = [nodes.emphasis(sectname, sectname)]
for subdir, title in self.titles:
if filename.startswith(subdir):
newnodes.append(nodes.Text(' (in ', ' (in '))
newnodes.append(nodes.emphasis(title, title))
newnodes.append(nodes.Text(')', ')'))
break
else:
pass
pendingnode.replace_self(newnodes)
return largetree return largetree
def finish(self): def finish(self):
@ -757,15 +780,15 @@ class ChangesBuilder(Builder):
name = 'changes' name = 'changes'
def init(self): def init(self):
from ._jinja import Environment, FileSystemLoader from sphinx._jinja import Environment, FileSystemLoader
templates_path = path.join(path.dirname(__file__), 'templates') templates_path = path.join(path.dirname(__file__), 'templates')
jinja_env = Environment(loader=FileSystemLoader(templates_path), jinja_env = Environment(loader=SphinxFileSystemLoader([templates_path]),
# disable traceback, more likely that something in the # disable traceback, more likely that something in the
# application is broken than in the templates # application is broken than in the templates
friendly_traceback=False) friendly_traceback=False)
self.ftemplate = jinja_env.get_template('versionchanges_frameset.html') self.ftemplate = jinja_env.get_template('changes/frameset.html')
self.vtemplate = jinja_env.get_template('versionchanges.html') self.vtemplate = jinja_env.get_template('changes/versionchanges.html')
self.stemplate = jinja_env.get_template('rstsource.html') self.stemplate = jinja_env.get_template('changes/rstsource.html')
def get_outdated_files(self): def get_outdated_files(self):
return self.outdir return self.outdir
@ -813,15 +836,22 @@ class ChangesBuilder(Builder):
(entry, filename, lineno)) (entry, filename, lineno))
ctx = { ctx = {
'project': self.config.get('project', 'Python'),
'version': ver, 'version': ver,
'libchanges': sorted(libchanges.iteritems()), 'libchanges': sorted(libchanges.iteritems()),
'apichanges': sorted(apichanges), 'apichanges': sorted(apichanges),
'otherchanges': sorted(otherchanges.iteritems()), 'otherchanges': sorted(otherchanges.iteritems()),
} }
with open(path.join(self.outdir, 'index.html'), 'w') as f: f = open(path.join(self.outdir, 'index.html'), 'w')
try:
f.write(self.ftemplate.render(ctx)) f.write(self.ftemplate.render(ctx))
with open(path.join(self.outdir, 'changes.html'), 'w') as f: finally:
f.close()
f = open(path.join(self.outdir, 'changes.html'), 'w')
try:
f.write(self.vtemplate.render(ctx)) f.write(self.vtemplate.render(ctx))
finally:
f.close()
hltext = ['.. versionadded:: %s' % ver, hltext = ['.. versionadded:: %s' % ver,
'.. versionchanged:: %s' % ver, '.. versionchanged:: %s' % ver,
@ -837,14 +867,17 @@ class ChangesBuilder(Builder):
self.msg('copying source files...') self.msg('copying source files...')
for filename in self.env.all_files: for filename in self.env.all_files:
with open(path.join(self.srcdir, os_path(filename))) as f: f = open(path.join(self.srcdir, os_path(filename)))
lines = f.readlines() lines = f.readlines()
targetfn = path.join(self.outdir, 'rst', os_path(filename)) + '.html' targetfn = path.join(self.outdir, 'rst', os_path(filename)) + '.html'
ensuredir(path.dirname(targetfn)) ensuredir(path.dirname(targetfn))
with codecs.open(targetfn, 'w', 'utf8') as f: f = codecs.open(targetfn, 'w', 'utf8')
try:
text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines)) text = ''.join(hl(i+1, line) for (i, line) in enumerate(lines))
ctx = {'filename': filename, 'text': text} ctx = {'filename': filename, 'text': text}
f.write(self.stemplate.render(ctx)) f.write(self.stemplate.render(ctx))
finally:
f.close()
shutil.copyfile(path.join(path.dirname(__file__), 'style', 'default.css'), shutil.copyfile(path.join(path.dirname(__file__), 'style', 'default.css'),
path.join(self.outdir, 'default.css')) path.join(self.outdir, 'default.css'))

View File

@ -8,7 +8,6 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import re import re
import string import string
@ -19,7 +18,7 @@ from docutils import nodes
from docutils.parsers.rst import directives, roles from docutils.parsers.rst import directives, roles
from docutils.parsers.rst.directives import admonitions from docutils.parsers.rst.directives import admonitions
from . import addnodes from sphinx import addnodes
# ------ index markup -------------------------------------------------------------- # ------ index markup --------------------------------------------------------------
@ -142,7 +141,7 @@ def parse_py_signature(signode, sig, desctype, env):
else: else:
fullname = env.currclass + '.' + name fullname = env.currclass + '.' + name
else: else:
fullname = classname + name if classname else name fullname = classname and classname + name or name
if classname: if classname:
signode += addnodes.desc_classname(classname, classname) signode += addnodes.desc_classname(classname, classname)
@ -285,7 +284,7 @@ def add_refcount_annotation(env, node, name):
if entry.result_refs is None: if entry.result_refs is None:
rc += "Always NULL." rc += "Always NULL."
else: else:
rc += ("New" if entry.result_refs else "Borrowed") + " reference." rc += (entry.result_refs and "New" or "Borrowed") + " reference."
node += addnodes.refcount(rc, rc) node += addnodes.refcount(rc, rc)
@ -347,7 +346,7 @@ def desc_directive(desctype, arguments, options, content, lineno,
# only add target and index entry if this is the first description of the # only add target and index entry if this is the first description of the
# function name in this desc block # function name in this desc block
if not noindex and name not in names: if not noindex and name not in names:
fullname = (env.currmodule + '.' if env.currmodule else '') + name fullname = (env.currmodule and env.currmodule + '.' or '') + name
# note target # note target
if fullname not in state.document.ids: if fullname not in state.document.ids:
signode['names'].append(fullname) signode['names'].append(fullname)
@ -612,8 +611,9 @@ def literalinclude_directive(name, arguments, options, content, lineno,
fn = path.normpath(path.join(source_dir, fn)) fn = path.normpath(path.join(source_dir, fn))
try: try:
with open(fn) as f: f = open(fn)
text = f.read() text = f.read()
f.close()
except (IOError, OSError): except (IOError, OSError):
retnode = state.document.reporter.warning( retnode = state.document.reporter.warning(
'Include file %r not found or reading it failed' % arguments[0], line=lineno) 'Include file %r not found or reading it failed' % arguments[0], line=lineno)

View File

@ -8,18 +8,22 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import re import re
import os import os
import time import time
import heapq import heapq
import hashlib
import difflib import difflib
import itertools import itertools
import cPickle as pickle import cPickle as pickle
from os import path from os import path
from string import uppercase from string import uppercase
try:
import hashlib
md5 = hashlib.md5
except:
import md5
md5 = md5.new
from docutils import nodes from docutils import nodes
from docutils.io import FileInput from docutils.io import FileInput
@ -37,9 +41,9 @@ Body.enum.converters['loweralpha'] = \
Body.enum.converters['lowerroman'] = \ Body.enum.converters['lowerroman'] = \
Body.enum.converters['upperroman'] = lambda x: None Body.enum.converters['upperroman'] = lambda x: None
from . import addnodes from sphinx import addnodes
from .util import get_matching_files, os_path, SEP from sphinx.util import get_matching_files, os_path, SEP
from .refcounting import Refcounts from sphinx.refcounting import Refcounts
default_settings = { default_settings = {
'embed_stylesheet': False, 'embed_stylesheet': False,
@ -156,8 +160,11 @@ class BuildEnvironment:
@staticmethod @staticmethod
def frompickle(filename): def frompickle(filename):
with open(filename, 'rb') as picklefile: picklefile = open(filename, 'rb')
try:
env = pickle.load(picklefile) env = pickle.load(picklefile)
finally:
picklefile.close()
if env.version != ENV_VERSION: if env.version != ENV_VERSION:
raise IOError('env version not current') raise IOError('env version not current')
return env return env
@ -166,8 +173,11 @@ class BuildEnvironment:
# remove unpicklable attributes # remove unpicklable attributes
warnfunc = self._warnfunc warnfunc = self._warnfunc
self.set_warnfunc(None) self.set_warnfunc(None)
with open(filename, 'wb') as picklefile: picklefile = open(filename, 'wb')
try:
pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL) pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL)
finally:
picklefile.close()
# reset stream # reset stream
self.set_warnfunc(warnfunc) self.set_warnfunc(warnfunc)
@ -178,9 +188,8 @@ class BuildEnvironment:
self.srcdir = srcdir self.srcdir = srcdir
self.config = {} self.config = {}
# read the refcounts file # refcount data if present
self.refcounts = Refcounts.fromfile( self.refcounts = {}
path.join(self.srcdir, 'data', 'refcounts.dat'))
# the docutils settings for building # the docutils settings for building
self.settings = default_settings.copy() self.settings = default_settings.copy()
@ -194,7 +203,7 @@ class BuildEnvironment:
# Build times -- to determine changed files # Build times -- to determine changed files
# Also use this as an inventory of all existing and built filenames. # Also use this as an inventory of all existing and built filenames.
self.all_files = {} # filename -> (mtime, md5) at the time of build self.all_files = {} # filename -> (mtime, md5sum) at the time of build
# File metadata # File metadata
self.metadata = {} # filename -> dict of metadata items self.metadata = {} # filename -> dict of metadata items
@ -291,21 +300,22 @@ class BuildEnvironment:
os_path(filename)[:-3] + 'doctree')): os_path(filename)[:-3] + 'doctree')):
changed.append(filename) changed.append(filename)
continue continue
mtime, md5 = self.all_files[filename] mtime, md5sum = self.all_files[filename]
newmtime = path.getmtime(path.join(self.srcdir, os_path(filename))) newmtime = path.getmtime(path.join(self.srcdir, os_path(filename)))
if newmtime == mtime: if newmtime == mtime:
continue continue
# check the MD5 # check the MD5
#with file(path.join(self.srcdir, filename), 'rb') as f: #with file(path.join(self.srcdir, filename), 'rb') as f:
# newmd5 = hashlib.md5(f.read()).digest() # newmd5sum = md5(f.read()).digest()
#if newmd5 != md5: #if newmd5sum != md5sum:
changed.append(filename) changed.append(filename)
return added, changed, removed return added, changed, removed
# If one of these config values changes, all files need to be re-read. # If one of these config values changes, all files need to be re-read.
influential_config_values = [ influential_config_values = [
'version', 'release', 'today', 'today_fmt', 'unused_files' 'version', 'release', 'today', 'today_fmt', 'unused_files',
'project', 'refcount_file', 'add_function_parentheses', 'add_module_names'
] ]
def update(self, config): def update(self, config):
@ -330,14 +340,15 @@ class BuildEnvironment:
self.config = config self.config = config
# read the refcounts file
if self.config.get('refcount_file'):
self.refcounts = Refcounts.fromfile(
path.join(self.srcdir, self.config['refcount_file']))
# clear all files no longer present # clear all files no longer present
for filename in removed: for filename in removed:
self.clear_file(filename) self.clear_file(filename)
# re-read the refcount file
self.refcounts = Refcounts.fromfile(
path.join(self.srcdir, 'data', 'refcounts.dat'))
# read all new and changed files # read all new and changed files
for filename in added + changed: for filename in added + changed:
yield filename yield filename
@ -364,9 +375,12 @@ class BuildEnvironment:
self.build_toc_from(filename, doctree) self.build_toc_from(filename, doctree)
# calculate the MD5 of the file at time of build # calculate the MD5 of the file at time of build
with file(src_path, 'rb') as f: f = open(src_path, 'rb')
md5 = hashlib.md5(f.read()).digest() try:
self.all_files[filename] = (path.getmtime(src_path), md5) md5sum = md5(f.read()).digest()
finally:
f.close()
self.all_files[filename] = (path.getmtime(src_path), md5sum)
# make it picklable # make it picklable
doctree.reporter = None doctree.reporter = None
@ -388,8 +402,11 @@ class BuildEnvironment:
dirname = path.dirname(doctree_filename) dirname = path.dirname(doctree_filename)
if not path.isdir(dirname): if not path.isdir(dirname):
os.makedirs(dirname) os.makedirs(dirname)
with file(doctree_filename, 'wb') as f: f = open(doctree_filename, 'wb')
try:
pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL)
finally:
f.close()
else: else:
return doctree return doctree
@ -446,9 +463,9 @@ class BuildEnvironment:
includefiles_len = len(includefiles) includefiles_len = len(includefiles)
for i, includefile in enumerate(includefiles): for i, includefile in enumerate(includefiles):
# the "previous" file for the first toctree item is the parent # the "previous" file for the first toctree item is the parent
previous = includefiles[i-1] if i > 0 else filename previous = i > 0 and includefiles[i-1] or filename
# the "next" file for the last toctree item is the parent again # the "next" file for the last toctree item is the parent again
next = includefiles[i+1] if i < includefiles_len-1 else filename next = i < includefiles_len-1 and includefiles[i+1] or filename
self.toctree_relations[includefile] = [filename, previous, next] self.toctree_relations[includefile] = [filename, previous, next]
# note that if the included file is rebuilt, this one must be # note that if the included file is rebuilt, this one must be
# too (since the TOC of the included file could have changed) # too (since the TOC of the included file could have changed)
@ -539,8 +556,11 @@ class BuildEnvironment:
def get_doctree(self, filename): def get_doctree(self, filename):
"""Read the doctree for a file from the pickle and return it.""" """Read the doctree for a file from the pickle and return it."""
doctree_filename = path.join(self.doctreedir, os_path(filename)[:-3] + 'doctree') doctree_filename = path.join(self.doctreedir, os_path(filename)[:-3] + 'doctree')
with file(doctree_filename, 'rb') as f: f = open(doctree_filename, 'rb')
try:
doctree = pickle.load(f) doctree = pickle.load(f)
finally:
f.close()
doctree.reporter = Reporter(filename, 2, 4, stream=RedirStream(self._warnfunc)) doctree.reporter = Reporter(filename, 2, 4, stream=RedirStream(self._warnfunc))
return doctree return doctree
@ -617,9 +637,11 @@ class BuildEnvironment:
if filename == docfilename: if filename == docfilename:
newnode['refid'] = labelid newnode['refid'] = labelid
else: else:
# in case the following calls raises NoUri... # set more info in contnode in case the following call
# else the final node will contain a label name # raises NoUri, the builder will have to resolve these
contnode = innernode contnode = addnodes.pending_xref('')
contnode['reffilename'] = filename
contnode['refsectname'] = sectname
newnode['refuri'] = builder.get_relative_uri( newnode['refuri'] = builder.get_relative_uri(
docfilename, filename) + '#' + labelid docfilename, filename) + '#' + labelid
newnode.append(innernode) newnode.append(innernode)
@ -669,14 +691,14 @@ class BuildEnvironment:
newnode['refuri'] = ( newnode['refuri'] = (
builder.get_relative_uri(docfilename, filename) + anchor) builder.get_relative_uri(docfilename, filename) + anchor)
newnode['reftitle'] = '%s%s%s' % ( newnode['reftitle'] = '%s%s%s' % (
('(%s) ' % platform if platform else ''), (platform and '(%s) ' % platform),
synopsis, (' (deprecated)' if deprecated else '')) synopsis, (deprecated and ' (deprecated)' or ''))
newnode.append(contnode) newnode.append(contnode)
else: else:
# "descrefs" # "descrefs"
modname = node['modname'] modname = node['modname']
clsname = node['classname'] clsname = node['classname']
searchorder = 1 if node.hasattr('refspecific') else 0 searchorder = node.hasattr('refspecific') and 1 or 0
name, desc = self.find_desc(modname, clsname, name, desc = self.find_desc(modname, clsname,
target, typ, searchorder) target, typ, searchorder)
if not desc: if not desc:
@ -716,7 +738,10 @@ class BuildEnvironment:
# new entry types must be listed in directives.py! # new entry types must be listed in directives.py!
for type, string, tid, alias in entries: for type, string, tid, alias in entries:
if type == 'single': if type == 'single':
entry, _, subentry = string.partition(';') try:
entry, subentry = string.split(';', 1)
except:
entry, subentry = string, ''
add_entry(entry.strip(), subentry.strip()) add_entry(entry.strip(), subentry.strip())
elif type == 'pair': elif type == 'pair':
first, second = map(lambda x: x.strip(), string.split(';', 1)) first, second = map(lambda x: x.strip(), string.split(';', 1))

View File

@ -9,9 +9,9 @@
:license: BSD. :license: BSD.
""" """
import sys
import cgi import cgi
import parser import parser
from collections import defaultdict
try: try:
import pygments import pygments
@ -42,7 +42,7 @@ else:
Number: '#208050', Number: '#208050',
}) })
lexers = defaultdict(TextLexer, lexers = dict(
none = TextLexer(), none = TextLexer(),
python = PythonLexer(), python = PythonLexer(),
pycon = PythonConsoleLexer(), pycon = PythonConsoleLexer(),
@ -71,8 +71,12 @@ def highlight_block(source, lang, dest='html'):
else: else:
# maybe Python -- try parsing it # maybe Python -- try parsing it
try: try:
parser.suite('from __future__ import with_statement\n' + # if we're using 2.5, use the with statement
source + '\n') if sys.version_info >= (2, 5):
parser.suite('from __future__ import with_statement\n' +
source + '\n')
else:
parser.suite(source + '\n')
except (SyntaxError, UnicodeEncodeError): except (SyntaxError, UnicodeEncodeError):
return unhighlighted() return unhighlighted()
else: else:

View File

@ -9,7 +9,6 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import os import os
import cgi import cgi
@ -17,7 +16,7 @@ from os import path
from docutils import nodes from docutils import nodes
from . import addnodes from sphinx import addnodes
# Project file (*.hhp) template. 'outname' is the file basename (like # Project file (*.hhp) template. 'outname' is the file basename (like
# the pythlp in pythlp.hhp); 'version' is the doc version number (like # the pythlp in pythlp.hhp); 'version' is the doc version number (like
@ -64,10 +63,10 @@ Full text search stop list file=%(outname)s.stp
Full-text search=Yes Full-text search=Yes
Index file=%(outname)s.hhk Index file=%(outname)s.hhk
Language=0x409 Language=0x409
Title=Python %(version)s Documentation Title=%(project)s %(version)s Documentation
[WINDOWS] [WINDOWS]
%(outname)s="Python %(version)s Documentation","%(outname)s.hhc","%(outname)s.hhk",\ %(outname)s="%(project)s %(version)s Documentation","%(outname)s.hhc","%(outname)s.hhk",\
"index.html","index.html",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0 "index.html","index.html",,,,,0x63520,220,0x10384e,[0,0,1024,768],,,,,,,0
[FILES] [FILES]
@ -119,24 +118,32 @@ was will with
def build_hhx(builder, outdir, outname): def build_hhx(builder, outdir, outname):
builder.msg('dumping stopword list...') builder.msg('dumping stopword list...')
with open(path.join(outdir, outname+'.stp'), 'w') as f: f = open(path.join(outdir, outname+'.stp'), 'w')
try:
for word in sorted(stopwords): for word in sorted(stopwords):
print >>f, word print >>f, word
finally:
f.close()
builder.msg('writing project file...') builder.msg('writing project file...')
with open(path.join(outdir, outname+'.hhp'), 'w') as f: f = open(path.join(outdir, outname+'.hhp'), 'w')
try:
f.write(project_template % {'outname': outname, f.write(project_template % {'outname': outname,
'version': builder.config['version']}) 'version': builder.config['version'],
'project': builder.config['project']})
if not outdir.endswith(os.sep): if not outdir.endswith(os.sep):
outdir += os.sep outdir += os.sep
olen = len(outdir) olen = len(outdir)
for root, dirs, files in os.walk(outdir): for root, dirs, files in os.walk(outdir):
for fn in files: for fn in files:
if fn.endswith(('.html', '.css', '.js')): if fn.endswith('.html') or fn.endswith('.css') or fn.endswith('.js'):
print >>f, path.join(root, fn)[olen:].replace('/', '\\') print >>f, path.join(root, fn)[olen:].replace('/', '\\')
finally:
f.close()
builder.msg('writing TOC file...') builder.msg('writing TOC file...')
with open(path.join(outdir, outname+'.hhc'), 'w') as f: f = open(path.join(outdir, outname+'.hhc'), 'w')
try:
f.write(contents_header) f.write(contents_header)
# special books # special books
f.write('<LI> ' + object_sitemap % ('Main page', 'index.html')) f.write('<LI> ' + object_sitemap % ('Main page', 'index.html'))
@ -167,9 +174,12 @@ def build_hhx(builder, outdir, outname):
write_toc(node[0], ullevel) write_toc(node[0], ullevel)
write_toc(toc) write_toc(toc)
f.write(contents_footer) f.write(contents_footer)
finally:
f.close()
builder.msg('writing index file...') builder.msg('writing index file...')
with open(path.join(outdir, outname+'.hhk'), 'w') as f: f = open(path.join(outdir, outname+'.hhk'), 'w')
try:
f.write('<UL>\n') f.write('<UL>\n')
def write_index(title, refs, subitems): def write_index(title, refs, subitems):
if refs: if refs:
@ -186,3 +196,5 @@ def build_hhx(builder, outdir, outname):
for title, (refs, subitems) in group: for title, (refs, subitems) in group:
write_index(title, refs, subitems) write_index(title, refs, subitems)
f.write('</UL>\n') f.write('</UL>\n')
finally:
f.close()

View File

@ -12,7 +12,7 @@
from docutils import nodes from docutils import nodes
from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator
from .util.smartypants import sphinx_smarty_pants from sphinx.util.smartypants import sphinx_smarty_pants
class HTMLWriter(Writer): class HTMLWriter(Writer):
@ -162,7 +162,7 @@ def translator_class(builder):
# overwritten # overwritten
def visit_literal_block(self, node): def visit_literal_block(self, node):
from .highlighting import highlight_block from sphinx.highlighting import highlight_block
self.body.append(highlight_block(node.rawsource, self.highlightlang)) self.body.append(highlight_block(node.rawsource, self.highlightlang))
raise nodes.SkipNode raise nodes.SkipNode
@ -246,7 +246,8 @@ def translator_class(builder):
def depart_title(self, node): def depart_title(self, node):
close_tag = self.context[-1] close_tag = self.context[-1]
if builder.name != 'htmlhelp' and \ if builder.name != 'htmlhelp' and \
close_tag.startswith(('</h', '</a></h')) and \ (close_tag.startswith('</h') or
close_tag.startswith('</a></h')) and \
node.parent.hasattr('ids') and node.parent['ids']: node.parent.hasattr('ids') and node.parent['ids']:
aname = node.parent['ids'][0] aname = node.parent['ids'][0]
# add permalink anchor # add permalink anchor

View File

@ -18,8 +18,8 @@ import string
from docutils import frontend, nodes, languages, writers, utils from docutils import frontend, nodes, languages, writers, utils
from . import addnodes from sphinx import addnodes
from . import highlighting from sphinx import highlighting
HEADER = r'''%% Generated by Sphinx. HEADER = r'''%% Generated by Sphinx.
@ -30,12 +30,8 @@ HEADER = r'''%% Generated by Sphinx.
\title{%(title)s} \title{%(title)s}
\date{%(date)s} \date{%(date)s}
\release{%(release)s} \release{%(release)s}
\author{Guido van Rossum\\ %% XXX \author{%(author)s}
Fred L. Drake, Jr., editor} %(preamble)s
\authoraddress{
\strong{Python Software Foundation}\\
Email: \email{docs@python.org}
}
\makeindex \makeindex
\makemodindex \makemodindex
''' '''
@ -103,6 +99,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.options = {'docclass': docclass, self.options = {'docclass': docclass,
'papersize': paper, 'papersize': paper,
'pointsize': builder.config.get('latex_font_size', '10pt'), 'pointsize': builder.config.get('latex_font_size', '10pt'),
'preamble': builder.config['latex_preamble'],
'author': document.settings.author,
'filename': document.settings.filename, 'filename': document.settings.filename,
'title': None, # is determined later 'title': None, # is determined later
'release': builder.config['release'], 'release': builder.config['release'],
@ -112,7 +110,10 @@ class LaTeXTranslator(nodes.NodeVisitor):
self.descstack = [] self.descstack = []
self.highlightlang = 'python' self.highlightlang = 'python'
self.written_ids = set() self.written_ids = set()
self.top_sectionlevel = 0 if docclass == 'manual' else 1 if docclass == 'manual':
self.top_sectionlevel = 0
else:
self.top_sectionlevel = 1
# flags # flags
self.verbatim = None self.verbatim = None
self.in_title = 0 self.in_title = 0
@ -577,7 +578,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
uri = node.get('refuri', '') uri = node.get('refuri', '')
if self.in_title or not uri: if self.in_title or not uri:
self.context.append('') self.context.append('')
elif uri.startswith(('mailto:', 'http:', 'ftp:')): elif uri.startswith('mailto:') or uri.startswith('http:') or \
uri.startswith('ftp:'):
self.body.append('\\href{%s}{' % self.encode(uri)) self.body.append('\\href{%s}{' % self.encode(uri))
self.context.append('}') self.context.append('}')
elif uri.startswith('#'): elif uri.startswith('#'):

View File

@ -6,10 +6,11 @@
Extract version info from Include/patchlevel.h. Extract version info from Include/patchlevel.h.
Adapted from Doc/tools/getversioninfo. Adapted from Doc/tools/getversioninfo.
XXX Python specific
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import os import os
import re import re
@ -23,12 +24,15 @@ def get_version_info(srcdir):
rx = re.compile(r"\s*#define\s+([a-zA-Z][a-zA-Z_0-9]*)\s+([a-zA-Z_0-9]+)") rx = re.compile(r"\s*#define\s+([a-zA-Z][a-zA-Z_0-9]*)\s+([a-zA-Z_0-9]+)")
d = {} d = {}
with open(patchlevel_h) as f: f = open(patchlevel_h)
try:
for line in f: for line in f:
m = rx.match(line) m = rx.match(line)
if m is not None: if m is not None:
name, value = m.group(1, 2) name, value = m.group(1, 2)
d[name] = value d[name] = value
finally:
f.close()
release = version = "%s.%s" % (d["PY_MAJOR_VERSION"], d["PY_MINOR_VERSION"]) release = version = "%s.%s" % (d["PY_MAJOR_VERSION"], d["PY_MINOR_VERSION"])
micro = int(d["PY_MICRO_VERSION"]) micro = int(d["PY_MICRO_VERSION"])

View File

@ -9,8 +9,6 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
class RCEntry: class RCEntry:
def __init__(self, name): def __init__(self, name):
@ -24,7 +22,8 @@ class Refcounts(dict):
@classmethod @classmethod
def fromfile(cls, filename): def fromfile(cls, filename):
d = cls() d = cls()
with open(filename, 'r') as fp: fp = open(filename, 'r')
try:
for line in fp: for line in fp:
line = line.strip() line = line.strip()
if line[:1] in ("", "#"): if line[:1] in ("", "#"):
@ -49,4 +48,6 @@ class Refcounts(dict):
else: else:
entry.result_type = type entry.result_type = type
entry.result_refs = refcount entry.result_refs = refcount
finally:
fp.close()
return d return d

View File

@ -14,7 +14,7 @@ import re
from docutils import nodes, utils from docutils import nodes, utils
from docutils.parsers.rst import roles from docutils.parsers.rst import roles
from . import addnodes from sphinx import addnodes
ws_re = re.compile(r'\s+') ws_re = re.compile(r'\s+')
@ -128,7 +128,10 @@ def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
if typ == 'term': if typ == 'term':
pnode['reftarget'] = ws_re.sub(' ', text).lower() pnode['reftarget'] = ws_re.sub(' ', text).lower()
elif typ == 'option': elif typ == 'option':
pnode['reftarget'] = text[1:] if text[0] in '-/' else text if text[0] in '-/':
pnode['reftarget'] = text[1:]
else:
pnode['reftarget'] = text
else: else:
pnode['reftarget'] = ws_re.sub('', text) pnode['reftarget'] = ws_re.sub('', text)
pnode['modname'] = env.currmodule pnode['modname'] = env.currmodule

View File

@ -10,12 +10,11 @@
""" """
import re import re
import pickle import pickle
from collections import defaultdict
from docutils.nodes import Text, NodeVisitor from docutils.nodes import Text, NodeVisitor
from .util.stemmer import PorterStemmer from sphinx.util.stemmer import PorterStemmer
from .util.json import dump_json, load_json from sphinx.util.json import dump_json, load_json
word_re = re.compile(r'\w+(?u)') word_re = re.compile(r'\w+(?u)')
@ -61,18 +60,14 @@ class IndexBuilder(object):
self._titles = {} self._titles = {}
# stemmed word -> set(filenames) # stemmed word -> set(filenames)
self._mapping = {} self._mapping = {}
# category -> set(filenames)
self._categories = {}
def load(self, stream, format): def load(self, stream, format):
"""Reconstruct from frozen data.""" """Reconstruct from frozen data."""
frozen = self.formats[format][1](stream.read()) frozen = self.formats[format][1](stream.read())
index2fn = frozen[0] index2fn = frozen[0]
self._titles = dict(zip(frozen[0], frozen[2])) self._titles = dict(zip(frozen[0], frozen[1]))
self._categories = dict((k, set(index2fn[i] for i in v))
for (k, v) in frozen[1].iteritems())
self._mapping = dict((k, set(index2fn[i] for i in v)) self._mapping = dict((k, set(index2fn[i] for i in v))
for (k, v) in frozen[3].iteritems()) for (k, v) in frozen[2].iteritems())
def dump(self, stream, format): def dump(self, stream, format):
"""Dump the frozen index to a stream.""" """Dump the frozen index to a stream."""
@ -87,8 +82,6 @@ class IndexBuilder(object):
fn2index = dict((f, i) for (i, f) in enumerate(fns)) fn2index = dict((f, i) for (i, f) in enumerate(fns))
return [ return [
fns, fns,
dict((k, [fn2index[fn] for fn in v])
for (k, v) in self._categories.iteritems()),
titles, titles,
dict((k, [fn2index[fn] for fn in v]) dict((k, [fn2index[fn] for fn in v])
for (k, v) in self._mapping.iteritems()), for (k, v) in self._mapping.iteritems()),
@ -103,13 +96,10 @@ class IndexBuilder(object):
self._titles = new_titles self._titles = new_titles
for wordnames in self._mapping.itervalues(): for wordnames in self._mapping.itervalues():
wordnames.intersection_update(filenames) wordnames.intersection_update(filenames)
for catnames in self._categories.itervalues():
catnames.intersection_update(filenames)
def feed(self, filename, category, title, doctree): def feed(self, filename, title, doctree):
"""Feed a doctree to the index.""" """Feed a doctree to the index."""
self._titles[filename] = title self._titles[filename] = title
self._categories.setdefault(category, set()).add(filename)
visitor = WordCollector(doctree) visitor = WordCollector(doctree)
doctree.walk(visitor) doctree.walk(visitor)
@ -125,25 +115,24 @@ class SearchFrontend(object):
""" """
def __init__(self, index): def __init__(self, index):
self.filenames, self.areas, self.titles, self.words = index self.filenames, self.titles, self.words = index
self._stemmer = Stemmer() self._stemmer = Stemmer()
def query(self, required, excluded, areas): def query(self, required, excluded):
file_map = defaultdict(set) file_map = {}
for word in required: for word in required:
if word not in self.words: if word not in self.words:
break break
for fid in self.words[word]: for fid in self.words[word]:
file_map[fid].add(word) file_map.setdefault(fid, set()).add(word)
return sorted(((self.filenames[fid], self.titles[fid]) return sorted(((self.filenames[fid], self.titles[fid])
for fid, words in file_map.iteritems() for fid, words in file_map.iteritems()
if len(words) == len(required) and if len(words) == len(required) and not
any(fid in self.areas.get(area, ()) for area in areas) and not
any(fid in self.words.get(word, ()) for word in excluded) any(fid in self.words.get(word, ()) for word in excluded)
), key=lambda x: x[1].lower()) ), key=lambda x: x[1].lower())
def search(self, searchstring, areas): def search(self, searchstring):
required = set() required = set()
excluded = set() excluded = set()
for word in searchstring.split(): for word in searchstring.split():
@ -154,4 +143,4 @@ class SearchFrontend(object):
storage = required storage = required
storage.add(self._stemmer.stem(word.lower())) storage.add(self._stemmer.stem(word.lower()))
return self.query(required, excluded, areas) return self.query(required, excluded)

View File

@ -1,5 +1,5 @@
/** /**
* Python Doc Design * Sphinx Doc Design
*/ */
body { body {

View File

@ -1,5 +1,5 @@
/** /**
* Python Doc Design -- Right Side Bar Overrides * Sphinx Doc Design -- Right Side Bar Overrides
*/ */

View File

@ -228,27 +228,15 @@ var Search = {
var params = $.getQueryParameters(); var params = $.getQueryParameters();
if (params.q) { if (params.q) {
var query = params.q[0]; var query = params.q[0];
var areas = params.area || [];
// auto default
if (areas.length == 1 && areas[0] == 'default') {
areas = ['tutorial', 'library', 'install', 'distutils'];
}
// update input fields
$('input[@type="checkbox"]').each(function() {
this.checked = $.contains(areas, this.value);
});
$('input[@name="q"]')[0].value = query; $('input[@name="q"]')[0].value = query;
this.performSearch(query);
this.performSearch(query, areas);
} }
}, },
/** /**
* perform a search for something * perform a search for something
*/ */
performSearch : function(query, areas) { performSearch : function(query) {
// create the required interface elements // create the required interface elements
var out = $('#search-results'); var out = $('#search-results');
var title = $('<h2>Searching</h2>').appendTo(out); var title = $('<h2>Searching</h2>').appendTo(out);
@ -301,14 +289,12 @@ var Search = {
console.debug('SEARCH: searching for:'); console.debug('SEARCH: searching for:');
console.info('required: ', searchwords); console.info('required: ', searchwords);
console.info('excluded: ', excluded); console.info('excluded: ', excluded);
console.info('areas: ', areas);
// fetch searchindex and perform search // fetch searchindex and perform search
$.getJSON('searchindex.json', function(data) { $.getJSON('searchindex.json', function(data) {
// prepare search // prepare search
var filenames = data[0]; var filenames = data[0];
var areaMap = data[1];
var titles = data[2] var titles = data[2]
var words = data[3]; var words = data[3];
var fileMap = {}; var fileMap = {};
@ -342,38 +328,25 @@ var Search = {
if (fileMap[file].length != searchwords.length) { if (fileMap[file].length != searchwords.length) {
continue; continue;
} }
var valid = false;
// check if the file is in one of the searched
// areas.
for (var i = 0; i < areas.length; i++) {
if ($.contains(areaMap[areas[i]] || [], file)) {
valid = true;
break;
}
};
// ensure that none of the excluded words is in the // ensure that none of the excluded words is in the
// search result. // search result.
if (valid) { for (var i = 0; i < excluded.length; i++) {
for (var i = 0; i < excluded.length; i++) { if ($.contains(words[excluded[i]] || [], file)) {
if ($.contains(words[excluded[i]] || [], file)) { valid = false;
valid = false; break;
break;
}
} }
}
// if we have still a valid result we can add it // if we have still a valid result we can add it
// to the result list // to the result list
if (valid) { if (valid) {
results.push([filenames[file], titles[file]]); results.push([filenames[file], titles[file]]);
}
} }
} }
// delete unused variables in order to not waste // delete unused variables in order to not waste
// memory until list is retrieved completely // memory until list is retrieved completely
delete filenames, areaMap, titles, words, data; delete filenames, titles, words, data;
// now sort the results by title // now sort the results by title
results.sort(function(a, b) { results.sort(function(a, b) {

View File

@ -1,5 +1,5 @@
/** /**
* Python Doc Design -- Sticky Sidebar Overrides * Sphinx Doc Design -- Sticky Sidebar Overrides
*/ */
div.sidebar { div.sidebar {

View File

@ -1,5 +1,5 @@
/** /**
* Python Doc Design * Sphinx Doc Design -- traditional python.org style
*/ */
body { body {

View File

@ -2,7 +2,7 @@
"http://www.w3.org/TR/html4/frameset.dtd"> "http://www.w3.org/TR/html4/frameset.dtd">
<html> <html>
<head> <head>
<title>Changes in Version {{ version }} &mdash; Python Documentation</title> <title>Changes in Version {{ version }} &mdash; {{ project }} Documentation</title>
</head> </head>
<frameset cols="45%,*"> <frameset cols="45%,*">
<frame name="main" src="changes.html"> <frame name="main" src="changes.html">

View File

@ -2,7 +2,7 @@
"http://www.w3.org/TR/html4/loose.dtd"> "http://www.w3.org/TR/html4/loose.dtd">
<html> <html>
<head> <head>
<title>{{ filename }} &mdash; Python Documentation</title> <title>{{ filename }} &mdash; {{ project }} Documentation</title>
<style type="text/css"> <style type="text/css">
.hl { background-color: yellow } .hl { background-color: yellow }
</style> </style>

View File

@ -9,7 +9,7 @@
<head> <head>
<link rel="stylesheet" href="default.css"> <link rel="stylesheet" href="default.css">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Changes in Version {{ version }} &mdash; Python Documentation</title> <title>Changes in Version {{ version }} &mdash; {{ project }} Documentation</title>
</head> </head>
<body> <body>
<div class="document"> <div class="document">

View File

@ -1,53 +0,0 @@
{% extends "layout.html" %}
{% set title = 'Download' %}
{% block body %}
<h1>Download Python {{ release }} Documentation
{%- if last_updated %} (last updated on {{ last_updated }}){% endif %}</h1>
<p>To download an archive containing all the documents for this version of
Python in one of various formats, follow one of links in this table. The numbers
in the table are the size of the download files in Kilobytes.</p>
{# XXX download links, relative to download_base_url #}
<p>These archives contain all the content in the documentation section.</p>
<h2>Unpacking</h2>
<p>Unix users should download the .tar.bz2 archives; these are bzipped tar
archives and can be handled in the usual way using tar and the bzip2
program. The <a href="http://www.info-zip.org">InfoZIP</a> unzip program can be
used to handle the ZIP archives if desired. The .tar.bz2 archives provide the
best compression and fastest download times.</p>
<p>Windows users can use the ZIP archives since those are customary on that
platform. These are created on Unix using the InfoZIP zip program. They may be
unpacked using the free WiZ tool (from the InfoZIP developers) or any other
tool for handling ZIP archives; any of them should work.</p>
<p>Note that the .tar.bz2 files are smaller than the other archives; Windows
users may want to install the bzip2 tools on their systems as well. Windows
binaries for a command-line tool are available at <a
href="http://www.bzip.org">The bzip2 and libbzip2 official home page</a>, but
most other archiving utilities support the tar and bzip2 formats as well.</p>
<h2>Problems</h2>
<p><strong>Printing PDFs using Adobe Acrobat Reader 5.0:</strong> Adobe has
reportedly admitted that there is a bug in Acrobat Reader 5.0 which causes it
not to print at least some PDF files generated by pdfTeX. This software is used
to produce the PDF version of the Python documentation, and our documents
definately trigger this bug in Acrobat Reader. To print the PDF files, use
Acrobat Reader 4.x, ghostscript, or xpdf.</p>
<p>Reportedly, Acrobat Reader 6.0 can print these documents without this
problem, but we've not yet had an opportunity to confirm the report. Sadly,
version 6.0 is not yet available on Unix platforms.</p>
<p>If you have comments or suggestions for the Python documentation, please send
email to <a href="docs@python.org">docs@python.org</a>.</p>
{% endblock %}

View File

@ -4,67 +4,27 @@
(pathto('@rss/recent'), 'application/rss+xml', 'Recent Comments') (pathto('@rss/recent'), 'application/rss+xml', 'Recent Comments')
] %} ] %}
{% block body %} {% block body %}
<h1>Python Documentation</h1> <h1>{{ project }} Documentation</h1>
<p> <p>
Welcome! This is the documentation for Python Welcome! This is the documentation for {{ project }}
{{ release }}{% if last_updated %}, last updated {{ last_updated }}{% endif %}. {{ release }}{% if last_updated %}, last updated {{ last_updated }}{% endif %}.
</p> </p>
{% if indextemplate %}
<p><strong>Parts of the documentation:</strong></p> {{ rendertemplate(indextemplate) }}
<table class="contentstable" align="center"><tr> {% else %}
<td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("whatsnew/" + version + ".rst") }}">What's new in Python {{ version }}?</a><br>
<span class="linkdescr">changes since previous major release</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("tutorial/index.rst") }}">Tutorial</a><br>
<span class="linkdescr">start here</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("using/index.rst") }}">Using Python</a><br>
<span class="linkdescr">how to use Python on different platforms</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("reference/index.rst") }}">Language Reference</a><br>
<span class="linkdescr">describes syntax and language elements</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("library/index.rst") }}">Library Reference</a><br>
<span class="linkdescr">keep this under your pillow</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("howto/index.rst") }}">Python HOWTOs</a><br>
<span class="linkdescr">in-depth documents on specific topics</span></p>
</td><td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("extending/index.rst") }}">Extending and Embedding</a><br>
<span class="linkdescr">tutorial for C/C++ programmers</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("c-api/index.rst") }}">Python/C API</a><br>
<span class="linkdescr">reference for C/C++ programmers</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("install/index.rst") }}">Installing Python Modules</a><br>
<span class="linkdescr">information for installers &amp; sys-admins</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("distutils/index.rst") }}">Distributing Python Modules</a><br>
<span class="linkdescr">sharing modules with others</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("documenting/index.rst") }}">Documenting Python</a><br>
<span class="linkdescr">guide for documentation authors</span></p>
</td></tr>
</table>
<p><strong>Indices and tables:</strong></p> <p><strong>Indices and tables:</strong></p>
<table class="contentstable" align="center"><tr> <table class="contentstable" align="center"><tr>
<td width="50%"> <td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("modindex.rst") }}">Global Module Index</a><br> <p class="biglink"><a class="biglink" href="{{ pathto("contents") }}">Complete Table of Contents</a><br>
<span class="linkdescr">quick access to all modules</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("genindex.rst") }}">General Index</a><br>
<span class="linkdescr">all functions, classes, terms</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("glossary.rst") }}">Glossary</a><br>
<span class="linkdescr">the most important terms explained</span></p>
</td><td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("search.rst") }}">Search page</a><br>
<span class="linkdescr">search this documentation</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("contents.rst") }}">Complete Table of Contents</a><br>
<span class="linkdescr">lists all sections and subsections</span></p> <span class="linkdescr">lists all sections and subsections</span></p>
</td></tr> <p class="biglink"><a class="biglink" href="{{ pathto("search") }}">Search page</a><br>
</table> <span class="linkdescr">search this documentation</span></p>
<p><strong>Meta information:</strong></p>
<table class="contentstable" align="center"><tr>
<td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("bugs.rst") }}">Reporting bugs</a></p>
<p class="biglink"><a class="biglink" href="{{ pathto("about.rst") }}">About the documentation</a></p>
</td><td width="50%"> </td><td width="50%">
<p class="biglink"><a class="biglink" href="{{ pathto("license.rst") }}">History and License of Python</a></p> <p class="biglink"><a class="biglink" href="{{ pathto("modindex") }}">Global Module Index</a><br>
<p class="biglink"><a class="biglink" href="{{ pathto("copyright.rst") }}">Copyright</a></p> <span class="linkdescr">quick access to all modules</span></p>
<p class="biglink"><a class="biglink" href="{{ pathto("genindex") }}">General Index</a><br>
<span class="linkdescr">all functions, classes, terms</span></p>
</td></tr> </td></tr>
</table> </table>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1,4 +1,4 @@
{% if builder != 'htmlhelp' %}{% set titlesuffix = " &mdash; Python Documentation" %}{% endif -%} {% if builder != 'htmlhelp' %}{% set titlesuffix = " &mdash; " + project + " Documentation" %}{% endif -%}
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd"> "http://www.w3.org/TR/html4/loose.dtd">
<html> <html>
@ -6,7 +6,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>{{ title|striptags }}{{ titlesuffix }}</title> <title>{{ title|striptags }}{{ titlesuffix }}</title>
{%- if builder == 'web' %} {%- if builder == 'web' %}
<link rel="stylesheet" href="{{ pathto('index.rst') }}?do=stylesheet{% <link rel="stylesheet" href="{{ pathto('index') }}?do=stylesheet{%
if in_admin_panel %}&admin=yes{% endif %}" type="text/css"> if in_admin_panel %}&admin=yes{% endif %}" type="text/css">
{%- for link, type, title in page_links %} {%- for link, type, title in page_links %}
<link rel="alternate" type="{{ type|e(true) }}" title="{{ title|e(true) }}" href="{{ link|e(true) }}"> <link rel="alternate" type="{{ type|e(true) }}" title="{{ title|e(true) }}" href="{{ link|e(true) }}">
@ -26,12 +26,16 @@
<script type="text/javascript" src="{{ pathto('style/interface.js', 1) }}"></script> <script type="text/javascript" src="{{ pathto('style/interface.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('style/doctools.js', 1) }}"></script> <script type="text/javascript" src="{{ pathto('style/doctools.js', 1) }}"></script>
{%- endif %} {%- endif %}
<link rel="author" title="About these documents" href="{{ pathto('about.rst') }}"> {%- if hasdoc('about') %}
<link rel="contents" title="Global table of contents" href="{{ pathto('contents.rst') }}"> <link rel="author" title="About these documents" href="{{ pathto('about') }}">
<link rel="index" title="Global index" href="{{ pathto('genindex.rst') }}"> {%- endif %}
<link rel="search" title="Search" href="{{ pathto('search.rst') }}"> <link rel="contents" title="Global table of contents" href="{{ pathto('contents') }}">
<link rel="copyright" title="Copyright" href="{{ pathto('copyright.rst') }}"> <link rel="index" title="Global index" href="{{ pathto('genindex') }}">
<link rel="top" title="Python Documentation" href="{{ pathto('index.rst') }}"> <link rel="search" title="Search" href="{{ pathto('search') }}">
{%- if hasdoc('copyright') %}
<link rel="copyright" title="Copyright" href="{{ pathto('copyright') }}">
{%- endif %}
<link rel="top" title="{{ project }} Documentation" href="{{ pathto('index') }}">
{%- if parents %} {%- if parents %}
<link rel="up" title="{{ parents[-1].title|striptags }}" href="{{ parents[-1].link|e }}"> <link rel="up" title="{{ parents[-1].title|striptags }}" href="{{ parents[-1].link|e }}">
{%- endif %} {%- endif %}
@ -48,8 +52,8 @@
<div class="related"> <div class="related">
<h3>Navigation</h3> <h3>Navigation</h3>
<ul> <ul>
<li class="right" style="margin-right: 10px"><a href="{{ pathto('genindex.rst') }}" title="General Index" accesskey="I">index</a></li> <li class="right" style="margin-right: 10px"><a href="{{ pathto('genindex') }}" title="General Index" accesskey="I">index</a></li>
<li class="right"><a href="{{ pathto('modindex.rst') }}" title="Global Module Index" accesskey="M">modules</a> |</li> <li class="right"><a href="{{ pathto('modindex') }}" title="Global Module Index" accesskey="M">modules</a> |</li>
{%- if next %} {%- if next %}
<li class="right"><a href="{{ next.link|e }}" title="{{ next.title|striptags }}" accesskey="N">next</a> |</li> <li class="right"><a href="{{ next.link|e }}" title="{{ next.title|striptags }}" accesskey="N">next</a> |</li>
{%- endif %} {%- endif %}
@ -57,10 +61,10 @@
<li class="right"><a href="{{ prev.link|e }}" title="{{ prev.title|striptags }}" accesskey="P">previous</a> |</li> <li class="right"><a href="{{ prev.link|e }}" title="{{ prev.title|striptags }}" accesskey="P">previous</a> |</li>
{%- endif %} {%- endif %}
{%- if builder == 'web' %} {%- if builder == 'web' %}
<li class="right"><a href="{{ pathto('settings.rst') }}" <li class="right"><a href="{{ pathto('settings') }}"
title="Customize your viewing settings" accesskey="S">settings</a> |</li> title="Customize your viewing settings" accesskey="S">settings</a> |</li>
{%- endif %} {%- endif %}
<li><a href="{{ pathto('index.rst') }}">Python v{{ release }} Documentation</a> &raquo;</li> <li><a href="{{ pathto('index') }}">{{ project }} v{{ release }} Documentation</a> &raquo;</li>
{%- for parent in parents %} {%- for parent in parents %}
<li><a href="{{ parent.link|e }}" accesskey="U">{{ parent.title }}</a> &raquo;</li> <li><a href="{{ parent.link|e }}" accesskey="U">{{ parent.title }}</a> &raquo;</li>
{%- endfor %} {%- endfor %}
@ -69,26 +73,72 @@
{% endfilter %} {% endfilter %}
<div class="document"> <div class="document">
<div class="documentwrapper"> <div class="documentwrapper">
{%- if builder != 'htmlhelp' %} {%- if builder != 'htmlhelp' %}
<div class="bodywrapper"> <div class="bodywrapper">
{%- endif %} {%- endif %}
<div class="body"> <div class="body">
{% block body %}{% endblock %} {% block body %}{% endblock %}
</div> </div>
{%- if builder != 'htmlhelp' %} {%- if builder != 'htmlhelp' %}
</div> </div>
{%- endif %} {%- endif %}
</div> </div>
{%- if builder != 'htmlhelp' %} {%- if builder != 'htmlhelp' %}
{%- include "sidebar.html" %} <div class="sidebar">
<div class="sidebarwrapper">
{% if display_toc %}
<h3>Table Of Contents</h3>
{{ toc }}
{% endif %}
{%- if prev %}
<h4>Previous topic</h4>
<p class="topless"><a href="{{ prev.link|e }}" title="previous chapter">{{ prev.title }}</a></p>
{%- endif %}
{%- if next %}
<h4>Next topic</h4>
<p class="topless"><a href="{{ next.link|e }}" title="next chapter">{{ next.title }}</a></p>
{%- endif %}
{% if sourcename %}
<h3>This Page</h3>
<ul class="this-page-menu">
{% if builder == 'web' %}
<li><a href="#comments">Comments ({{ comments|length }} so far)</a></li>
<li><a href="{{ pathto('@edit/' + sourcename)|e }}">Suggest Change</a></li>
<li><a href="{{ pathto('@source/' + sourcename)|e }}">Show Source</a></li>
{% elif builder == 'html' %}
<li><a href="{{ pathto(sourcename, true)|e }}">Show Source</a></li>
{% endif %}
</ul>
{% endif %}
{% if customsidebar %}
{{ rendertemplate(customsidebar) }}
{% endif %}
{% if current_page_name != "search" %}
<h3>{{ builder == 'web' and 'Keyword' or 'Quick' }} search</h3>
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" size="18"> <input type="submit" value="Go">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
{% if builder == 'web' %}
<p style="font-size: 90%">Enter a module, class or function name.</p>
{% endif %}
{% endif %}
</div>
</div>
{%- endif %} {%- endif %}
<div class="clearer"></div> <div class="clearer"></div>
</div> </div>
{{ relbar }} {{ relbar }}
<div class="footer"> <div class="footer">
&copy; <a href="{{ pathto('copyright.rst') }}">Copyright</a> {%- if hasdoc('copyright') %}
1990-2007, Python Software Foundation. &copy; <a href="{{ pathto('copyright') }}">Copyright</a> {{ copyright }}.
{% if last_updated %}Last updated on {{ last_updated }}.{% endif %} {%- else %}
&copy; Copyright {{ copyright }}.
{%- endif %}
{%- if last_updated %}
Last updated on {{ last_updated }}.
{%- endif %}
</div> </div>
</body> </body>
</html> </html>

View File

@ -6,39 +6,14 @@
{% block body %} {% block body %}
<h1 id="search-documentation">Search Documentation</h1> <h1 id="search-documentation">Search Documentation</h1>
<p> <p>
From here you can search the Python documentation. Enter your search From here you can search the {{ project }} documentation. Enter your search
words into the box below and click "search". Note that the search words into the box below and click "search". Note that the search
function will automatically search for all of the words. Pages function will automatically search for all of the words. Pages
containing less words won't appear in the result list. containing less words won't appear in the result list.
</p> </p>
<p>
In order to speed up the results you can limit your search by
excluding some of the sections listed below.
</p>
<form action="" method="get"> <form action="" method="get">
<input type="text" name="q" value=""> <input type="text" name="q" value="">
<input type="submit" value="search"> <input type="submit" value="search">
<p>
Sections:
</p>
<ul class="fakelist">
{% for id, name, checked in [
('tutorial', 'Python Tutorial', true),
('library', 'Library Reference', true),
('using', 'Using Python', true),
('reference', 'Language Reference', false),
('extending', 'Extending and Embedding', false),
('c-api', 'Python/C API', false),
('install', 'Installing Python Modules', true),
('distutils', 'Distributing Python Modules', true),
('documenting', 'Documenting Python', false),
('whatsnew', 'What\'s new in Python?', false),
] -%}
<li><input type="checkbox" name="area" id="area-{{ id }}" value="{{ id
}}"{% if checked %} checked{% endif %}>
<label for="area-{{ id }}">{{ name }}</label></li>
{% endfor %}
</ul>
</form> </form>
{% if search_performed %} {% if search_performed %}
<h2>Search Results</h2> <h2>Search Results</h2>

View File

@ -1,6 +0,0 @@
{% extends "layout.html" %}
{% set title = 'Page Source' %}
{% block body %}
<h1 id="page-source">Page Source</h1>
{{ highlighted_code }}
{% endblock %}

View File

@ -1,62 +0,0 @@
{# this file is included by layout.html #}
<div class="sidebar">
<div class="sidebarwrapper">
{% if display_toc %}
<h3>Table Of Contents</h3>
{{ toc }}
{% endif %}
{%- if prev %}
<h4>Previous topic</h4>
<p class="topless"><a href="{{ prev.link|e }}" title="previous chapter">{{ prev.title }}</a></p>
{%- endif %}
{%- if next %}
<h4>Next topic</h4>
<p class="topless"><a href="{{ next.link|e }}" title="next chapter">{{ next.title }}</a></p>
{%- endif %}
{% if sourcename %}
<h3>This Page</h3>
<ul class="this-page-menu">
{% if builder == 'web' %}
<li><a href="#comments">Comments ({{ comments|length }} so far)</a></li>
<li><a href="{{ pathto('@edit/' + sourcename)|e }}">Suggest Change</a></li>
<li><a href="{{ pathto('@source/' + sourcename)|e }}">Show Source</a></li>
{% elif builder == 'html' %}
<li><a href="{{ pathto(sourcename, true)|e }}">Show Source</a></li>
{% endif %}
{# <li><a href="http://bugs.python.org/XXX?page={{ sourcename|e }}">Report Bug</a></li> #}
</ul>
{% endif %}
{% if current_page_name == "index" %}
<h3>Download</h3>
<p><a href="{{ pathto('download.rst')|e }}">Download these documents</a></p>
<h3>Other resources</h3>
<ul>
{# XXX: many of these should probably be merged in the main docs #}
<li><a href="http://www.python.org/doc/faq/">FAQs</a></li>
<li><a href="http://www.python.org/doc/intros/">Introductions</a></li>
<li><a href="http://www.python.org/doc/essays/">Guido's Essays</a></li>
<li><a href="http://www.python.org/doc/newstyle/">New-style Classes</a></li>
<li><a href="http://www.python.org/dev/peps/">PEP Index</a></li>
<li><a href="http://wiki.python.org/moin/BeginnersGuide">Beginner's Guide</a></li>
<li><a href="http://www.python.org/topics/">Topic Guides</a></li>
<li><a href="http://wiki.python.org/moin/PythonBooks">Book List</a></li>
<li><a href="http://www.python.org/doc/av/">Audio/Visual Talks</a></li>
<li><a href="http://www.python.org/doc/other/">Other Doc Collections</a></li>
<li>&nbsp;</li>
<li><a href="http://www.python.org/doc/versions/">Previous versions</a></li>
<li>&nbsp;</li>
</ul>
{% endif %}
{% if current_page_name != "search" %}
<h3>{{ builder == 'web' and 'Keyword' or 'Quick' }} search</h3>
<form class="search" action="{{ pathto('search.rst') }}" method="get">
<input type="text" name="q" size="18"> <input type="submit" value="Go">
<input type="hidden" name="check_keywords" value="yes">
<input type="hidden" name="area" value="default">
</form>
{% if builder == 'web' %}
<p style="font-size: 90%">Enter a module, class or function name.</p>
{% endif %}
{% endif %}
</div>
</div>

View File

@ -3,7 +3,7 @@
<h1>Moderate Comments</h1> <h1>Moderate Comments</h1>
<p> <p>
From here you can delete and edit comments. If you want to be From here you can delete and edit comments. If you want to be
informed about new comments you can use the <a href="{{ pathto('index.rst') informed about new comments you can use the <a href="{{ pathto('index')
}}?feed=recent_comments">feed</a> provided. }}?feed=recent_comments">feed</a> provided.
</p> </p>
<form action="" method="post"> <form action="" method="post">
@ -71,7 +71,7 @@
<th colspan="4" class="recent_comments"> <th colspan="4" class="recent_comments">
<a href="{{ pathto('@admin/moderate_comments/recent_comments/', true) <a href="{{ pathto('@admin/moderate_comments/recent_comments/', true)
}}">Recent Comments</a> }}">Recent Comments</a>
<span class="meta">(<a href="{{ pathto('index.rst') <span class="meta">(<a href="{{ pathto('index')
}}?feed=recent_comments">feed</a>)</span> }}?feed=recent_comments">feed</a>)</span>
</th> </th>
</tr> </tr>

View File

@ -19,7 +19,7 @@
<p> <p>
HTML is not supported, relative link targets are treated as HTML is not supported, relative link targets are treated as
quicklinks and code blocks that start with "&gt;&gt;&gt;" are quicklinks and code blocks that start with "&gt;&gt;&gt;" are
highlighted as interactive python sessions. highlighted as interactive Python sessions.
</p> </p>
</div> </div>
</div> </div>

View File

@ -22,9 +22,10 @@
<h1 id="suggest-changes-for-this-page">Suggest changes for this page</h1> <h1 id="suggest-changes-for-this-page">Suggest changes for this page</h1>
{% if not rendered %} {% if not rendered %}
<p>Here you can edit the source of &#8220;{{ doctitle|striptags }}&#8221; and <p>Here you can edit the source of &#8220;{{ doctitle|striptags }}&#8221; and
submit the results as a patch to the Python documentation team. If you want submit the results as a patch to the {{ project }} documentation team.
to know more about reST, the markup language used, read {# XXX Python specific #}
<a href="{{ pathto('documenting/index.rst') }}">Documenting Python</a>.</p> If you want to know more about reST, the markup language used, read
<a href="{{ pathto('documenting/index') }}">Documenting Python</a>.</p>
{% endif %} {% endif %}
<form action="{{ submiturl }}" method="post"> <form action="{{ submiturl }}" method="post">
<div id="suggest-changes-box"> <div id="suggest-changes-box">

View File

@ -20,12 +20,12 @@
</ul> </ul>
{% endif %} {% endif %}
<p> <p>
If you want to search the entire Python documentation for the string If you want to search the entire {{ project }} documentation for the string
"{{ keyword|e }}", then <a href="{{ pathto('search.rst') }}?q={{ keyword|e "{{ keyword|e }}", then <a href="{{ pathto('search') }}?q={{ keyword|e
}}">use the search function</a>. }}">use the search function</a>.
</p> </p>
<p> <p>
For a quick overview over all documented modules, For a quick overview over all documented modules,
<a href="{{ pathto('library/index.rst') }}">click here</a>. <a href="{{ pathto('modindex') }}">click here</a>.
</p> </p>
{% endblock %} {% endblock %}

View File

@ -6,6 +6,6 @@
The page {{ req.path|e }} does not exist on this server. The page {{ req.path|e }} does not exist on this server.
</p> </p>
<p> <p>
Click here to <a href="{{ pathto('index.rst') }}">return to the index</a>. Click here to <a href="{{ pathto('index') }}">return to the index</a>.
</p> </p>
{% endblock %} {% endblock %}

View File

@ -2,13 +2,13 @@
{% set title = 'Settings' %} {% set title = 'Settings' %}
{% set current_page_name = 'settings' %} {% set current_page_name = 'settings' %}
{% block body %} {% block body %}
<h1>Python Documentation Settings</h1> <h1>{{ project }} Documentation Settings</h1>
<p> <p>
Here you can customize how you want to view the Python documentation. Here you can customize how you want to view the {{ project }} documentation.
These settings are saved using a cookie on your computer. These settings are saved using a cookie on your computer.
</p> </p>
<form action="{{ pathto('settings.rst') }}" method="post"> <form action="{{ pathto('settings') }}" method="post">
<p class="subhead">Select your stylesheet:</p> <p class="subhead">Select your stylesheet:</p>
<p> <p>
{%- for design, (foo, descr) in known_designs %} {%- for design, (foo, descr) in known_designs %}

View File

@ -5,7 +5,7 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<h1>Patch submitted</h1> <h1>Patch submitted</h1>
<p>Your patch has been submitted to the Python documentation team and will be <p>Your patch has been submitted to the {{ project }} documentation team and will be
processed shortly.</p> processed shortly.</p>
<p>You will be redirected to the <p>You will be redirected to the
<a href="{{ backlink|e }}">original documentation page</a> shortly.</p> <a href="{{ backlink|e }}">original documentation page</a> shortly.</p>

View File

@ -77,14 +77,6 @@ def get_matching_files(dirname, pattern, exclude=()):
yield canonical_path(qualified_name) yield canonical_path(qualified_name)
def get_category(filename):
"""Get the "category" part of a RST filename."""
parts = filename.split(SEP, 1)
if len(parts) < 2:
return
return parts[0]
def shorten_result(text='', keywords=[], maxlen=240, fuzz=60): def shorten_result(text='', keywords=[], maxlen=240, fuzz=60):
if not text: if not text:
text = '' text = ''

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
sphinx.util.stemmer sphinx.util.stemmer

View File

@ -9,9 +9,9 @@
:license: BSD. :license: BSD.
""" """
from .util import render_template from sphinx.web.util import render_template
from .wsgiutil import Response, RedirectResponse, NotFound from sphinx.web.wsgiutil import Response, RedirectResponse, NotFound
from .database import Comment from sphinx.web.database import Comment
class AdminPanel(object): class AdminPanel(object):

View File

@ -9,7 +9,7 @@
:copyright: 2007-2008 by Armin Ronacher. :copyright: 2007-2008 by Armin Ronacher.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import re import re
import urllib import urllib
import time import time
@ -43,14 +43,20 @@ class AntiSpam(object):
else: else:
lines = [l.strip() for l in data.splitlines() lines = [l.strip() for l in data.splitlines()
if not l.startswith('#')] if not l.startswith('#')]
with file(bad_content_file, 'w') as f: f = open(bad_content_file, 'w')
try:
f.write('\n'.join(lines)) f.write('\n'.join(lines))
finally:
f.close()
last_change = int(time.time()) last_change = int(time.time())
if lines is None: if lines is None:
try: try:
with file(bad_content_file) as f: f = open(bad_content_file)
try:
lines = [l.strip() for l in f] lines = [l.strip() for l in f]
finally:
f.close()
except: except:
lines = [] lines = []
self.rules = [re.compile(rule) for rule in lines if rule] self.rules = [re.compile(rule) for rule in lines if rule]

View File

@ -9,7 +9,6 @@
:copyright: 2007-2008 by Georg Brandl, Armin Ronacher. :copyright: 2007-2008 by Georg Brandl, Armin Ronacher.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import os import os
import re import re
@ -24,24 +23,23 @@ import cPickle as pickle
import cStringIO as StringIO import cStringIO as StringIO
from os import path from os import path
from itertools import groupby from itertools import groupby
from collections import defaultdict
from .feed import Feed from sphinx.web.feed import Feed
from .mail import Email from sphinx.web.mail import Email
from .util import render_template, get_target_uri, blackhole_dict, striptags from sphinx.web.util import render_template, get_target_uri, blackhole_dict, striptags
from .admin import AdminPanel from sphinx.web.admin import AdminPanel
from .userdb import UserDatabase from sphinx.web.userdb import UserDatabase
from .robots import robots_txt from sphinx.web.robots import robots_txt
from .oldurls import handle_html_url from sphinx.web.oldurls import handle_html_url
from .antispam import AntiSpam from sphinx.web.antispam import AntiSpam
from .database import connect, set_connection, Comment from sphinx.web.database import connect, set_connection, Comment
from .wsgiutil import Request, Response, RedirectResponse, \ from sphinx.web.wsgiutil import Request, Response, RedirectResponse, \
JSONResponse, SharedDataMiddleware, NotFound, get_base_uri JSONResponse, SharedDataMiddleware, NotFound, get_base_uri
from ..util import relative_uri from sphinx.util import relative_uri
from ..search import SearchFrontend from sphinx.search import SearchFrontend
from ..htmlwriter import HTMLWriter from sphinx.htmlwriter import HTMLWriter
from ..builder import LAST_BUILD_FILENAME, ENV_PICKLE_FILENAME from sphinx.builder import LAST_BUILD_FILENAME, ENV_PICKLE_FILENAME
from docutils.io import StringOutput from docutils.io import StringOutput
from docutils.utils import Reporter from docutils.utils import Reporter
@ -123,8 +121,11 @@ class DocumentationApplication(object):
""" """
def __init__(self, config): def __init__(self, config):
self.cache = blackhole_dict() if config['debug'] else {} if config['debug']:
self.freqmodules = defaultdict(int) self.cache = blackhole_dict()
else:
self.cache = {}
self.freqmodules = {}
self.last_most_frequent = [] self.last_most_frequent = []
self.generated_stylesheets = {} self.generated_stylesheets = {}
self.config = config self.config = config
@ -139,19 +140,31 @@ class DocumentationApplication(object):
def load_env(self, new_mtime): def load_env(self, new_mtime):
with env_lock: env_lock.acquire()
try:
if self.buildmtime == new_mtime: if self.buildmtime == new_mtime:
# happens if another thread already reloaded the env # happens if another thread already reloaded the env
return return
print "* Loading the environment..." print "* Loading the environment..."
with file(path.join(self.data_root, ENV_PICKLE_FILENAME), 'rb') as f: f = open(path.join(self.data_root, ENV_PICKLE_FILENAME), 'rb')
try:
self.env = pickle.load(f) self.env = pickle.load(f)
with file(path.join(self.data_root, 'globalcontext.pickle'), 'rb') as f: finally:
f.close()
f = open(path.join(self.data_root, 'globalcontext.pickle'), 'rb')
try:
self.globalcontext = pickle.load(f) self.globalcontext = pickle.load(f)
with file(path.join(self.data_root, 'searchindex.pickle'), 'rb') as f: finally:
f.close()
f = open(path.join(self.data_root, 'searchindex.pickle'), 'rb')
try:
self.search_frontend = SearchFrontend(pickle.load(f)) self.search_frontend = SearchFrontend(pickle.load(f))
finally:
f.close()
self.buildmtime = new_mtime self.buildmtime = new_mtime
self.cache.clear() self.cache.clear()
finally:
env_lock.release()
def search(self, req): def search(self, req):
@ -167,12 +180,15 @@ class DocumentationApplication(object):
""" """
Get the reST source of a page. Get the reST source of a page.
""" """
page_id = self.env.get_real_filename(page) page_id = self.env.get_real_filename(page)[:-4]
if page_id is None: if page_id is None:
raise NotFound() raise NotFound()
filename = path.join(self.data_root, 'sources', page_id)[:-3] + 'txt' filename = path.join(self.data_root, 'sources', page_id) + '.txt'
with file(filename) as f: f = open(filename)
try:
return page_id, f.read() return page_id, f.read()
finally:
f.close()
def show_source(self, req, page): def show_source(self, req, page):
@ -191,7 +207,7 @@ class DocumentationApplication(object):
return Response(render_template(req, 'edit.html', self.globalcontext, dict( return Response(render_template(req, 'edit.html', self.globalcontext, dict(
contents=contents, contents=contents,
pagename=page, pagename=page,
doctitle=self.globalcontext['titles'].get(page_id) or 'this page', doctitle=self.globalcontext['titles'].get(page_id+'.rst') or 'this page',
submiturl=relative_uri('/@edit/'+page+'/', '/@submit/'+page), submiturl=relative_uri('/@edit/'+page+'/', '/@submit/'+page),
))) )))
@ -209,11 +225,11 @@ class DocumentationApplication(object):
builder = MockBuilder() builder = MockBuilder()
builder.config = env2.config builder.config = env2.config
writer = HTMLWriter(builder) writer = HTMLWriter(builder)
doctree = env2.read_file(page_id, pathname, save_parsed=False) doctree = env2.read_file(page_id+'.rst', pathname, save_parsed=False)
doctree = env2.get_and_resolve_doctree(page_id, builder, doctree) doctree = env2.get_and_resolve_doctree(page_id+'.rst', builder, doctree)
doctree.settings = OptionParser(defaults=env2.settings, doctree.settings = OptionParser(defaults=env2.settings,
components=(writer,)).get_default_values() components=(writer,)).get_default_values()
doctree.reporter = Reporter(page_id, 2, 4, stream=warning_stream) doctree.reporter = Reporter(page_id+'.rst', 2, 4, stream=warning_stream)
output = writer.write(doctree, destination) output = writer.write(doctree, destination)
writer.assemble_parts() writer.assemble_parts()
return writer.parts['fragment'] return writer.parts['fragment']
@ -302,7 +318,7 @@ class DocumentationApplication(object):
referer = '' referer = ''
else: else:
referer = referer[len(base):] referer = referer[len(base):]
referer = referer.rpartition('?')[0] or referer referer = referer.split('?')[0] or referer
if req.method == 'POST': if req.method == 'POST':
if req.form.get('cancel'): if req.form.get('cancel'):
@ -362,8 +378,11 @@ class DocumentationApplication(object):
yield '@modindex' yield '@modindex'
filename = path.join(self.data_root, 'modindex.fpickle') filename = path.join(self.data_root, 'modindex.fpickle')
with open(filename, 'rb') as f: f = open(filename, 'rb')
try:
context = pickle.load(f) context = pickle.load(f)
finally:
f.close()
if showpf: if showpf:
entries = context['modindexentries'] entries = context['modindexentries']
i = 0 i = 0
@ -386,7 +405,7 @@ class DocumentationApplication(object):
""" """
Show the "new comment" form. Show the "new comment" form.
""" """
page_id = self.env.get_real_filename(page) page_id = self.env.get_real_filename(page)[:-4]
ajax_mode = req.args.get('mode') == 'ajax' ajax_mode = req.args.get('mode') == 'ajax'
target = req.args.get('target') target = req.args.get('target')
page_comment_mode = not target page_comment_mode = not target
@ -466,7 +485,7 @@ class DocumentationApplication(object):
return return
comment_url = '@comments/%s/' % url comment_url = '@comments/%s/' % url
page_id = self.env.get_real_filename(url) page_id = self.env.get_real_filename(url)[:-4]
tx = context['body'] tx = context['body']
all_comments = Comment.get_for_page(page_id) all_comments = Comment.get_for_page(page_id)
global_comments = [] global_comments = []
@ -509,17 +528,17 @@ class DocumentationApplication(object):
Show the requested documentation page or raise an Show the requested documentation page or raise an
`NotFound` exception to display a page with close matches. `NotFound` exception to display a page with close matches.
""" """
page_id = self.env.get_real_filename(url) page_id = self.env.get_real_filename(url)[:-4]
if page_id is None: if page_id is None:
raise NotFound(show_keyword_matches=True) raise NotFound(show_keyword_matches=True)
# increment view count of all modules on that page # increment view count of all modules on that page
for modname in self.env.filemodules.get(page_id, ()): for modname in self.env.filemodules.get(page_id+'.rst', ()):
self.freqmodules[modname] += 1 self.freqmodules[modname] = self.freqmodules.get(modname, 0) + 1
# comments enabled? # comments enabled?
comments = self.env.metadata[page_id].get('nocomments', False) comments = self.env.metadata[page_id+'.rst'].get('nocomments', False)
# how does the user want to view comments? # how does the user want to view comments?
commentmode = req.session.get('comments', 'inline') if comments else '' commentmode = comments and req.session.get('comments', 'inline') or ''
# show "old URL" message? -> no caching possible # show "old URL" message? -> no caching possible
oldurl = req.args.get('oldurl') oldurl = req.args.get('oldurl')
@ -530,9 +549,12 @@ class DocumentationApplication(object):
yield page_id + '|' + commentmode yield page_id + '|' + commentmode
# cache miss; load the page and render it # cache miss; load the page and render it
filename = path.join(self.data_root, page_id[:-3] + 'fpickle') filename = path.join(self.data_root, page_id + '.fpickle')
with open(filename, 'rb') as f: f = open(filename, 'rb')
try:
context = pickle.load(f) context = pickle.load(f)
finally:
f.close()
# add comments to paqe text # add comments to paqe text
if commentmode != 'none': if commentmode != 'none':
@ -546,8 +568,11 @@ class DocumentationApplication(object):
def get_special_page(self, req, name): def get_special_page(self, req, name):
yield '@'+name yield '@'+name
filename = path.join(self.data_root, name + '.fpickle') filename = path.join(self.data_root, name + '.fpickle')
with open(filename, 'rb') as f: f = open(filename, 'rb')
try:
context = pickle.load(f) context = pickle.load(f)
finally:
f.close()
yield render_template(req, name+'.html', yield render_template(req, name+'.html',
self.globalcontext, context) self.globalcontext, context)
@ -559,8 +584,8 @@ class DocumentationApplication(object):
feed.add_item(comment.title, comment.author, comment.url, feed.add_item(comment.title, comment.author, comment.url,
comment.parsed_comment_body, comment.pub_date) comment.parsed_comment_body, comment.pub_date)
else: else:
page_id = self.env.get_real_filename(url) page_id = self.env.get_real_filename(url)[:-4]
doctitle = striptags(self.globalcontext['titles'].get(page_id, url)) doctitle = striptags(self.globalcontext['titles'].get(page_id+'.rst', url))
feed = Feed(req, 'Comments for "%s"' % doctitle, feed = Feed(req, 'Comments for "%s"' % doctitle,
'List of comments for the topic "%s"' % doctitle, url) 'List of comments for the topic "%s"' % doctitle, url)
for comment in Comment.get_for_page(page_id): for comment in Comment.get_for_page(page_id):
@ -632,7 +657,7 @@ class DocumentationApplication(object):
'close_matches': close_matches, 'close_matches': close_matches,
'good_matches_count': good_matches, 'good_matches_count': good_matches,
'keyword': term 'keyword': term
}, self.globalcontext), status=404 if is_error_page else 404) }, self.globalcontext), status=404)
def get_user_stylesheet(self, req): def get_user_stylesheet(self, req):
@ -650,15 +675,21 @@ class DocumentationApplication(object):
else: else:
stylesheet = [] stylesheet = []
for filename in known_designs[style][0]: for filename in known_designs[style][0]:
with file(path.join(self.data_root, 'style', filename)) as f: f = open(path.join(self.data_root, 'style', filename))
try:
stylesheet.append(f.read()) stylesheet.append(f.read())
finally:
f.close()
stylesheet = '\n'.join(stylesheet) stylesheet = '\n'.join(stylesheet)
if not self.config.get('debug'): if not self.config.get('debug'):
self.generated_stylesheets[style] = stylesheet self.generated_stylesheets[style] = stylesheet
if req.args.get('admin') == 'yes': if req.args.get('admin') == 'yes':
with file(path.join(self.data_root, 'style', 'admin.css')) as f: f = open(path.join(self.data_root, 'style', 'admin.css'))
try:
stylesheet += '\n' + f.read() stylesheet += '\n' + f.read()
finally:
f.close()
# XXX: add timestamp based http caching # XXX: add timestamp based http caching
return Response(stylesheet, mimetype='text/css') return Response(stylesheet, mimetype='text/css')

View File

@ -17,7 +17,7 @@ import sqlite3
from datetime import datetime from datetime import datetime
from threading import local from threading import local
from .markup import markup from sphinx.web.markup import markup
_thread_local = local() _thread_local = local()
@ -88,14 +88,14 @@ class Comment(object):
@property @property
def url(self): def url(self):
return '%s#comment-%s' % ( return '%s#comment-%s' % (
self.associated_page[:-4], self.associated_page,
self.comment_id self.comment_id
) )
@property @property
def parsed_comment_body(self): def parsed_comment_body(self):
from .util import get_target_uri from sphinx.web.util import get_target_uri
from ..util import relative_uri from sphinx.util import relative_uri
uri = get_target_uri(self.associated_page) uri = get_target_uri(self.associated_page)
def make_rel_link(keyword): def make_rel_link(keyword):
return relative_uri(uri, 'q/%s/' % keyword) return relative_uri(uri, 'q/%s/' % keyword)
@ -153,7 +153,7 @@ class Comment(object):
cur = get_cursor() cur = get_cursor()
cur.execute('''select * from comments where associated_page = ? cur.execute('''select * from comments where associated_page = ?
order by associated_name, comment_id %s''' % order by associated_name, comment_id %s''' %
('desc' if reverse else 'asc'), (reverse and 'desc' or 'asc'),
(associated_page,)) (associated_page,))
try: try:
return [Comment._make_comment(row) for row in cur] return [Comment._make_comment(row) for row in cur]

View File

@ -42,7 +42,7 @@ import cgi
import re import re
from urlparse import urlparse from urlparse import urlparse
from ..highlighting import highlight_block from sphinx.highlighting import highlight_block
inline_formatting = { inline_formatting = {
@ -212,7 +212,7 @@ class MarkupParser(object):
elif protocol == 'javascript': elif protocol == 'javascript':
href = href[11:] href = href[11:]
paragraph.append('<a href="%s"%s>%s</a>' % (cgi.escape(href), paragraph.append('<a href="%s"%s>%s</a>' % (cgi.escape(href),
' rel="nofollow"' if nofollow else '', nofollow and ' rel="nofollow"' or '',
cgi.escape(caption))) cgi.escape(caption)))
elif token == 'code_block': elif token == 'code_block':
result.append(highlight_block(data, 'python')) result.append(highlight_block(data, 'python'))

View File

@ -11,7 +11,7 @@
import re import re
from .wsgiutil import RedirectResponse, NotFound from sphinx.web.wsgiutil import RedirectResponse, NotFound
_module_re = re.compile(r'module-(.*)\.html') _module_re = re.compile(r'module-(.*)\.html')
@ -78,7 +78,7 @@ def handle_html_url(req, url):
# tutorial # tutorial
elif url[:4] == 'tut/': elif url[:4] == 'tut/':
try: try:
node = int(url[8:].partition('.html')[0]) node = int(url[8:].split('.html')[0])
except ValueError: except ValueError:
pass pass
else: else:

View File

@ -63,7 +63,7 @@ def restart_with_reloader():
def run_with_reloader(main_func, extra_watch): def run_with_reloader(main_func, extra_watch):
""" """
Run the given function in an independent python interpreter. Run the given function in an independent Python interpreter.
""" """
if os.environ.get('RUN_MAIN') == 'true': if os.environ.get('RUN_MAIN') == 'true':
thread.start_new_thread(main_func, ()) thread.start_new_thread(main_func, ())

View File

@ -10,11 +10,10 @@
:copyright: 2007-2008 by Armin Ronacher. :copyright: 2007-2008 by Armin Ronacher.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
from os import path from os import path
from hashlib import sha1 from hashlib import sha1
from random import choice, randrange from random import choice, randrange
from collections import defaultdict
def gen_password(length=8, add_numbers=True, mix_case=True, def gen_password(length=8, add_numbers=True, mix_case=True,
@ -56,17 +55,19 @@ class UserDatabase(object):
def __init__(self, filename): def __init__(self, filename):
self.filename = filename self.filename = filename
self.users = {} self.users = {}
self.privileges = defaultdict(set) self.privileges = {}
if path.exists(filename): if path.exists(filename):
with file(filename) as f: f = open(filename)
try:
for line in f: for line in f:
line = line.strip() line = line.strip()
if line and line[0] != '#': if line and line[0] != '#':
parts = line.split(':') parts = line.split(':')
self.users[parts[0]] = parts[1] self.users[parts[0]] = parts[1]
self.privileges[parts[0]].update(x for x in self.privileges.setdefault(parts[0], set()).update(
parts[2].split(',') x for x in parts[2].split(',') if x)
if x) finally:
f.close()
def set_password(self, user, password): def set_password(self, user, password):
"""Encode the password for a user (also adds users).""" """Encode the password for a user (also adds users)."""
@ -84,7 +85,10 @@ class UserDatabase(object):
self.users[user] == sha1('%s|%s' % (user, password)).hexdigest() self.users[user] == sha1('%s|%s' % (user, password)).hexdigest()
def save(self): def save(self):
with file(self.filename, 'w') as f: f = open(self.filename, 'w')
try:
for username, password in self.users.iteritems(): for username, password in self.users.iteritems():
privileges = ','.join(self.privileges.get(username, ())) privileges = ','.join(self.privileges.get(username, ()))
f.write('%s:%s:%s\n' % (username, password, privileges)) f.write('%s:%s:%s\n' % (username, password, privileges))
finally:
f.close()

View File

@ -8,22 +8,21 @@
:copyright: 2007-2008 by Georg Brandl. :copyright: 2007-2008 by Georg Brandl.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import re import re
from os import path from os import path
from ..util import relative_uri from sphinx.util import relative_uri
from .._jinja import Environment, FileSystemLoader from sphinx._jinja import Environment, FileSystemLoader
def get_target_uri(source_filename): def get_target_uri(source_filename):
"""Get the web-URI for a given reST file name.""" """Get the web-URI for a given reST file name (without extension)."""
if source_filename == 'index.rst': if source_filename == 'index':
return '' return ''
if source_filename.endswith('/index.rst'): if source_filename.endswith('/index'):
return source_filename[:-9] # up to / return source_filename[:-5] # up to /
return source_filename[:-4] + '/' return source_filename + '/'
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Python documentation web application configuration file # Sphinx documentation web application configuration file
# #
# Where the server listens. # Where the server listens.

View File

@ -9,7 +9,6 @@
:copyright: 2007-2008 by Armin Ronacher. :copyright: 2007-2008 by Armin Ronacher.
:license: BSD. :license: BSD.
""" """
from __future__ import with_statement
import cgi import cgi
import urllib import urllib
@ -23,8 +22,8 @@ from hashlib import sha1
from datetime import datetime from datetime import datetime
from cStringIO import StringIO from cStringIO import StringIO
from .util import lazy_property from sphinx.web.util import lazy_property
from ..util.json import dump_json from sphinx.util.json import dump_json
HTTP_STATUS_CODES = { HTTP_STATUS_CODES = {
@ -371,8 +370,11 @@ class Session(dict):
self.sid = sid self.sid = sid
if sid is not None: if sid is not None:
if path.exists(self.filename): if path.exists(self.filename):
with file(self.filename, 'rb') as f: f = open(self.filename, 'rb')
try:
self.update(pickle.load(f)) self.update(pickle.load(f))
finally:
f.close()
self._orig = dict(self) self._orig = dict(self)
@property @property
@ -387,8 +389,11 @@ class Session(dict):
def save(self): def save(self):
if self.sid is None: if self.sid is None:
self.sid = sha1('%s|%s' % (time(), random())).hexdigest() self.sid = sha1('%s|%s' % (time(), random())).hexdigest()
with file(self.filename, 'wb') as f: f = open(self.filename, 'wb')
try:
pickle.dump(dict(self), f, pickle.HIGHEST_PROTOCOL) pickle.dump(dict(self), f, pickle.HIGHEST_PROTOCOL)
finally:
f.close()
self._orig = dict(self) self._orig = dict(self)
@ -669,8 +674,11 @@ class SharedDataMiddleware(object):
start_response('200 OK', [('Content-Type', mime_type), start_response('200 OK', [('Content-Type', mime_type),
('Cache-Control', 'public'), ('Cache-Control', 'public'),
('Expires', expiry)]) ('Expires', expiry)])
with open(filename, 'rb') as f: f = open(filename, 'rb')
try:
return [f.read()] return [f.read()]
finally:
f.close()
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
p = environ.get('PATH_INFO', '') p = environ.get('PATH_INFO', '')