Coverage for /Users/OORDCOR/Documents/code/bump-my-version/bumpversion/cli.py: 0%

145 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-02-24 07:45 -0600

1"""bump-my-version Command line interface.""" 

2 

3from pathlib import Path 

4from typing import List, Optional 

5 

6import questionary 

7import rich_click as click 

8from click.core import Context 

9from tomlkit import dumps 

10 

11from bumpversion import __version__ 

12from bumpversion.aliases import AliasedGroup 

13from bumpversion.bump import do_bump 

14from bumpversion.config import get_configuration 

15from bumpversion.config.create import create_configuration 

16from bumpversion.config.files import find_config_file 

17from bumpversion.files import ConfiguredFile, modify_files 

18from bumpversion.show import do_show, log_list 

19from bumpversion.ui import get_indented_logger, print_info, print_warning, setup_logging 

20from bumpversion.utils import get_context, get_overrides 

21from bumpversion.visualize import visualize 

22 

23logger = get_indented_logger(__name__) 

24 

25 

26@click.group( 

27 cls=AliasedGroup, 

28 invoke_without_command=True, 

29 context_settings={ 

30 "ignore_unknown_options": True, 

31 "allow_interspersed_args": True, 

32 "help_option_names": ["-h", "--help"], 

33 }, 

34 add_help_option=False, 

35) 

36@click.version_option(version=__version__) 

37@click.pass_context 

38def cli(ctx: Context) -> None: 

39 """Version bump your Python project.""" 

40 if ctx.invoked_subcommand is None: 

41 ctx.invoke(bump, *ctx.args) 

42 

43 

44click.rich_click.OPTION_GROUPS = { 

45 "bumpversion bump": [ 

46 { 

47 "name": "Configuration", 

48 "options": [ 

49 "--config-file", 

50 "--current-version", 

51 "--new-version", 

52 "--parse", 

53 "--serialize", 

54 "--search", 

55 "--replace", 

56 "--no-configured-files", 

57 "--ignore-missing-files", 

58 "--ignore-missing-version", 

59 ], 

60 }, 

61 { 

62 "name": "Output", 

63 "options": ["--dry-run", "--verbose"], 

64 }, 

65 { 

66 "name": "Committing and tagging", 

67 "options": [ 

68 "--allow-dirty" "--commit", 

69 "--commit-args", 

70 "--message", 

71 "--tag", 

72 "--tag-name", 

73 "--tag-message", 

74 "--sign-tags", 

75 ], 

76 }, 

77 ] 

78} 

79 

80 

81@cli.command(context_settings={"ignore_unknown_options": True}) 

82@click.argument("args", nargs=-1, type=str) 

83@click.option( 

84 "--config-file", 

85 metavar="FILE", 

86 required=False, 

87 envvar="BUMPVERSION_CONFIG_FILE", 

88 type=click.Path(exists=True), 

89 help="Config file to read most of the variables from.", 

90) 

91@click.option( 

92 "-v", 

93 "--verbose", 

94 count=True, 

95 required=False, 

96 envvar="BUMPVERSION_VERBOSE", 

97 help="Print verbose logging to stderr. Can specify several times for more verbosity.", 

98) 

99@click.option( 

100 "--allow-dirty/--no-allow-dirty", 

101 default=None, 

102 required=False, 

103 envvar="BUMPVERSION_ALLOW_DIRTY", 

104 help="Don't abort if working directory is dirty, or explicitly abort if dirty.", 

105) 

106@click.option( 

107 "--current-version", 

108 metavar="VERSION", 

109 required=False, 

110 envvar="BUMPVERSION_CURRENT_VERSION", 

111 help="Version that needs to be updated", 

112) 

113@click.option( 

114 "--new-version", 

115 metavar="VERSION", 

116 required=False, 

117 envvar="BUMPVERSION_NEW_VERSION", 

118 help="New version that should be in the files", 

119) 

