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

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

2

3 from pathlib import Path

4 from typing import List, Optional

5

6 import questionary

7 import rich_click as click

8 from click.core import Context

9 from tomlkit import dumps

10

11 from bumpversion import __version__

12 from bumpversion.aliases import AliasedGroup

13 from bumpversion.bump import do_bump

14 from bumpversion.config import get_configuration

15 from bumpversion.config.create import create_configuration

16 from bumpversion.config.files import find_config_file

17 from bumpversion.files import ConfiguredFile, modify_files

18 from bumpversion.show import do_show, log_list

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

20 from bumpversion.utils import get_context, get_overrides

21 from bumpversion.visualize import visualize

22

23 logger = 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

38 def 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

44 click.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)

236 def 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)

356 def 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)

469 def 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)

556 def 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.")

582 def 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)