mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
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:
@@ -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
|
||||
|
||||
@@ -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
10
sphinx/addons/__init__.py
Normal 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
50
sphinx/addons/ifconfig.py
Normal 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)
|
||||
95
sphinx/addons/refcounting.py
Normal file
95
sphinx/addons/refcounting.py
Normal 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
180
sphinx/application.py
Normal 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)
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 -----------------------------------------------
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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("’", str)
|
||||
str = double_quote_start_re.sub("”", str)
|
||||
s = single_quote_start_re.sub("’", s)
|
||||
s = double_quote_start_re.sub("”", 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("“‘", str)
|
||||
str = single_quote_sets_re.sub("‘“", str)
|
||||
s = double_quote_sets_re.sub("“‘", s)
|
||||
s = single_quote_sets_re.sub("‘“", s)
|
||||
|
||||
# Special case for decade abbreviations (the '80s):
|
||||
str = decade_abbr_re.sub("’", str)
|
||||
s = decade_abbr_re.sub("’", s)
|
||||
|
||||
str = opening_single_quotes_regex.sub(r"\1‘", str)
|
||||
str = closing_single_quotes_regex.sub(r"\1’", str)
|
||||
str = closing_single_quotes_regex_2.sub(r"\1’\2", str)
|
||||
s = opening_single_quotes_regex.sub(r"\1‘", s)
|
||||
s = closing_single_quotes_regex.sub(r"\1’", s)
|
||||
s = closing_single_quotes_regex_2.sub(r"\1’\2", s)
|
||||
|
||||
# Any remaining single quotes should be opening ones:
|
||||
str = str.replace("'", "‘")
|
||||
s = s.replace("'", "‘")
|
||||
|
||||
str = opening_double_quotes_regex.sub(r"\1“", str)
|
||||
str = closing_double_quotes_regex.sub(r"”", str)
|
||||
str = closing_double_quotes_regex_2.sub(r"\1”", str)
|
||||
s = opening_double_quotes_regex.sub(r"\1“", s)
|
||||
s = closing_double_quotes_regex.sub(r"”", s)
|
||||
s = closing_double_quotes_regex_2.sub(r"\1”", s)
|
||||
|
||||
# Any remaining quotes should be opening ones.
|
||||
str = str.replace('"', "“")
|
||||
|
||||
return str
|
||||
return s.replace('"', "“")
|
||||
|
||||
|
||||
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: “Isn't this fun?”
|
||||
"""
|
||||
return str.replace("``", "“").replace("''", "”")
|
||||
return s.replace("``", "“").replace("''", "”")
|
||||
|
||||
|
||||
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: ‘Isn’t this fun?’
|
||||
"""
|
||||
return str.replace('`', "‘").replace("'", "’")
|
||||
return s.replace('`', "‘").replace("'", "’")
|
||||
|
||||
|
||||
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('---', "—").replace('--', "–")
|
||||
return s.replace('---', "—").replace('--', "–")
|
||||
|
||||
|
||||
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('---', "–").replace('--', "—")
|
||||
return s.replace('---', "–").replace('--', "—")
|
||||
|
||||
|
||||
|
||||
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…?
|
||||
"""
|
||||
return str.replace('...', "…").replace('. . .', "…")
|
||||
return s.replace('...', "…").replace('. . .', "…")
|
||||
|
||||
|
||||
__author__ = "Chad Miller <smartypantspy@chad.org>"
|
||||
|
||||
Reference in New Issue
Block a user