More refactoring:

* Move refcounting into an addon module.
* Rename the extension manager to Application and use it throughout.
* Fix some bugs found by pylint.
* Add "ifconfig" addon.
This commit is contained in:
Georg Brandl
2008-01-27 20:23:25 +00:00
parent cf0dcada71
commit 4f7a8f3f96
16 changed files with 487 additions and 369 deletions

View File

@@ -15,55 +15,19 @@ import getopt
from os import path
from cStringIO import StringIO
from sphinx.config import Config, ConfigError
from sphinx.builder import builders
from sphinx.extension import EventManager
from sphinx.application import Application
from sphinx.util.console import nocolor
__version__ = '$Revision: 5369 $'[11:-2]
def init_builder(buildername, srcdirname, outdirname, doctreedir,
confoverrides, status, warning=sys.stderr, freshenv=False):
# read config
config = Config(srcdirname, 'conf.py')
if confoverrides:
for key, val in confoverrides.items():
setattr(config, key, val)
# extensibility
events = EventManager()
for extension in config.extensions:
try:
mod = __import__(extension, None, None, ['setup'])
except ImportError, err:
raise ConfigError('Could not import extension %s' % module, err)
if hasattr(mod, 'setup'):
mod.setup(events, builders)
if buildername not in builders:
print >>warning, 'Builder name %s not registered' % buildername
return None
if buildername is None:
print >>status, 'No builder selected, using default: html'
buildername = 'html'
builderclass = builders[buildername]
builder = builderclass(srcdirname, outdirname, doctreedir,
status_stream=status, warning_stream=warning,
events=events, config=config, freshenv=freshenv)
events.emit('builder-created', builder)
return builder
def usage(argv, msg=None):
if msg:
print >>sys.stderr, msg
print >>sys.stderr
print >>sys.stderr, """\
usage: %s [options] sourcedir outdir [filenames...]"
options: -b <builder> -- builder to use (one of %s)
options: -b <builder> -- builder to use; default is html
-a -- write all files; default is to only write new and changed files
-E -- don't use a saved environment, always read all files
-d <path> -- path for the cached environment and doctree files
@@ -75,7 +39,7 @@ options: -b <builder> -- builder to use (one of %s)
modi:
* without -a and without filenames, write new and changed files.
* with -a, write all files.
* with filenames, write these.""" % (argv[0], ', '.join(builders))
* with filenames, write these.""" % (argv[0],)
def main(argv):
@@ -85,15 +49,15 @@ def main(argv):
try:
opts, args = getopt.getopt(argv[1:], 'ab:d:D:NEqP')
srcdirname = path.abspath(args[0])
if not path.isdir(srcdirname):
srcdir = path.abspath(args[0])
if not path.isdir(srcdir):
print >>sys.stderr, 'Error: Cannot find source directory.'
return 1
if not path.isfile(path.join(srcdirname, 'conf.py')):
if not path.isfile(path.join(srcdir, 'conf.py')):
print >>sys.stderr, 'Error: Source directory doesn\'t contain conf.py file.'
return 1
outdirname = path.abspath(args[1])
if not path.isdir(outdirname):
outdir = path.abspath(args[1])
if not path.isdir(outdir):
print >>sys.stderr, 'Error: Cannot find output directory.'
return 1
except (IndexError, getopt.error):
@@ -113,7 +77,7 @@ def main(argv):
freshenv = use_pdb = False
status = sys.stdout
confoverrides = {}
doctreedir = path.join(outdirname, '.doctrees')
doctreedir = path.join(outdir, '.doctrees')
for opt, val in opts:
if opt == '-b':
buildername = val
@@ -139,18 +103,18 @@ def main(argv):
elif opt == '-P':
use_pdb = True
builder = init_builder(buildername, srcdirname, outdirname, doctreedir,
confoverrides, status, sys.stderr, freshenv)
if not builder:
app = Application(srcdir, outdir, doctreedir, buildername,
confoverrides, status, sys.stderr, freshenv)
if not app.builder:
return 1
try:
if all_files:
builder.build_all()
app.builder.build_all()
elif filenames:
builder.build_specific(filenames)
app.builder.build_specific(filenames)
else:
builder.build_update()
app.builder.build_update()
except:
if not use_pdb:
raise

View File

@@ -29,9 +29,6 @@ class desc_optional(nodes.Part, nodes.Inline, nodes.TextElement):
def astext(self):
return '[' + nodes.TextElement.astext(self) + ']'
# refcount annotation
class refcount(nodes.emphasis): pass
# \versionadded, \versionchanged, \deprecated
class versionmodified(nodes.Admonition, nodes.TextElement): pass

10
sphinx/addons/__init__.py Normal file
View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
"""
sphinx.addons
~~~~~~~~~~~~~
Contains Sphinx features not activated by default.
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""

