Hide keyboard shortcuts

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 

15 

16 

17DEFAULT_CONFIG = dict( 

18 default_image_width=None, 

19 default_image_height=None, 

20 cache_path='_images', 

21) 

22 

23 

24class simpleimage_node(nodes.General, nodes.Element): 

25 

26 """ 

27 Defines *image* node. 

28 """ 

29 pass 

30 

31 

32class SimpleImageDirective(Directive): 

33 """ 

34 Adds an image to a page. It can be done by adding:: 

35 

36 .. simpleimage:: filename.png 

37 :width: 400 

38 :height: 600 

39 

40 Available options: 

41 

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 

61 

62 def run(self): 

63 """ 

64 Runs the directive. 

65 

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 = '' 

75 

76 source = self.state.document.current_source 

77 filename = self.arguments[0] 

78 

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 

87 

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)) 

95 

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)) 

101 

102 if not is_url: 

103 env.images_mapping.add_file('', filename) 

104 

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 

112 

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 

122 

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 

131 

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) 

137 

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 

147 

148 

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)) 

167 

168 

169def _clean_value(val): 

170 if isinstance(val, tuple): 

171 return val[0] 

172 return val 

173 

174 

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) 

206 

207 

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) 

242 

243 

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) 

275 

276 

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) 

300 

301 

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) 

325 

326 

327def initialize_simpleimages_directive(app): 

328 """ 

329 Initializes the image directives. 

330 """ 

331 global DEFAULT_CONFIG 

332 

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() 

337 

338 

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)) 

357 

358 app.add_directive('simpleimage', SimpleImageDirective) 

359 return {'version': sphinx.__display_version__, 'parallel_read_safe': True}