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
« prev ^ index » next coverage.py v7.4.4, created at 2024-06-12 09:26 -0500
1"""bump-my-version Command line interface."""
3from pathlib import Path
4from typing import List, Optional
6import questionary
7import rich_click as click
8from click.core import Context
9from tomlkit import dumps
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
23logger = get_indented_logger(__name__)
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
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}
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.
254 ARGS may contain any of the following:
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.
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)
265 logger.info("Starting BumpVersion %s", __version__)
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 )
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
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()
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())
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
308 logger.dedent()
309 do_bump(version_part, new_version, config, found_config_file, dry_run)
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.
344 ARGS may contain one or more configuration attributes. For example:
346 - `bump-my-version show current_version`
348 - `bump-my-version show files.0.filename`
350 - `bump-my-version show scm_info.branch_name`
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)
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)
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.
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)
491 logger.info("Starting BumpVersion %s", __version__)
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 )
511 found_config_file = find_config_file(config_file)
512 config = get_configuration(found_config_file, **overrides)
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()
518 if no_configured_files:
519 config.excluded_paths = list(config.resolved_filemap.keys())
521 if files:
522 config.add_files(files)
523 config.included_paths = files
525 configured_files = [
526 ConfiguredFile(file_cfg, config.version_config, search, replace) for file_cfg in config.files_to_modify
527 ]
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
535 ctx = get_context(config, version, next_version)
537 modify_files(configured_files, version, next_version, ctx, dry_run)
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()
559 destination_config = create_configuration(destination, prompt)
561 if destination == "stdout":
562 print_info(dumps(destination_config))
563 else:
564 Path(destination).write_text(dumps(destination_config), encoding="utf-8")
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)