import datetime import re import packaging.version import warnings class CalVer(packaging.version.Version): """Class for parsing calver strings. See https://calver.org/ for details on the calver standard. This class is specifically for calver strings in the format `YY.MM.DD_MICRO`, which is one of many possible implementations of the standard.""" pattern = re.compile( r""" (?P\d{2})\. (?P\d{1,2})\. (?P\d{1,2}) (?:_(?P\d+))? (?P(alpha|beta|dev|rc\d+|post\d+))? $ """, re.VERBOSE ) def __init__(self, version): super(CalVer, self).__init__(version) match = self.pattern.match(version) if not match: raise ValueError("Version string does not match CalVer pattern") self.groups = match.groupdict() if any((i.startswith("0") for i in (self.groups["month"], self.groups["day"]))): raise ValueError("Leading 0's are not allowed in the version string") assert self.as_date() @classmethod def new(cls): """Generate a new version number from the current date. :return: An CalVer object representing the current date :rtype: CalVer """ version_string = datetime.date.today().strftime("%Y.%m.%d") return cls(version_string) @property def year(self): return int(self.groups["year"]) @property def month(self): return int(self.groups["month"]) @property def day(self): return int(self.groups["day"]) @property def minor(self): if self.groups["minor"]: return int(self.groups["minor"]) else: return None @property def modifier(self): if self.groups["modifier"]: return self.groups["modifier"] else: return None def as_date(self): return datetime.date(self.year, self.month, self.day) class NSCLIVersion(CalVer): def __init__(self, version): super(NSCLIVersion, self).__init__(version) self.validate_for_python_package(version) self.validate_for_debian() def validate_for_python_package(self, version): if str(self) != version: raise ValueError("Version should be '%s', not %r" % (str(self), version)) def validate_for_debian(self): """From https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-version : The upstream_version may contain only alphanumerics [6] and the characters . + - ~ (full stop, plus, hyphen, tilde) and should start with a digit. If there is no debian_revision then hyphens are not allowed. [6] Alphanumerics are A-Za-z0-9 only. """ version = str(self) if not re.match(r"[A-Za-z0-9\.\+\-\~]*$", version): raise ValueError("Version number contains characters not allowed in Debian upstream_version numbers.") if not re.match(r"\d", version): raise ValueError("Version number does not start with a digit") if re.search("-", version): raise ValueError("Version number should not contain a hyphen") if re.match(r".*rc\d+$", version): warnings.warn( "Debian doesn't handle release-candidate versioning the same way Python does. " "1.0rc0 is a newer version than 1.0, according to apt.") if __name__ == "__main__": from nscli import __version__ NSCLIVersion(__version__)