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

2@file 

3@brief Helper for the setup 

4""" 

5 

6import os 

7import sys 

8import re 

9import warnings 

10import hashlib 

11import datetime 

12 

13 

14def get_available_setup_commands(): 

15 """ 

16 Returns the list of commands :epkg:`pyquickhelper` implements 

17 or allows. 

18 """ 

19 commands = ['bdist_egg', 'bdist_msi', 'bdist_wheel', 'bdist_wininst', 'build27', 

20 'build_ext', 'build_script', 'build_sphinx', 'clean_pyd', 'clean_space', 

21 'copy27', 'copy_dist', 'copy_sphinx', 'history', 'lab', 'local_pypi', 

22 'notebook', 'publish', 'publish_doc', 'register', 'run27', 'run_pylint', 

23 'sdist', 'setup_hook', 'setupdep', 'test_local_pypi', 

24 'unittests', 'unittests_GUI', 'unittests_LONG', 'unittests_SKIP', 

25 'upload_docs', 'write_version', 'local_jenkins'] 

26 return commands 

27 

28 

29def get_available_build_commands(): 

30 """ 

31 Returns commands which build the package. 

32 """ 

33 return {"sdist", "bdist_wheel", "publish", "publish_doc", "register", 

34 "upload_docs", "bdist_wininst", "build_ext"} 

35 

36 

37def available_commands_list(argv): 

38 """ 

39 Checks that on command handled by pyquickhelper is part of the arguments. 

40 

41 @param argv ``sys.args`` 

42 @return bool 

43 """ 

44 commands = get_available_setup_commands() 

45 for c in commands: 

46 if c in argv: 

47 return True 

48 return False 

49 

50 

51def process_standard_options_for_setup( 

52 argv, file_or_folder, project_var_name, module_name=None, unittest_modules=None, 

53 pattern_copy=None, 

54 requirements=None, port=8067, blog_list=None, default_engine_paths=None, 

55 extra_ext=None, add_htmlhelp=False, setup_params=None, coverage_options=None, 

56 coverage_exclude_lines=None, func_sphinx_begin=None, func_sphinx_end=None, 

57 additional_notebook_path=None, additional_local_path=None, copy_add_ext=None, 

58 nbformats=("ipynb", "html", "python", "rst", "slides", 

59 "pdf", "github"), 

60 layout=None, direct_call=False, 

61 additional_ut_path=None, 

62 skip_function=None, covtoken=None, hook_print=True, 

63 stdout=None, stderr=None, use_run_cmd=False, filter_warning=None, 

64 file_filter_pep8=None, github_owner=None, 

65 existing_history=None, coverage_root='src', 

66 fexclude=None, skip_issues=None, fLOG=None): 

67 """ 

68 Processes the standard options the module pyquickhelper is 

69 able to process assuming the module which calls this function 

70 follows the same design as *pyquickhelper*, it will process the following 

71 options: 

72 

73 .. runpython:: 

74 

75 from pyquickhelper.pycode import process_standard_options_for_setup_help 

76 process_standard_options_for_setup_help("--help-commands") 

77 

78 @param argv = *sys.argv* 

79 @param file_or_folder file ``setup.py`` or folder which contains it 

80 @param project_var_name display name of the module 

81 @param module_name module name, None if equal to *project_var_name* 

82 (``import <module_name>``) 

83 @param unittest_modules modules added for the unit tests, 

84 see @see fn py3to2_convert_tree 

85 @param pattern_copy see @see fn py3to2_convert_tree 

86 @param requirements dependencies, fetched with a local pipy server from 

87 ``http://localhost:port/`` 

88 @param port port for the local pipy server 

89 @param blog_list list of blog to listen for this module 

90 (usually stored in ``module.__blog__``) 

91 @param default_engine_paths define the default location for python engine, 

92 should be dictionary *{ engine: path }*, see below. 

93 @param extra_ext extra file extension to process (add a page for each of them, 

94 ex ``["doc"]``) 

95 @param add_htmlhelp run HTML Help too (only on Windows) 

96 @param setup_params parameters send to @see fn call_setup_hook 

97 @param coverage_options see @see fn main_wrapper_tests 

98 @param coverage_exclude_lines see @see fn main_wrapper_tests 

99 @param func_sphinx_begin function called before the documentation generation, 

100 it gets the same parameters as this function (all named), 

101 use ``**args**`` 

102 @param func_sphinx_end function called after the documentation generation, 

103 it gets the same parameters as this function (all named), 

104 use ``**args**`` 

105 @param additional_notebook_path additional paths to add when launching the notebook 

106 @param additional_local_path additional paths to add when running a local command 

107 @param copy_add_ext additional file extensions to copy 

108 @param nbformats requested formats for the notebooks conversion 

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

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

111 if None --> ``["html", "pdf"]`` 

112 @param additional_ut_path additional paths to add when running unit tests 

113 @param skip_function function to skip unit tests, see @ee fn main_wrapper_tests 

114 @param covtoken token used when publishing coverage report to 

115 `codecov <https://codecov.io/>`_, 

116 more in @see fn main_wrapper_tests 

117 @param fLOG logging function 

118 @param hook_print enable, disable print when calling *_setup_hook* 

119 @param stdout redirect stdout for unit test if not None 

120 @param stderr redirect stderr for unit test if not None 

121 @param use_run_cmd to run the sphinx documentation with @see fn run_cmd and 

122 not ``os.system`` 

123 @param filter_warning see @see fn main_wrapper_tests 

124 @param file_filter_pep8 function to filter out files for which checking pep8 

125 (see @see fn remove_extra_spaces_folder) 

126 @param github_owner :epkg:`github` owner of the package 

127 @param existing_history existing history, retrieves existing issues stored 

128 in that file 

129 @param coverage_root see @see fn main_wrapper_tests 

130 @param direct_call @see fn generate_help_sphinx 

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

132 used to build the documentation 

133 @param skip_issues skip a given list of issues when building the history 

134 @return True (an option was processed) or False, 

135 the file ``setup.py`` should call function ``setup`` 

136 

137 The command ``build_script`` is used, the flag ``--private`` can be used to 

138 avoid producing scripts to publish the module on `Pypi <https://pypi.python.org/pypi>`_. 

139 

140 An example for *default_engine_paths*:: 

141 

142 default_engine_paths = { 

143 "windows": { 

144 "__PY35__": None, 

145 "__PY36_X64__": "c:\\Python365_x64", 

146 "__PY37_X64__": "c:\\Python372_x64", 

147 "__PY38_X64__": "c:\\Python387_x64", 

148 "__PY39_X64__": "c:\\Python391_x64", 

149 }, 

150 } 

151 

152 Parameters *coverage_options*, *coverage_exclude_lines*, *copy_add_ext* were added 

