diff --git a/doc/Makefile b/doc/Makefile index c09f9168..efc61de2 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -10,7 +10,7 @@ PAPER = PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source - +DEST = build .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @@ -29,7 +29,8 @@ help: @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: - -rm -rf build/* + -rm -rf $(DEST)/* + -rm -rf source/api api: mkdir -p source/api @@ -37,33 +38,33 @@ api: @echo "Build API docs finished." html: api - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DEST)/html @echo @echo "Build finished. The HTML pages are in build/html." dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) build/dirhtml + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(DEST)/dirhtml @echo @echo "Build finished. The HTML pages are in build/dirhtml." pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(DEST)/pickle @echo @echo "Build finished; now you can process the pickle files." json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(DEST)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(DEST)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in build/htmlhelp." qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) build/qthelp + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(DEST)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in build/qthelp, like this:" @@ -72,7 +73,7 @@ qthelp: @echo "# assistant -collectionFile build/qthelp/scikitsimage.qhc" devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) build/devhelp + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(DEST)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @@ -81,30 +82,33 @@ devhelp: @echo "# devhelp" latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(DEST)/latex @echo - @echo "Build finished; the LaTeX files are in build/latex." + @echo "Build finished; the LaTeX files are in $(DEST)/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." latexpdf: latex - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(DEST)/latex @echo "Running LaTeX files through pdflatex..." make -C build/latex all-pdf @echo "pdflatex finished; the PDF files are in build/latex." changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(DEST)/changes @echo @echo "The overview file is in build/changes." linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(DEST)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in build/linkcheck/output.txt." doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) build/doctest + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(DEST)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in build/doctest/output.txt." + +gh-pages: + python gh-pages.py diff --git a/doc/ext/docscrape.py b/doc/ext/docscrape.py index 0e073da9..ad5998cc 100644 --- a/doc/ext/docscrape.py +++ b/doc/ext/docscrape.py @@ -84,7 +84,7 @@ class Reader(object): class NumpyDocString(object): - def __init__(self,docstring): + def __init__(self, docstring, config={}): docstring = textwrap.dedent(docstring).split('\n') self._doc = Reader(docstring) @@ -183,7 +183,7 @@ class NumpyDocString(object): return params - + _name_rgx = re.compile(r"^\s*(:(?P\w+):`(?P[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) def _parse_see_also(self, content): @@ -216,7 +216,7 @@ class NumpyDocString(object): current_func = None rest = [] - + for line in content: if not line.strip(): continue @@ -258,7 +258,7 @@ class NumpyDocString(object): if len(line) > 2: out[line[1]] = strip_each_in(line[2].split(',')) return out - + def _parse_summary(self): """Grab signature (if given) and summary""" if self._is_at_section(): @@ -275,7 +275,7 @@ class NumpyDocString(object): if not self._is_at_section(): self['Extended Summary'] = self._read_to_next_section() - + def _parse(self): self._doc.reset() self._parse_summary() @@ -408,22 +408,17 @@ def header(text, style='-'): class FunctionDoc(NumpyDocString): - def __init__(self, func, role='func', doc=None): + def __init__(self, func, role='func', doc=None, config={}): self._f = func self._role = role # e.g. "func" or "meth" - if doc is None: - doc = inspect.getdoc(func) or '' - try: - NumpyDocString.__init__(self, doc) - except ValueError, e: - print '*'*78 - print "ERROR: '%s' while parsing `%s`" % (e, self._f) - print '*'*78 - #print "Docstring follows:" - #print doclines - #print '='*78 - if not self['Signature']: + if doc is None: + if func is None: + raise ValueError("No function or docstring given") + doc = inspect.getdoc(func) or '' + NumpyDocString.__init__(self, doc) + + if not self['Signature'] and func is not None: func, func_name = self.get_func() try: # try to read signature @@ -442,7 +437,7 @@ class FunctionDoc(NumpyDocString): else: func = self._f return func, func_name - + def __str__(self): out = '' @@ -463,35 +458,41 @@ class FunctionDoc(NumpyDocString): class ClassDoc(NumpyDocString): - def __init__(self,cls,modulename='',func_doc=FunctionDoc,doc=None): - if not inspect.isclass(cls): - raise ValueError("Initialise using a class. Got %r" % cls) + def __init__(self, cls, doc=None, modulename='', func_doc=FunctionDoc, + config={}): + if not inspect.isclass(cls) and cls is not None: + raise ValueError("Expected a class or None, but got %r" % cls) self._cls = cls if modulename and not modulename.endswith('.'): modulename += '.' self._mod = modulename - self._name = cls.__name__ - self._func_doc = func_doc if doc is None: + if cls is None: + raise ValueError("No class or documentation string given") doc = pydoc.getdoc(cls) NumpyDocString.__init__(self, doc) - if not self['Methods']: - self['Methods'] = [(name, '', '') for name in sorted(self.methods)] - - if not self['Attributes']: - self['Attributes'] = [(name, '', '') - for name in sorted(self.properties)] + if config.get('show_class_members', True): + if not self['Methods']: + self['Methods'] = [(name, '', '') + for name in sorted(self.methods)] + if not self['Attributes']: + self['Attributes'] = [(name, '', '') + for name in sorted(self.properties)] @property def methods(self): + if self._cls is None: + return [] return [name for name,func in inspect.getmembers(self._cls) if not name.startswith('_') and callable(func)] @property def properties(self): + if self._cls is None: + return [] return [name for name,func in inspect.getmembers(self._cls) if not name.startswith('_') and func is None] diff --git a/doc/ext/docscrape_sphinx.py b/doc/ext/docscrape_sphinx.py index 12907731..9f4350d4 100644 --- a/doc/ext/docscrape_sphinx.py +++ b/doc/ext/docscrape_sphinx.py @@ -3,7 +3,9 @@ import sphinx from docscrape import NumpyDocString, FunctionDoc, ClassDoc class SphinxDocString(NumpyDocString): - use_plots = False + def __init__(self, docstring, config={}): + self.use_plots = config.get('use_plots', False) + NumpyDocString.__init__(self, docstring, config=config) # string conversion routines def _str_header(self, name, symbol='`'): @@ -189,17 +191,21 @@ class SphinxDocString(NumpyDocString): return '\n'.join(out) class SphinxFunctionDoc(SphinxDocString, FunctionDoc): - pass + def __init__(self, obj, doc=None, config={}): + self.use_plots = config.get('use_plots', False) + FunctionDoc.__init__(self, obj, doc=doc, config=config) class SphinxClassDoc(SphinxDocString, ClassDoc): - pass + def __init__(self, obj, doc=None, func_doc=None, config={}): + self.use_plots = config.get('use_plots', False) + ClassDoc.__init__(self, obj, doc=doc, func_doc=None, config=config) class SphinxObjDoc(SphinxDocString): - def __init__(self, obj, doc): + def __init__(self, obj, doc=None, config={}): self._f = obj - SphinxDocString.__init__(self, doc) + SphinxDocString.__init__(self, doc, config=config) -def get_doc_object(obj, what=None, doc=None): +def get_doc_object(obj, what=None, doc=None, config={}): if what is None: if inspect.isclass(obj): what = 'class' @@ -210,10 +216,11 @@ def get_doc_object(obj, what=None, doc=None): else: what = 'object' if what == 'class': - return SphinxClassDoc(obj, '', func_doc=SphinxFunctionDoc, doc=doc) + return SphinxClassDoc(obj, func_doc=SphinxFunctionDoc, doc=doc, + config=config) elif what in ('function', 'method'): - return SphinxFunctionDoc(obj, '', doc=doc) + return SphinxFunctionDoc(obj, doc=doc, config=config) else: if doc is None: doc = pydoc.getdoc(obj) - return SphinxObjDoc(obj, doc) + return SphinxObjDoc(obj, doc, config=config) diff --git a/doc/ext/numpydoc.py b/doc/ext/numpydoc.py index 707107da..aa390056 100644 --- a/doc/ext/numpydoc.py +++ b/doc/ext/numpydoc.py @@ -24,14 +24,16 @@ import inspect def mangle_docstrings(app, what, name, obj, options, lines, reference_offset=[0]): + cfg = dict(use_plots=app.config.numpydoc_use_plots, + show_class_members=app.config.numpydoc_show_class_members) + if what == 'module': # Strip top title title_re = re.compile(ur'^\s*[#*=]{4,}\n[a-z0-9 -]+\n[#*=]{4,}\s*', re.I|re.S) lines[:] = title_re.sub(u'', u"\n".join(lines)).split(u"\n") else: - doc = get_doc_object(obj, what, u"\n".join(lines)) - doc.use_plots = app.config.numpydoc_use_plots + doc = get_doc_object(obj, what, u"\n".join(lines), config=cfg) lines[:] = unicode(doc).split(u"\n") if app.config.numpydoc_edit_link and hasattr(obj, '__name__') and \ @@ -71,7 +73,8 @@ def mangle_docstrings(app, what, name, obj, options, lines, def mangle_signature(app, what, name, obj, options, sig, retann): # Do not try to inspect classes that don't define `__init__` if (inspect.isclass(obj) and - 'initializes x; see ' in pydoc.getdoc(obj.__init__)): + (not hasattr(obj, '__init__') or + 'initializes x; see ' in pydoc.getdoc(obj.__init__))): return '', '' if not (callable(obj) or hasattr(obj, '__argspec_is_invalid_')): return @@ -82,73 +85,63 @@ def mangle_signature(app, what, name, obj, options, sig, retann): sig = re.sub(u"^[^(]*", u"", doc['Signature']) return sig, u'' -def initialize(app): - try: - app.connect('autodoc-process-signature', mangle_signature) - except: - monkeypatch_sphinx_ext_autodoc() - def setup(app, get_doc_object_=get_doc_object): global get_doc_object get_doc_object = get_doc_object_ - - app.connect('autodoc-process-docstring', mangle_docstrings) - app.connect('builder-inited', initialize) - app.add_config_value('numpydoc_edit_link', None, True) - app.add_config_value('numpydoc_use_plots', None, False) - # Extra mangling directives - name_type = { - 'cfunction': 'function', - 'cmember': 'attribute', - 'cmacro': 'function', - 'ctype': 'class', - 'cvar': 'object', - 'class': 'class', + app.connect('autodoc-process-docstring', mangle_docstrings) + app.connect('autodoc-process-signature', mangle_signature) + app.add_config_value('numpydoc_edit_link', None, False) + app.add_config_value('numpydoc_use_plots', None, False) + app.add_config_value('numpydoc_show_class_members', True, True) + + # Extra mangling domains + app.add_domain(NumpyPythonDomain) + app.add_domain(NumpyCDomain) + +#------------------------------------------------------------------------------ +# Docstring-mangling domains +#------------------------------------------------------------------------------ + +from docutils.statemachine import ViewList +from sphinx.domains.c import CDomain +from sphinx.domains.python import PythonDomain + +class ManglingDomainBase(object): + directive_mangling_map = {} + + def __init__(self, *a, **kw): + super(ManglingDomainBase, self).__init__(*a, **kw) + self.wrap_mangling_directives() + + def wrap_mangling_directives(self): + for name, objtype in self.directive_mangling_map.items(): + self.directives[name] = wrap_mangling_directive( + self.directives[name], objtype) + +class NumpyPythonDomain(ManglingDomainBase, PythonDomain): + name = 'np' + directive_mangling_map = { 'function': 'function', - 'attribute': 'attribute', + 'class': 'class', + 'exception': 'class', 'method': 'function', - 'staticmethod': 'function', 'classmethod': 'function', + 'staticmethod': 'function', + 'attribute': 'attribute', } - for name, objtype in name_type.items(): - app.add_directive('np-' + name, wrap_mangling_directive(name, objtype)) - -#------------------------------------------------------------------------------ -# Input-mangling directives -#------------------------------------------------------------------------------ -from docutils.statemachine import ViewList - -def get_directive(name): - from docutils.parsers.rst import directives - try: - return directives.directive(name, None, None)[0] - except AttributeError: - pass - try: - # docutils 0.4 - return directives._directives[name] - except (AttributeError, KeyError): - raise RuntimeError("No directive named '%s' found" % name) - -def wrap_mangling_directive(base_directive_name, objtype): - base_directive = get_directive(base_directive_name) - - if inspect.isfunction(base_directive): - base_func = base_directive - class base_directive(Directive): - required_arguments = base_func.arguments[0] - optional_arguments = base_func.arguments[1] - final_argument_whitespace = base_func.arguments[2] - option_spec = base_func.options - has_content = base_func.content - def run(self): - return base_func(self.name, self.arguments, self.options, - self.content, self.lineno, - self.content_offset, self.block_text, - self.state, self.state_machine) +class NumpyCDomain(ManglingDomainBase, CDomain): + name = 'np-c' + directive_mangling_map = { + 'function': 'function', + 'member': 'attribute', + 'macro': 'function', + 'type': 'class', + 'var': 'object', + } +def wrap_mangling_directive(base_directive, objtype): class directive(base_directive): def run(self): env = self.state.document.settings.env @@ -169,24 +162,3 @@ def wrap_mangling_directive(base_directive_name, objtype): return directive -#------------------------------------------------------------------------------ -# Monkeypatch sphinx.ext.autodoc to accept argspecless autodocs (Sphinx < 0.5) -#------------------------------------------------------------------------------ - -def monkeypatch_sphinx_ext_autodoc(): - global _original_format_signature - import sphinx.ext.autodoc - - if sphinx.ext.autodoc.format_signature is our_format_signature: - return - - print "[numpydoc] Monkeypatching sphinx.ext.autodoc ..." - _original_format_signature = sphinx.ext.autodoc.format_signature - sphinx.ext.autodoc.format_signature = our_format_signature - -def our_format_signature(what, obj): - r = mangle_signature(None, what, None, obj, None, None, None) - if r is not None: - return r[0] - else: - return _original_format_signature(what, obj) diff --git a/doc/ext/plot_directive.py b/doc/ext/plot_directive.py index 05c5ce33..d092f590 100644 --- a/doc/ext/plot_directive.py +++ b/doc/ext/plot_directive.py @@ -1,44 +1,78 @@ """ -A special directive for generating a matplotlib plot. +A directive for including a matplotlib plot in a Sphinx document. -.. warning:: +By default, in HTML output, `plot` will include a .png file with a +link to a high-res .png and .pdf. In LaTeX output, it will include a +.pdf. - This is a hacked version of plot_directive.py from Matplotlib. - It's very much subject to change! +The source code for the plot may be included in one of three ways: + 1. **A path to a source file** as the argument to the directive:: -Usage ------ + .. plot:: path/to/plot.py -Can be used like this:: + When a path to a source file is given, the content of the + directive may optionally contain a caption for the plot:: - .. plot:: examples/example.py + .. plot:: path/to/plot.py - .. plot:: + This is the caption for the plot - import matplotlib.pyplot as plt - plt.plot([1,2,3], [4,5,6]) + Additionally, one my specify the name of a function to call (with + no arguments) immediately after importing the module:: - .. plot:: + .. plot:: path/to/plot.py plot_function1 - A plotting example: + 2. Included as **inline content** to the directive:: - >>> import matplotlib.pyplot as plt - >>> plt.plot([1,2,3], [4,5,6]) + .. plot:: -The content is interpreted as doctest formatted if it has a line starting -with ``>>>``. + import matplotlib.pyplot as plt + import matplotlib.image as mpimg + import numpy as np + img = mpimg.imread('_static/stinkbug.png') + imgplot = plt.imshow(img) -The ``plot`` directive supports the options + 3. Using **doctest** syntax:: + + .. plot:: + A plotting example: + >>> import matplotlib.pyplot as plt + >>> plt.plot([1,2,3], [4,5,6]) + +Options +------- + +The ``plot`` directive supports the following options: format : {'python', 'doctest'} Specify the format of the input include-source : bool - Whether to display the source code. Default can be changed in conf.py - -and the ``image`` directive options ``alt``, ``height``, ``width``, -``scale``, ``align``, ``class``. + Whether to display the source code. The default can be changed + using the `plot_include_source` variable in conf.py + + encoding : str + If this source file is in a non-UTF8 or non-ASCII encoding, + the encoding must be specified using the `:encoding:` option. + The encoding will not be inferred using the ``-*- coding -*-`` + metacomment. + + context : bool + If provided, the code will be run in the context of all + previous plot directives for which the `:context:` option was + specified. This only applies to inline code plot directives, + not those run from files. + + nofigs : bool + If specified, the code block will be run, but no figures will + be inserted. This is usually useful with the ``:context:`` + option. + +Additionally, this directive supports all of the options of the +`image` directive, except for `target` (since plot will add its own +target). These include `alt`, `height`, `width`, `scale`, `align` and +`class`. Configuration options --------------------- @@ -52,9 +86,9 @@ The plot directive has the following configuration options: Code that should be executed before each plot. plot_basedir - Base directory, to which plot:: file names are relative to. - (If None or empty, file names are relative to the directoly where - the file containing the directive is.) + Base directory, to which ``plot::`` file names are relative + to. (If None or empty, file names are relative to the + directoly where the file containing the directive is.) plot_formats File formats to generate. List of tuples or strings:: @@ -64,48 +98,113 @@ The plot directive has the following configuration options: that determine the file format and the DPI. For entries whose DPI was omitted, sensible defaults are chosen. -TODO ----- + plot_html_show_formats + Whether to show links to the files in HTML. -* Refactor Latex output; now it's plain images, but it would be nice - to make them appear side-by-side, or in floats. + plot_rcparams + A dictionary containing any non-standard rcParams that should + be applied before each plot. """ -import sys, os, glob, shutil, imp, warnings, cStringIO, re, textwrap, traceback +import sys, os, glob, shutil, imp, warnings, cStringIO, re, textwrap, \ + traceback, exceptions + +from docutils.parsers.rst import directives +from docutils import nodes +from docutils.parsers.rst.directives.images import Image +align = Image.align import sphinx -import warnings -warnings.warn("A plot_directive module is also available under " - "matplotlib.sphinxext; expect this numpydoc.plot_directive " - "module to be deprecated after relevant features have been " - "integrated there.", - FutureWarning, stacklevel=2) +sphinx_version = sphinx.__version__.split(".") +# The split is necessary for sphinx beta versions where the string is +# '6b1' +sphinx_version = tuple([int(re.split('[a-z]', x)[0]) + for x in sphinx_version[:2]]) +try: + # Sphinx depends on either Jinja or Jinja2 + import jinja2 + def format_template(template, **kw): + return jinja2.Template(template).render(**kw) +except ImportError: + import jinja + def format_template(template, **kw): + return jinja.from_string(template, **kw) + +import matplotlib +import matplotlib.cbook as cbook +matplotlib.use('Agg') +import matplotlib.pyplot as plt +from matplotlib import _pylab_helpers + +__version__ = 2 + +#------------------------------------------------------------------------------ +# Relative pathnames +#------------------------------------------------------------------------------ + +# os.path.relpath is new in Python 2.6 +try: + from os.path import relpath +except ImportError: + # Copied from Python 2.7 + if 'posix' in sys.builtin_module_names: + def relpath(path, start=os.path.curdir): + """Return a relative version of a path""" + from os.path import sep, curdir, join, abspath, commonprefix, \ + pardir + + if not path: + raise ValueError("no path specified") + + start_list = abspath(start).split(sep) + path_list = abspath(path).split(sep) + + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + elif 'nt' in sys.builtin_module_names: + def relpath(path, start=os.path.curdir): + """Return a relative version of a path""" + from os.path import sep, curdir, join, abspath, commonprefix, \ + pardir, splitunc + + if not path: + raise ValueError("no path specified") + start_list = abspath(start).split(sep) + path_list = abspath(path).split(sep) + if start_list[0].lower() != path_list[0].lower(): + unc_path, rest = splitunc(path) + unc_start, rest = splitunc(start) + if bool(unc_path) ^ bool(unc_start): + raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" + % (path, start)) + else: + raise ValueError("path is on drive %s, start on drive %s" + % (path_list[0], start_list[0])) + # Work out how much of the filepath is shared by start and path. + for i in range(min(len(start_list), len(path_list))): + if start_list[i].lower() != path_list[i].lower(): + break + else: + i += 1 + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + else: + raise RuntimeError("Unsupported platform (no relpath available!)") #------------------------------------------------------------------------------ # Registration hook #------------------------------------------------------------------------------ -def setup(app): - setup.app = app - setup.config = app.config - setup.confdir = app.confdir - - app.add_config_value('plot_pre_code', '', True) - app.add_config_value('plot_include_source', False, True) - app.add_config_value('plot_formats', ['png', 'hires.png', 'pdf'], True) - app.add_config_value('plot_basedir', None, True) - - app.add_directive('plot', plot_directive, True, (0, 1, False), - **plot_directive_options) - -#------------------------------------------------------------------------------ -# plot:: directive -#------------------------------------------------------------------------------ -from docutils.parsers.rst import directives -from docutils import nodes - def plot_directive(name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): return run(arguments, content, options, state_machine, state, lineno) @@ -123,254 +222,74 @@ def _option_boolean(arg): raise ValueError('"%s" unknown boolean' % arg) def _option_format(arg): - return directives.choice(arg, ('python', 'lisp')) + return directives.choice(arg, ('python', 'doctest')) def _option_align(arg): return directives.choice(arg, ("top", "middle", "bottom", "left", "center", "right")) -plot_directive_options = {'alt': directives.unchanged, - 'height': directives.length_or_unitless, - 'width': directives.length_or_percentage_or_unitless, - 'scale': directives.nonnegative_int, - 'align': _option_align, - 'class': directives.class_option, - 'include-source': _option_boolean, - 'format': _option_format, - } +def mark_plot_labels(app, document): + """ + To make plots referenceable, we need to move the reference from + the "htmlonly" (or "latexonly") node to the actual figure node + itself. + """ + for name, explicit in document.nametypes.iteritems(): + if not explicit: + continue + labelid = document.nameids[name] + if labelid is None: + continue + node = document.ids[labelid] + if node.tagname in ('html_only', 'latex_only'): + for n in node: + if n.tagname == 'figure': + sectname = name + for c in n: + if c.tagname == 'caption': + sectname = c.astext() + break + + node['ids'].remove(labelid) + node['names'].remove(name) + n['ids'].append(labelid) + n['names'].append(name) + document.settings.env.labels[name] = \ + document.settings.env.docname, labelid, sectname + break + +def setup(app): + setup.app = app + setup.config = app.config + setup.confdir = app.confdir + + options = {'alt': directives.unchanged, + 'height': directives.length_or_unitless, + 'width': directives.length_or_percentage_or_unitless, + 'scale': directives.nonnegative_int, + 'align': _option_align, + 'class': directives.class_option, + 'include-source': _option_boolean, + 'format': _option_format, + 'context': directives.flag, + 'nofigs': directives.flag, + 'encoding': directives.encoding + } + + app.add_directive('plot', plot_directive, True, (0, 2, False), **options) + app.add_config_value('plot_pre_code', None, True) + app.add_config_value('plot_include_source', False, True) + app.add_config_value('plot_formats', ['png', 'hires.png', 'pdf'], True) + app.add_config_value('plot_basedir', None, True) + app.add_config_value('plot_html_show_formats', True, True) + app.add_config_value('plot_rcparams', {}, True) + + app.connect('doctree-read', mark_plot_labels) #------------------------------------------------------------------------------ -# Generating output +# Doctest handling #------------------------------------------------------------------------------ -from docutils import nodes, utils - -try: - # Sphinx depends on either Jinja or Jinja2 - import jinja2 - def format_template(template, **kw): - return jinja2.Template(template).render(**kw) -except ImportError: - import jinja - def format_template(template, **kw): - return jinja.from_string(template, **kw) - -TEMPLATE = """ -{{ source_code }} - -{{ only_html }} - - {% if source_code %} - (`Source code <{{ source_link }}>`__) - - .. admonition:: Output - :class: plot-output - - {% endif %} - - {% for img in images %} - .. figure:: {{ build_dir }}/{{ img.basename }}.png - {%- for option in options %} - {{ option }} - {% endfor %} - - (`Source code <{{source_link}}>`__) - - {# We do not need to link to all the different image - formats. The .png is included on the webpage, and a the - source snippet is given. - - ( - {%- if not source_code -%} - `Source code <{{source_link}}>`__ - {%- for fmt in img.formats -%} - , `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ - {%- endfor -%} - {%- else -%} - {%- for fmt in img.formats -%} - {%- if not loop.first -%}, {% endif -%} - `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ - {%- endfor -%} - {%- endif -%} - ) - - #} - {% endfor %} - -{{ only_latex }} - - {% for img in images %} - .. image:: {{ build_dir }}/{{ img.basename }}.pdf - {% endfor %} - -""" - -class ImageFile(object): - def __init__(self, basename, dirname): - self.basename = basename - self.dirname = dirname - self.formats = [] - - def filename(self, format): - return os.path.join(self.dirname, "%s.%s" % (self.basename, format)) - - def filenames(self): - return [self.filename(fmt) for fmt in self.formats] - -def run(arguments, content, options, state_machine, state, lineno): - if arguments and content: - raise RuntimeError("plot:: directive can't have both args and content") - - document = state_machine.document - config = document.settings.env.config - - options.setdefault('include-source', config.plot_include_source) - - # determine input - rst_file = document.attributes['source'] - rst_dir = os.path.dirname(rst_file) - - if arguments: - if not config.plot_basedir: - source_file_name = os.path.join(rst_dir, - directives.uri(arguments[0])) - else: - source_file_name = os.path.join(setup.confdir, config.plot_basedir, - directives.uri(arguments[0])) - code = open(source_file_name, 'r').read() - output_base = os.path.basename(source_file_name) - else: - source_file_name = rst_file - code = textwrap.dedent("\n".join(map(str, content))) - counter = document.attributes.get('_plot_counter', 0) + 1 - document.attributes['_plot_counter'] = counter - base, ext = os.path.splitext(os.path.basename(source_file_name)) - output_base = '%s-%d.py' % (base, counter) - - base, source_ext = os.path.splitext(output_base) - if source_ext in ('.py', '.rst', '.txt'): - output_base = base - else: - source_ext = '' - - # ensure that LaTeX includegraphics doesn't choke in foo.bar.pdf filenames - output_base = output_base.replace('.', '-') - - # is it in doctest format? - is_doctest = contains_doctest(code) - if options.has_key('format'): - if options['format'] == 'python': - is_doctest = False - else: - is_doctest = True - - # determine output directory name fragment - source_rel_name = relpath(source_file_name, setup.confdir) - source_rel_dir = os.path.dirname(source_rel_name) - while source_rel_dir.startswith(os.path.sep): - source_rel_dir = source_rel_dir[1:] - - # build_dir: where to place output files (temporarily) - build_dir = os.path.join(os.path.dirname(setup.app.doctreedir), - 'plot_directive', - source_rel_dir) - if not os.path.exists(build_dir): - os.makedirs(build_dir) - - # output_dir: final location in the builder's directory - dest_dir = os.path.abspath(os.path.join(setup.app.builder.outdir, - source_rel_dir)) - - # how to link to files from the RST file - dest_dir_link = os.path.join(relpath(setup.confdir, rst_dir), - source_rel_dir).replace(os.path.sep, '/') - build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/') - source_link = dest_dir_link + '/' + output_base + source_ext - - # make figures - try: - images = makefig(code, source_file_name, build_dir, output_base, - config) - except PlotError, err: - reporter = state.memo.reporter - sm = reporter.system_message( - 3, "Exception occurred in plotting %s: %s" % (output_base, err), - line=lineno) - return [sm] - - # generate output restructuredtext - if options['include-source']: - if is_doctest: - lines = [''] - lines += [row.rstrip() for row in code.split('\n')] - else: - lines = ['.. code-block:: python', ''] - lines += [' %s' % row.rstrip() for row in code.split('\n')] - source_code = "\n".join(lines) - else: - source_code = "" - - opts = [':%s: %s' % (key, val) for key, val in options.items() - if key in ('alt', 'height', 'width', 'scale', 'align', 'class')] - - if sphinx.__version__ >= "0.6": - only_html = ".. only:: html" - only_latex = ".. only:: latex" - else: - only_html = ".. htmlonly::" - only_latex = ".. latexonly::" - - result = format_template( - TEMPLATE, - dest_dir=dest_dir_link, - build_dir=build_dir_link, - source_link=source_link, - only_html=only_html, - only_latex=only_latex, - options=opts, - images=images, - source_code=source_code) - - lines = result.split("\n") - if len(lines): - state_machine.insert_input( - lines, state_machine.input_lines.source(0)) - - # copy image files to builder's output directory - if not os.path.exists(dest_dir): - os.makedirs(dest_dir) - -# Commented out so that images are not copied to the 'plots' -# directory. Sphinx copies to images to '_images' anyway, so no need. - -# for img in images: -# for fn in img.filenames(): -# shutil.copyfile(fn, os.path.join(dest_dir, os.path.basename(fn))) - - # copy script (if necessary) - if source_file_name == rst_file: - target_name = os.path.join(dest_dir, output_base + source_ext) - f = open(target_name, 'w') - f.write(unescape_doctest(code)) - f.close() - else: - shutil.copy(source_file_name, dest_dir) - - return [] - - -#------------------------------------------------------------------------------ -# Run code and capture figures -#------------------------------------------------------------------------------ - -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt -import matplotlib.image as image -from matplotlib import _pylab_helpers - -import exceptions - def contains_doctest(text): try: # check if it's valid Python as-is @@ -402,12 +321,127 @@ def unescape_doctest(text): code += "\n" return code +def split_code_at_show(text): + """ + Split code at plt.show() + + """ + + parts = [] + is_doctest = contains_doctest(text) + + part = [] + for line in text.split("\n"): + if (not is_doctest and line.strip() == 'plt.show()') or \ + (is_doctest and line.strip() == '>>> plt.show()'): + part.append(line) + parts.append("\n".join(part)) + part = [] + else: + part.append(line) + if "\n".join(part).strip(): + parts.append("\n".join(part)) + return parts + +#------------------------------------------------------------------------------ +# Template +#------------------------------------------------------------------------------ + + +TEMPLATE = """ +{{ source_code }} + +{{ only_html }} + + {% if source_link or (html_show_formats and not multi_image) %} + ( + {%- if source_link -%} + `Source code <{{ source_link }}>`__ + {%- endif -%} + {%- if html_show_formats and not multi_image -%} + {%- for img in images -%} + {%- for fmt in img.formats -%} + {%- if source_link or not loop.first -%}, {% endif -%} + `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ + {%- endfor -%} + {%- endfor -%} + {%- endif -%} + ) + {% endif %} + + {% for img in images %} + .. figure:: {{ build_dir }}/{{ img.basename }}.png + {%- for option in options %} + {{ option }} + {% endfor %} + + {% if html_show_formats and multi_image -%} + ( + {%- for fmt in img.formats -%} + {%- if not loop.first -%}, {% endif -%} + `{{ fmt }} <{{ dest_dir }}/{{ img.basename }}.{{ fmt }}>`__ + {%- endfor -%} + ) + {%- endif -%} + + {{ caption }} + {% endfor %} + +{{ only_latex }} + + {% for img in images %} + .. image:: {{ build_dir }}/{{ img.basename }}.pdf + {% endfor %} + +""" + +exception_template = """ +.. htmlonly:: + + [`source code <%(linkdir)s/%(basename)s.py>`__] + +Exception occurred rendering plot. + +""" + +# the context of the plot for all directives specified with the +# :context: option +plot_context = dict() + +class ImageFile(object): + def __init__(self, basename, dirname): + self.basename = basename + self.dirname = dirname + self.formats = [] + + def filename(self, format): + return os.path.join(self.dirname, "%s.%s" % (self.basename, format)) + + def filenames(self): + return [self.filename(fmt) for fmt in self.formats] + +def out_of_date(original, derived): + """ + Returns True if derivative is out-of-date wrt original, + both of which are full file paths. + """ + return (not os.path.exists(derived) or + (os.path.exists(original) and + os.stat(derived).st_mtime < os.stat(original).st_mtime)) + class PlotError(RuntimeError): pass -def run_code(code, code_path): +def run_code(code, code_path, ns=None, function_name=None): + """ + Import a Python module from a path, and run the function given by + name, if function_name is not None. + """ + # Change the working directory to the directory of the example, so - # it can get at its data files, if any. + # it can get at its data files, if any. Add its path to sys.path + # so it can import any helper modules sitting beside it. + pwd = os.getcwd() old_sys_path = list(sys.path) if code_path is not None: @@ -422,13 +456,20 @@ def run_code(code, code_path): # Reset sys.argv old_sys_argv = sys.argv sys.argv = [code_path] - + try: try: code = unescape_doctest(code) - ns = {} - exec setup.config.plot_pre_code in ns + if ns is None: + ns = {} + if not ns: + if setup.config.plot_pre_code is None: + exec "import numpy as np\nfrom matplotlib import pyplot as plt\n" in ns + else: + exec setup.config.plot_pre_code in ns exec code in ns + if function_name is not None: + exec function_name + "()" in ns except (Exception, SystemExit), err: raise PlotError(traceback.format_exc()) finally: @@ -438,29 +479,22 @@ def run_code(code, code_path): sys.stdout = stdout return ns +def clear_state(plot_rcparams): + plt.close('all') + matplotlib.rc_file_defaults() + matplotlib.rcParams.update(plot_rcparams) -#------------------------------------------------------------------------------ -# Generating figures -#------------------------------------------------------------------------------ - -def out_of_date(original, derived): +def render_figures(code, code_path, output_dir, output_base, context, + function_name, config): """ - Returns True if derivative is out-of-date wrt original, - both of which are full file paths. + Run a pyplot script and save the low and high res PNGs and a PDF + in outdir. + + Save the images under *output_dir* with file names derived from + *output_base* """ - return (not os.path.exists(derived) - or os.stat(derived).st_mtime < os.stat(original).st_mtime) - - -def makefig(code, code_path, output_dir, output_base, config): - """ - Run a pyplot script *code* and save the images under *output_dir* - with file names derived from *output_base* - - """ - # -- Parse format list - default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 50} + default_dpi = {'png': 80, 'hires.png': 200, 'pdf': 200} formats = [] for fmt in config.plot_formats: if isinstance(fmt, str): @@ -472,6 +506,9 @@ def makefig(code, code_path, output_dir, output_base, config): # -- Try to determine if all images already exist + code_pieces = split_code_at_show(code) + + # Look for single-figure output files first # Look for single-figure output files first all_exists = True img = ImageFile(output_base, output_dir) @@ -482,95 +519,246 @@ def makefig(code, code_path, output_dir, output_base, config): img.formats.append(format) if all_exists: - return [img] + return [(code, [img])] # Then look for multi-figure output files - images = [] + results = [] all_exists = True - for i in xrange(1000): - img = ImageFile('%s_%02d' % (output_base, i), output_dir) - for format, dpi in formats: - if out_of_date(code_path, img.filename(format)): - all_exists = False + for i, code_piece in enumerate(code_pieces): + images = [] + for j in xrange(1000): + if len(code_pieces) > 1: + img = ImageFile('%s_%02d_%02d' % (output_base, i, j), output_dir) + else: + img = ImageFile('%s_%02d' % (output_base, j), output_dir) + for format, dpi in formats: + if out_of_date(code_path, img.filename(format)): + all_exists = False + break + img.formats.append(format) + + # assume that if we have one, we have them all + if not all_exists: + all_exists = (j > 0) break - img.formats.append(format) - - # assume that if we have one, we have them all + images.append(img) if not all_exists: - all_exists = (i > 0) break - images.append(img) + results.append((code_piece, images)) if all_exists: - return images + return results - # -- We didn't find the files, so build them + # We didn't find the files, so build them - # Clear between runs - plt.close('all') + results = [] + if context: + ns = plot_context + else: + ns = {} - # Run code - run_code(code, code_path) + for i, code_piece in enumerate(code_pieces): + if not context: + clear_state(config.plot_rcparams) + run_code(code_piece, code_path, ns, function_name) - # Collect images - images = [] + images = [] + fig_managers = _pylab_helpers.Gcf.get_all_fig_managers() + for j, figman in enumerate(fig_managers): + if len(fig_managers) == 1 and len(code_pieces) == 1: + img = ImageFile(output_base, output_dir) + elif len(code_pieces) == 1: + img = ImageFile("%s_%02d" % (output_base, j), output_dir) + else: + img = ImageFile("%s_%02d_%02d" % (output_base, i, j), + output_dir) + images.append(img) + for format, dpi in formats: + try: + figman.canvas.figure.savefig(img.filename(format), dpi=dpi) + except exceptions.BaseException as err: + raise PlotError(traceback.format_exc()) + img.formats.append(format) - fig_managers = _pylab_helpers.Gcf.get_all_fig_managers() - for i, figman in enumerate(fig_managers): - if len(fig_managers) == 1: - img = ImageFile(output_base, output_dir) + results.append((code_piece, images)) + + return results + +def run(arguments, content, options, state_machine, state, lineno): + # The user may provide a filename *or* Python code content, but not both + if arguments and content: + raise RuntimeError("plot:: directive can't have both args and content") + + document = state_machine.document + config = document.settings.env.config + nofigs = options.has_key('nofigs') + + options.setdefault('include-source', config.plot_include_source) + context = options.has_key('context') + + rst_file = document.attributes['source'] + rst_dir = os.path.dirname(rst_file) + + if len(arguments): + if not config.plot_basedir: + source_file_name = os.path.join(setup.app.builder.srcdir, + directives.uri(arguments[0])) else: - img = ImageFile("%s_%02d" % (output_base, i), output_dir) - images.append(img) - for format, dpi in formats: - try: - figman.canvas.figure.savefig(img.filename(format), dpi=dpi) - except exceptions.BaseException, err: - raise PlotError(traceback.format_exc()) - img.formats.append(format) + source_file_name = os.path.join(setup.confdir, config.plot_basedir, + directives.uri(arguments[0])) - return images + # If there is content, it will be passed as a caption. + caption = '\n'.join(content) - -#------------------------------------------------------------------------------ -# Relative pathnames -#------------------------------------------------------------------------------ - -try: - from os.path import relpath -except ImportError: - def relpath(target, base=os.curdir): - """ - Return a relative path to the target from either the current - dir or an optional base dir. Base can be a directory - specified either as absolute or relative to current dir. - """ - - if not os.path.exists(target): - raise OSError, 'Target does not exist: '+target - - if not os.path.isdir(base): - raise OSError, 'Base is not a directory or does not exist: '+base - - base_list = (os.path.abspath(base)).split(os.sep) - target_list = (os.path.abspath(target)).split(os.sep) - - # On the windows platform the target may be on a completely - # different drive from the base. - if os.name in ['nt','dos','os2'] and base_list[0] <> target_list[0]: - raise OSError, 'Target is on a different drive to base. Target: '+target_list[0].upper()+', base: '+base_list[0].upper() - - # Starting from the filepath root, work out how much of the - # filepath is shared by base and target. - for i in range(min(len(base_list), len(target_list))): - if base_list[i] <> target_list[i]: break + # If the optional function name is provided, use it + if len(arguments) == 2: + function_name = arguments[1] else: - # If we broke out of the loop, i is pointing to the first - # differing path elements. If we didn't break out of the - # loop, i is pointing to identical path elements. - # Increment i so that in all cases it points to the first - # differing path elements. - i+=1 + function_name = None - rel_list = [os.pardir] * (len(base_list)-i) + target_list[i:] - return os.path.join(*rel_list) + fd = open(source_file_name, 'r') + code = fd.read() + fd.close() + output_base = os.path.basename(source_file_name) + else: + source_file_name = rst_file + code = textwrap.dedent("\n".join(map(str, content))) + counter = document.attributes.get('_plot_counter', 0) + 1 + document.attributes['_plot_counter'] = counter + base, ext = os.path.splitext(os.path.basename(source_file_name)) + output_base = '%s-%d.py' % (base, counter) + function_name = None + caption = '' + + base, source_ext = os.path.splitext(output_base) + if source_ext in ('.py', '.rst', '.txt'): + output_base = base + else: + source_ext = '' + + # ensure that LaTeX includegraphics doesn't choke in foo.bar.pdf filenames + output_base = output_base.replace('.', '-') + + # is it in doctest format? + is_doctest = contains_doctest(code) + if options.has_key('format'): + if options['format'] == 'python': + is_doctest = False + else: + is_doctest = True + + # determine output directory name fragment + source_rel_name = relpath(source_file_name, setup.confdir) + source_rel_dir = os.path.dirname(source_rel_name) + while source_rel_dir.startswith(os.path.sep): + source_rel_dir = source_rel_dir[1:] + + # build_dir: where to place output files (temporarily) + build_dir = os.path.join(os.path.dirname(setup.app.doctreedir), + 'plot_directive', + source_rel_dir) + # get rid of .. in paths, also changes pathsep + # see note in Python docs for warning about symbolic links on Windows. + # need to compare source and dest paths at end + build_dir = os.path.normpath(build_dir) + + if not os.path.exists(build_dir): + os.makedirs(build_dir) + + # output_dir: final location in the builder's directory + dest_dir = os.path.abspath(os.path.join(setup.app.builder.outdir, + source_rel_dir)) + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) # no problem here for me, but just use built-ins + + # how to link to files from the RST file + dest_dir_link = os.path.join(relpath(setup.confdir, rst_dir), + source_rel_dir).replace(os.path.sep, '/') + build_dir_link = relpath(build_dir, rst_dir).replace(os.path.sep, '/') + source_link = dest_dir_link + '/' + output_base + source_ext + + # make figures + try: + results = render_figures(code, source_file_name, build_dir, output_base, + context, function_name, config) + errors = [] + except PlotError, err: + reporter = state.memo.reporter + sm = reporter.system_message( + 2, "Exception occurred in plotting %s: %s" % (output_base, err), + line=lineno) + results = [(code, [])] + errors = [sm] + + # Properly indent the caption + caption = '\n'.join(' ' + line.strip() + for line in caption.split('\n')) + + # generate output restructuredtext + total_lines = [] + for j, (code_piece, images) in enumerate(results): + if options['include-source']: + if is_doctest: + lines = [''] + lines += [row.rstrip() for row in code_piece.split('\n')] + else: + lines = ['.. code-block:: python', ''] + lines += [' %s' % row.rstrip() + for row in code_piece.split('\n')] + source_code = "\n".join(lines) + else: + source_code = "" + + if nofigs: + images = [] + + opts = [':%s: %s' % (key, val) for key, val in options.items() + if key in ('alt', 'height', 'width', 'scale', 'align', 'class')] + + only_html = ".. only:: html" + only_latex = ".. only:: latex" + + if j == 0: + src_link = source_link + else: + src_link = None + + result = format_template( + TEMPLATE, + dest_dir=dest_dir_link, + build_dir=build_dir_link, + source_link=src_link, + multi_image=len(images) > 1, + only_html=only_html, + only_latex=only_latex, + options=opts, + images=images, + source_code=source_code, + html_show_formats=config.plot_html_show_formats, + caption=caption) + + total_lines.extend(result.split("\n")) + total_lines.extend("\n") + + if total_lines: + state_machine.insert_input(total_lines, source=source_file_name) + + # copy image files to builder's output directory, if necessary + if not os.path.exists(dest_dir): + cbook.mkdirs(dest_dir) + + for code_piece, images in results: + for img in images: + for fn in img.filenames(): + destimg = os.path.join(dest_dir, os.path.basename(fn)) + if fn != destimg: + shutil.copyfile(fn, destimg) + + # copy script (if necessary) + if source_file_name == rst_file: + target_name = os.path.join(dest_dir, output_base + source_ext) + f = open(target_name, 'w') + f.write(unescape_doctest(code)) + f.close() + + return errors diff --git a/doc/gh-pages.py b/doc/gh-pages.py new file mode 100644 index 00000000..ddd9d2bb --- /dev/null +++ b/doc/gh-pages.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python +"""Script to commit the doc build outputs into the github-pages repo. + +Use: + + gh-pages.py [tag] + +If no tag is given, the current output of 'git describe' is used. If given, +that is how the resulting directory will be named. + +In practice, you should use either actual clean tags from a current build or +something like 'current' as a stable URL for the mest current version of the """ + +#----------------------------------------------------------------------------- +# Imports +#----------------------------------------------------------------------------- +import os +import re +import shutil +import sys +from os import chdir as cd +from os.path import join as pjoin + +from subprocess import Popen, PIPE, CalledProcessError, check_call + +#----------------------------------------------------------------------------- +# Globals +#----------------------------------------------------------------------------- + +pages_dir = 'gh-pages' +html_dir = 'build/html' +pdf_dir = 'build/latex' +pages_repo = 'git@github.com:scikits-image/docs.git' + +#----------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------- +def sh(cmd): + """Execute command in a subshell, return status code.""" + return check_call(cmd, shell=True) + + +def sh2(cmd): + """Execute command in a subshell, return stdout. + + Stderr is unbuffered from the subshell.x""" + p = Popen(cmd, stdout=PIPE, shell=True) + out = p.communicate()[0] + retcode = p.returncode + if retcode: + print out.rstrip() + raise CalledProcessError(retcode, cmd) + else: + return out.rstrip() + + +def sh3(cmd): + """Execute command in a subshell, return stdout, stderr + + If anything appears in stderr, print it out to sys.stderr""" + p = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True) + out, err = p.communicate() + retcode = p.returncode + if retcode: + raise CalledProcessError(retcode, cmd) + else: + return out.rstrip(), err.rstrip() + + +def init_repo(path): + """clone the gh-pages repo if we haven't already.""" + sh("git clone %s %s"%(pages_repo, path)) + here = os.getcwd() + cd(path) + sh('git checkout gh-pages') + cd(here) + +#----------------------------------------------------------------------------- +# Script starts +#----------------------------------------------------------------------------- +if __name__ == '__main__': + # find the version number from setup.py + setup_lines = open('../setup.py').readlines() + tag = 'vUndefined' + for l in setup_lines: + if l.startswith('VERSION'): + tag = l.split("'")[1] + break + + startdir = os.getcwd() + if not os.path.exists(pages_dir): + # init the repo + init_repo(pages_dir) + else: + # ensure up-to-date before operating + cd(pages_dir) + sh('git checkout gh-pages') + sh('git pull') + cd(startdir) + + dest = os.path.join(pages_dir, tag) + # This is pretty unforgiving: we unconditionally nuke the destination + # directory, and then copy the html tree in there + shutil.rmtree(dest, ignore_errors=True) + shutil.copytree(html_dir, dest) + # copy pdf file into tree + #shutil.copy(pjoin(pdf_dir, 'scikits.image.pdf'), pjoin(dest, 'scikits.image.pdf')) + index_html = """ + + %s documentation + + """ % (tag, tag) + with open(os.path.join(pages_dir, "index.html"), 'w') as f: + f.write(index_html) + + try: + cd(pages_dir) + status = sh2('git status | head -1') + branch = re.match('\# On branch (.*)$', status).group(1) + if branch != 'gh-pages': + e = 'On %r, git branch is %r, MUST be "gh-pages"' % (pages_dir, + branch) + raise RuntimeError(e) + sh("touch .nojekyll") + sh('git add .nojekyll') + sh('git add index.html') + sh('git add %s' % tag) + sh2('git commit -m"Updated doc release: %s"' % tag) + + print 'Most recent commit:' + sys.stdout.flush() + sh('git --no-pager log --oneline HEAD~1..') + finally: + cd(startdir) + + print + print 'Now verify the build in: %r' % dest + print "If everything looks good, 'git push'" diff --git a/doc/push_github b/doc/push_github index 9b3db2df..3753263c 100755 --- a/doc/push_github +++ b/doc/push_github @@ -31,9 +31,9 @@ mkdir -p /tmp/_scikits_image_backup for f in $ignore_files; do cp $f /tmp/_scikits_image_backup done -git co scikits/image/version.py +git checkout scikits/image/version.py -git co gh-pages || exit +git checkout gh-pages || exit rm -rf ./* cp -r /tmp/scikits.image.docs/* . @@ -42,7 +42,7 @@ sed -i 's/_static/static/g' `find . -name "*.html"` sed -i 's/_images/images/g' `find . -name "*.html"` mv _static static mv _images images -for f in `find . | grep "./" | grep -v ".git"`; do +for f in `find . | grep "./" | grep -v "\.git"`; do git add $f done @@ -57,7 +57,7 @@ read git commit -m "Update docs." git push origin gh-pages -git co $branch +git checkout $branch for f in $ignore_files; do cp /tmp/_scikits_image_backup/`basename $f` $f diff --git a/doc/source/api/api.txt b/doc/source/api/api.txt deleted file mode 100644 index 3c6beeff..00000000 --- a/doc/source/api/api.txt +++ /dev/null @@ -1,15 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -API Reference -============= - -.. toctree:: - - scikits.image - scikits.image.color - scikits.image.filter - scikits.image.graph - scikits.image.io - scikits.image.morphology - scikits.image.opencv - scikits.image.transform diff --git a/doc/source/api/scikits.image.analysis.txt b/doc/source/api/scikits.image.analysis.txt deleted file mode 100644 index 59b6ddd2..00000000 --- a/doc/source/api/scikits.image.analysis.txt +++ /dev/null @@ -1,16 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -Module: :mod:`analysis` -======================= -.. automodule:: scikits.image.analysis - -.. currentmodule:: scikits.image.analysis -.. autosummary:: - - scikits.image.analysis.shortest_path - -shortest_path -------------- - -.. autofunction:: scikits.image.analysis.shortest_path - diff --git a/doc/source/api/scikits.image.color.txt b/doc/source/api/scikits.image.color.txt deleted file mode 100644 index cad07a5d..00000000 --- a/doc/source/api/scikits.image.color.txt +++ /dev/null @@ -1,64 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -Module: :mod:`color` -==================== -.. automodule:: scikits.image.color - -.. currentmodule:: scikits.image.color -.. autosummary:: - - scikits.image.color.convert_colorspace - scikits.image.color.hsv2rgb - scikits.image.color.rgb2gray - scikits.image.color.rgb2grey - scikits.image.color.rgb2hsv - scikits.image.color.rgb2rgbcie - scikits.image.color.rgb2xyz - scikits.image.color.rgbcie2rgb - scikits.image.color.xyz2rgb - -convert_colorspace ------------------- - -.. autofunction:: scikits.image.color.convert_colorspace - -hsv2rgb -------- - -.. autofunction:: scikits.image.color.hsv2rgb - -rgb2gray --------- - -.. autofunction:: scikits.image.color.rgb2gray - -rgb2grey --------- - -.. autofunction:: scikits.image.color.rgb2grey - -rgb2hsv -------- - -.. autofunction:: scikits.image.color.rgb2hsv - -rgb2rgbcie ----------- - -.. autofunction:: scikits.image.color.rgb2rgbcie - -rgb2xyz -------- - -.. autofunction:: scikits.image.color.rgb2xyz - -rgbcie2rgb ----------- - -.. autofunction:: scikits.image.color.rgbcie2rgb - -xyz2rgb -------- - -.. autofunction:: scikits.image.color.xyz2rgb - diff --git a/doc/source/api/scikits.image.filter.txt b/doc/source/api/scikits.image.filter.txt deleted file mode 100644 index 0f1064bd..00000000 --- a/doc/source/api/scikits.image.filter.txt +++ /dev/null @@ -1,45 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -Module: :mod:`filter` -===================== -Inheritance diagram for ``scikits.image.filter``: - -.. inheritance-diagram:: scikits.image.filter - :parts: 3 - -.. automodule:: scikits.image.filter - -.. currentmodule:: scikits.image.filter - -:class:`LPIFilter2D` --------------------- - - -.. autoclass:: LPIFilter2D - :members: - :undoc-members: - :show-inheritance: - :inherited-members: - - .. automethod:: __init__ -.. autosummary:: - - scikits.image.filter.inverse - scikits.image.filter.median_filter - scikits.image.filter.wiener - -inverse -------- - -.. autofunction:: scikits.image.filter.inverse - -median_filter -------------- - -.. autofunction:: scikits.image.filter.median_filter - -wiener ------- - -.. autofunction:: scikits.image.filter.wiener - diff --git a/doc/source/api/scikits.image.io.txt b/doc/source/api/scikits.image.io.txt deleted file mode 100644 index ec8ad675..00000000 --- a/doc/source/api/scikits.image.io.txt +++ /dev/null @@ -1,111 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -Module: :mod:`io` -================= -Inheritance diagram for ``scikits.image.io``: - -.. inheritance-diagram:: scikits.image.io - :parts: 3 - -.. automodule:: scikits.image.io - -.. currentmodule:: scikits.image.io - -:class:`ImageCollection` ------------------------- - - -.. autoclass:: ImageCollection - :members: - :undoc-members: - :show-inheritance: - :inherited-members: - - .. automethod:: __init__ - -:class:`MultiImage` -------------------- - - -.. autoclass:: MultiImage - :members: - :undoc-members: - :show-inheritance: - :inherited-members: - - .. automethod:: __init__ -.. autosummary:: - - scikits.image.io.imread - scikits.image.io.imread_collection - scikits.image.io.imsave - scikits.image.io.imshow - scikits.image.io.load_sift - scikits.image.io.load_surf - scikits.image.io.plugin_info - scikits.image.io.plugins - scikits.image.io.pop - scikits.image.io.push - scikits.image.io.show - scikits.image.io.use_plugin - -imread ------- - -.. autofunction:: scikits.image.io.imread - -imread_collection ------------------ - -.. autofunction:: scikits.image.io.imread_collection - -imsave ------- - -.. autofunction:: scikits.image.io.imsave - -imshow ------- - -.. autofunction:: scikits.image.io.imshow - -load_sift ---------- - -.. autofunction:: scikits.image.io.load_sift - -load_surf ---------- - -.. autofunction:: scikits.image.io.load_surf - -plugin_info ------------ - -.. autofunction:: scikits.image.io.plugin_info - -plugins -------- - -.. autofunction:: scikits.image.io.plugins - -pop ---- - -.. autofunction:: scikits.image.io.pop - -push ----- - -.. autofunction:: scikits.image.io.push - -show ----- - -.. autofunction:: scikits.image.io.show - -use_plugin ----------- - -.. autofunction:: scikits.image.io.use_plugin - diff --git a/doc/source/api/scikits.image.opencv.txt b/doc/source/api/scikits.image.opencv.txt deleted file mode 100644 index 6946677c..00000000 --- a/doc/source/api/scikits.image.opencv.txt +++ /dev/null @@ -1,237 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -Module: :mod:`opencv` -===================== -Inheritance diagram for ``scikits.image.opencv``: - -.. inheritance-diagram:: scikits.image.opencv - :parts: 3 - -.. automodule:: scikits.image.opencv - -.. currentmodule:: scikits.image.opencv - -:class:`cvdoc` --------------- - - -.. autoclass:: cvdoc - :members: - :undoc-members: - :show-inheritance: - :inherited-members: - - .. automethod:: __init__ -.. autosummary:: - - scikits.image.opencv.cvAdaptiveThreshold - scikits.image.opencv.cvCalibrateCamera2 - scikits.image.opencv.cvCanny - scikits.image.opencv.cvCornerEigenValsAndVecs - scikits.image.opencv.cvCornerHarris - scikits.image.opencv.cvCornerMinEigenVal - scikits.image.opencv.cvCvtColor - scikits.image.opencv.cvDilate - scikits.image.opencv.cvDrawChessboardCorners - scikits.image.opencv.cvErode - scikits.image.opencv.cvFilter2D - scikits.image.opencv.cvFindChessboardCorners - scikits.image.opencv.cvFindCornerSubPix - scikits.image.opencv.cvFindExtrinsicCameraParams2 - scikits.image.opencv.cvFindFundamentalMat - scikits.image.opencv.cvFloodFill - scikits.image.opencv.cvGetQuadrangleSubPix - scikits.image.opencv.cvGetRectSubPix - scikits.image.opencv.cvGoodFeaturesToTrack - scikits.image.opencv.cvIntegral - scikits.image.opencv.cvLaplace - scikits.image.opencv.cvLogPolar - scikits.image.opencv.cvMatchTemplate - scikits.image.opencv.cvMorphologyEx - scikits.image.opencv.cvPreCornerDetect - scikits.image.opencv.cvPyrDown - scikits.image.opencv.cvPyrUp - scikits.image.opencv.cvResize - scikits.image.opencv.cvSmooth - scikits.image.opencv.cvSobel - scikits.image.opencv.cvThreshold - scikits.image.opencv.cvUndistort2 - scikits.image.opencv.cvWarpAffine - scikits.image.opencv.cvWarpPerspective - scikits.image.opencv.cvWatershed - -cvAdaptiveThreshold -------------------- - -.. autofunction:: scikits.image.opencv.cvAdaptiveThreshold - -cvCalibrateCamera2 ------------------- - -.. autofunction:: scikits.image.opencv.cvCalibrateCamera2 - -cvCanny -------- - -.. autofunction:: scikits.image.opencv.cvCanny - -cvCornerEigenValsAndVecs ------------------------- - -.. autofunction:: scikits.image.opencv.cvCornerEigenValsAndVecs - -cvCornerHarris --------------- - -.. autofunction:: scikits.image.opencv.cvCornerHarris - -cvCornerMinEigenVal -------------------- - -.. autofunction:: scikits.image.opencv.cvCornerMinEigenVal - -cvCvtColor ----------- - -.. autofunction:: scikits.image.opencv.cvCvtColor - -cvDilate --------- - -.. autofunction:: scikits.image.opencv.cvDilate - -cvDrawChessboardCorners ------------------------ - -.. autofunction:: scikits.image.opencv.cvDrawChessboardCorners - -cvErode -------- - -.. autofunction:: scikits.image.opencv.cvErode - -cvFilter2D ----------- - -.. autofunction:: scikits.image.opencv.cvFilter2D - -cvFindChessboardCorners ------------------------ - -.. autofunction:: scikits.image.opencv.cvFindChessboardCorners - -cvFindCornerSubPix ------------------- - -.. autofunction:: scikits.image.opencv.cvFindCornerSubPix - -cvFindExtrinsicCameraParams2 ----------------------------- - -.. autofunction:: scikits.image.opencv.cvFindExtrinsicCameraParams2 - -cvFindFundamentalMat --------------------- - -.. autofunction:: scikits.image.opencv.cvFindFundamentalMat - -cvFloodFill ------------ - -.. autofunction:: scikits.image.opencv.cvFloodFill - -cvGetQuadrangleSubPix ---------------------- - -.. autofunction:: scikits.image.opencv.cvGetQuadrangleSubPix - -cvGetRectSubPix ---------------- - -.. autofunction:: scikits.image.opencv.cvGetRectSubPix - -cvGoodFeaturesToTrack ---------------------- - -.. autofunction:: scikits.image.opencv.cvGoodFeaturesToTrack - -cvIntegral ----------- - -.. autofunction:: scikits.image.opencv.cvIntegral - -cvLaplace ---------- - -.. autofunction:: scikits.image.opencv.cvLaplace - -cvLogPolar ----------- - -.. autofunction:: scikits.image.opencv.cvLogPolar - -cvMatchTemplate ---------------- - -.. autofunction:: scikits.image.opencv.cvMatchTemplate - -cvMorphologyEx --------------- - -.. autofunction:: scikits.image.opencv.cvMorphologyEx - -cvPreCornerDetect ------------------ - -.. autofunction:: scikits.image.opencv.cvPreCornerDetect - -cvPyrDown ---------- - -.. autofunction:: scikits.image.opencv.cvPyrDown - -cvPyrUp -------- - -.. autofunction:: scikits.image.opencv.cvPyrUp - -cvResize --------- - -.. autofunction:: scikits.image.opencv.cvResize - -cvSmooth --------- - -.. autofunction:: scikits.image.opencv.cvSmooth - -cvSobel -------- - -.. autofunction:: scikits.image.opencv.cvSobel - -cvThreshold ------------ - -.. autofunction:: scikits.image.opencv.cvThreshold - -cvUndistort2 ------------- - -.. autofunction:: scikits.image.opencv.cvUndistort2 - -cvWarpAffine ------------- - -.. autofunction:: scikits.image.opencv.cvWarpAffine - -cvWarpPerspective ------------------ - -.. autofunction:: scikits.image.opencv.cvWarpPerspective - -cvWatershed ------------ - -.. autofunction:: scikits.image.opencv.cvWatershed - diff --git a/doc/source/api/scikits.image.transform.txt b/doc/source/api/scikits.image.transform.txt deleted file mode 100644 index ca1c6792..00000000 --- a/doc/source/api/scikits.image.transform.txt +++ /dev/null @@ -1,34 +0,0 @@ -.. AUTO-GENERATED FILE -- DO NOT EDIT! - -Module: :mod:`transform` -======================== -.. automodule:: scikits.image.transform - -.. currentmodule:: scikits.image.transform -.. autosummary:: - - scikits.image.transform.frt2 - scikits.image.transform.homography - scikits.image.transform.hough - scikits.image.transform.ifrt2 - -frt2 ----- - -.. autofunction:: scikits.image.transform.frt2 - -homography ----------- - -.. autofunction:: scikits.image.transform.homography - -hough ------ - -.. autofunction:: scikits.image.transform.hough - -ifrt2 ------ - -.. autofunction:: scikits.image.transform.ifrt2 - diff --git a/doc/source/gitwash/forking_hell.txt b/doc/source/gitwash/forking_hell.txt index c367ce07..8ef86ffc 100644 --- a/doc/source/gitwash/forking_hell.txt +++ b/doc/source/gitwash/forking_hell.txt @@ -1,8 +1,8 @@ .. _forking: -========================================== +============================================ Making your own copy (fork) of scikits.image -========================================== +============================================ You need to do this only once. The instructions here are very similar to the instructions at http://help.github.com/forking/ - please see that @@ -18,7 +18,7 @@ You then need to configure your account to allow write access - see the ``Generating SSH keys`` help on `github help`_. Create your own forked copy of scikits.image_ -========================================= +============================================= #. Log into your github_ account. #. Go to the scikits.image_ github home at `scikits.image github`_. diff --git a/doc/source/gitwash/index.txt b/doc/source/gitwash/index.txt index c67e629f..0acb8fb9 100644 --- a/doc/source/gitwash/index.txt +++ b/doc/source/gitwash/index.txt @@ -1,7 +1,7 @@ .. _using-git: Working with *scikits.image* source code -====================================== +======================================== Contents: diff --git a/doc/tools/apigen.py b/doc/tools/apigen.py index dc9a6eb7..58a82fda 100644 --- a/doc/tools/apigen.py +++ b/doc/tools/apigen.py @@ -92,15 +92,21 @@ class ApiDocWriter(object): ''' # It's also possible to imagine caching the module parsing here self._package_name = package_name - self.root_module = __import__(package_name) - self.root_path = self.root_module.__path__[-1] - self.root_path = os.path.join(self.root_path, - os.path.sep.join(package_name.split('.')[1:])) + root_module = self._import(package_name) + self.root_path = root_module.__path__[-1] self.written_modules = None package_name = property(get_package_name, set_package_name, None, 'get/set package_name') + def _import(self, name): + ''' Import namespace package ''' + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + def _get_object_name(self, line): ''' Get second token in line >>> docwriter = ApiDocWriter('sphinx') @@ -194,14 +200,15 @@ class ApiDocWriter(object): classes : list of str A list of (public) class names in the module. """ - exec 'import ' + uri - exec 'mod = ' + uri + mod = __import__(uri, fromlist=[uri]) # find all public objects in the module. obj_strs = [obj for obj in dir(mod) if not obj.startswith('_')] functions = [] classes = [] for obj_str in obj_strs: # find the actual object from its string representation + if obj_str not in mod.__dict__: + continue obj = mod.__dict__[obj_str] # figure out if obj is a function or class if hasattr(obj, 'func_name') or \