Thread: [Epydoc-commits] SF.net SVN: epydoc: [1337] trunk/epydoc/src
Brought to you by:
edloper
From: <ed...@us...> - 2006-08-30 01:52:13
|
Revision: 1337 Author: edloper Date: 2006-08-29 18:52:10 -0700 (Tue, 29 Aug 2006) ViewCVS: http://svn.sourceforge.net/epydoc/?rev=1337&view=rev Log Message: ----------- - Added tools directory & custom rst2html script. Added Paths: ----------- trunk/epydoc/src/tools/ trunk/epydoc/src/tools/rst2html.py Added: trunk/epydoc/src/tools/rst2html.py =================================================================== --- trunk/epydoc/src/tools/rst2html.py (rev 0) +++ trunk/epydoc/src/tools/rst2html.py 2006-08-30 01:52:10 UTC (rev 1337) @@ -0,0 +1,40 @@ +#!/usr/bin/python + +r""" +A customized driver for converting docutils reStructuredText documents +into HTML. This is used to generated HTML versions of the regression +files, for the webpage. +""" + +# Docutils imports +from docutils.core import publish_cmdline, default_description +from docutils.writers.html4css1 import HTMLTranslator, Writer as HTMLWriter +import docutils.nodes + +# Epydoc imports. Make sure path contains the 'right' epydoc. +import sys +sys.path.insert(0, '../') +from epydoc.markup.doctest import doctest_to_html + +class CustomizedHTMLWriter(HTMLWriter): + settings_defaults = (HTMLWriter.settings_defaults or {}).copy() + settings_defaults.update({ + 'stylesheet': 'doctest.css', + 'stylesheet_path': None, + 'output_encoding': 'ascii', + 'output_encoding_error_handler': 'xmlcharrefreplace', + }) + + def __init__(self): + HTMLWriter.__init__(self) + self.translator_class = CustomizedHTMLTranslator + +class CustomizedHTMLTranslator(HTMLTranslator): + def visit_doctest_block(self, node): + self.body.append(doctest_to_html(str(node[0]))) + raise docutils.nodes.SkipNode + +description = ('Generates HTML documents from reStructuredText ' + 'documents. ' + default_description) +writer = CustomizedHTMLWriter() +docutils.core.publish_cmdline(writer=writer, description=description) This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |
From: <dva...@us...> - 2007-02-18 23:07:29
|
Revision: 1531 http://svn.sourceforge.net/epydoc/?rev=1531&view=rev Author: dvarrazzo Date: 2007-02-18 15:07:25 -0800 (Sun, 18 Feb 2007) Log Message: ----------- - Added interpreted text directives to refer to external API. - Epydoc HTML writer generates an index file suitable to be referred to. - Added a script wrapping a Docutils HTML writer with external API reference support. Modified Paths: -------------- trunk/epydoc/src/epydoc/cli.py trunk/epydoc/src/epydoc/docwriter/html.py trunk/epydoc/src/epydoc/markup/restructuredtext.py Added Paths: ----------- trunk/epydoc/src/epydoc/docwriter/xlink.py trunk/epydoc/src/scripts/apirst2html.py Modified: trunk/epydoc/src/epydoc/cli.py =================================================================== --- trunk/epydoc/src/epydoc/cli.py 2007-02-18 14:05:45 UTC (rev 1530) +++ trunk/epydoc/src/epydoc/cli.py 2007-02-18 23:07:25 UTC (rev 1531) @@ -76,6 +76,12 @@ import ConfigParser from epydoc.docwriter.html_css import STYLESHEETS as CSS_STYLESHEETS +# This module is only available if Docutils are in the system +try: + from epydoc.docwriter import xlink +except: + xlink = None + INHERITANCE_STYLES = ('grouped', 'listed', 'included') GRAPH_TYPES = ('classtree', 'callgraph', 'umlclasstree') ACTIONS = ('html', 'text', 'latex', 'dvi', 'ps', 'pdf', 'check') @@ -129,7 +135,8 @@ debug=epydoc.DEBUG, profile=False, graphs=[], list_classes_separately=False, graph_font=None, graph_font_size=None, include_source_code=True, pstat_files=[], simple_term=False, fail_on=None, - exclude=[], exclude_parse=[], exclude_introspect=[]) + exclude=[], exclude_parse=[], exclude_introspect=[], + external_api=[],external_api_file=[],external_api_root=[]) def parse_arguments(): # Construct the option parser. @@ -212,7 +219,7 @@ "list of available help topics") - generation_group = OptionGroup(optparser, 'Generation options') + generation_group = OptionGroup(optparser, 'Generation Options') optparser.add_option_group(generation_group) generation_group.add_option("--docformat", @@ -279,7 +286,7 @@ help=("Include a page with the process log (epydoc-log.html)")) - output_group = OptionGroup(optparser, 'Output options') + output_group = OptionGroup(optparser, 'Output Options') optparser.add_option_group(output_group) output_group.add_option("--name", @@ -325,7 +332,19 @@ "its own section, instead of listing them under their " "containing module.")) - graph_group = OptionGroup(optparser, 'Graph options') + # The group of external API options. + # Skip if the module couldn't be imported (usually missing docutils) + if xlink is not None: + link_group = OptionGroup(optparser, + xlink.ApiLinkReader.settings_spec[0]) + optparser.add_option_group(link_group) + + for help, names, opts in xlink.ApiLinkReader.settings_spec[2]: + opts = opts.copy() + opts['help'] = help + link_group.add_option(*names, **opts) + + graph_group = OptionGroup(optparser, 'Graph Options') optparser.add_option_group(graph_group) graph_group.add_option('--graph', @@ -363,7 +382,7 @@ "will be written to profile.out.")) - return_group = OptionGroup(optparser, 'Return value options') + return_group = OptionGroup(optparser, 'Return Value Options') optparser.add_option_group(return_group) return_group.add_option("--fail-on-error", @@ -547,6 +566,14 @@ elif optname in ('separate-classes', 'separate_classes'): options.list_classes_separately = _str_to_bool(val, optname) + # External API + elif optname in ('external-api', 'external_api'): + options.external_api.extend(val.replace(',', ' ').split()) + elif optname in ('external-api-file', 'external_api_file'): + options.external_api_file.append(val) + elif optname in ('external-api-root', 'external_api_root'): + options.external_api_root.append(val) + # Graph options elif optname == 'graph': graphtypes = val.replace(',', '').split() @@ -662,6 +689,14 @@ from epydoc import docstringparser docstringparser.DEFAULT_DOCFORMAT = options.docformat + # Configure the external API linking + if xlink is not None: + try: + xlink.ApiLinkReader.read_configuration(options, problematic=False) + except Exception, exc: + log.error("Error while configuring external API linking: %s: %s" + % (exc.__class__.__name__, exc)) + # Set the dot path if options.dotpath: from epydoc.docwriter import dotgraph Modified: trunk/epydoc/src/epydoc/docwriter/html.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/html.py 2007-02-18 14:05:45 UTC (rev 1530) +++ trunk/epydoc/src/epydoc/docwriter/html.py 2007-02-18 23:07:25 UTC (rev 1531) @@ -430,7 +430,7 @@ if isinstance(doc, ModuleDoc) and is_src_filename(doc.filename): self.modules_with_sourcecode.add(doc) self._num_files = (len(self.class_list) + 2*len(self.module_list) + - 12 + len(self.METADATA_INDICES)) + 13 + len(self.METADATA_INDICES)) if self._incl_sourcecode: self._num_files += len(self.modules_with_sourcecode) if self._split_ident_index: @@ -647,6 +647,9 @@ # Write the auto-redirect page. self._write(self.write_redirect_page, directory, 'redirect.html') + # Write the mapping object name -> URL + self._write(self.write_api_list, directory, 'api-objects.txt') + # Write the index.html files. # (this must be done last, since it might copy another file) self._files_written += 1 @@ -2953,6 +2956,35 @@ # \------------------------------------------------------------/ #//////////////////////////////////////////////////////////// + #{ URLs list + #//////////////////////////////////////////////////////////// + + def write_api_list(self, out): + """ + Write a list of mapping name->url for all the documented objects. + """ + # Construct a list of all the module & class pages that we're + # documenting. The redirect_url javascript will scan through + # this list, looking for a page name that matches the + # requested dotted name. + skip = (ModuleDoc, ClassDoc, type(UNKNOWN)) + for val_doc in self.module_list: + self.write_url_record(out, val_doc) + for var in val_doc.variables.itervalues(): + if not isinstance(var.value, skip): + self.write_url_record(out, var) + + for val_doc in self.class_list: + self.write_url_record(out, val_doc) + for var in val_doc.variables.itervalues(): + self.write_url_record(out, var) + + def write_url_record(self, out, obj): + url = self.url(obj) + if url is not None: + out("%s\t%s\n" % (obj.canonical_name, url)) + + #//////////////////////////////////////////////////////////// #{ Helper functions #//////////////////////////////////////////////////////////// Added: trunk/epydoc/src/epydoc/docwriter/xlink.py =================================================================== --- trunk/epydoc/src/epydoc/docwriter/xlink.py (rev 0) +++ trunk/epydoc/src/epydoc/docwriter/xlink.py 2007-02-18 23:07:25 UTC (rev 1531) @@ -0,0 +1,459 @@ +""" +A Docutils_ interpreted text role for cross-API reference support. + +This module allows a Docutils_ document to refer to elements defined in +external API documentation. It is possible to refer to many external API +from the same document. + +Each API documentation is assigned a new interpreted text role: using such +interpreted text, an user can specify an object name inside an API +documentation. The system will convert such text into an url and generate a +reference to it. For example, if the API ``db`` is defined, being a database +package, then a certain method may be referred as:: + + :db:`Connection.cursor()` + +To define a new API, an *index file* must be provided. This file contains +a mapping from the object name to the URL part required to resolve such object. + +Index file +---------- + +Each line in the the index file describes an object. + +Each line contains the fully qualified name of the object and the URL at which +the documentation is located. The fields are separated by a ``<tab>`` +character. + +The URL's in the file are relative from the documentation root: the system can +be configured to add a prefix in front of each returned URL. + +Allowed names +------------- + +When a name is used in an API text role, it is split over any *separator*. +The separators defined are '``.``', '``::``', '``->``'. All the text from the +first noise char (neither a separator nor alphanumeric or '``_``') is +discarded. The same algorithm is applied when the index file is read. + +First the sequence of name parts is looked for in the provided index file. +If no matching name is found, a partial match against the trailing part of the +names in the index is performed. If no object is found, or if the trailing part +of the name may refer to many objects, a warning is issued and no reference +is created. + +Configuration +------------- + +This module provides the class `ApiLinkReader` a replacement for the Docutils +standalone reader. Such reader specifies the settings required for the +API canonical roles configuration. The same command line options are exposed by +Epydoc. + +The script ``apirst2html.py`` is a frontend for the `ApiLinkReader` reader. + +API Linking Options:: + + --external-api=NAME + Define a new API document. A new interpreted text + role NAME will be added. + --external-api-file=NAME:FILENAME + Use records in FILENAME to resolve objects in the API + named NAME. + --external-api-root=NAME:STRING + Use STRING as prefix for the URL generated from the + API NAME. + +.. _Docutils: http://docutils.sourceforge.net/ +""" + +# $Id$ +__version__ = "$Revision$"[11:-2] +__author__ = "Daniele Varrazzo" +__copyright__ = "Copyright (C) 2007 by Daniele Varrazzo" +__docformat__ = 'reStructuredText en' + +import re +import sys + +from docutils.parsers.rst import roles +from docutils import nodes, utils + +class UrlGenerator: + """ + Generate URL from an object name. + """ + class IndexAmbiguous(IndexError): + """ + The name looked for is ambiguous + """ + + def get_url(self, name): + """Look for a name and return the matching URL documentation. + + First look for a fully qualified name. If not found, try with partial + name. + + If no url exists for the given object, return `None`. + + :Parameters: + `name` : `str` + the name to look for + + :return: the URL that can be used to reach the `name` documentation. + `None` if no such URL exists. + :rtype: `str` + + :Exceptions: + - `IndexError`: no object found with `name` + - `DocUrlGenerator.IndexAmbiguous` : more than one object found with + a non-fully qualified name; notice that this is an ``IndexError`` + subclass + """ + raise NotImplementedError + + def get_canonical_name(self, name): + """ + Convert an object name into a canonical name. + + the canonical name of an object is a tuple of strings containing its + name fragments, splitted on any allowed separator ('``.``', '``::``', + '``->``'). + + Noise such parenthesis to indicate a function is discarded. + + :Parameters: + `name` : `str` + an object name, such as ``os.path.prefix()`` or ``lib::foo::bar`` + + :return: the fully qualified name such ``('os', 'path', 'prefix')`` and + ``('lib', 'foo', 'bar')`` + :rtype: `tuple` of `str` + """ + rv = [] + for m in self._SEP_RE.finditer(name): + groups = m.groups() + if groups[0] is not None: + rv.append(groups[0]) + elif groups[2] is not None: + break + + return tuple(rv) + + _SEP_RE = re.compile(r"""(?x) + # Tokenize the input into keyword, separator, noise + ([a-zA-Z0-9_]+) | # A keyword is a alphanum word + ( \. | \:\: | \-\> ) | # These are the allowed separators + (.) # If it doesn't fit, it's noise. + # Matching a single noise char is enough, because it + # is used to break the tokenization as soon as some noise + # is found. + """) + + +class VoidUrlGenerator(UrlGenerator): + """ + Don't actually know any url, but don't report any error. + + Useful if an index file is not available, but a document linking to it + is to be generated, and warnings are to be avoided. + + Don't report any object as missing, Don't return any url anyway. + """ + def get_url(self, name): + return None + + +class DocUrlGenerator(UrlGenerator): + """ + Read a *documentation index* and generate URL's for it. + """ + def __init__(self): + self._exact_matches = {} + """ + A map from an object fully qualified name to its URL. + + Values are both the name as tuple of fragments and as read from the + records (see `load_records()`), mostly to help `_partial_names` to + perform lookup for unambiguous names. + """ + + self._partial_names= {} + """ + A map from partial names to the fully qualified names they may refer. + + The keys are the possible left sub-tuples of fully qualified names, + the values are list of strings as provided by the index. + + If the list for a given tuple contains a single item, the partial + match is not ambuguous. In this case the string can be looked up in + `_exact_matches`. + + If the name fragment is ambiguous, a warning may be issued to the user. + The items can be used to provide an informative message to the user, + to help him qualifying the name in a unambiguous manner. + """ + + self.prefix = '' + """ + Prefix portion for the URL's returned by `get_url()`. + """ + + def get_url(self, name): + cname = self.get_canonical_name(name) + url = self._exact_matches.get(cname, None) + if url is None: + + # go for a partial match + vals = self._partial_names.get(cname) + if len(vals) == 1: + url = self._exact_matches[vals[0]] + + elif not vals: + raise IndexError( + "no object named '%s' found" % (name)) + + else: + raise self.IndexAmbiguous( + "found %d objects that '%s' may refer to: %s" + % (len(vals), name, ", ".join(["'%s'" % n for n in vals]))) + + return self.prefix + url + + #{ Content loading + # --------------- + + def clear(self): + """ + Clear the current class content. + """ + self._exact_matches.clear() + self._partial_names.clear() + + def load_index(self, f): + """ + Read the content of an index file. + + Populate the internal maps with the file content using `load_records()`. + + :Parameters: + f : `str` or file + a file name or file-like object fron which read the index. + """ + if isinstance(f, basestring): + f = file(f) + + self.load_records(self._iter_tuples(f)) + + def _iter_tuples(self, f): + """Iterate on a file returning 2-tuples.""" + for row in f: + yield row.rstrip().split('\t', 1) + + def load_records(self, records): + """ + Read a sequence of pairs name -> url and populate the internal maps. + + :Parameters: + records : iterable + the sequence of pairs (*name*, *url*) to add to the maps. + """ + for name, url in records: + cname = self.get_canonical_name(name) + if not cname: + # Have to decide how to warn. + raise NotImplementedError("WARNING NAME NOT VALID") + + self._exact_matches[name] = url + self._exact_matches[cname] = url + + # Link the different ambiguous fragments to the url + for i in range(1, len(cname)): + self._partial_names.setdefault(cname[i:], []).append(name) + +#{ API register +# ------------ + +api_register = {} +""" +Mapping from the API name to the `UrlGenerator` to be used. +""" + +def register_api(name, generator=None): + """Register the API `name` into the `api_register`. + + A registered API is available to the markup as the interpreted text + role ``name``. + + If a `generator` is not provided, register a `VoidUrlGenerator` instance: + in this case no warning will be issued for missing names, but no URL will + be generated and all the dotted names will simply be rendered as literals. + + :Parameters: + `name` : `str` + the name of the generator to be registered + `generator` : `UrlGenerator` + the object to register to translate names into URLs. + """ + if generator is None: + generator = VoidUrlGenerator() + + api_register[name] = generator + +def set_api_file(name, file): + """Set an URL generator populated with data from `file`. + + Use `file` to populate a new `DocUrlGenerator` instance and register it + as `name`. + + :Parameters: + `name` : `str` + the name of the generator to be registered + `file` : `str` or file + the file to parse populate the URL generator + """ + generator = DocUrlGenerator() + generator.load_index(file) + register_api(name, generator) + +def set_api_root(name, prefix): + """Set the root for the URLs returned by a registered URL generator. + + :Parameters: + `name` : `str` + the name of the generator to be updated + `prefix` : `str` + the prefix for the generated URL's + + :Exceptions: + - `IndexError`: `name` is not a registered generator + """ + api_register[name].prefix = prefix + +def create_api_role(name, problematic): + """ + Create and register a new role to create links for an API documentation. + + Create a role called `name`, which will use the ``name`` registered + URL resolver to create a link for an object. + """ + def resolve_api_name(n, rawtext, text, lineno, inliner, + options={}, content=[]): + + # node in monotype font + text = utils.unescape(text) + node = nodes.literal(rawtext, text, **options) + + # Get the resolver from the register and create an url from it. + try: + url = api_register[n].get_url(text) + except IndexError, exc: + msg = inliner.reporter.warning(str(exc), line=lineno) + if problematic: + prb = inliner.problematic(rawtext, text, msg) + return [prb], [msg] + else: + return [node], [] + + if url is not None: + node = nodes.reference(rawtext, '', node, refuri=url, **options) + return [node], [] + + roles.register_local_role(name, resolve_api_name) + + +#{ Command line parsing +# -------------------- + +#from docutils import SettingsSpec +from optparse import OptionValueError + +def split_name(value): + """ + Split an option in form ``NAME:VALUE`` and check if ``NAME`` exists. + """ + parts = value.split(':', 1) + if len(parts) != 2: + raise OptionValueError( + "option value must be specified as NAME:VALUE; got '%s' instead" + % value) + + name, val = parts + + if name not in api_register: + raise OptionValueError( + "the name '%s' has not been registered; use --external-api" + % name) + + return (name, val) + +from docutils.readers.standalone import Reader + +class ApiLinkReader(Reader): + """ + A Docutils standalone reader allowing external documentation links. + + The reader configure the url resolvers at the time `read()` is invoked the + first time. + """ + #: The option parser configuration. + settings_spec = ( + 'API Linking Options', + None, + (( + 'Define a new API document. A new interpreted text role NAME will be ' + 'added.', + ['--external-api'], + {'metavar': 'NAME', 'action': 'append'} + ), ( + 'Use records in FILENAME to resolve objects in the API named NAME.', + ['--external-api-file'], + {'metavar': 'NAME:FILENAME', 'action': 'append'} + ), ( + 'Use STRING as prefix for the URL generated from the API NAME.', + ['--external-api-root'], + {'metavar': 'NAME:STRING', 'action': 'append'} + ),)) + Reader.settings_spec + + def read(self, source, parser, settings): + self.read_configuration(settings, problematic=True) + return Reader.read(self, source, parser, settings) + + def read_configuration(self, settings, problematic=True): + """ + Read the configuration for the configured URL resolver. + + Register a new role for each configured API. + + :Parameters: + `settings` + the settings structure containing the options to read. + `problematic` : `bool` + if True, the registered role will create problematic nodes in + case of failed references. If False, a warning will be raised + anyway, but the output will appear as an ordinary literal. + """ + # Read config only once + if hasattr(self, '_conf'): + return + ApiLinkReader._conf = True + + try: + if settings.external_api is not None: + for name in settings.external_api: + register_api(name) + create_api_role(name, problematic=problematic) + + if settings.external_api_file is not None: + for name, file in map(split_name, settings.external_api_file): + set_api_file(name, file) + + if settings.external_api_root is not None: + for name, root in map(split_name, settings.external_api_root): + set_api_root(name, root) + + except OptionValueError, exc: + print >>sys.stderr, "%s: %s" % (exc.__class__.__name__, exc) + sys.exit(2) + + read_configuration = classmethod(read_configuration) Property changes on: trunk/epydoc/src/epydoc/docwriter/xlink.py ___________________________________________________________________ Name: svn:keywords + Id Revision Name: svn:eol-style + native Modified: trunk/epydoc/src/epydoc/markup/restructuredtext.py =================================================================== --- trunk/epydoc/src/epydoc/markup/restructuredtext.py 2007-02-18 14:05:45 UTC (rev 1530) +++ trunk/epydoc/src/epydoc/markup/restructuredtext.py 2007-02-18 23:07:25 UTC (rev 1531) @@ -88,6 +88,7 @@ from epydoc.markup import * from epydoc.apidoc import ModuleDoc, ClassDoc from epydoc.docwriter.dotgraph import * +from epydoc.docwriter.xlink import ApiLinkReader from epydoc.markup.doctest import doctest_to_html, doctest_to_latex, \ HTMLDoctestColorizer @@ -207,7 +208,7 @@ def __repr__(self): return '<ParsedRstDocstring: ...>' -class _EpydocReader(StandaloneReader): +class _EpydocReader(ApiLinkReader): """ A reader that captures all errors that are generated by parsing, and appends them to a list. @@ -220,18 +221,18 @@ version = [int(v) for v in docutils.__version__.split('.')] version += [ 0 ] * (3 - len(version)) if version < [0,4,0]: - default_transforms = list(StandaloneReader.default_transforms) + default_transforms = list(ApiLinkReader.default_transforms) try: default_transforms.remove(docutils.transforms.frontmatter.DocInfo) except ValueError: pass else: def get_transforms(self): - return [t for t in StandaloneReader.get_transforms(self) + return [t for t in ApiLinkReader.get_transforms(self) if t != docutils.transforms.frontmatter.DocInfo] del version def __init__(self, errors): self._errors = errors - StandaloneReader.__init__(self) + ApiLinkReader.__init__(self) def new_document(self): document = new_document(self.source.source_path, self.settings) Added: trunk/epydoc/src/scripts/apirst2html.py =================================================================== --- trunk/epydoc/src/scripts/apirst2html.py (rev 0) +++ trunk/epydoc/src/scripts/apirst2html.py 2007-02-18 23:07:25 UTC (rev 1531) @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: iso-8859-1 -*- + +"""An HTML writer supporting link to external documentation. + +This module is a frontend for the Docutils_ HTML writer. It allows a document +to reference objects documented in the API documentation generated by +extraction tools such as Doxygen_ or Epydoc_. + +.. _Docutils: http://docutils.sourceforge.net/ +.. _Doxygen: http://www.doxygen.org/ +.. _Epydoc: http://epydoc.sourceforge.net/ +""" + +# $Id$ +__version__ = "$Revision$"[11:-2] +__author__ = "Daniele Varrazzo" +__copyright__ = "Copyright (C) 2007 by Daniele Varrazzo" +__docformat__ = 'reStructuredText en' + +try: + import locale + locale.setlocale(locale.LC_ALL, '') +except: + pass + +# We have to do some path magic to prevent Python from getting +# confused about the difference between the ``epydoc.py`` script, and the +# real ``epydoc`` package. So remove ``sys.path[0]``, which contains the +# directory of the script. +import sys, os.path +script_path = os.path.abspath(sys.path[0]) +sys.path = [p for p in sys.path if os.path.abspath(p) != script_path] + +import epydoc.docwriter.xlink as xlink + +from docutils.core import publish_cmdline, default_description +description = ('Generates (X)HTML documents with API documentation links. ' + + default_description) +publish_cmdline(reader=xlink.ApiLinkReader(), writer_name='html', + description=description) Property changes on: trunk/epydoc/src/scripts/apirst2html.py ___________________________________________________________________ Name: svn:executable + * Name: svn:keywords + Id Revision Name: svn:eol-style + native This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |