-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for version compare and for checking for version part argument #20
base: master
Are you sure you want to change the base?
Changes from all commits
f024dd4
3a30f4e
13b145d
a79f1db
9ee9ec7
8aa406b
91b213d
a135eb2
23640ac
fc768bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,12 +14,15 @@ | |
|
||
|
||
import argparse | ||
from argparse import _AppendAction | ||
import os | ||
import re | ||
import sre_constants | ||
import subprocess | ||
import warnings | ||
import io | ||
import operator | ||
import logging | ||
from string import Formatter | ||
from datetime import datetime | ||
from difflib import unified_diff | ||
|
@@ -29,6 +32,7 @@ | |
import codecs | ||
|
||
from bumpversion.version_part import VersionPart, NumericVersionPartConfiguration, ConfiguredVersionPartConfiguration | ||
from bumpversion.functions import NumericFunction | ||
|
||
if sys.version_info[0] == 2: | ||
sys.stdout = codecs.getwriter('utf-8')(sys.stdout) | ||
|
@@ -40,11 +44,11 @@ | |
sys.version.split("\n")[0].split(" ")[0], | ||
) | ||
|
||
import logging | ||
|
||
logger = logging.getLogger("bumpversion.logger") | ||
logger_list = logging.getLogger("bumpversion.list") | ||
|
||
from argparse import _AppendAction | ||
|
||
class DiscardDefaultIfSpecifiedAppendAction(_AppendAction): | ||
|
||
''' | ||
|
@@ -59,11 +63,13 @@ def __call__(self, parser, namespace, values, option_string=None): | |
super(DiscardDefaultIfSpecifiedAppendAction, self).__call__( | ||
parser, namespace, values, option_string=None) | ||
|
||
|
||
time_context = { | ||
'now': datetime.now(), | ||
'utcnow': datetime.utcnow(), | ||
} | ||
|
||
|
||
class BaseVCS(object): | ||
|
||
@classmethod | ||
|
@@ -203,12 +209,14 @@ def tag(cls, sign, name, message): | |
command += ['--message', message] | ||
subprocess.check_output(command) | ||
|
||
|
||
VCS = [Git, Mercurial] | ||
|
||
|
||
def prefixed_environ(): | ||
return dict((("${}".format(key), value) for key, value in os.environ.items())) | ||
|
||
|
||
class ConfiguredFile(object): | ||
|
||
def __init__(self, path, versionconfig): | ||
|
@@ -301,30 +309,41 @@ def __str__(self): | |
def __repr__(self): | ||
return '<bumpversion.ConfiguredFile:{}>'.format(self.path) | ||
|
||
|
||
class IncompleteVersionRepresenationException(Exception): | ||
def __init__(self, message): | ||
self.message = message | ||
|
||
|
||
class MissingValueForSerializationException(Exception): | ||
def __init__(self, message): | ||
self.message = message | ||
|
||
|
||
class WorkingDirectoryIsDirtyException(Exception): | ||
def __init__(self, message): | ||
self.message = message | ||
|
||
|
||
class MercurialDoesNotSupportSignedTagsException(Exception): | ||
def __init__(self, message): | ||
self.message = message | ||
|
||
|
||
class UnkownPart(Exception): | ||
def __init__(self, message): | ||
self.message = message | ||
|
||
|
||
def keyvaluestring(d): | ||
return ", ".join("{}={}".format(k, v) for k, v in sorted(d.items())) | ||
|
||
|
||
class Version(object): | ||
|
||
def __init__(self, values, original=None): | ||
def __init__(self, values, order): | ||
self._values = dict(values) | ||
self.original = original | ||
self.order = order | ||
|
||
def __getitem__(self, key): | ||
return self._values[key] | ||
|
@@ -336,7 +355,78 @@ def __iter__(self): | |
return iter(self._values) | ||
|
||
def __repr__(self): | ||
return '<bumpversion.Version:{}>'.format(keyvaluestring(self._values)) | ||
by_part = ", ".join("{}={}".format(k, v) for k, v in self.items()) | ||
return '<{}:{}>'.format(self.__class__, by_part) | ||
|
||
def __hash__(self): | ||
return hash(tuple((k, v) for k, v in self.items())) | ||
|
||
def _compare(self, other, method, strict=True): | ||
""" | ||
When comparing versions we need to compare the three parts before we can decide if there is a difference. | ||
Non-strict comparators need to be treated differently as they can not fail if the initial parts are equal | ||
:param other: the other Version | ||
:param method: the compare method | ||
:param strict: if the comparsion is strict | ||
:return: | ||
""" | ||
if set(self.order).difference(other.order): | ||
raise TypeError("Versions use different parts, cant compare them.") | ||
for (x, y) in zip(self.values(), other.values()): | ||
if x == y: | ||
continue | ||
else: | ||
return method(x, y) | ||
return not strict | ||
# try: | ||
|
||
# | ||
# for vals in ((v, other[k]) for k, v in self.items()): | ||
# if vals[0] == vals[1]: | ||
# continue | ||
# return method(vals[0], vals[1]) | ||
# return not strict | ||
# except KeyError: | ||
# | ||
|
||
def __eq__(self, other): | ||
if self is other: | ||
return True | ||
if not isinstance(other, Version): | ||
return False | ||
try: | ||
return all(v == other[k] for k, v in self.items()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this ignore the case there There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does, we could do a len compare first to make sure both have the same number of parts. I will add this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On a second thought, I think equality should be strict, that is: |
||
except KeyError: | ||
raise TypeError("Versions use different parts, cant compare them.") | ||
|
||
def __ne__(self, other): | ||
return not self == other | ||
|
||
def __le__(self, other): | ||
return self._compare(other, operator.lt, False) | ||
|
||
def __ge__(self, other): | ||
return self._compare(other, operator.gt, False) | ||
|
||
def __lt__(self, other): | ||
return self._compare(other, operator.lt) | ||
|
||
def __gt__(self, other): | ||
return self._compare(other, operator.gt) | ||
|
||
def items(self): | ||
for k in self.order: | ||
try: | ||
yield k, self._values[k] | ||
except KeyError: | ||
raise StopIteration | ||
|
||
def values(self): | ||
for k in self.order: | ||
try: | ||
yield self._values[k] | ||
except KeyError: | ||
raise StopIteration | ||
|
||
def bump(self, part_name, order): | ||
bumped = False | ||
|
@@ -354,10 +444,11 @@ def bump(self, part_name, order): | |
else: | ||
new_values[label] = self._values[label].copy() | ||
|
||
new_version = Version(new_values) | ||
new_version = Version(new_values, self.order) | ||
|
||
return new_version | ||
|
||
|
||
class VersionConfig(object): | ||
|
||
""" | ||
|
@@ -410,8 +501,7 @@ def parse(self, version_string): | |
for key, value in match.groupdict().items(): | ||
_parsed[key] = VersionPart(value, self.part_configs.get(key)) | ||
|
||
|
||
v = Version(_parsed, version_string) | ||
v = Version(_parsed, [o for o in self.order()]) | ||
|
||
logger.info("Parsed the following values: %s" % keyvaluestring(v._values)) | ||
|
||
|
@@ -472,7 +562,6 @@ def _serialize(self, version, serialize_format, context, raise_if_incomplete=Fal | |
|
||
return serialized | ||
|
||
|
||
def _choose_serialize_format(self, version, context): | ||
|
||
chosen = None | ||
|
@@ -504,6 +593,7 @@ def serialize(self, version, context): | |
# logger.info("Serialized to '{}'".format(serialized)) | ||
return serialized | ||
|
||
|
||
OPTIONAL_ARGUMENTS_THAT_TAKE_VALUES = [ | ||
'--config-file', | ||
'--current-version', | ||
|
@@ -540,6 +630,7 @@ def split_args_in_optional_and_positional(args): | |
|
||
return (positionals, args) | ||
|
||
|
||
def main(original_args=None): | ||
|
||
positionals, args = split_args_in_optional_and_positional( | ||
|
@@ -756,10 +847,16 @@ def main(original_args=None): | |
if not 'new_version' in defaults and known_args.current_version: | ||
try: | ||
if current_version and len(positionals) > 0: | ||
logger.info("Attempting to increment part '{}'".format(positionals[0])) | ||
new_version = current_version.bump(positionals[0], vc.order()) | ||
logger.info("Values are now: " + keyvaluestring(new_version._values)) | ||
defaults['new_version'] = vc.serialize(new_version, context) | ||
part = positionals[0] | ||
logger.info("Attempting to increment part '{}'".format(part)) | ||
if part in vc.order(): | ||
logger.info("Bumped part found in parse parts") | ||
new_version = current_version.bump(part, vc.order()) | ||
logger.info("Values are now: " + keyvaluestring(new_version._values)) | ||
defaults['new_version'] = vc.serialize(new_version, context) | ||
else: | ||
logger.info("Bumped part not found in parse parts") | ||
raise UnkownPart("Bumped part not found in parse parts.") | ||
except MissingValueForSerializationException as e: | ||
logger.info("Opportunistic finding of new_version failed: " + e.message) | ||
except IncompleteVersionRepresenationException as e: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is really hard to understand at first sight. The
strict
parameter should be named something clearer, perhapsallow_equal
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, I have changed the logic and improved the docs so the intent is more clear