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 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 Calls the function @see fn _setup_hook to initialize 

226 the module before generating the documentation. 

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

228 

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

230 

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

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

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

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

235 The function directly calls 

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

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

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

239 It does not require to get script location. 

240 Not enough stable from virtual environment. 

241 

242 Set ``BOKEH_DOCS_MISSING_API_KEY_OK`` to 1. 

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

244 The function was updated to use Sphinx 1.6.2. 

245 However, you should read blog post 

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

247 if you have any trouble with custom css. 

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

249 Parameter *parallel* was added. 

250 The parameter *nblayout* in the configuration file specifies 

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

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

253 the gallery if they match this regular expression. 

254 It automatically adds video and image directives. 

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

256 configuration file to allow or remove unicode characters 

257 before compiling the latex output. 

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

259 the generation of the documentation. Do not import it 

260 directly. 

261 """ 

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

263 

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

265 if layout is None: 

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

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

268 setup_environment_for_help(fLOG=fLOG) 

269 # we keep a clean list of modules 

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

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

272 

273 if add_htmlhelp: # pragma: no cover 

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

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

276 fLOG("[generate_help_sphinx] add add_htmlhelp") 

277 

278 if extra_ext is None: 

279 extra_ext = [] 

280 

281 def lay_build_override_newconf(t3): 

282 if isinstance(t3, str): 

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

284 elif len(t3) == 1: 

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

286 elif len(t3) == 2: 

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

288 elif len(t3) == 3: 

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

290 else: 

291 lay, build, override, newconf = t3 

292 return lay, build, override, newconf 

293 

294 directives.register_directive("blogpost", BlogPostDirective) 

295 directives.register_directive("blogpostagg", BlogPostDirectiveAgg) 

296 directives.register_directive("runpython", RunPythonDirective) 

297 directives.register_directive("sharenet", ShareNetDirective) 

298 directives.register_directive("video", VideoDirective) 

299 directives.register_directive("simpleimage", SimpleImageDirective) 

300 directives.register_directive("image", ImageDirective) 

301 directives.register_directive("todoext", TodoExt) 

302 directives.register_directive("mathdef", MathDef) 

303 directives.register_directive("quote", QuoteNode) 

304 directives.register_directive("blocref", BlocRef) 

305 directives.register_directive("exref", ExRef) 

306 directives.register_directive("faqref", FaqRef) 

307 directives.register_directive("nbref", NbRef) 

308 directives.register_directive("cmdref", CmdRef) 

309 directives.register_directive("postcontents", PostContentsDirective) 

310 directives.register_directive("tocdelay", TocDelayDirective) 

311 directives.register_directive("youtube", YoutubeDirective) 

312 directives.register_directive("thumbnail", ImageDirective) 

313 directives.register_directive("collapse", CollapseDirective) 

314 directives.register_directive("gdot", GDotDirective) 

315 roles.register_canonical_role("sharenet", sharenet_role) 

316 roles.register_canonical_role("bigger", bigger_role) 

317 roles.register_canonical_role("githublink", githublink_role) 

318 roles.register_canonical_role("gitlog", gitlog_role) 

319 roles.register_canonical_role("tpl", tpl_role) 

320 roles.register_canonical_role("epkg", epkg_role) 

321 roles.register_canonical_role("downloadlink", process_downloadlink_role) 

322 

323 if "conf" in sys.modules: 

324 raise ImportError( # pragma: no cover 

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

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

327 

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

329 # root_source 

330 ############ 

331 root = os.path.abspath(root) 

332 froot = root 

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

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

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

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

337 root_package = root 

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

339 raise FileNotFoundError( # pragma: no cover 

340 "Unable to find source root from '{}'.".format(root)) 

341 fLOG("[generate_help_sphinx] root='{0}'".format(root)) 

342 fLOG("[generate_help_sphinx] root_package='{0}'".format(root_package)) 

343 fLOG("[generate_help_sphinx] root_source='{0}'".format(root_source)) 

344 fLOG("[generate_help_sphinx] root_sphinxdoc='{0}'".format(root_sphinxdoc)) 

345 conf_paths = [root_source, root_package] 

346 if extra_paths: 

347 conf_paths.extend(extra_paths) 

348 

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

350 # we import conf_base, specific to multi layers 

351 ######################################## 

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

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

354 code = "from conf_base import *" 

355 with python_path_append(conf_paths): 

356 try: 

357 module_conf = execute_script_get_local_variables( 

358 code, folder=root_source, check=True) 

359 except RuntimeError as e: 

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

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

362 

363 if module_conf is None: 

364 raise ImportError( 

365 "Unable to import '{0}' which defines the help generation".format(confb)) 

366 if 'ERROR' in module_conf: 

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

368 "-----------------------", 

369 module_conf['ERROR']]) 

370 raise ImportError(msg) 

371 conf_base = dictionary_as_class(module_conf) 

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

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

374 

375 copypath = list(sys.path) 

376 

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

378 html_static_paths = [] 

379 build_paths = [] 

380 all_tocs = [] 

381 parameters = [] 

382 

383 ################################### 

384 # import others conf, we must do it now 

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

386 # we assume the configuration are not too different 

387 # about language for example, latex_path, pandoc_path 

388 ################################################# 

389 for t3 in layout: 

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

391 if newconf is None: 

392 continue 

393 fLOG("[generate_help_sphinx] newconf: '{}' - {}".format(newconf, t3)) 

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

395 # add missing templates 

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

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

398 all_tocs, build_paths, parameters, 

399 html_static_paths, fLOG) 

400 

401 ################################################################ 

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

403 # import conf.py 

404 ################################################################ 

405 with python_path_append(conf_paths): 

406 try: 

407 module_conf = execute_script_get_local_variables( 

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

409 except ImportError as e: # pragma: no cover 

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

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

412 if module_conf is None: 

413 raise ImportError( # pragma: no cover 

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

415 if 'ERROR' in module_conf: 

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

417 "----------------------- ERROR:", 

418 module_conf['ERROR'], 

419 "------------------------ root_source:", 

420 root_source]) 

421 raise ImportError(msg) 

422 if len(module_conf) == 0: 

423 raise ImportError( 

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

425 theconf = dictionary_as_class(module_conf) 

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

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

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

429 all_tocs.extend(tocs) 

430 

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

432 # some checkings on the configuration 

433 ############################## 

434 _check_sphinx_configuration(theconf, fLOG) 

435 

436 ############################################################## 

437 # Extracts variables from the configuration. 

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

439 # We extract other information from the configuration 

440 ############################################################## 

441 html_static_path = theconf.__dict__.get("html_static_path", "phdoc_static") 

442 if isinstance(html_static_path, list): 

443 html_static_path = html_static_path[0] 

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

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

446 raise FileNotFoundError( # pragma: no cover 

447 "no static path:" + html_static_path) 

448 html_static_paths.append(html_static_path) 

449 build_paths.append( 

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

451 custom_latex_processing = theconf.__dict__.get( 

452 "custom_latex_processing", None) 

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

454 # The configuration file is pickled by sphinx 

455 # and parameter should not be functions. 

456 if isinstance(custom_latex_processing, str): 

457 custom_latex_processing = find_custom_latex_processing( # pylint: disable=E1111 

458 custom_latex_processing) 

459 res = custom_latex_processing("dummy phrase") 

460 if res is None: 

461 raise ValueError( 

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

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

464 snippet_folder = theconf.__dict__.get( 

465 "notebook_custom_snippet_folder", None) 

466 if snippet_folder: 

467 snippet_folder = os.path.join( 

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

469 

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

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

472 raise TypeError("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(" {0}/{1} - '{2}': '{3}'".format(i + 1, len(nblinks), k, v)) 

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

523 fLOG("[generate_help_sphinx] nbneg_pattern='{0}'".format(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("[generate_help_sphinx] [javascript]: '{0}'".format(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 = [(".*[.]%s$" % 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("[generate_help_sphinx] module_name='{}'".format(module_name)) 

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

599 fLOG("[generate_help_sphinx] root='{}' root_package='{}'" 

600 "".format(root, root_package)) 

601 fLOG("[generate_help_sphinx] dest_doc='{}'".format(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("[generate_help_sphinx] subfolders={0}".format(subfolders)) 

608 prepare_file_for_sphinx_help_generation( 

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

610 rootrep=("_doc.sphinxdoc.source.%s." % (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 fLOG("[generate_help_sphinx] BlogPostList.write_aggregated") 

642 plist.write_aggregated(blog_fold, 

643 blog_title=theconf.__dict__.get( 

644 "blog_title", project_var_name), 

645 blog_description=theconf.__dict__.get( 

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

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

648 else: 

649 plist = None 

650 

651 fLOG("[generate_help_sphinx] end blogs") 

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

653 

654 ########### 

655 # notebooks 

656 ########### 

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

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

659 fLOG("[generate_help_sphinx] begin notebooks") 

660 indextxtnote = None 

661 indexlistnote = [] 

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

663 notebook_doc = os.path.abspath( 

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

665 if os.path.exists(notebook_dir): 

666 fLOG(" look into '{0}'".format(notebook_dir)) 

667 fLOG(" -pattern '{0}'".format(nbneg_pattern)) 

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

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

670 notebooks = [_ for _ in notebooks if ( 

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

672 fLOG(" found {0} notebooks".format(len(notebooks))) 

673 if len(notebooks) > 0: 

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

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

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

677 os.makedirs(build) 

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

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

680 for note in notebooks: 

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

682 indexlistnote.append((no, note)) 

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

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

685 os.mkdir(notebook_doc) 

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

687 formats=nbformats, latex_path=latex_path, 

688 pandoc_path=pandoc_path, fLOG=fLOG, nblinks=nblinks, 

689 notebook_replacements=notebook_replacements) 

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

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

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

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

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

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

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

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

698 nbs = indexlistnote 

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

700 nbs.sort() 

701 build_notebooks_gallery(nbs, os.path.join( 

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

703 snippet_folder=snippet_folder, fLOG=fLOG) 

704 build_all_notebooks_coverage(nbs, os.path.join( 

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

706 

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

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

709 if len(imgs) > 0: 

710 gallery_dirs = nbexamples_conf.get( 

711 'gallery_dirs', None) if nbexamples_conf else None 

712 for img in imgs: 

713 shutil.copy(img, notebook_doc) 

714 if gallery_dirs: 

715 for d in gallery_dirs: 

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

717 os.makedirs(d) 

718 shutil.copy(img, d) 

719 else: 

720 fLOG("---- no folder '{0}'".format(notebook_dir)) 

721 

722 fLOG("[generate_help_sphinx] end notebooks") 

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

724 

725 ############################################# 

726 # replace placeholder as blog posts list into tocs files 

727 ############################################# 

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

729 fLOG("[generate_help_sphinx] blog placeholder") 

730 if plist is not None: 

731 replace_placeholder_by_recent_blogpost( 

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

733 

734 ################################# 

735 # run the documentation generation 

736 ################################# 

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

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

739 temp = os.environ["PATH"] 

740 pyts = get_executables_path() 

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

742 script = sepj.join(pyts) 

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

744 temp = script + sepj + temp 

745 os.environ["PATH"] = temp 

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

747 pa = os.getcwd() 

748 

749 # bokeh trick 

750 updates_env = dict(BOKEH_DOCS_MISSING_API_KEY_OK=1) 

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

752 if k not in os.environ: 

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

754 

755 thispath = os.path.normpath(root) 

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

757 

758 ################ 

759 # checks encoding 

760 ################ 

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

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

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

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

765 for name in files: 

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

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

768 try: 

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

770 f.read() 

771 except UnicodeDecodeError as e: # pragma: no cover 

772 raise HelpGenException( 

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

774 except Exception as e: # pragma: no cover 

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

776 

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

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

779 raise FileNotFoundError(docpath) 

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

781 

782 os.chdir(docpath) 

783 

784 ##################### 

785 # builds command lines 

786 ##################### 

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

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

789 cmds = [] 

790 lays = [] 

791 cmds_post = [] 

792 for t3 in layout: 

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

794 

795 if lay == "pdf": 

796 lay = "elatex" 

797 

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

799 if os.path.exists(build): 

800 for fold in os.listdir(build): 

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

802 remove_folder(build) 

803 

804 over_ = ["{0}={1}".format(k, v) for k, v in override.items()] 

805 over = [] 

806 for o in over_: 

807 over.append("-D") 

808 over.append(o) 

809 

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

811 

812 cmd = ["sphinx-build", "-j%d" % parallel, "-v", "-T", "-b", "{0}".format(lay), 

813 "-d", "{0}/doctrees".format(build)] + over + sconf + ["source", "{0}/{1}".format(build, lay)] 

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

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

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

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

818 lays.append(lay) 

819 

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

821 # we cannot execute htmlhelp in the same folder 

822 # as it changes the encoding 

823 cmd = ["sphinx-build", "-j%d" % parallel, "-v", "-T", "-b", "{0}help".format(lay), 

824 "-d", "{0}/doctrees".format(build)] + over + sconf + ["source", "{0}/{1}html".format(build, lay)] 

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

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

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

828 lays.append(lay) 

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

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

831 '"%s"' % hhp 

832 cmds_post.append(cmdp) 

833 

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

835 

836 ############################################################### 

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

838 ############################################################### 

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

840 fLOG("[generate_help_sphinx] RUN SPHINX") 

841 for cmd, build, kind in cmds: 

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

843 fLOG( 

844 "##################################################################################################") 

845 fLOG( 

846 "##################### run sphinx #################################################################") 

847 fLOG( 

848 "##################################################################################################") 

849 fLOG("[generate_help_sphinx]", cmd) 

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

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

852 

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

854 for ex in existing: 

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

856 doesrem = False 

857 elif ex not in list_modules_start: 

858 doesrem = True 

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

860 'pyquickhelper', 'yaml', 'xlsxwriter', '_csv', 

861 '_lsprof', '_multiprocessing', '_overlapped', '_sqlite3', 

862 'alabaster', 'asyncio', 'babel', 'bokeh', 'cProfile', 

863 'certifi', 'colorsys', 'concurrent', 'csv', 'cycler', 

864 'dateutil', 'decorator', 'docutils', 'fractions', 'gc', 

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

866 'jinja2', 'mimetypes', 'multiprocessing', 'pathlib', 

867 'pickleshare', 'profile', 'prompt_toolkit', 'pstats', 

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

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

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

871 'sphinxcontrib', 'zmq', 'nbformat', 'nbconvert', 

872 'encodings', 'entrypoints', 'html', 'ipykernel', 'isodate', 

873 'jsonschema', 'jupyter_client', 'mistune', 'nbbrowserpdf', 

874 'notebook', 'pyparsing', 'zmq', 'jupyter_core', 

875 'timeit', 'sphinxcontrib_images_lightbox2', 'win32con'): 

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

877 doesrem = False 

878 else: 

879 doesrem = False 

880 if doesrem: 

881 fLOG( 

882 "[generate_help_sphinx] remove '{0}' from sys.modules".format(ex)) 

883 del sys.modules[ex] 

884 

885 fLOG( 

886 "##################################################################################################") 

887 fLOG("[generate_help_sphinx] direct_call={0}".format(direct_call)) 

888 fLOG("[generate_help_sphinx] cmd='''{0}'''".format(cmd)) 

889 if isinstance(cmd, list): 

890 fLOG("[generate_help_sphinx] cmd='''{0}'''".format(" ".join(cmd))) 

891 fLOG("[generate_help_sphinx] kind='{0}'".format(kind)) 

892 fLOG("[generate_help_sphinx] build='{0}'".format(build)) 

893 fLOG("[generate_help_sphinx] direct_call={0}".format(direct_call)) 

894 

895 # direct call 

896 with python_path_append(root_source): 

897 if direct_call: 

898 # mostly to debug 

899 out = StringIO() 

900 err = StringIO() 

901 memo_out = sys.stdout 

902 memo_err = sys.stderr 

903 sys.stdout = out 

904 sys.stderr = out 

905 try: 

906 build_main(cmd[1:]) 

907 except SystemExit as e: # pragma: no cover 

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

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

910 sys.stdout = memo_out 

911 sys.stderr = memo_err 

912 out = out.getvalue() 

913 err = err.getvalue() 

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

915 lines = [ 

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

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

918 else: 

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

920 "filter out some lines" 

921 args = [ 

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

923 if args: 

924 fLOG(*args, **kwargs) 

925 

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

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

928 lines = [ 

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

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

931 

932 fLOG("[generate_help_sphinx] end cmd len(out)={0} len(err)={1}".format( 

933 len(out), len(err))) 

934 

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

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

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

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

939 if "RemovedInSphinx" in _: 

940 return False 

941 if "while setting up extension" in _: 

942 return False 

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

944 return False 

945 return True 

946 out = "\n".join( 

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

948 raise HelpGenException( 

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

950 cmd, out, err, direct_call)) 

951 

952 fLOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 

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

954 fLOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 

955 warnings.warn( 

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

957 out, err), UserWarning) 

958 fLOG("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") 

959 

960 if kind == "html": 

961 fLOG("#########################################################") 

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

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

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

965 raise FileNotFoundError( 

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

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

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

969 "\n-----" 

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

971 os.path.abspath(findex), 

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

973 

974 fLOG("#########################################################") 

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

976 

977 fLOG( 

978 "##################################################################################################") 

979 fLOG( 

980 "##################### end run sphinx #############################################################") 

981 fLOG( 

982 "##################################################################################################") 

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

984 

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

986 # we should not need that 

987 for build_path in build_paths: 

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

989 fLOG("[generate_help_sphinx] build_path not found '{0}'".format( 

990 build_path)) 

991 continue 

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

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

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

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

996 f.write(style_figure_notebook[1]) 

997 

998 if add_htmlhelp: # pragma: no cover 

999 # we call HtmlHelp 

1000 fLOG("[generate_help_sphinx] run HTMLHELP") 

1001 for cmd in cmds_post: 

1002 fLOG("running", cmd) 

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

1004 communicate=True, timeout=600) 

1005 fLOG(out) 

1006 if len(err) > 0: 

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

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

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

1010 

1011 ##################################### 

1012 # we copy some file such as rss.xml 

1013 ##################################### 

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

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

1016 for toco in tocopy: 

1017 if os.path.exists(toco): 

1018 fLOG("[generate_help_sphinx] copy '{}'".format( 

1019 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("[generate_help_sphinx] not found '{}'".format( 

1026 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 else: 

1089 fLOG( 

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

1091 

1092 ###### 

1093 # next 

1094 ###### 

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

1096 fLOG("[generate_help_sphinx] LATEX") 

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

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

1099 post_process_latex_output( 

1100 froot, False, custom_latex_processing=custom_latex_processing) 

1101 

1102 if "pdf" in layout: 

1103 fLOG("[generate_help_sphinx] compile_latex_output_final", 

1104 froot, "**", latex_path) 

1105 compile_latex_output_final( 

1106 froot, latex_path, False, latex_book=latex_book, fLOG=fLOG, 

1107 custom_latex_processing=custom_latex_processing, 

1108 remove_unicode=remove_unicode) 

1109 

1110 if "html" in layout: 

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

1112 if os.path.exists(nbf): 

1113 post_process_html_nb_output_static_file(nbf, fLOG=fLOG) 

1114 post_process_html_nb_output_static_file( 

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

1116 

1117 for build_path in build_paths: 

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

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

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

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

1122 for img in enumerate_copy_images_for_slides(src, dest): 

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

1124 

1125 ###### 

1126 # copy pdf to html 

1127 ###### 

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

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

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

1131 pdfs = os.listdir(latex) 

1132 for pdf in pdfs: 

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

1134 if ext != '.pdf': 

1135 continue 

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

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

1138 shutil.copy(full, html) 

1139 

1140 ##### 

1141 # end 

1142 ##### 

1143 os.chdir(pa) 

1144 fLOG("################################") 

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

1146 fLOG("################################") 

1147 for i, row in enumerate(datetime_rows): 

1148 if i == 0: 

1149 a = row[1] 

1150 else: 

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

1152 b = row[1] 

1153 d = b - a 

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

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

1156 fLOG(mes) 

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

1158 

1159 

1160def _process_sphinx_in_private_cmd(list_args, fLOG): 

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

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

1163 res = [] 

1164 for i, c in enumerate(list_args): 

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

1166 continue 

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

1168 res.append(c) 

1169 else: 

1170 res.append('"{0}"'.format(c)) 

1171 sargs = " ".join(res) 

1172 cmd = '"{0}" "{1}" {2}'.format( 

1173 sys.executable.replace("w.exe", ".exe"), this, sargs) 

1174 fLOG(" ", cmd) 

1175 fLOG("[generate_help_sphinx] _process_sphinx_in_private_cmd BEGIN") 

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

1177 communicate=False, tell_if_no_output=120) 

1178 fLOG("[generate_help_sphinx] _process_sphinx_in_private_cmd END") 

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

1180 lines = [ 

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

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

1183 return out, err 

1184 

1185 

1186def _check_sphinx_configuration(conf, fLOG): 

1187 """ 

1188 Operates some verification on the configuration. 

1189 

1190 @param conf :epkg:`sphinx` configuration 

1191 @param fLOG logging function 

1192 """ 

1193 clean_folders = [] 

1194 if hasattr(conf, "sphinx_gallery_conf"): 

1195 sphinx_gallery_conf = conf.sphinx_gallery_conf 

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

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

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

1199 raise ValueError( 

1200 'sphinx_gallery_conf["examples_dirs"] and sphinx_gallery_conf["gallery_dirs"] do not have the same size.' + add) 

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

1202 fLOG( 

1203 "[sphinx-gallery] {0} discovered".format(len(sphinx_gallery_conf["examples_dirs"]))) 

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

1205 sphinx_gallery_conf["gallery_dirs"]): 

1206 fLOG("[sphinx-gallery] src '{0}'".format(a)) 

1207 fLOG("[sphinx-gallery] dest '{0}'".format(b)) 

1208 clean_folders.append(a) 

1209 for cl in clean_folders: 

1210 fLOG("[sphinx-gallery] clean '{0}'".format(cl)) 

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

1212 if temp.startswith("temp_"): 

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

1214 fLOG("[sphinx-gallery] remove '{0}'".format(cl)) 

1215 remove_folder(aaa) 

1216 

1217 

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

1219 all_tocs, build_paths, parameters, 

1220 html_static_paths, fLOG): 

1221 """ 

