Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Defines a :epkg:`sphinx` extension to add button to share a page
5"""
6import os
7import copy
8import shutil
9from html import escape
10import sphinx
11from docutils import nodes
12from docutils.parsers.rst import Directive, directives
13from sphinx.util.logging import getLogger
14from sphinx.util import FilenameUniqDict
17DEFAULT_CONFIG = dict(
18 default_image_width=None,
19 default_image_height=None,
20 cache_path='_images',
21)
24class simpleimage_node(nodes.General, nodes.Element):
26 """
27 Defines *image* node.
28 """
29 pass
32class SimpleImageDirective(Directive):
33 """
34 Adds an image to a page. It can be done by adding::
36 .. simpleimage:: filename.png
37 :width: 400
38 :height: 600
40 Available options:
42 * ``:width:``, ``:height:``, ``:scale:``: resize the image
43 * ``:target:``: for HTML, clickable image
44 * ``:alt:``: for HTML
45 * ``:download:`` if the image is a url, it downloads the image.
46 * ``:convert:`` convert the image into a new format
47 """
48 required_arguments = True
49 optional_arguments = 0
50 final_argument_whitespace = True
51 option_spec = {'width': directives.unchanged,
52 'height': directives.unchanged,
53 'scale': directives.unchanged,
54 'target': directives.unchanged,
55 'alt': directives.unchanged,
56 'download': directives.unchanged,
57 'convert': directives.unchanged,
58 }
59 has_content = True
60 node_class = simpleimage_node
62 def run(self):
63 """
64 Runs the directive.
66 @return a list of nodes
67 """
68 env = self.state.document.settings.env
69 conf = env.app.config.simpleimages_config
70 docname = None if env is None else env.docname
71 if docname is not None:
72 docname = docname.replace("\\", "/").split("/")[-1]
73 else:
74 docname = ''
76 source = self.state.document.current_source
77 filename = self.arguments[0]
79 if '://' in filename:
80 logger = getLogger("simpleimage") # pragma: no cover
81 logger.warning( # pragma: no cover
82 "[simpleimage] url detected '{0}' in docname '{1}' - line {2}"
83 ".".format(filename, docname, self.lineno))
84 is_url = True
85 else:
86 is_url = False
88 convert = self.options.get('convert', None)
89 if convert:
90 logger = getLogger("simpleimage") # pragma: no cover
91 logger.warning( # pragma: no cover
92 "[simpleimage] convert into '{3}' not implemented for '{0}' in "
93 "docname '{1}' - line {2}.".format(
94 filename, docname, self.lineno, convert))
96 download = self.options.get('download', None)
97 if convert:
98 logger = getLogger("simpleimage")
99 logger.warning( # pragma: no cover
100 "[simpleimage] download not implemented for '{0}' in docname '{1}' - line {2}.".format(filename, docname, self.lineno))
102 if not is_url:
103 env.images_mapping.add_file('', filename)
105 srcdir = env.srcdir
106 rstrel = os.path.relpath(source, srcdir)
107 rstfold = os.path.split(rstrel)[0]
108 cache = os.path.join(srcdir, conf['cache_path'])
109 img = os.path.join(cache, filename)
110 abspath = None
111 relpath = None
113 if os.path.exists(img):
114 abspath = img
115 relpath = cache
116 else:
117 last = rstfold.replace('\\', '/')
118 img = os.path.join(srcdir, last, filename)
119 if os.path.exists(img):
120 relpath = last
121 abspath = img
123 if abspath is None:
124 logger = getLogger("simpleimage") # pragma: no cover
125 logger.warning( # pragma: no cover
126 "[simpleimage] Unable to find '{0}' in docname '{1}' - line {2} - srcdir='{3}'.".format(
127 filename, docname, self.lineno, srcdir))
128 else:
129 abspath = None
130 relpath = None
132 width = self.options.get('width', conf['default_image_width'])
133 height = self.options.get('height', conf['default_image_height'])
134 scale = self.options.get('scale', None)
135 alt = self.options.get('alt', None)
136 target = self.options.get('target', None)
138 # build node
139 node = self.__class__.node_class(uri=filename, docname=docname, lineno=self.lineno,
140 width=width, height=height, abspath=abspath,
141 relpath=relpath, is_url=is_url, alt=alt, scale=scale,
142 target=target, convert=convert, download=download)
143 node['classes'] += ["place-image"]
144 node['image'] = filename
145 ns = [node]
146 return ns
149def visit_simpleimage_node(self, node):
150 """
151 Visits a image node.
152 Copies the image.
153 """
154 if node['abspath'] is not None:
155 outdir = self.builder.outdir
156 relpath = os.path.join(outdir, node['relpath'])
157 dname = os.path.split(node['uri'])[0]
158 if dname:
159 relpath = os.path.join(relpath, dname)
160 if not os.path.exists(relpath):
161 os.makedirs(relpath)
162 if os.path.dirname(node['abspath']) != relpath:
163 shutil.copy(node['abspath'], relpath)
164 logger = getLogger("image") # pragma: no cover
165 logger.info("[image] copy '{0}' to '{1}'".format( # pragma: no cover
166 node['uri'], relpath))
169def _clean_value(val):
170 if isinstance(val, tuple):
171 return val[0]
172 return val
175def depart_simpleimage_node_html(self, node):
176 """
177 What to do when leaving a node *image*
178 the function should have different behaviour,
179 depending on the format, or the setup should
180 specify a different function for each.
181 """
182 if node.hasattr("uri"):
183 filename = node["uri"]
184 width = _clean_value(node["width"])
185 height = _clean_value(node["height"])
186 scale = node["scale"]
187 alt = node["alt"]
188 target = node["target"]
189 found = node["abspath"] is not None or node["is_url"]
190 if not found: # pragma: no cover
191 body = "<b>unable to find '{0}'</b>".format(filename)
192 self.body.append(body)
193 else:
194 body = '<img src="{0}" {1} {2}/>'
195 width = ' width="{0}"'.format(width) if width else ""
196 height = ' height="{0}"'.format(height) if height else ""
197 if width or height:
198 style = "{0}{1}".format(width, height)
199 elif scale:
200 style = " width={0}%".format(scale)
201 alt = ' alt="{0}"'.format(escape(alt)) if alt else ""
202 body = body.format(filename, style, alt)
203 if target:
204 body = '<a href="{0}">{1}</a>'.format(escape(target), body)
205 self.body.append(body)
208def depart_simpleimage_node_text(self, node):
209 """
210 What to do when leaving a node *image*
211 the function should have different behaviour,
212 depending on the format, or the setup should
213 specify a different function for each.
214 """
215 if 'rst' in (self.builder.name, self.builder.format):
216 depart_simpleimage_node_rst(self, node)
217 elif 'md' in (self.builder.name, self.builder.format):
218 depart_simpleimage_node_md(self, node)
219 elif 'latex' in (self.builder.name, self.builder.format):
220 depart_simpleimage_node_latex(self, node)
221 elif node.hasattr("uri"):
222 filename = node["uri"]
223 width = _clean_value(node["width"])
224 height = _clean_value(node["height"])
225 scale = node["scale"]
226 alt = node["alt"]
227 target = node["target"]
228 found = node["abspath"] is not None or node["is_url"]
229 if not found: # pragma: no cover
230 body = "unable to find '{0}'".format(filename)
231 self.body.append(body)
232 else:
233 body = '\nimage {0}{1}{2}: {3}{4}\n'
234 width = ' width="{0}"'.format(width) if width else ""
235 height = ' height="{0}"'.format(height) if height else ""
236 scale = ' scale="{0}"'.format(scale) if scale else ""
237 alt = ' alt="{0}"'.format(alt.replace('"', '\\"')) if alt else ""
238 target = ' target="{0}"'.format(
239 target.replace('"', '\\"')) if target else ""
240 body = body.format(width, height, scale, filename, alt, target)
241 self.add_text(body)
244def depart_simpleimage_node_latex(self, node):
245 """
246 What to do when leaving a node *image*
247 the function should have different behaviour,
248 depending on the format, or the setup should
249 specify a different function for each.
250 """
251 if node.hasattr("uri"):
252 width = _clean_value(node["width"])
253 height = _clean_value(node["height"])
254 scale = node["scale"]
255 alt = node["alt"]
256 full = os.path.join(node["relpath"], node['uri'])
257 found = node['abspath'] is not None or node["is_url"]
258 if not found: # pragma: no cover
259 body = "\\textbf{{unable to find '{0}'}}".format(full)
260 self.body.append(body)
261 else:
262 body = '\\includegraphics{0}{{{1}}}\n'
263 width = "width={0}".format(width) if width else ""
264 height = "height={0}".format(height) if height else ""
265 scale = "scale={0}".format(scale) if scale else ""
266 if width or height or scale:
267 dims = [_ for _ in [width, height, scale] if _]
268 style = "[{0}]".format(",".join(dims))
269 else:
270 style = ""
271 alt = ' alt="{0}"'.format(alt.replace('"', '\\"')) if alt else ""
272 full = full.replace('\\', '/').replace('_', '\\_')
273 body = body.format(style, full)
274 self.body.append(body)
277def depart_simpleimage_node_rst(self, node):
278 """
279 What to do when leaving a node *image*
280 the function should have different behaviour,
281 depending on the format, or the setup should
282 specify a different function for each.
283 """
284 if node.hasattr("uri"):
285 filename = node["uri"]
286 found = node["abspath"] is not None or node["is_url"]
287 if not found: # pragma: no cover
288 body = ".. simpleimage:: {0} [not found]".format(filename)
289 self.add_text(body + self.nl)
290 else:
291 options = SimpleImageDirective.option_spec
292 body = ".. simpleimage:: {0}".format(filename)
293 self.new_state(0)
294 self.add_text(body + self.nl)
295 for opt in options:
296 v = node.get(opt, None)
297 if v:
298 self.add_text(' :{0}: {1}'.format(opt, v) + self.nl)
299 self.end_state(wrap=False)
302def depart_simpleimage_node_md(self, node):
303 """
304 What to do when leaving a node *image*
305 the function should have different behaviour,
306 depending on the format, or the setup should
307 specify a different function for each.
308 """
309 if node.hasattr("uri"):
310 filename = node["uri"]
311 found = node["abspath"] is not None or node["is_url"]
312 if not found: # pragma: no cover
313 body = "[{0}](not found)".format(filename)
314 self.add_text(body + self.nl)
315 else:
316 alt = node.get("alt", "")
317 uri = filename
318 width = node.get('width', '').replace('px', '')
319 height = node.get('height', '').replace('px', '')
320 style = " ={0}x{1}".format(width, height)
321 if style == " =x":
322 style = ""
323 text = "![{0}]({1}{2})".format(alt, uri, style)
324 self.add_text(text)
327def initialize_simpleimages_directive(app):
328 """
329 Initializes the image directives.
330 """
331 global DEFAULT_CONFIG
333 config = copy.deepcopy(DEFAULT_CONFIG)
334 config.update(app.config.simpleimages_config)
335 app.config.simpleimages_config = config
336 app.env.images_mapping = FilenameUniqDict()
339def setup(app):
340 """
341 setup for ``image`` (sphinx)
342 """
343 global DEFAULT_CONFIG
344 if hasattr(app, "add_mapping"):
345 app.add_mapping('simpleimages_mapping', simpleimage_node)
346 app.add_config_value('simpleimages_config', DEFAULT_CONFIG, 'env')
347 app.connect('builder-inited', initialize_simpleimages_directive)
348 app.add_node(simpleimage_node,
349 html=(visit_simpleimage_node, depart_simpleimage_node_html),
350 epub=(visit_simpleimage_node, depart_simpleimage_node_html),
351 elatex=(visit_simpleimage_node,
352 depart_simpleimage_node_latex),
353 latex=(visit_simpleimage_node, depart_simpleimage_node_latex),
354 rst=(visit_simpleimage_node, depart_simpleimage_node_rst),
355 md=(visit_simpleimage_node, depart_simpleimage_node_md),
356 text=(visit_simpleimage_node, depart_simpleimage_node_text))
358 app.add_directive('simpleimage', SimpleImageDirective)
359 return {'version': sphinx.__display_version__, 'parallel_read_safe': True}