From b09e628b0f33614e81521ad60e94472a0db09a4d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 16 Jan 2008 20:27:25 +0000 Subject: [PATCH] A few refactorings in Sphinx. --- sphinx/__init__.py | 35 +- sphinx/_jinja.py | 34 +- sphinx/addnodes.py | 2 + sphinx/builder.py | 367 ++++++++++-------- sphinx/directives.py | 14 +- sphinx/environment.py | 91 +++-- sphinx/highlighting.py | 12 +- sphinx/htmlhelp.py | 32 +- sphinx/htmlwriter.py | 7 +- sphinx/latexwriter.py | 22 +- sphinx/patchlevel.py | 8 +- sphinx/refcounting.py | 7 +- sphinx/roles.py | 7 +- sphinx/search.py | 35 +- sphinx/style/default.css | 2 +- sphinx/style/rightsidebar.css | 2 +- sphinx/style/searchtools.js | 51 +-- sphinx/style/stickysidebar.css | 2 +- sphinx/style/traditional.css | 2 +- .../frameset.html} | 2 +- sphinx/templates/{ => changes}/rstsource.html | 2 +- .../{ => changes}/versionchanges.html | 2 +- sphinx/templates/download.html | 53 --- sphinx/templates/index.html | 66 +--- sphinx/templates/layout.html | 90 ++++- sphinx/templates/search.html | 27 +- sphinx/templates/show_source.html | 6 - sphinx/templates/sidebar.html | 62 --- sphinx/templates/{ => web}/_commentform.html | 0 .../{ => web}/admin/change_password.html | 0 sphinx/templates/{ => web}/admin/index.html | 0 sphinx/templates/{ => web}/admin/layout.html | 0 sphinx/templates/{ => web}/admin/login.html | 0 .../{ => web}/admin/manage_users.html | 0 .../{ => web}/admin/moderate_comments.html | 4 +- sphinx/templates/{ => web}/commentform.html | 2 +- sphinx/templates/{ => web}/comments.html | 0 sphinx/templates/{ => web}/edit.html | 7 +- .../templates/{ => web}/inlinecomments.html | 0 .../{ => web}/keyword_not_found.html | 6 +- sphinx/templates/{ => web}/not_found.html | 2 +- sphinx/templates/{ => web}/settings.html | 6 +- sphinx/templates/{ => web}/submitted.html | 2 +- sphinx/util/__init__.py | 8 - sphinx/util/stemmer.py | 1 - sphinx/web/admin.py | 6 +- sphinx/web/antispam.py | 12 +- sphinx/web/application.py | 123 +++--- sphinx/web/database.py | 10 +- sphinx/web/markup.py | 4 +- sphinx/web/oldurls.py | 4 +- sphinx/web/serve.py | 2 +- sphinx/web/userdb.py | 20 +- sphinx/web/util.py | 15 +- sphinx/web/webconf.py | 2 +- sphinx/web/wsgiutil.py | 20 +- 56 files changed, 631 insertions(+), 667 deletions(-) rename sphinx/templates/{versionchanges_frameset.html => changes/frameset.html} (72%) rename sphinx/templates/{ => changes}/rstsource.html (79%) rename sphinx/templates/{ => changes}/versionchanges.html (91%) delete mode 100644 sphinx/templates/download.html delete mode 100644 sphinx/templates/show_source.html delete mode 100644 sphinx/templates/sidebar.html rename sphinx/templates/{ => web}/_commentform.html (100%) rename sphinx/templates/{ => web}/admin/change_password.html (100%) rename sphinx/templates/{ => web}/admin/index.html (100%) rename sphinx/templates/{ => web}/admin/layout.html (100%) rename sphinx/templates/{ => web}/admin/login.html (100%) rename sphinx/templates/{ => web}/admin/manage_users.html (100%) rename sphinx/templates/{ => web}/admin/moderate_comments.html (98%) rename sphinx/templates/{ => web}/commentform.html (93%) rename sphinx/templates/{ => web}/comments.html (100%) rename sphinx/templates/{ => web}/edit.html (87%) rename sphinx/templates/{ => web}/inlinecomments.html (100%) rename sphinx/templates/{ => web}/keyword_not_found.html (81%) rename sphinx/templates/{ => web}/not_found.html (74%) rename sphinx/templates/{ => web}/settings.html (87%) rename sphinx/templates/{ => web}/submitted.html (76%) diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 593bd20dc..82cbb55eb 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -3,7 +3,7 @@ Sphinx ~~~~~~ - The Python documentation toolchain. + The Sphinx documentation toolchain. :copyright: 2007-2008 by Georg Brandl. :license: BSD. @@ -14,8 +14,8 @@ import getopt from os import path from cStringIO import StringIO -from .builder import builders -from .util.console import nocolor +from sphinx.builder import builders +from sphinx.util.console import nocolor __version__ = '$Revision: 5369 $' @@ -31,7 +31,6 @@ options: -b -- builder to use (one of %s) -E -- don't use a saved environment, always read all files -d -- path for the cached environment and doctree files (default outdir/.doctrees) - -O -- give option to to the builder (-O help for list) -D -- override a setting in sourcedir/conf.py -N -- do not do colored output -q -- no output on stdout, just warnings on stderr @@ -44,7 +43,7 @@ modi: def main(argv): 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]) if not path.isdir(srcdirname): print >>sys.stderr, 'Error: Cannot find source directory.' @@ -70,9 +69,8 @@ def main(argv): return 1 builder = all_files = None - opt_help = freshenv = use_pdb = False + freshenv = use_pdb = False status = sys.stdout - options = {} confoverrides = {} doctreedir = path.join(outdirname, '.doctrees') for opt, val in opts: @@ -88,18 +86,6 @@ def main(argv): all_files = True elif opt == '-d': 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': key, val = val.split('=') try: @@ -125,14 +111,8 @@ def main(argv): 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: - builderobj = builderobj(srcdirname, outdirname, doctreedir, options, + builderobj = builderobj(srcdirname, outdirname, doctreedir, status_stream=status, warning_stream=sys.stderr, confoverrides=confoverrides, @@ -146,7 +126,8 @@ def main(argv): except: if not use_pdb: raise - import pdb + import pdb, traceback + traceback.print_exc() pdb.post_mortem(sys.exc_info()[2]) diff --git a/sphinx/_jinja.py b/sphinx/_jinja.py index c89311314..92975b85a 100644 --- a/sphinx/_jinja.py +++ b/sphinx/_jinja.py @@ -8,11 +8,41 @@ :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ -from __future__ import absolute_import import sys +import codecs from os import path 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() diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index e5f88a601..f0c378523 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -3,6 +3,8 @@ sphinx.addnodes ~~~~~~~~~~~~~~~ + Additional docutils nodes. + :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ diff --git a/sphinx/builder.py b/sphinx/builder.py index eed5cd62f..357875bc3 100644 --- a/sphinx/builder.py +++ b/sphinx/builder.py @@ -8,7 +8,6 @@ :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ -from __future__ import with_statement import os import sys @@ -21,26 +20,27 @@ import cStringIO as StringIO from os import path from cgi import escape +from docutils import nodes from docutils.io import StringOutput, FileOutput, DocTreeInput from docutils.core import publish_parts from docutils.utils import new_document from docutils.readers import doctree from docutils.frontend import OptionParser -from .util import (get_matching_files, attrdict, status_iterator, ensuredir, - get_category, relative_uri, os_path, SEP) -from .htmlhelp import build_hhx -from .patchlevel import get_version_info, get_sys_version_info -from .htmlwriter import HTMLWriter -from .latexwriter import LaTeXWriter -from .environment import BuildEnvironment, NoUri -from .highlighting import pygments, highlight_block, get_stylesheet -from .util.console import bold, purple, green +from sphinx import addnodes +from sphinx.util import (get_matching_files, attrdict, status_iterator, + ensuredir, relative_uri, os_path, SEP) +from sphinx.htmlhelp import build_hhx +from sphinx.patchlevel import get_version_info, get_sys_version_info +from sphinx.htmlwriter import HTMLWriter +from sphinx.latexwriter import LaTeXWriter +from sphinx.environment import BuildEnvironment, NoUri +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 -from . import roles -from . import directives +from sphinx import roles +from sphinx import directives ENV_PICKLE_FILENAME = 'environment.pickle' LAST_BUILD_FILENAME = 'last_build' @@ -53,33 +53,18 @@ class relpath_to(object): self.builder = builder def __call__(self, otheruri, resource=False): if not resource: - otheruri = self.builder.get_target_uri(otheruri) + otheruri = self.builder.get_target_uri(otheruri + '.rst') 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): """ Builds target formats from the reST sources. """ - option_spec = {} - def __init__(self, srcdirname, outdirname, doctreedirname, - options, confoverrides=None, env=None, - status_stream=None, warning_stream=None, - freshenv=False): + confoverrides=None, env=None, freshenv=False, + status_stream=None, warning_stream=None): self.srcdir = srcdirname self.outdir = outdirname self.doctreedir = doctreedirname @@ -87,9 +72,6 @@ class Builder(object): os.mkdir(doctreedirname) self.freshenv = freshenv - self.options = attrdict(options) - self.validate_options() - self.status_stream = status_stream or sys.stdout self.warning_stream = warning_stream or sys.stderr @@ -97,7 +79,12 @@ class Builder(object): self.env = env 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 del self.config['__builtins__'] for key, val in self.config.items(): @@ -105,31 +92,23 @@ class Builder(object): del self.config[key] if confoverrides: self.config.update(confoverrides) - # replace version info if 'auto' - if self.config['version'] == 'auto' or self.config['release'] == 'auto': + # replace version info if '' + if self.config['version'] == '' or self.config['release'] == '': try: version, release = get_version_info(srcdirname) except (IOError, OSError): version, release = get_sys_version_info() self.warn('Can\'t get version info from Include/patchlevel.h, ' 'using version of this interpreter (%s).' % release) - if self.config['version'] == 'auto': + if self.config['version'] == '': self.config['version'] = version - if self.config['release'] == 'auto': + if self.config['release'] == '': self.config['release'] = release self.init() # 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): if not nobold: message = bold(message) if nonl: @@ -214,16 +193,20 @@ class Builder(object): updated_filenames = [] # while reading, collect all warnings from docutils - with collect_env_warnings(self): - self.msg('reading, updating environment:', nonl=1) - iterator = self.env.update(self.config) - self.msg(iterator.next(), nonl=1, nobold=1) - for filename in iterator: - if not updated_filenames: - self.msg('') - updated_filenames.append(filename) - self.msg(purple(filename), nonl=1, nobold=1) - self.msg() + warnings = [] + self.env.set_warnfunc(warnings.append) + self.msg('reading, updating environment:', nonl=1) + iterator = self.env.update(self.config) + self.msg(iterator.next(), nonl=1, nobold=1) + for filename in iterator: + if not updated_filenames: + self.msg('') + updated_filenames.append(filename) + 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: # save the environment @@ -260,12 +243,16 @@ class Builder(object): self.prepare_writing(filenames) # write target files - with collect_env_warnings(self): - self.msg('writing output...') - for filename in status_iterator(sorted(filenames), green, - stream=self.status_stream): - doctree = self.env.get_and_resolve_doctree(filename, self) - self.write_file(filename, doctree) + warnings = [] + self.env.set_warnfunc(warnings.append) + self.msg('writing output...') + for filename in status_iterator(sorted(filenames), green, + stream=self.status_stream): + 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): raise NotImplementedError @@ -288,22 +275,28 @@ class StandaloneHTMLBuilder(Builder): def init(self): """Load templates.""" # lazily import this, maybe other builders won't need it - from ._jinja import Environment, FileSystemLoader + from sphinx._jinja import Environment, SphinxFileSystemLoader # load templates self.templates = {} templates_path = path.join(path.dirname(__file__), 'templates') - jinja_env = Environment(loader=FileSystemLoader(templates_path), - # disable traceback, more likely that something in the - # application is broken than in the templates - friendly_traceback=False) + self.jinja_env = Environment(loader=SphinxFileSystemLoader([templates_path]), + # disable traceback, more likely that something + # in the application is broken than in the templates + friendly_traceback=False) + # pre-load built-in templates for fname in os.listdir(templates_path): 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): """Utility: Render a lone doctree node.""" - doc = new_document('foo') + doc = new_document('') doc.append(node) return publish_parts( doc, @@ -314,7 +307,7 @@ class StandaloneHTMLBuilder(Builder): ) def prepare_writing(self, filenames): - from .search import IndexBuilder + from sphinx.search import IndexBuilder self.indexer = IndexBuilder() self.load_indexer(filenames) self.docwriter = HTMLWriter(self) @@ -331,13 +324,15 @@ class StandaloneHTMLBuilder(Builder): self.last_updated = None self.globalcontext = dict( - last_updated = self.last_updated, - builder = self.name, + project = self.config.get('project', 'Python'), + copyright = self.config.get('copyright', ''), release = self.config['release'], version = self.config['version'], + last_updated = self.last_updated, + builder = self.name, parents = [], - len = len, titles = {}, + len = len, # the built-in ) def write_file(self, filename, doctree): @@ -375,7 +370,6 @@ class StandaloneHTMLBuilder(Builder): context = dict( title = title, sourcename = sourcename, - pathto = relpath_to(self, self.get_target_uri(filename)), body = self.docwriter.parts['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 @@ -386,7 +380,7 @@ class StandaloneHTMLBuilder(Builder): ) self.index_file(filename, doctree, title) - self.handle_file(filename, context) + self.handle_page(filename[:-4], context) def finish(self): self.msg('writing additional files...') @@ -402,10 +396,8 @@ class StandaloneHTMLBuilder(Builder): genindexcontext = dict( genindexentries = self.env.index, 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 @@ -422,12 +414,12 @@ class StandaloneHTMLBuilder(Builder): cg = 0 # collapse group fl = '' # first letter for mn, (fn, sy, pl, dep) in modules: - pl = pl.split(', ') if pl else [] + pl = pl and pl.split(', ') or [] platforms.update(pl) if fl != mn[0].lower() and mn[0] != '_': modindexentries.append(['', False, 0, False, mn[0].upper(), '', [], False]) - tn = mn.partition('.')[0] + tn = mn.split('.')[0] if tn != mn: # submodule if pmn == tn: @@ -447,32 +439,22 @@ class StandaloneHTMLBuilder(Builder): modindexcontext = dict( modindexentries = modindexentries, platforms = platforms, - current_page_name = 'modindex', - pathto = relpath_to(self, self.get_target_uri('modindex.rst')), ) - self.handle_file('modindex.rst', modindexcontext, 'modindex') - - # 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') + self.handle_page('modindex', modindexcontext, 'modindex.html') # the search page - searchcontext = dict( - pathto = relpath_to(self, self.get_target_uri('search.rst')), - current_page_name = 'search', - ) - self.handle_file('search.rst', searchcontext, 'search') + self.handle_page('search', {}, 'search.html') + + # additional pages from conf.py + for pagename, template in self.config.get('html_additional_pages', {}).items(): + 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 self.msg('copying style files...') @@ -512,8 +494,11 @@ class StandaloneHTMLBuilder(Builder): def load_indexer(self, filenames): 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') + finally: + f.close() except (IOError, OSError): pass # delete all entries for files that will be rebuilt @@ -522,32 +507,43 @@ class StandaloneHTMLBuilder(Builder): def index_file(self, filename, doctree, title): # only index pages with title if self.indexer is not None and title: - category = get_category(filename) - if category is not None: - self.indexer.feed(self.get_target_uri(filename)[:-5], # strip '.html' - category, title, doctree) + self.indexer.feed(self.get_target_uri(filename)[:-5], # strip '.html' + title, doctree) - def handle_file(self, filename, context, templatename='page'): + def handle_page(self, pagename, context, templatename='page.html'): 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) - 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 try: - with codecs.open(outfilename, 'w', 'utf-8') as fp: - fp.write(output) + f = codecs.open(outfilename, 'w', 'utf-8') + try: + f.write(output) + finally: + f.close() except (IOError, OSError), err: self.warn("Error writing file %s: %s" % (outfilename, err)) if self.copysource and context.get('sourcename'): # 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']))) def handle_finish(self): self.msg('dumping search index...') 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') + finally: + f.close() class WebHTMLBuilder(StandaloneHTMLBuilder): @@ -581,44 +577,57 @@ class WebHTMLBuilder(StandaloneHTMLBuilder): def load_indexer(self, filenames): 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') + finally: + f.close() except (IOError, OSError): pass # delete all entries for files that will be rebuilt self.indexer.prune(set(self.env.all_files) - set(filenames)) 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: - category = get_category(filename) - if category is not None: - self.indexer.feed(filename, category, title, doctree) + self.indexer.feed(filename, title, doctree) - def handle_file(self, filename, context, templatename='page'): - outfilename = path.join(self.outdir, os_path(filename)[:-4] + '.fpickle') + def handle_page(self, pagename, context, templatename='page.html'): + 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)) - context.pop('pathto', None) # can't be pickled - with file(outfilename, 'wb') as fp: - pickle.dump(context, fp, 2) + f = open(outfilename, 'wb') + try: + pickle.dump(context, f, 2) + finally: + f.close() # if there is a source file, copy the source file for the "show source" link if context.get('sourcename'): source_name = path.join(self.outdir, 'sources', os_path(context['sourcename'])) 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): # dump the global context outfilename = path.join(self.outdir, 'globalcontext.pickle') - with file(outfilename, 'wb') as fp: - pickle.dump(self.globalcontext, fp, 2) + f = open(outfilename, 'wb') + try: + pickle.dump(self.globalcontext, f, 2) + finally: + f.close() self.msg('dumping search index...') 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') + finally: + f.close() # copy the environment file from the doctree dir to the output dir # as needed by the web app @@ -641,15 +650,11 @@ class HTMLHelpBuilder(StandaloneHTMLBuilder): """ name = 'htmlhelp' - option_spec = { - 'outname': 'Output file base name (default "pydoc")' - } - # don't copy the reST source copysource = False 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): @@ -660,6 +665,17 @@ class LaTeXBuilder(Builder): def init(self): 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('', 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): return 'all documents' # for now @@ -674,49 +690,42 @@ class LaTeXBuilder(Builder): else: 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): - # first, assemble the "special" docs that are in every PDF - specials = [] - for fname in ["glossary", "about", "license", "copyright"]: - specials.append(self.env.get_doctree(fname+".rst")) + # first, assemble the "appendix" docs that are in every PDF + appendices = [] + for fname in self.config.get('latex_appendices', []): + appendices.append(self.env.get_doctree(fname)) docwriter = LaTeXWriter(self) docsettings = OptionParser( defaults=self.env.settings, 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_path=path.join(self.outdir, targetname), encoding='utf-8') print "processing", targetname + "...", doctree = self.assemble_doctree( - sourcename, specials=(docclass == 'manual') and specials or []) + sourcename, appendices=(docclass == 'manual') and appendices or []) print "writing...", doctree.settings = docsettings + doctree.settings.author = author doctree.settings.filename = sourcename doctree.settings.docclass = docclass output = docwriter.write(doctree, destination) print "done" - def assemble_doctree(self, indexfile, specials): + def assemble_doctree(self, indexfile, appendices): self.filenames = set([indexfile, 'glossary.rst', 'about.rst', 'license.rst', 'copyright.rst']) print green(indexfile), def process_tree(filename, tree): - #tree = tree.deepcopy() XXX + tree = tree.deepcopy() for toctreenode in tree.traverse(addnodes.toctree): newnodes = [] includefiles = map(str, toctreenode['includefiles']) @@ -734,11 +743,25 @@ class LaTeXBuilder(Builder): toctreenode.parent.replace(toctreenode, newnodes) return tree largetree = process_tree(indexfile, self.env.get_doctree(indexfile)) - largetree.extend(specials) + largetree.extend(appendices) print print "resolving references..." - # XXX problem here: :ref:s to distant tex files 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 def finish(self): @@ -757,15 +780,15 @@ class ChangesBuilder(Builder): name = 'changes' def init(self): - from ._jinja import Environment, FileSystemLoader + from sphinx._jinja import Environment, FileSystemLoader 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 # application is broken than in the templates friendly_traceback=False) - self.ftemplate = jinja_env.get_template('versionchanges_frameset.html') - self.vtemplate = jinja_env.get_template('versionchanges.html') - self.stemplate = jinja_env.get_template('rstsource.html') + self.ftemplate = jinja_env.get_template('changes/frameset.html') + self.vtemplate = jinja_env.get_template('changes/versionchanges.html') + self.stemplate = jinja_env.get_template('changes/rstsource.html') def get_outdated_files(self): return self.outdir @@ -813,15 +836,22 @@ class ChangesBuilder(Builder): (entry, filename, lineno)) ctx = { + 'project': self.config.get('project', 'Python'), 'version': ver, 'libchanges': sorted(libchanges.iteritems()), 'apichanges': sorted(apichanges), '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)) - 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)) + finally: + f.close() hltext = ['.. versionadded:: %s' % ver, '.. versionchanged:: %s' % ver, @@ -837,14 +867,17 @@ class ChangesBuilder(Builder): self.msg('copying source files...') for filename in self.env.all_files: - with open(path.join(self.srcdir, os_path(filename))) as f: - lines = f.readlines() + f = open(path.join(self.srcdir, os_path(filename))) + lines = f.readlines() targetfn = path.join(self.outdir, 'rst', os_path(filename)) + '.html' 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)) ctx = {'filename': filename, 'text': text} f.write(self.stemplate.render(ctx)) + finally: + f.close() shutil.copyfile(path.join(path.dirname(__file__), 'style', 'default.css'), path.join(self.outdir, 'default.css')) diff --git a/sphinx/directives.py b/sphinx/directives.py index 16ae589aa..b8d7eb514 100644 --- a/sphinx/directives.py +++ b/sphinx/directives.py @@ -8,7 +8,6 @@ :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ -from __future__ import with_statement import re import string @@ -19,7 +18,7 @@ from docutils import nodes from docutils.parsers.rst import directives, roles from docutils.parsers.rst.directives import admonitions -from . import addnodes +from sphinx import addnodes # ------ index markup -------------------------------------------------------------- @@ -142,7 +141,7 @@ def parse_py_signature(signode, sig, desctype, env): else: fullname = env.currclass + '.' + name else: - fullname = classname + name if classname else name + fullname = classname and classname + name or name if classname: signode += addnodes.desc_classname(classname, classname) @@ -285,7 +284,7 @@ def add_refcount_annotation(env, node, name): if entry.result_refs is None: rc += "Always NULL." else: - rc += ("New" if entry.result_refs else "Borrowed") + " reference." + rc += (entry.result_refs and "New" or "Borrowed") + " reference." 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 # function name in this desc block 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 if fullname not in state.document.ids: signode['names'].append(fullname) @@ -612,8 +611,9 @@ def literalinclude_directive(name, arguments, options, content, lineno, fn = path.normpath(path.join(source_dir, fn)) try: - with open(fn) as f: - text = f.read() + f = open(fn) + text = f.read() + f.close() except (IOError, OSError): retnode = state.document.reporter.warning( 'Include file %r not found or reading it failed' % arguments[0], line=lineno) diff --git a/sphinx/environment.py b/sphinx/environment.py index 140df1605..4d8181048 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -8,18 +8,22 @@ :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ -from __future__ import with_statement import re import os import time import heapq -import hashlib import difflib import itertools import cPickle as pickle from os import path from string import uppercase +try: + import hashlib + md5 = hashlib.md5 +except: + import md5 + md5 = md5.new from docutils import nodes from docutils.io import FileInput @@ -37,9 +41,9 @@ Body.enum.converters['loweralpha'] = \ Body.enum.converters['lowerroman'] = \ Body.enum.converters['upperroman'] = lambda x: None -from . import addnodes -from .util import get_matching_files, os_path, SEP -from .refcounting import Refcounts +from sphinx import addnodes +from sphinx.util import get_matching_files, os_path, SEP +from sphinx.refcounting import Refcounts default_settings = { 'embed_stylesheet': False, @@ -156,8 +160,11 @@ class BuildEnvironment: @staticmethod def frompickle(filename): - with open(filename, 'rb') as picklefile: + picklefile = open(filename, 'rb') + try: env = pickle.load(picklefile) + finally: + picklefile.close() if env.version != ENV_VERSION: raise IOError('env version not current') return env @@ -166,8 +173,11 @@ class BuildEnvironment: # remove unpicklable attributes warnfunc = self._warnfunc self.set_warnfunc(None) - with open(filename, 'wb') as picklefile: + picklefile = open(filename, 'wb') + try: pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL) + finally: + picklefile.close() # reset stream self.set_warnfunc(warnfunc) @@ -178,9 +188,8 @@ class BuildEnvironment: self.srcdir = srcdir self.config = {} - # read the refcounts file - self.refcounts = Refcounts.fromfile( - path.join(self.srcdir, 'data', 'refcounts.dat')) + # refcount data if present + self.refcounts = {} # the docutils settings for building self.settings = default_settings.copy() @@ -194,7 +203,7 @@ class BuildEnvironment: # Build times -- to determine changed files # 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 self.metadata = {} # filename -> dict of metadata items @@ -291,21 +300,22 @@ class BuildEnvironment: os_path(filename)[:-3] + 'doctree')): changed.append(filename) continue - mtime, md5 = self.all_files[filename] + mtime, md5sum = self.all_files[filename] newmtime = path.getmtime(path.join(self.srcdir, os_path(filename))) if newmtime == mtime: continue # check the MD5 #with file(path.join(self.srcdir, filename), 'rb') as f: - # newmd5 = hashlib.md5(f.read()).digest() - #if newmd5 != md5: + # newmd5sum = md5(f.read()).digest() + #if newmd5sum != md5sum: changed.append(filename) return added, changed, removed # If one of these config values changes, all files need to be re-read. 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): @@ -330,14 +340,15 @@ class BuildEnvironment: 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 for filename in removed: 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 for filename in added + changed: yield filename @@ -364,9 +375,12 @@ class BuildEnvironment: self.build_toc_from(filename, doctree) # calculate the MD5 of the file at time of build - with file(src_path, 'rb') as f: - md5 = hashlib.md5(f.read()).digest() - self.all_files[filename] = (path.getmtime(src_path), md5) + f = open(src_path, 'rb') + try: + md5sum = md5(f.read()).digest() + finally: + f.close() + self.all_files[filename] = (path.getmtime(src_path), md5sum) # make it picklable doctree.reporter = None @@ -388,8 +402,11 @@ class BuildEnvironment: dirname = path.dirname(doctree_filename) if not path.isdir(dirname): os.makedirs(dirname) - with file(doctree_filename, 'wb') as f: + f = open(doctree_filename, 'wb') + try: pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) + finally: + f.close() else: return doctree @@ -446,9 +463,9 @@ class BuildEnvironment: includefiles_len = len(includefiles) for i, includefile in enumerate(includefiles): # 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 - 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] # note that if the included file is rebuilt, this one must be # too (since the TOC of the included file could have changed) @@ -539,8 +556,11 @@ class BuildEnvironment: def get_doctree(self, filename): """Read the doctree for a file from the pickle and return it.""" 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) + finally: + f.close() doctree.reporter = Reporter(filename, 2, 4, stream=RedirStream(self._warnfunc)) return doctree @@ -617,9 +637,11 @@ class BuildEnvironment: if filename == docfilename: newnode['refid'] = labelid else: - # in case the following calls raises NoUri... - # else the final node will contain a label name - contnode = innernode + # set more info in contnode in case the following call + # raises NoUri, the builder will have to resolve these + contnode = addnodes.pending_xref('') + contnode['reffilename'] = filename + contnode['refsectname'] = sectname newnode['refuri'] = builder.get_relative_uri( docfilename, filename) + '#' + labelid newnode.append(innernode) @@ -669,14 +691,14 @@ class BuildEnvironment: newnode['refuri'] = ( builder.get_relative_uri(docfilename, filename) + anchor) newnode['reftitle'] = '%s%s%s' % ( - ('(%s) ' % platform if platform else ''), - synopsis, (' (deprecated)' if deprecated else '')) + (platform and '(%s) ' % platform), + synopsis, (deprecated and ' (deprecated)' or '')) newnode.append(contnode) else: # "descrefs" modname = node['modname'] 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, target, typ, searchorder) if not desc: @@ -716,7 +738,10 @@ class BuildEnvironment: # new entry types must be listed in directives.py! for type, string, tid, alias in entries: if type == 'single': - entry, _, subentry = string.partition(';') + try: + entry, subentry = string.split(';', 1) + except: + entry, subentry = string, '' add_entry(entry.strip(), subentry.strip()) elif type == 'pair': first, second = map(lambda x: x.strip(), string.split(';', 1)) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 34421110f..1734a669b 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -9,9 +9,9 @@ :license: BSD. """ +import sys import cgi import parser -from collections import defaultdict try: import pygments @@ -42,7 +42,7 @@ else: Number: '#208050', }) - lexers = defaultdict(TextLexer, + lexers = dict( none = TextLexer(), python = PythonLexer(), pycon = PythonConsoleLexer(), @@ -71,8 +71,12 @@ def highlight_block(source, lang, dest='html'): else: # maybe Python -- try parsing it try: - parser.suite('from __future__ import with_statement\n' + - source + '\n') + # if we're using 2.5, use the with statement + if sys.version_info >= (2, 5): + parser.suite('from __future__ import with_statement\n' + + source + '\n') + else: + parser.suite(source + '\n') except (SyntaxError, UnicodeEncodeError): return unhighlighted() else: diff --git a/sphinx/htmlhelp.py b/sphinx/htmlhelp.py index 262c4631c..3587757c9 100644 --- a/sphinx/htmlhelp.py +++ b/sphinx/htmlhelp.py @@ -9,7 +9,6 @@ :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ -from __future__ import with_statement import os import cgi @@ -17,7 +16,7 @@ from os import path from docutils import nodes -from . import addnodes +from sphinx import addnodes # Project file (*.hhp) template. 'outname' is the file basename (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 Index file=%(outname)s.hhk Language=0x409 -Title=Python %(version)s Documentation +Title=%(project)s %(version)s Documentation [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 [FILES] @@ -119,24 +118,32 @@ was will with def build_hhx(builder, outdir, outname): 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): print >>f, word + finally: + f.close() 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, - 'version': builder.config['version']}) + 'version': builder.config['version'], + 'project': builder.config['project']}) if not outdir.endswith(os.sep): outdir += os.sep olen = len(outdir) for root, dirs, files in os.walk(outdir): 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('/', '\\') + finally: + f.close() 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) # special books f.write('
  • ' + object_sitemap % ('Main page', 'index.html')) @@ -167,9 +174,12 @@ def build_hhx(builder, outdir, outname): write_toc(node[0], ullevel) write_toc(toc) f.write(contents_footer) + finally: + f.close() 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('
      \n') def write_index(title, refs, subitems): if refs: @@ -186,3 +196,5 @@ def build_hhx(builder, outdir, outname): for title, (refs, subitems) in group: write_index(title, refs, subitems) f.write('
    \n') + finally: + f.close() diff --git a/sphinx/htmlwriter.py b/sphinx/htmlwriter.py index 56536c5cd..ccc2fa91b 100644 --- a/sphinx/htmlwriter.py +++ b/sphinx/htmlwriter.py @@ -12,7 +12,7 @@ from docutils import nodes 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): @@ -162,7 +162,7 @@ def translator_class(builder): # overwritten 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)) raise nodes.SkipNode @@ -246,7 +246,8 @@ def translator_class(builder): def depart_title(self, node): close_tag = self.context[-1] if builder.name != 'htmlhelp' and \ - close_tag.startswith((' set(filenames) self._mapping = {} - # category -> set(filenames) - self._categories = {} def load(self, stream, format): """Reconstruct from frozen data.""" frozen = self.formats[format][1](stream.read()) index2fn = frozen[0] - self._titles = dict(zip(frozen[0], frozen[2])) - self._categories = dict((k, set(index2fn[i] for i in v)) - for (k, v) in frozen[1].iteritems()) + self._titles = dict(zip(frozen[0], frozen[1])) 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): """Dump the frozen index to a stream.""" @@ -87,8 +82,6 @@ class IndexBuilder(object): fn2index = dict((f, i) for (i, f) in enumerate(fns)) return [ fns, - dict((k, [fn2index[fn] for fn in v]) - for (k, v) in self._categories.iteritems()), titles, dict((k, [fn2index[fn] for fn in v]) for (k, v) in self._mapping.iteritems()), @@ -103,13 +96,10 @@ class IndexBuilder(object): self._titles = new_titles for wordnames in self._mapping.itervalues(): 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.""" self._titles[filename] = title - self._categories.setdefault(category, set()).add(filename) visitor = WordCollector(doctree) doctree.walk(visitor) @@ -125,25 +115,24 @@ class SearchFrontend(object): """ def __init__(self, index): - self.filenames, self.areas, self.titles, self.words = index + self.filenames, self.titles, self.words = index self._stemmer = Stemmer() - def query(self, required, excluded, areas): - file_map = defaultdict(set) + def query(self, required, excluded): + file_map = {} for word in required: if word not in self.words: break 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]) for fid, words in file_map.iteritems() - if len(words) == len(required) and - any(fid in self.areas.get(area, ()) for area in areas) and not + if len(words) == len(required) and not any(fid in self.words.get(word, ()) for word in excluded) ), key=lambda x: x[1].lower()) - def search(self, searchstring, areas): + def search(self, searchstring): required = set() excluded = set() for word in searchstring.split(): @@ -154,4 +143,4 @@ class SearchFrontend(object): storage = required storage.add(self._stemmer.stem(word.lower())) - return self.query(required, excluded, areas) + return self.query(required, excluded) diff --git a/sphinx/style/default.css b/sphinx/style/default.css index ce7076da1..c91a5c337 100644 --- a/sphinx/style/default.css +++ b/sphinx/style/default.css @@ -1,5 +1,5 @@ /** - * Python Doc Design + * Sphinx Doc Design */ body { diff --git a/sphinx/style/rightsidebar.css b/sphinx/style/rightsidebar.css index 0d72b9a97..d9c42a496 100644 --- a/sphinx/style/rightsidebar.css +++ b/sphinx/style/rightsidebar.css @@ -1,5 +1,5 @@ /** - * Python Doc Design -- Right Side Bar Overrides + * Sphinx Doc Design -- Right Side Bar Overrides */ diff --git a/sphinx/style/searchtools.js b/sphinx/style/searchtools.js index 81fcfe9b1..d7eb5d64a 100644 --- a/sphinx/style/searchtools.js +++ b/sphinx/style/searchtools.js @@ -228,27 +228,15 @@ var Search = { var params = $.getQueryParameters(); if (params.q) { 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; - - this.performSearch(query, areas); + this.performSearch(query); } }, /** * perform a search for something */ - performSearch : function(query, areas) { + performSearch : function(query) { // create the required interface elements var out = $('#search-results'); var title = $('

    Searching

    ').appendTo(out); @@ -301,14 +289,12 @@ var Search = { console.debug('SEARCH: searching for:'); console.info('required: ', searchwords); console.info('excluded: ', excluded); - console.info('areas: ', areas); // fetch searchindex and perform search $.getJSON('searchindex.json', function(data) { // prepare search var filenames = data[0]; - var areaMap = data[1]; var titles = data[2] var words = data[3]; var fileMap = {}; @@ -342,38 +328,25 @@ var Search = { if (fileMap[file].length != searchwords.length) { 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 // search result. - if (valid) { - for (var i = 0; i < excluded.length; i++) { - if ($.contains(words[excluded[i]] || [], file)) { - valid = false; - break; - } + for (var i = 0; i < excluded.length; i++) { + if ($.contains(words[excluded[i]] || [], file)) { + valid = false; + break; } + } - // if we have still a valid result we can add it - // to the result list - if (valid) { - results.push([filenames[file], titles[file]]); - } + // if we have still a valid result we can add it + // to the result list + if (valid) { + results.push([filenames[file], titles[file]]); } } // delete unused variables in order to not waste // memory until list is retrieved completely - delete filenames, areaMap, titles, words, data; + delete filenames, titles, words, data; // now sort the results by title results.sort(function(a, b) { diff --git a/sphinx/style/stickysidebar.css b/sphinx/style/stickysidebar.css index e106ee004..a635ac2c8 100644 --- a/sphinx/style/stickysidebar.css +++ b/sphinx/style/stickysidebar.css @@ -1,5 +1,5 @@ /** - * Python Doc Design -- Sticky Sidebar Overrides + * Sphinx Doc Design -- Sticky Sidebar Overrides */ div.sidebar { diff --git a/sphinx/style/traditional.css b/sphinx/style/traditional.css index 58ee1ad3c..daf44fcdd 100644 --- a/sphinx/style/traditional.css +++ b/sphinx/style/traditional.css @@ -1,5 +1,5 @@ /** - * Python Doc Design + * Sphinx Doc Design -- traditional python.org style */ body { diff --git a/sphinx/templates/versionchanges_frameset.html b/sphinx/templates/changes/frameset.html similarity index 72% rename from sphinx/templates/versionchanges_frameset.html rename to sphinx/templates/changes/frameset.html index 5c99af9ab..f7732ba58 100644 --- a/sphinx/templates/versionchanges_frameset.html +++ b/sphinx/templates/changes/frameset.html @@ -2,7 +2,7 @@ "http://www.w3.org/TR/html4/frameset.dtd"> - Changes in Version {{ version }} — Python Documentation + Changes in Version {{ version }} — {{ project }} Documentation diff --git a/sphinx/templates/rstsource.html b/sphinx/templates/changes/rstsource.html similarity index 79% rename from sphinx/templates/rstsource.html rename to sphinx/templates/changes/rstsource.html index 4abef620f..2aedd5a58 100644 --- a/sphinx/templates/rstsource.html +++ b/sphinx/templates/changes/rstsource.html @@ -2,7 +2,7 @@ "http://www.w3.org/TR/html4/loose.dtd"> - {{ filename }} — Python Documentation + {{ filename }} — {{ project }} Documentation diff --git a/sphinx/templates/versionchanges.html b/sphinx/templates/changes/versionchanges.html similarity index 91% rename from sphinx/templates/versionchanges.html rename to sphinx/templates/changes/versionchanges.html index e31de59c6..4e037afdf 100644 --- a/sphinx/templates/versionchanges.html +++ b/sphinx/templates/changes/versionchanges.html @@ -9,7 +9,7 @@ - Changes in Version {{ version }} — Python Documentation + Changes in Version {{ version }} — {{ project }} Documentation
    diff --git a/sphinx/templates/download.html b/sphinx/templates/download.html deleted file mode 100644 index 2ddb833f6..000000000 --- a/sphinx/templates/download.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "layout.html" %} -{% set title = 'Download' %} -{% block body %} - -

    Download Python {{ release }} Documentation - {%- if last_updated %} (last updated on {{ last_updated }}){% endif %}

    - -

    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.

    - -{# XXX download links, relative to download_base_url #} - - -

    These archives contain all the content in the documentation section.

    - -

    Unpacking

    - -

    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 InfoZIP unzip program can be -used to handle the ZIP archives if desired. The .tar.bz2 archives provide the -best compression and fastest download times.

    - -

    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.

    - -

    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 The bzip2 and libbzip2 official home page, but -most other archiving utilities support the tar and bzip2 formats as well.

    - - -

    Problems

    - -

    Printing PDFs using Adobe Acrobat Reader 5.0: 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.

    - -

    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.

    - -

    If you have comments or suggestions for the Python documentation, please send -email to docs@python.org.

    - -{% endblock %} diff --git a/sphinx/templates/index.html b/sphinx/templates/index.html index 884be2b8c..fa4b52ac6 100644 --- a/sphinx/templates/index.html +++ b/sphinx/templates/index.html @@ -4,67 +4,27 @@ (pathto('@rss/recent'), 'application/rss+xml', 'Recent Comments') ] %} {% block body %} -

    Python Documentation

    +

    {{ project }} Documentation

    - Welcome! This is the documentation for Python + Welcome! This is the documentation for {{ project }} {{ release }}{% if last_updated %}, last updated {{ last_updated }}{% endif %}.

    - -

    Parts of the documentation:

    - - -
    - - - - - - - - - - - - -
    - + {% if indextemplate %} + {{ rendertemplate(indextemplate) }} + {% else %}

    Indices and tables:

    -
    - - - - - - -
    - -

    Meta information:

    - -
    - - + - - + +
    - + {% endif %} {% endblock %} diff --git a/sphinx/templates/layout.html b/sphinx/templates/layout.html index df7301a04..0f518d911 100644 --- a/sphinx/templates/layout.html +++ b/sphinx/templates/layout.html @@ -1,4 +1,4 @@ -{% if builder != 'htmlhelp' %}{% set titlesuffix = " — Python Documentation" %}{% endif -%} +{% if builder != 'htmlhelp' %}{% set titlesuffix = " — " + project + " Documentation" %}{% endif -%} @@ -6,7 +6,7 @@ {{ title|striptags }}{{ titlesuffix }} {%- if builder == 'web' %} - {%- for link, type, title in page_links %} @@ -26,12 +26,16 @@ {%- endif %} - - - - - - + {%- if hasdoc('about') %} + + {%- endif %} + + + + {%- if hasdoc('copyright') %} + + {%- endif %} + {%- if parents %} {%- endif %} @@ -48,8 +52,8 @@
    diff --git a/sphinx/templates/comments.html b/sphinx/templates/web/comments.html similarity index 100% rename from sphinx/templates/comments.html rename to sphinx/templates/web/comments.html diff --git a/sphinx/templates/edit.html b/sphinx/templates/web/edit.html similarity index 87% rename from sphinx/templates/edit.html rename to sphinx/templates/web/edit.html index 69b8e9ce7..a40063687 100644 --- a/sphinx/templates/edit.html +++ b/sphinx/templates/web/edit.html @@ -22,9 +22,10 @@

    Suggest changes for this page

    {% if not rendered %}

    Here you can edit the source of “{{ doctitle|striptags }}” and - submit the results as a patch to the Python documentation team. If you want - to know more about reST, the markup language used, read - Documenting Python.

    + submit the results as a patch to the {{ project }} documentation team. + {# XXX Python specific #} + If you want to know more about reST, the markup language used, read + Documenting Python.

    {% endif %}
    diff --git a/sphinx/templates/inlinecomments.html b/sphinx/templates/web/inlinecomments.html similarity index 100% rename from sphinx/templates/inlinecomments.html rename to sphinx/templates/web/inlinecomments.html diff --git a/sphinx/templates/keyword_not_found.html b/sphinx/templates/web/keyword_not_found.html similarity index 81% rename from sphinx/templates/keyword_not_found.html rename to sphinx/templates/web/keyword_not_found.html index 859eef612..55e42c120 100644 --- a/sphinx/templates/keyword_not_found.html +++ b/sphinx/templates/web/keyword_not_found.html @@ -20,12 +20,12 @@ {% endif %}

    - If you want to search the entire Python documentation for the string - "{{ keyword|e }}", then use the search function.

    For a quick overview over all documented modules, - click here. + click here.

    {% endblock %} diff --git a/sphinx/templates/not_found.html b/sphinx/templates/web/not_found.html similarity index 74% rename from sphinx/templates/not_found.html rename to sphinx/templates/web/not_found.html index 5c4fa109e..bff8af0f6 100644 --- a/sphinx/templates/not_found.html +++ b/sphinx/templates/web/not_found.html @@ -6,6 +6,6 @@ The page {{ req.path|e }} does not exist on this server.

    - Click here to return to the index. + Click here to return to the index.

    {% endblock %} diff --git a/sphinx/templates/settings.html b/sphinx/templates/web/settings.html similarity index 87% rename from sphinx/templates/settings.html rename to sphinx/templates/web/settings.html index 670526713..936087d16 100644 --- a/sphinx/templates/settings.html +++ b/sphinx/templates/web/settings.html @@ -2,13 +2,13 @@ {% set title = 'Settings' %} {% set current_page_name = 'settings' %} {% block body %} -

    Python Documentation Settings

    +

    {{ project }} Documentation Settings

    - 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.

    - +

    Select your stylesheet:

    {%- for design, (foo, descr) in known_designs %} diff --git a/sphinx/templates/submitted.html b/sphinx/templates/web/submitted.html similarity index 76% rename from sphinx/templates/submitted.html rename to sphinx/templates/web/submitted.html index 845f809a7..5b542aeeb 100644 --- a/sphinx/templates/submitted.html +++ b/sphinx/templates/web/submitted.html @@ -5,7 +5,7 @@ {% endblock %} {% block body %}

    Patch submitted

    -

    Your patch has been submitted to the Python documentation team and will be +

    Your patch has been submitted to the {{ project }} documentation team and will be processed shortly.

    You will be redirected to the original documentation page shortly.

    diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index b75fa5bbb..de5bc5582 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -77,14 +77,6 @@ def get_matching_files(dirname, pattern, exclude=()): 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): if not text: text = '' diff --git a/sphinx/util/stemmer.py b/sphinx/util/stemmer.py index a70cfea3e..2ca991f3b 100644 --- a/sphinx/util/stemmer.py +++ b/sphinx/util/stemmer.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- """ sphinx.util.stemmer diff --git a/sphinx/web/admin.py b/sphinx/web/admin.py index 80f847b63..12004475b 100644 --- a/sphinx/web/admin.py +++ b/sphinx/web/admin.py @@ -9,9 +9,9 @@ :license: BSD. """ -from .util import render_template -from .wsgiutil import Response, RedirectResponse, NotFound -from .database import Comment +from sphinx.web.util import render_template +from sphinx.web.wsgiutil import Response, RedirectResponse, NotFound +from sphinx.web.database import Comment class AdminPanel(object): diff --git a/sphinx/web/antispam.py b/sphinx/web/antispam.py index ab9f5f666..3c14d9e41 100644 --- a/sphinx/web/antispam.py +++ b/sphinx/web/antispam.py @@ -9,7 +9,7 @@ :copyright: 2007-2008 by Armin Ronacher. :license: BSD. """ -from __future__ import with_statement + import re import urllib import time @@ -43,14 +43,20 @@ class AntiSpam(object): else: lines = [l.strip() for l in data.splitlines() if not l.startswith('#')] - with file(bad_content_file, 'w') as f: + f = open(bad_content_file, 'w') + try: f.write('\n'.join(lines)) + finally: + f.close() last_change = int(time.time()) if lines is None: try: - with file(bad_content_file) as f: + f = open(bad_content_file) + try: lines = [l.strip() for l in f] + finally: + f.close() except: lines = [] self.rules = [re.compile(rule) for rule in lines if rule] diff --git a/sphinx/web/application.py b/sphinx/web/application.py index 5ec11a6f8..49e393822 100644 --- a/sphinx/web/application.py +++ b/sphinx/web/application.py @@ -9,7 +9,6 @@ :copyright: 2007-2008 by Georg Brandl, Armin Ronacher. :license: BSD. """ -from __future__ import with_statement import os import re @@ -24,24 +23,23 @@ import cPickle as pickle import cStringIO as StringIO from os import path from itertools import groupby -from collections import defaultdict -from .feed import Feed -from .mail import Email -from .util import render_template, get_target_uri, blackhole_dict, striptags -from .admin import AdminPanel -from .userdb import UserDatabase -from .robots import robots_txt -from .oldurls import handle_html_url -from .antispam import AntiSpam -from .database import connect, set_connection, Comment -from .wsgiutil import Request, Response, RedirectResponse, \ +from sphinx.web.feed import Feed +from sphinx.web.mail import Email +from sphinx.web.util import render_template, get_target_uri, blackhole_dict, striptags +from sphinx.web.admin import AdminPanel +from sphinx.web.userdb import UserDatabase +from sphinx.web.robots import robots_txt +from sphinx.web.oldurls import handle_html_url +from sphinx.web.antispam import AntiSpam +from sphinx.web.database import connect, set_connection, Comment +from sphinx.web.wsgiutil import Request, Response, RedirectResponse, \ JSONResponse, SharedDataMiddleware, NotFound, get_base_uri -from ..util import relative_uri -from ..search import SearchFrontend -from ..htmlwriter import HTMLWriter -from ..builder import LAST_BUILD_FILENAME, ENV_PICKLE_FILENAME +from sphinx.util import relative_uri +from sphinx.search import SearchFrontend +from sphinx.htmlwriter import HTMLWriter +from sphinx.builder import LAST_BUILD_FILENAME, ENV_PICKLE_FILENAME from docutils.io import StringOutput from docutils.utils import Reporter @@ -123,8 +121,11 @@ class DocumentationApplication(object): """ def __init__(self, config): - self.cache = blackhole_dict() if config['debug'] else {} - self.freqmodules = defaultdict(int) + if config['debug']: + self.cache = blackhole_dict() + else: + self.cache = {} + self.freqmodules = {} self.last_most_frequent = [] self.generated_stylesheets = {} self.config = config @@ -139,19 +140,31 @@ class DocumentationApplication(object): def load_env(self, new_mtime): - with env_lock: + env_lock.acquire() + try: if self.buildmtime == new_mtime: # happens if another thread already reloaded the env return 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) - 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) - 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)) + finally: + f.close() self.buildmtime = new_mtime self.cache.clear() + finally: + env_lock.release() def search(self, req): @@ -167,12 +180,15 @@ class DocumentationApplication(object): """ 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: raise NotFound() - filename = path.join(self.data_root, 'sources', page_id)[:-3] + 'txt' - with file(filename) as f: + filename = path.join(self.data_root, 'sources', page_id) + '.txt' + f = open(filename) + try: return page_id, f.read() + finally: + f.close() def show_source(self, req, page): @@ -191,7 +207,7 @@ class DocumentationApplication(object): return Response(render_template(req, 'edit.html', self.globalcontext, dict( contents=contents, 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), ))) @@ -209,11 +225,11 @@ class DocumentationApplication(object): builder = MockBuilder() builder.config = env2.config writer = HTMLWriter(builder) - doctree = env2.read_file(page_id, pathname, save_parsed=False) - doctree = env2.get_and_resolve_doctree(page_id, builder, doctree) + doctree = env2.read_file(page_id+'.rst', pathname, save_parsed=False) + doctree = env2.get_and_resolve_doctree(page_id+'.rst', builder, doctree) doctree.settings = OptionParser(defaults=env2.settings, 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) writer.assemble_parts() return writer.parts['fragment'] @@ -302,7 +318,7 @@ class DocumentationApplication(object): referer = '' else: referer = referer[len(base):] - referer = referer.rpartition('?')[0] or referer + referer = referer.split('?')[0] or referer if req.method == 'POST': if req.form.get('cancel'): @@ -362,8 +378,11 @@ class DocumentationApplication(object): yield '@modindex' filename = path.join(self.data_root, 'modindex.fpickle') - with open(filename, 'rb') as f: + f = open(filename, 'rb') + try: context = pickle.load(f) + finally: + f.close() if showpf: entries = context['modindexentries'] i = 0 @@ -386,7 +405,7 @@ class DocumentationApplication(object): """ 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' target = req.args.get('target') page_comment_mode = not target @@ -466,7 +485,7 @@ class DocumentationApplication(object): return 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'] all_comments = Comment.get_for_page(page_id) global_comments = [] @@ -509,17 +528,17 @@ class DocumentationApplication(object): Show the requested documentation page or raise an `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: raise NotFound(show_keyword_matches=True) # increment view count of all modules on that page - for modname in self.env.filemodules.get(page_id, ()): - self.freqmodules[modname] += 1 + for modname in self.env.filemodules.get(page_id+'.rst', ()): + self.freqmodules[modname] = self.freqmodules.get(modname, 0) + 1 # 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? - 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 oldurl = req.args.get('oldurl') @@ -530,9 +549,12 @@ class DocumentationApplication(object): yield page_id + '|' + commentmode # cache miss; load the page and render it - filename = path.join(self.data_root, page_id[:-3] + 'fpickle') - with open(filename, 'rb') as f: + filename = path.join(self.data_root, page_id + '.fpickle') + f = open(filename, 'rb') + try: context = pickle.load(f) + finally: + f.close() # add comments to paqe text if commentmode != 'none': @@ -546,8 +568,11 @@ class DocumentationApplication(object): def get_special_page(self, req, name): yield '@'+name filename = path.join(self.data_root, name + '.fpickle') - with open(filename, 'rb') as f: + f = open(filename, 'rb') + try: context = pickle.load(f) + finally: + f.close() yield render_template(req, name+'.html', self.globalcontext, context) @@ -559,8 +584,8 @@ class DocumentationApplication(object): feed.add_item(comment.title, comment.author, comment.url, comment.parsed_comment_body, comment.pub_date) else: - page_id = self.env.get_real_filename(url) - doctitle = striptags(self.globalcontext['titles'].get(page_id, url)) + page_id = self.env.get_real_filename(url)[:-4] + doctitle = striptags(self.globalcontext['titles'].get(page_id+'.rst', url)) feed = Feed(req, 'Comments for "%s"' % doctitle, 'List of comments for the topic "%s"' % doctitle, url) for comment in Comment.get_for_page(page_id): @@ -632,7 +657,7 @@ class DocumentationApplication(object): 'close_matches': close_matches, 'good_matches_count': good_matches, 'keyword': term - }, self.globalcontext), status=404 if is_error_page else 404) + }, self.globalcontext), status=404) def get_user_stylesheet(self, req): @@ -650,15 +675,21 @@ class DocumentationApplication(object): else: stylesheet = [] 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()) + finally: + f.close() stylesheet = '\n'.join(stylesheet) if not self.config.get('debug'): self.generated_stylesheets[style] = stylesheet 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() + finally: + f.close() # XXX: add timestamp based http caching return Response(stylesheet, mimetype='text/css') diff --git a/sphinx/web/database.py b/sphinx/web/database.py index c5393bf27..dd1d33224 100644 --- a/sphinx/web/database.py +++ b/sphinx/web/database.py @@ -17,7 +17,7 @@ import sqlite3 from datetime import datetime from threading import local -from .markup import markup +from sphinx.web.markup import markup _thread_local = local() @@ -88,14 +88,14 @@ class Comment(object): @property def url(self): return '%s#comment-%s' % ( - self.associated_page[:-4], + self.associated_page, self.comment_id ) @property def parsed_comment_body(self): - from .util import get_target_uri - from ..util import relative_uri + from sphinx.web.util import get_target_uri + from sphinx.util import relative_uri uri = get_target_uri(self.associated_page) def make_rel_link(keyword): return relative_uri(uri, 'q/%s/' % keyword) @@ -153,7 +153,7 @@ class Comment(object): cur = get_cursor() cur.execute('''select * from comments where associated_page = ? order by associated_name, comment_id %s''' % - ('desc' if reverse else 'asc'), + (reverse and 'desc' or 'asc'), (associated_page,)) try: return [Comment._make_comment(row) for row in cur] diff --git a/sphinx/web/markup.py b/sphinx/web/markup.py index 460d139f9..481e00f50 100644 --- a/sphinx/web/markup.py +++ b/sphinx/web/markup.py @@ -42,7 +42,7 @@ import cgi import re from urlparse import urlparse -from ..highlighting import highlight_block +from sphinx.highlighting import highlight_block inline_formatting = { @@ -212,7 +212,7 @@ class MarkupParser(object): elif protocol == 'javascript': href = href[11:] paragraph.append('%s' % (cgi.escape(href), - ' rel="nofollow"' if nofollow else '', + nofollow and ' rel="nofollow"' or '', cgi.escape(caption))) elif token == 'code_block': result.append(highlight_block(data, 'python')) diff --git a/sphinx/web/oldurls.py b/sphinx/web/oldurls.py index 3077c6cfb..d34d1eabf 100644 --- a/sphinx/web/oldurls.py +++ b/sphinx/web/oldurls.py @@ -11,7 +11,7 @@ import re -from .wsgiutil import RedirectResponse, NotFound +from sphinx.web.wsgiutil import RedirectResponse, NotFound _module_re = re.compile(r'module-(.*)\.html') @@ -78,7 +78,7 @@ def handle_html_url(req, url): # tutorial elif url[:4] == 'tut/': try: - node = int(url[8:].partition('.html')[0]) + node = int(url[8:].split('.html')[0]) except ValueError: pass else: diff --git a/sphinx/web/serve.py b/sphinx/web/serve.py index a1e7d78b4..6a94e1c37 100644 --- a/sphinx/web/serve.py +++ b/sphinx/web/serve.py @@ -63,7 +63,7 @@ def restart_with_reloader(): 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': thread.start_new_thread(main_func, ()) diff --git a/sphinx/web/userdb.py b/sphinx/web/userdb.py index f9eefd92d..39e6479bc 100644 --- a/sphinx/web/userdb.py +++ b/sphinx/web/userdb.py @@ -10,11 +10,10 @@ :copyright: 2007-2008 by Armin Ronacher. :license: BSD. """ -from __future__ import with_statement + from os import path from hashlib import sha1 from random import choice, randrange -from collections import defaultdict def gen_password(length=8, add_numbers=True, mix_case=True, @@ -56,17 +55,19 @@ class UserDatabase(object): def __init__(self, filename): self.filename = filename self.users = {} - self.privileges = defaultdict(set) + self.privileges = {} if path.exists(filename): - with file(filename) as f: + f = open(filename) + try: for line in f: line = line.strip() if line and line[0] != '#': parts = line.split(':') self.users[parts[0]] = parts[1] - self.privileges[parts[0]].update(x for x in - parts[2].split(',') - if x) + self.privileges.setdefault(parts[0], set()).update( + x for x in parts[2].split(',') if x) + finally: + f.close() def set_password(self, user, password): """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() def save(self): - with file(self.filename, 'w') as f: + f = open(self.filename, 'w') + try: for username, password in self.users.iteritems(): privileges = ','.join(self.privileges.get(username, ())) f.write('%s:%s:%s\n' % (username, password, privileges)) + finally: + f.close() diff --git a/sphinx/web/util.py b/sphinx/web/util.py index 1723f122b..c69c96dfd 100644 --- a/sphinx/web/util.py +++ b/sphinx/web/util.py @@ -8,22 +8,21 @@ :copyright: 2007-2008 by Georg Brandl. :license: BSD. """ -from __future__ import with_statement import re from os import path -from ..util import relative_uri -from .._jinja import Environment, FileSystemLoader +from sphinx.util import relative_uri +from sphinx._jinja import Environment, FileSystemLoader def get_target_uri(source_filename): - """Get the web-URI for a given reST file name.""" - if source_filename == 'index.rst': + """Get the web-URI for a given reST file name (without extension).""" + if source_filename == 'index': return '' - if source_filename.endswith('/index.rst'): - return source_filename[:-9] # up to / - return source_filename[:-4] + '/' + if source_filename.endswith('/index'): + return source_filename[:-5] # up to / + return source_filename + '/' # ------------------------------------------------------------------------------ diff --git a/sphinx/web/webconf.py b/sphinx/web/webconf.py index 97377a878..8a567c451 100644 --- a/sphinx/web/webconf.py +++ b/sphinx/web/webconf.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Python documentation web application configuration file +# Sphinx documentation web application configuration file # # Where the server listens. diff --git a/sphinx/web/wsgiutil.py b/sphinx/web/wsgiutil.py index 3deccbee1..9a74d92c8 100644 --- a/sphinx/web/wsgiutil.py +++ b/sphinx/web/wsgiutil.py @@ -9,7 +9,6 @@ :copyright: 2007-2008 by Armin Ronacher. :license: BSD. """ -from __future__ import with_statement import cgi import urllib @@ -23,8 +22,8 @@ from hashlib import sha1 from datetime import datetime from cStringIO import StringIO -from .util import lazy_property -from ..util.json import dump_json +from sphinx.web.util import lazy_property +from sphinx.util.json import dump_json HTTP_STATUS_CODES = { @@ -371,8 +370,11 @@ class Session(dict): self.sid = sid if sid is not None: if path.exists(self.filename): - with file(self.filename, 'rb') as f: + f = open(self.filename, 'rb') + try: self.update(pickle.load(f)) + finally: + f.close() self._orig = dict(self) @property @@ -387,8 +389,11 @@ class Session(dict): def save(self): if self.sid is None: 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) + finally: + f.close() self._orig = dict(self) @@ -669,8 +674,11 @@ class SharedDataMiddleware(object): start_response('200 OK', [('Content-Type', mime_type), ('Cache-Control', 'public'), ('Expires', expiry)]) - with open(filename, 'rb') as f: + f = open(filename, 'rb') + try: return [f.read()] + finally: + f.close() def __call__(self, environ, start_response): p = environ.get('PATH_INFO', '')