diff --git a/CHANGES b/CHANGES index 92b42dfb0..546e06a74 100644 --- a/CHANGES +++ b/CHANGES @@ -14,8 +14,8 @@ Documentation ------------- -Release 1.3.1 (in development) -=============================== +Release 1.3.1 (released Mar 17, 2015) +===================================== Bugs fixed ---------- @@ -23,7 +23,16 @@ Bugs fixed * #1769: allows generating quickstart files/dirs for destination dir that doesn't overwrite existent files/dirs. Thanks to WAKAYAMA shirou. * #1773: sphinx-quickstart doesn't accept non-ASCII character as a option argument. - +* #1766: the message "least Python 2.6 to run" is at best misleading. +* #1772: cross reference in docstrings like ``:param .write:`` breaks building. +* #1770, #1774: ``literalinclude`` with empty file occurs exception. Thanks to + Takayuki Hirai. +* #1777: Sphinx 1.3 can't load extra theme. Thanks to tell-k. +* #1776: ``source_suffix = ['.rst']`` cause unfriendly error on prior version. +* #1771: automated .mo building doesn't work properly. +* #1783: Autodoc: Python2 Allow unicode string in __all__. + Thanks to Jens Hedegaard Nielsen. +* #1781: Setting `html_domain_indices` to a list raises a type check warnings. Release 1.3 (released Mar 10, 2015) =================================== diff --git a/doc/markup/misc.rst b/doc/markup/misc.rst index fd31480a6..2855f1fad 100644 --- a/doc/markup/misc.rst +++ b/doc/markup/misc.rst @@ -263,5 +263,5 @@ following directive exists: format and the builder name. Note that the current builder tag is not available in ``conf.py``, it is - only available after the builder is intialized. + only available after the builder is initialized. diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 720c5a8df..1abbd23aa 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -55,7 +55,7 @@ def build_main(argv=sys.argv): """Sphinx build "main" command-line entry.""" if (sys.version_info[:3] < (2, 6, 0) or (3, 0, 0) <= sys.version_info[:3] < (3, 3, 0)): - sys.stderr.write('Error: Sphinx requires at least Python 2.6 to run.\n') + sys.stderr.write('Error: Sphinx requires at least Python 2.6 or 3.3 to run.\n') return 1 try: from sphinx import cmdline diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 60fccf233..38cde6123 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -21,7 +21,8 @@ except ImportError: from docutils import nodes from sphinx.util import i18n, path_stabilize -from sphinx.util.osutil import SEP, relative_uri, find_catalog +from sphinx.util.osutil import SEP, relative_uri +from sphinx.util.i18n import find_catalog from sphinx.util.console import bold, darkgreen from sphinx.util.parallel import ParallelTasks, SerialTasks, make_chunks, \ parallel_available @@ -166,9 +167,11 @@ class Builder(object): catalog.write_mo(self.config.language) def compile_all_catalogs(self): - catalogs = i18n.get_catalogs( + catalogs = i18n.find_catalog_source_files( [path.join(self.srcdir, x) for x in self.config.locale_dirs], - self.config.language, True) + self.config.language, + gettext_compact=self.config.gettext_compact, + force_all=True) message = 'all of %d po files' % len(catalogs) self.compile_catalogs(catalogs, message) @@ -179,17 +182,19 @@ class Builder(object): return dom specified_domains = set(map(to_domain, specified_files)) - catalogs = i18n.get_catalogs( + catalogs = i18n.find_catalog_source_files( [path.join(self.srcdir, x) for x in self.config.locale_dirs], - self.config.language, True) - catalogs = [f for f in catalogs if f.domain in specified_domains] + self.config.language, + domains=list(specified_domains), + gettext_compact=self.config.gettext_compact) message = 'targets for %d po files that are specified' % len(catalogs) self.compile_catalogs(catalogs, message) def compile_update_catalogs(self): - catalogs = i18n.get_catalogs( + catalogs = i18n.find_catalog_source_files( [path.join(self.srcdir, x) for x in self.config.locale_dirs], - self.config.language) + self.config.language, + gettext_compact=self.config.gettext_compact) message = 'targets for %d po files that are out of date' % len(catalogs) self.compile_catalogs(catalogs, message) diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index c23675d36..ec6a18805 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -23,7 +23,8 @@ from six import iteritems from sphinx.builders import Builder from sphinx.util import split_index_msg from sphinx.util.nodes import extract_messages, traverse_translatable_index -from sphinx.util.osutil import safe_relpath, ensuredir, find_catalog, SEP +from sphinx.util.osutil import safe_relpath, ensuredir, SEP +from sphinx.util.i18n import find_catalog from sphinx.util.console import darkgreen, purple, bold from sphinx.locale import pairindextypes diff --git a/sphinx/config.py b/sphinx/config.py index ac3358dda..090875a79 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -28,6 +28,10 @@ if PY3: CONFIG_EXIT_ERROR = "The configuration file (or one of the modules it imports) " \ "called sys.exit()" +IGNORE_CONFIG_TYPE_CHECKS = ( + 'html_domain_indices', 'latex_domain_indices', 'texinfo_domain_indices' +) + class Config(object): """ @@ -288,6 +292,8 @@ class Config(object): # NB. since config values might use l_() we have to wait with calling # this method until i18n is initialized for name in self._raw_config: + if name in IGNORE_CONFIG_TYPE_CHECKS: + continue # for a while, ignore multiple types config value. see #1781 if name not in Config.config_values: continue # we don't know a default value default, dummy_rebuild = Config.config_values[name] diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 1f7a856a7..5043a31e4 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -219,7 +219,7 @@ class LiteralInclude(Directive): lines = self.read_with_encoding(filename, document, codec_info, encoding) - if not isinstance(lines[0], string_types): + if lines and not isinstance(lines[0], string_types): return lines diffsource = self.options.get('diff') diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index c9a03de2b..b64e797b0 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -90,13 +90,15 @@ class PyXrefMixin(object): result = super(PyXrefMixin, self).make_xref(rolename, domain, target, innernode, contnode) result['refspecific'] = True - if target.startswith('.'): - result['reftarget'] = target[1:] - result[0][0] = nodes.Text(target[1:]) - if target.startswith('~'): - result['reftarget'] = target[1:] - title = target.split('.')[-1] - result[0][0] = nodes.Text(title) + if target.startswith(('.', '~')): + prefix, result['reftarget'] = target[0], target[1:] + if prefix == '.': + text = target[1:] + elif prefix == '~': + text = target.split('.')[-1] + for node in result.traverse(nodes.Text): + node.parent[node.parent.index(node)] = nodes.Text(text) + break return result diff --git a/sphinx/environment.py b/sphinx/environment.py index 79212e96b..11f395b43 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -40,7 +40,8 @@ from sphinx import addnodes from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ FilenameUniqDict, get_figtype, import_object from sphinx.util.nodes import clean_astext, make_refnode, WarningStream, is_translatable -from sphinx.util.osutil import SEP, find_catalog_files, getcwd, fs_encoding +from sphinx.util.osutil import SEP, getcwd, fs_encoding +from sphinx.util.i18n import find_catalog_files from sphinx.util.console import bold, purple from sphinx.util.matching import compile_matchers from sphinx.util.parallel import ParallelTasks, parallel_available, make_chunks diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index bf0ad5fa9..ff6a30ecc 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -17,7 +17,7 @@ import inspect import traceback from types import FunctionType, BuiltinFunctionType, MethodType -from six import iteritems, itervalues, text_type, class_types +from six import iteritems, itervalues, text_type, class_types, string_types from docutils import nodes from docutils.utils import assemble_option_dict from docutils.statemachine import ViewList @@ -888,7 +888,7 @@ class ModuleDocumenter(Documenter): memberlist = self.object.__all__ # Sometimes __all__ is broken... if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): + all(isinstance(entry, string_types) for entry in memberlist): self.directive.warn( '__all__ should be a list of strings, not %r ' '(in module %s) -- ignoring __all__' % diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index acb71bf28..4847c0845 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -208,6 +208,14 @@ def init(locale_dirs, language, catalog='sphinx'): translator = None # the None entry is the system's default locale path has_translation = True + + # compile mo files if po file is updated + # TODO: remove circular importing + from sphinx.util.i18n import find_catalog_source_files + for catinfo in find_catalog_source_files(locale_dirs, language, domains=[catalog]): + catinfo.write_mo(language) + + # loading for dir_ in locale_dirs: try: trans = gettext.translation(catalog, localedir=dir_, diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 0548f69c5..f13a5c7cd 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -108,7 +108,9 @@ extensions = [%(extensions)s] templates_path = ['%(dot)stemplates'] # The suffix(es) of source filenames. -source_suffix = ['%(suffix)s'] +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '%(suffix)s' # The encoding of source files. #source_encoding = 'utf-8-sig' diff --git a/sphinx/theming.py b/sphinx/theming.py index 8f27c010c..6c2d3ad9d 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -86,7 +86,7 @@ class Theme(object): if not path.isdir(themedir): continue for theme in os.listdir(themedir): - if theme != 'name': + if theme != name: continue if not path.isfile(path.join(themedir, theme, THEMECONF)): continue diff --git a/sphinx/transforms.py b/sphinx/transforms.py index 789bf5d39..681b50e05 100644 --- a/sphinx/transforms.py +++ b/sphinx/transforms.py @@ -23,7 +23,8 @@ from sphinx.util import split_index_msg from sphinx.util.nodes import ( traverse_translatable_index, extract_messages, LITERAL_TYPE_NODES, IMAGE_TYPE_NODES, ) -from sphinx.util.osutil import ustrftime, find_catalog +from sphinx.util.osutil import ustrftime +from sphinx.util.i18n import find_catalog from sphinx.util.pycompat import indent from sphinx.domains.std import ( make_term_from_paragraph_node, diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 7bd0c34ea..efdc31828 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -8,7 +8,7 @@ :copyright: Copyright 2007-2015 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ - +import gettext from os import path from collections import namedtuple @@ -16,6 +16,7 @@ from babel.messages.pofile import read_po from babel.messages.mofile import write_mo from sphinx.util.osutil import walk +from sphinx.util import SEP LocaleFileInfoBase = namedtuple('CatalogInfo', 'base_dir,domain') @@ -50,13 +51,36 @@ class CatalogInfo(LocaleFileInfoBase): write_mo(mo, read_po(po, locale)) -def get_catalogs(locale_dirs, locale, gettext_compact=False, force_all=False): +def find_catalog(docname, compaction): + if compaction: + ret = docname.split(SEP, 1)[0] + else: + ret = docname + + return ret + + +def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): + if not(lang and locale_dirs): + return [] + + domain = find_catalog(docname, compaction) + files = [gettext.find(domain, path.join(srcdir, dir_), [lang]) + for dir_ in locale_dirs] + files = [path.relpath(f, srcdir) for f in files if f] + return files + + +def find_catalog_source_files(locale_dirs, locale, domains=None, gettext_compact=False, + force_all=False): """ :param list locale_dirs: list of path as `['locale_dir1', 'locale_dir2', ...]` to find translation catalogs. Each path contains a structure such as `/LC_MESSAGES/domain.po`. :param str locale: a language as `'en'` + :param list domains: list of domain names to get. If empty list or None + is specified, get all domain names. default is None. :param boolean gettext_compact: * False: keep domains directory structure (default). * True: domains in the sub directory will be merged into 1 file. @@ -70,6 +94,9 @@ def get_catalogs(locale_dirs, locale, gettext_compact=False, force_all=False): catalogs = set() for locale_dir in locale_dirs: + if not locale_dir: + continue # skip system locale directory + base_dir = path.join(locale_dir, locale, 'LC_MESSAGES') if not path.exists(base_dir): @@ -82,6 +109,9 @@ def get_catalogs(locale_dirs, locale, gettext_compact=False, force_all=False): domain = path.relpath(path.join(dirpath, base), base_dir) if gettext_compact and path.sep in domain: domain = path.split(domain)[0] + domain = domain.replace(path.sep, SEP) + if domains and domain not in domains: + continue cat = CatalogInfo(base_dir, domain) if force_all or cat.is_outdated(): catalogs.add(cat) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 46f908292..20c6a90cc 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -17,7 +17,6 @@ import time import errno import locale import shutil -import gettext from os import path import contextlib @@ -170,26 +169,6 @@ def safe_relpath(path, start=None): return path -def find_catalog(docname, compaction): - if compaction: - ret = docname.split(SEP, 1)[0] - else: - ret = docname - - return ret - - -def find_catalog_files(docname, srcdir, locale_dirs, lang, compaction): - if not(lang and locale_dirs): - return [] - - domain = find_catalog(docname, compaction) - files = [gettext.find(domain, path.join(srcdir, dir_), [lang]) - for dir_ in locale_dirs] - files = [path.relpath(f, srcdir) for f in files if f] - return files - - fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() diff --git a/tests/roots/test-theming/MANIFEST.in b/tests/roots/test-theming/MANIFEST.in new file mode 100644 index 000000000..0e977e756 --- /dev/null +++ b/tests/roots/test-theming/MANIFEST.in @@ -0,0 +1,2 @@ +recursive-include test_theme *.conf + diff --git a/tests/roots/test-theming/conf.py b/tests/roots/test-theming/conf.py new file mode 100644 index 000000000..2717087d1 --- /dev/null +++ b/tests/roots/test-theming/conf.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +html_theme = 'test-theme' +master_doc = 'index' + diff --git a/tests/roots/test-theming/index.rst b/tests/roots/test-theming/index.rst new file mode 100644 index 000000000..18a9a4e2f --- /dev/null +++ b/tests/roots/test-theming/index.rst @@ -0,0 +1,5 @@ +======= +Theming +======= + + diff --git a/tests/roots/test-theming/setup.py b/tests/roots/test-theming/setup.py new file mode 100644 index 000000000..34299647d --- /dev/null +++ b/tests/roots/test-theming/setup.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +# -*- coding: utf-8 -*- +from setuptools import setup, find_packages + +setup( + name='test-theme', + packages=find_packages(), + include_package_data=True, + entry_points=""" + [sphinx_themes] + path = test_theme:get_path + """, +) + + diff --git a/tests/roots/test-theming/test_theme/__init__.py b/tests/roots/test-theming/test_theme/__init__.py new file mode 100644 index 000000000..2d63e888f --- /dev/null +++ b/tests/roots/test-theming/test_theme/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +import os + +def get_path(): + return os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/roots/test-theming/test_theme/test-theme/theme.conf b/tests/roots/test-theming/test_theme/test-theme/theme.conf new file mode 100644 index 000000000..0d8403f0b --- /dev/null +++ b/tests/roots/test-theming/test_theme/test-theme/theme.conf @@ -0,0 +1,2 @@ +[theme] +inherit = classic diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index b06e71a57..7b1cc05e0 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -55,10 +55,13 @@ def test_compile_all_catalogs(app, status, warning): @with_app(buildername='html', testroot='intl', confoverrides={'language': 'en', 'locale_dirs': [locale_dir]}) def test_compile_specific_catalogs(app, status, warning): - app.builder.compile_specific_catalogs(['admonitions']) - catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' - actual = set(find_files(catalog_dir, '.mo')) + def get_actual(): + return set(find_files(catalog_dir, '.mo')) + + actual_on_boot = get_actual() # sphinx.mo might be included + app.builder.compile_specific_catalogs(['admonitions']) + actual = get_actual() - actual_on_boot assert actual == set(['admonitions.mo']) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 51f815049..013700417 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -144,7 +144,7 @@ def test_quickstart_defaults(tempdir): execfile_(conffile, ns) assert ns['extensions'] == [] assert ns['templates_path'] == ['_templates'] - assert ns['source_suffix'] == ['.rst'] + assert ns['source_suffix'] == '.rst' assert ns['master_doc'] == 'index' assert ns['project'] == 'Sphinx Test' assert ns['copyright'] == '%s, Georg Brandl' % time.strftime('%Y') @@ -203,7 +203,7 @@ def test_quickstart_all_answers(tempdir): 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo' ] assert ns['templates_path'] == ['.templates'] - assert ns['source_suffix'] == ['.txt'] + assert ns['source_suffix'] == '.txt' assert ns['master_doc'] == 'contents' assert ns['project'] == u'STASI™' assert ns['copyright'] == u'%s, Wolfgang Schäuble & G\'Beckstein' % \ diff --git a/tests/test_theming.py b/tests/test_theming.py index 77a8b5d8f..45229c842 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -14,7 +14,7 @@ import zipfile from sphinx.theming import Theme, ThemeError -from util import with_app, raises +from util import with_app, raises, TestApp @with_app(confoverrides={'html_theme': 'ziptheme', diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index ba18026ca..c74962fc5 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -72,13 +72,13 @@ def test_get_catalogs_for_xx(dir): (dir / 'loc1' / 'xx' / 'LC_ALL').makedirs() (dir / 'loc1' / 'xx' / 'LC_ALL' / 'test7.po').write_text('#') - catalogs = i18n.get_catalogs([dir / 'loc1'], 'xx', force_all=False) + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', force_all=False) domains = set(c.domain for c in catalogs) assert domains == set([ 'test1', 'test2', - path.normpath('sub/test4'), - path.normpath('sub/test5'), + 'sub/test4', + 'sub/test5', ]) @@ -89,22 +89,22 @@ def test_get_catalogs_for_en(dir): (dir / 'loc1' / 'en' / 'LC_MESSAGES').makedirs() (dir / 'loc1' / 'en' / 'LC_MESSAGES' / 'en_dom.po').write_text('#') - catalogs = i18n.get_catalogs([dir / 'loc1'], 'en', force_all=False) + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'en', force_all=False) domains = set(c.domain for c in catalogs) assert domains == set(['en_dom']) @with_tempdir def test_get_catalogs_with_non_existent_locale(dir): - catalogs = i18n.get_catalogs([dir / 'loc1'], 'xx') + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx') assert not catalogs - catalogs = i18n.get_catalogs([dir / 'loc1'], None) + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], None) assert not catalogs def test_get_catalogs_with_non_existent_locale_dirs(): - catalogs = i18n.get_catalogs(['dummy'], 'xx') + catalogs = i18n.find_catalog_source_files(['dummy'], 'xx') assert not catalogs @@ -123,16 +123,16 @@ def test_get_catalogs_for_xx_without_outdated(dir): (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.po').write_text('#') (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test5.mo').write_text('#') - catalogs = i18n.get_catalogs([dir / 'loc1'], 'xx', force_all=False) + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', force_all=False) assert not catalogs - catalogs = i18n.get_catalogs([dir / 'loc1'], 'xx', force_all=True) + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', force_all=True) domains = set(c.domain for c in catalogs) assert domains == set([ 'test1', 'test2', - path.normpath('sub/test4'), - path.normpath('sub/test5'), + 'sub/test4', + 'sub/test5', ]) @@ -144,7 +144,7 @@ def test_get_catalogs_from_multiple_locale_dirs(dir): (dir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#') (dir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#') - catalogs = i18n.get_catalogs([dir / 'loc1', dir / 'loc2'], 'xx') + catalogs = i18n.find_catalog_source_files([dir / 'loc1', dir / 'loc2'], 'xx') domains = sorted(c.domain for c in catalogs) assert domains == ['test1', 'test1', 'test2'] @@ -158,6 +158,6 @@ def test_get_catalogs_with_compact(dir): (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test3.po').write_text('#') (dir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#') - catalogs = i18n.get_catalogs([dir / 'loc1'], 'xx', gettext_compact=True) + catalogs = i18n.find_catalog_source_files([dir / 'loc1'], 'xx', gettext_compact=True) domains = set(c.domain for c in catalogs) assert domains == set(['test1', 'test2', 'sub'])