Coverage for pyquickhelper/helpgen/sphinx_main.py: 80%

568 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-03 02:21 +0200

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 sys 

9import shutil 

10import warnings 

11from datetime import datetime 

12from io import StringIO 

13from docutils.parsers.rst import directives, roles 

14from sphinx.cmd.build import main as build_main 

15from ..filehelper import remove_folder 

16from ..loghelper import python_path_append 

17from ..loghelper.process_script import execute_script_get_local_variables, dictionary_as_class 

18from ..loghelper.flog import run_cmd, fLOG 

19from .utils_sphinx_doc import prepare_file_for_sphinx_help_generation 

20from .utils_sphinx_doc_helpers import HelpGenException, ImportErrorHelpGen 

21from .conf_path_tools import find_latex_path, find_pandoc_path 

22from ..filehelper.synchelper import explore_folder 

23from ..filehelper import synchronize_folder 

24from .post_process import post_process_latex_output 

25from .process_notebooks import process_notebooks, build_notebooks_gallery, build_all_notebooks_coverage 

26from .sphinx_helper import post_process_html_nb_output_static_file 

27from .install_js_dep import install_javascript_tools 

28from .sphinx_main_helper import setup_environment_for_help, get_executables_path, generate_changes_repo 

29from .sphinx_main_helper import compile_latex_output_final, replace_placeholder_by_recent_blogpost 

30from .sphinx_main_helper import format_history, enumerate_copy_images_for_slides 

31from .sphinx_main_verification import verification_html_format 

32from .sphinx_main_missing_html_files import add_missing_files 

33from .style_css_template import style_figure_notebook 

34from .post_process_custom import find_custom_latex_processing 

35from ..sphinxext.blog_post_list import BlogPostList 

36from ..sphinxext.sphinx_blog_extension import BlogPostDirective, BlogPostDirectiveAgg 

37from ..sphinxext.sphinx_runpython_extension import RunPythonDirective 

38from ..sphinxext.sphinx_postcontents_extension import PostContentsDirective 

39from ..sphinxext.sphinx_tocdelay_extension import TocDelayDirective 

40from ..sphinxext.sphinx_youtube_extension import YoutubeDirective 

41from ..sphinxext.sphinx_sharenet_extension import ShareNetDirective, sharenet_role 

42from ..sphinxext.sphinx_downloadlink_extension import process_downloadlink_role 

43from ..sphinxext.sphinx_video_extension import VideoDirective 

44from ..sphinxext.sphinx_image_extension import SimpleImageDirective 

45from ..sphinxext.sphinximages.sphinxtrib.images import ImageDirective 

46from ..sphinxext.sphinx_template_extension import tpl_role 

47from ..sphinxext.sphinx_epkg_extension import epkg_role 

48from ..sphinxext.sphinx_bigger_extension import bigger_role 

49from ..sphinxext.sphinx_githublink_extension import githublink_role 

50from ..sphinxext.sphinx_gitlog_extension import gitlog_role 

51from ..sphinxext.sphinx_mathdef_extension import MathDef 

52from ..sphinxext.sphinx_quote_extension import QuoteNode 

53from ..sphinxext.sphinx_blocref_extension import BlocRef 

54from ..sphinxext.sphinx_exref_extension import ExRef 

55from ..sphinxext.sphinx_faqref_extension import FaqRef 

56from ..sphinxext.sphinx_nbref_extension import NbRef 

57from ..sphinxext.sphinx_cmdref_extension import CmdRef 

58from ..sphinxext.sphinx_todoext_extension import TodoExt 

59from ..sphinxext.sphinx_collapse_extension import CollapseDirective 

60from ..sphinxext.sphinx_gdot_extension import GDotDirective 

61 

62template_examples = """ 

63 

64List of programs 

65++++++++++++++++ 

66 

67.. toctree:: 

68 :maxdepth: 2 

69 

70.. autosummary:: __init__.py 

71 :toctree: %s/ 

72 :template: modules.rst 

73 

74Another list 

75++++++++++++ 

76 

77""" 

78 

79 

80def generate_help_sphinx(project_var_name, clean=False, root=".", 

81 filter_commit=lambda c: c.strip() != "documentation", 

82 extra_ext=None, 

83 nbformats=("ipynb", "slides", "html", "python", 

84 "rst", "pdf", "github"), 

85 layout=None, 

86 module_name=None, from_repo=True, add_htmlhelp=False, 

87 copy_add_ext=None, direct_call=False, fLOG=fLOG, 

88 parallel=1, extra_paths=None, fexclude=None): 

