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

139 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-06-12 09:26 -0500

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.bump import do_bump 

13from bumpversion.config import get_configuration 

14from bumpversion.config.create import create_configuration 

15from bumpversion.config.files import find_config_file 

16from bumpversion.context import get_context 

17from bumpversion.files import ConfiguredFile, modify_files 

18from bumpversion.show import do_show 

19from bumpversion.ui import get_indented_logger, print_info, setup_logging 

20from bumpversion.utils import get_overrides 

21from bumpversion.visualize import visualize 

22 

23logger = get_indented_logger(__name__) 

24 

25 

26@click.group( 

27 context_settings={ 

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

29 }, 

30 add_help_option=True, 

31) 

32@click.version_option(version=__version__) 

33@click.pass_context 

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

35 """Version bump your Python project.""" 

36 pass 

37 

38 

39click.rich_click.OPTION_GROUPS = { 

40 "bumpversion bump": [ 

41 { 

42 "name": "Configuration", 

43 "options": [ 

44 "--config-file", 

45 "--current-version", 

46 "--new-version", 

47 "--parse", 

48 "--serialize", 

49 "--search", 

50 "--replace", 

51 "--no-configured-files", 

52 "--ignore-missing-files", 

53 "--ignore-missing-version", 

54 ], 

55 }, 

56 { 

57 "name": "Output", 

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

59 }, 

60 { 

61 "name": "Committing and tagging", 

62 "options": [ 

63 "--allow-dirty", 

64 "--commit", 

65 "--commit-args", 

66 "--message", 

67 "--tag", 

68 "--tag-name", 

69 "--tag-message", 

70 "--sign-tags", 

71 ], 

72 }, 

73 ] 

74} 

75 

76 

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

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

79@click.option( 

80 "--config-file", 

81 metavar="FILE", 

82 required=False, 

83 envvar="BUMPVERSION_CONFIG_FILE", 

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

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

86) 

87@click.option( 

88 "-v", 

89 "--verbose", 

90 count=True, 

91 required=False, 

92 envvar="BUMPVERSION_VERBOSE", 

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

94) 

95@click.option( 

96 "--allow-dirty/--no-allow-dirty", 

97 default=None, 

98 required=False, 

99 envvar="BUMPVERSION_ALLOW_DIRTY", 

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

101) 

102@click.option( 

103 "--current-version", 

104 metavar="VERSION", 

105 required=False, 

106 envvar="BUMPVERSION_CURRENT_VERSION", 

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

108) 

109@click.option( 

110 "--new-version", 

111 metavar="VERSION", 

112 required=False, 

113 envvar="BUMPVERSION_NEW_VERSION", 

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

115) 

116@click.option( 

117 "--parse", 

118 metavar="REGEX", 

119 required=False, 

120 envvar="BUMPVERSION_PARSE", 

121 help="Regex parsing the version string", 

122) 

123@click.option( 

124 "--serialize", 

125 metavar="FORMAT", 

126 multiple=True, 

127 required=False, 

128 envvar="BUMPVERSION_SERIALIZE", 

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

130) 

131@click.option( 

132 "--search", 

133 metavar="SEARCH", 

134 required=False, 

135 envvar="BUMPVERSION_SEARCH", 

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

137) 

138@click.option( 

139 "--replace", 

140 metavar="REPLACE", 

141 required=False, 

142 envvar="BUMPVERSION_REPLACE", 

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

144) 

145@click.option( 

146 "--regex/--no-regex", 

147 default=None, 

148 envvar="BUMPVERSION_REGEX", 

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

150) 

151@click.option( 

152 "--no-configured-files", 

153 is_flag=True, 

154 envvar="BUMPVERSION_NO_CONFIGURED_FILES", 

155 help=( 

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

157 "ignoring the files from the configuration file." 

158 ), 

159) 

160@click.option( 

161 "--ignore-missing-files/--no-ignore-missing-files", 

162 default=None, 

163 envvar="BUMPVERSION_IGNORE_MISSING_FILES", 

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

165) 

166@click.option( 

167 "--ignore-missing-version/--no-ignore-missing-version", 

168 default=None, 

169 envvar="BUMPVERSION_IGNORE_MISSING_VERSION", 

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

171) 

172@click.option( 

173 "--dry-run", 

174 "-n", 

175 is_flag=True, 

176 envvar="BUMPVERSION_DRY_RUN", 

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

178) 

179@click.option( 

180 "--commit/--no-commit", 

181 default=None, 

182 envvar="BUMPVERSION_COMMIT", 

183 help="Commit to version control", 

184) 

185@click.option( 

186 "--tag/--no-tag", 

187 default=None, 

188 envvar="BUMPVERSION_TAG", 

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

190) 

191@click.option( 

192 "--sign-tags/--no-sign-tags", 

193 default=None, 

194 envvar="BUMPVERSION_SIGN_TAGS", 

195 help="Sign tags if created", 

196) 

197@click.option( 

198 "--tag-name", 

199 metavar="TAG_NAME", 

200 required=False, 

201 envvar="BUMPVERSION_TAG_NAME", 

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

203) 

