-
-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
49 changed files
with
3,707 additions
and
116 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
"""Main entrypoint for module.""" | ||
from bumpversion.cli import cli | ||
|
||
cli() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
"""Utilities for handling command aliases.""" | ||
from typing import List | ||
|
||
import rich_click as click | ||
from click import Context | ||
from rich_click.rich_group import RichGroup | ||
|
||
|
||
class AliasedGroup(RichGroup): | ||
""" | ||
This following example implements a subclass of Group that accepts a prefix for a command. | ||
If there were a command called ``push``, it would accept ``pus`` as an alias (so long as it was unique) | ||
""" | ||
|
||
def get_command(self, ctx: Context, cmd_name: str) -> None: | ||
"""Given a context and a command name, this returns a Command object if it exists or returns None.""" | ||
rv = click.Group.get_command(self, ctx, cmd_name) | ||
if rv is not None: | ||
return rv | ||
matches = [x for x in self.list_commands(ctx) if x.startswith(cmd_name)] | ||
if not matches: | ||
return None | ||
elif len(matches) == 1: | ||
return click.Group.get_command(self, ctx, matches[0]) | ||
ctx.fail(f"Too many matches: {', '.join(sorted(matches))}") | ||
|
||
def resolve_command(self, ctx: Context, args: List[str]) -> tuple: | ||
"""Find the command and make sure the full command name is returned.""" | ||
# always return the full command name | ||
_, cmd, args = super().resolve_command(ctx, args) | ||
return cmd.name, cmd, args |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
""" | ||
Automatically detect the true Python type of a string and cast it to the correct type. | ||
Based on https://github.com/cgreer/cgAutoCast/blob/master/cgAutoCast.py | ||
""" | ||
|
||
import contextlib | ||
from typing import Any | ||
|
||
|
||
def boolify(s: str) -> bool: | ||
"""Convert a string to a boolean.""" | ||
if s in {"True", "true"}: | ||
return True | ||
if s in {"False", "false"}: | ||
return False | ||
raise ValueError("Not Boolean Value!") | ||
|
||
|
||
def noneify(s: str) -> None: | ||
"""Convert a string to None.""" | ||
if s == "None": | ||
return None | ||
raise ValueError("Not None Value!") | ||
|
||
|
||
def listify(s: str) -> list: | ||
""" | ||
Convert a string representation of a list into list of homogenous basic types. | ||
Type of elements in list is determined via first element. Successive elements are | ||
cast to that type. | ||
Args: | ||
s: String representation of a list. | ||
Raises: | ||
ValueError: If string does not represent a list. | ||
TypeError: If string does not represent a list of homogenous basic types. | ||
Returns: | ||
List of homogenous basic types. | ||
""" | ||
if "," not in s and "\n" not in s: | ||
raise ValueError("Not a List") | ||
|
||
# derive the type of the variable | ||
str_list = s.strip().split(",") if "," in s else s.strip().split("\n") | ||
element_caster = str | ||
for caster in (boolify, int, float, noneify, element_caster): | ||
with contextlib.suppress(ValueError): | ||
caster(str_list[0]) # type: ignore[operator] | ||
element_caster = caster # type: ignore[assignment] | ||
break | ||
# cast all elements | ||
try: | ||
return [element_caster(x) for x in str_list] | ||
except ValueError as e: | ||
raise TypeError("Autocasted list must be all same type") from e | ||
|
||
|
||
def autocast_value(var: Any) -> Any: | ||
""" | ||
Guess the string representation of the variable's type. | ||
Args: | ||
var: Value to autocast. | ||
Returns: | ||
The autocasted value. | ||
""" | ||
if not isinstance(var, str): # don't need to guess non-string types | ||
return var | ||
|
||
# guess string representation of var | ||
for caster in (boolify, int, float, noneify, listify): | ||
with contextlib.suppress(ValueError): | ||
return caster(var) # type: ignore[operator] | ||
|
||
return var |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
"""Version changing methods.""" | ||
import logging | ||
import shlex | ||
from pathlib import Path | ||
from typing import TYPE_CHECKING, ChainMap, List, Optional | ||
|
||
if TYPE_CHECKING: # pragma: no-coverage | ||
from bumpversion.files import ConfiguredFile | ||
from bumpversion.version_part import Version | ||
|
||
from bumpversion.config import Config, update_config_file | ||
from bumpversion.exceptions import ConfigurationError | ||
from bumpversion.utils import get_context, key_val_string | ||
|
||
logger = logging.getLogger("bumpversion") | ||
|
||
|
||
def get_next_version( | ||
current_version: "Version", config: Config, version_part: Optional[str], new_version: Optional[str] | ||
) -> "Version": | ||
""" | ||
Bump the version_part to the next value. | ||
Args: | ||
current_version: The current version | ||
config: The current configuration | ||
version_part: Optional part of the version to bump | ||
new_version: Optional specific version to bump to | ||
Returns: | ||
The new version | ||
Raises: | ||
ConfigurationError: If it can't generate the next version. | ||
""" | ||
if new_version: | ||
next_version = config.version_config.parse(new_version) | ||
elif version_part: | ||
logger.info("Attempting to increment part '%s'", version_part) | ||
next_version = current_version.bump(version_part, config.version_config.order) | ||
else: | ||
raise ConfigurationError("Unable to get the next version.") | ||
|
||
logger.info("Values are now: %s", key_val_string(next_version.values)) | ||
return next_version | ||
|
||
|
||
def do_bump( | ||
version_part: Optional[str], | ||
new_version: Optional[str], | ||
config: Config, | ||
config_file: Optional[Path] = None, | ||
dry_run: bool = False, | ||
) -> None: | ||
""" | ||
Bump the version_part to the next value or set the version to new_version. | ||
Args: | ||
version_part: The version part to bump | ||
new_version: The explicit version to set | ||
config: The configuration to use | ||
config_file: The configuration file to update | ||
dry_run: True if the operation should be a dry run | ||
""" | ||
from bumpversion.files import modify_files, resolve_file_config | ||
|
||
ctx = get_context(config) | ||
version = config.version_config.parse(config.current_version) | ||
next_version = get_next_version(version, config, version_part, new_version) | ||
next_version_str = config.version_config.serialize(next_version, ctx) | ||
logger.info("New version will be '%s'", next_version_str) | ||
|
||
ctx = get_context(config, version, next_version) | ||
|
||
configured_files = resolve_file_config(config.files, config.version_config) | ||
|
||
modify_files(configured_files, version, next_version, ctx, dry_run) | ||
|
||
update_config_file(config_file, config.current_version, next_version_str, dry_run) | ||
|
||
commit_and_tag(config, config_file, configured_files, ctx, dry_run) | ||
|
||
|
||
def commit_and_tag( | ||
config: Config, | ||
config_file: Optional[Path], | ||
configured_files: List["ConfiguredFile"], | ||
ctx: ChainMap, | ||
dry_run: bool = False, | ||
) -> None: | ||
""" | ||
Commit and tag the changes, if a tool is configured. | ||
Args: | ||
config: The configuration | ||
config_file: The configuration file to include in the commit, if it exists | ||
configured_files: A list of files to commit | ||
ctx: The context used to render the tag and tag message | ||
dry_run: True if the operation should be a dry run | ||
""" | ||
if not config.scm_info.tool: | ||
return | ||
|
||
extra_args = shlex.split(config.commit_args) if config.commit_args else [] | ||
|
||
commit_files = {f.path for f in configured_files} | ||
if config_file: | ||
commit_files |= {str(config_file)} | ||
|
||
config.scm_info.tool.commit_to_scm(list(commit_files), config, ctx, extra_args, dry_run) | ||
config.scm_info.tool.tag_in_scm(config, ctx, dry_run) |
Oops, something went wrong.