"""
Few helpers for :epkg:`Sphinx`.
:githublink:`%|py|5`
"""
import io
import logging
import os
import pickle
import urllib.parse as urllib_parse
import urllib.request as urllib_request
from urllib.parse import urljoin
from sphinx.util.inventory import InventoryFile
[docs]def info_blocref(app, doctree, fromdocname, class_name,
entry_name, class_node, class_node_list):
"""
Log information with :epkg:`Sphinx`.
:param app: application (Sphinx)
:param doctree: document tree
:param fromdocname: document currently being compiled
:param class_name: name of the node
:param entry_name: entry name in ``TITLES``
:param class_node: class node (:class:`blocref_node <pyquickhelper.sphinxext.sphinx_blocref_extension.blocref_node>`)
:param class_node_list: class node list (:class:`blocreflist <pyquickhelper.sphinxext.sphinx_blocref_extension.blocreflist>`)
:githublink:`%|py|27`
"""
incconf = '%s_include_%ss' % (class_name, class_name)
rows2 = []
for node in doctree.traverse(class_node_list):
breftag = node.get("breftag", None)
rows2.append("tag={0} do={1}".format(breftag, app.config[incconf]))
if len(rows2) == 0:
return False
attr_name = '%s_all_%ss' % (class_name, class_name)
env = app.builder.env
if hasattr(env, attr_name):
bloc_list_env = getattr(env, attr_name)
else:
bloc_list_env = []
rows = [" [info_blocref]",
"len(bloc_list_env)={0}".format(len(bloc_list_env)), ]
rows.extend(rows2)
rows.extend(["fromdocname='{0}'".format(fromdocname),
"entry_name='{0}'".format(entry_name),
"class_name='{0}'".format(class_name),
"class_node='{0}'".format(class_node),
"class_node_list='{0}'".format(class_node_list),
"doctree='{0}'".format(type(doctree)),
"#doctree={0}".format(len(doctree))])
message = " ".join(rows)
logger = logging.getLogger("info_blocref")
logger.info(message)
return True
[docs]def sphinx_lang(env, default_value='en'):
"""
Returns the language defined in the configuration file.
:param env: environment
:param default_value: default value
:return: language
:githublink:`%|py|66`
"""
if hasattr(env, "settings"):
settings = env.settings
if hasattr(settings, "language_code"):
lang = env.settings.language_code # pragma: no cover
else:
lang = "en"
else:
settings = None # pragma: no cover
lang = "en" # pragma: no cover
return lang
[docs]class TinyNode:
"""
Returned by :func:`traverse <pyquickhelper.sphinxext.sphinx_ext_helper.traverse>`.
:githublink:`%|py|82`
"""
[docs] def __init__(self, parent):
"""
Create a note
:param parent: parent node
:githublink:`%|py|89`
"""
self.parent = parent
[docs]class NodeEnter(TinyNode):
"""
Returned by function :func:`traverse <pyquickhelper.sphinxext.sphinx_ext_helper.traverse>`.
:githublink:`%|py|96`
"""
pass
[docs]class NodeLeave(TinyNode):
"""
Returned by function :func:`traverse <pyquickhelper.sphinxext.sphinx_ext_helper.traverse>`.
:githublink:`%|py|103`
"""
pass
[docs]class WrappedNode:
"""
Wraps a docutils node.
:githublink:`%|py|110`
"""
[docs] def __init__(self, node):
self.node = node
[docs]def traverse(node, depth=0):
"""
Enumerates through all children but insert a node whenever
digging or leaving the childrens nodes.
:param node: node (from doctree)
:param depth: current depth
:return: enumerate (depth, node)
:class:`NodeEnter <pyquickhelper.sphinxext.sphinx_ext_helper.NodeEnter>` and :class:`NodeLeave` are returned whenever entering or leaving nodes.
:githublink:`%|py|126`
"""
if isinstance(node, WrappedNode):
node = node.node
ne = NodeEnter(node)
nl = NodeLeave(node)
yield (depth, ne)
yield (depth, node)
for n in node.children:
for r in traverse(n, depth + 1):
yield r
yield (depth, nl)
[docs]def _get_data(url):
"""
Loads file ``objects.inv`` generated by
extension :epkg:`sphinx.ext.intersphinx`.
:param url: url of documentation, example
``https://pandas.pydata.org/docs/``
:return: instance of `InventoryFile`
:githublink:`%|py|147`
"""
url_inv = urljoin(url, "objects.inv")
if urllib_parse.urlparse(url_inv).scheme in ('http', 'https'):
user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11' # noqa: E501
headers = {'User-Agent': user_agent}
req = urllib_request.Request(url_inv, None, headers)
resp = urllib_request.urlopen(req)
data = resp.read()
else:
with open(url, 'rb') as fid:
data = fid.read()
inv = InventoryFile.load(io.BytesIO(data), url, urljoin)
return inv
[docs]def get_index(index_url, cache_dir):
"""
Retrieves documentation data for a specific module.
:param url: url of documentation, example
``https://pandas.pydata.org/docs/``
:param cache_dir: restore a cached inventory stored with pickle
:return: instance of `InventoryFile`
:githublink:`%|py|171`
"""
if cache_dir is not None:
base_file = index_url.replace("/", "_").split(':')[-1] + ".pkl"
full_file = os.path.join(cache_dir, base_file)
if os.path.exists(full_file):
with open(full_file, "rb") as f:
return pickle.load(f)
index = _get_data(index_url)
if cache_dir is not None:
with open(full_file, "wb") as f:
pickle.dump(index, f)
return index