89 """ 

90 Runs the help generation: 

91 

92 - copies every file in another folder, 

93 - replaces comments in doxygen format into rst format, 

94 - replaces local import by global import (tweaking sys.path too), 

95 - calls sphinx to generate the documentation. 

96 

97 @param project_var_name project name 

98 @param clean if True, cleans the previous documentation first 

99 (:epkg:`html` files) 

100 @param root see below 

101 @param filter_commit function which accepts a commit to show on the documentation 

102 (based on the comment) 

103 @param extra_ext list of file extensions to document (not .py) 

104 @param nbformats requested formats for the notebooks conversion 

105 @param layout list of formats sphinx should generate such as html, latex, pdf, docx, 

106 it is a list of tuple (layout, build directory, parameters to override), 

107 if None --> ``[("html", "build", {})]`` 

108 @param module_name name of the module (must be the folder name ``src/module_name`` 

109 if None, ``module_name`` 

110 will be replaced by *project_var_name* 

111 @param from_repo if True, assumes the sources come from a source repository, 

112 False otherwise 

113 @param add_htmlhelp run :epkg:`HTML` Help too (only on :epkg:`Windows`) 

114 @param copy_add_ext additional file extension to copy 

115 @param direct_call direct call to sphinx with *sphinx_build* if *True* 

116 or run a command line in an another process to get 

117 a clear environment 

118 @param parallel degree of parallelization 

119 @param extra_paths extra paths when importing configuration 

120 @param fexclude function which tells which file not to copy in the folder 

121 used to build the documentation 

122 @param fLOG logging function 

123 

124 The result is stored in path: ``root/_doc/sphinxdoc/source``. 

125 We assume the file ``root/_doc/sphinxdoc/source/conf.py`` exists 

126 as well as ``root/_doc/sphinxdoc/source/index.rst``. 

127 

128 If you generate latex/pdf files, you should add variables ``latex_path`` and ``pandoc_path`` 

129 in your file ``conf.py`` which defines the help. 

130 

131 You can exclud some part while generating the documentation by adding: 

132 

133 * ``# -- HELP BEGIN EXCLUDE --`` 

134 * ``# -- HELP END EXCLUDE --`` 

135 

136 :: 

137 

138 latex_path = r"C:/Program Files/MiKTeX 2.9/miktex/bin/x64" 

139 pandoc_path = r"%USERPROFILE%/AppData/Local/Pandoc" 

140 

141 .. exref:: 

142 :title: Run help generation 

143 :index: extension, extra extension, ext 

144 

145 :: 

146 

147 # from the main folder which contains folder src or the sources 

148 generate_help_sphinx("pyquickhelper") 

149 

150 By default, the function only consider files end by ``.py`` and ``.rst`` but you could 

151 add other files sharing the same extensions by adding this one 

152 in the ``extra_ext`` list. 

153 

154 The function requires: 

155 

156 - :epkg:`pandoc` 

157 - latex 

158 

159 @warning Some themes such as `Bootstrap Sphinx Theme <http://ryan-roemer.github.io/sphinx-bootstrap-theme/>`_ 

160 do not work on Internet Explorer. In that case, the 

161 file ``<python_path>/Lib/site-packages/sphinx/themes/basic/layout.html`` 

162 must be modified to add the following line (just below ``Content-Type``). 

163 

164 :: 

165 

166 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 

167 

168 .. index:: PEP8, autopep8 

169 

170 The code should follow as much as possible the sytle convention 

171 `PEP8 <https://www.python.org/dev/peps/pep-0008/>`_. 

172 The module `autopep8 <https://pypi.python.org/pypi/autopep8>`_ can modify a file or all files contained in one folder 

173 by running the following command line: 

174 

175 :: 

176 

177 autopep8 <folder> --recursive --in-place --pep8-passes 2000 --verbose 

178 

179 **About encoding:** utf-8 without BOM is the recommanded option. 

180 

181 **About languages:** only one language can be specificied even if you have 

182 multiple configuration file. Only the language specified in the main 

183 ``conf.py`` will be considered. 

184 

185 **About blog posts:** the function uses sphinx directives ``blogpost`` and ``blogpostagg`` to create 

186 a simple blog aggregator. Blog posts will be aggregated by months and categories. 

187 Link to others parts to the documentation are possible. 

188 The function also create a file ``rss.xml`` which contains the ten last added blog post. 

189 This file contains an absolute link to the blog posts. However, because the documentation 

190 can be published anywhere, the string ``__BLOG_ROOT__`` was inserted 

191 instead of the absolute url to the website. It must be replaced before uploaded 

192 or the parameter *blog_root* can be specified in the configuration file ``conf.py``. 

193 

194 @warning Parameter *add_htmlhelp* calls `Html Help WorkShop 

195 <https://msdn.microsoft.com/en-us/library/windows/desktop/ms669985%28v=vs.85%29.aspx>`_. 

196 It also changes the encoding of the HTMLoutput into cp1552 (encoding for Windows) 

197 instead of utf-8. 

198 

199 @warning An issue was raised on Linux due to the use of ``.. only:: html`` 

200 (``AttributeError: Can't pickle local object 'setup.<locals>.<lambda>'``). 

201 It disappeared when using only one thread and not 2 as 

202 it was previously. Parameter *parallel* was introduced to 

203 make that change and the default value is not 1. 

204 

205 .. index:: SVG, Inkscape 

206 

207 **Others necessary tools:** 

208 

209 SVG included in a notebook (or any RST file) requires `Inkscape <https://inkscape.org/>`_ 

210 to be converted into Latex. 

211 

212 .. faqref:: 

213 :title: How to dd an extra layer to the documentation? 

214 

215 The following `commit <https://github.com/sdpython/python3_module_template/commit/ 

216 75d765a293f65a37b3208601d17d3b0daa891af6>`_ 

217 on project `python3_module_template <https://github.com/sdpython/python3_module_template/>`_ 

218 shows which changes needs to be done to add an extra layer of for the documentation. 

219 

220 The function assumes :epkg:`IPython` 3 is installed. 

221 It might no work for earlier versions (notebooks). 

222 Parameters *from_repo*, *use_run_cmd* were added. 

223 Notebook conversion to slides is implemented, 

224 install :epkg:`reveal.js` if not installed. 

225 Parameter *add_htmlhelp* was added. It runs HtmlHelp on Windows :: 

226 

227 "C:\\Program Files (x86)\\HTML Help Workshop\\hhc.exe" build\\htmlhelp\\<module>.hhp 

228 

229 The documentation includes blog (with sphinx command ``.. blogpost::`` 

230 and python scripts ``.. runpython::``. The second command runs a python 

231 script which outputs RST documntation adds it to the current documentation. 

232 The function automatically adds custom role and custom directive ``sharenet``. 

233 The function directly calls 

234 `sphinx <https://www.sphinx-doc.org/en/master/>`_, 

235 `nbconvert <https://nbconvert.readthedocs.io/en/latest/>`_. 

236 When there are too many notebooks, the notebook index is difficult to read. 

237 It does not require to get script location. 

238 Not enough stable from virtual environment. 

239 

240 Set ``BOKEH_DOCS_MISSING_API_KEY_OK`` to 1. 

241 bokeh sphinx extension requires that or a key for the google API (???). 

242 The function was updated to use Sphinx 1.6.2. 

243 However, you should read blog post 

244 :ref:`Bug in Sphinx 1.6.2 for custom css <sphinx-162-bug-custom-css>` 

245 if you have any trouble with custom css. 

246 Add a report in ``all_notebooks.rst`` about notebook coverage. 

247 Parameter *parallel* was added. 

248 The parameter *nblayout* in the configuration file specifies 

249 the layout for the notebook gallery. ``'classic'`` or ``'table'``. 

250 The parameter *nbneg_pattern* can be used to remove notebooks from 

251 the gallery if they match this regular expression. 

252 It automatically adds video and image directives. 

253 *remove_unicode* can set to False or True in the documentation 

254 configuration file to allow or remove unicode characters 

255 before compiling the latex output. 

256 Import ``conf.py`` in a separate process before running 

257 the generation of the documentation. Do not import it 

258 directly. 

259 """ 

260 datetime_rows = [("begin", datetime.now())] 

261 

262 fLOG("---- JENKINS BEGIN DOCUMENTATION ----") 

263 if layout is None: 

264 layout = [("html", "build", {})] 

265 fLOG("[generate_help_sphinx] ---- layout", layout) 

266 setup_environment_for_help(fLOG=fLOG) 

267 # we keep a clean list of modules 

268 # sphinx configuration is a module and the function loads and unloads it 

269 list_modules_start = set(sys.modules.keys()) 

270 

271 if add_htmlhelp: # pragma: no cover 

272 if not sys.platform.startswith("win"): 

273 raise ValueError("add_htmlhelp is True and the OS is not Windows") 

274 fLOG("[generate_help_sphinx] add add_htmlhelp") 

275 

276 if extra_ext is None: 

277 extra_ext = [] 

278 

279 def lay_build_override_newconf(t3): 

280 if isinstance(t3, str): 

281 lay, build, override, newconf = t3, "build", {}, None 

282 elif len(t3) == 1: 

283 lay, build, override, newconf = t3[0], "build", {}, None 

284 elif len(t3) == 2: 

285 lay, build, override, newconf = t3[0], t3[1], {}, None 

286 elif len(t3) == 3: 

287 lay, build, override, newconf = t3[0], t3[1], t3[2], None 

288 else: 

289 lay, build, override, newconf = t3 

290 return lay, build, override, newconf 

291 

292 directives.register_directive("blogpost", BlogPostDirective) 

293 directives.register_directive("blogpostagg", BlogPostDirectiveAgg) 

294 directives.register_directive("runpython", RunPythonDirective) 

295 directives.register_directive("sharenet", ShareNetDirective) 

296 directives.register_directive("video", VideoDirective) 

297 directives.register_directive("simpleimage", SimpleImageDirective) 

298 directives.register_directive("image", ImageDirective) 

299 directives.register_directive("todoext", TodoExt) 

300 directives.register_directive("mathdef", MathDef) 

301 directives.register_directive("quote", QuoteNode) 

302 directives.register_directive("blocref", BlocRef) 

303 directives.register_directive("exref", ExRef) 

304 directives.register_directive("faqref", FaqRef) 

305 directives.register_directive("nbref", NbRef) 

306 directives.register_directive("cmdref", CmdRef) 

307 directives.register_directive("postcontents", PostContentsDirective) 

308 directives.register_directive("tocdelay", TocDelayDirective) 

309 directives.register_directive("youtube", YoutubeDirective) 

310 directives.register_directive("thumbnail", ImageDirective) 

311 directives.register_directive("collapse", CollapseDirective) 

312 directives.register_directive("gdot", GDotDirective) 

313 roles.register_canonical_role("sharenet", sharenet_role) 

314 roles.register_canonical_role("bigger", bigger_role) 

315 roles.register_canonical_role("githublink", githublink_role) 

316 roles.register_canonical_role("gitlog", gitlog_role) 

317 roles.register_canonical_role("tpl", tpl_role) 

318 roles.register_canonical_role("epkg", epkg_role) 

319 roles.register_canonical_role("downloadlink", process_downloadlink_role) 

320 

321 if "conf" in sys.modules: 

322 raise ImportError( # pragma: no cover 

323 "module conf was imported, this function expects not to:\n{0}".format( 

324 sys.modules["conf"].__file__)) 

325 

326 ############ 

327 # root_source 

328 ############ 

329 root = os.path.abspath(root) 

330 froot = root 

331 root_sphinxdoc = os.path.join(root, "_doc", "sphinxdoc") 

332 root_source = os.path.join(root_sphinxdoc, "source") 

333 root_package = os.path.join(root, "src") 

334 if not os.path.exists(root_package): 

335 root_package = root 

336 if not os.path.exists(root_package): 

337 raise FileNotFoundError( # pragma: no cover 

338 f"Unable to find source root from '{root}'.") 

339 fLOG(f"[generate_help_sphinx] root='{root}'") 

340 fLOG(f"[generate_help_sphinx] root_package='{root_package}'") 

341 fLOG(f"[generate_help_sphinx] root_source='{root_source}'") 

342 fLOG(f"[generate_help_sphinx] root_sphinxdoc='{root_sphinxdoc}'") 

343 conf_paths = [root_source, root_package] 

344 if extra_paths: 

345 conf_paths.extend(extra_paths) 

346 

347 ######################################## 

348 # we import conf_base, specific to multi layers 

349 ######################################## 

350 confb = os.path.join(root_source, "conf_base.py") 

351 if os.path.exists(confb): # pragma: no cover 

352 code = "from conf_base import *" 

353 with python_path_append(conf_paths): 

354 try: 

355 module_conf = execute_script_get_local_variables( 

356 code, folder=root_source, check=True) 

357 except RuntimeError as e: 

358 raise ImportError("Unable to import conf_base '{}' from '{}'\nsys.path=\n{}".format( 

359 confb, root_source, "\n".join(sys.path))) from e 

360 

361 if module_conf is None: 

362 raise ImportError( 

363 f"Unable to import '{confb}' which defines the help generation") 

364 if 'ERROR' in module_conf: 

365 msg = "\n".join(["paths:"] + conf_paths + [ 

366 "-----------------------", 

367 module_conf['ERROR']]) 

368 raise ImportError(msg) 

369 conf_base = dictionary_as_class(module_conf) 

370 fLOG("[generate_help_sphinx] conf_base.__file__='{0}'".format( 

371 os.path.abspath(conf_base.__file__))) # pylint: disable=E1101 

372 

373 copypath = list(sys.path) 

374 

375 # stores static path for every layout, we store them to copy 

376 html_static_paths = [] 

377 build_paths = [] 

378 all_tocs = [] 

379 parameters = [] 

380 

381 ################################### 

382 # import others conf, we must do it now 

383 # it takes too long to do it after if there is an error 

384 # we assume the configuration are not too different 

385 # about language for example, latex_path, pandoc_path 

386 ################################################# 

387 for t3 in layout: 

388 lay, build, override, newconf = lay_build_override_newconf(t3) 

389 if newconf is None: 

390 continue 

391 fLOG(f"[generate_help_sphinx] newconf: '{newconf}' - {t3}") 

392 # we need to import this file to guess the template directory and 

393 # add missing templates 

394 folds = os.path.join(root_sphinxdoc, newconf) 

395 _import_conf_extract_parameter(root, root_source, folds, build, newconf, 

396 all_tocs, build_paths, parameters, 

397 html_static_paths, fLOG) 

398 

399 ################################################################ 

400 # we add the source path to the list of path to considered before importing 

401 # import conf.py 

402 ################################################################ 

403 with python_path_append(conf_paths): 

404 try: 

405 module_conf = execute_script_get_local_variables( 

406 "from conf import *", folder=root_source, check=True) 

407 except ImportError as e: # pragma: no cover 

408 raise ImportError("Unable to import 'conf.py' from '{0}', sys.path=\n{1}\nBEFORE:\n{2}".format( 

409 root_source, "\n".join(sys.path), "\n".join(copypath))) from e 

410 if module_conf is None: 

411 raise ImportError( # pragma: no cover 

412 "unable to import 'conf.py' which defines the help generation") 

413 if 'ERROR' in module_conf: # pragma: no cover 

414 msg = "\n".join(["paths:"] + conf_paths + [ 

415 "----------------------- ERROR:", 

416 module_conf['ERROR'], 

417 "------------------------ root_source:", 

418 root_source]) 

419 raise ImportError(msg) 

420 if len(module_conf) == 0: 

421 raise ImportError( 

422 "No extracted local variable.") # pragma: no cover 

423 theconf = dictionary_as_class(module_conf) 

424 fLOG("[generate_help_sphinx] conf.__file__='{0}'".format( 

425 os.path.abspath(theconf.__file__))) # pylint: disable=E1101 

426 tocs = add_missing_files(root, theconf, "__INSERT__", fLOG) 

427 all_tocs.extend(tocs) 

428 

429 ############################## 

430 # some checkings on the configuration 

431 ############################## 

432 galleries = _check_sphinx_configuration(theconf, fLOG) 

433 galleries = [os.path.relpath(g, start=root_source) for g in galleries] 

434 

435 ############################################################## 

436 # Extracts variables from the configuration. 

437 # We store the html_static_path in html_static_paths for the base conf 

438 # We extract other information from the configuration 

439 ############################################################## 

440 html_static_path = theconf.__dict__.get("html_static_path", "_static") 

441 if isinstance(html_static_path, list): 

442 html_static_path = html_static_path[0] 

443 html_static_path = os.path.join(root_source, html_static_path) 

444 if not os.path.exists(html_static_path): 

445 raise FileNotFoundError( # pragma: no cover 

446 f"no static path: {html_static_path!r}.") 

447 html_static_paths.append(html_static_path) 

448 build_paths.append( 

449 os.path.normpath(os.path.join(html_static_path, "..", "..", "build", "html"))) 

450 custom_latex_processing = theconf.__dict__.get( 

451 "custom_latex_processing", None) 

452 if custom_latex_processing is not None: # pragma: no cover 

453 # The configuration file is pickled by sphinx 

454 # and parameter should not be functions. 

455 if isinstance(custom_latex_processing, str): 

456 custom_latex_processing = find_custom_latex_processing( # pylint: disable=E1111 

457 custom_latex_processing) 

458 res = custom_latex_processing("dummy phrase") 

459 if res is None: 

460 raise ValueError( 

461 "Result of function custom_latex_processing should not be None.") 

462 remove_unicode = theconf.__dict__.get("remove_unicode", False) 

463 snippet_folder = theconf.__dict__.get( 

464 "notebook_custom_snippet_folder", None) 

465 if snippet_folder: 

466 snippet_folder = os.path.join( 

467 os.path.dirname(theconf.__file__), snippet_folder) # pylint: disable=E1101 

468 

469 notebook_replacements = theconf.__dict__.get("notebook_replacements", None) 

470 if notebook_replacements is not None and not isinstance(notebook_replacements, dict): 

471 raise TypeError( # pragma: no cover 

472 "latex_notebook_replacements should be a dictionary not {0}".format( 

473 type(notebook_replacements))) 

474 

475 #################################### 

476 # modifies the version number in conf.py 

477 #################################### 

478 readme = os.path.join(root, "README.rst") 

479 if not os.path.exists(readme): 

480 raise FileNotFoundError(readme) # pragma: no cover 

481 shutil.copy(readme, root_source) 

482 license = os.path.join(root, "LICENSE.txt") 

483 if not os.path.exists(license): 

484 raise FileNotFoundError(license) # pragma: no cover 

485 shutil.copy(license, root_source) 

486 history = os.path.join(root, "HISTORY.rst") 

487 if os.path.exists(history): 

488 dest = os.path.join(root_source, "HISTORY.rst") 

489 format_history(history, dest) 

490 

491 ########## 

492 # language 

493 ########## 

494 language = theconf.__dict__.get("language", "en") 

495 use_sys = theconf.__dict__.get("enable_disabled_parts", None) 

496 latex_book = theconf.__dict__.get('latex_book', False) 

497 nbexamples_conf = theconf.__dict__.get('example_gallery_config', None) 

498 # examples_conf = theconf.__dict__.get('sphinx_gallery_conf', None) 

499 

500 ########## 

501 # auto_rst_generation 

502 ########## 

503 auto_rst_generation = theconf.__dict__.get("auto_rst_generation", True) 

504 

505 ospath = os.environ["PATH"] 

506 latex_path = theconf.__dict__.get("latex_path", find_latex_path()) 

507 # graphviz_dot = theconf.__dict__.get("graphviz_dot", find_graphviz_dot()) 