120@click.option( 

121 "--parse", 

122 metavar="REGEX", 

123 required=False, 

124 envvar="BUMPVERSION_PARSE", 

125 help="Regex parsing the version string", 

126) 

127@click.option( 

128 "--serialize", 

129 metavar="FORMAT", 

130 multiple=True, 

131 required=False, 

132 envvar="BUMPVERSION_SERIALIZE", 

133 help="How to format what is parsed back to a version", 

134) 

135@click.option( 

136 "--search", 

137 metavar="SEARCH", 

138 required=False, 

139 envvar="BUMPVERSION_SEARCH", 

140 help="Template for complete string to search", 

141) 

142@click.option( 

143 "--replace", 

144 metavar="REPLACE", 

145 required=False, 

146 envvar="BUMPVERSION_REPLACE", 

147 help="Template for complete string to replace", 

148) 

149@click.option( 

150 "--regex/--no-regex", 

151 default=None, 

152 envvar="BUMPVERSION_REGEX", 

153 help="Treat the search parameter as a regular expression or explicitly do not treat it as a regular expression.", 

154) 

155@click.option( 

156 "--no-configured-files", 

157 is_flag=True, 

158 envvar="BUMPVERSION_NO_CONFIGURED_FILES", 

159 help=( 

160 "Only replace the version in files specified on the command line, " 

161 "ignoring the files from the configuration file." 

162 ), 

163) 

164@click.option( 

165 "--ignore-missing-files", 

166 is_flag=True, 

167 envvar="BUMPVERSION_IGNORE_MISSING_FILES", 

168 help="Ignore any missing files when searching and replacing in files.", 

169) 

170@click.option( 

171 "--ignore-missing-version", 

172 is_flag=True, 

173 envvar="BUMPVERSION_IGNORE_MISSING_VERSION", 

174 help="Ignore any Version Not Found errors when searching and replacing in files.", 

175) 

176@click.option( 

177 "--dry-run", 

178 "-n", 

179 is_flag=True, 

180 envvar="BUMPVERSION_DRY_RUN", 

181 help="Don't write any files, just pretend.", 

182) 

183@click.option( 

184 "--commit/--no-commit", 

185 default=None, 

186 envvar="BUMPVERSION_COMMIT", 

187 help="Commit to version control", 

188) 

189@click.option( 

190 "--tag/--no-tag", 

191 default=None, 

192 envvar="BUMPVERSION_TAG", 

193 help="Create a tag in version control", 

194) 

195@click.option( 

196 "--sign-tags/--no-sign-tags", 

197 default=None, 

198 envvar="BUMPVERSION_SIGN_TAGS", 

199 help="Sign tags if created", 

200) 

201@click.option( 

202 "--tag-name", 

203 metavar="TAG_NAME", 

204 required=False, 

205 envvar="BUMPVERSION_TAG_NAME", 

206 help="Tag name (only works with --tag)", 

207) 

208@click.option( 

209 "--tag-message", 

210 metavar="TAG_MESSAGE", 

211 required=False, 

212 envvar="BUMPVERSION_TAG_MESSAGE", 

213 help="Tag message", 

214) 

215@click.option( 

216 "-m", 

217 "--message", 

218 metavar="COMMIT_MSG", 

219 required=False, 

220 envvar="BUMPVERSION_MESSAGE", 

221 help="Commit message", 

222) 

223@click.option( 

224 "--commit-args", 

225 metavar="COMMIT_ARGS", 

226 required=False, 

227 envvar="BUMPVERSION_COMMIT_ARGS", 

228 help="Extra arguments to commit command", 

229) 

230@click.option( 

231 "--list", 

232 "show_list", 

233 is_flag=True, 

234 help="List machine readable information", 

235) 

236def bump( 

237 # version_part: str, 

238 args: list, 

239 config_file: Optional[str], 

240 verbose: int, 

241 allow_dirty: Optional[bool], 

242 current_version: Optional[str], 

243 new_version: Optional[str], 

244 parse: Optional[str], 

245 serialize: Optional[List[str]], 

246 search: Optional[str], 

247 replace: Optional[str], 

248 regex: Optional[bool], 

249 no_configured_files: bool, 

250 ignore_missing_files: bool, 

251 ignore_missing_version: bool, 

252 dry_run: bool, 

253 commit: Optional[bool], 

254 tag: Optional[bool], 

255 sign_tags: Optional[bool], 

256 tag_name: Optional[str], 

257 tag_message: Optional[str], 

258 message: Optional[str], 

259 commit_args: Optional[str], 

260 show_list: bool, 

261) -> None: 

262 """ 

263 Change the version. 

264 

265 ARGS may contain any of the following: 

266 

267 VERSION_PART is the part of the version to increase, e.g. `minor`. 

268 Valid values include those given in the `--serialize` / `--parse` option. 

269 

270 FILES are additional file(s) to modify. 

271 If you want to rewrite only files specified on the command line, use with the 

272 `--no-configured-files` option. 

273 """ 

274 setup_logging(verbose) 

275 

276 logger.info("Starting BumpVersion %s", __version__) 

277 

278 overrides = get_overrides( 

279 allow_dirty=allow_dirty, 

280 current_version=current_version, 

281 parse=parse, 

282 serialize=serialize or None, 

283 search=search, 

284 replace=replace, 

285 commit=commit, 

286 tag=tag, 

287 sign_tags=sign_tags, 

288 tag_name=tag_name, 

289 tag_message=tag_message, 

290 message=message, 

291 commit_args=commit_args, 

292 ignore_missing_files=ignore_missing_files, 

293 ignore_missing_version=ignore_missing_version, 

294 regex=regex, 

295 ) 

296 

297 found_config_file = find_config_file(config_file) 

298 config = get_configuration(found_config_file, **overrides) 

299 if args: 

300 if args[0] not in config.parts.keys(): 

301 raise click.BadArgumentUsage(f"Unknown version part: {args[0]}") 

302 version_part = args[0] 

303 files = args[1:] 

304 else: 

305 version_part = None 

306 files = args 

307 

308 if show_list: 

309 print_warning("DEPRECATED: The --list option is deprecated and will be removed in a future version.") 

310 log_list(config, version_part, new_version) 

311 return 

312 

313 config.allow_dirty = allow_dirty if allow_dirty is not None else config.allow_dirty 

314 if not config.allow_dirty and config.scm_info.tool: 

315 config.scm_info.tool.assert_nondirty() 

316 

317 if no_configured_files: 

318 config.excluded_paths = list(config.resolved_filemap.keys()) 

319 

320 if files: 

321 config.add_files(files) 

322 config.included_paths = files 

323 

324 logger.dedent() 

325 do_bump(version_part, new_version, config, found_config_file, dry_run) 

326 

327 

328@cli.command() 

329@click.argument("args", nargs=-1, type=str) 

330@click.option( 

331 "--config-file", 

332 metavar="FILE", 

333 required=False, 

334 envvar="BUMPVERSION_CONFIG_FILE", 

335 type=click.Path(exists=True), 

336 help="Config file to read most of the variables from.", 

337) 

338@click.option( 

339 "-f", 

340 "--format", 

341 "format_", 

342 required=False, 

343 envvar="BUMPVERSION_FORMAT", 

344 type=click.Choice(["default", "yaml", "json"], case_sensitive=False), 

345 default="default", 

346 help="Config file to read most of the variables from.", 

347) 

348@click.option( 

349 "-i", 

350 "--increment", 

351 required=False, 

352 envvar="BUMPVERSION_INCREMENT", 

353 type=str, 

354 help="Increment the version part and add `new_version` to the configuration.", 

355) 

356def show(args: List[str], config_file: Optional[str], format_: str, increment: Optional[str]) -> None: 

357 """Show current configuration information.""" 

358 found_config_file = find_config_file(config_file) 