50
sphinx/addons/ifconfig.py Normal file
View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
"""
sphinx.addons.ifconfig
~~~~~~~~~~~~~~~~~~~~~~
Provides the ``ifconfig`` directive that allows to write documentation
that is included depending on configuration variables.
Usage::
.. ifconfig:: releaselevel in ('alpha', 'beta', 'rc')
This stuff is only included in the built docs for unstable versions.
The argument for ``ifconfig`` is a plain Python expression, evaluated in the
namespace of the project configuration (that is, all variables from ``conf.py``
are available.)
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
from docutils import nodes
class ifconfig(nodes.Element): pass
def ifconfig_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
node = ifconfig()
node['expr'] = arguments[0]
state.nested_parse(content, content_offset, node)
return [node]
def process_ifconfig_nodes(app, doctree, docfilename):
ns = app.config.__dict__.copy()
ns['builder'] = app.builder.name
for node in doctree.traverse(ifconfig):
if not eval(node['expr'], ns):
node.replace_self([])
else:
node.replace_self(node.children)
def setup(app):
app.add_node(ifconfig)
app.add_directive('ifconfig', ifconfig_directive, 1, (1, 0, 1))
app.connect('doctree-resolved', process_ifconfig_nodes)

View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
"""
sphinx.addons.refcounting
~~~~~~~~~~~~~~~~~~~~~~~~~
Supports reference count annotations for C API functions. Based on
refcount.py and anno-api.py in the old Python documentation tools.
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
from os import path
from docutils import nodes
from sphinx import addnodes
# refcount annotation
class refcount(nodes.emphasis): pass
class RCEntry:
def __init__(self, name):
self.name = name
self.args = []
self.result_type = ''
self.result_refs = None
class Refcounts(dict):
@classmethod
def fromfile(cls, filename):
d = cls()
fp = open(filename, 'r')
try:
for line in fp:
line = line.strip()
if line[:1] in ("", "#"):
# blank lines and comments
continue
parts = line.split(":", 4)
if len(parts) != 5:
raise ValueError("Wrong field count in %r" % line)
function, type, arg, refcount, comment = parts
# Get the entry, creating it if needed:
try:
entry = d[function]
except KeyError:
entry = d[function] = RCEntry(function)
if not refcount or refcount == "null":
refcount = None
else:
refcount = int(refcount)
# Update the entry with the new parameter or the result information.
if arg:
entry.args.append((arg, type, refcount))
else:
entry.result_type = type
entry.result_refs = refcount
finally:
fp.close()
return d
def add_refcount_annotations(self, app, doctree):
for node in doctree.traverse(addnodes.desc_content):
par = node.parent
if par['desctype'] != 'cfunction':
continue
if not par[0].has_key('names') or not par[0]['names']:
continue
entry = self.get(par[0]['names'][0])
if not entry:
continue
elif entry.result_type not in ("PyObject*", "PyVarObject*"):
continue
rc = 'Return value: '
if entry.result_refs is None:
rc += "Always NULL."
else:
rc += (entry.result_refs and "New" or "Borrowed") + " reference."
node.insert(0, refcount(rc, rc))
def init_refcounts(app):
if app.config.refcount_file:
refcounts = Refcounts.fromfile(
path.join(app.srcdir, app.config.refcount_file))
app.connect('doctree-read', refcounts.add_refcount_annotations)
def setup(app):
app.add_node(refcount)
app.add_config_value('refcount_file', '', True)
app.connect('builder-inited', init_refcounts)

180
sphinx/application.py Normal file
View File