153 for function @see fn main_wrapper_tests. 

154 Parameter *unittest_modules* accepts a list of string and 2-uple. 

155 If it is a 2-uple, the first string is used to convert Python 3 code into Python 2 

156 (in case the local folder is different from the module name), 

157 the second string is used to add local path to the variable ``PYTHON_PATH``. 

158 If it is a single string, it means both name strings are equal. 

159 Parameters *func_sphinx_begin* and *func_sphinx_end* were added 

160 to pre-process or post-process the documentation. 

161 Parameter *additional_notebook_path* was added to specify some additional 

162 paths when preparing the script *auto_cmd_notebook.bat*. 

163 Parameters *layout*, *nbformats* were added for 

164 function @see fn generate_help_sphinx. 

165 The coverage computation can be disable by specifying 

166 ``coverage_options["disable_coverage"] = True``. 

167 Parameter *covtoken* was added to post the coverage report to :epkg:`codecov`. 

168 Option ``-e`` and ``-g`` were added to 

169 filter file by regular expressions (in with *e*, out with *g*). 

170 """ 

171 if module_name is not None and ( 

172 len(module_name) == 0 or module_name[0] == '_'): 

173 raise RuntimeError( 

174 "module cannot be empty or start with '_': %r." % module_name) 

175 if fLOG is None: # pragma: no cover 

176 from ..loghelper.flog import noLOG 

177 fLOG = noLOG 

178 if skip_function is None: # pragma: no cover 

179 from .utils_tests_private import default_skip_function 

180 skip_function = default_skip_function 

181 if pattern_copy is None: 

182 # delayed import 

183 from .default_regular_expression import _setup_pattern_copy 

184 pattern_copy = _setup_pattern_copy 

185 

186 if layout is None: 

187 layout = ["html", "pdf"] 

188 

189 if "--help" in argv or "--help-commands" in argv: 

190 process_standard_options_for_setup_help(argv) 

191 return not len(get_available_build_commands() & set(argv)) 

192 fLOG("[process_standard_options_for_setup]", argv) 

193 fLOG("[process_standard_options_for_setup] python version:", sys.version_info) 

194 

195 def process_argv_for_unittest(argv): 

196 if "-d" in argv: 

197 ld = argv.index("-d") 

198 if ld >= len(argv) - 1: 

199 raise ValueError( # pragma: no cover 

200 "Option -d should be follow by a duration in seconds.") 

201 d = float(argv[ld + 1]) 

202 else: 

203 d = None 

204 

205 if "-f" in argv: 

206 lf = argv.index("-f") 

207 if lf >= len(argv) - 1: 

208 raise ValueError( # pragma: no cover 

209 "Option -d should be follow by a duration in seconds.") 

210 f = argv[lf + 1] 

211 else: 

212 f = None 

213 

214 if "-e" in argv: 

215 le = argv.index("-e") 

216 if le >= len(argv) - 1: 

217 raise ValueError( # pragma: no cover 

218 "Option -e should be follow by a regular expression.") 

219 pattern = argv[le + 1] 

220 if len(pattern) >= 2 and pattern[0] == pattern[-1] == '"': 

221 pattern = pattern[1:-1] 

222 e = re.compile(pattern) 

223 else: 

224 e = None 

225 

226 if "-g" in argv: 

227 lg = argv.index("-g") 

228 if lg >= len(argv) - 1: 

229 raise ValueError( # pragma: no cover 

230 "Option -g should be follow by a regular expression.") 

231 pattern = argv[lg + 1] 

232 if len(pattern) >= 2 and pattern[0] == pattern[-1] == '"': 

233 pattern = pattern[1:-1] 

234 g = re.compile(pattern) 

235 else: 

236 g = None 

237 

238 if f is None and d is None and e is None and g is None: 

239 return skip_function # pragma: no cover 

240 

241 def ereg(name): 

242 return (e is None) or (e.search(name) is not None) 

243 

244 def greg(name): 

245 return (g is None) or (g.search(name) is None) 

246 

247 if f is not None: 

248 if d is not None: # pragma: no cover 

249 raise NotImplementedError( 

250 "Options -f and -d cannot be specified at the same time.") 

251 

252 def allow(name, code, duration): # pragma: no cover 

253 name = os.path.split(name)[-1] 

254 return f not in name and ereg(name) and greg(name) 

255 return allow # pragma: no cover 

256 else: 

257 # d is not None 

258 def skip_allowd(name, code, duration): 

259 name = os.path.split(name)[-1] 

260 cond = ( 

261 (duration is None or d is None or duration <= d) and 

262 ereg(name) and greg(name)) 

263 return not cond 

264 return skip_allowd 

265 

266 folder = file_or_folder if os.path.isdir( 

267 file_or_folder) else os.path.dirname(file_or_folder) 

268 unit_test_folder = os.path.join(folder, "_unittests") 

269 fLOG("unittest_modules={0}".format(unittest_modules)) 

270 

271 if unittest_modules is None: 

272 unittest_modules_py3to2 = None 

273 unittest_modules_script = None 

274 else: # pragma: no cover 

275 unittest_modules_py3to2 = [] 

276 unittest_modules_script = [] 

277 for mod in unittest_modules: 

278 if isinstance(mod, tuple): 

279 unittest_modules_py3to2.append(mod[0]) 

280 unittest_modules_script.append(mod[1]) 

281 else: 

282 unittest_modules_py3to2.append(mod) 

283 unittest_modules_script.append(mod) 

284 

285 # dump unit test coverage? 

286 

287 def dump_coverage_fct(full=True): 

288 mn = project_var_name if module_name is None else module_name 

289 full_path = _get_dump_default_path(folder, mn, argv) 

290 if full_path is None or full: 

291 return full_path 

292 else: 

293 sub = os.path.split(full_path)[0] 

294 sub = os.path.split(sub)[0] 

295 return sub 

296 

297 # starts interpreting the commands 

298 

299 if "clean_space" in argv: # pragma: no cover 

300 rem = clean_space_for_setup( 

301 file_or_folder, file_filter=file_filter_pep8) 

302 print("[clean_space] number of impacted files (pep8 + rst):", len(rem)) 

303 rem = clean_notebooks_for_numbers(file_or_folder) 

304 print("[clean_space] number of impacted notebooks:", len(rem)) 

305 return True 

306 

307 if "run_pylint" in argv: 

308 verbose = '-v' in argv 

309 pos = argv.index('run_pylint') 

310 ignores = [_[2:] for _ in argv if _[:2] == '-i'] 

311 ignores = None if len(ignores) == 0 else tuple(ignores) 

312 argv = [_ for _ in argv if _ not in ( 

313 '-v', '-') and not _.startswith('-i')] 

314 pattern = argv[pos + 1] if len(argv) > pos + 1 else ".*[.]py$" 

315 neg_pattern = argv[pos + 2] if len(argv) > pos + 2 else None 

316 print("[run_pylint] run_pylint for sources pattern='{0}' neg_pattern='{1}'".format( 

317 pattern, neg_pattern)) 

318 src_folder = os.path.join(folder, "src") 

319 if not os.path.exists(src_folder): 

320 src_folder = folder 

321 run_pylint_for_setup(src_folder, fLOG=print, pattern=pattern, 

322 verbose=verbose, pylint_ignore=ignores) 

323 print("[run_pylint] run_pylint for unittest") 

324 run_pylint_for_setup(os.path.join(folder, "_unittests"), 

325 fLOG=print, pattern=pattern, verbose=verbose, 

326 pylint_ignore=ignores) 

327 return True 

328 

329 elif 'history' in argv: # pragma: no cover 

330 dest = ' '.join(argv).split( # pylint: disable=C0207 

331 'history')[-1].strip() 

332 if not dest: 

333 dest = os.path.join(folder, 'HISTORY.rst') 

334 if existing_history is None: 

335 hfold = get_folder(file_or_folder) 

336 histo = os.path.join(hfold, 'HISTORY.rst') 

337 if os.path.exists(histo): 

338 existing_history = histo 

339 if existing_history is not None: 

340 print('[history] existing ', existing_history) 

341 print('[history] ', dest) 

342 build_history_from_setup( 

343 dest, owner=github_owner, module=project_var_name, 

344 existing_history=existing_history, 

345 skip_issues=skip_issues, fLOG=fLOG) 

346 return True 

347 

348 elif "write_version" in argv: 

349 fLOG("---- JENKINS BEGIN WRITE VERSION ----") 

350 write_version_for_setup(file_or_folder, module_name=module_name) 

351 fLOG("---- JENKINS BEGIN END VERSION ----") 

352 return True 

353 

354 elif "clean_pyd" in argv: 

355 clean_space_for_setup(file_or_folder) 

356 return True 

357 

358 elif "build_sphinx" in argv: 

359 # delayed import 

360 from .call_setup_hook import call_setup_hook 

361 try: 

362 from nbconvert.nbconvertapp import main as nbconvert_main 

363 if nbconvert_main is None: # pragma: no cover 

364 raise AttributeError("nbconvert_main is None") 

365 except AttributeError as e: # pragma: no cover 

366 raise ImportError( 

367 "Unable to import nbconvert, cannot generate the documentation") from e 

368 if setup_params is None: 

369 setup_params = {} 

370 out, err = call_setup_hook( 

371 folder, project_var_name if module_name is None else module_name, 

372 fLOG=fLOG, **setup_params) 

373 if len(err) > 0 and err != "no _setup_hook": 

374 raise Exception( # pragma: no cover 

375 "Unable to run _setup_hook\nOUT:\n{0}\n[setuperror]\n" 

376 "{1}".format(out, err)) 

377 

378 if func_sphinx_begin is not None: 

379 func_sphinx_begin( 

380 argv=argv, file_or_folder=file_or_folder, 

381 project_var_name=project_var_name, 

382 module_name=module_name, unittest_modules=unittest_modules, 

383 pattern_copy=pattern_copy, 

384 requirements=requirements, port=port, blog_list=blog_list, 

385 default_engine_paths=default_engine_paths, 

386 extra_ext=extra_ext, add_htmlhelp=add_htmlhelp, 

387 setup_params=setup_params, coverage_options=coverage_options, 

388 coverage_exclude_lines=coverage_exclude_lines, 

389 func_sphinx_begin=func_sphinx_begin, 

390 func_sphinx_end=func_sphinx_end, 

391 additional_notebook_path=additional_notebook_path, 

392 nbformats=nbformats, layout=layout, 

393 skip_function=skip_function, 

394 addition_ut_path=additional_ut_path, fLOG=fLOG) 

395 standard_help_for_setup( 

396 argv, file_or_folder, project_var_name, 

397 module_name=module_name, extra_ext=extra_ext, 

398 add_htmlhelp=add_htmlhelp, copy_add_ext=copy_add_ext, 

399 nbformats=nbformats, layout=layout, 

400 use_run_cmd=use_run_cmd, fLOG=fLOG, direct_call=direct_call, 

401 fexclude=fexclude) 

402 

403 if func_sphinx_end is not None: 

404 func_sphinx_end( 

405 argv=argv, file_or_folder=file_or_folder, 

406 project_var_name=project_var_name, 

407 module_name=module_name, unittest_modules=unittest_modules, 

408 pattern_copy=pattern_copy, 

409 requirements=requirements, port=port, blog_list=blog_list, 

410 default_engine_paths=default_engine_paths, 

411 extra_ext=extra_ext, add_htmlhelp=add_htmlhelp, 

412 setup_params=setup_params, coverage_options=coverage_options, 

413 coverage_exclude_lines=coverage_exclude_lines, 

414 func_sphinx_begin=func_sphinx_begin, 

415 func_sphinx_end=func_sphinx_end, 

416 additional_notebook_path=additional_notebook_path, 

417 nbformats=nbformats, layout=layout, 

418 skip_function=skip_function, 

419 addition_ut_path=additional_ut_path, fLOG=fLOG) 

420 

421 return True 

422 

423 elif "unittests" in argv: 

424 skip_f = process_argv_for_unittest(argv) 

425 run_unittests_for_setup( 

426 file_or_folder, setup_params=setup_params, 

427 coverage_options=coverage_options, 

428 coverage_exclude_lines=coverage_exclude_lines, 

429 additional_ut_path=additional_ut_path, 

430 skip_function=skip_f, covtoken=covtoken, 

431 hook_print=hook_print, stdout=stdout, stderr=stderr, 

432 filter_warning=filter_warning, dump_coverage=dump_coverage_fct(), 

433 add_coverage_folder=dump_coverage_fct(False), 

434 coverage_root=coverage_root, fLOG=fLOG) 

435 return True 

436 

437 elif "setup_hook" in argv: 

438 fLOG("---- JENKINS BEGIN SETUPHOOK ----") 

439 run_unittests_for_setup( 

440 file_or_folder, setup_params=setup_params, only_setup_hook=True, 

441 coverage_options=coverage_options, coverage_exclude_lines=coverage_exclude_lines, 

442 additional_ut_path=additional_ut_path, skip_function=skip_function, 

443 hook_print=hook_print, stdout=stdout, stderr=stderr, dump_coverage=dump_coverage_fct(), 

444 fLOG=fLOG) 

445 fLOG("---- JENKINS END SETUPHOOK ----") 

446 return True 

447 

448 elif "unittests_LONG" in argv: 

449 def skip_long(name, code, duration): 

450 return "test_LONG_" not in name 

451 run_unittests_for_setup( 

452 file_or_folder, skip_function=skip_long, setup_params=setup_params, 

453 coverage_options=coverage_options, coverage_exclude_lines=coverage_exclude_lines, 

454 additional_ut_path=additional_ut_path, hook_print=hook_print, 

455 stdout=stdout, stderr=stderr, dump_coverage=dump_coverage_fct(), 

456 fLOG=fLOG) 

457 return True 

458 

459 elif "unittests_SKIP" in argv: 

460 def skip_skip(name, code, duration): 

461 return "test_SKIP_" not in name 

462 run_unittests_for_setup( 

463 file_or_folder, skip_function=skip_skip, setup_params=setup_params, 

464 coverage_options=coverage_options, coverage_exclude_lines=coverage_exclude_lines, 

465 additional_ut_path=additional_ut_path, hook_print=hook_print, 

466 stdout=stdout, stderr=stderr, dump_coverage=dump_coverage_fct(), 

467 fLOG=fLOG) 

468 return True 

469 

470 elif "unittests_GUI" in argv: # pragma: no cover 

471 def skip_skip(name, code, duration): 

472 return "test_GUI_" not in name 

473 run_unittests_for_setup( 

474 file_or_folder, skip_function=skip_skip, setup_params=setup_params, 

475 coverage_options=coverage_options, coverage_exclude_lines=coverage_exclude_lines, 

476 additional_ut_path=additional_ut_path, hook_print=hook_print, 

477 stdout=stdout, stderr=stderr, dump_coverage=dump_coverage_fct(), 

478 fLOG=fLOG) 

479 return True 

480 

481 elif "build_script" in argv: 

482 # delayed import 

483 from .build_helper import get_extra_script_command, get_script_command, get_build_script 

484 

485 # script running setup.py 

486 script = get_build_script( 

487 project_var_name, requirements=requirements, port=port, 

488 default_engine_paths=default_engine_paths) 

489 binto = os.path.join(folder, "bin") 

490 if not os.path.exists(binto): 

491 os.mkdir(binto) 

492 with open(os.path.join(folder, "bin", "auto_unittest_setup_help.%s" % get_script_extension()), "w") as f: 

493 f.write(script) 

494 

495 for c in ("build_script", "clean_space", 

496 "write_version", "clean_pyd", 

497 "build_sphinx", "unittests", 

498 "unittests_LONG", "unittests_SKIP", "unittests_GUI", 

499 "unittests -d 5", "setup_hook", "copy27", 

500 "local_pypi", 'run_pylint'): 

501 sc = get_script_command( 

502 c, project_var_name, requirements=requirements, port=port, platform=sys.platform, 

503 default_engine_paths=default_engine_paths, additional_local_path=additional_local_path) 

504 cn = c.replace(" ", "_") 

505 with open(os.path.join(folder, "bin", "auto_setup_%s.%s" % (cn, get_script_extension())), "w") as f: 

506 f.write(sc) 

507 

508 # script running for a developper 

509 

510 for c in {"notebook", "publish", "publish_doc", "local_pypi", "run27", 

511 "build27", "setupdep", "copy_dist", 

512 "any_setup_command", "build_dist", 

513 "copy_sphinx", "lab", "history"}: 

514 if "--private" in argv and "publish" in c: 

515 # we skip this to avoid producing scripts for publish 

516 # functionalities 

517 continue 

518 sc = get_extra_script_command(c, project_var_name, requirements=requirements, 

519 port=port, platform=sys.platform, 

520 default_engine_paths=default_engine_paths, 

521 unit_test_folder=unit_test_folder, 

522 unittest_modules=unittest_modules_script, 

523 additional_notebook_path=additional_notebook_path, 

524 additional_local_path=additional_local_path) 

525 if sc is None: 

526 continue 

527 if c == "setupdep": 

528 folder_setup = os.path.join(folder, "build", "auto_setup") 

529 if not os.path.exists(folder_setup): 

530 os.makedirs(folder_setup) 

531 with open(os.path.join(folder_setup, "auto_setup_dep.py"), "w") as f: 

532 f.write(sc) 

533 else: 

534 with open(os.path.join(folder, "bin", "auto_cmd_%s.%s" % (c, get_script_extension())), "w") as f: 

535 f.write(sc) 

536 

537 # script for anybody 

538 write_module_scripts( 

539 folder, platform=sys.platform, blog_list=blog_list, default_engine_paths=default_engine_paths) 

540 

541 # pyproj for PTVS 

542 if sys.platform.startswith("win"): # pragma: no cover 

543 write_pyproj(folder) 

544 

545 return True 

546 

547 elif "copy27" in argv: 

548 # delayed import 

549 from .py3to2 import py3to2_convert_tree 

550 root = os.path.abspath(os.path.dirname(file_or_folder)) 

551 root = os.path.normpath(root) 

552 dest = os.path.join(root, "dist_module27") 

553 py3to2_convert_tree( 

554 root, dest, unittest_modules=unittest_modules_py3to2, pattern_copy=pattern_copy) 

555 return True 

556 

557 elif "local_pypi" in argv: # pragma: no cover 

558 # delayed import 

559 from ..filehelper import get_url_content_timeout 

560 url = "http://localhost:{0}/".format(port) 

561 content = get_url_content_timeout(url, timeout=5) 

562 if content is None or len(content) == 0: 

563 raise Exception("test failed for url: " + url) 

564 print(content) 

565 return True 

566 

567 elif 'local_jenkins' in argv: # pragma: no cover 

568 pos = argv.index("local_jenkins") 

569 user = argv[pos + 1] 

570 password = argv[pos + 2] 

571 if len(argv) > pos + 3: 

572 location = argv[pos + 3] 

573 else: 

574 if sys.platform.startswith("win"): # pragma: no cover 

575 location = "\\Jenkins" 

576 else: 

577 location = "somewhere/workspace" 

578 if len(argv) > pos + 4: 

579 server_url = argv[pos + 4] 

580 else: 

581 server_url = "http://localhost:8080/" 

582 from ..jenkinshelper import JenkinsExt, setup_jenkins_server_yml, default_jenkins_jobs 

583 modules = default_jenkins_jobs( 

584 github_owner=github_owner, module_name=project_var_name if module_name is None else module_name) 

585 key = "Python%d%d" % sys.version_info[:2] 

586 engines = {key: os.path.abspath(os.path.dirname(sys.executable))} 

587 js = JenkinsExt(server_url, user, password, engines=engines) 

588 setup_jenkins_server_yml(js, github=github_owner, modules=modules, fLOG=fLOG, overwrite=True, 

589 delete_first=False, location=location) 

590 return True 

591 

592 else: 

593 return False 

594 

595 

596def get_script_extension(): 

597 """ 