359 config = get_configuration(found_config_file) 

360 

361 if not args: 

362 do_show("all", config=config, format_=format_, increment=increment) 

363 else: 

364 do_show(*args, config=config, format_=format_, increment=increment) 

365 

366 

367@cli.command() 

368@click.argument("files", nargs=-1, type=str) 

369@click.option( 

370 "--config-file", 

371 metavar="FILE", 

372 required=False, 

373 envvar="BUMPVERSION_CONFIG_FILE", 

374 type=click.Path(exists=True), 

375 help="Config file to read most of the variables from.", 

376) 

377@click.option( 

378 "-v", 

379 "--verbose", 

380 count=True, 

381 required=False, 

382 envvar="BUMPVERSION_VERBOSE", 

383 help="Print verbose logging to stderr. Can specify several times for more verbosity.", 

384) 

385@click.option( 

386 "--allow-dirty/--no-allow-dirty", 

387 default=None, 

388 required=False, 

389 envvar="BUMPVERSION_ALLOW_DIRTY", 

390 help="Don't abort if working directory is dirty, or explicitly abort if dirty.", 

391) 

392@click.option( 

393 "--current-version", 

394 metavar="VERSION", 

395 required=False, 

396 envvar="BUMPVERSION_CURRENT_VERSION", 

397 help="Version that needs to be updated", 

398) 

399@click.option( 

400 "--new-version", 

401 metavar="VERSION", 

402 required=False, 

403 envvar="BUMPVERSION_NEW_VERSION", 

404 help="New version that should be in the files. If not specified, it will be None.", 

405) 

406@click.option( 

407 "--parse", 

408 metavar="REGEX", 

409 required=False, 

410 envvar="BUMPVERSION_PARSE", 

411 help="Regex parsing the version string", 

412) 

413@click.option( 

414 "--serialize", 

415 metavar="FORMAT", 

416 multiple=True, 

417 required=False, 

418 envvar="BUMPVERSION_SERIALIZE", 

419 help="How to format what is parsed back to a version", 

420) 

421@click.option( 

422 "--search", 

423 metavar="SEARCH", 

424 required=False, 

425 envvar="BUMPVERSION_SEARCH", 

426 help="Template for complete string to search", 

427) 

428@click.option( 

429 "--replace", 

430 metavar="REPLACE", 

431 required=False, 

432 envvar="BUMPVERSION_REPLACE", 

433 help="Template for complete string to replace", 

434) 

435@click.option( 

436 "--regex/--no-regex", 

437 default=False, 

438 envvar="BUMPVERSION_REGEX", 

439 help="Treat the search parameter as a regular expression or explicitly do not treat it as a regular expression.", 

440) 

441@click.option( 

442 "--no-configured-files", 

443 is_flag=True, 

444 envvar="BUMPVERSION_NO_CONFIGURED_FILES", 

445 help=( 

446 "Only replace the version in files specified on the command line, " 

447 "ignoring the files from the configuration file." 

448 ), 

449) 

450@click.option( 

451 "--ignore-missing-version", 

452 is_flag=True, 

453 envvar="BUMPVERSION_IGNORE_MISSING_VERSION", 

454 help="Ignore any Version Not Found errors when searching and replacing in files.", 

455) 

456@click.option( 

457 "--ignore-missing-files", 

458 is_flag=True, 

459 envvar="BUMPVERSION_IGNORE_MISSING_FILES", 

460 help="Ignore any missing files when searching and replacing in files.", 

461) 

462@click.option( 

463 "--dry-run", 

464 "-n", 

465 is_flag=True, 

466 envvar="BUMPVERSION_DRY_RUN", 

467 help="Don't write any files, just pretend.", 

468) 