1222 Imports the configuration file and extracts some 

1223 of the parameters it defines. 

1224 Fills the following lists. 

1225 

1226 @param root folder of the package 

1227 @param root_source folder of the sources 

1228 @param folds folder of the documentation 

1229 @param build build path 

1230 @param newconf unused except in an error message 

1231 @param all_tocs list to fill 

1232 @param build_paths list to fill 

1233 @param parameters list to fill 

1234 @param html_static_paths list to fill 

1235 @param fLOG logging function 

1236 

1237 * all_tocs 

1238 * build_paths 

1239 * parameters 

1240 * html_static_paths 

1241 """ 

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

1243 with python_path_append(folds): 

1244 if fLOG: 

1245 fLOG( 

1246 "[_import_conf_extract_parameter] import from '{0}'".format(folds)) 

1247 try: 

1248 module_conf = execute_script_get_local_variables( 

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

1250 except Exception as ee: # pragma: no cover 

1251 raise HelpGenException( 

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

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

1254 if 'ERROR' in module_conf: 

1255 raise ImportError("\n" + module_conf['ERROR'] + "\n") 

1256 if len(module_conf) == 0: 

1257 raise ImportError("Unable to extract local variable from conf.py.") 

1258 

1259 if module_conf is None: 

1260 raise ImportError( # pragma: no cover 

1261 "Unable to import '{0}' which defines the help generation".format(newconf)) 

1262 thenewconf = dictionary_as_class(module_conf) 

1263 if fLOG: 

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

1265 "epkg_dictionary", "latex_elements", "imgmath_latex_preamble", "preamble")) 

1266 

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

1268 all_tocs.extend(tocs) 

1269 

1270 # we store the html_static_path in html_static_paths 

1271 html_static_path = thenewconf.__dict__.get( 

1272 "html_static_path", "phdoc_static") 

1273 if isinstance(html_static_path, list): 

1274 html_static_path = html_static_path[0] 

1275 html_static_path = os.path.normpath( 

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

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

1278 raise FileNotFoundError( # pragma: no cover 

1279 "no static path:" + html_static_path) 

1280 html_static_paths.append(html_static_path) 

1281 build_paths.append( 

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

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

1284 parameters.append(pp)