598 Returns the scripts extension based on the system it is running on. 

599 

600 @return bat or sh 

601 """ 

602 if sys.platform.startswith("win"): # pragma: no cover 

603 return "bat" 

604 return "sh" 

605 

606 

607def get_folder(file_or_folder): 

608 """ 

609 Returns the folder which contains ``setup.py``. 

610 

611 @param file_or_folder file ``setup.py`` or folder which contains it 

612 @return folder 

613 """ 

614 file_or_folder = os.path.abspath(file_or_folder) 

615 if os.path.isdir(file_or_folder): 

616 folder = file_or_folder 

617 else: 

618 folder = os.path.dirname(file_or_folder) 

619 return folder 

620 

621 

622def write_version_for_setup(file_or_folder, exc=False, module_name=None): 

623 """ 

624 Extracts the version number, 

625 the function writes the files ``version.txt`` in this folder. 

626 

627 @param file_or_folder file ``setup.py`` or folder which contains it 

628 @param exc raises an exception if cannot look into git folder 

629 @param module_name module name 

630 @return version number 

631 """ 

632 # delayed import to speed up import of pycode 

633 from ..loghelper.pyrepo_helper import SourceRepository 

634 src = SourceRepository(commandline=True) 

635 ffolder = get_folder(file_or_folder) 

636 try: 

637 version = src.version(ffolder) 

638 except Exception as e: # pragma: no cover 

639 if exc: 

640 raise e 

641 return None 

642 if version in ["0", 0, None]: 

643 raise Exception( # pragma: no cover 

644 "issue with version {0}".format(version)) 

645 

646 # write version number 

647 if version is not None: 

648 with open(os.path.join(ffolder, "version.txt"), "w") as f: 

649 f.write(str(version) + "\n") 

650 

651 modifies_init_file(ffolder, version, module_name=module_name) 

652 return version 

653 

654 

655def clean_space_for_setup(file_or_folder, file_filter=None): 

656 """ 