469def replace( 

470 files: list, 

471 config_file: Optional[str], 

472 verbose: int, 

473 allow_dirty: Optional[bool], 

474 current_version: Optional[str], 

475 new_version: Optional[str], 

476 parse: Optional[str], 

477 serialize: Optional[List[str]], 

478 search: Optional[str], 

479 replace: Optional[str], 

480 regex: bool, 

481 no_configured_files: bool, 

482 ignore_missing_version: bool, 

483 ignore_missing_files: bool, 

484 dry_run: bool, 

485) -> None: 

486 """ 

487 Replace the version in files. 

488 

489 FILES are additional file(s) to modify. 

490 If you want to rewrite only files specified on the command line, use with the 

491 `--no-configured-files` option. 

492 """ 

493 setup_logging(verbose) 

494 

495 logger.info("Starting BumpVersion %s", __version__) 

496 

497 overrides = get_overrides( 

498 allow_dirty=allow_dirty, 

499 current_version=current_version, 

500 new_version=new_version, 

501 parse=parse, 

502 serialize=serialize or None, 

503 commit=False, 

504 tag=False, 

505 sign_tags=False, 

506 tag_name=None, 

507 tag_message=None, 

508 message=None, 

509 commit_args=None, 

510 ignore_missing_version=ignore_missing_version, 

511 ignore_missing_files=ignore_missing_files, 

512 regex=regex, 

513 ) 

514 

515 found_config_file = find_config_file(config_file) 

516 config = get_configuration(found_config_file, **overrides) 

517 

518 config.allow_dirty = allow_dirty if allow_dirty is not None else config.allow_dirty 

519 if not config.allow_dirty and config.scm_info.tool: 

520 config.scm_info.tool.assert_nondirty() 

521 

522 if no_configured_files: 

523 config.excluded_paths = list(config.resolved_filemap.keys()) 

524 

525 if files: 

526 config.add_files(files) 

527 config.included_paths = files 

528 

529 configured_files = [ 

530 ConfiguredFile(file_cfg, config.version_config, search, replace) for file_cfg in config.files_to_modify 

531 ] 

532 

533 version = config.version_config.parse(config.current_version) 

534 if new_version: 

535 next_version = config.version_config.parse(new_version) 

536 else: 

537 next_version = None 

538 

539 ctx = get_context(config, version, next_version) 

540 

541 modify_files(configured_files, version, next_version, ctx, dry_run) 

542 

543 

544@cli.command() 

545@click.option( 

546 "--prompt/--no-prompt", 

547 default=True, 

548 help="Ask the user questions about the configuration.", 

549) 

550@click.option( 

551 "--destination", 

552 default="stdout", 

553 help="Where to write the sample configuration.", 

554 type=click.Choice(["stdout", ".bumpversion.toml", "pyproject.toml"]), 

555) 

556def sample_config(prompt: bool, destination: str) -> None: 

557 """Print a sample configuration file.""" 

558 if prompt: 

559 destination = questionary.select( 

560 "Destination", choices=["stdout", ".bumpversion.toml", "pyproject.toml"], default=destination 

561 ).ask() 

562 

563 destination_config = create_configuration(destination, prompt) 

564 

565 if destination == "stdout": 

566 print_info(dumps(destination_config)) 

567 else: 

568 Path(destination).write_text(dumps(destination_config)) 

569 

570 

571@cli.command() 

572@click.argument("version", nargs=1, type=str, required=False, default="") 

573@click.option( 

574 "--config-file", 

575 metavar="FILE", 

576 required=False, 

577 envvar="BUMPVERSION_CONFIG_FILE", 

578 type=click.Path(exists=True), 

579 help="Config file to read most of the variables from.", 

580) 

581@click.option("--ascii", is_flag=True, help="Use ASCII characters only.") 

582def show_bump(version: str, config_file: Optional[str], ascii: bool) -> None: 

583 """Show the possible versions resulting from the bump subcommand.""" 

584 found_config_file = find_config_file(config_file) 

585 config = get_configuration(found_config_file) 

586 if not version: 

587 version = config.current_version 

588 box_style = "ascii" if ascii else "light" 

589 visualize(config=config, version_str=version, box_style=box_style)