508 pandoc_path = theconf.__dict__.get("pandoc_path", find_pandoc_path()) 

509 if os.path.isfile(latex_path): 

510 latex_path = os.path.dirname(latex_path) 

511 

512 ########## 

513 # nblinks: references for the notebooks, dictionary {(ref, format): link} 

514 ########## 

515 nblayout = theconf.__dict__.get("nblayout", "classic") 

516 nblinks = theconf.__dict__.get("nblinks", None) 

517 nbneg_pattern = theconf.__dict__.get("nbneg_pattern", None) 

518 if nblinks is not None and len(nblinks) > 0: 

519 fLOG("[generate_help_sphinx] NBLINKS - BEGIN") 

520 for i, (k, v) in enumerate(sorted(nblinks.items())): 

521 fLOG(f" {i + 1}/{len(nblinks)} - '{k}': '{v}'") 

522 fLOG("[generate_help_sphinx] NBLINKS - END") 

523 fLOG(f"[generate_help_sphinx] nbneg_pattern='{nbneg_pattern}'") 

524 

525 # add to PATH 

526 sep = ";" if sys.platform.startswith("win") else ":" 

527 if latex_path not in ospath: 

528 os.environ["PATH"] += sep + latex_path 

529 if pandoc_path not in ospath: 

530 os.environ["PATH"] += sep + pandoc_path 

531 

532 ######### 

533 # changes 

534 ######### 

535 datetime_rows = [("changes", datetime.now())] 

536 chan = os.path.join(root, "_doc", "sphinxdoc", "source", "filechanges.rst") 

537 if "modify_commit" in theconf.__dict__: 

538 modify_commit = theconf.modify_commit # pylint: disable=E1101 

539 else: 

540 modify_commit = None 

541 generate_changes_repo( 

542 chan, root, filter_commit=filter_commit, exception_if_empty=from_repo, 

543 fLOG=fLOG, modify_commit=modify_commit) 

544 

545 ###################################### 

546 # we copy javascript dependencies, reveal.js 

547 ###################################### 

548 datetime_rows = [("javascript", datetime.now())] 

549 fLOG("[generate_help_sphinx] JAVASCRIPT:", html_static_paths) 

550 fLOG("[generate_help_sphinx] ROOT:", root_sphinxdoc) 

551 fLOG("[generate_help_sphinx] BUILD:", build_paths) 

552 for html_static_path in html_static_paths: 

553 found = install_javascript_tools( 

554 root_sphinxdoc, dest=html_static_path, fLOG=fLOG) 

555 fLOG(f"[generate_help_sphinx] [javascript]: '{found}'") 

556 

557 ############################ 

558 # we copy the extended styles (notebook, snippets) 

559 ############################ 

560 datetime_rows = [("copy", datetime.now())] 

561 for html_static_path in html_static_paths: 

562 dest = os.path.join(html_static_path, style_figure_notebook[0]) 

563 fLOG(" CREATE-CSS", dest) 

564 with open(dest, "w", encoding="utf-8") as f: 

565 f.write(style_figure_notebook[1]) 

566 

567 # We should not need that. 

568 # for build in build_paths: 

569 # dest = os.path.join(build, "_downloads") 

570 # if not os.path.exists(dest): 

571 # os.makedirs(dest) 

572 # install_javascript_tools( 

573 # root_sphinxdoc, dest=dest, fLOG=fLOG) 

574 

575 ############## 

576 # copy the files 

577 ############## 

578 fLOG("---- JENKINS BEGIN DOCUMENTATION COPY FILES ----") 

579 optional_dirs = [] 

580 mapped_function = [(f".*[.]{ext.strip('.')}$", None) 

581 for ext in extra_ext] 

582 

583 ################################### 

584 # we save the module already imported 

585 ################################### 

586 if module_name is None: 

587 module_name = project_var_name 

588 

589 sys_modules = set(sys.modules.keys()) 

590 

591 #################### 

592 # generates extra files 

593 #################### 

594 datetime_rows = [("prepare", datetime.now())] 

595 try: 

596 dest_doc = os.path.join(root, "_doc", "sphinxdoc", "source") 

597 fLOG(f"[generate_help_sphinx] module_name='{module_name}'") 

598 fLOG(f"[generate_help_sphinx] project_var_name='{project_var_name}'") 

599 fLOG( 

600 f"[generate_help_sphinx] root='{root}' root_package='{root_package}'") 

601 fLOG(f"[generate_help_sphinx] dest_doc='{dest_doc}'") 

602 subfolders = [] 

603 if root_package.endswith("src"): 

604 subfolders.append(("src/" + module_name, module_name)) 

605 else: 

606 subfolders.append((module_name, module_name)) 

607 fLOG(f"[generate_help_sphinx] subfolders={subfolders}") 

608 prepare_file_for_sphinx_help_generation( 

609 {}, root, dest_doc, subfolders=subfolders, silent=True, 

610 rootrep=(f"_doc.sphinxdoc.source.{module_name}.", ""), 

611 optional_dirs=optional_dirs, mapped_function=mapped_function, 

612 replace_relative_import=False, module_name=module_name, 

613 copy_add_ext=copy_add_ext, use_sys=use_sys, fexclude=fexclude, 

614 auto_rst_generation=auto_rst_generation, fLOG=fLOG) 

615 

616 except ImportErrorHelpGen as e: # pragma: no cover 

617 fLOG( 

618 "[generate_help_sphinx] major failure, no solution found yet, please run again the script") 

619 fLOG("[generate_help_sphinx] list of added modules:") 

620 remove = [k for k in sys.modules if k not in sys_modules] 

621 for k in sorted(remove): 

622 fLOG("[generate_help_sphinx] ", k) 

623 

624 raise e 

625 

626 fLOG("[generate_help_sphinx] end of prepare_file_for_sphinx_help_generation") 

627 fLOG("---- JENKINS END DOCUMENTATION COPY FILES ----") 

628 

629 ###### 

630 # blog 

631 ###### 

632 datetime_rows = [("blog", datetime.now())] 

633 fLOG("---- JENKINS BEGIN DOCUMENTATION BLOGS ----") 

634 fLOG("[generate_help_sphinx] begin blogs") 

635 blog_fold = os.path.join( 

636 os.path.join(root, "_doc/sphinxdoc/source", "blog")) 

637 

638 if os.path.exists(blog_fold): 

639 fLOG("[generate_help_sphinx] BlogPostList") 

640 plist = BlogPostList(blog_fold, language=language, fLOG=fLOG, 

641 conf=theconf) 

642 fLOG("[generate_help_sphinx] BlogPostList.write_aggregated") 

643 plist.write_aggregated( 

644 blog_fold, 

645 blog_title=theconf.__dict__.get("blog_title", project_var_name), 

646 blog_description=theconf.__dict__.get( 

647 "blog_description", "blog associated to " + project_var_name), 

648 blog_root=theconf.__dict__.get("blog_root", "__BLOG_ROOT__")) 

649 else: 

650 plist = None 

651 

652 fLOG("[generate_help_sphinx] end blogs") 

653 fLOG("---- JENKINS END DOCUMENTATION BLOGS ----") 

654 

655 ########### 

656 # notebooks 

657 ########### 

658 datetime_rows = [("notebooks", datetime.now())] 

659 fLOG("---- JENKINS BEGIN DOCUMENTATION NOTEBOOKS ----") 

660 fLOG("[generate_help_sphinx] begin notebooks") 

661 indextxtnote = None 

662 indexlistnote = [] 

663 notebook_dir = os.path.abspath(os.path.join(root, "_doc", "notebooks")) 

664 notebook_doc = os.path.abspath( 

665 os.path.join(root, "_doc", "sphinxdoc", "source", "notebooks")) 

666 if os.path.exists(notebook_dir): 

667 fLOG(f" look into '{notebook_dir}'") 

668 fLOG(f" -pattern '{nbneg_pattern}'") 

669 notebooks = explore_folder(notebook_dir, pattern=".*[.]ipynb", neg_pattern=nbneg_pattern, 

670 fullname=True, fLOG=fLOG)[1] 

671 notebooks = [_ for _ in notebooks if ( 

672 "checkpoint" not in _ and "/build/" not in _.replace("\\", "/"))] 

673 fLOG(f" found {len(notebooks)} notebooks") 

674 if len(notebooks) > 0: 

675 fLOG("[generate_help_sphinx] **** notebooks", nbformats) 

676 build = os.path.join(root, "build", "notebooks") 

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

678 os.makedirs(build) 

679 indextxtnote = os.path.join(build, "index_notebooks.txt") 

680 with open(indextxtnote, "w", encoding="utf-8") as f: 

681 for note in notebooks: 

682 no = os.path.relpath(note, notebook_dir) 

683 indexlistnote.append((no, note)) 

684 f.write(no + "\n") 

685 if not os.path.exists(notebook_doc): 

686 os.mkdir(notebook_doc) 

687 nbs_all = process_notebooks(notebooks, build=build, outfold=notebook_doc, 

688 formats=nbformats, latex_path=latex_path, 

689 pandoc_path=pandoc_path, fLOG=fLOG, nblinks=nblinks, 

690 notebook_replacements=notebook_replacements) 

691 nbs_all = set(_[0] for _ in nbs_all 

692 if os.path.splitext(_[0])[-1] == ".rst") 

693 if len(nbs_all) != len(indexlistnote): # pragma: no cover 

694 ext1 = "nbs_all:\n{0}".format("\n".join(nbs_all)) 

695 ext2 = "indexlistnote:\n{0}".format( 

696 "\n".join(str(_) for _ in indexlistnote)) 

697 raise ValueError("Different lengths {0} != {1}\n{2}\n{3}".format( 

698 len(nbs_all), len(indexlistnote), ext1, ext2)) 

699 nbs = indexlistnote 

700 fLOG("[generate_help_sphinx] *#* NB, add:", len(nbs)) 

701 nbs.sort() 

702 build_notebooks_gallery(nbs, os.path.join( 

703 notebook_doc, "..", "all_notebooks.rst"), layout=nblayout, 

704 snippet_folder=snippet_folder, fLOG=fLOG) 

705 build_all_notebooks_coverage(nbs, os.path.join( 

706 notebook_doc, "..", "all_notebooks_coverage.rst"), module_name, fLOG=fLOG) 

707 

708 imgs = [os.path.join(notebook_dir, _) 

709 for _ in os.listdir(notebook_dir) if ".png" in _] 

710 if len(imgs) > 0: 

711 gallery_dirs = nbexamples_conf.get( 

712 'gallery_dirs', None) if nbexamples_conf else None 

713 for img in imgs: 

714 shutil.copy(img, notebook_doc) 

715 if gallery_dirs: 

716 for d in gallery_dirs: 

717 if not os.path.exists(d): 

718 os.makedirs(d) 

719 shutil.copy(img, d) 

720 else: 

721 fLOG(f"---- no folder '{notebook_dir}'") 

722 

723 fLOG("[generate_help_sphinx] end notebooks") 

724 fLOG("---- JENKINS END DOCUMENTATION NOTEBOOKS ----") 

725 

726 ############################################# 

727 # replace placeholder as blog posts list into tocs files 

728 ############################################# 

729 datetime_rows = [("replace", datetime.now())] 

730 fLOG("[generate_help_sphinx] blog placeholder") 

731 if plist is not None: 

732 replace_placeholder_by_recent_blogpost( 

733 all_tocs, plist, "__INSERT__", fLOG=fLOG) 

734 

735 ################################# 

736 # run the documentation generation 

737 ################################# 

738 datetime_rows = [("sphinx", datetime.now())] 

739 fLOG("[generate_help_sphinx] prepare for SPHINX") 

740 temp = os.environ["PATH"] 

741 pyts = get_executables_path() 

742 sepj = ";" if sys.platform.startswith("win") else ":" 

743 script = sepj.join(pyts) 

744 fLOG("[generate_help_sphinx] adding " + script) 

745 temp = script + sepj + temp 

746 os.environ["PATH"] = temp 

747 fLOG("[generate_help_sphinx] changing PATH", temp) 

748 pa = os.getcwd() 

749 

750 # bokeh trick 

751 updates_env = dict(BOKEH_DOCS_MISSING_API_KEY_OK=1) 

752 for k, v in updates_env.items(): 

753 if k not in os.environ: 

754 os.environ[k] = str(v) 

755 

756 thispath = os.path.normpath(root) 

757 docpath = os.path.normpath(os.path.join(thispath, "_doc", "sphinxdoc")) 

758 

759 ################ 

760 # checks encoding 

761 ################ 

762 datetime_rows = [("encoding", datetime.now())] 

763 fLOG("---- JENKINS BEGIN DOCUMENTATION ENCODING ----") 

764 fLOG("[generate_help_sphinx] checking encoding utf8...") 

765 for rt, _, files in os.walk(docpath): 

766 for name in files: 

767 thn = os.path.join(rt, name) 

768 if name.endswith(".rst"): 

769 try: 

770 with open(thn, "r", encoding="utf8") as f: 

771 f.read() 

772 except UnicodeDecodeError as e: # pragma: no cover 

773 raise HelpGenException( 

774 "issue with encoding in a file", thn) from e 

775 except Exception as e: # pragma: no cover 

776 raise HelpGenException("issue with file ", thn) from e 

777 

778 fLOG("[generate_help_sphinx] running sphinx... from", docpath) 

779 if not os.path.exists(docpath): 

780 raise FileNotFoundError(docpath) # pragma: no cover 

781 fLOG("---- JENKINS END DOCUMENTATION ENCODING ----") 

782 

783 os.chdir(docpath) 

784 

785 ##################### 

786 # builds command lines 

787 ##################### 

788 datetime_rows = [("build", datetime.now())] 

789 fLOG("[generate_help_sphinx] sphinx command lines") 

790 cmds = [] 

791 lays = [] 

792 cmds_post = [] 

793 for t3 in layout: 

794 lay, build, override, newconf = lay_build_override_newconf(t3) 

795 

796 if lay == "pdf": 

797 lay = "elatex" 

798 

799 if clean and sys.platform.startswith("win"): 

800 if os.path.exists(build): 

801 for fold in os.listdir(build): 

802 remove_folder(os.path.join(build, fold)) 

803 remove_folder(build) 

804 

805 over_ = [f"{k}={v}" for k, v in override.items()] 

806 over = [] 

807 for o in over_: 

808 over.append("-D") 

809 over.append(o) 

810 

811 sconf = [] if newconf is None else ["-c", newconf] 

812 

813 cmd = ["sphinx-build", "-j%d" % parallel, "-v", "-T", "-b", f"{lay}", 

814 "-d", f"{build}/doctrees"] + over + sconf + ["source", f"{build}/{lay}"] 

815 if lay in ('latex', 'pdf', 'elatex'): 

816 cmd.extend(["-D", "imgmath_image_format=png"]) 

817 cmds.append((cmd, build, lay)) 

818 fLOG("[generate_help_sphinx] run:", cmd) 

819 lays.append(lay) 

820 

821 if add_htmlhelp and lay == "html": # pragma: no cover 

822 # we cannot execute htmlhelp in the same folder 

823 # as it changes the encoding 

824 cmd = ["sphinx-build", "-j%d" % parallel, "-v", "-T", "-b", f"{lay}help", 

825 "-d", f"{build}/doctrees"] + over + sconf + ["source", f"{build}/{lay}html"] 

826 cmd.extend(["-D", "imgmath_image_format=png"]) 

827 cmds.append((cmd, build, "add_htmlhelp")) 

828 fLOG("[generate_help_sphinx] run:", cmd) 

829 lays.append(lay) 

830 hhp = os.path.join(build, lay + "help", module_name + "_doc.hhp") 

831 cmdp = '"C:\\Program Files (x86)\\HTML Help Workshop\\hhc.exe" ' + \ 

832 f'"{hhp}"' 

833 cmds_post.append(cmdp) 

834 

835 # cmd = "make {0}".format(lay) 

836 

837 ############################################################### 

838 # run cmds (prefer to use os.system instead of run_cmd if it gets stuck) 

839 ############################################################### 