657 .. index:: pep8 

658 

659 Does some cleaning within the module, apply :epkg:`pep8` rules. 

660 

661 @param file_or_folder file ``setup.py`` or folder which contains it 

662 @param file_filter file filter (see @see fn remove_extra_spaces_folder) 

663 @return impacted files 

664 """ 

665 # delayed import 

666 from .code_helper import remove_extra_spaces_folder 

667 ffolder = get_folder(file_or_folder) 

668 rem = remove_extra_spaces_folder( 

669 ffolder, extensions=[".py", ".rst", ".md", ".bat", ".sh"], 

670 file_filter=file_filter) 

671 return rem 

672 

673 

674def clean_notebooks_for_numbers(file_or_folder): 

675 """ 

676 Upgrades notebooks to the latest format and 

677 cleans notebooks execution numbers and rearranges the JSON file. 

678 

679 @param file_or_folder file ``setup.py`` or folder which contains it 

680 @return impacted files 

681 

682 .. index:: notebooks 

683 """ 

684 from ..ipythonhelper.notebook_helper import upgrade_notebook, remove_execution_number 

685 from ..filehelper import explore_folder_iterfile 

686 ffolder = get_folder(file_or_folder) 

687 fold2 = os.path.normpath( 

688 os.path.join(ffolder, "_doc", "notebooks")) 

689 mod = [] 

690 for nbf in explore_folder_iterfile(fold2, pattern=".*[.]ipynb"): 

691 t = upgrade_notebook(nbf) 

692 if t: 

693 mod.append(nbf) # pragma: no cover 

694 # remove numbers 

695 s = remove_execution_number(nbf, nbf) 

696 if s: 

697 mod.append(nbf) 

698 return mod 

699 

700 

701def standard_help_for_setup(argv, file_or_folder, project_var_name, module_name=None, 

702 extra_ext=None, add_htmlhelp=False, copy_add_ext=None, 

703 nbformats=("ipynb", "html", "python", 

704 "rst", "slides", "pdf"), 

705 layout=None, use_run_cmd=False, direct_call=False, 

706 fexclude=None, fLOG=None): 

707 """ 

