# -*- coding: utf-8 -*-
"""
Defines a :epkg:`sphinx` extension to add button to share a page
:githublink:`%|py|6`
"""
import os
import copy
import shutil
from html import escape
import sphinx
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from sphinx.util.logging import getLogger
from sphinx.util import FilenameUniqDict
DEFAULT_CONFIG = dict(
default_image_width=None,
default_image_height=None,
cache_path='_images',
)
[docs]class simpleimage_node(nodes.General, nodes.Element):
"""
Defines *image* node.
:githublink:`%|py|28`
"""
pass
[docs]class SimpleImageDirective(Directive):
"""
Adds an image to a page. It can be done by adding::
.. simpleimage:: filename.png
:width: 400
:height: 600
Available options:
* ``:width:``, ``:height:``, ``:scale:``: resize the image
* ``:target:``: for HTML, clickable image
* ``:alt:``: for HTML
* ``:download:`` if the image is a url, it downloads the image.
* ``:convert:`` convert the image into a new format
:githublink:`%|py|47`
"""
required_arguments = True
optional_arguments = 0
final_argument_whitespace = True
option_spec = {'width': directives.unchanged,
'height': directives.unchanged,
'scale': directives.unchanged,
'target': directives.unchanged,
'alt': directives.unchanged,
'download': directives.unchanged,
'convert': directives.unchanged,
}
has_content = True
node_class = simpleimage_node
[docs] def run(self):
"""
Runs the directive.
:return: a list of nodes
:githublink:`%|py|67`
"""
env = self.state.document.settings.env
conf = env.app.config.simpleimages_config
docname = None if env is None else env.docname
if docname is not None:
docname = docname.replace("\\", "/").split("/")[-1]
else:
docname = ''
source = self.state.document.current_source
filename = self.arguments[0]
if '://' in filename:
logger = getLogger("simpleimage")
logger.warning(
"[simpleimage] url detected '{0}' in docname '{1}' - line {2}.".format(filename, docname, self.lineno))
is_url = True
else:
is_url = False
convert = self.options.get('convert', None)
if convert:
logger = getLogger("simpleimage")
logger.warning(
"[simpleimage] convert into '{3}' not implemented for '{0}' in docname '{1}' - line {2}.".format(
filename, docname, self.lineno, convert))
download = self.options.get('download', None)
if convert:
logger = getLogger("simpleimage")
logger.warning(
"[simpleimage] download not implemented for '{0}' in docname '{1}' - line {2}.".format(filename, docname, self.lineno))
if not is_url:
env.images_mapping.add_file('', filename)
srcdir = env.srcdir
rstrel = os.path.relpath(source, srcdir)
rstfold = os.path.split(rstrel)[0]
cache = os.path.join(srcdir, conf['cache_path'])
img = os.path.join(cache, filename)
abspath = None
relpath = None
if os.path.exists(img):
abspath = img
relpath = cache
else:
last = rstfold.replace('\\', '/')
img = os.path.join(srcdir, last, filename)
if os.path.exists(img):
relpath = last
abspath = img
if abspath is None:
logger = getLogger("simpleimage")
logger.warning(
"[simpleimage] Unable to find '{0}' in docname '{1}' - line {2} - srcdir='{3}'.".format(
filename, docname, self.lineno, srcdir))
else:
abspath = None
relpath = None
width = self.options.get('width', conf['default_image_width'])
height = self.options.get('height', conf['default_image_height'])
scale = self.options.get('scale', None)
alt = self.options.get('alt', None)
target = self.options.get('target', None)
# build node
node = self.__class__.node_class(uri=filename, docname=docname, lineno=self.lineno,
width=width, height=height, abspath=abspath,
relpath=relpath, is_url=is_url, alt=alt, scale=scale,
target=target, convert=convert, download=download)
node['classes'] += ["place-image"]
node['image'] = filename
ns = [node]
return ns
[docs]def visit_simpleimage_node(self, node):
"""
Visits a image node.
Copies the image.
:githublink:`%|py|151`
"""
if node['abspath'] is not None:
outdir = self.builder.outdir
relpath = os.path.join(outdir, node['relpath'])
dname = os.path.split(node['uri'])[0]
if dname:
relpath = os.path.join(relpath, dname)
if not os.path.exists(relpath):
os.makedirs(relpath)
if os.path.dirname(node['abspath']) != relpath:
shutil.copy(node['abspath'], relpath)
logger = getLogger("image")
logger.info("[image] copy '{0}' to '{1}'".format(
node['uri'], relpath))
[docs]def _clean_value(val):
if isinstance(val, tuple):
return val[0]
else:
return val
[docs]def depart_simpleimage_node_html(self, node):
"""
What to do when leaving a node *image*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|180`
"""
if node.hasattr("uri"):
filename = node["uri"]
width = _clean_value(node["width"])
height = _clean_value(node["height"])
scale = node["scale"]
alt = node["alt"]
target = node["target"]
found = node["abspath"] is not None or node["is_url"]
if not found: # pragma: no cover
body = "<b>unable to find '{0}'</b>".format(filename)
self.body.append(body)
else:
body = '<img src="{0}" {1} {2}/>'
width = ' width="{0}"'.format(width) if width else ""
height = ' height="{0}"'.format(height) if height else ""
if width or height:
style = "{0}{1}".format(width, height)
elif scale:
style = " width={0}%".format(scale)
alt = ' alt="{0}"'.format(escape(alt)) if alt else ""
body = body.format(filename, style, alt)
if target:
body = '<a href="{0}">{1}</a>'.format(escape(target), body)
self.body.append(body)
[docs]def depart_simpleimage_node_text(self, node):
"""
What to do when leaving a node *image*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|213`
"""
if 'rst' in (self.builder.name, self.builder.format):
depart_simpleimage_node_rst(self, node)
elif 'md' in (self.builder.name, self.builder.format):
depart_simpleimage_node_md(self, node)
elif 'latex' in (self.builder.name, self.builder.format):
depart_simpleimage_node_latex(self, node)
elif node.hasattr("uri"):
filename = node["uri"]
width = _clean_value(node["width"])
height = _clean_value(node["height"])
scale = node["scale"]
alt = node["alt"]
target = node["target"]
found = node["abspath"] is not None or node["is_url"]
if not found: # pragma: no cover
body = "unable to find '{0}'".format(filename)
self.body.append(body)
else:
body = '\nimage {0}{1}{2}: {3}{4}\n'
width = ' width="{0}"'.format(width) if width else ""
height = ' height="{0}"'.format(height) if height else ""
scale = ' scale="{0}"'.format(scale) if scale else ""
alt = ' alt="{0}"'.format(alt.replace('"', '\\"')) if alt else ""
target = ' target="{0}"'.format(
target.replace('"', '\\"')) if target else ""
body = body.format(width, height, scale, filename, alt, target)
self.add_text(body)
[docs]def depart_simpleimage_node_latex(self, node):
"""
What to do when leaving a node *image*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|249`
"""
if node.hasattr("uri"):
width = _clean_value(node["width"])
height = _clean_value(node["height"])
scale = node["scale"]
alt = node["alt"]
full = os.path.join(node["relpath"], node['uri'])
found = node['abspath'] is not None or node["is_url"]
if not found: # pragma: no cover
body = "\\textbf{{unable to find '{0}'}}".format(full)
self.body.append(body)
else:
body = '\\includegraphics{0}{{{1}}}\n'
width = "width={0}".format(width) if width else ""
height = "height={0}".format(height) if height else ""
scale = "scale={0}".format(scale) if scale else ""
if width or height or scale:
dims = [_ for _ in [width, height, scale] if _]
style = "[{0}]".format(",".join(dims))
else:
style = ""
alt = ' alt="{0}"'.format(alt.replace('"', '\\"')) if alt else ""
full = full.replace('\\', '/').replace('_', '\\_')
body = body.format(style, full)
self.body.append(body)
[docs]def depart_simpleimage_node_rst(self, node):
"""
What to do when leaving a node *image*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|282`
"""
if node.hasattr("uri"):
filename = node["uri"]
found = node["abspath"] is not None or node["is_url"]
if not found: # pragma: no cover
body = ".. simpleimage:: {0} [not found]".format(filename)
self.add_text(body + self.nl)
else:
options = SimpleImageDirective.option_spec
body = ".. simpleimage:: {0}".format(filename)
self.new_state(0)
self.add_text(body + self.nl)
for opt in options:
v = node.get(opt, None)
if v:
self.add_text(' :{0}: {1}'.format(opt, v) + self.nl)
self.end_state(wrap=False)
[docs]def depart_simpleimage_node_md(self, node):
"""
What to do when leaving a node *image*
the function should have different behaviour,
depending on the format, or the setup should
specify a different function for each.
:githublink:`%|py|307`
"""
if node.hasattr("uri"):
filename = node["uri"]
found = node["abspath"] is not None or node["is_url"]
if not found: # pragma: no cover
body = "[{0}](not found)".format(filename)
self.add_text(body + self.nl)
else:
alt = node.get("alt", "")
uri = filename
width = node.get('width', '').replace('px', '')
height = node.get('height', '').replace('px', '')
style = " ={0}x{1}".format(width, height)
if style == " =x":
style = ""
text = "![{0}]({1}{2})".format(alt, uri, style)
self.add_text(text)
[docs]def initialize_simpleimages_directive(app):
"""
Initializes the image directives.
:githublink:`%|py|329`
"""
global DEFAULT_CONFIG
config = copy.deepcopy(DEFAULT_CONFIG)
config.update(app.config.simpleimages_config)
app.config.simpleimages_config = config
app.env.images_mapping = FilenameUniqDict()
[docs]def setup(app):
"""
setup for ``image`` (sphinx)
:githublink:`%|py|341`
"""
global DEFAULT_CONFIG
if hasattr(app, "add_mapping"):
app.add_mapping('simpleimages_mapping', simpleimage_node)
app.add_config_value('simpleimages_config', DEFAULT_CONFIG, 'env')
app.connect('builder-inited', initialize_simpleimages_directive)
app.add_node(simpleimage_node,
html=(visit_simpleimage_node, depart_simpleimage_node_html),
epub=(visit_simpleimage_node, depart_simpleimage_node_html),
elatex=(visit_simpleimage_node,
depart_simpleimage_node_latex),
latex=(visit_simpleimage_node, depart_simpleimage_node_latex),
rst=(visit_simpleimage_node, depart_simpleimage_node_rst),
md=(visit_simpleimage_node, depart_simpleimage_node_md),
text=(visit_simpleimage_node, depart_simpleimage_node_text))
app.add_directive('simpleimage', SimpleImageDirective)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}