840 datetime_rows = [("cmd", datetime.now())] 

841 fLOG("[generate_help_sphinx] RUN SPHINX") 

842 for cmd, build, kind in cmds: 

843 fLOG("---- JENKINS BEGIN DOCUMENTATION SPHINX ----") 

844 fLOG( 

845 "##################################################################################################") 

846 fLOG( 

847 "##################### run sphinx #################################################################") 

848 fLOG( 

849 "##################################################################################################") 

850 fLOG("[generate_help_sphinx]", cmd) 

851 fLOG("[generate_help_sphinx] from ", os.getcwd()) 

852 fLOG("[generate_help_sphinx] PATH ", os.environ["PATH"]) 

853 

854 existing = list(sorted(sys.modules.keys())) 

855 for ex in existing: 

856 if ex[0] == '_': 

857 doesrem = False 

858 elif ex not in list_modules_start: 

859 doesrem = True 

860 for pr in ('pywintypes', 'pandas', 'IPython', 'jupyter', 'numpy', 'scipy', 'matplotlib', 

861 'pyquickhelper', 'yaml', 'xlsxwriter', '_csv', 

862 '_lsprof', '_multiprocessing', '_overlapped', '_sqlite3', 

863 'alabaster', 'asyncio', 'babel', 'bokeh', 'cProfile', 

864 'certifi', 'colorsys', 'concurrent', 'csv', 'cycler', 

865 'dateutil', 'decorator', 'docutils', 'fractions', 'gc', 

866 'getopt', 'getpass', 'hmac', 'http', 'imp', 'ipython_genutils', 

867 'jinja2', 'mimetypes', 'multiprocessing', 'pathlib', 

868 'pickleshare', 'profile', 'prompt_toolkit', 'pstats', 

869 'pydoc', 'py', 'pygments', 'requests', 'runpy', 'simplegeneric', 

870 'sphinx', 'sqlite3', 'tornado', 'traitlets', 'typing', 'wcwidth', 

871 'pythoncom', 'distutils', 'six', 'webbrowser', 'win32api', 'win32com', 

872 'sphinxcontrib', 'zmq', 'nbformat', 'nbconvert', 

873 'encodings', 'entrypoints', 'html', 'ipykernel', 'isodate', 

874 'jsonschema', 'jupyter_client', 'mistune', 'nbbrowserpdf', 

875 'notebook', 'pyparsing', 'zmq', 'jupyter_core', 

876 'timeit', 'sphinxcontrib_images_lightbox2', 'win32con'): 

877 if ex == pr or ex.startswith(pr + "."): 

878 doesrem = False 

879 else: 

880 doesrem = False 

881 if doesrem: 

882 fLOG( 

883 f"[generate_help_sphinx] remove '{ex}' from sys.modules") 

884 del sys.modules[ex] 

885 

886 fLOG( 

887 "##################################################################################################") 

888 fLOG(f"[generate_help_sphinx] direct_call={direct_call}") 

889 fLOG(f"[generate_help_sphinx] cmd='''{cmd}'''") 

890 if isinstance(cmd, list): 

891 fLOG(f"[generate_help_sphinx] cmd='''{' '.join(cmd)}'''") 

892 fLOG(f"[generate_help_sphinx] kind='{kind}'") 

893 fLOG(f"[generate_help_sphinx] build='{build}'") 

894 fLOG(f"[generate_help_sphinx] direct_call={direct_call}") 

895 

896 # direct call 

897 with python_path_append(root_source): 

898 if direct_call: # pragma: no cover 

899 # mostly to debug 

900 out = StringIO() 

901 err = StringIO() 

902 memo_out = sys.stdout 

903 memo_err = sys.stderr 

904 sys.stdout = out 

905 sys.stderr = out 

906 try: 

907 build_main(cmd[1:]) 

908 except SystemExit as e: # pragma: no cover 

909 raise SystemExit("Unable to run Sphinx\n--CMD\n{0}\n--ERR--\n{1}\n--CWD--\n{2}\n--OUT--\n{3}\n--".format( 

910 cmd, err.getvalue(), os.getcwd(), out.getvalue())) from e 

911 sys.stdout = memo_out 

912 sys.stderr = memo_err 

913 out = out.getvalue() 

914 err = err.getvalue() 

915 lines = ['***OUT/***'] + out.split('\n') + ['***OUT\\***'] 

916 lines = [ 

917 _ for _ in lines if "toctree contains reference to document 'blog/" not in _] 

918 out = "\n".join(lines) 

919 else: 

920 def customfLOG(*args, **kwargs): 

921 "filter out some lines" 

922 args = [ 

923 _ for _ in args if "toctree contains reference to document 'blog/" not in _] 

924 if args: 

925 fLOG(*args, **kwargs) 

926 

927 out, err = _process_sphinx_in_private_cmd(cmd, fLOG=customfLOG) 

928 lines = ['***OUT//***'] + out.split('\n') + ['***OUT\\\\***'] 

929 lines = [ 

930 _ for _ in lines if "toctree contains reference to document 'blog/" not in _] 

931 out = "\n".join(lines) 

932 

933 fLOG( 

934 f"[generate_help_sphinx] end cmd len(out)={len(out)} len(err)={len(err)}") 

935 

936 if len(err) > 0 or len(out) > 0: 

937 if ((len(err) > 0 and "Exception occurred:" in err) or 

938 (len(out) > 0 and "Exception occurred:" in out)): 

939 def keep_line(_): # pragma: no cover 

940 if "RemovedInSphinx" in _: 

941 return False 

942 if "while setting up extension" in _: 

943 return False 

944 if "toctree contains reference to document 'blog/" in _: 

945 return False 

946 return True 

947 out = "\n".join( 

948 filter(lambda _: keep_line(_), out.split("\n"))) 

949 raise HelpGenException( # pragma: no cover 

950 "Sphinx raised an exception (direct_call={3})\n--CMD--\n{0}\n--OUT--\n{1}\n[sphinxerror]-3\n{2}".format( 

951 cmd, out, err, direct_call)) 

952 

953 fLOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 

954 fLOG("[generate_help_sphinx]", kind, "~~~~", cmd) 

955 fLOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 

956 warnings.warn( 

957 "Sphinx went through errors. Check if any of them is important.\n---OUT---\n{0}\n[sphinxerror]-2\n{1}\n----".format( 

958 out, err), UserWarning) 

959 fLOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 

960 

961 if kind == "html": 

962 fLOG("#########################################################") 

963 fLOG("[generate_help_sphinx] check that index.html exists") 

964 findex = os.path.join(build, kind, "index.html") 

965 if not os.path.exists(findex): 

966 raise FileNotFoundError( # pragma: no cover 

967 "something went wrong, unable to find {0}\n" 

968 "--CMD--\n{1}\n--OUT--\n{2}\n--ERR--\n{3}\n" 

969 "--LAY--\n{4}\n--INDEX--\n{5}\n-----\nPYTHONPATH={6}" 

970 "\n-----" 

971 "".format(findex, cmd, out, err, kind, 

972 os.path.abspath(findex), 

973 os.environ.get('PYTHONPATH', 'empty'))) 

974 

975 fLOG("#########################################################") 

976 verification_html_format(os.path.join(build, kind), fLOG=fLOG) 

977 

978 fLOG( 

979 "##################################################################################################") 

980 fLOG( 

981 "##################### end run sphinx #############################################################") 

982 fLOG( 

983 "##################################################################################################") 

984 fLOG("---- JENKINS END DOCUMENTATION SPHINX ----") 

985 

986 # we copy the extended styles (notebook, snippets) (again in build folders) 

987 # we should not need that 

988 for build_path in build_paths: 

989 if not os.path.exists(build_path): 

990 fLOG( 

991 f"[generate_help_sphinx] build_path not found '{build_path}'") 

992 continue 

993 dest = os.path.join(build_path, "_static", style_figure_notebook[0]) 

994 if not os.path.exists(dest): # pragma: no cover 

995 fLOG("[generate_help_sphinx] CREATE-CSS2", dest) 

996 with open(dest, "w", encoding="utf-8") as f: 

997 f.write(style_figure_notebook[1]) 

998 

999 if add_htmlhelp: # pragma: no cover 

1000 # we call HtmlHelp 

1001 fLOG("[generate_help_sphinx] run HTMLHELP") 

1002 for cmd in cmds_post: 

1003 fLOG("running", cmd) 

1004 out, err = run_cmd(cmd, wait=True, fLOG=fLOG, 

1005 communicate=True, timeout=600) 

1006 fLOG(out) 

1007 if len(err) > 0: 

1008 mes = "Sphinx went through errors. Check if any of them is important.\nOUT:\n{0}\n[sphinxerror]-1\n{1}" 

1009 warnings.warn(mes.format(out, err), UserWarning) 

1010 fLOG("[generate_help_sphinx] end run HTMLHELP") 

1011 

1012 ##################################### 

1013 # we copy some file such as rss.xml 

1014 ##################################### 

1015 fLOG("---- JENKINS BEGIN COPY RSS.XML ----") 

1016 tocopy = [os.path.join(docpath, "source", "blog", "rss.xml")] 

1017 for toco in tocopy: 

1018 if os.path.exists(toco): 

1019 fLOG(f"[generate_help_sphinx] copy '{os.path.split(toco)[-1]}'") 

1020 for build_path in build_paths: 

1021 dest = os.path.join(build_path, "_downloads") 

1022 if os.path.exists(dest): 

1023 shutil.copy(toco, dest) 

1024 else: 

1025 fLOG( 

1026 f"[generate_help_sphinx] not found '{os.path.split(toco)[-1]}'") 

1027 

1028 fLOG("---- JENKINS END COPY RSS.XML ----") 

1029 

1030 ##################################### 

1031 # we copy the coverage files if it is missing 

1032 ##################################### 

1033 datetime_rows = [("converage", datetime.now())] 

1034 fLOG("---- JENKINS BEGIN DOCUMENTATION COVERAGE ----") 

1035 fLOG("[generate_help_sphinx] copy coverage") 

1036 covfold = os.path.join(docpath, "source", "coverage") 

1037 if os.path.exists(covfold): # pragma: no cover 

1038 fLOG("[generate_help_sphinx] coverage folder:", covfold) 

1039 allfiles = os.listdir(covfold) 

1040 allf = [_ for _ in allfiles if _.endswith(".rst")] 

1041 if len(allf) == 0: 

1042 # no rst file --> we copy 

1043 allfiles = [os.path.join(covfold, _) for _ in allfiles] 

1044 allfiles = [_ for _ in allfiles if os.path.isfile(_)] 

1045 for lay in lays: 

1046 layfolder = os.path.join(docpath, build, lay) 

1047 fLOG("[generate_help_sphinx] coverage docpath:", docpath, " -- ", 

1048 build, " -- ", lay, " ---- ", layfolder) 

1049 if os.path.exists(layfolder): 

1050 covbuild = os.path.join(layfolder, "coverage") 

1051 fLOG("[coverage] covbuild", covbuild) 

1052 if not os.path.exists(covbuild): 

1053 os.mkdir(covbuild) 

1054 for f in allfiles: 

1055 fLOG("[generate_help_sphinx] coverage copy ", 

1056 f, " to ", covbuild) 

1057 shutil.copy(f, covbuild) 

1058 else: 

1059 fLOG("[sphinxerror]-B coverage files with rst in", covfold) 

1060 else: 

1061 fLOG("[generate_help_sphinx] no coverage files", covfold) 

1062 fLOG("---- JENKINS END DOCUMENTATION COVERAGE ----") 

1063 

1064 ######################################################### 

1065 # we copy javascript dependencies to build _download/javascript 

1066 ######################################################### 

1067 datetime_rows = [("javascript", datetime.now())] 

1068 # for every layout 

1069 fLOG("[generate_help_sphinx] [reveal.js] JAVASCRIPT: COPY", html_static_paths) 

1070 fLOG("[generate_help_sphinx] [reveal.js] BUILD:", build_paths) 

1071 for subf in ["html"]: 

1072 for html_static_path, build_path in zip(html_static_paths, build_paths): 

1073 for sname in ["_downloads", "notebooks"]: 

1074 builddoc = os.path.join(build_path, subf, sname) 

1075 if not os.path.exists(builddoc): 

1076 builddoc = os.path.join(build_path, "..", subf, sname) 

1077 if not os.path.exists(builddoc): 

1078 builddoc = os.path.join(build_path, sname) 

1079 if os.path.exists(builddoc): 

1080 # no download, there is probably no notebooks 

1081 # so it is not needed 

1082 fLOG("[generate_help_sphinx] copy javascript static files from", 

1083 html_static_path, "to", builddoc) 

1084 copy = synchronize_folder( 

1085 html_static_path, builddoc, copy_1to2=True, fLOG=fLOG) 

1086 fLOG("[generate_help_sphinx] javascript", 

1087 len(copy), "files copied") 

1088 for gal in galleries: 

1089 gal_path = os.path.join(html_static_path, "..", gal) 

1090 if not os.path.exists(gal_path): 

1091 continue 

1092 folders = ['.'] + [f for f in os.listdir(gal_path) 

1093 if os.path.isdir(f)] 

1094 for fold in folders: 

1095 gal_src = os.path.join(gal_path, fold, "images") 

1096 if not os.path.exists(gal_src): 

1097 continue 

1098 gal_dst = os.path.join( 

1099 build_path, gal, fold, "_images") 

1100 if not os.path.exists(gal_dst): 

1101 os.makedirs(gal_dst) 

1102 fLOG("[generate_help_sphinx] copy images to ", 

1103 gal_src, "to", gal_dst) 

1104 copy = synchronize_folder( 

1105 gal_src, gal_dst, copy_1to2=True, fLOG=fLOG) 

1106 fLOG("[generate_help_sphinx] notebooks", 

1107 len(copy), "files copied") 

1108 

1109 gal_src = os.path.join( 

1110 build_path, "_images", "math") 

1111 if os.path.exists(gal_src): 

1112 gal_dst = os.path.join( 

1113 build_path, gal, "_images", "math") 

1114 if not os.path.exists(gal_dst): 

1115 os.makedirs(gal_dst) 

1116 fLOG(f"[generate_help_sphinx] copy images to " 

1117 f"{gal_src!r} to {gal_dst!r}") 

1118 copy = synchronize_folder( 

1119 gal_src, gal_dst, copy_1to2=True, fLOG=fLOG) 

1120 fLOG("[generate_help_sphinx] notebooks", 

1121 len(copy), "files copied") 

1122 else: 

1123 fLOG( 

1124 "[generate_help_sphinx] [reveal.js] no need, no folder", builddoc) 

1125 

1126 ###### 

1127 # next 

1128 ###### 

1129 datetime_rows = [("latex", datetime.now())] 

1130 fLOG("[generate_help_sphinx] LATEX") 

1131 if "latex" in layout or "elatex" in layout: 

1132 fLOG("[generate_help_sphinx] post_process_latex_output", froot) 

1133 post_process_latex_output( 

1134 froot, False, custom_latex_processing=custom_latex_processing) 

1135 

1136 if "pdf" in layout: 

1137 fLOG("[generate_help_sphinx] compile_latex_output_final", 

1138 froot, "**", latex_path) 

1139 compile_latex_output_final( 

1140 froot, latex_path, False, latex_book=latex_book, fLOG=fLOG, 

1141 custom_latex_processing=custom_latex_processing, 

1142 remove_unicode=remove_unicode) 

1143 

1144 if "html" in layout: 

1145 nbf = os.path.join(build, "html", "notebooks") 

1146 if os.path.exists(nbf): 

1147 post_process_html_nb_output_static_file(nbf, fLOG=fLOG) 

1148 post_process_html_nb_output_static_file( 

1149 os.path.join(build, "html", "_downloads"), fLOG=fLOG) 

1150 

1151 for build_path in build_paths: 

1152 src = os.path.join(build_path, "_images") 

1153 dest = os.path.join(build_path, "notebooks") 

1154 if os.path.exists(src) and os.path.exists(dest): 

1155 fLOG("[generate_help_sphinx] [imgs] look for images in ", src) 

