Source code for pyscaffold.contrib.setuptools_scm.version

from __future__ import print_function
import datetime
import warnings
import re
from itertools import chain, repeat, islice

from .config import Configuration
from .utils import trace, string_types

from pkg_resources import iter_entry_points

from pkg_resources import parse_version as pkg_parse_version

SEMVER_MINOR = 2
SEMVER_PATCH = 3
SEMVER_LEN = 3


def _pad(iterable, size, padding=None):
    padded = chain(iterable, repeat(padding))
    return list(islice(padded, size))


def _parse_version_tag(tag, config):
    tagstring = tag if not isinstance(tag, string_types) else str(tag)
    match = config.tag_regex.match(tagstring)

    result = None
    if match:
        if len(match.groups()) == 1:
            key = 1
        else:
            key = "version"

        result = {
            "version": match.group(key),
            "prefix": match.group(0)[:match.start(key)],
            "suffix": match.group(0)[match.end(key):],
        }

    trace("tag '%s' parsed to %s" % (tag, result))
    return result


def _get_version_class():
    modern_version = pkg_parse_version("1.0")
    if isinstance(modern_version, tuple):
        return None
    else:
        return type(modern_version)


VERSION_CLASS = _get_version_class()


[docs]class SetuptoolsOutdatedWarning(Warning): pass
# append so integrators can disable the warning warnings.simplefilter("error", SetuptoolsOutdatedWarning, append=True) def _warn_if_setuptools_outdated(): if VERSION_CLASS is None: warnings.warn("your setuptools is too old (<12)", SetuptoolsOutdatedWarning)
[docs]def callable_or_entrypoint(group, callable_or_name): trace("ep", (group, callable_or_name)) if callable(callable_or_name): return callable_or_name for ep in iter_entry_points(group, callable_or_name): trace("ep found:", ep.name) return ep.load()
[docs]def tag_to_version(tag, config=None): """ take a tag that might be prefixed with a keyword and return only the version part :param config: optional configuration object """ trace("tag", tag) if not config: config = Configuration() tagdict = _parse_version_tag(tag, config) if not isinstance(tagdict, dict) or not tagdict.get("version", None): warnings.warn("tag %r no version found" % (tag,)) return None version = tagdict["version"] trace("version pre parse", version) if tagdict.get("suffix", ""): warnings.warn( "tag %r will be stripped of its suffix '%s'" % (tag, tagdict["suffix"]) ) if VERSION_CLASS is not None: version = pkg_parse_version(version) trace("version", repr(version)) return version
[docs]def tags_to_versions(tags, config=None): """ take tags that might be prefixed with a keyword and return only the version part :param tags: an iterable of tags :param config: optional configuration object """ result = [] for tag in tags: tag = tag_to_version(tag, config=config) if tag: result.append(tag) return result
[docs]class ScmVersion(object): def __init__( self, tag_version, distance=None, node=None, dirty=False, preformatted=False, branch=None, **kw ): if kw: trace("unknown args", kw) self.tag = tag_version if dirty and distance is None: distance = 0 self.distance = distance self.node = node self.time = datetime.datetime.now() self._extra = kw self.dirty = dirty self.preformatted = preformatted self.branch = branch @property def extra(self): warnings.warn( "ScmVersion.extra is deprecated and will be removed in future", category=DeprecationWarning, stacklevel=2, ) return self._extra @property def exact(self): return self.distance is None def __repr__(self): return self.format_with( "<ScmVersion {tag} d={distance} n={node} d={dirty} b={branch}>" )
[docs] def format_with(self, fmt, **kw): return fmt.format( time=self.time, tag=self.tag, distance=self.distance, node=self.node, dirty=self.dirty, branch=self.branch, **kw )
[docs] def format_choice(self, clean_format, dirty_format, **kw): return self.format_with(dirty_format if self.dirty else clean_format, **kw)
[docs] def format_next_version(self, guess_next, fmt="{guessed}.dev{distance}", **kw): guessed = guess_next(self.tag, **kw) return self.format_with(fmt, guessed=guessed)
def _parse_tag(tag, preformatted, config): if preformatted: return tag if VERSION_CLASS is None or not isinstance(tag, VERSION_CLASS): tag = tag_to_version(tag, config) return tag
[docs]def meta( tag, distance=None, dirty=False, node=None, preformatted=False, config=None, **kw ): if not config: warnings.warn( "meta invoked without explicit configuration," " will use defaults where required." ) parsed_version = _parse_tag(tag, preformatted, config) trace("version", tag, "->", parsed_version) assert parsed_version is not None, "cant parse version %s" % tag return ScmVersion(parsed_version, distance, node, dirty, preformatted, **kw)
[docs]def guess_next_version(tag_version): version = _strip_local(str(tag_version)) return _bump_dev(version) or _bump_regex(version)
def _strip_local(version_string): public, sep, local = version_string.partition("+") return public def _bump_dev(version): if ".dev" not in version: return prefix, tail = version.rsplit(".dev", 1) assert tail == "0", "own dev numbers are unsupported" return prefix def _bump_regex(version): prefix, tail = re.match(r"(.*?)(\d+)$", version).groups() return "%s%d" % (prefix, int(tail) + 1)
[docs]def guess_next_dev_version(version): if version.exact: return version.format_with("{tag}") else: return version.format_next_version(guess_next_version)
[docs]def guess_next_simple_semver(version, retain, increment=True): parts = map(int, str(version).split(".")) parts = _pad(parts, retain, 0) if increment: parts[-1] += 1 parts = _pad(parts, SEMVER_LEN, 0) return ".".join(map(str, parts))
[docs]def simplified_semver_version(version): if version.exact: return guess_next_simple_semver(version.tag, retain=SEMVER_LEN, increment=False) else: if version.branch is not None and "feature" in version.branch: return version.format_next_version( guess_next_simple_semver, retain=SEMVER_MINOR ) else: return version.format_next_version( guess_next_simple_semver, retain=SEMVER_PATCH )
def _format_local_with_time(version, time_format): if version.exact or version.node is None: return version.format_choice( "", "+d{time:{time_format}}", time_format=time_format ) else: return version.format_choice( "+{node}", "+{node}.d{time:{time_format}}", time_format=time_format )
[docs]def get_local_node_and_date(version): return _format_local_with_time(version, time_format="%Y%m%d")
[docs]def get_local_node_and_timestamp(version, fmt="%Y%m%d%H%M%S"): return _format_local_with_time(version, time_format=fmt)
[docs]def get_local_dirty_tag(version): return version.format_choice("", "+dirty")
[docs]def postrelease_version(version): if version.exact: return version.format_with("{tag}") else: return version.format_with("{tag}.post{distance}")
[docs]def format_version(version, **config): trace("scm version", version) trace("config", config) if version.preformatted: return version.tag version_scheme = callable_or_entrypoint( "setuptools_scm.version_scheme", config["version_scheme"] ) local_scheme = callable_or_entrypoint( "setuptools_scm.local_scheme", config["local_scheme"] ) main_version = version_scheme(version) trace("version", main_version) local_version = local_scheme(version) trace("local_version", local_version) return version_scheme(version) + local_scheme(version)