204@click.option( 

205 "--tag-message", 

206 metavar="TAG_MESSAGE", 

207 required=False, 

208 envvar="BUMPVERSION_TAG_MESSAGE", 

209 help="Tag message", 

210) 

211@click.option( 

212 "-m", 

213 "--message", 

214 metavar="COMMIT_MSG", 

215 required=False, 

216 envvar="BUMPVERSION_MESSAGE", 

217 help="Commit message", 

218) 

219@click.option( 

220 "--commit-args", 

221 metavar="COMMIT_ARGS", 

222 required=False, 

223 envvar="BUMPVERSION_COMMIT_ARGS", 

224 help="Extra arguments to commit command", 

225) 

226def bump( 

227 # version_part: str, 

228 args: list, 

229 config_file: Optional[str], 

230 verbose: int, 

231 allow_dirty: Optional[bool], 

232 current_version: Optional[str], 

233 new_version: Optional[str], 

234 parse: Optional[str], 

235 serialize: Optional[List[str]], 

236 search: Optional[str], 

237 replace: Optional[str], 

238 regex: Optional[bool], 

239 no_configured_files: bool, 

240 ignore_missing_files: bool, 

241 ignore_missing_version: bool, 

242 dry_run: bool, 

243 commit: Optional[bool], 

244 tag: Optional[bool], 

245 sign_tags: Optional[bool], 

246 tag_name: Optional[str], 

247 tag_message: Optional[str], 

248 message: Optional[str], 

249 commit_args: Optional[str], 

250) -> None: 

251 """ 

252 Change the version. 

253 

254 ARGS may contain any of the following: 

255 

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

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

258 

259 FILES are additional file(s) to modify. 

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

261 `--no-configured-files` option. 

262 """ 

263 setup_logging(verbose) 

264 

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

266 

267 overrides = get_overrides( 

268 allow_dirty=allow_dirty, 

269 current_version=current_version, 

270 parse=parse, 

271 serialize=serialize or None, 

272 search=search, 

273 replace=replace, 

274 commit=commit, 

275 tag=tag, 

276 sign_tags=sign_tags, 

277 tag_name=tag_name, 

278 tag_message=tag_message, 

279 message=message, 

280 commit_args=commit_args, 

281 ignore_missing_files=ignore_missing_files, 

282 ignore_missing_version=ignore_missing_version, 

283 regex=regex, 

284 ) 

285 

286 found_config_file = find_config_file(config_file) 

287 config = get_configuration(found_config_file, **overrides) 

288 if args: 288 ↛ 294line 288 didn't jump to line 294, because the condition on line 288 was never false

289 if args[0] not in config.parts.keys(): 289 ↛ 290line 289 didn't jump to line 290, because the condition on line 289 was never true

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

291 version_part = args[0] 

292 files = args[1:] 

293 else: 

294 version_part = None 

295 files = args 

296 

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

298 if not config.allow_dirty and config.scm_info.tool: 298 ↛ 299line 298 didn't jump to line 299, because the condition on line 298 was never true

299 config.scm_info.tool.assert_nondirty() 

300 

301 if no_configured_files: 301 ↛ 302line 301 didn't jump to line 302, because the condition on line 301 was never true

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

303 

304 if files: 304 ↛ 305line 304 didn't jump to line 305, because the condition on line 304 was never true

305 config.add_files(files) 

306 config.included_paths = files 

307 

308 logger.dedent() 

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

310 

311 

312@cli.command() 

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

314@click.option( 

315 "--config-file", 

316 metavar="FILE", 

317 required=False, 

318 envvar="BUMPVERSION_CONFIG_FILE", 

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

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

321) 

322@click.option( 

323 "-f", 

324 "--format", 

325 "format_", 

326 required=False, 

327 envvar="BUMPVERSION_FORMAT", 

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

329 default="default", 

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

331) 

332@click.option( 

333 "-i", 

334 "--increment", 

335 required=False, 

336 envvar="BUMPVERSION_INCREMENT", 

337 type=str, 

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

339) 

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

341 """ 

342 Show current configuration information. 

343 

344 ARGS may contain one or more configuration attributes. For example: 

345 

346 - `bump-my-version show current_version` 

347 

348 - `bump-my-version show files.0.filename` 

349 

350 - `bump-my-version show scm_info.branch_name` 

351 

352 - `bump-my-version show current_version scm_info.distance_to_latest_tag` 

353 """ 

354 found_config_file = find_config_file(config_file) 

355 config = get_configuration(found_config_file) 

356 

357 if not args: 

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

359 else: 

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

361 

362 

363@cli.command() 

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

365@click.option( 

366 "--config-file", 

367 metavar="FILE", 

368 required=False, 

369 envvar="BUMPVERSION_CONFIG_FILE", 

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

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

372) 

373@click.option( 

374 "-v", 

375 "--verbose", 

376 count=True, 

377 required=False, 

378 envvar="BUMPVERSION_VERBOSE", 

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

380) 