708 Standard function which generates help assuming they follow 

709 the same design as *pyquickhelper*. 

710 

711 @param argv it should be ``sys.argv`` 

712 @param file_or_folder file ``setup.py`` or folder which contains it 

713 @param project_var_name display name of the module 

714 @param module_name module name, None if equal to *project_var_name* 

715 (``import <module_name>``) 

716 @param extra_ext extra file extension to process (ex ``["doc"]``) 

717 @param add_htmlhelp run HTML Help too (only on Windows) 

718 @param copy_add_ext additional extension of files to copy 

719 @param nbformats notebooks format to generate 

720 @param layout layout for the documentation, if None --> ``["html", "pdf"]`` 

721 @param use_run_cmd use function @see fn run_cmd instead of ``os.system`` 

722 to build the documentation 

723 @param direct_call see @see fn generate_help_sphinx 

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

725 used to build the documentation 

726 @param fLOG logging function 

727 

728 The function outputs some information through function @see fn fLOG. 

729 

730 A page will be added for each extra file extension mentioned in *extra_ext* if 

731 some of these were found. 

732 """ 

733 if fLOG is None: # pragma: no cover 

734 from ..loghelper.flog import noLOG 

735 fLOG = noLOG 

736 if "--help" in argv: # pragma: no cover 

737 from ..helpgen.help_usage import get_help_usage 

738 print(get_help_usage()) 

739 else: 

740 from ..helpgen.sphinx_main import generate_help_sphinx 

741 

742 if layout is None: 

743 layout = ["html", "pdf"] # pragma: no cover 

744 if module_name is None: 

745 module_name = project_var_name 

746 

747 ffolder = get_folder(file_or_folder) 

748 source = os.path.join(ffolder, "_doc", "sphinxdoc", "source") 

749 

750 if not os.path.exists(source): 

751 raise FileNotFoundError( # pragma: no cover 

752 "you must get the source from GitHub to build the documentation,\nfolder {0} " 

753 "should exist\n(file_or_folder={1})\n(ffolder={2})\n(cwd={3})".format( 

754 source, file_or_folder, ffolder, os.getcwd())) 

755 

756 if "conf" in sys.modules: # pragma: no cover 

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

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

759 del sys.modules["conf"] 

760 

761 project_name = os.path.split( 

762 os.path.split(os.path.abspath(ffolder))[0])[-1] 

763 

764 generate_help_sphinx(project_name, module_name=module_name, layout=layout, 

765 extra_ext=extra_ext, nbformats=nbformats, add_htmlhelp=add_htmlhelp, 

766 copy_add_ext=copy_add_ext, fLOG=fLOG, root=ffolder, 

767 direct_call=direct_call, fexclude=fexclude) 

768 

769 

770def run_unittests_for_setup(file_or_folder, skip_function=None, setup_params=None, 

771 only_setup_hook=False, coverage_options=None, coverage_exclude_lines=None, 

772 additional_ut_path=None, covtoken=None, hook_print=True, stdout=None, 

773 stderr=None, filter_warning=None, dump_coverage=None, 

774 add_coverage_folder=None, coverage_root='src', fLOG=None): 

775 """ 