@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
"""
sphinx.application
~~~~~~~~~~~~~~~~~~
Sphinx application object.
Gracefully adapted from the TextPress system by Armin.
:copyright: 2008 by Georg Brandl, Armin Ronacher.
:license: BSD.
"""
import sys
from docutils import nodes
from docutils.parsers.rst import directives, roles
from sphinx.config import Config
from sphinx.builder import builtin_builders
class ExtensionError(Exception):
"""Raised if something's wrong with the configuration."""
def __init__(self, message, orig_exc=None):
self.message = message
self.orig_exc = orig_exc
def __repr__(self):
if self.orig_exc:
return '%s(%r, %r)' % (self.__class__.__name__,
self.message, self.orig_exc)
return '%s(%r)' % (self.__class__.__name__, self.message)
def __str__(self):
if self.orig_exc:
return '%s (exception: %s)' % (self.message, self.orig_exc)
return self.message
# List of all known events. Maps name to arguments description.
events = {
'builder-inited': 'builder instance',
'doctree-read' : 'the doctree before being pickled',
'doctree-resolved' : 'the doctree, the filename, the builder',
}
class Application(object):
def __init__(self, srcdir, outdir, doctreedir, buildername,
confoverrides, status, warning=sys.stderr, freshenv=False):
self.next_listener_id = 0
self._listeners = {}
self.builderclasses = builtin_builders.copy()
self.builder = None
self.srcdir = srcdir
self.outdir = outdir
self.doctreedir = doctreedir
self._status = status
self._warning = warning
# read config
self.config = Config(srcdir, 'conf.py')
if confoverrides:
for key, val in confoverrides.items():
setattr(self.config, key, val)
# load all extension modules
for extension in getattr(self.config, 'extensions', ()):
self.setup_extension(extension)
# this must happen after loading extension modules, since they
# can add custom config values
self.config.init_defaults()
if buildername is None:
print >>status, 'No builder selected, using default: html'
buildername = 'html'
if buildername not in self.builderclasses:
print >>warning, 'Builder name %s not registered' % buildername
return
builderclass = self.builderclasses[buildername]
self.builder = builderclass(self, freshenv=freshenv)
self.emit('builder-inited')
def warn(self, message):
self._warning.write('WARNING: %s\n' % message)
def info(self, message='', nonl=False):
if nonl:
self._status.write(message)
else:
self._status.write(message + '\n')
self._status.flush()
# general extensibility interface
def setup_extension(self, extension):
"""Import and setup a Sphinx extension module."""
try:
mod = __import__(extension, None, None, ['setup'])
except ImportError, err:
raise ExtensionError('Could not import extension %s' % extension, err)
if hasattr(mod, 'setup'):
mod.setup(self)
def import_object(self, objname, source=None):
"""Import an object from a 'module.name' string."""
try:
module, name = objname.rsplit('.', 1)
except ValueError, err:
raise ExtensionError('Invalid full object name %s' % objname +
(source and ' (needed for %s)' % source or ''), err)
try:
return getattr(__import__(module, None, None, [name]), name)
except ImportError, err:
raise ExtensionError('Could not import %s' % module +
(source and ' (needed for %s)' % source or ''), err)
except AttributeError, err:
raise ExtensionError('Could not find %s' % objname +
(source and ' (needed for %s)' % source or ''), err)
# event interface
def _validate_event(self, event):
event = intern(event)
if event not in events:
raise ExtensionError('Unknown event name: %s' % event)
def connect(self, event, callback):
self._validate_event(event)
listener_id = self.next_listener_id
if event not in self._listeners:
self._listeners[event] = {listener_id: callback}
else:
self._listeners[event][listener_id] = callback
self.next_listener_id += 1
return listener_id
def disconnect(self, listener_id):
for event in self._listeners:
event.pop(listener_id, None)
def emit(self, event, *args):
result = []
if event in self._listeners:
for _, callback in self._listeners[event].iteritems():
result.append(callback(self, *args))
return result
# registering addon parts
def add_builder(self, builder):
if not hasattr(builder, 'name'):
raise ExtensionError('Builder class %s has no "name" attribute' % builder)
if builder.name in self.builderclasses:
raise ExtensionError('Builder %r already exists (in module %s)' % (
builder.name, self.builderclasses[builder.name].__module__))
self.builderclasses[builder.name] = builder
def add_config_value(self, name, default, rebuild_env):
if name in self.config.values:
raise ExtensionError('Config value %r already present')
self.config.values[name] = (default, rebuild_env)
def add_node(self, node):
nodes._add_node_class_names([node.__name__])
def add_directive(self, name, cls, content, arguments):
cls.content = content
cls.arguments = arguments
directives.register_directive(name, cls)
def add_role(self, name, role):
roles.register_canonical_role(name, role)

View File

