summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcon-f-use <con-f-use@gmx.net>2020-03-18 18:29:24 +0100
committercon-f-use <con-f-use@gmx.net>2020-03-18 18:29:24 +0100
commit1d1baf66446c4e8c049d2a21d86fe78dd3766250 (patch)
tree7836ef273ac7085ab0d4e1b6177977b6cebecb44
parent69eb0fa5660645a9b28c078b5b3e8dddc74267fa (diff)
downloadsetuptools-scm-1d1baf66446c4e8c049d2a21d86fe78dd3766250.tar.gz
Change ScmVersion.time to UTC
-rw-r--r--utils.py158
-rw-r--r--version.py313
2 files changed, 471 insertions, 0 deletions
diff --git a/utils.py b/utils.py
new file mode 100644
index 0000000..ab0dede
--- /dev/null
+++ b/utils.py
@@ -0,0 +1,158 @@
+"""
+utils
+"""
+from __future__ import print_function, unicode_literals
+import inspect
+import warnings
+import sys
+import shlex
+import subprocess
+import os
+import io
+import platform
+import traceback
+import datetime
+
+
+DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))
+IS_WINDOWS = platform.system() == "Windows"
+PY2 = sys.version_info < (3,)
+PY3 = sys.version_info > (3,)
+string_types = (str,) if PY3 else (str, unicode) # noqa
+
+
+def no_git_env(env):
+ # adapted from pre-commit
+ # Too many bugs dealing with environment variables and GIT:
+ # https://github.com/pre-commit/pre-commit/issues/300
+ # In git 2.6.3 (maybe others), git exports GIT_WORK_TREE while running
+ # pre-commit hooks
+ # In git 1.9.1 (maybe others), git exports GIT_DIR and GIT_INDEX_FILE
+ # while running pre-commit hooks in submodules.
+ # GIT_DIR: Causes git clone to clone wrong thing
+ # GIT_INDEX_FILE: Causes 'error invalid object ...' during commit
+ for k, v in env.items():
+ if k.startswith("GIT_"):
+ trace(k, v)
+ return {
+ k: v
+ for k, v in env.items()
+ if not k.startswith("GIT_")
+ or k in ("GIT_EXEC_PATH", "GIT_SSH", "GIT_SSH_COMMAND")
+ }
+
+
+def trace(*k):
+ if DEBUG:
+ print(*k)
+ sys.stdout.flush()
+
+
+def trace_exception():
+ DEBUG and traceback.print_exc()
+
+
+def ensure_stripped_str(str_or_bytes):
+ if isinstance(str_or_bytes, str):
+ return str_or_bytes.strip()
+ else:
+ return str_or_bytes.decode("utf-8", "surrogateescape").strip()
+
+
+def _always_strings(env_dict):
+ """
+ On Windows and Python 2, environment dictionaries must be strings
+ and not unicode.
+ """
+ if IS_WINDOWS or PY2:
+ env_dict.update((key, str(value)) for (key, value) in env_dict.items())
+ return env_dict
+
+
+def _popen_pipes(cmd, cwd):
+ return subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=str(cwd),
+ env=_always_strings(
+ dict(
+ no_git_env(os.environ),
+ # os.environ,
+ # try to disable i18n
+ LC_ALL="C",
+ LANGUAGE="",
+ HGPLAIN="1",
+ )
+ ),
+ )
+
+
+def do_ex(cmd, cwd="."):
+ trace("cmd", repr(cmd))
+ if os.name == "posix" and not isinstance(cmd, (list, tuple)):
+ cmd = shlex.split(cmd)
+
+ p = _popen_pipes(cmd, cwd)
+ out, err = p.communicate()
+ if out:
+ trace("out", repr(out))
+ if err:
+ trace("err", repr(err))
+ if p.returncode:
+ trace("ret", p.returncode)
+ return ensure_stripped_str(out), ensure_stripped_str(err), p.returncode
+
+
+def do(cmd, cwd="."):
+ out, err, ret = do_ex(cmd, cwd)
+ if ret:
+ print(err)
+ return out
+
+
+def data_from_mime(path):
+ with io.open(path, encoding="utf-8") as fp:
+ content = fp.read()
+ trace("content", repr(content))
+ # the complex conditions come from reading pseudo-mime-messages
+ data = dict(x.split(": ", 1) for x in content.splitlines() if ": " in x)
+ trace("data", data)
+ return data
+
+
+class UTC(datetime.tzinfo):
+ _ZERO = datetime.timedelta(0)
+ def utcoffset(self, dt):
+ return self._ZERO
+ def tzname(self, dt):
+ return "UTC"
+ def dst(self, dt):
+ return self._ZERO
+utc = UTC()
+
+
+def function_has_arg(fn, argname):
+ assert inspect.isfunction(fn)
+
+ if PY2:
+ argspec = inspect.getargspec(fn).args
+ else:
+
+ argspec = inspect.signature(fn).parameters
+
+ return argname in argspec
+
+
+def has_command(name):
+ try:
+ p = _popen_pipes([name, "help"], ".")
+ except OSError:
+ trace(*sys.exc_info())
+ res = False
+ else:
+ p.communicate()
+ res = not p.returncode
+ if not res:
+ warnings.warn("%r was not found" % name)
+ return res
diff --git a/version.py b/version.py
new file mode 100644
index 0000000..aa4bd49
--- /dev/null
+++ b/version.py
@@ -0,0 +1,313 @@
+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, utc
+
+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 '{}' parsed to {}".format(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()
+
+
+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)
+
+
+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()
+
+
+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".format(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 '{}'".format(
+ tag, tagdict["suffix"]
+ )
+ )
+
+ if VERSION_CLASS is not None:
+ version = pkg_parse_version(version)
+ trace("version", repr(version))
+
+ return version
+
+
+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
+
+
+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(utc)
+ 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}>"
+ )
+
+ 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
+ )
+
+ def format_choice(self, clean_format, dirty_format, **kw):
+ return self.format_with(dirty_format if self.dirty else clean_format, **kw)
+
+ 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
+
+
+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)
+
+
+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)
+
+
+def guess_next_dev_version(version):
+ if version.exact:
+ return version.format_with("{tag}")
+ else:
+ return version.format_next_version(guess_next_version)
+
+
+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))
+
+
+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
+ )
+
+
+def get_local_node_and_date(version):
+ return _format_local_with_time(version, time_format="%Y%m%d")
+
+
+def get_local_node_and_timestamp(version, fmt="%Y%m%d%H%M%S"):
+ return _format_local_with_time(version, time_format=fmt)
+
+
+def get_local_dirty_tag(version):
+ return version.format_choice("", "+dirty")
+
+
+def get_no_local_node(_):
+ return ""
+
+
+def postrelease_version(version):
+ if version.exact:
+ return version.format_with("{tag}")
+ else:
+ return version.format_with("{tag}.post{distance}")
+
+
+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)