776 Runs the unit tests and computes the coverage, stores 

777 the results in ``_doc/sphinxdoc/source/coverage`` 

778 assuming the module follows the same design as *pyquickhelper*. 

779 

780 @param file_or_folder file ``setup.py`` or folder which contains it 

781 @param skip_function see @see fn main_wrapper_tests 

782 @param setup_params see @see fn main_wrapper_tests 

783 @param only_setup_hook see @see fn main_wrapper_tests 

784 @param coverage_options see @see fn main_wrapper_tests 

785 @param coverage_exclude_lines see @see fn main_wrapper_tests 

786 @param additional_ut_path see @see fn main_wrapper_tests 

787 @param covtoken see @see fn main_wrapper_tests 

788 @param hook_print see @see fn main_wrapper_tests 

789 @param stdout see @see fn main_wrapper_tests 

790 @param stderr see @see fn main_wrapper_tests 

791 @param filter_warning see @see fn main_wrapper_tests 

792 @param coverage_root see @see fn main_wrapper_tests 

793 @param dump_coverage location where to dump the coverage 

794 @param add_coverage_folder additional folder where to look for other coverage reports 

795 @param fLOG logging function 

796 

797 See function @see fn main_wrapper_tests. 

798 The coverage computation can be disabled by specifying 

799 ``coverage_options["disable_coverage"] = True``. 

800 *covtoken* was added to post the coverage report to 

801 `codecov <https://codecov.io/>`_. 

802 Parameter *dump_coverage* 

803 dumps the unit test coverage in another location. 

804 """ 

805 # delayed import 

806 from .tkinter_helper import fix_tkinter_issues_virtualenv 

807 from .utils_tests import main_wrapper_tests 

808 ffolder = get_folder(file_or_folder) 

809 funit = os.path.join(ffolder, "_unittests") 

810 if not os.path.exists(funit): 

811 raise FileNotFoundError( # pragma: no cover 

812 "You must get the whole source to run the unittests," 

813 "\nfolder {0} should exist".format(funit)) 

814 

815 if skip_function is None: # pragma: no cover 

816 from .utils_tests_private import default_skip_function 

817 skip_function = default_skip_function 

818 if fLOG is None: # pragma: no cover 

819 from ..loghelper.flog import noLOG 

820 fLOG = noLOG 

821 

822 fix_tkinter_issues_virtualenv(fLOG=fLOG) 

823 

824 cov = True 

825 if coverage_options: 

826 if "disable_coverage" in coverage_options and coverage_options["disable_coverage"]: 

827 cov = False 

828 

829 if dump_coverage is not None and not cov: 

830 dump_coverage = None 

831 

832 logfile = os.path.join(funit, "unittests.out") 

833 main_wrapper_tests( 

834 logfile, add_coverage=cov, skip_function=skip_function, setup_params=setup_params, 

835 only_setup_hook=only_setup_hook, coverage_options=coverage_options, 

836 coverage_exclude_lines=coverage_exclude_lines, additional_ut_path=additional_ut_path, 

837 covtoken=covtoken, hook_print=hook_print, stdout=stdout, stderr=stderr, 

838 filter_warning=filter_warning, dump_coverage=dump_coverage, 

839 add_coverage_folder=add_coverage_folder, coverage_root=coverage_root, fLOG=fLOG) 

840 

841 

842def copy27_for_setup(file_or_folder): # pragma: no cover 

843 """ 

844 Prepares a copy of the source for :epkg:`Python` 2.7, 

845 assuming the module follows the same design as *pyquickhelper*. 

846 

847 @param file_or_folder file ``setup.py`` or folder which contains it 

848 """ 

849 # delayed import 

850 from .py3to2 import py3to2_convert_tree 

851 root = get_folder(file_or_folder) 

852 root = os.path.normpath(root) 

853 dest = os.path.join(root, "dist_module27") 

854 py3to2_convert_tree(root, dest) 

855 

856 

857def write_pyproj(file_or_folder, location=None): 

858 """ 

859 Creates a project 

860 `pyproj <https://docs.microsoft.com/fr-fr/visualstudio/python/managing-python-projects-in-visual-studio>`_ 

861 to work with `PTVS <https://pytools.codeplex.com/>`_ 

862 (Python Tools for Visual Studio) 

863 

864 @param file_or_folder file ``setup.py`` or folder which contains it 

865 @param location if not None, stores the project into this folder 

866 

867 This functionality fails with :epkg:`Python` 2.7 (encoding). 

868 """ 

869 # delayed import 

870 from ..filehelper import explore_folder_iterfile 

871 from .build_helper import get_pyproj_project 

872 

873 avoid = ["dist", "build", "dist_module27", 

874 "_doc", "_virtualenv", "_virtualenv27", "_venv"] 

875 

876 def filter(name): 

877 if os.path.splitext(name)[-1] != ".py": 

878 return False 

879 if "temp_" in name: 

880 return False 

881 if "temp2_" in name: 

882 return False # pragma: no cover 

883 for a in avoid: 

884 if name.startswith(a + "\\"): 

885 return False # pragma: no cover 

886 if name.startswith(a + "/"): 

887 return False 

888 return True 

889 

890 root = get_folder(file_or_folder) 

891 root = os.path.normpath(root) 

892 name = os.path.split(root)[-1] 

893 if location is None: 

894 dest = os.path.join(root, "ptvs_project.pyproj") # pragma: no cover 

895 else: 

896 dest = os.path.join(location, "ptvs_project.pyproj") 

897 all_files = [os.path.relpath(_, root) 

898 for _ in explore_folder_iterfile(root)] 

899 all_files = [_ for _ in all_files if filter(_)] 

900 pyproj = get_pyproj_project(name, all_files) 

901 with open(dest, "w", encoding="utf8") as f: 

902 f.write(pyproj.strip()) 

903 

904 

905def process_standard_options_for_setup_help(argv): 

906 """ 

907 Prints the added options available through this module. 

