diff options
Diffstat (limited to 'sphinx/util')
-rw-r--r-- | sphinx/util/__init__.py | 55 | ||||
-rw-r--r-- | sphinx/util/compat.py | 2 | ||||
-rw-r--r-- | sphinx/util/docfields.py | 28 | ||||
-rw-r--r-- | sphinx/util/docutils.py | 99 | ||||
-rw-r--r-- | sphinx/util/fileutil.py | 82 | ||||
-rw-r--r-- | sphinx/util/i18n.py | 12 | ||||
-rw-r--r-- | sphinx/util/inspect.py | 14 | ||||
-rw-r--r-- | sphinx/util/matching.py | 21 | ||||
-rw-r--r-- | sphinx/util/nodes.py | 49 | ||||
-rw-r--r-- | sphinx/util/osutil.py | 90 | ||||
-rw-r--r-- | sphinx/util/png.py | 15 | ||||
-rw-r--r-- | sphinx/util/pycompat.py | 7 | ||||
-rw-r--r-- | sphinx/util/requests.py | 55 | ||||
-rw-r--r-- | sphinx/util/template.py | 61 |
14 files changed, 504 insertions, 86 deletions
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 9c5365aa7..7ac5c62f7 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -8,6 +8,7 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import absolute_import import os import re @@ -18,20 +19,17 @@ import posixpath import traceback import unicodedata from os import path -from codecs import open, BOM_UTF8 +from codecs import BOM_UTF8 from collections import deque from six import iteritems, text_type, binary_type from six.moves import range from six.moves.urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode -import docutils from docutils.utils import relative_path -import jinja2 - -import sphinx from sphinx.errors import PycodeError, SphinxParallelError, ExtensionError from sphinx.util.console import strip_colors +from sphinx.util.fileutil import copy_asset_file from sphinx.util.osutil import fs_encoding # import other utilities; partly for backwards compatibility, so don't @@ -148,7 +146,7 @@ class FilenameUniqDict(dict): def copy_static_entry(source, targetdir, builder, context={}, exclude_matchers=(), level=0): - """Copy a HTML builder static_path entry from source to targetdir. + """[DEPRECATED] Copy a HTML builder static_path entry from source to targetdir. Handles all possible cases of files, directories and subdirectories. """ @@ -158,16 +156,7 @@ def copy_static_entry(source, targetdir, builder, context={}, if matcher(relpath): return if path.isfile(source): - target = path.join(targetdir, path.basename(source)) - if source.lower().endswith('_t') and builder.templates: - # templated! - fsrc = open(source, 'r', encoding='utf-8') - fdst = open(target[:-2], 'w', encoding='utf-8') - fdst.write(builder.templates.render_string(fsrc.read(), context)) - fsrc.close() - fdst.close() - else: - copyfile(source, target) + copy_asset_file(source, targetdir, context, builder.templates) elif path.isdir(source): if not path.isdir(targetdir): os.mkdir(targetdir) @@ -182,37 +171,6 @@ def copy_static_entry(source, targetdir, builder, context={}, exclude_matchers=exclude_matchers) -def copy_extra_entry(source, targetdir, exclude_matchers=()): - """Copy a HTML builder extra_path entry from source to targetdir. - - Handles all possible cases of files, directories and subdirectories. - """ - def excluded(path): - relpath = relative_path(os.path.dirname(source), path) - return any(matcher(relpath) for matcher in exclude_matchers) - - def copy_extra_file(source_, targetdir_): - if not excluded(source_): - target = path.join(targetdir_, os.path.basename(source_)) - copyfile(source_, target) - - if os.path.isfile(source): - copy_extra_file(source, targetdir) - return - - for root, dirs, files in os.walk(source): - reltargetdir = os.path.join(targetdir, relative_path(source, root)) - for dir in dirs[:]: - if excluded(os.path.join(root, dir)): - dirs.remove(dir) - else: - target = os.path.join(reltargetdir, dir) - if not path.exists(target): - os.mkdir(target) - for file in files: - copy_extra_file(os.path.join(root, file), reltargetdir) - - _DEBUG_HEADER = '''\ # Sphinx version: %s # Python version: %s (%s) @@ -226,6 +184,9 @@ _DEBUG_HEADER = '''\ def save_traceback(app): """Save the current exception's traceback in a temporary file.""" + import sphinx + import jinja2 + import docutils import platform exc = sys.exc_info()[1] if isinstance(exc, SphinxParallelError): diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index 5329cb668..0af65cbe3 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -8,6 +8,8 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import absolute_import + import warnings from docutils import nodes diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index f4eb703ce..d5cb4038f 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -9,6 +9,7 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import absolute_import from docutils import nodes @@ -62,6 +63,10 @@ class Field(object): refnode += contnode or innernode(target, target) return refnode + def make_xrefs(self, rolename, domain, target, + innernode=addnodes.literal_emphasis, contnode=None): + return [self.make_xref(rolename, domain, target, innernode, contnode)] + def make_entry(self, fieldarg, content): return (fieldarg, content) @@ -70,14 +75,15 @@ class Field(object): fieldname = nodes.field_name('', self.label) if fieldarg: fieldname += nodes.Text(' ') - fieldname += self.make_xref(self.rolename, domain, - fieldarg, nodes.Text) + fieldname.extend(self.make_xrefs(self.rolename, domain, + fieldarg, nodes.Text)) + if len(content) == 1 and ( isinstance(content[0], nodes.Text) or (isinstance(content[0], nodes.inline) and len(content[0]) == 1 and isinstance(content[0][0], nodes.Text))): - content = [self.make_xref(self.bodyrolename, domain, - content[0].astext(), contnode=content[0])] + content = self.make_xrefs(self.bodyrolename, domain, + content[0].astext(), contnode=content[0]) fieldbody = nodes.field_body('', nodes.paragraph('', '', *content)) return nodes.field('', fieldname, fieldbody) @@ -108,14 +114,16 @@ class GroupedField(Field): listnode = self.list_type() for fieldarg, content in items: par = nodes.paragraph() - par += self.make_xref(self.rolename, domain, fieldarg, - addnodes.literal_strong) + par.extend(self.make_xrefs(self.rolename, domain, fieldarg, + addnodes.literal_strong)) par += nodes.Text(' -- ') par += content listnode += nodes.list_item('', par) + if len(items) == 1 and self.can_collapse: fieldbody = nodes.field_body('', listnode[0][0]) return nodes.field('', fieldname, fieldbody) + fieldbody = nodes.field_body('', listnode) return nodes.field('', fieldname, fieldbody) @@ -150,8 +158,8 @@ class TypedField(GroupedField): def make_field(self, types, domain, items): def handle_item(fieldarg, content): par = nodes.paragraph() - par += self.make_xref(self.rolename, domain, fieldarg, - addnodes.literal_strong) + par.extend(self.make_xrefs(self.rolename, domain, fieldarg, + addnodes.literal_strong)) if fieldarg in types: par += nodes.Text(' (') # NOTE: using .pop() here to prevent a single type node to be @@ -160,8 +168,8 @@ class TypedField(GroupedField): fieldtype = types.pop(fieldarg) if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): typename = u''.join(n.astext() for n in fieldtype) - par += self.make_xref(self.typerolename, domain, typename, - addnodes.literal_emphasis) + par.extend(self.make_xrefs(self.typerolename, domain, typename, + addnodes.literal_emphasis)) else: par += fieldtype par += nodes.Text(')') diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py new file mode 100644 index 000000000..be9e2edad --- /dev/null +++ b/sphinx/util/docutils.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +""" + sphinx.util.docutils + ~~~~~~~~~~~~~~~~~~~~ + + Utility functions for docutils. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +from __future__ import absolute_import + +from copy import copy +from contextlib import contextmanager +from docutils.parsers.rst import directives, roles + + +@contextmanager +def docutils_namespace(): + """Create namespace for reST parsers.""" + try: + _directives = copy(directives._directives) + _roles = copy(roles._roles) + + yield + finally: + directives._directives = _directives + roles._roles = _roles + + +class ElementLookupError(Exception): + pass + + +class sphinx_domains(object): + """Monkey-patch directive and role dispatch, so that domain-specific + markup takes precedence. + """ + def __init__(self, env): + self.env = env + self.directive_func = None + self.roles_func = None + + def __enter__(self): + self.enable() + + def __exit__(self, type, value, traceback): + self.disable() + + def enable(self): + self.directive_func = directives.directive + self.role_func = roles.role + + directives.directive = self.lookup_directive + roles.role = self.lookup_role + + def disable(self): + directives.directive = self.directive_func + roles.role = self.role_func + + def lookup_domain_element(self, type, name): + """Lookup a markup element (directive or role), given its name which can + be a full name (with domain). + """ + name = name.lower() + # explicit domain given? + if ':' in name: + domain_name, name = name.split(':', 1) + if domain_name in self.env.domains: + domain = self.env.domains[domain_name] + element = getattr(domain, type)(name) + if element is not None: + return element, [] + # else look in the default domain + else: + def_domain = self.env.temp_data.get('default_domain') + if def_domain is not None: + element = getattr(def_domain, type)(name) + if element is not None: + return element, [] + + # always look in the std domain + element = getattr(self.env.domains['std'], type)(name) + if element is not None: + return element, [] + + raise ElementLookupError + + def lookup_directive(self, name, lang_module, document): + try: + return self.lookup_domain_element('directive', name) + except ElementLookupError: + return self.directive_func(name, lang_module, document) + + def lookup_role(self, name, lang_module, lineno, reporter): + try: + return self.lookup_domain_element('role', name) + except ElementLookupError: + return self.role_func(name, lang_module, lineno, reporter) diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py new file mode 100644 index 000000000..4375b7e61 --- /dev/null +++ b/sphinx/util/fileutil.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +""" + sphinx.util.fileutil + ~~~~~~~~~~~~~~~~~~~~ + + File utility functions for Sphinx. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +from __future__ import absolute_import + +import os +import codecs +import posixpath +from docutils.utils import relative_path +from sphinx.util.osutil import copyfile, ensuredir, walk + + +def copy_asset_file(source, destination, context=None, renderer=None): + """Copy an asset file to destination. + + On copying, it expands the template variables if context argument is given and + the asset is a template file. + + :param source: The path to source file + :param destination: The path to destination file or directory + :param context: The template variables. If not given, template files are simply copied + :param renderer: The template engine. If not given, SphinxRenderer is used by default + """ + if not os.path.exists(source): + return + + if os.path.exists(destination) and os.path.isdir(destination): + # Use source filename if destination points a directory + destination = os.path.join(destination, os.path.basename(source)) + + if source.lower().endswith('_t') and context: + if renderer is None: + from sphinx.util.template import SphinxRenderer + renderer = SphinxRenderer() + + with codecs.open(source, 'r', encoding='utf-8') as fsrc: + with codecs.open(destination[:-2], 'w', encoding='utf-8') as fdst: + fdst.write(renderer.render_string(fsrc.read(), context)) + else: + copyfile(source, destination) + + +def copy_asset(source, destination, excluded=lambda path: False, context=None, renderer=None): + """Copy asset files to destination recursively. + + On copying, it expands the template variables if context argument is given and + the asset is a template file. + + :param source: The path to source file or directory + :param destination: The path to destination directory + :param excluded: The matcher to determine the given path should be copied or not + :param context: The template variables. If not given, template files are simply copied + :param renderer: The template engine. If not given, SphinxRenderer is used by default + """ + if not os.path.exists(source): + return + + ensuredir(destination) + if os.path.isfile(source): + copy_asset_file(source, destination, context, renderer) + return + + for root, dirs, files in walk(source): + reldir = relative_path(source, root) + for dir in dirs[:]: + if excluded(posixpath.join(reldir, dir)): + dirs.remove(dir) + else: + ensuredir(posixpath.join(destination, reldir, dir)) + + for filename in files: + if not excluded(posixpath.join(reldir, filename)): + copy_asset_file(posixpath.join(root, filename), + posixpath.join(destination, reldir), + context, renderer) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 5b396820c..112353d47 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -230,10 +230,16 @@ def get_image_filename_for_language(filename, env): return filename filename_format = env.config.figure_language_filename - root, ext = path.splitext(filename) + d = dict() + d['root'], d['ext'] = path.splitext(filename) + dirname = path.dirname(d['root']) + if dirname and not dirname.endswith(path.sep): + dirname += path.sep + d['path'] = dirname + d['basename'] = path.basename(d['root']) + d['language'] = env.config.language try: - return filename_format.format(root=root, ext=ext, - language=env.config.language) + return filename_format.format(**d) except KeyError as exc: raise SphinxError('Invalid figure_language_filename: %r' % exc) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 81ffe25c8..147d43592 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -60,7 +60,7 @@ if PY3: raise TypeError('%r is not a Python function' % func) return inspect.getfullargspec(func) -else: # 2.6, 2.7 +else: # 2.7 from functools import partial def getargspec(func): @@ -94,6 +94,18 @@ else: # 2.6, 2.7 pass return inspect.ArgSpec(args, varargs, varkw, func_defaults) +try: + import enum +except ImportError: + enum = None + + +def isenumattribute(x): + """Check if the object is attribute of enum.""" + if enum is None: + return False + return isinstance(x, enum.Enum) + def isdescriptor(x): """Check if the object is some kind of descriptor.""" diff --git a/sphinx/util/matching.py b/sphinx/util/matching.py index 91fda6378..fc7750be9 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -62,6 +62,27 @@ def compile_matchers(patterns): return [re.compile(_translate_pattern(pat)).match for pat in patterns] +class Matcher(object): + """A pattern matcher for Multiple shell-style glob patterns. + + Note: this modifies the patterns to work with copy_asset(). + For example, "**/index.rst" matches with "index.rst" + """ + + def __init__(self, patterns): + expanded = [pat[3:] for pat in patterns if pat.startswith('**/')] + self.patterns = compile_matchers(patterns + expanded) + + def __call__(self, string): + return self.match(string) + + def match(self, string): + return any(pat(string) for pat in self.patterns) + + +DOTFILES = Matcher(['**/.*']) + + _pat_cache = {} diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index e4a2fd73b..fe3b0f2f9 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -8,6 +8,7 @@ :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +from __future__ import absolute_import import re @@ -85,7 +86,18 @@ IGNORED_NODES = ( ) +def is_pending_meta(node): + if (isinstance(node, nodes.pending) and + isinstance(node.details.get('nodes', [None])[0], addnodes.meta)): + return True + else: + return False + + def is_translatable(node): + if isinstance(node, addnodes.translatable): + return True + if isinstance(node, nodes.TextElement): if not node.source: return False # built-in message @@ -103,6 +115,11 @@ def is_translatable(node): if isinstance(node, nodes.image) and node.get('translatable'): return True + if isinstance(node, addnodes.meta): + return True + if is_pending_meta(node): + return True + return False @@ -114,11 +131,18 @@ LITERAL_TYPE_NODES = ( IMAGE_TYPE_NODES = ( nodes.image, ) +META_TYPE_NODES = ( + addnodes.meta, +) def extract_messages(doctree): """Extract translatable messages from a document tree.""" for node in doctree.traverse(is_translatable): + if isinstance(node, addnodes.translatable): + for msg in node.extract_original_messages(): + yield node, msg + continue if isinstance(node, LITERAL_TYPE_NODES): msg = node.rawsource if not msg: @@ -127,6 +151,10 @@ def extract_messages(doctree): msg = '.. image:: %s' % node['uri'] if node.get('alt'): msg += '\n :alt: %s' % node['alt'] + elif isinstance(node, META_TYPE_NODES): + msg = node.rawcontent + elif is_pending_meta(node): + msg = node.details['nodes'][0].rawcontent else: msg = node.rawsource.replace('\n', ' ').strip() @@ -293,6 +321,27 @@ def set_role_source_info(inliner, lineno, node): node.source, node.line = inliner.reporter.get_source_and_line(lineno) +def process_only_nodes(doctree, tags, warn_node=None): + # A comment on the comment() nodes being inserted: replacing by [] would + # result in a "Losing ids" exception if there is a target node before + # the only node, so we make sure docutils can transfer the id to + # something, even if it's just a comment and will lose the id anyway... + for node in doctree.traverse(addnodes.only): + try: + ret = tags.eval_condition(node['expr']) + except Exception as err: + if warn_node is None: + raise err + warn_node('exception while evaluating only ' + 'directive expression: %s' % err, node) + node.replace_self(node.children or nodes.comment()) + else: + if ret: + node.replace_self(node.children or nodes.comment()) + else: + node.replace_self(nodes.comment()) + + # monkey-patch Element.copy to copy the rawsource and line def _new_copy(self): diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index b416a8c1f..b8fffb220 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -17,8 +17,10 @@ import time import errno import locale import shutil +import filecmp from os import path import contextlib +from io import BytesIO, StringIO from six import PY2, text_type @@ -78,8 +80,8 @@ def ensuredir(path): raise -# This function is same as os.walk of Python2.6, 2.7, 3.2, 3.3 except a -# customization that check UnicodeError. +# This function is same as os.walk of Python2.7 except a customization +# that check UnicodeError. # The customization obstacle to replace the function with the os.walk. def walk(top, topdown=True, followlinks=False): """Backport of os.walk from 2.6, where the *followlinks* argument was @@ -141,13 +143,16 @@ def copytimes(source, dest): def copyfile(source, dest): - """Copy a file and its modification times, if possible.""" - shutil.copyfile(source, dest) - try: - # don't do full copystat because the source may be read-only - copytimes(source, dest) - except OSError: - pass + """Copy a file and its modification times, if possible. + + Note: ``copyfile`` skips copying if the file has not been changed""" + if not path.exists(dest) or not filecmp.cmp(source, dest): + shutil.copyfile(source, dest) + try: + # don't do full copystat because the source may be read-only + copytimes(source, dest) + except OSError: + pass no_fn_re = re.compile(r'[^a-zA-Z0-9_-]') @@ -215,6 +220,73 @@ def cd(target_dir): os.chdir(cwd) +class FileAvoidWrite(object): + """File-like object that buffers output and only writes if content changed. + + Use this class like when writing to a file to avoid touching the original + file if the content hasn't changed. This is useful in scenarios where file + mtime is used to invalidate caches or trigger new behavior. + + When writing to this file handle, all writes are buffered until the object + is closed. + + Objects can be used as context managers. + """ + def __init__(self, path): + self._path = path + self._io = None + + def write(self, data): + if not self._io: + if isinstance(data, text_type): + self._io = StringIO() + else: + self._io = BytesIO() + + self._io.write(data) + + def close(self): + """Stop accepting writes and write file, if needed.""" + if not self._io: + raise Exception('FileAvoidWrite does not support empty files.') + + buf = self.getvalue() + self._io.close() + + r_mode = 'r' + w_mode = 'w' + if isinstance(self._io, BytesIO): + r_mode = 'rb' + w_mode = 'wb' + + old_content = None + + try: + with open(self._path, r_mode) as old_f: + old_content = old_f.read() + if old_content == buf: + return + except IOError: + pass + + with open(self._path, w_mode) as f: + f.write(buf) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.close() + + def __getattr__(self, name): + # Proxy to _io instance. + if not self._io: + raise Exception('Must write to FileAvoidWrite before other ' + 'methods can be used') + + return getattr(self._io, name) + + def rmtree(path): if os.path.isdir(path): shutil.rmtree(path) diff --git a/sphinx/util/png.py b/sphinx/util/png.py index e28445a42..476d45ccd 100644 --- a/sphinx/util/png.py +++ b/sphinx/util/png.py @@ -23,18 +23,14 @@ IEND_CHUNK = b'\x00\x00\x00\x00IEND\xAE\x42\x60\x82' def read_png_depth(filename): """Read the special tEXt chunk indicating the depth from a PNG file.""" - result = None - f = open(filename, 'rb') - try: + with open(filename, 'rb') as f: f.seek(- (LEN_IEND + LEN_DEPTH), 2) depthchunk = f.read(LEN_DEPTH) if not depthchunk.startswith(DEPTH_CHUNK_LEN + DEPTH_CHUNK_START): # either not a PNG file or not containing the depth chunk return None - result = struct.unpack('!i', depthchunk[14:18])[0] - finally: - f.close() - return result + else: + return struct.unpack('!i', depthchunk[14:18])[0] def write_png_depth(filename, depth): @@ -43,8 +39,7 @@ def write_png_depth(filename, depth): The chunk is placed immediately before the special IEND chunk. """ data = struct.pack('!i', depth) - f = open(filename, 'r+b') - try: + with open(filename, 'r+b') as f: # seek to the beginning of the IEND chunk f.seek(-LEN_IEND, 2) # overwrite it with the depth chunk @@ -54,5 +49,3 @@ def write_png_depth(filename, depth): f.write(struct.pack('!I', crc)) # replace the IEND chunk f.write(IEND_CHUNK) - finally: - f.close() diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 4503c34e1..e3b17ef62 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -55,7 +55,7 @@ if PY3: return text_type(tree) from html import escape as htmlescape # noqa: >= Python 3.2 - class UnicodeMixin: + class UnicodeMixin(object): """Mixin class to handle defining the proper __str__/__unicode__ methods in Python 2 or 3.""" @@ -105,11 +105,8 @@ def execfile_(filepath, _globals, open=open): from sphinx.util.osutil import fs_encoding # get config source -- 'b' is a no-op under 2.x, while 'U' is # ignored under 3.x (but 3.x compile() accepts \r\n newlines) - f = open(filepath, 'rbU') - try: + with open(filepath, 'rbU') as f: source = f.read() - finally: - f.close() # py26 accept only LF eol instead of CRLF if sys.version_info[:2] == (2, 6): diff --git a/sphinx/util/requests.py b/sphinx/util/requests.py new file mode 100644 index 000000000..36ac1e0e7 --- /dev/null +++ b/sphinx/util/requests.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +""" + sphinx.util.requests + ~~~~~~~~~~~~~~~~~~~~ + + Simple requests package loader + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from __future__ import absolute_import + +import requests +import warnings +import pkg_resources +from requests.packages.urllib3.exceptions import SSLError + +# try to load requests[security] +try: + pkg_resources.require(['requests[security]']) +except pkg_resources.DistributionNotFound: + import ssl + if not getattr(ssl, 'HAS_SNI', False): + # don't complain on each url processed about the SSL issue + requests.packages.urllib3.disable_warnings( + requests.packages.urllib3.exceptions.InsecurePlatformWarning) + warnings.warn( + 'Some links may return broken results due to being unable to ' + 'check the Server Name Indication (SNI) in the returned SSL cert ' + 'against the hostname in the url requested. Recommended to ' + 'install "requests[security]" as a dependency or upgrade to ' + 'a python version with SNI support (Python 3 and Python 2.7.9+).' + ) +except pkg_resources.UnknownExtra: + warnings.warn( + 'Some links may return broken results due to being unable to ' + 'check the Server Name Indication (SNI) in the returned SSL cert ' + 'against the hostname in the url requested. Recommended to ' + 'install requests-2.4.1+.' + ) + +useragent_header = [('User-Agent', + 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0')] + + +def is_ssl_error(exc): + if isinstance(exc, SSLError): + return True + else: + args = getattr(exc, 'args', []) + if args and isinstance(args[0], SSLError): + return True + else: + return False diff --git a/sphinx/util/template.py b/sphinx/util/template.py new file mode 100644 index 000000000..7cb897e7d --- /dev/null +++ b/sphinx/util/template.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +""" + sphinx.util.template + ~~~~~~~~~~~~~~~~~~~~ + + Templates utility functions for Sphinx. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +from jinja2.sandbox import SandboxedEnvironment + +from sphinx import package_dir +from sphinx.jinja2glue import SphinxFileSystemLoader + + +class BaseRenderer(object): + def __init__(self, loader=None): + self.env = SandboxedEnvironment(loader=loader) + self.env.filters['repr'] = repr + + def render(self, template_name, context): + return self.env.get_template(template_name).render(context) + + def render_string(self, source, context): + return self.env.from_string(source).render(context) + + +class FileRenderer(BaseRenderer): + def __init__(self, search_path): + loader = SphinxFileSystemLoader(search_path) + super(FileRenderer, self).__init__(loader) + + @classmethod + def render_from_file(cls, filename, context): + dirname = os.path.dirname(filename) + basename = os.path.basename(filename) + return cls(dirname).render(basename, context) + + +class SphinxRenderer(FileRenderer): + def __init__(self): + super(SphinxRenderer, self).__init__(os.path.join(package_dir, 'templates')) + + @classmethod + def render_from_file(cls, filename, context): + return FileRenderer.render_from_file(filename, context) + + +class LaTeXRenderer(SphinxRenderer): + def __init__(self): + super(LaTeXRenderer, self).__init__() + + # use JSP/eRuby like tagging instead because curly bracket; the default + # tagging of jinja2 is not good for LaTeX sources. + self.env.variable_start_string = '<%=' + self.env.variable_end_string = '%>' + self.env.block_start_string = '<%' + self.env.block_end_string = '%>' |