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 Contains the main function to generate the documentation 

5for a module designed the same way as this one, @see fn generate_help_sphinx. 

6""" 

7import os 

8import re 

9import warnings 

10import glob 

11from .utils_sphinx_doc_helpers import HelpGenException 

12 

13 

14template_examples = """ 

15 

16List of programs 

17++++++++++++++++ 

18 

19.. toctree:: 

20 :maxdepth: 2 

21 

22.. autosummary:: __init__.py 

23 :toctree: %s/ 

24 :template: modules.rst 

25 

26Another list 

27++++++++++++ 

28 

29""" 

30 

31 

32def update_notebook_link(text, format, nblinks, fLOG): 

33 """ 

34 A notebook can contain a link ``[anchor](find://...)`` 

35 and it will be converted into: ``:ref:...`` in rst format. 

36 

37 @param text text to look into 

38 @param format format 

39 @param nblinks list of mappings *(reference: url)* 

40 @param fLOG logging function 

41 @return modified text 

42 """ 

43 def get_url_from_nblinks(nblinks, url, format): 

44 if isinstance(nblinks, dict): 

45 if (url, format) in nblinks: 

46 url = nblinks[url, format] 

47 elif url in nblinks: 

48 url = nblinks[url] 

49 if url.startswith("find://"): 

50 short = url[7:] 

51 if (short, format) in nblinks: 

52 url = nblinks[short, format] 

53 elif short in nblinks: 

54 url = nblinks[short] 

55 else: 

56 url = nblinks(url, format) 

57 if url.startswith("find://"): 

58 if format == 'python': 

59 url = url[7:] 

60 else: # pragma: no cover 

61 snb = "\n".join("'{0}': '{1}'".format(k, v) 

62 for k, v in sorted(nblinks.items())) 

63 extension = ( 

64 "You shoud add links into variable 'nblinks' " 

65 "into documentation configuration file.") 

66 extension += "\nnblinks={0}".format(nblinks) 

67 raise HelpGenException( 

68 "Unable to find a replacement for '{0}' format='{1}' in \n{2}\n{3}".format( 

69 url, format, snb, extension)) 

70 return url 

71 

72 if nblinks is None: 

73 nblinks = {} 

74 if format == "rst": 

75 def reprst(le): 

76 anc, url = le.groups() 

77 url = get_url_from_nblinks(nblinks, url, format) 

78 if "://" in url: 

79 new_url = "`{0} <{1}>`_".format(anc, url) 

80 else: 

81 new_url = ":ref:`{0} <{1}>`".format(anc, url) 

82 if fLOG: 

83 fLOG(" [update_notebook_link]1 add in ", 

84 format, ":", new_url) 

85 return new_url 

86 reg = re.compile("`([^`]+?) <find://([^`<>]+?)>`_") 

87 new_text = reg.sub(reprst, text) 

88 elif format in ("html", "slides", "slides2"): 

89 def rephtml(le): 

90 anc, url = le.groups() 

91 url = get_url_from_nblinks(nblinks, url, format) 

92 new_url = "<a href=\"{0}.html\">{1}</a>".format(anc, url) 

93 if fLOG: 

94 fLOG(" [update_notebook_link]2 add in ", 

95 format, ":", new_url) 

96 return new_url 

97 reg = re.compile("<a href=\\\"find://([^\\\"]+?)\\\">([^`<>]+?)</a>") 

98 new_text = reg.sub(rephtml, text) 

99 elif format in ("ipynb", "python"): 

100 def repipy(le): 

101 anc, url = le.groups() 

102 url = get_url_from_nblinks(nblinks, "find://" + url, format) 

103 if not url.startswith("http"): 

104 mes = "\n".join("{0}: '{1}'".format(k, v) 

105 for k, v in sorted(nblinks.items())) 

106 extension = "You should add this link into the documentation " \ 

107 "configuration file in variable 'nblinks'." 

108 raise HelpGenException( # pragma: no cover 

109 "A reference was not found: '{0}' - '{1}' " 

110 "format={2}, nblinks=\n{3}\n{4}".format( 

111 anc, url, format, mes, extension)) 

112 new_url = "[{0}]({1})".format(anc, url) 

113 if fLOG: 

114 fLOG(" [update_notebook_link]3 add in ", 

115 format, ":", new_url) 

116 return new_url 

117 reg = re.compile("[\\[]([^[]+?)[\\]][(]find://([^ ]+)[)]") 

118 new_text = reg.sub(repipy, text) 

119 elif format in ("latex", "elatex"): 

120 def replat(le): 

121 url, anc = le.groups() 

122 url = get_url_from_nblinks(nblinks, url, format) 

123 if not url.endswith(".html") and not url.endswith(".js") and not url.endswith(".css"): 

124 url += ".html" 

125 new_url = "\\href{{{0}}}{{{1}}}".format(url, anc) 

126 if fLOG: 

127 fLOG(" [update_notebook_link]4 add in ", 

128 format, ":", new_url) 

129 return new_url 

130 reg = re.compile("\\\\href{find://([^{} ]+?)}{([^{}]+)}") 

131 new_text = reg.sub(replat, text) 

132 # {\hyperref[\detokenize{c_classes/classes:chap-classe}] 

133 # {\sphinxcrossref{\DUrole{std,std-ref}{Classes}}}} 

134 else: 

135 raise NotImplementedError( # pragma: no cover 

136 "Unsupported format '{0}'\n{1}".format(format, text)) 

137 return new_text 

138 

139 

140def _notebook_replacements(nbtext, notebook_replacements, fLOG=None): 

141 """ 

142 Makes some replacements in a notebook. 

143 

144 @param nbtext text to process 

145 @param notebook_replacements dictionary of replacements 

146 @param fLOG logging function 

147 @return text 

148 """ 

149 if notebook_replacements is None: 

150 return nbtext 

151 for k, v in notebook_replacements: 

152 if k in nbtext: 

153 fLOG( 

154 "[_notebook_replacements] replace '{0}' -> '{1}'".format(k, v)) 

155 nbtext = nbtext.replace(k, v) 

156 if '"nbformat": 4,' in nbtext: # pragma: no cover 

157 rep = ['"nbformat_minor": 0', '"nbformat_minor": 1', 

158 '"nbformat_minor": 2'] 

159 for r in rep: 

160 if r in nbtext: 

161 nbtext = nbtext.replace(r, '"nbformat_minor": 4') 

162 return nbtext 

163 

164 

165def post_process_latex_output(root, doall, latex_book=False, exc=True, 

166 custom_latex_processing=None, nblinks=None, 

167 remove_unicode=True, fLOG=None, notebook_replacements=None): 

168 """ 

169 Postprocesses the latex file produced by :epkg:`sphinx`. 

170 

171 @param root root path or latex file to process 

172 @param doall do all transformations 

173 @param latex_book customized for a book 

174 @param exc raises an exception or a warning 

175 @param custom_latex_processing function which does some post processing of the full latex file 

176 @param nblinks dictionary ``{ ref : url }`` where to look for references 

177 @param remove_unicode remove unicode characters (fails with latex) 

178 @param notebook_replacements string replacement in notebooks 

179 @param fLOG logging function 

180 """ 

181 if os.path.isfile(root): 

182 file = root 

183 if fLOG: 

184 fLOG("[post_process_latex_output] clean %r" % file) 

185 with open(file, "r", encoding="utf8") as f: 

186 content = f.read() 

187 with open(file + ".tex1~", "w", encoding="utf8") as f: 

188 f.write(content) 

189 content = post_process_latex( 

190 content, doall, latex_book=latex_book, exc=exc, 

191 custom_latex_processing=custom_latex_processing, nblinks=nblinks, 

192 file=file, remove_unicode=remove_unicode, fLOG=fLOG, 

193 notebook_replacements=notebook_replacements) 

194 with open(file, "w", encoding="utf8") as f: 

195 f.write(content) 

196 else: 

197 build = os.path.join(root, "_doc", "sphinxdoc", "build", "latex") 

198 if not os.path.exists(build): 

199 raise FileNotFoundError(build) 

200 for tex in os.listdir(build): 

201 if tex.endswith(".tex"): 

202 file = os.path.join(build, tex) 

203 fLOG("[post_process_latex_output] modify file", file) 

204 with open(file, "r", encoding="utf8") as f: 

205 content = f.read() 

206 with open(file + ".tex2~", "w", encoding="utf8") as f: 

207 f.write(content) 

208 content = post_process_latex( 

209 content, doall, info=file, latex_book=latex_book, exc=exc, 

210 custom_latex_processing=custom_latex_processing, nblinks=nblinks, 

211 file=file, remove_unicode=remove_unicode, fLOG=fLOG, 

212 notebook_replacements=notebook_replacements) 

213 with open(file, "w", encoding="utf8") as f: 

214 f.write(content) 

215 

216 

217def post_process_python_output(root, doall, exc=True, nblinks=None, fLOG=None, notebook_replacements=None): 

218 """ 

219 Postprocesses the python file produced by :epkg:`sphinx`. 

220 

221 @param root root path or python file to process 

222 @param doall unused 

223 @param exc raise an exception if needed 

224 @param nblinks dictionary ``{ref: url}`` 

225 @param notebook_replacements string replacement in notebooks 

226 @param fLOG logging function 

227 """ 

228 if os.path.isfile(root): 

229 file = root 

230 if fLOG: 

231 fLOG("[post_process_python_output] clean %r" % file) 

232 with open(file, "r", encoding="utf8") as f: 

233 content = f.read() 

234 content = post_process_python( 

235 content, doall, nblinks=nblinks, file=file, fLOG=fLOG, 

236 notebook_replacements=notebook_replacements) 

237 with open(file, "w", encoding="utf8") as f: 

238 f.write(content) 

239 else: 

240 build = os.path.join(root, "_doc", "sphinxdoc", "build", "latex") 

241 if not os.path.exists(build) and exc: 

242 raise FileNotFoundError(build) 

243 for tex in os.listdir(build): 

244 if tex.endswith(".tex"): 

245 file = os.path.join(build, tex) 

246 fLOG("[post_process_python_output] modify file", file) 

247 with open(file, "r", encoding="utf8") as f: 

248 content = f.read() 

249 content = post_process_python( 

250 content, doall, info=file, nblinks=nblinks, file=file, fLOG=fLOG) 

251 with open(file, "w", encoding="utf8") as f: 

252 f.write(content) 

253 

254 

255def post_process_latex_output_any(file, custom_latex_processing, nblinks=None, 

256 remove_unicode=False, fLOG=None, notebook_replacements=None): 

257 """ 

258 Postprocesses the latex file produced by :epkg:`sphinx`. 

259 

260 @param file latex filename 

261 @param custom_latex_processing function which does some post processing of the full latex file 

262 @param nblinks dictionary ``{url: link}`` 

263 @param remove_unicode remove unicode characters 

264 @param notebook_replacements string replacement in notebooks 

265 @param fLOG logging function 

266 """ 

267 if fLOG: 

268 fLOG("[post_process_latex_output_any] ** post_process_latex_output_any ", file) 

269 if not os.path.exists(file): 

270 raise FileNotFoundError( # pragma: no cover 

271 "Unable to find '{}', other files in the same folder\n{}".format( 

272 file, "\n".join(os.listdir(os.path.dirname(file))))) 

273 with open(file, "r", encoding="utf8") as f: 

274 content = f.read() 

275 with open(file + ".tex3.u{0}~".format(1 if remove_unicode else 0), "w", encoding="utf8") as f: 

276 f.write(content) 

277 content = post_process_latex(content, True, info=file, nblinks=nblinks, file=file, 

278 remove_unicode=remove_unicode, fLOG=fLOG, 

279 notebook_replacements=notebook_replacements) 

280 with open(file, "w", encoding="utf8") as f: 

281 f.write(content) 

282 

283 

284def post_process_rst_output(file, html, pdf, python, slides, is_notebook=False, 

285 exc=True, github=False, notebook=None, nblinks=None, fLOG=None, 

286 notebook_replacements=None): 

287 """ 

288 Processes a :epkg:`rst` file generated from the conversion of a notebook. 

289 

290 @param file filename 

291 @param pdf if True, add a link to the :epkg:`pdf`, 

292 assuming it will exists at the same location 

293 @param html if True, add a link to the :epkg:`html` conversion 

294 @param python if True, add a link to the :epkg:`Python` conversion 

295 @param slides if True, add a link to the slides conversion 

296 @param is_notebook does something more if the file is a notebook 

297 @param exc raises an exception (True) or a warning (False) 

298 @param github add a link to the notebook on :epkg:`github` 

299 @param notebook location of the notebook, file might be a copy 

300 @param nblinks links added to a notebook, dictionary ``{ref: url}`` 

301 @param notebook_replacements string replacement in notebooks 

302 @param fLOG logging function 

303 

304 The function adds the following replacement 

305 ``st = st.replace("\\\\mathbb{1}", "\\\\mathbf{1\\\\!\\\\!1}")``. 

306 and checks that audio is only included in :epkg:`HTML`. 

307 """ 

308 if fLOG: 

309 fLOG("[post_process_rst_output] clean %r" % file) 

310 

311 name = os.path.split(file)[1] 

312 noext = os.path.splitext(name)[0] 

313 with open(file, "r", encoding="utf8") as f: 

314 lines = f.readlines() 

315 with open(file + "~", "w", encoding="utf8") as f: 

316 f.write("".join(lines)) 

317 

318 # Probably not the best way to fix that. 

319 # For some reason, nbconvert adds None as the first row. 

320 if lines[0] == 'None\n': 

321 lines[0] = '\n' # pragma: no cover 

322 

323 if any(line == 'None\n' for line in lines): 

324 raise HelpGenException( # pragma: no cover 

325 "One row unexpectedly contains only None in '{}'\n{}".format( 

326 file, "".join(lines[:20]))) 

327 

328 # Removes empty lines in inserted code, also adds line number. 

329 def startss(line): 

330 for b in ["::", ".. parsed-literal::", ".. code:: python", 

331 ".. code-block:: python"]: 

332 if line.startswith(b): 

333 return b 

334 return None 

335 

336 codeb = [".. code:: python", ".. code-block:: python"] 

337 inbloc = False 

338 for pos, line in enumerate(lines): 

339 if not inbloc: 

340 b = startss(line) 

341 if b is None: 

342 pass 

343 else: 

344 if b in codeb: 

345 # we remove line number for the notebooks 

346 if "notebook" not in file: 

347 lines[pos] = "{0}\n :linenos:\n\n".format(codeb[-1]) 

348 else: 

349 lines[pos] = "{0}\n\n".format(codeb[-1]) 

350 inbloc = True 

351 memopos = pos 

352 else: 

353 if len(line.strip(" \r\n")) == 0 and pos < len(lines) - 1 and \ 

354 lines[pos + 1].startswith(" ") and len(lines[pos + 1].strip(" \r\n")) > 0: 

355 lines[pos] = "" 

356 

357 elif not line.startswith(" ") and line != "\n": 

358 inbloc = False 

359 

360 if lines[memopos].startswith("::"): 

361 code = "".join( 

362 (_[4:] if _.startswith(" ") else _) for _ in lines[memopos + 1:pos]) 

363 if len(code) == 0: 

364 fLOG( # pragma: no cover 

365 "[post_process_rst_output] EMPTY-SECTION in ", file) 

366 else: 

367 try: 

368 cmp = compile(code, "", "exec") 

369 if cmp is not None: 

370 lines[memopos] = "{0}\n :linenos:\n".format( 

371 ".. code-block:: python") 

372 except Exception: # pragma: no cover 

373 pass 

374 

375 memopos = None 

376 

377 # code and images 

378 imgreg = re.compile("[.][.] image:: (.*)") 

379 for pos in range(0, len(lines)): 

380 # lines[pos] = lines[pos].replace(".. code:: python","::") 

381 if lines[pos].strip().startswith(".. image::"): 

382 # we assume every image should be placed in the same folder as the 

383 # notebook itself 

384 img = imgreg.findall(lines[pos]) 

385 if len(img) == 0: 

386 raise HelpGenException( # pragma: no cover 

387 "Unable to extract image name in '{0}'".format(lines[pos])) 

388 nameimg = img[0] 

389 short = nameimg.replace("%5C", "/") 

390 short = os.path.split(short)[-1] 

391 lines[pos] = lines[pos].replace(nameimg, short) 

392 

393 # title 

394 for pos, line in enumerate(lines): 

395 line = line.strip("\n\r") 

396 if len(line) > 0 and line == "=" * len(line): 

397 # lines[pos] = lines[pos].replace("=", "*") 

398 pos2 = pos - 1 

399 li = len(lines[pos]) 

400 while len(lines[pos2]) != li: 

401 pos2 -= 1 

402 sep = "" if lines[pos2].endswith("\n") else "\n" 

403 lines[pos2] = "{0}{2}{1}".format(lines[pos], lines[pos2], sep) 

404 for p in range(pos2 + 1, pos): 

405 if lines[p] == "\n": # pragma: no cover 

406 lines[p] = "" 

407 break 

408 

409 pos += 1 

410 if pos >= len(lines): 

411 mes = "Unable to find a title in notebook '{0}'".format(file) 

412 if exc: 

413 raise HelpGenException(mes) # pragma: no cover 

414 warnings.warn(mes, UserWarning) 

415 

416 # label 

417 labelname = name.replace(" ", "").replace("_", "").replace( 

418 ":", "").replace(".", "").replace(",", "") 

419 label = "\n.. _{0}:\n\n".format(labelname) 

420 lines.insert(0, label) 

421 

422 # links 

423 links = ['**Links:** :download:`notebook <{0}.ipynb>`'.format(noext)] 

424 if html: 

425 links.append(':downloadlink:`html <{0}2html.html>`'.format(noext)) 

426 if pdf: 

427 links.append(':download:`PDF <{0}.pdf>`'.format(noext)) 

428 if python: 

429 links.append(':download:`python <{0}.py>`'.format(noext)) 

430 if slides: 

431 links.append(':downloadlink:`slides <{0}.slides.html>`'.format(noext)) 

432 

433 if github: 

434 if notebook is None: 

435 raise ValueError( # pragma: no cover 

436 "Cannot add a link on github, notebook is None for " 

437 "file='{0}'".format(file)) 

438 docname = notebook 

439 folder = docname 

440 git = os.path.join(folder, ".git") 

441 while len(folder) > 0 and not os.path.exists(git): 

442 folder = os.path.split(folder)[0] 

443 git = os.path.join(folder, ".git") 

444 if len(folder) > 0: 

445 path = docname[len(folder):] 

446 tried = [] 

447 if path.strip('/\\').startswith('build'): 

448 # The notebook may be in a build folder but is not 

449 # the original notebook. The function does something 

450 # if the path starts with `build`. 

451 subfolds = os.listdir(folder) 

452 for sub in subfolds: 

453 fulls = os.path.join(folder, sub) 

454 if not os.path.isdir(fulls): 

455 continue 

456 if not ('_doc' in sub or 'notebook' in sub or 'example' in sub): 

457 continue 

458 # Search for another version of the file. 

459 last_name = os.path.split(docname)[-1] 

460 tried.append((last_name, fulls)) 

461 selected = glob.glob( 

462 fulls + "/**/" + last_name, recursive=True) 

463 if len(selected) == 1: 

464 docname = selected[0] 

465 path = docname[len(folder):] 

466 break 

467 if "blob/master/build" in path or "build/notebooks" in path: 

468 # raise RuntimeError( # pragma: no cover 

469 warnings.warn( # pragma: no cover 

470 "Unexpected substring found in %r in folder %r\n" 

471 "--TRIED--\n%r" % (path, folder, "\n".join(map(str, tried)))) 

472 links.append( 

473 ":githublink:`GitHub|{0}|*`".format(path.replace("\\", "/").lstrip("/"))) 

474 lines[pos] = "{0}\n\n.. only:: html\n\n {1}\n\n".format( 

475 lines[pos], ", ".join(links)) 

476 

477 # we remove the 

478 # <div 

479 # style="position:absolute; 

480 # .... 

481 # </div> 

482 reg = re.compile( 

483 "([.]{2} raw[:]{2} html[\\n ]+<div[\\n ]+style=.?position:absolute;(.|\\n)*?[.]{2} raw[:]{2} html[\\n ]+</div>)") 

484 merged = "".join(lines) 

485 r = reg.findall(merged) 

486 if len(r) > 0: 

487 fLOG("[post_process_rst_output] *** remove div absolute in ", file) 

488 for spa in r: 

489 rep = spa[0] 

490 nbl = len(rep.split("\n")) 

491 merged = merged.replace(rep, "\n" * nbl) 

492 lines = [(_ + "\n") for _ in merged.split("\n")] 

493 

494 # bullets 

495 for pos, line in enumerate(lines): 

496 if pos == 0: 

497 continue 

498 if len(line) > 0 and (line.startswith("- ") or line.startswith("* ")) \ 

499 and pos < len(lines) - 1: 

500 next = lines[pos + 1] 

501 prev = lines[pos - 1] 

502 if (next.startswith("- ") or next.startswith("* ")) \ 

503 and not (prev.startswith("- ") or prev.startswith("* ")) \ 

504 and not prev.startswith(" "): 

505 lines[pos - 1] += "\n" 

506 elif line.startswith("- ") and next.startswith(" ") \ 

507 and not prev.startswith(" ") and not prev.startswith("- "): 

508 lines[pos - 1] += "\n" 

509 elif line.startswith("- "): 

510 pass 

511 

512 # remove last :: 

513 i = len(lines) 

514 for i in range(len(lines), 1, -1): 

515 s = lines[i - 1].strip(" \n\r") 

516 if len(s) != 0 and s != "::": 

517 break 

518 

519 if i < len(lines): 

520 del lines[i:] 

521 

522 # specific treatment for notebooks 

523 if is_notebook: 

524 # change links <#Alink --> <#alink 

525 reg = re.compile("(<#[A-Z][a-zA-Z0-9_+-]+>)") 

526 for i, line in enumerate(lines): 

527 r = reg.search(line) 

528 if r: 

529 memo = r.groups()[0] 

530 new_memo = "<#" + memo[2].lower() + memo[3:] 

531 new_memo = new_memo.replace("+", "") 

532 line = line.replace(memo, new_memo) 

533 lines[i] = line 

534 

535 # checking for find:// 

536 content = "".join(lines) 

537 content = update_notebook_link(content, "rst", nblinks=nblinks, fLOG=fLOG) 

538 if "find://" in content: 

539 raise HelpGenException( # pragma: no cover 

540 "find:// was found in '{0}'.\nYou should " 

541 "add or extend 'nblinks' in conf.py.".format(file)) 

542 

543 # notebooks replacements 

544 content = _notebook_replacements(content, notebook_replacements, fLOG) 

545 

546 # replaces the function 

547 content = content.replace("\\mathbb{1}", "\\mathbf{1\\!\\!1}") 

548 

549 with open(file, "w", encoding="utf8") as f: 

550 f.write(content) 

551 

552 

553def post_process_html_output(file, pdf, python, slides, exc=True, 

554 nblinks=None, fLOG=None, 

555 notebook_replacements=None): 

556 """ 

557 Processes a HTML file generated from the conversion of a notebook. 

558 

559 @param file filename 

560 @param pdf if True, add a link to the PDF, assuming it will exists 

561 at the same location 

562 @param python if True, add a link to the Python conversion 

563 @param slides if True, add a link to the slides conversion 

564 @param exc raises an exception (True) or a warning (False) 

565 @param nblinks dictionary ``{ref: url}`` 

566 @param notebook_replacements string replacement in notebooks 

567 @param fLOG logging function 

568 """ 

569 if not os.path.exists(file): 

570 raise FileNotFoundError(file) # pragma: no cover 

571 if fLOG: 

572 fLOG("[post_process_html_output] clean %r" % file) 

573 with open(file, "r", encoding="utf8") as f: 

574 text = f.read() 

575 

576 # mathjax 

577 text = text.replace( 

578 "https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML", 

579 "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML") 

580 

581 # notebook replacements 

582 if fLOG: 

583 fLOG("[post_process_html_output] nb:", notebook_replacements) 

584 text = _notebook_replacements(text, notebook_replacements, fLOG) 

585 

586 text = update_notebook_link(text, "html", nblinks=nblinks, fLOG=fLOG) 

587 if "find://" in text: 

588 raise HelpGenException( # pragma: no cover 

589 "find:// was found in '{0}'.\nYou should add " 

590 "or extend 'nblinks' in conf.py.".format(file)) 

591 

592 # js 

593 if fLOG: 

594 fLOG("[post_process_html_output] js: replacements") 

595 repl = {'https://unpkg.com/@jupyter-widgets/html-manager@^0.20.0/dist/embed-amd.js': 

596 '../_static/embed-amd.js'} 

597 lines = text.split('\n') 

598 new_lines = [] 

599 for line in lines: 

600 if "https://cdnjs.cloudflare.com/ajax/libs/require.js" in line: 

601 if fLOG: 

602 fLOG("[post_process_html_output] js: skip %r" % line) 

603 continue 

604 new_lines.append(line) 

605 text = "\n".join(new_lines) 

606 for k, v in repl.items(): 

607 if k in text: 

608 if fLOG: # pragma: no cover 

609 fLOG("[post_process_html_output] js: replace %r -> %r" % (k, v)) 

610 text = text.replace(k, v) 

611 

612 with open(file, "w", encoding="utf8") as f: 

613 f.write(text) 

614 

615 

616def post_process_slides_output(file, pdf, python, slides, exc=True, 

617 nblinks=None, fLOG=None, 

618 notebook_replacements=None): 

619 """ 

620 Processes a :epkg:`HTML` file generated from the conversion of a notebook. 

621 

622 @param file filename 

623 @param pdf if True, add a link to the PDF, assuming it will 

624 exists at the same location 

625 @param python if True, add a link to the Python conversion 

626 @param slides if True, add a link to the slides conversion 

627 @param exc raises an exception (True) or a warning (False) 

628 @param nblinks dictionary ``{ref: url}`` 

629 @param notebook_replacements string replacement in notebooks 

630 @param fLOG logging function 

631 """ 

632 if (len(file) > 5000 or not os.path.exists(file)) and "<html" in file: 

633 text = file # pragma: no cover 

634 save = False # pragma: no cover 

635 else: 

636 if not os.path.exists(file): 

637 raise FileNotFoundError(file) # pragma: no cover 

638 if fLOG: 

639 fLOG("[post_process_slides_output] clean %r" % file) 

640 # fold, name = os.path.split(file) 

641 with open(file, "r", encoding="utf8") as f: 

642 text = f.read() 

643 save = True 

644 

645 # reveal.js 

646 require = "require(" in text 

647 text = text.replace("reveal.js/dist/reveal.css", 

648 "reveal.js/css/reveal.css") 

649 text = text.replace("reveal.js/dist/theme/simple.css", 

650 "reveal.js/css/theme/simple.css") 

651 text = text.replace("https://unpkg.com/@jupyter-widgets/html-manager@0.20.0/dist/embed-amd.js", 

652 "embed-amd.js") 

653 lines = text.split("\n") 

654 for i, line in enumerate(lines): 

655 if '<script src="reveal.js/lib/js/head.min.js"></script>' in line: 

656 lines[i] = ( 

657 '<script src="reveal.js/js/jquery.min.js"></script>\n' + lines[i]) 

658 if '<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>' in line: 

659 lines[i] = "" 

660 if '<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/' in line: 

661 lines[i] = "" 

662 if lines[i] == "</script>" and require: 

663 lines[i] += '\n<script src="require.js"></script>' 

664 require = False 

665 text = "\n".join(lines) 

666 

667 # mathjax 

668 text = text.replace("https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML", 

669 "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML") 

670 text = update_notebook_link(text, "slides", nblinks=nblinks, fLOG=fLOG) 

671 if "find://" in text: 

672 raise HelpGenException( # pragma: no cover 

673 "find:// was found in '{0}'.\nYou should add " 

674 "or extend 'nblinks' in conf.py.".format(file)) 

675 

676 # notebook replacements 

677 text = _notebook_replacements(text, notebook_replacements, fLOG) 

678 

679 if save: 

680 with open(file, "w", encoding="utf8") as f: 

681 f.write(text) 

682 return text 

683 

684 

685def post_process_latex(st, doall, info=None, latex_book=False, exc=True, 

686 custom_latex_processing=None, nblinks=None, file=None, 

687 remove_unicode=False, fLOG=None, notebook_replacements=None): 

688 """ 

689 Modifies a :epkg:`latex` file after its generation by :epkg:`sphinx`. 

690 

691 @param st string 

692 @param doall do all transformations 

693 @param info for more understandable error messages 

694 @param latex_book customized for a book 

695 @param exc raises an exception or a warning 

696 @param custom_latex_processing function which takes and returns a string, 

697 final post processing 

698 @param nblinks dictionary ``{ref: url}`` 

699 @param file only used when an exception is raised 

700 @param remove_unicode remove unicode character (fails when converting into PDF) 

701 @param notebook_replacements string replacement in notebooks 

702 @param fLOG logging function 

703 @return string 

704 

705 *SVG* included in a notebook (or in *RST* file) usually do not word. 

706 :epkg:`Inkscape` should be used to convert them into Latex. 

707 The function is less strict on the checking of `$`. 

708 The function replaces ``\\mathbb{1}`` by ``\\mathbf{1\\!\\!1}``. 

709 

710 .. index:: chinese characters, latex, unicode 

711 

712 .. faqref:: 

713 :title: Why a ¿ is showing the final PDF? 

714 

715 Unicode, chinese characters are an issue because the latex compiler 

716 prompts on those if the necessary packages are not installed. 

717 `pdflatex <https://en.wikipedia.org/w/index.php?title=PdfTeX&redirect=no>`_ 

718 does not accepts inline chinese 

719 characters, `xetex <https://en.wikipedia.org/wiki/XeTeX>`_ 

720 should be used instead: 

721 see `How to input Traditional Chinese in pdfLaTeX 

722 <http://tex.stackexchange.com/questions/200449/how-to-input-traditional-chinese-in-pdflatex>`_. 

723 Until this is being implemented, the unicode will unfortunately be removed 

724 in this function. 

725 

726 .. versionchanged:: 1.9 

727 Removes the uses of package `parskip 

728 <https://ctan.org/pkg/parskip?lang=en>`_, it uses command 

729 ``\\DeclareRelease`` which is not always recognized. 

730 """ 

731 if fLOG: 

732 fLOG("[post_process_latex] ** enter post_process_latex", 

733 doall, "%post_process_latex" in st) 

734 weird_character = set(chr(i) for i in range(1, 9)) 

735 

736 def clean_unicode(c): 

737 if c == "’": 

738 return "'" 

739 if c == "…": 

740 return "..." 

741 if ord(c) >= 255 or c in weird_character: 

742 return "\\textquestiondown " 

743 return c 

744 

745 def clean_line(line): 

746 if line.startswith("\\documentclass"): 

747 line = line.replace("{None}", "{report}") 

748 return line 

749 

750 lines = st.split("\n") 

751 lines = list(map(clean_line, lines)) 

752 st = "\n".join("".join(map(clean_unicode, line)) for line in lines) 

753 

754 # we count the number of times we have \$ (which is unexpected unless the 

755 # currency is used. 

756 dollar = st.split("\\$") 

757 if len(dollar) > 0 and ( 

758 info is None or os.path.splitext(info)[-1] != ".html"): 

759 # it could be an issue, for the time being, we raise 

760 # an exception if a formula is too long 

761 exp = re.compile(r"(.{200}[\\]\$\$)") 

762 found = 0 

763 records = [] 

764 for m in exp.finditer(st): 

765 found += 1 

766 p1, p2 = m.start(), m.end() 

767 sub = st[p1:p2].strip(" \r\n").replace( 

768 "\n", " ").replace("\r", "").replace("\t", " ") 

769 sub2 = sub[-10:] 

770 records.append((info, p1, p2, sub, sub2, "")) 

771 if len(records) > 0: # pragma: no cover 

772 messages = [str(i) + ":" + ("unexpected \\$ in a latex file:\n {0}\n" + 

773 "at position: {1},{2}\n substring: {3}\n " + 

774 "around: {4}\n temp=[{5}]").format(*rec) 

775 for i, rec in enumerate(records)] 

776 for mes in messages: 

777 warnings.warn(mes, UserWarning) 

778 

779 st = st.replace("<br />", "\\\\") 

780 st = st.replace("»", '"') 

781 st = st.replace("\\mathbb{1}", "\\mathbf{1\\!\\!1}") 

782 st = st.replace( 

783 "\\documentclass[11pt]{article}", "\\documentclass[10pt]{article}") 

784 

785 if not doall and not latex_book: 

786 st = st.replace( 

787 "\\maketitle", "\\maketitle\n\n\\newchapter{Introduction}") 

788 

789 st = st.replace("%5C", "/") \ 

790 .replace("%3A", ":") \ 

791 .replace("\\includegraphics{notebooks\\", "\\includegraphics {") 

792 st = st.replace( 

793 "\\begin{document}", "\\setlength{\\parindent}{0cm}%s\\begin {document}" % "\n") 

794 st = st.replace("DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\}}", 

795 "DefineVerbatimEnvironment{Highlighting}{Verbatim} {commandchars=\\\\\\{\\},fontsize=\\small}") 

796 st = st.replace("\\textquotesingle{}", "'") 

797 st = st.replace("\u0001", "\\u1") 

798 st = st.replace("\\begin{notice}{note}\\end{notice}", "") 

799 

800 # hyperref 

801 if doall and "%post_process_latex" not in st: 

802 st = "%post_process_latex\n" + st 

803 reg = re.compile("hyperref[\\[]([a-zA-Z0-9]+)[\\]][\\{](.*?)[\\}]") 

804 allhyp = reg.findall(st) 

805 sections = [] 

806 for id, section in allhyp: 

807 sec = r"\subsection{%s} \label{%s}" % (section, id) 

808 sections.append((id, section, sec)) 

809 elif not doall and not latex_book: 

810 sections = [] 

811 # first section 

812 lines = st.split("\n") 

813 for i, line in enumerate(lines): 

814 if "\\section" in line: 

815 lines[i] = "\\newchapter{Documentation}\n" + lines[i] 

816 break 

817 st = "\n".join(lines) 

818 else: 

819 sections = [] 

820 

821 if len(sections) > 0: 

822 lines = st.split("\n") 

823 for i, line in enumerate(lines): 

824 for _, section, sec in sections: 

825 if line.strip("\r\n ") == section: 

826 fLOG(" **", section, " --> ", sec) 

827 lines[i] = sec 

828 st = "\n".join(lines) 

829 

830 if not latex_book: 

831 st = st.replace("\\chapter", "\\section") 

832 st = st.replace("\\newchapter", "\\chapter") 

833 

834 comment_out = [ 

835 '\\usepackage{parskip}', 

836 '\\usepackage{fontspec}', 

837 '\\begin{center}\\rule{0.5\\linewidth}{\\linethickness}\\end{center}\n', 

838 ] 

839 for co in comment_out: 

840 if co in st: 

841 st = st.replace(co, "%" + co) 

842 if "\\usepackage{multirow}" in st: 

843 st = st.replace( 

844 "\\usepackage{svg}\\usepackage{multirow}", 

845 "\\usepackage{multirow}\\usepackage{amssymb}\\usepackage{latexsym}\\usepackage{amsfonts}\\usepackage{ulem}\\usepackage{textcomp}") 

846 elif "\\usepackage{hyperref}" in st: 

847 st = st.replace( 

848 "\\usepackage{svg}\\usepackage{hyperref}", 

849 "\\usepackage{hyperref}\\usepackage{amssymb}\\usepackage{latexsym}\\usepackage{amsfonts}\\usepackage{ulem}\\usepackage{textcomp}") 

850 else: 

851 raise HelpGenException( # pragma: no cover 

852 "unable to add new instructions usepackage in file {0}".format(info)) 

853 

854 # SVG does not work unless it is converted (nbconvert should handle that 

855 # case) 

856 reg = re.compile("([\\\\]includegraphics[{].*?[.]svg[}])") 

857 fall = reg.findall(st) 

858 for found in fall: 

859 st = st.replace(found, "%" + found) 

860 

861 # fix references 

862 st = update_notebook_link(st, "latex", nblinks=nblinks, fLOG=fLOG) 

863 if "find://" in st: 

864 raise HelpGenException( # pragma: no cover 

865 "find:// was found in '{0}'\nYou should add or extend " 

866 "'nblinks' in conf.py.\n{1}".format(file, st)) 

867 

868 # notebook replacements 

869 st = _notebook_replacements(st, notebook_replacements, fLOG) 

870 

871 # end 

872 if custom_latex_processing is not None: 

873 st = custom_latex_processing(st) # pragma: no cover 

874 

875 if remove_unicode: 

876 encoding = 'ascii' 

877 else: 

878 encoding = 'utf-8' 

879 st0 = st 

880 bst = st.encode(encoding, errors='replace') 

881 st = bst.decode(encoding, errors='replace') 

882 if st0 != st and fLOG: 

883 fLOG("[post_process_latex] characters were removed for encoding", encoding) 

884 return st 

885 

886 

887def post_process_python(st, doall, info=None, nblinks=None, file=None, fLOG=None, notebook_replacements=None): 

888 """ 

889 Modifies a python file after its generation by :epkg:`sphinx`. 

890 

891 @param st string 

892 @param doall do all transformations 

893 @param info for more understandable error messages 

894 @param nblinks dictionary ``{ref: url}`` 

895 @param file used only when an exception is raised 

896 @param fLOG logging function 

897 @param notebook_replacements string replacement in notebooks 

898 @return string 

899 """ 

900 st = st.strip("\n \r\t") 

901 st = st.replace("# coding: utf-8", "# -*- coding: utf-8 -*-") 

902 st = update_notebook_link(st, "python", nblinks=nblinks, fLOG=fLOG) 

903 if "find://" in st: 

904 raise HelpGenException( # pragma: no cover 

905 "find:// was found in '{0}'.\nYou should add or extend " 

906 "'nblinks' in conf.py.".format(file)) 

907 

908 # notebook replacements 

909 st = _notebook_replacements(st, notebook_replacements, fLOG) 

910 

911 return st 

912 

913 

914def remove_character_under32(s): 

915 """ 

916 Removes :epkg:`ASCII` characters in *[0..31]*. 

917 

918 @param s string to process 

919 @return filtered string 

920 """ 

921 ls = "" 

922 for c in s: 

923 d = ord(c) 

924 if 0 <= d < 32: 

925 ls += " " 

926 else: 

927 ls += c 

928 return ls