1156 for img in enumerate_copy_images_for_slides(src, dest): 

1157 fLOG("[generate_help_sphinx] [imgs] copy image for slides:", img) 

1158 

1159 ###### 

1160 # copy pdf to html 

1161 ###### 

1162 latex = os.path.join(build_path, "latex") 

1163 html = os.path.join(build_path, "latex") 

1164 if os.path.exists(html) and os.path.exists(latex): # pragma: no cover 

1165 pdfs = os.listdir(latex) 

1166 for pdf in pdfs: 

1167 ext = os.path.splitext(pdf)[-1] 

1168 if ext != '.pdf': 

1169 continue 

1170 full = os.path.join(latex, pdf) 

1171 fLOG("[generate_help_sphinx] [pdf] copy:", pdf) 

1172 shutil.copy(full, html) 

1173 

1174 ##### 

1175 # end 

1176 ##### 

1177 os.chdir(pa) 

1178 fLOG("################################") 

1179 fLOG("#### END - check log for success") 

1180 fLOG("################################") 

1181 for i, row in enumerate(datetime_rows): 

1182 if i == 0: 

1183 a = row[1] 

1184 else: 

1185 a = datetime_rows[i - 1][1] 

1186 b = row[1] 

1187 d = b - a 

1188 mes = "[generate_help_sphinx] {0}{1}: {2} [{3} --> {4}]".format( 

1189 row[0], " " * (15 - len(row[0])), d, a, b) 

1190 fLOG(mes) 

1191 fLOG("---- JENKINS END DOCUMENTATION ----") 

1192 

1193 

1194def _process_sphinx_in_private_cmd(list_args, fLOG): 

1195 this = os.path.join(os.path.dirname( 

1196 os.path.abspath(__file__)), "process_sphinx_cmd.py") 

1197 res = [] 

1198 for i, c in enumerate(list_args): 

1199 if i == 0 and c in ("sphinx-main", "sphinx-build"): 

1200 continue 

1201 if c[0] == '"' or c[-1] == '"' or ' ' not in c: 

1202 res.append(c) 

1203 else: 

1204 res.append(f'"{c}"') 

1205 sargs = " ".join(res) 

1206 cmd = f"\"{sys.executable.replace('w.exe', '.exe')}\" \"{this}\" {sargs}" 

1207 fLOG(" ", cmd) 

1208 fLOG("[generate_help_sphinx] _process_sphinx_in_private_cmd BEGIN") 

1209 out, err = run_cmd(cmd, wait=True, fLOG=fLOG, 

1210 communicate=False, tell_if_no_output=120) 

1211 fLOG("[generate_help_sphinx] _process_sphinx_in_private_cmd END") 

1212 lines = out.split('\n') 

1213 lines = [ 

1214 _ for _ in lines if "toctree contains reference to document 'blog/" not in _] 

1215 out = "\n".join(lines) 

1216 return out, err 

1217 

1218 

1219def _check_sphinx_configuration(conf, fLOG): 

1220 """ 

1221 Operates some verification on the configuration. 

1222 

1223 :param conf: :epkg:`sphinx` configuration 

1224 :param fLOG: logging function 

1225 :return: list of gallery folders 

1226 """ 

1227 galleries = [] 

1228 clean_folders = [] 

1229 if hasattr(conf, "sphinx_gallery_conf"): 

1230 sphinx_gallery_conf = conf.sphinx_gallery_conf 

1231 if len(sphinx_gallery_conf["examples_dirs"]) != len(sphinx_gallery_conf["gallery_dirs"]): 

1232 add = "\nexamples_dirs={0}\ngallery_dirs={1}".format( 

1233 sphinx_gallery_conf["examples_dirs"], sphinx_gallery_conf["gallery_dirs"]) 

1234 raise ValueError( # pragma: no cover 

1235 'sphinx_gallery_conf["examples_dirs"] and sphinx_gallery_conf["gallery_dirs"] ' 

1236 'do not have the same size.' + add) 

1237 if len(sphinx_gallery_conf["examples_dirs"]) > 0: 

1238 fLOG( 

1239 f"[sphinx-gallery] {len(sphinx_gallery_conf['examples_dirs'])} discovered") 

1240 for a, b in zip(sphinx_gallery_conf["examples_dirs"], 

1241 sphinx_gallery_conf["gallery_dirs"]): 

1242 fLOG(f"[sphinx-gallery] src '{a}'") 

1243 fLOG(f"[sphinx-gallery] dest '{b}'") 

1244 clean_folders.append(a) 

1245 if len(sphinx_gallery_conf["gallery_dirs"]) > 0: 

1246 galleries.extend(sphinx_gallery_conf["gallery_dirs"]) 

1247 for cl in clean_folders: 

1248 fLOG(f"[sphinx-gallery] clean '{cl}'") 

1249 for temp in os.listdir(cl): # pragma: no cover 

1250 if temp.startswith("temp_"): 

1251 aaa = os.path.join(cl, temp) 

1252 fLOG(f"[sphinx-gallery] remove '{cl}'") 

1253 remove_folder(aaa) 

1254 return galleries 

1255 

1256 

1257def _import_conf_extract_parameter(root, root_source, folds, build, newconf, 

1258 all_tocs, build_paths, parameters, 

1259 html_static_paths, fLOG): 

1260 """ 

1261 Imports the configuration file and extracts some 

1262 of the parameters it defines. 

1263 Fills the following lists. 

1264 

1265 @param root folder of the package 

1266 @param root_source folder of the sources 

1267 @param folds folder of the documentation 

1268 @param build build path 

1269 @param newconf unused except in an error message 

1270 @param all_tocs list to fill 

1271 @param build_paths list to fill 

1272 @param parameters list to fill 

1273 @param html_static_paths list to fill 

1274 @param fLOG logging function 

1275 

1276 * all_tocs 

1277 * build_paths 

1278 * parameters 

1279 * html_static_paths 

1280 """ 

1281 # trick, we place the good folder in the first position 

1282 with python_path_append(folds): 

1283 if fLOG: 

1284 fLOG( 

1285 f"[_import_conf_extract_parameter] import from '{folds}'") 

1286 try: 

1287 module_conf = execute_script_get_local_variables( 

1288 "from conf import *", folder=folds) 

1289 except Exception as ee: # pragma: no cover 

1290 raise HelpGenException( 

1291 "Unable to import a config file (root_source='{0}').".format( 

1292 folds), os.path.join(folds, "conf.py")) from ee 

1293 if 'ERROR' in module_conf: 

1294 raise ImportError( # pragma: no cover 

1295 "\n" + module_conf['ERROR'] + "\n") 

1296 if len(module_conf) == 0: 

1297 raise ImportError( # pragma: no cover 

1298 "Unable to extract local variable from conf.py.") 

1299 

1300 if module_conf is None: 

1301 raise ImportError( # pragma: no cover 

1302 f"Unable to import '{newconf}' which defines the help generation") 

1303 thenewconf = dictionary_as_class(module_conf) 

1304 if fLOG: 

1305 fLOG("[_import_conf_extract_parameter] import:", thenewconf.drop( 

1306 "epkg_dictionary", "latex_elements", "imgmath_latex_preamble", "preamble")) 

1307 

1308 tocs = add_missing_files(root, thenewconf, "__INSERT__", fLOG) 

1309 all_tocs.extend(tocs) 

1310 

1311 # we store the html_static_path in html_static_paths 

1312 html_static_path = thenewconf.__dict__.get( 

1313 "html_static_path", "_static") 

1314 if isinstance(html_static_path, list): 

1315 html_static_path = html_static_path[0] 

1316 html_static_path = os.path.normpath( 

1317 os.path.join(root_source, html_static_path)) 

1318 if not os.path.exists(html_static_path): 

1319 raise FileNotFoundError( # pragma: no cover 

1320 "no static path:" + html_static_path) 

1321 html_static_paths.append(html_static_path) 

1322 build_paths.append( 

1323 os.path.normpath(os.path.join(html_static_path, "..", "..", build, "html"))) 

1324 pp = dict(latex_book=thenewconf.latex_book) # pylint: disable=E1101 

1325 parameters.append(pp)