diff --git a/CHANGES b/CHANGES index 0367be1a0..21d0f72c3 100644 --- a/CHANGES +++ b/CHANGES @@ -94,9 +94,20 @@ Release 1.0 (in development) * Added ``htmltitle`` block in layout template. -Release 0.6.4 (in development) +Release 0.6.5 (in development) ============================== +* #321: Fix link generation in the LaTeX builder. + + +Release 0.6.4 (Jan 12, 2010) +============================ + +* Improve the handling of non-Unicode strings in the configuration. + +* #316: Catch OSErrors occurring when calling graphviz with + arguments it doesn't understand. + * Restore compatibility with Pygments >= 1.2. * #295: Fix escaping of hyperref targets in LaTeX output. diff --git a/doc/_templates/index.html b/doc/_templates/index.html index 0f4d4c641..a68a62514 100644 --- a/doc/_templates/index.html +++ b/doc/_templates/index.html @@ -46,8 +46,10 @@

The Python documentation and this page are different examples of Sphinx in use. - You can also download a PDF version - of the Sphinx documentation, generated from the LaTeX Sphinx produces. + You can also download PDF versions of the Sphinx documentation: + a version generated from + the LaTeX Sphinx produces, and a + version generated by rst2pdf.

For examples of how Sphinx source files look, use the “Show source” diff --git a/doc/faq.rst b/doc/faq.rst index 05b39ef4d..81204c08d 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -63,6 +63,39 @@ github pages `_ to prepare Sphinx HTML output. +Google Analytics + You can use a custom ``layout.html`` template, like this: + + .. code-block:: html+django + + {% extends "!layout.html" %} + + {%- block extrahead %} + {{ super() }} + + {% endblock %} + + {% block footer %} + {{ super() }} +

+ {% endblock %} + .. _api role: http://git.savannah.gnu.org/cgit/kenozooid.git/tree/doc/extapi.py .. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py diff --git a/setup.py b/setup.py index 981270c88..aedfb8e78 100644 --- a/setup.py +++ b/setup.py @@ -21,19 +21,20 @@ Sphinx uses reStructuredText as its markup language, and many of its strengths come from the power and straightforwardness of reStructuredText and its parsing and translating suite, the Docutils. -Although it is still under constant development, the following features -are already present, work fine and can be seen "in action" in the Python docs: +Among its features are the following: -* Output formats: HTML (including Windows HTML Help), plain text and LaTeX, - for printable PDF versions +* Output formats: HTML (including derivative formats such as HTML Help, Epub + and Qt Help), plain text and LaTeX or direct PDF output using rst2pdf * Extensive cross-references: semantic markup and automatic links for functions, classes, glossary terms and similar pieces of information * Hierarchical structure: easy definition of a document tree, with automatic links to siblings, parents and children * Automatic indices: general index as well as a module index * Code handling: automatic highlighting using the Pygments highlighter +* Flexible HTML output using the Jinja 2 templating engine * Various extensions are available, e.g. for automatic testing of snippets - and inclusion of appropriately formatted docstrings. + and inclusion of appropriately formatted docstrings +* Setuptools integration A development egg can be found `here `_. diff --git a/sphinx/application.py b/sphinx/application.py index f95c5fe3b..395a21629 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -98,6 +98,7 @@ class Sphinx(object): # read config self.tags = Tags(tags) self.config = Config(confdir, CONFIG_FILENAME, confoverrides, self.tags) + self.config.check_unicode(self.warn) # load all extension modules for extension in self.config.extensions: diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index cb800556c..a56919dbd 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -72,7 +72,8 @@ class StandaloneHTMLBuilder(Builder): embedded = False # for things like HTML help or Qt help: suppresses sidebar # This is a class attribute because it is mutated by Sphinx.add_javascript. - script_files = ['_static/jquery.js', '_static/doctools.js'] + script_files = ['_static/jquery.js', '_static/underscore.js', + '_static/doctools.js'] # Dito for this one. css_files = [] @@ -731,7 +732,14 @@ class StandaloneHTMLBuilder(Builder): self.app.emit('html-page-context', pagename, templatename, ctx, event_arg) - output = self.templates.render(templatename, ctx) + try: + output = self.templates.render(templatename, ctx) + except UnicodeError: + self.warn("a Unicode error occurred when rendering the page %s. " + "Please make sure all config values that contain " + "non-ASCII content are Unicode strings." % pagename) + return + if not outfilename: outfilename = self.get_outfilename(pagename) # outfilename's path is in general different from self.outdir diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py index 0db30d553..751bf28cd 100644 --- a/sphinx/builders/latex.py +++ b/sphinx/builders/latex.py @@ -102,7 +102,13 @@ class LaTeXBuilder(Builder): doctree.settings.title = title doctree.settings.docname = docname doctree.settings.docclass = docclass - docwriter.write(doctree, destination) + try: + docwriter.write(doctree, destination) + except UnicodeError: + self.warn("a Unicode error occurred when writing the output. " + "Please make sure all config values that contain " + "non-ASCII content are Unicode strings.") + return self.info("done") def assemble_doctree(self, indexfile, toctree_only, appendices): diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 8a069b325..dc3269e23 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -198,10 +198,13 @@ def main(argv): tbpath = save_traceback() print >>error, red('The full traceback has been saved ' 'in %s, if you want to report the ' - 'issue to the author.' % tbpath) + 'issue to the developers.' % tbpath) print >>error, ('Please also report this if it was a user ' 'error, so that a better error message ' 'can be provided next time.') - print >>error, ('Send reports to sphinx-dev@googlegroups.com. ' - 'Thanks!') + print >>error, ( + 'Either send bugs to the mailing list at ' + ',\n' + 'or report them in the tracker at ' + '. Thanks!') return 1 diff --git a/sphinx/config.py b/sphinx/config.py index 23e5698b1..6cf8a270b 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -10,9 +10,13 @@ """ import os +import re from os import path from sphinx.util import make_filename +from sphinx.errors import ConfigError + +nonascii_re = re.compile(r'[\x80-\xff]') class Config(object): @@ -140,20 +144,35 @@ class Config(object): self.values = Config.config_values.copy() config = {} if dirname is not None: - config['__file__'] = path.join(dirname, filename) + config_file = path.join(dirname, filename) + config['__file__'] = config_file config['tags'] = tags olddir = os.getcwd() try: - os.chdir(dirname) - execfile(config['__file__'], config) + try: + os.chdir(dirname) + execfile(config['__file__'], config) + except SyntaxError, err: + raise ConfigError('There is a syntax error in your ' + 'configuration file: ' + str(err)) finally: os.chdir(olddir) + self._raw_config = config # these two must be preinitialized because extensions can add their # own config values self.setup = config.get('setup', None) self.extensions = config.get('extensions', []) + def check_unicode(self, warn): + # check all string values for non-ASCII characters in + # bytestrings, since that can + for name, value in self._raw_config.iteritems(): + if isinstance(value, str) and nonascii_re.search(value): + warn('the config value %r is set to a string with non-ASCII ' + 'characters; this can lead to Unicode errors occurring. ' + 'Please use Unicode strings, e.g. u"Content".' % name) + def init_values(self): config = self._raw_config for valname, value in self.overrides.iteritems(): diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 4afbcdbfc..40f63e0be 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -369,7 +369,7 @@ class StandardDomain(Domain): docname, labelid = self.data['objects'].get((typ, target), ('', '')) if not docname: if typ == 'term': - env.warn(node['refdoc'], + env.warn(node.get('refdoc', fromdocname), 'term not in glossary: %s' % target, node.line) return None else: diff --git a/sphinx/environment.py b/sphinx/environment.py index 38be61e43..dc5059369 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1222,6 +1222,7 @@ class BuildEnvironment: typ = node['reftype'] target = node['reftarget'] + refdoc = node.get('refdoc', fromdocname) try: if node.has_key('refdomain') and node['refdomain']: @@ -1240,7 +1241,7 @@ class BuildEnvironment: docname, labelid = self.anonlabels.get(target, ('','')) sectname = node.astext() if not docname: - self.warn(node['refdoc'], 'undefined label: %s' % + self.warn(refdoc, 'undefined label: %s' % target, node.line) else: # reference to named label; the final node will @@ -1248,8 +1249,7 @@ class BuildEnvironment: docname, labelid, sectname = self.labels.get(target, ('','','')) if not docname: - self.warn( - node['refdoc'], + self.warn(refdoc, 'undefined label: %s' % target + ' -- if you ' 'don\'t give a link caption the label must ' 'precede a section header.', node.line) @@ -1273,9 +1273,9 @@ class BuildEnvironment: elif typ == 'doc': # directly reference to document by source name; # can be absolute or relative - docname = docname_join(node['refdoc'], target) + docname = docname_join(refdoc, target) if docname not in self.all_docs: - self.warn(node['refdoc'], + self.warn(refdoc, 'unknown document: %s' % docname, node.line) else: if node['refexplicit']: @@ -1300,8 +1300,7 @@ class BuildEnvironment: # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.labels.get(target, ('','','')) if not docname: - #self.warn(node['refdoc'], - # 'unknown keyword: %s' % target) + #self.warn(refdoc, 'unknown keyword: %s' % target) pass else: newnode = make_refnode(builder, fromdocname, docname, diff --git a/sphinx/errors.py b/sphinx/errors.py index ca70fe4b2..6e8c4e1c6 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -44,6 +44,10 @@ class ExtensionError(SphinxError): return parent_str +class ConfigError(SphinxError): + category = 'Configuration error' + + class ThemeError(SphinxError): category = 'Theme error' diff --git a/sphinx/ext/autosummary/templates/autosummary/module.rst b/sphinx/ext/autosummary/templates/autosummary/module.rst index cc76c9e00..c14456ba1 100644 --- a/sphinx/ext/autosummary/templates/autosummary/module.rst +++ b/sphinx/ext/autosummary/templates/autosummary/module.rst @@ -30,7 +30,7 @@ .. rubric:: Exceptions .. autosummary:: - {% for item in classes %} + {% for item in exceptions %} {{ item }} {%- endfor %} {% endif %} diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 2f78d0887..3cd069c61 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -24,7 +24,7 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx.errors import SphinxError -from sphinx.util import ensuredir, ENOENT +from sphinx.util import ensuredir, ENOENT, EPIPE from sphinx.util.compat import Directive @@ -113,6 +113,10 @@ def render_dot(self, code, options, format, prefix='graphviz'): ensuredir(path.dirname(outfn)) + # graphviz expects UTF-8 by default + if isinstance(code, unicode): + code = code.encode('utf-8') + dot_args = [self.builder.config.graphviz_dot] dot_args.extend(self.builder.config.graphviz_dot_args) dot_args.extend(options) @@ -129,10 +133,17 @@ def render_dot(self, code, options, format, prefix='graphviz'): self.builder.config.graphviz_dot) self.builder._graphviz_warned_dot = True return None, None - # graphviz expects UTF-8 by default - if isinstance(code, unicode): - code = code.encode('utf-8') - stdout, stderr = p.communicate(code) + try: + # Graphviz may close standard input when an error occurs, + # resulting in a broken pipe on communicate() + stdout, stderr = p.communicate(code) + except OSError, err: + if err.errno != EPIPE: + raise + # in this case, read the standard output and standard error streams + # directly, to get the error message(s) + stdout, stderr = p.stdout.read(), p.stderr.read() + p.wait() if p.returncode != 0: raise GraphvizError('dot exited with error:\n[stderr]\n%s\n' '[stdout]\n%s' % (stderr, stdout)) diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index bf3375ff2..e91c4f3eb 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -9,6 +9,11 @@ * */ +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + /** * make the code below compatible with browsers without * an installed firebug like debugger @@ -54,7 +59,7 @@ jQuery.getQueryParameters = function(s) { result[key] = [value]; } return result; -} +}; /** * small function to check if an array contains @@ -66,7 +71,7 @@ jQuery.contains = function(arr, item) { return true; } return false; -} +}; /** * highlight a given string on a jquery object by wrapping it in @@ -96,7 +101,7 @@ jQuery.fn.highlightText = function(text, className) { return this.each(function() { highlight(this); }); -} +}; /** * Small JavaScript module for the documentation. diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 67bf695d5..c12471063 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -312,7 +312,7 @@ var Search = { var tmp = query.split(/\s+/); var object = (tmp.length == 1) ? tmp[0].toLowerCase() : null; for (var i = 0; i < tmp.length; i++) { - if (stopwords.indexOf(tmp[i]) != -1 || tmp[i].match(/^\d+$/)) { + if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/)) { // skip this word continue; } diff --git a/sphinx/themes/basic/static/underscore.js b/sphinx/themes/basic/static/underscore.js new file mode 100644 index 000000000..9146e0860 --- /dev/null +++ b/sphinx/themes/basic/static/underscore.js @@ -0,0 +1,16 @@ +(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)}); +return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length); +var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false; +if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length== +0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&& +a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g, +" ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments); +o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); diff --git a/sphinx/themes/default/static/default.css_t b/sphinx/themes/default/static/default.css_t index fb9fbe898..cddc85f72 100644 --- a/sphinx/themes/default/static/default.css_t +++ b/sphinx/themes/default/static/default.css_t @@ -210,6 +210,18 @@ div.admonition p.admonition-title + p { display: inline; } +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + div.note { background-color: #eee; border: 1px solid #ccc; diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index bd263545d..e6c0a661c 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -34,6 +34,7 @@ from sphinx.errors import PycodeError # Errnos that we need. EEXIST = getattr(errno, 'EEXIST', 0) ENOENT = getattr(errno, 'ENOENT', 0) +EPIPE = getattr(errno, 'EPIPE', 0) # Generally useful regular expressions. ws_re = re.compile(r'\s+') diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index eb8128ba3..ba7de6249 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -1046,13 +1046,15 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\\href{%s}{' % self.encode_uri(uri)) self.context.append('}') elif uri.startswith('#'): - self.body.append('\\hyperlink{%s}{' % uri[1:]) + # references to labels + self.body.append('\\hyperlink{%s}{' % self.idescape(uri[1:])) self.context.append('}') elif uri.startswith('%'): + # references to documents or labels inside documents hashindex = uri.find('#') targetname = (hashindex == -1) and '--doc-' + uri[1:] \ or uri[hashindex+1:] - self.body.append('\\hyperlink{%s}{' % targetname) + self.body.append('\\hyperlink{%s}{' % self.idescape(targetname)) self.context.append('}') elif uri.startswith('@token'): if self.in_production_list: diff --git a/tests/test_config.py b/tests/test_config.py index 426b808ea..0ccdd9091 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -12,7 +12,8 @@ from util import * -from sphinx.application import ExtensionError +from sphinx.config import Config +from sphinx.errors import ExtensionError, ConfigError @with_app(confoverrides={'master_doc': 'master', 'nonexisting_value': 'True', @@ -77,3 +78,19 @@ def test_extension_values(app): 'html_title', 'x', True) raises_msg(ExtensionError, 'already present', app.add_config_value, 'value_from_ext', 'x', True) + + +@with_tempdir +def test_errors_warnings(dir): + # test the error for syntax errors in the config file + write_file(dir / 'conf.py', 'project = \n') + raises_msg(ConfigError, 'conf.py', Config, dir, 'conf.py', {}, None) + + # test the warning for bytestrings with non-ascii content + write_file(dir / 'conf.py', '# -*- coding: latin-1\nproject = "foo\xe4"\n') + cfg = Config(dir, 'conf.py', {}, None) + warned = [False] + def warn(msg): + warned[0] = True + cfg.check_unicode(warn) + assert warned[0] diff --git a/utils/check_sources.py b/utils/check_sources.py index ec7695d57..6f78c26e0 100755 --- a/utils/check_sources.py +++ b/utils/check_sources.py @@ -154,8 +154,7 @@ def check_whitespace_and_spelling(fn, lines): yield lno+1, '"%s" used' % word -bad_tags = ('', '', '' - '
', '', '', '', '', '', '
', '