Skip to content

Commit

Permalink
Add --save CLI command
Browse files Browse the repository at this point in the history
  • Loading branch information
vemel committed Nov 6, 2024
1 parent 607c5a2 commit 0279640
Show file tree
Hide file tree
Showing 13 changed files with 192 additions and 140 deletions.
1 change: 0 additions & 1 deletion .github/workflows/on_push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ jobs:
strategy:
matrix:
version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/newversion.svg?color=blue)](https://pypi.org/project/newversion)
[![Coverage](https://img.shields.io/codecov/c/github/vemel/newversion)](https://codecov.io/gh/vemel/newversion)

- [NewVersion - Your version manager](#newversion---your-version-manager)
- [NewVersion - PEP 440 version manager](#newversion---pep-440-version-manager)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
Expand Down Expand Up @@ -40,13 +40,14 @@ newversion # 0.0.0
newversion bump major # 1.0.0

# get package version from pyproject.toml, setup.cfg or setup.py
newversion package # 1.2.3
newversion package | newversion bump # 1.2.4
newversion package | newversion get minor # 2
newversion -p # 1.2.3
newversion -p bump # 1.2.4
newversion -p bump pre # 1.2.4rc1
newversion -p get minor # 2


# bump minor version and update package version
newversion package | newversion bump minor | newversion set_package
newversion -p --save bump minor

echo "1.2.3rc1" | newversion bump micro # 1.2.3
echo "1.2.3rc1" | newversion bump minor # 1.3.0
Expand Down
53 changes: 44 additions & 9 deletions newversion/cli_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import contextlib
import enum
import importlib.metadata
import logging
import sys
from collections.abc import Sequence
from dataclasses import dataclass
from typing import Any, Optional, Sequence, Type, Union
from typing import Any, Optional, Union

from newversion.constants import PACKAGE_NAME, Commands, VersionParts
from newversion.type_defs import ReleaseNonLocalTypeDef
Expand Down Expand Up @@ -54,8 +56,9 @@ class CLINamespace:
increment: int
other: Version
value: int
verbose: bool
quiet: bool
log_level: int
package: bool
save: bool


class EnumListAction(argparse.Action):
Expand All @@ -66,16 +69,20 @@ class EnumListAction(argparse.Action):
def __init__(
self,
*,
type: Type[enum.Enum], # noqa: A002
type: type[enum.Enum], # noqa: A002
option_strings: Sequence[str],
dest: str,
default: Optional[Sequence[enum.Enum]] = None,
required: bool = False,
choices: Optional[Sequence[enum.Enum]] = None,
nargs: Union[str, int, None] = None,
**kwargs: Optional[str],
) -> None:
self._enum_class = type
super_choices = choices if choices is not None else list(self._enum_class)
self._is_singular = default is not None and nargs is None
if self._is_singular:
nargs = "?"

super().__init__(
choices=tuple(e.value for e in super_choices),
Expand All @@ -84,6 +91,7 @@ def __init__(
dest=dest,
type=None,
required=required,
nargs=nargs,
**kwargs,
)

Expand All @@ -103,6 +111,12 @@ def __call__(
if isinstance(value, list):
value_list.extend([i for i in value if isinstance(i, str)])
enum_values = [self._enum_class(i) for i in value_list]

if self._is_singular:
enum_values = enum_values[0] if enum_values else self.default
else:
enum_values = enum_values or self.default

setattr(namespace, self.dest, enum_values)


Expand All @@ -128,6 +142,21 @@ def parse_args(args: Sequence[str]) -> CLINamespace:
default=None,
help="Input version, can be provided as a pipe-in as well.",
)
parser.add_argument(
"-p",
"--package",
action="store_true",
help="Get or set Python package version. Supports pyproject.toml, setup.cfg and setup.py.",
)
parser.add_argument(
"-s",
"--save",
action="store_true",
help=(
"Set output version as Python package version."
" Supports pyproject.toml, setup.cfg and setup.py."
),
)
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose logging")
parser.add_argument("-q", "--quiet", action="store_true", help="No logging")

Expand All @@ -148,7 +177,6 @@ def parse_args(args: Sequence[str]) -> CLINamespace:
VersionParts.BETA,
VersionParts.DEV,
],
nargs="?",
default=VersionParts.MICRO,
help=f"Release type. Default: {VersionParts.MICRO.value}",
)
Expand All @@ -165,6 +193,7 @@ def parse_args(args: Sequence[str]) -> CLINamespace:
"release",
type=VersionParts,
action=EnumListAction,
default=VersionParts.FULL,
help="Release type",
)

Expand Down Expand Up @@ -192,6 +221,7 @@ def parse_args(args: Sequence[str]) -> CLINamespace:
Commands.PACKAGE.value,
help="Get Python package version. Supports pyproject.toml, setup.cfg and setup.py.",
)

subparsers.add_parser(
Commands.SET_PACKAGE.value,
help="Set Python package version. Supports pyproject.toml, setup.cfg and setup.py.",
Expand Down Expand Up @@ -262,13 +292,18 @@ def parse_args(args: Sequence[str]) -> CLINamespace:
if result.version is None:
result.version = get_stdin()

log_level = logging.DEBUG if result.verbose else logging.INFO
if result.quiet:
log_level = logging.CRITICAL

return CLINamespace(
version=result.version,
command=Commands(result.command),
release=getattr(result, "release", "major"),
command=Commands(result.command) if result.command else Commands.UNKNOWN,
release=result.release if getattr(result, "release", "") else VersionParts.MICRO.value,
increment=getattr(result, "increment", 1),
other=getattr(result, "other", Version.zero()),
value=getattr(result, "value", 1),
verbose=result.verbose,
quiet=result.quiet,
log_level=log_level,
package=result.package,
save=result.save,
)
2 changes: 2 additions & 0 deletions newversion/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class VersionParts(enum.Enum):
MICRO = "micro"
LOCAL = "local"
EPOCH = "epoch"
FULL = "full"


class Commands(enum.Enum):
Expand All @@ -46,3 +47,4 @@ class Commands(enum.Enum):
STABLE = "stable"
PACKAGE = "package"
SET_PACKAGE = "set_package"
UNKNOWN = "unknown"
89 changes: 49 additions & 40 deletions newversion/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def __init__(
self,
version: Optional[Version] = None,
) -> None:
self._input = version if version is not None else Version.zero()
self.version = version if version is not None else Version.zero()

def command_get(
self,
Expand All @@ -43,39 +43,48 @@ def command_get(
raise ExecutorError(f"Unknown release name: {release}") from None

if release_part == VersionParts.LOCAL:
return self._input.local or ""
return self.version.local or ""

if release_part == VersionParts.PRE:
return f"{self._input.pre[0]}{self._input.pre[1]}" if self._input.pre else ""
return f"{self.version.pre[0]}{self.version.pre[1]}" if self.version.pre else ""

if release_part == VersionParts.POST:
return str(self._input.post) if self._input.post else "0"
return str(self.version.post) if self.version.post else "0"

if release_part == VersionParts.DEV:
return str(self._input.dev) if self._input.dev else "0"
return str(self.version.dev) if self.version.dev else "0"

if release_part == VersionParts.ALPHA:
return (
str(self._input.pre[-1]) if self._input.pre and self._input.pre[0] == "a" else "0"
str(self.version.pre[-1])
if self.version.pre and self.version.pre[0] == "a"
else "0"
)

if release_part == VersionParts.BETA:
return (
str(self._input.pre[-1]) if self._input.pre and self._input.pre[0] == "b" else "0"
str(self.version.pre[-1])
if self.version.pre and self.version.pre[0] == "b"
else "0"
)

if release_part == VersionParts.RC:
return (
str(self._input.pre[-1]) if self._input.pre and self._input.pre[0] == "rc" else "0"
str(self.version.pre[-1])
if self.version.pre and self.version.pre[0] == "rc"
else "0"
)

if release_part == VersionParts.EPOCH:
return str(self._input.epoch) if self._input.epoch else "0"
return str(self.version.epoch) if self.version.epoch else "0"

if release_part == VersionParts.FULL:
return str(self.version)

result = {
VersionParts.MAJOR: self._input.major,
VersionParts.MINOR: self._input.minor,
VersionParts.MICRO: self._input.micro,
VersionParts.MAJOR: self.version.major,
VersionParts.MINOR: self.version.minor,
VersionParts.MICRO: self.version.micro,
}[release_part]
return str(result)

Expand All @@ -100,23 +109,23 @@ def command_bump(self, release: ReleaseNonLocalTypeDef, increment: int) -> Versi
VersionParts.MINOR,
VersionParts.MICRO,
):
return self._input.bump_release(release_part.value, increment)
return self.version.bump_release(release_part.value, increment)

if release_part == VersionParts.PRE:
return self._input.bump_prerelease(increment)
return self.version.bump_prerelease(increment)

if release_part == VersionParts.POST:
return self._input.bump_postrelease(increment)
return self.version.bump_postrelease(increment)

if release_part in (
VersionParts.RC,
VersionParts.ALPHA,
VersionParts.BETA,
):
return self._input.bump_prerelease(increment, release_part.value)
return self.version.bump_prerelease(increment, release_part.value)

if release_part == VersionParts.DEV:
return self._input.bump_dev(increment)
return self.version.bump_dev(increment)

raise ExecutorError(f"Unknown release name: {release}")

Expand All @@ -137,33 +146,33 @@ def command_set(self, release: ReleaseNonLocalTypeDef, value: int) -> Version:
raise ExecutorError(f"Unknown release name: {release}") from None

if release_part == VersionParts.PRE:
if self._input.prerelease_type == VersionParts.ALPHA.value:
return self._input.replace(alpha=value)
if self._input.prerelease_type == VersionParts.BETA.value:
return self._input.replace(beta=value)
if self._input.prerelease_type == VersionParts.RC.value:
return self._input.replace(rc=value)
if self.version.prerelease_type == VersionParts.ALPHA.value:
return self.version.replace(alpha=value)
if self.version.prerelease_type == VersionParts.BETA.value:
return self.version.replace(beta=value)
if self.version.prerelease_type == VersionParts.RC.value:
return self.version.replace(rc=value)

return self._input.replace(rc=value)
return self.version.replace(rc=value)

if release_part == VersionParts.POST:
return self._input.replace(post=value)
return self.version.replace(post=value)
if release_part == VersionParts.EPOCH:
return self._input.replace(epoch=value)
return self.version.replace(epoch=value)
if release_part == VersionParts.MAJOR:
return self._input.replace(major=value)
return self.version.replace(major=value)
if release_part == VersionParts.MINOR:
return self._input.replace(minor=value)
return self.version.replace(minor=value)
if release_part == VersionParts.MICRO:
return self._input.replace(micro=value)
return self.version.replace(micro=value)
if release_part == VersionParts.ALPHA:
return self._input.replace(alpha=value)
return self.version.replace(alpha=value)
if release_part == VersionParts.BETA:
return self._input.replace(beta=value)
return self.version.replace(beta=value)
if release_part == VersionParts.RC:
return self._input.replace(rc=value)
return self.version.replace(rc=value)
if release_part == VersionParts.DEV:
return self._input.replace(dev=value)
return self.version.replace(dev=value)

raise ExecutorError(f"Unknown release name: {release}") from None

Expand All @@ -174,7 +183,7 @@ def command_stable(self) -> Version:
Returns:
A new Version.
"""
return self._input.get_stable()
return self.version.get_stable()

def command_is_stable(self) -> None:
"""
Expand All @@ -183,8 +192,8 @@ def command_is_stable(self) -> None:
Raises:
ExecutorError -- If it is not.
"""
if not self._input.is_stable:
raise ExecutorError(f"Version {self._input} is not stable")
if not self.version.is_stable:
raise ExecutorError(f"Version {self.version} is not stable")

def command_compare(self, command: OperatorTypeDef, other: Version) -> None:
"""
Expand All @@ -207,8 +216,8 @@ def command_compare(self, command: OperatorTypeDef, other: Version) -> None:
Commands.NE: (operator.ne, "equal to"),
}
op, message = commands[command_enum]
if not (op(self._input, other)):
raise ExecutorError(f"Version {self._input} is {message} {other}")
if not (op(self.version, other)):
raise ExecutorError(f"Version {self.version} is {message} {other}")

def command_get_version(self) -> Version:
"""
Expand All @@ -229,7 +238,7 @@ def command_get_version(self) -> Version:
except PackageVersionError as e:
raise ExecutorError(e) from None

def command_set_version(self) -> None:
def command_set_version(self, version: Version) -> None:
"""
Set the package version.
Expand All @@ -241,6 +250,6 @@ def command_set_version(self) -> None:
ExecutorError: If there is an error setting the package version.
"""
try:
PackageVersion(Path.cwd()).set(self._input)
PackageVersion(Path.cwd()).set(version)
except PackageVersionError as e:
raise ExecutorError(e) from None
Loading

0 comments on commit 0279640

Please sign in to comment.