908 """ 

909 commands = { 

910 "build27": "build the wheel for Python 2.7 (if available), it requires to run copy27 first", 

911 "build_script": "produce various scripts to build the module", 

912 "build_sphinx": "build the documentation", 

913 "build_wheel": "build the wheel", 

914 "clean_space": "clean unnecessary spaces in the code, applies :epkg:`pycodestyle` on all files", 

915 "clean_pyd": "clean file ``*.pyd``", 

916 "copy27": "create a modified copy of the module to run on Python 2.7 (if available), it requires to run copy27 first", 

917 "copy_dist": "copy documentation to folder dist", 

918 "copy_sphinx": "modify and copy sources to _doc/sphinxdoc/source/<module>", 

919 "history": "builds the history of the package in RST", 

920 "local_jenkins": "sets up jobs on a local jenkins server", 

921 "run27": "run the unit tests for the Python 2.7", 

922 "run_pylint": "run pylint on the sources, allowed parameters <pattern> <neg_pattern>", 

923 "setup_hook": "call function setup_hook which initializes the module before running unit tests", 

924 "unittests": "run the unit tests which do not contain test_LONG, test_SKIP or test_GUI in their file name", 

925 "unittests_LONG": "run the unit tests which contain test_LONG their file name", 

926 "unittests_SKIP": "run the unit tests which contain test_SKIP their file name", 

927 "unittests_GUI": "run the unit tests which contain test_GUI their file name", 

928 "write_version": "write a file ``version.txt`` with the version number (assuming sources are host with git)", 

929 } 

930 

931 if "--help-commands" in argv: 

932 print("Commands processed by pyquickhelper:") 

933 for k, v in sorted(commands.items()): 

934 print(" {0}{1}{2}".format( 

935 k, " " * (len("copy27 ") - len(k)), v)) 

936 print() 

937 elif "--help" in argv: 

938 docu = 0 

939 for k, v in sorted(commands.items()): 

940 if k in argv: 

941 docu += 1 

942 

943 if docu == 0: # pragma: no cover 

944 print("pyquickhelper commands:") 

945 print() 

946 for k in sorted(commands): 

947 process_standard_options_for_setup_help(['--help', k]) 

948 print() 

949 else: 

950 for k, v in sorted(commands.items()): 

951 if k in argv: 

952 docu += 1 

953 print(" setup.py {0}{1}{2}".format( 

954 k, " " * (20 - len(k)), v)) 

955 if k == "unittests": 

956 print( 

957 "\n {0} [-d seconds] [-f file] [-e regex] [-g regex]\n\n {1}".format(k, v)) 

958 print( 

959 " -d seconds run all unit tests for which predicted duration is below a given threshold.") 

960 print( 

961 " -f file run all unit tests in file (do not use the full path)") 

962 print( 

963 " -e regex run all unit tests files matching the regular expression") 

964 print( 

965 " -g regex run all unit tests files not matching the regular expression") 

966 print() 

967 elif k == "local_jenkins": # pragma: no cover 

968 print() 

969 print( 

970 " {0} user password [location] [server]".format(k)) 

971 print(" default location is somewhere/workspace") 

972 print(" default server is http://localhost:8080/") 

973 print() 

974 

975 

976def write_module_scripts(folder, platform=sys.platform, blog_list=None, 

977 default_engine_paths=None, command=None): 

978 """ 

979 Writes a couple of scripts which allow a user to be faster on some tasks 

980 or to easily get information about the module. 

981 

982 @param folder where to write the script 

983 @param platform platform 

984 @param blog_list blog list to follow, should be attribute ``__blog__`` of the module 

985 @param command None to generate scripts for all commands or a value in *[blog, doc]*. 

986 @param default_engine_paths default engines (or python distributions) 

987 @return list of written scripts 

988 

989 The function produces the following files: 

990 

991 * *auto_rss_list.xml*: list of rss stream to follow 

992 * *auto_rss_database.db3*: stores blog posts 

993 * *auto_rss_server.py*: runs a server which updates the scripts and runs a server. It also open the default browser. 

994 * *auto_rss_server.(bat|sh)*: run *auto_run_server.py*, the file on Linux might be missing if there is an equivalent python script 

995 

996 .. faqref:: 

997 :title: How to generate auto_rss_server.py? 

998 

999 The following code generates the script *auto_rss_local.py* 

1000 which runs a local server to read blog posts included 

1001 in the documentation (it uses module 

1002 `pyrsslocal <http://www.xavierdupre.fr/app/pyrsslocal/helpsphinx/index.html>`_):: 

1003 

1004 from pyquickhelper.pycode import write_module_scripts, __blog__ 

1005 write_module_scripts(".", blog_list=__blog__, command="blog") 

1006 """ 

1007 # delayed import 

1008 from .build_helper import get_script_module 

1009 default_set = {"blog", "doc"} 

1010 if command is not None: 

1011 if command not in default_set: 

1012 raise ValueError( # pragma: no cover 

1013 "command {0} is not available in {1}".format(command, default_set)) 

1014 commands = {command} 

1015 else: 

1016 commands = default_set 

1017 

1018 res = [] 

1019 for c in commands: 

1020 sc = get_script_module( 

1021 c, platform=sys.platform, blog_list=blog_list, default_engine_paths=default_engine_paths) 

1022 if sc is None: 

1023 continue # pragma: no cover 

1024 tobin = os.path.join(folder, "bin") 

1025 if not os.path.exists(tobin): 

1026 os.mkdir(tobin) 

1027 for item in sc: 

1028 if isinstance(item, tuple): 

1029 name = os.path.join(folder, "bin", item[0]) 

1030 with open(name, "w", encoding="utf8") as f: 

1031 f.write(item[1]) 

1032 res.append(name) 

1033 else: # pragma: no cover 

1034 name = os.path.join( 

1035 folder, "bin", "auto_run_%s.%s" % (c, get_script_extension())) 

1036 with open(name, "w") as f: 

1037 f.write(item) 

1038 res.append(name) 

1039 return res 

1040 

1041 

1042def _get_dump_default_path(location, module_name, argv): 

1043 """ 

1044 Proposes a default location to dump results about unit tests execution. 

1045 

1046 @param location location of the module 

1047 @param module_name module name 

1048 @param argv argument on the command line 

1049 @return location of the dump 

1050 

1051 The result is None for remote continuous integration. 

1052 """ 

1053 from . import is_travis_or_appveyor 

1054 if is_travis_or_appveyor(): 

1055 return None # pragma: no cover 

1056 hash = hash_list(argv) 

1057 setup = os.path.join(location, "setup.py") 

1058 if not os.path.exists(setup): 

1059 raise FileNotFoundError(setup) # pragma: no cover 

1060 fold = os.path.join(location, "..", "_coverage_dumps") 

1061 if not os.path.exists(fold): 

1062 os.mkdir(fold) 

1063 dt = datetime.datetime.now().strftime("%Y%m%dT%H%M") 

1064 if module_name is None: 

1065 raise ValueError("module_name cannot be None") # pragma: no cover 

1066 dump = os.path.join(fold, module_name, hash, dt) 

1067 if not os.path.exists(dump): 

1068 os.makedirs(dump) 

1069 return dump 

1070 

1071 

1072def hash_list(argv, size=8): 

1073 """ 

