cli usability improvements

This commit is contained in:
Manuel Barkhau 2020-10-08 20:36:58 +00:00
parent 86a321b287
commit 3efb72dd3c
5 changed files with 143 additions and 79 deletions

View file

@ -131,7 +131,7 @@ jobs = 4
output-format = colorized
# Maximum number of locals for function / method body
max-locals = 25
max-locals = 20
# Maximum number of arguments for function / method
max-args = 12

View file

@ -100,6 +100,52 @@ def _validate_release_tag(tag: typ.Optional[str]) -> None:
sys.exit(1)
def _validate_flags(
raw_pattern: str,
major : bool,
minor : bool,
patch : bool,
) -> None:
if "{" in raw_pattern and "}" in raw_pattern:
# only validate for new style patterns
return
valid = True
if major and "MAJOR" not in raw_pattern:
logger.error(f"Flag --major is not applicable to pattern '{raw_pattern}'")
valid = False
if minor and "MINOR" not in raw_pattern:
logger.error(f"Flag --minor is not applicable to pattern '{raw_pattern}'")
valid = False
if patch and "PATCH" not in raw_pattern:
logger.error(f"Flag --patch is not applicable to pattern '{raw_pattern}'")
valid = False
if not valid:
sys.exit(1)
def _log_no_change(subcmd: str, version_pattern: str, old_version: str) -> None:
msg = (
f"Version did not change: '{old_version}'. "
f"Invalid version and/or pattern '{version_pattern}'."
)
logger.error(msg)
is_semver = "{semver}" in version_pattern or (
"MAJOR" in version_pattern and "MAJOR" in version_pattern and "PATCH" in version_pattern
)
if is_semver:
logger.warning(f"pycalver {subcmd} [--major/--minor/--patch] required for use with SemVer.")
else:
available_flags = [
"--" + part.lower() for part in ['MAJOR', 'MINOR', 'PATCH'] if part in version_pattern
]
if available_flags:
available_flags_str = "/".join(available_flags)
logger.info(f"Perhaps try: pycalver {subcmd} {available_flags_str} ")
@click.group()
@click.version_option(version="v202010.1041-beta")
@click.help_option()
@ -151,6 +197,7 @@ def test(
tag = release # use internal naming convention
_validate_release_tag(tag)
_validate_flags(raw_pattern, major, minor, patch)
_date = _validate_date(date, pin_date)
new_version = incr_dispatch(
@ -165,7 +212,7 @@ def test(
date=_date,
)
if new_version is None:
logger.error(f"Invalid version '{old_version}' and/or pattern '{raw_pattern}'.")
_log_no_change('test', raw_pattern, old_version)
sys.exit(1)
pep440_version = version.to_pep440(new_version)
@ -395,7 +442,7 @@ def incr_dispatch(
logger.info("regex = " + regexfmt.pyexpr_regex(pattern.regexp.pattern))
if has_v1_part:
return v1version.incr(
new_version = v1version.incr(
old_version,
raw_pattern=raw_pattern,
tag=tag,
@ -407,7 +454,7 @@ def incr_dispatch(
date=date,
)
else:
return v2version.incr(
new_version = v2version.incr(
old_version,
raw_pattern=raw_pattern,
tag=tag,
@ -419,6 +466,15 @@ def incr_dispatch(
date=date,
)
if new_version is None:
return None
elif pkg_resources.parse_version(new_version) <= pkg_resources.parse_version(old_version):
logger.error("Invariant violated: New version must be greater than old version ")
logger.error(f" Result: '{new_version}' > '{old_version}' -> False")
return None
else:
return new_version
def _bump(
cfg : config.Config,
@ -625,16 +681,7 @@ def bump(
)
if new_version is None:
is_semver = "{semver}" in cfg.version_pattern or (
"MAJOR" in cfg.version_pattern
and "MAJOR" in cfg.version_pattern
and "PATCH" in cfg.version_pattern
)
has_semver_inc = major or minor or patch
if is_semver and not has_semver_inc:
logger.warning("bump --major/--minor/--patch required when using semver.")
else:
logger.error(f"Invalid version '{old_version}' and/or pattern '{cfg.version_pattern}'.")
_log_no_change('bump', cfg.version_pattern, old_version)
sys.exit(1)
logger.info(f"Old Version: {old_version}")

View file

@ -102,65 +102,26 @@ FieldValues = typ.Dict[FieldKey, MatchGroupStr]
VersionInfoKW = typ.Dict[str, typ.Union[str, int, None]]
def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
"""Parse normalized V2VersionInfo from groups of a matched pattern.
def _parse_calendar_info(field_values: FieldValues) -> version.V2CalendarInfo:
"""Parse normalized V2CalendarInfo from groups of a matched pattern.
>>> vinfo = _parse_version_info({'year_y': "2018", 'month': "11", 'bid': "0099"})
>>> (vinfo.year_y, vinfo.month, vinfo.quarter, vinfo.bid, vinfo.tag)
(2018, 11, 4, '0099', 'final')
>>> vinfo = _parse_version_info({'year_y': "18", 'month': "11"})
>>> (vinfo.year_y, vinfo.month, vinfo.quarter)
(2018, 11, 4)
>>> vinfo = _parse_version_info({'year_y': "2018", 'doy': "11", 'bid': "099", 'tag': "beta"})
>>> (vinfo.year_y, vinfo.month, vinfo.dom, vinfo.doy, vinfo.bid, vinfo.tag)
(2018, 1, 11, 11, '099', 'beta')
>>> vinfo = _parse_version_info({'year_y': "2018", 'month': "6", 'dom': "15"})
>>> (vinfo.year_y, vinfo.month, vinfo.dom, vinfo.doy)
(2018, 6, 15, 166)
>>> vinfo = _parse_version_info({'major': "1", 'minor': "23", 'patch': "45"})
>>> (vinfo.major, vinfo.minor, vinfo.patch)
(1, 23, 45)
>>> vinfo = _parse_version_info({'major': "1", 'minor': "023", 'patch': "0045"})
>>> (vinfo.major, vinfo.minor, vinfo.patch)
(1, 23, 45)
>>> vinfo = _parse_version_info({'year_y': "2021", 'week_w': "02"})
>>> (vinfo.year_y, vinfo.week_w)
>>> cinfo = _parse_version_info({'year_y': "2021", 'week_w': "02"})
>>> (cinfo.year_y, cinfo.week_w)
(2021, 2)
>>> vinfo = _parse_version_info({'year_y': "2021", 'week_u': "02"})
>>> (vinfo.year_y, vinfo.week_u)
>>> cinfo = _parse_version_info({'year_y': "2021", 'week_u': "02"})
>>> (cinfo.year_y, cinfo.week_u)
(2021, 2)
>>> vinfo = _parse_version_info({'year_g': "2021", 'week_v': "02"})
>>> (vinfo.year_g, vinfo.week_v)
>>> cinfo = _parse_version_info({'year_g': "2021", 'week_v': "02"})
>>> (cinfo.year_g, cinfo.week_v)
(2021, 2)
>>> vinfo = _parse_version_info({'year_y': "2021", 'month': "01", 'dom': "03"})
>>> (vinfo.year_y, vinfo.month, vinfo.dom, vinfo.tag)
(2021, 1, 3, 'final')
>>> (vinfo.year_y, vinfo.week_w, vinfo.year_y, vinfo.week_u,vinfo.year_g, vinfo.week_v)
>>> cinfo = _parse_version_info({'year_y': "2021", 'month': "01", 'dom': "03"})
>>> (cinfo.year_y, cinfo.month, cinfo.dom)
(2021, 1, 3)
>>> (cinfo.year_y, cinfo.week_w, cinfo.year_y, cinfo.week_u,cinfo.year_g, cinfo.week_v)
(2021, 0, 2021, 1, 2020, 53)
"""
# pylint:disable=dangerous-default-value; We don't mutate args, mypy would fail if we did.
for key in field_values:
assert key in VALID_FIELD_KEYS, key
fvals = field_values
tag = fvals.get('tag' ) or ""
pytag = fvals.get('pytag') or ""
if tag and not pytag:
pytag = version.PEP440_TAG_BY_RELEASE[tag]
elif pytag and not tag:
tag = version.RELEASE_BY_PEP440_TAG[pytag]
if not tag:
tag = "final"
date: typ.Optional[dt.date] = None
year_y: MaybeInt = int(fvals['year_y']) if 'year_y' in fvals else None
@ -205,16 +166,7 @@ def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
if quarter is None and month:
quarter = version.quarter_from_month(month)
# NOTE (mb 2020-09-18): If a part is optional, fvals[<field>] may be None
major = int(fvals.get('major') or 0)
minor = int(fvals.get('minor') or 0)
patch = int(fvals.get('patch') or 0)
num = int(fvals.get('num' ) or 0)
bid = fvals['bid'] if 'bid' in fvals else "1000"
inc0 = int(fvals.get('inc0') or 0)
inc1 = int(fvals.get('inc1') or 1)
vinfo = version.V2VersionInfo(
return version.V2CalendarInfo(
year_y=year_y,
year_g=year_g,
quarter=quarter,
@ -224,6 +176,74 @@ def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
week_w=week_w,
week_u=week_u,
week_v=week_v,
)
def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
"""Parse normalized V2VersionInfo from groups of a matched pattern.
>>> vinfo = _parse_version_info({'year_y': "2018", 'month': "11", 'bid': "0099"})
>>> (vinfo.year_y, vinfo.month, vinfo.quarter, vinfo.bid, vinfo.tag)
(2018, 11, 4, '0099', 'final')
>>> vinfo = _parse_version_info({'year_y': "18", 'month': "11"})
>>> (vinfo.year_y, vinfo.month, vinfo.quarter)
(2018, 11, 4)
>>> vinfo = _parse_version_info({'year_y': "2018", 'doy': "11", 'bid': "099", 'tag': "beta"})
>>> (vinfo.year_y, vinfo.month, vinfo.dom, vinfo.doy, vinfo.bid, vinfo.tag)
(2018, 1, 11, 11, '099', 'beta')
>>> vinfo = _parse_version_info({'year_y': "2018", 'month': "6", 'dom': "15"})
>>> (vinfo.year_y, vinfo.month, vinfo.dom, vinfo.doy)
(2018, 6, 15, 166)
>>> vinfo = _parse_version_info({'major': "1", 'minor': "23", 'patch': "45"})
>>> (vinfo.major, vinfo.minor, vinfo.patch)
(1, 23, 45)
>>> vinfo = _parse_version_info({'major': "1", 'minor': "023", 'patch': "0045"})
>>> (vinfo.major, vinfo.minor, vinfo.patch, vinfo.tag)
(1, 23, 45, 'final')
"""
# pylint:disable=dangerous-default-value; We don't mutate args, mypy would fail if we did.
for key in field_values:
assert key in VALID_FIELD_KEYS, key
cinfo = _parse_calendar_info(field_values)
fvals = field_values
tag = fvals.get('tag' ) or ""
pytag = fvals.get('pytag') or ""
if tag and not pytag:
pytag = version.PEP440_TAG_BY_RELEASE[tag]
elif pytag and not tag:
tag = version.RELEASE_BY_PEP440_TAG[pytag]
if not tag:
tag = "final"
# NOTE (mb 2020-09-18): If a part is optional, fvals[<field>] may be None
major = int(fvals.get('major') or 0)
minor = int(fvals.get('minor') or 0)
patch = int(fvals.get('patch') or 0)
num = int(fvals.get('num' ) or 0)
bid = fvals['bid'] if 'bid' in fvals else "1000"
inc0 = int(fvals.get('inc0') or 0)
inc1 = int(fvals.get('inc1') or 1)
return version.V2VersionInfo(
year_y=cinfo.year_y,
year_g=cinfo.year_g,
quarter=cinfo.quarter,
month=cinfo.month,
dom=cinfo.dom,
doy=cinfo.doy,
week_w=cinfo.week_w,
week_u=cinfo.week_u,
week_v=cinfo.week_v,
major=major,
minor=minor,
patch=patch,
@ -234,7 +254,6 @@ def _parse_version_info(field_values: FieldValues) -> version.V2VersionInfo:
inc0=inc0,
inc1=inc1,
)
return vinfo
def parse_version_info(

View file

@ -126,8 +126,6 @@ V2_FIELD_INITIAL_VALUES = {
'major': "0",
'minor': "0",
'patch': "0",
'tag' : "final",
'pytag': "",
'num' : "0",
'inc0' : "0",
'inc1' : "1",

View file

@ -566,7 +566,7 @@ def test_v1_bump_semver_warning(runner, caplog, version_pattern):
assert result.exit_code == 1
assert any("version did not change" in r.message for r in caplog.records)
assert any("--major/--minor/--patch required" in r.message for r in caplog.records)
assert any("[--major/--minor/--patch] required" in r.message for r in caplog.records)
result = runner.invoke(cli.cli, ['bump', "-vv", "-n", "--dry", "--patch"])
assert result.exit_code == 0