@@ -10,12 +10,10 @@
"""
import os
import sys
import time
import codecs
import shutil
import cPickle as pickle
import cStringIO as StringIO
from os import path
from cgi import escape
@@ -23,18 +21,16 @@ 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 docutils.readers.doctree import Reader as DoctreeReader
from sphinx import addnodes
from sphinx.util import (get_matching_files, attrdict, status_iterator,
ensuredir, relative_uri, os_path, SEP)
from sphinx.util import (get_matching_files, ensuredir, relative_uri, os_path, SEP)
from sphinx.htmlhelp import build_hhx
from sphinx.extension import DummyEventManager, import_object
from sphinx.htmlwriter import HTMLWriter, HTMLTranslator, SmartyPantsHTMLTranslator
from sphinx.latexwriter import LaTeXWriter
from sphinx.environment import BuildEnvironment, NoUri
from sphinx.highlighting import pygments, highlight_block, get_stylesheet
from sphinx.highlighting import pygments, get_stylesheet
from sphinx.util.console import bold, purple, green
# side effect: registers roles and directives
@@ -49,40 +45,26 @@ class Builder(object):
Builds target formats from the reST sources.
"""
def __init__(self, srcdirname, outdirname, doctreedirname,
config, env=None, freshenv=False, events=None,
status_stream=None, warning_stream=None):
self.srcdir = srcdirname
self.outdir = outdirname
self.doctreedir = doctreedirname
if not path.isdir(doctreedirname):
os.mkdir(doctreedirname)
self.freshenv = freshenv
def __init__(self, app, env=None, freshenv=False):
self.srcdir = app.srcdir
self.outdir = app.outdir
self.doctreedir = app.doctreedir
if not path.isdir(self.doctreedir):
os.mkdir(self.doctreedir)
self.status_stream = status_stream or sys.stdout
self.warning_stream = warning_stream or sys.stderr
self.app = app
self.warn = app.warn
self.info = app.info
self.config = app.config
self.config = config
# if None, this is set in load_env()
self.env = env
self.events = events or DummyEventManager()
self.freshenv = freshenv
self.init()
# helper methods
def msg(self, message='', nonl=False, nobold=False):
if not nobold: message = bold(message)
if nonl:
print >>self.status_stream, message,
else:
print >>self.status_stream, message
self.status_stream.flush()
def warn(self, message):
print >>self.warning_stream, 'WARNING:', message
def init(self):
"""Load necessary templates and perform initialization."""
raise NotImplementedError
@@ -122,6 +104,17 @@ class Builder(object):
"""Return a list of output files that are outdated."""
raise NotImplementedError
def status_iterator(self, iterable, summary, colorfunc):
l = -1
for item in iterable:
if l == -1:
self.info(bold(summary), nonl=1)
l = 0
self.info(colorfunc(item) + ' ', nonl=1)
yield item
if l == 0:
self.info()
# build methods
def load_env(self):
@@ -131,12 +124,12 @@ class Builder(object):
return
if not self.freshenv:
try:
self.msg('trying to load pickled env...', nonl=True)
self.info(bold('trying to load pickled env... '), nonl=True)
self.env = BuildEnvironment.frompickle(
path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.msg('done', nobold=True)
self.info('done')
except Exception, err:
self.msg('failed: %s' % err, nobold=True)
self.info('failed: %s' % err)
self.env = BuildEnvironment(self.srcdir, self.doctreedir)
else:
self.env = BuildEnvironment(self.srcdir, self.doctreedir)
@@ -161,7 +154,7 @@ class Builder(object):
self.load_env()
to_build = self.get_outdated_files()
if not to_build:
self.msg('no target files are out of date, exiting.')
self.info(bold('no target files are out of date, exiting.'))
return
if isinstance(to_build, str):
self.build([], to_build)
@@ -173,36 +166,32 @@ class Builder(object):
def build(self, filenames, summary=None):
if summary:
self.msg('building [%s]:' % self.name, nonl=1)
self.msg(summary, nobold=1)
self.info(bold('building [%s]: ' % self.name), nonl=1)
self.info(summary)
updated_filenames = []
# while reading, collect all warnings from docutils
warnings = []
self.env.set_warnfunc(warnings.append)
self.msg('reading, updating environment:', nonl=1)
iterator = self.env.update(
self.config,
hook=lambda doctree: self.events.emit('doctree-read', doctree))
self.msg(iterator.next(), nonl=1, nobold=1)
for filename in iterator:
if not updated_filenames:
self.msg('')
self.info(bold('updating environment: '), nonl=1)
iterator = self.env.update(self.config, self.app)
# the first item in the iterator is a summary message
self.info(iterator.next())
for filename in self.status_iterator(iterator, 'reading... ', purple):
updated_filenames.append(filename)
self.msg(purple(filename), nonl=1, nobold=1)
self.msg()
# nothing further to do, the environment has already done the reading
for warning in warnings:
self.warn(warning)
self.env.set_warnfunc(self.warn)
if updated_filenames:
# save the environment
self.msg('pickling the env...', nonl=True)
self.info(bold('pickling the env... '), nonl=True)
self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.msg('done', nobold=True)
self.info('done')
# global actions
self.msg('checking consistency...')
self.info(bold('checking consistency...'))
self.env.check_consistency()
# another indirection to support methods which don't build files
@@ -210,9 +199,9 @@ class Builder(object):
self.write(filenames, updated_filenames)
# finish (write style files etc.)
self.msg('finishing...')
self.info(bold('finishing... '))
self.finish()
self.msg('done!')
self.info(bold('build succeeded.'))
def write(self, build_filenames, updated_filenames):
if build_filenames is None: # build_all
@@ -225,16 +214,15 @@ class Builder(object):
filenames.add(tocfilename)
filenames.add('contents.rst')
self.msg('creating index...')
self.info(bold('creating index...'))
self.env.create_index(self)
self.prepare_writing(filenames)
# write target files
warnings = []
self.env.set_warnfunc(warnings.append)
self.msg('writing output...')
for filename in status_iterator(sorted(filenames), green,
stream=self.status_stream):
for filename in self.status_iterator(sorted(filenames),
'writing output... ', green):
doctree = self.env.get_and_resolve_doctree(filename, self)
self.write_file(filename, doctree)
for warning in warnings:
@@ -263,8 +251,8 @@ class StandaloneHTMLBuilder(Builder):
"""Load templates."""
self.init_templates()
if self.config.html_translator_class:
self.translator_class = import_object(self.config.html_translator_class,
'html_translator_class setting')
self.translator_class = self.app.import_object(
self.config.html_translator_class, 'html_translator_class setting')
elif self.config.html_use_smartypants:
self.translator_class = SmartyPantsHTMLTranslator
else:
@@ -277,7 +265,7 @@ class StandaloneHTMLBuilder(Builder):
return publish_parts(
doc,
source_class=DocTreeInput,
reader=doctree.Reader(),
reader=DoctreeReader(),
writer=HTMLWriter(self),
settings_overrides={'output_encoding': 'unicode'}
)
@@ -316,7 +304,7 @@ class StandaloneHTMLBuilder(Builder):
destination = StringOutput(encoding='utf-8')
doctree.settings = self.docsettings
output = self.docwriter.write(doctree, destination)
self.docwriter.write(doctree, destination)
self.docwriter.assemble_parts()
prev = next = None
@@ -360,14 +348,14 @@ class StandaloneHTMLBuilder(Builder):
self.handle_page(pagename, context)
def finish(self):
self.msg('writing additional files...')
self.info(bold('writing additional files...'))
# the global general index
# the total count of lines for each index letter, used to distribute
# the entries into two columns
indexcounts = []
for key, entries in self.env.index:
for _, entries in self.env.index:
indexcounts.append(sum(1 + len(subitems) for _, (_, subitems) in entries))
genindexcontext = dict(
@@ -434,7 +422,7 @@ class StandaloneHTMLBuilder(Builder):
self.handle_page('index', {'indextemplate': indextemplate}, 'index.html')
# copy style files
self.msg('copying style files...')
self.info(bold('copying style files...'))
styledirname = path.join(path.dirname(__file__), 'style')
ensuredir(path.join(self.outdir, 'style'))
for filename in os.listdir(styledirname):
@@ -519,7 +507,7 @@ class StandaloneHTMLBuilder(Builder):
path.join(self.outdir, os_path(ctx['sourcename'])))
def handle_finish(self):
self.msg('dumping search index...')
self.info(bold('dumping search index...'))
self.indexer.prune([fn[:-4] for fn in self.env.all_files])
f = open(path.join(self.outdir, 'searchindex.json'), 'w')
try:
@@ -576,7 +564,7 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
def handle_page(self, pagename, context, templatename='page.html'):
context['current_page_name'] = pagename
sidebarfile = self.confightml_sidebars.get(pagename, '')
sidebarfile = self.config.html_sidebars.get(pagename, '')
if sidebarfile:
context['customsidebar'] = path.join(self.srcdir, sidebarfile)
outfilename = path.join(self.outdir, os_path(pagename) + '.fpickle')
@@ -603,7 +591,7 @@ class WebHTMLBuilder(StandaloneHTMLBuilder):
finally:
f.close()
self.msg('dumping search index...')
self.info(bold('dumping search index...'))
self.indexer.prune(self.env.all_files)
f = open(path.join(self.outdir, 'searchindex.pickle'), 'wb')
try:
@@ -698,7 +686,7 @@ class LaTeXBuilder(Builder):
doctree.settings.author = author
doctree.settings.filename = sourcename
doctree.settings.docclass = docclass
output = docwriter.write(doctree, destination)
docwriter.write(doctree, destination)
print "done"
def assemble_doctree(self, indexfile, appendices):
@@ -746,7 +734,7 @@ class LaTeXBuilder(Builder):
return largetree
def finish(self):
self.msg('copying TeX support files...')
self.info(bold('copying TeX support files...'))
styledirname = path.join(path.dirname(__file__), 'texinputs')
for filename in os.listdir(styledirname):
if not filename.startswith('.'):
@@ -780,7 +768,7 @@ class ChangesBuilder(Builder):
libchanges = {}
apichanges = []
otherchanges = {}
self.msg('writing summary file...')
self.info(bold('writing summary file...'))
for type, filename, lineno, module, descname, content in \
self.env.versionchanges[version]:
ttext = self.typemap[type]
@@ -841,7 +829,7 @@ class ChangesBuilder(Builder):
break
return line
self.msg('copying source files...')
self.info(bold('copying source files...'))
for filename in self.env.all_files:
f = open(path.join(self.srcdir, os_path(filename)))
lines = f.readlines()
@@ -868,7 +856,7 @@ class ChangesBuilder(Builder):
pass
builders = {
builtin_builders = {
'html': StandaloneHTMLBuilder,
'web': WebHTMLBuilder,
'htmlhelp': HTMLHelpBuilder,

View File

@@ -10,29 +10,10 @@
"""
import os
import sys
import types
from os import path
class ConfigError(Exception):
"""Raised if something's wrong with the configuration."""
def __init__(self, message, orig_exc=None):
self.message = message
self.orig_exc = orig_exc
def __repr__(self):
if self.orig_exc:
return 'ConfigError(%r, %r)' % (self.message, self.orig_exc)
return 'ConfigError(%r)' % self.message
def __str__(self):
if self.orig_exc:
return '%s (exception: %s)' % (self.message, self.orig_exc)
return self.message
class Config(object):
"""Configuration file abstraction."""
@@ -53,7 +34,6 @@ class Config(object):
# general reading options
unused_files = ([], True),
refcount_file = ('', True),
add_function_parentheses = (True, True),
add_module_names = (True, True),
@@ -77,6 +57,7 @@ class Config(object):
)
def __init__(self, dirname, filename):
self.values = self.config_values.copy()
config = {}
olddir = os.getcwd()
try:
@@ -90,14 +71,10 @@ class Config(object):
del config[key]
self.__dict__.update(config)
def __getattr__(self, name):
if name in self.config_values:
defval = self.config_values[name][0]
setattr(self, name, defval)
return defval
if name[0:1] == '_':
return object.__getattr__(self, name)
raise AttributeError('no configuration value named %r' % name)
def init_defaults(self):
for val in self.values:
if val not in self.__dict__:
self.__dict__[val] = self.values[val][0]
def __getitem__(self, name):
return getattr(self, name)

View File

@@ -15,7 +15,7 @@ import posixpath
from os import path
from docutils import nodes
from docutils.parsers.rst import directives, roles
from docutils.parsers.rst import directives
from docutils.parsers.rst.directives import admonitions
from sphinx import addnodes
@@ -273,21 +273,6 @@ def parse_option_desc(signode, sig):
return optname
def add_refcount_annotation(env, node, name):
"""Add a reference count annotation. Return None."""
entry = env.refcounts.get(name)
if not entry:
return
elif entry.result_type not in ("PyObject*", "PyVarObject*"):
return
rc = 'Return value: '
if entry.result_refs is None:
rc += "Always NULL."
else:
rc += (entry.result_refs and "New" or "Borrowed") + " reference."
node += addnodes.refcount(rc, rc)
def desc_directive(desctype, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
env = state.document.settings.env
@@ -361,8 +346,6 @@ def desc_directive(desctype, arguments, options, content, lineno,
fullname, fullname)
subnode = addnodes.desc_content()
if desctype == 'cfunction':
add_refcount_annotation(env, subnode, name)
# needed for automatic qualification of members
clsname_set = False
if desctype == 'class' and names:
@@ -407,8 +390,8 @@ desctypes = [
'describe',
]
for name in desctypes:
directives.register_directive(name, desc_directive)
for _name in desctypes:
directives.register_directive(_name, desc_directive)
# ------ versionadded/versionchanged -----------------------------------------------

View File

@@ -44,7 +44,6 @@ Body.enum.converters['loweralpha'] = \
from sphinx import addnodes
from sphinx.util import get_matching_files, os_path, SEP
from sphinx.refcounting import Refcounts
default_settings = {
'embed_stylesheet': False,
@@ -128,7 +127,7 @@ class MoveModuleTargets(Transform):
class MyStandaloneReader(standalone.Reader):
"""
Add our own Substitutions transform.
Add our own transforms.
"""
def get_transforms(self):
tf = standalone.Reader.get_transforms(self)
@@ -189,9 +188,6 @@ class BuildEnvironment:
self.srcdir = srcdir
self.config = None
# refcount data if present
self.refcounts = {}
# the docutils settings for building
self.settings = default_settings.copy()
self.settings['env'] = self
@@ -314,7 +310,7 @@ class BuildEnvironment:
return added, changed, removed
def update(self, config, hook=None):
def update(self, config, app=None):
"""(Re-)read all files new or changed since last update. Yields a summary
and then filenames as it processes them. Store all environment filenames
in the canonical format (ie using SEP as a separator in place of
@@ -341,11 +337,6 @@ class BuildEnvironment:
self.config = config
# read the refcounts file
if self.config.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)
@@ -353,14 +344,14 @@ class BuildEnvironment:
# read all new and changed files
for filename in added + changed:
yield filename
self.read_file(filename)
self.read_file(filename, app=app)
if 'contents.rst' not in self.all_files:
self._warnfunc('no master file contents.rst found')
# --------- SINGLE FILE BUILDING -------------------------------------------
def read_file(self, filename, src_path=None, save_parsed=True, hook=None):
def read_file(self, filename, src_path=None, save_parsed=True, app=None):
"""Parse a file and add/update inventory entries for the doctree.
If srcpath is given, read from a different source file."""
# remove all inventory entries for that file
@@ -386,9 +377,8 @@ class BuildEnvironment:
f.close()
self.all_files[filename] = (path.getmtime(src_path), md5sum)
# run post-read hook
if hook:
hook(doctree)
if app:
app.emit('doctree-read', doctree)
# make it picklable
doctree.reporter = None
@@ -590,7 +580,7 @@ class BuildEnvironment:
for includefile in includefiles:
try:
toc = self.tocs[includefile].deepcopy()
except KeyError, err:
except KeyError:
# this is raised if the included file does not exist
self._warnfunc('%s: toctree contains ref to nonexisting '
'file %r' % (filename, includefile))
@@ -622,6 +612,9 @@ class BuildEnvironment:
return doctree
descroles = frozenset(('data', 'exc', 'func', 'class', 'const', 'attr',
'meth', 'cfunc', 'cdata', 'ctype', 'cmacro'))
def resolve_references(self, doctree, docfilename, builder):
for node in doctree.traverse(addnodes.pending_xref):
contnode = node[0].deepcopy()
@@ -702,7 +695,7 @@ class BuildEnvironment:
(platform and '(%s) ' % platform),
synopsis, (deprecated and ' (deprecated)' or ''))
newnode.append(contnode)
else:
elif typ in self.descroles:
# "descrefs"
modname = node['modname']
clsname = node['classname']
@@ -720,11 +713,16 @@ class BuildEnvironment:
builder.get_relative_uri(docfilename, desc[0])
+ '#' + name)
newnode.append(contnode)
else:
raise RuntimeError('unknown xfileref node encountered: %s' % node)
except NoUri:
newnode = contnode
if newnode:
node.replace_self(newnode)
# allow custom references to be resolved
builder.app.emit('doctree-resolved', doctree, docfilename)
def create_index(self, builder, _fixre=re.compile(r'(.*) ([(][^()]*[)])')):
"""Create the real index from the collected index entries."""
new = {}

View File

@@ -1,83 +0,0 @@
# -*- coding: utf-8 -*-
"""
sphinx.extension
~~~~~~~~~~~~~~~~
Gracefully adapted from the TextPress event system by Armin.
:copyright: 2008 by Georg Brandl, Armin Ronacher.
:license: BSD.
"""
from sphinx.config import ConfigError
def import_object(objname, source=None):
"""Import an object from a 'module.name' string."""
try:
module, name = objname.rsplit('.', 1)
except ValueError, err:
raise ConfigError('Invalid full object name %s' % objname +
(source and ' (needed for %s)' % source or ''), err)
try:
return getattr(__import__(module, None, None, [name]), name)
except ImportError, err:
raise ConfigError('Could not import %s' % module +
(source and ' (needed for %s)' % source or ''), err)
except AttributeError, err:
raise ConfigError('Could not find %s' % objname +
(source and ' (needed for %s)' % source or ''), err)
# List of all known events. Maps name to arguments description.
events = {
'builder-created' : 'builder instance',
'doctree-read' : 'the doctree before being pickled',
}
class EventManager(object):
"""
Helper class that handles event listeners and events.
This is *not* a public interface. Always use the emit_event()
functions to access it or the connect_event() / disconnect_event()
functions on the application.
"""
def __init__(self):
self.next_listener_id = 0
self._listeners = {}
def _validate(self, event):
event = intern(event)
if event not in events:
raise RuntimeError('unknown event name: %s' % event)
def connect(self, event, callback):
self._validate(event)
listener_id = self.next_listener_id
if event not in self._listeners:
self._listeners[event] = {listener_id: callback}
else:
self._listeners[event][listener_id] = callback
self.next_listener_id += 1
return listener_id
def remove(self, listener_id):
for event in self._listeners:
event.pop(listener_id, None)
def emit(self, event, *args):
self._validate(event)
if event in self._listeners:
for listener_id, callback in self._listeners[event].iteritems():
yield listener_id, callback(*args)
class DummyEventManager(EventManager):
def connect(self, event, callback):
self._validate(event)
def remove(self, listener_id):
pass
def emit(self, event, *args):
self._validate(event)

View File

@@ -14,9 +14,8 @@
import re
import time
import string
from docutils import frontend, nodes, languages, writers, utils
from docutils import nodes, writers
from sphinx import addnodes
from sphinx import highlighting

View File

@@ -1,53 +0,0 @@
# -*- coding: utf-8 -*-
"""
sphinx.refcounting
~~~~~~~~~~~~~~~~~~
Handle reference counting annotations, based on refcount.py
and anno-api.py.
:copyright: 2007-2008 by Georg Brandl.
:license: BSD.
"""
class RCEntry:
def __init__(self, name):
self.name = name
self.args = []
self.result_type = ''
self.result_refs = None
class Refcounts(dict):
@classmethod
def fromfile(cls, filename):
d = cls()
fp = open(filename, 'r')
try:
for line in fp:
line = line.strip()
if line[:1] in ("", "#"):
# blank lines and comments
continue
parts = line.split(":", 4)
if len(parts) != 5:
raise ValueError("Wrong field count in %r" % line)
function, type, arg, refcount, comment = parts
# Get the entry, creating it if needed:
try:
entry = d[function]
except KeyError:
entry = d[function] = RCEntry(function)
if not refcount or refcount == "null":
refcount = None
else:
refcount = int(refcount)
# Update the entry with the new parameter or the result information.
if arg:
entry.args.append((arg, type, refcount))
else:
entry.result_type = type
entry.result_refs = refcount
finally:
fp.close()
return d

View File

@@ -51,15 +51,6 @@ def ensuredir(path):
raise
def status_iterator(iterable, colorfunc=lambda x: x, stream=sys.stdout):
"""Print out each item before yielding it."""
for item in iterable:
print >>stream, colorfunc(item),
stream.flush()
yield item
print >>stream
def get_matching_files(dirname, pattern, exclude=()):
"""Get all files matching a pattern in a directory, recursively."""
# dirname is a normalized absolute path.

View File

@@ -11,6 +11,30 @@
codes = {}
def get_terminal_width():
"""Borrowed from the py lib."""
try:
import os, termios, fcntl, struct
call = fcntl.ioctl(0, termios.TIOCGWINSZ, "\000"*8)
height, width = struct.unpack("hhhh", call)[:2]
terminal_width = width
except (SystemExit, KeyboardInterrupt):
raise
except:
# FALLBACK
terminal_width = int(os.environ.get('COLUMNS', 80))-1
return terminal_width
_tw = get_terminal_width()
def print_and_backspace(text, func):
if not codes:
# if no coloring, don't output fancy backspaces
func(text)
else:
func(text.ljust(_tw) + _tw * "\b")
def nocolor():
codes.clear()
@@ -31,8 +55,8 @@ _attrs = {
'blink': '05m',
}
for name, value in _attrs.items():
codes[name] = '\x1b[' + value
for _name, _value in _attrs.items():
codes[_name] = '\x1b[' + _value
_colors = [
('black', 'darkgray'),
@@ -49,5 +73,5 @@ for i, (dark, light) in enumerate(_colors):
codes[dark] = '\x1b[%im' % (i+30)
codes[light] = '\x1b[%i;01m' % (i+30)
for name in codes:
create_color_func(name)
for _name in codes:
create_color_func(_name)

View File

@@ -151,7 +151,7 @@ closing_single_quotes_regex_2 = re.compile(r"""
(\s | s\b)
""" % (close_class,), re.VERBOSE)
def educateQuotes(str):
def educateQuotes(s):
"""
Parameter: String.
@@ -163,35 +163,33 @@ def educateQuotes(str):
# Special case if the very first character is a quote
# followed by punctuation at a non-word-break. Close the quotes by brute force:
str = single_quote_start_re.sub("&#8217;", str)
str = double_quote_start_re.sub("&#8221;", str)
s = single_quote_start_re.sub("&#8217;", s)
s = double_quote_start_re.sub("&#8221;", s)
# Special case for double sets of quotes, e.g.:
# <p>He said, "'Quoted' words in a larger quote."</p>
str = double_quote_sets_re.sub("&#8220;&#8216;", str)
str = single_quote_sets_re.sub("&#8216;&#8220;", str)
s = double_quote_sets_re.sub("&#8220;&#8216;", s)
s = single_quote_sets_re.sub("&#8216;&#8220;", s)
# Special case for decade abbreviations (the '80s):
str = decade_abbr_re.sub("&#8217;", str)
s = decade_abbr_re.sub("&#8217;", s)
str = opening_single_quotes_regex.sub(r"\1&#8216;", str)
str = closing_single_quotes_regex.sub(r"\1&#8217;", str)
str = closing_single_quotes_regex_2.sub(r"\1&#8217;\2", str)
s = opening_single_quotes_regex.sub(r"\1&#8216;", s)
s = closing_single_quotes_regex.sub(r"\1&#8217;", s)
s = closing_single_quotes_regex_2.sub(r"\1&#8217;\2", s)
# Any remaining single quotes should be opening ones:
str = str.replace("'", "&#8216;")
s = s.replace("'", "&#8216;")
str = opening_double_quotes_regex.sub(r"\1&#8220;", str)
str = closing_double_quotes_regex.sub(r"&#8221;", str)
str = closing_double_quotes_regex_2.sub(r"\1&#8221;", str)
s = opening_double_quotes_regex.sub(r"\1&#8220;", s)
s = closing_double_quotes_regex.sub(r"&#8221;", s)
s = closing_double_quotes_regex_2.sub(r"\1&#8221;", s)
# Any remaining quotes should be opening ones.
str = str.replace('"', "&#8220;")
return str
return s.replace('"', "&#8220;")
def educateBackticks(str):
def educateBackticks(s):
"""
Parameter: String.
Returns: The string, with ``backticks'' -style double quotes
@@ -199,10 +197,10 @@ def educateBackticks(str):
Example input: ``Isn't this fun?''
Example output: &#8220;Isn't this fun?&#8221;
"""
return str.replace("``", "&#8220;").replace("''", "&#8221;")
return s.replace("``", "&#8220;").replace("''", "&#8221;")
def educateSingleBackticks(str):
def educateSingleBackticks(s):
"""
Parameter: String.
Returns: The string, with `backticks' -style single quotes
@@ -211,10 +209,10 @@ def educateSingleBackticks(str):
Example input: `Isn't this fun?'
Example output: &#8216;Isn&#8217;t this fun?&#8217;
"""
return str.replace('`', "&#8216;").replace("'", "&#8217;")
return s.replace('`', "&#8216;").replace("'", "&#8217;")
def educateDashesOldSchool(str):
def educateDashesOldSchool(s):
"""
Parameter: String.
@@ -222,10 +220,10 @@ def educateDashesOldSchool(str):
an en-dash HTML entity, and each "---" translated to
an em-dash HTML entity.
"""
return str.replace('---', "&#8212;").replace('--', "&#8211;")
return s.replace('---', "&#8212;").replace('--', "&#8211;")
def educateDashesOldSchoolInverted(str):
def educateDashesOldSchoolInverted(s):
"""
Parameter: String.
@@ -240,11 +238,11 @@ def educateDashesOldSchoolInverted(str):
the shortcut should be shorter to type. (Thanks to Aaron
Swartz for the idea.)
"""
return str.replace('---', "&#8211;").replace('--', "&#8212;")
return s.replace('---', "&#8211;").replace('--', "&#8212;")
def educateEllipses(str):
def educateEllipses(s):
"""
Parameter: String.
Returns: The string, with each instance of "..." translated to
@@ -253,7 +251,7 @@ def educateEllipses(str):
Example input: Huh...?
Example output: Huh&#8230;?
"""
return str.replace('...', "&#8230;").replace('. . .', "&#8230;")
return s.replace('...', "&#8230;").replace('. . .', "&#8230;")
__author__ = "Chad Miller <smartypantspy@chad.org>"