1074 Proposes a hash for the list of arguments. 

1075 

1076 @param argv list of arguments on the command line. 

1077 @param size size of the hash 

1078 @return string 

1079 """ 

1080 st = "--".join(map(str, argv)) 

1081 hash = hashlib.md5() 

1082 hash.update(st.encode("utf-8")) 

1083 res = hash.hexdigest() 

1084 if len(res) > 8: 

1085 return res[:8] 

1086 return res # pragma: no cover 

1087 

1088 

1089def build_history_from_setup(dest, owner, module, existing_history=None, 

1090 skip_issues=None, fLOG=None): # pragma: no cover 

1091 """ 

1092 Builds the history from :epkg:`github` and :epkg:`pypi`. 

1093 

1094 @param dest history will be written in this file 

1095 @param owner owner of the package on :epkg:`github` 

1096 @param module module name 

1097 @param existing_history existing history, retrieves existing issues stored 

1098 in that file 

1099 @param skip_issues skip a given list of issues when building the history 

1100 @param fLOG logging function 

1101 @return history 

1102 """ 

1103 # delayed import 

1104 from ..loghelper.history_helper import build_history, compile_history 

1105 if owner is None: 

1106 raise ValueError( # pragma: no cover 

1107 "owner must be specified.") 

1108 if "/" in owner: 

1109 raise ValueError( # pragma: no cover 

1110 "owner %r cannot contain '/'." % owner) 

1111 if fLOG is None: # pragma: no cover 

1112 from ..loghelper.flog import noLOG 

1113 fLOG = noLOG 

1114 repo = module 

1115 hist = build_history(owner, repo, unpublished=True, 

1116 existing_history=existing_history, 

1117 skip_issues=skip_issues, fLOG=fLOG) 

1118 output = compile_history(hist) 

1119 if dest is not None: 

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

1121 f.write(output) 

1122 return output 

1123 

1124 

1125def run_pylint_for_setup(folder, pattern=".*[.]py$", neg_pattern=None, 

1126 verbose=False, pylint_ignore=None, fLOG=None): 

1127 """ 

1128 Applies :epkg:`pylint` on subfolder *folder*. 

1129 

1130 @param folder folder where to look 

1131 @param pattern file to checks 

1132 @param neg_pattern negative pattern 

1133 @param pylint_ignore ignore these :epkg:`pylint` warnings or errors 

1134 @param verbose verbose 

1135 @param fLOG logging function 

1136 """ 

1137 # delayed import 

1138 from .utils_tests_helper import check_pep8 

1139 if fLOG is None: # pragma: no cover 

1140 from ..loghelper.flog import noLOG 

1141 fLOG = noLOG 

1142 check_pep8(folder, pattern=pattern, neg_pattern=neg_pattern, 

1143 pylint_ignore=pylint_ignore, verbose=verbose, fLOG=fLOG) 

1144 

1145 

1146def modifies_init_file(folder, version, module_name=None): 

1147 """ 

1148 Automatically modifies the init file. 

1149 

1150 @param folder where to find the init file 

1151 @param version commit number 

1152 @return modified init file 

1153 """ 

1154 def _update_version(v, nv): 

1155 vs = v.split('.') 

1156 if len(vs) <= 2: 

1157 return '.'.join(list(vs) + [nv]) 

1158 if len(vs) >= 3: 

1159 vs = list(vs) 

1160 vs[-1] = nv 

1161 return '.'.join(vs) 

1162 raise ValueError( # pragma: no cover 

1163 "Unable to process '{}' with new version '{}'.".format(v, nv)) 

1164 

1165 filename = None 

1166 if os.path.exists(folder): 

1167 if os.path.isdir(folder): 

1168 src = os.path.join(folder, 'src') 

1169 if module_name is None: 

1170 setu = os.path.join(folder, 'setup.py') 

1171 if not os.path.exists(setu): 

1172 raise FileNotFoundError( # pragma: no cover 

1173 "Unable to find 'setup.py' in '{}' and module_name is " 

1174 "None.".format(folder)) 

1175 reg = re.compile( 

1176 "(project_var_name = ['\\\"]([a-zA-Z][a-zA-Z_0-9]+)['\\\"])") 

1177 with open(setu, 'r', encoding='utf-8') as f: 

1178 cst = f.read() 

1179 find = reg.findall(cst) 

1180 if len(find) == 0: 

1181 raise FileNotFoundError( # pragma: no cover 

1182 "Unable to find 'project_var_name' in 'setup.py' in '{}' " 

1183 "and module_name is None.".format(folder)) 

1184 module_name = find[0][1] 

1185 if os.path.exists(src) and module_name is not None: 

1186 filename = os.path.join(src, module_name, '__init__.py') 

1187 elif os.path.exists(src) and module_name is not None: 

1188 filename = os.path.join(src, module_name, '__init__.py') 

1189 elif module_name is not None: 

1190 filename = os.path.join(folder, module_name, '__init__.py') 

1191 else: 

1192 raise FileNotFoundError( # pragma: no cover 

1193 "Unable to find '__init__.py' in '{}' (module_name is None).".format(folder)) 

1194 if not os.path.exists(filename): 

1195 raise FileNotFoundError( # pragma: no cover 

1196 "Unable to find '__init__.py' in '{}' (got '{}').".format(folder, filename)) 

1197 with open(filename, 'r', encoding='utf-8') as f: 

1198 content = f.read() 

1199 elif '__version__' in folder: 

1200 content = folder 

1201 else: 

1202 raise ValueError("Unable to process '{}'.".format(folder)) 

1203 

1204 reg = re.compile("(__version__ = ['\\\"]([0-9.]+)['\\\"])") 

1205 lines = content.split('\n') 

1206 modif = [] 

1207 rep = [] 

1208 for line in lines: 

1209 if line.startswith("__version__"): 

1210 find = reg.findall(line) 

1211 if len(find) != 1: 

1212 raise ValueError( # pragma: no cover 

1213 "Unable to find __version__ in '{}'".format(line)) 

1214 v = find[0][1] 

1215 nv = _update_version(v, str(version)) 

1216 newline = line.replace(v, nv) 

1217 modif.append(newline) 

1218 rep.append((line, newline)) 

1219 else: 

1220 modif.append(line) 

1221 if len(rep) == 0: 

1222 raise ValueError( # pragma: no cover 

1223 "Unable to find '__version__' in \n{}".format(content)) 

1224 

1225 content = '\n'.join(modif) 

1226 if filename is not None: 

1227 with open(filename, 'w', encoding='utf-8') as f: 

1228 f.write(content) 

1229 return content