381@click.option( 

382 "--allow-dirty/--no-allow-dirty", 

383 default=None, 

384 required=False, 

385 envvar="BUMPVERSION_ALLOW_DIRTY", 

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

387) 

388@click.option( 

389 "--current-version", 

390 metavar="VERSION", 

391 required=False, 

392 envvar="BUMPVERSION_CURRENT_VERSION", 

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

394) 

395@click.option( 

396 "--new-version", 

397 metavar="VERSION", 

398 required=False, 

399 envvar="BUMPVERSION_NEW_VERSION", 

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

401) 

402@click.option( 

403 "--parse", 

404 metavar="REGEX", 

405 required=False, 

406 envvar="BUMPVERSION_PARSE", 

407 help="Regex parsing the version string", 

408) 

409@click.option( 

410 "--serialize", 

411 metavar="FORMAT", 

412 multiple=True, 

413 required=False, 

414 envvar="BUMPVERSION_SERIALIZE", 

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

416) 

417@click.option( 

418 "--search", 

419 metavar="SEARCH", 

420 required=False, 

421 envvar="BUMPVERSION_SEARCH", 

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

423) 

424@click.option( 

425 "--replace", 

426 metavar="REPLACE", 

427 required=False, 

428 envvar="BUMPVERSION_REPLACE", 

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

430) 

431@click.option( 

432 "--regex/--no-regex", 

433 default=False, 

434 envvar="BUMPVERSION_REGEX", 

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

436) 

437@click.option( 

438 "--no-configured-files", 

439 is_flag=True, 

440 envvar="BUMPVERSION_NO_CONFIGURED_FILES", 

441 help=( 

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

443 "ignoring the files from the configuration file." 

444 ), 

445) 

446@click.option( 

447 "--ignore-missing-version", 

448 is_flag=True, 

449 envvar="BUMPVERSION_IGNORE_MISSING_VERSION", 

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

451) 

452@click.option( 

453 "--ignore-missing-files", 

454 is_flag=True, 

455 envvar="BUMPVERSION_IGNORE_MISSING_FILES", 

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

457) 

458@click.option( 

459 "--dry-run", 

460 "-n", 

461 is_flag=True, 

462 envvar="BUMPVERSION_DRY_RUN", 

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

464) 

465def replace( 

466 files: list, 

467 config_file: Optional[str], 

468 verbose: int, 

469 allow_dirty: Optional[bool], 

470 current_version: Optional[str], 

471 new_version: Optional[str], 

472 parse: Optional[str], 

473 serialize: Optional[List[str]], 

474 search: Optional[str], 

475 replace: Optional[str], 

476 regex: bool, 

477 no_configured_files: bool, 

478 ignore_missing_version: bool, 

479 ignore_missing_files: bool, 

480 dry_run: bool, 

481) -> None: 

482 """ 

483 Replace the version in files. 

484 

485 FILES are additional file(s) to modify. 

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

487 `--no-configured-files` option. 

488 """ 

489 setup_logging(verbose) 

490 

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

492 

493 overrides = get_overrides( 

494 allow_dirty=allow_dirty, 

495 current_version=current_version, 

496 new_version=new_version, 

497 parse=parse, 

498 serialize=serialize or None, 

499 commit=False, 

500 tag=False, 

501 sign_tags=False, 

502 tag_name=None, 

503 tag_message=None, 

504 message=None, 

505 commit_args=None, 

506 ignore_missing_version=ignore_missing_version, 

507 ignore_missing_files=ignore_missing_files, 

508 regex=regex, 

509 ) 

510 

511 found_config_file = find_config_file(config_file) 

512 config = get_configuration(found_config_file, **overrides) 

513 

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

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

516 config.scm_info.tool.assert_nondirty() 

517 

518 if no_configured_files: 

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

520 

521 if files: 

522 config.add_files(files) 

523 config.included_paths = files 

524 

525 configured_files = [ 

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

527 ] 

528 

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

530 if new_version: 

531 next_version = config.version_config.parse(new_version) 

532 else: 

533 next_version = None 

534 

535 ctx = get_context(config, version, next_version) 

536 

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

538 

539 

540@cli.command() 

541@click.option( 

542 "--prompt/--no-prompt", 

543 default=True, 

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

545) 

546@click.option( 

547 "--destination", 

548 default="stdout", 

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

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

551) 

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

553 """Print a sample configuration file.""" 

554 if prompt: 

555 destination = questionary.select( 

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

557 ).ask() 

558 

559 destination_config = create_configuration(destination, prompt) 

560 

561 if destination == "stdout": 

562 print_info(dumps(destination_config)) 

563 else: 

564 Path(destination).write_text(dumps(destination_config), encoding="utf-8") 

565 

566 

567@cli.command() 

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

569@click.option( 

570 "--config-file", 

571 metavar="FILE", 

572 required=False, 

573 envvar="BUMPVERSION_CONFIG_FILE", 

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

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

576) 

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

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

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

580 found_config_file = find_config_file(config_file) 

581 config = get_configuration(found_config_file) 

582 if not version: 

583 version = config.current_version 

584 box_style = "ascii" if ascii else "light" 

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