Commit c32fddfb authored by hoverhell's avatar hoverhell

Release 1.4.0

parent 591504af
......@@ -3,6 +3,16 @@
Release History
---------------
1.4.0 (2015-03-18)
++++++++++++++++++
- WIP _newrelease.py
- pyaux.base.group
- WARN: dict_merge: deepcopy the target by default (for safety)
- license file
- date ranges
1.3.2 (2015-01-28)
++++++++++++++++++
......
......@@ -7,14 +7,25 @@ import subprocess
import datetime
import logging
import argparse
import shlex
LOGLEVEL_EVERYTHING = 1
HISTORY_FILE = os.environ.get('HISTORY_FILE', 'HISTORY.rst')
HISTORY_TAG = os.environ.get('HISTORY_TAG', r'(\nRelease History\n-+\n)')
HISTORY_VERSION_TAG = os.environ.get(
'HISTORY_VERSION_TAG',
# 3 dot-separated numbers or more, ISO datestamp or more
r'^([0-9]+\.[0-9]+\.[0-9]+[^ ]*) \([0-9]{4}-[0-9]{2}-[0-9]{2}[^)]*\).*')
VERSION_TAG = os.environ.get(
'VERSION_TAG', r'''(\n(?:__)?version(?:__)? = ['"])(?P<version>[0-9.]+)(['"] *(?:#.*)?\n)''')
'VERSION_TAG',
# Includes 3 groups, the second one should be the current version
# ans is to be replaced with the new version; without quotes.
r'''(\n(?:__)?version(?:__)? = ['"])(?P<version>[0-9a-zA-Z.+-]+)(['"] *(?:#.*)?\n)''')
SETUP_VERSION_TAG = os.environ.get('SETUP_VERSION_TAG', VERSION_TAG)
VERSION_FILE = os.environ.get('VERSION_FILE', 'pyaux/__init__.py')
RELEASE_COMMIT_TPL = os.environ.get('RELEASE_COMMIT_TPL', 'Release %(version)s')
VERSION_TAG_TPL = os.environ.get('VERSION_TAG_TPL', '%(version)s')
_log = logging.getLogger('_newrelease.py')
......@@ -23,31 +34,45 @@ _log = logging.getLogger('_newrelease.py')
def make_parser():
parser = argparse.ArgumentParser(
description="Automatically prepare a new pypi release from git / github")
parser.add_argument(
'cmd',
choices=('prepare', 'check', 'finalise'),
nargs='?', default='prepare',
help=(u"Action to perform; usual workflow is 'prepare',"
u" check `git status`, 'check', then 'finalise'."))
parser.add_argument(
'-v', '--verbose',
dest='verbosity',
action='count', default=0,
help="Verbosity level; specify multiple times to increase verbosity")
parser.add_argument(
'--major',
dest='major', action='store_true',
help="Make a major release")
help="Make a major release (in `prepare`)")
parser.add_argument(
'--patch',
dest='patch', action='store_true',
help="Make a patch release (third number)")
help="Make a patch release (third number) (in `prepare`)")
parser.add_argument(
'--allow-unclean',
dest='allow_unclean', action='store_true',
help="Ignore reposotory taint")
help="Ignore reposotory taint (in `prepare`)")
parser.add_argument(
'--no-commit',
dest='no_commit', action='store_true',
help="Do not commit changes (in `finalise`)")
return parser
def run_sh(command, *args):
full_command = (command,) + args
_log.debug("full_command: %r", full_command)
def run_sh(*full_command):
_log.debug("Running command: %r", full_command)
process = subprocess.Popen(
full_command,
stdin=subprocess.PIPE,
shell=False, stdout=subprocess.PIPE)
stdout, _ = process.communicate()
_log.debug("Return code: %r", process.returncode)
......@@ -55,6 +80,11 @@ def run_sh(command, *args):
return stdout
def run_sh_cmd(cmd, **kwa):
full_command = shlex.split(cmd)
return run_sh(*full_command, **kwa)
def _inc_ver_val(val):
# NOTE: strips non-digit part of the version
val_digits = re.search('^([0-9]+)', str(val)).group(1)
......@@ -80,7 +110,7 @@ def _inc_ver(parts, num):
def update_version_in_text(text, new_version, current_version=None):
match = re.search(VERSION_TAG, text)
if match is None:
raise ValueError("Version not found in file %r" % (filename,))
raise ValueError("Version not found", text)
pre_tag, current_actual_ver, post_tag = match.groups()
if current_version is not None:
......@@ -101,34 +131,52 @@ def update_version_in_file(filename, new_version, **kwa):
with open(filename) as fo:
text = fo.read()
new_text = update_version_in_text(text, new_version, **kwa)
try:
new_text = update_version_in_text(text, new_version, **kwa)
except ValueError:
# Re-raise with filename instead of the text
raise ValueError("Version not found in file %r" % (filename,))
with open(filename, 'w') as fo:
fo.write(new_text)
return new_text
def main(args=None):
parser = make_parser()
params = parser.parse_args(args)
def get_current_version(text=None):
""" Obtain the current actual version from setup.py.
_log_levels = {
0: logging.WARNING,
1: logging.INFO,
2: logging.DEBUG,
3: LOGLEVEL_EVERYTHING,
}
logging.basicConfig(level=_log_levels[min(params.verbosity, 3)])
Mainly intended for `finalise`.
"""
if text is None:
with open('setup.py') as fo:
text = fo.read()
match = re.search(VERSION_TAG, text)
if not match:
raise ValueError("Could not find version in setup.py")
return match.group(2)
def get_git_mods():
return run_sh_cmd('git status --porcelain')
def get_git_taint():
return run_sh_cmd('git clean -d -x -n')
def prepare(params):
if not params.allow_unclean:
taint = run_sh('git', 'clean', '-d', '-x', '-n')
if taint.strip():
_log.critical("Cannot proceed as git repo is not clean:\n%r", taint)
return 13
mods = run_sh('git', 'status', '--porcelain')
mods = get_git_mods()
if mods.strip():
_log.critical("Cannot proceed as git repo has modifications:\n%r", mods)
return 14
taint = get_git_taint()
if taint.strip():
_log.critical("Cannot proceed as git repo is not clean:\n%r", taint)
return 13
with open(HISTORY_FILE) as fo:
history = fo.read()
......@@ -139,8 +187,7 @@ def main(args=None):
history_versions = history_parts[-1]
current_version_match = re.search(
# 3 dot-separated numbers or more, ISO datestamp or more
r'^([0-9]+\.[0-9]+\.[0-9]+[^ ]*) \([0-9]{4}-[0-9]{2}-[0-9]{2}[^)]*\).*',
HISTORY_VERSION_TAG,
history_versions, re.MULTILINE)
current_version = current_version_match.group(1)
today = datetime.date.today().isoformat()
......@@ -159,8 +206,9 @@ def main(args=None):
_log.debug("New version: %s", new_version)
version_tag = VERSION_TAG_TPL % dict(version=current_version)
git_history = run_sh(
"git", "log", "%s..HEAD" % (current_version,),
"git", "log", "%s..HEAD" % (version_tag,),
"--format=format: - %s")
_log.debug("Git history: %r", git_history)
......@@ -189,7 +237,73 @@ def main(args=None):
_log.critical("Error updating version: %r", exc)
return 2
raise Exception("TODO")
_log.info("Done")
def check(params):
run_sh_cmd('python setup.py develop')
run_sh_cmd('python setup.py test')
if os.path.exists('test.sh'):
run_sh('./test.sh')
def finalise(params):
try:
version = get_current_version()
except ValueError as exc:
_log.critical("Error getting current version: %r", exc)
return 2
_log.info("Found current version: %r", version)
if not params.no_commit:
mods = get_git_mods()
if not mods.strip():
_log.critical((
"Finalise should be called after `prepare` made the"
" changes and while they are not committed yet; alas, no"
" changes are found by git"))
return 4
taint = get_git_taint()
if taint.strip():
_log.critical((
"There are git-untracked files which should not exist;"
" please run `git clean -d -x -f` to remove them before"
" using `finalise`:\n%s"), taint)
return 5
tpl_env = dict(version=version)
release_msg = RELEASE_COMMIT_TPL % tpl_env
if not params.no_commit:
run_sh('git', 'commit', '-a', '-m', release_msg)
run_sh_cmd('python setup.py sdist')
run_sh_cmd('python setup.py sdist upload')
version_tag = VERSION_TAG_TPL % tpl_env
run_sh('git', 'tag', '-a', version_tag, '-m', release_msg)
run_sh_cmd('git push')
run_sh_cmd('git push --tags')
_log.debug("Done.")
def main(args=None):
parser = make_parser()
params = parser.parse_args(args)
_log_levels = {
0: logging.WARNING,
1: logging.INFO,
2: logging.DEBUG,
3: LOGLEVEL_EVERYTHING,
}
logging.basicConfig(level=_log_levels[min(params.verbosity, 3)])
if params.cmd == 'prepare':
return prepare(params)
elif params.cmd == 'finalise':
return finalise(params)
if __name__ == '__main__':
......
# coding: utf8
""" A collection of useful helpers """
__version__ = "1.3.2"
__version__ = "1.4.0"
## NOTE: no modules imported here should import `decimal` (otherwise
## `use_cdecimal` might become problematic for them)
......
......@@ -3,7 +3,7 @@
import os
from setuptools import setup
version = '1.3.2' # should be the same as pyaux.__version__
version = '1.4.0' # should be the same as pyaux.__version__
LONG_DESCRIPTION = """
Collection of helpers and useful things for Python
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment