tyop. update to latest cutarelease tool

This commit is contained in:
Trent Mick 2012-03-16 11:19:54 -07:00
parent ea39bf03a5
commit c8fe8dac34
2 changed files with 153 additions and 52 deletions

View file

@ -139,7 +139,7 @@ request handling. See the changelog for node-bunyan 0.3.0 for details.
## serializers ## serializers
Bunyan has a concept of **"serializers" to produce a JSON-able object from a Bunyan has a concept of **"serializers" to produce a JSON-able object from a
JavaScript object**, so your can easily do the following: JavaScript object**, so you can easily do the following:
log.info({req: <request object>}, "something about handling this request"); log.info({req: <request object>}, "something about handling this request");

View file

@ -13,13 +13,14 @@ Conventions:
- XXX - XXX
""" """
__version_info__ = (1, 0, 4) __version_info__ = (1, 0, 6)
__version__ = '.'.join(map(str, __version_info__)) __version__ = '.'.join(map(str, __version_info__))
import sys import sys
import os import os
from os.path import join, dirname, normpath, abspath, exists, basename, splitext from os.path import join, dirname, normpath, abspath, exists, basename, splitext
from glob import glob from glob import glob
from pprint import pprint
import re import re
import codecs import codecs
import logging import logging
@ -77,6 +78,8 @@ def cutarelease(project_name, version_files, dry_run=False):
Granted it might not be your cup of tea. I should add support for Granted it might not be your cup of tea. I should add support for
just `__version__ = "1.2.3"`. I'm open to other suggestions too. just `__version__ = "1.2.3"`. I'm open to other suggestions too.
""" """
dry_run_str = dry_run and " (dry-run)" or ""
if not version_files: if not version_files:
log.info("guessing version file") log.info("guessing version file")
candidates = [ candidates = [
@ -112,43 +115,37 @@ def cutarelease(project_name, version_files, dry_run=False):
if answer != "yes": if answer != "yes":
log.info("user abort") log.info("user abort")
return return
log.info("cutting a %s release", version) log.info("cutting a %s release%s", version, dry_run_str)
# Checks: Ensure there is a section in changes for this version. # Checks: Ensure there is a section in changes for this version.
changes_path = "CHANGES.md"
if not exists(changes_path):
raise Error("'%s' not found" % changes_path)
changes_txt = changes_txt_before = codecs.open(changes_path, 'r', 'utf-8').read()
changes_parser = re.compile(r'^##\s+(?:.*?\s+)?v?(?P<ver>[\d\.abc]+)'
r'(?P<nyr>\s+\(not yet released\))?'
r'(?P<body>.*?)(?=^##|\Z)', re.M | re.S) changes_path = "CHANGES.md"
changes_sections = changes_parser.findall(changes_txt) changes_txt, changes, nyr = parse_changelog(changes_path)
try: #pprint(changes)
top_ver = changes_sections[0][0] top_ver = changes[0]["version"]
except IndexError:
raise Error("unexpected error parsing `%s': parsed=%r" % (
changes_path, changes_sections))
if top_ver != version: if top_ver != version:
raise Error("top section in `%s' is for " raise Error("changelog '%s' top section says "
"version %r, expected version %r: aborting" "version %r, expected version %r: aborting"
% (changes_path, top_ver, version)) % (changes_path, top_ver, version))
top_nyr = changes_sections[0][1].strip() top_verline = changes[0]["verline"]
if not top_nyr: if not top_verline.endswith(nyr):
answer = query_yes_no("\n* * *\n" answer = query_yes_no("\n* * *\n"
"The top section in `%s' doesn't have the expected\n" "The changelog '%s' top section doesn't have the expected\n"
"'(not yet released)' marker. Has this been released already?" "'%s' marker. Has this been released already?"
% changes_path, default="yes") % (changes_path, nyr), default="yes")
print "* * *" print "* * *"
if answer != "no": if answer != "no":
log.info("abort") log.info("abort")
return return
top_body = changes_sections[0][2] top_body = changes[0]["body"]
if top_body.strip() == "(nothing yet)": if top_body.strip() == "(nothing yet)":
raise Error("top section body is `(nothing yet)': it looks like " raise Error("top section body is `(nothing yet)': it looks like "
"nothing has been added to this release") "nothing has been added to this release")
# Commits to prepare release. # Commits to prepare release.
changes_txt_before = changes_txt
changes_txt = changes_txt.replace(" (not yet released)", "", 1) changes_txt = changes_txt.replace(" (not yet released)", "", 1)
if not dry_run and changes_txt != changes_txt_before: if not dry_run and changes_txt != changes_txt_before:
log.info("prepare `%s' for release", changes_path) log.info("prepare `%s' for release", changes_path)
@ -170,26 +167,34 @@ def cutarelease(project_name, version_files, dry_run=False):
answer = query_yes_no("\n* * *\nPublish to npm?", default="yes") answer = query_yes_no("\n* * *\nPublish to npm?", default="yes")
print "* * *" print "* * *"
if answer == "yes": if answer == "yes":
run('npm publish') if dry_run:
log.info("skipping npm publish (dry-run)")
else:
run('npm publish')
elif exists("setup.py"): elif exists("setup.py"):
answer = query_yes_no("\n* * *\nPublish to pypi?", default="yes") answer = query_yes_no("\n* * *\nPublish to pypi?", default="yes")
print "* * *" print "* * *"
if answer == "yes": if answer == "yes":
run("%spython setup.py sdist --formats zip upload" if dry_run:
% _setup_command_prefix()) log.info("skipping pypi publish (dry-run)")
else:
run("%spython setup.py sdist --formats zip upload"
% _setup_command_prefix())
# Commits to prepare for future dev and push. # Commits to prepare for future dev and push.
# - update changelog file # - update changelog file
next_version_info = _get_next_version_info(version_info) next_version_info = _get_next_version_info(version_info)
next_version = _version_from_version_info(next_version_info) next_version = _version_from_version_info(next_version_info)
log.info("prepare for future dev (version %s)", next_version) log.info("prepare for future dev (version %s)", next_version)
marker = "## %s %s\n" % (project_name, version) marker = "## " + changes[0]["verline"]
if marker.endswith(nyr):
marker = marker[0:-len(nyr)]
if marker not in changes_txt: if marker not in changes_txt:
raise Error("couldn't find `%s' marker in `%s' " raise Error("couldn't find `%s' marker in `%s' "
"content: can't prep for subsequent dev" % (marker, changes_path)) "content: can't prep for subsequent dev" % (marker, changes_path))
changes_txt = changes_txt.replace("## %s %s\n" % (project_name, version), next_verline = "%s %s%s" % (marker.rsplit(None, 1)[0], next_version, nyr)
"## %s %s (not yet released)\n\n(nothing yet)\n\n## %s %s\n" % ( changes_txt = changes_txt.replace(marker + '\n',
project_name, next_version, project_name, version)) "%s\n\n(nothing yet)\n\n\n%s\n" % (next_verline, marker))
if not dry_run: if not dry_run:
f = codecs.open(changes_path, 'w', 'utf-8') f = codecs.open(changes_path, 'w', 'utf-8')
f.write(changes_txt) f.write(changes_txt)
@ -240,6 +245,9 @@ def cutarelease(project_name, version_files, dry_run=False):
#---- internal support routines #---- internal support routines
def _indent(s, indent=' '):
return indent + indent.join(s.splitlines(True))
def _tuple_from_version(version): def _tuple_from_version(version):
def _intify(s): def _intify(s):
try: try:
@ -355,6 +363,100 @@ def _parse_version_file(version_file):
return version_file_type, version_info return version_file_type, version_info
def parse_changelog(changes_path):
"""Parse the given changelog path and return `(content, parsed, nyr)`
where `nyr` is the ' (not yet released)' marker and `parsed` looks like:
[{'body': u'\n(nothing yet)\n\n',
'verline': u'restify 1.0.1 (not yet released)',
'version': u'1.0.1'}, # version is parsed out for top section only
{'body': u'...',
'verline': u'1.0.0'},
{'body': u'...',
'verline': u'1.0.0-rc2'},
{'body': u'...',
'verline': u'1.0.0-rc1'}]
A changelog (CHANGES.md) is expected to look like this:
# $project Changelog
## $next_version (not yet released)
...
## $version1
...
## $version2
... and so on
The version lines are enforced as follows:
- The top entry should have a " (not yet released)" suffix. "Should"
because recovery from half-cutarelease failures is supported.
- A version string must be extractable from there, but it tries to
be loose (though strict "X.Y.Z" versioning is preferred). Allowed
## 1.0.0
## my project 1.0.1
## foo 1.2.3-rc2
Basically, (a) the " (not yet released)" is stripped, (b) the
last token is the version, and (c) that version must start with
a digit (sanity check).
"""
if not exists(changes_path):
raise Error("changelog file '%s' not found" % changes_path)
content = codecs.open(changes_path, 'r', 'utf-8').read()
parser = re.compile(
r'^##\s*(?P<verline>[^\n]*?)\s*$(?P<body>.*?)(?=^##|\Z)',
re.M | re.S)
sections = parser.findall(content)
# Sanity checks on changelog format.
if not sections:
template = "## 1.0.0 (not yet released)\n\n(nothing yet)\n"
raise Error("changelog '%s' must have at least one section, "
"suggestion:\n\n%s" % (changes_path, _indent(template)))
first_section_verline = sections[0][0]
nyr = ' (not yet released)'
#if not first_section_verline.endswith(nyr):
# eg = "## %s%s" % (first_section_verline, nyr)
# raise Error("changelog '%s' top section must end with %r, "
# "naive e.g.: '%s'" % (changes_path, nyr, eg))
items = []
for i, section in enumerate(sections):
item = {
"verline": section[0],
"body": section[1]
}
if i == 0:
# We only bother to pull out 'version' for the top section.
verline = section[0]
if verline.endswith(nyr):
verline = verline[0:-len(nyr)]
version = verline.split()[-1]
try:
int(version[0])
except ValueError:
msg = ''
if version.endswith(')'):
msg = " (cutarelease is picky about the trailing %r " \
"on the top version line. Perhaps you misspelled " \
"that?)" % nyr
raise Error("changelog '%s' top section version '%s' is "
"invalid: first char isn't a number%s"
% (changes_path, version, msg))
item["version"] = version
items.append(item)
return content, items, nyr
## {{{ http://code.activestate.com/recipes/577058/ (r2) ## {{{ http://code.activestate.com/recipes/577058/ (r2)
def query_yes_no(question, default="yes"): def query_yes_no(question, default="yes"):
"""Ask a yes/no question via raw_input() and return their answer. """Ask a yes/no question via raw_input() and return their answer.
@ -461,7 +563,7 @@ def main(argv):
cutarelease(opts.project_name, opts.version_files, dry_run=opts.dry_run) cutarelease(opts.project_name, opts.version_files, dry_run=opts.dry_run)
## {{{ http://code.activestate.com/recipes/577258/ (r5) ## {{{ http://code.activestate.com/recipes/577258/ (r5+)
if __name__ == "__main__": if __name__ == "__main__":
try: try:
retval = main(sys.argv) retval = main(sys.argv)
@ -488,7 +590,6 @@ if __name__ == "__main__":
log.error(exc_info[0]) log.error(exc_info[0])
if not skip_it: if not skip_it:
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
print()
traceback.print_exception(*exc_info) traceback.print_exception(*exc_info)
sys.exit(1) sys.exit(1)
else: else: