(project_name, version_files, dry_run=False)
Cut a release.

    @param project_name {str}
    @param version_files {list} List of paths to files holding the version
        info for this project.

        If none are given it attempts to guess the version file:
        package.json or VERSION.txt or VERSION or $project_name.py
        or lib/$project_name.py or $project_name.js or lib/$project_name.js.

        The version file can be in one of the following forms:

        - A .py file, in which case the file is expect to have a top-level
          global called "__version_info__" as follows. [1]

            __version_info__ = (0, 7, 6)

          Note that I typically follow that with the following to get a
          string version attribute on my modules:

            __version__ = '.'.join(map(str, __version_info__))

        - A .js file, in which case the file is expected to have a top-level
          global called "VERSION" as follows:

            ver VERSION = "1.2.3";

        - A "package.json" file, typical of a node.js npm-using project.
          The package.json file must have a "version" field.

        - TODO: A simple version file whose only content is a "1.2.3"-style version
          string.

    [1]: This is a convention I tend to follow in my projects.
        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.
Cut a release.
Cut a release .
def cutarelease(project_name, version_files, dry_run=False):
    """Cut a release.

    @param project_name {str}
    @param version_files {list} List of paths to files holding the version
        info for this project.

        If none are given it attempts to guess the version file:
        package.json or VERSION.txt or VERSION or $project_name.py
        or lib/$project_name.py or $project_name.js or lib/$project_name.js.

        The version file can be in one of the following forms:

        - A .py file, in which case the file is expect to have a top-level
          global called "__version_info__" as follows. [1]

            __version_info__ = (0, 7, 6)

          Note that I typically follow that with the following to get a
          string version attribute on my modules:

            __version__ = '.'.join(map(str, __version_info__))

        - A .js file, in which case the file is expected to have a top-level
          global called "VERSION" as follows:

            ver VERSION = "1.2.3";

        - A "package.json" file, typical of a node.js npm-using project.
          The package.json file must have a "version" field.

        - TODO: A simple version file whose only content is a "1.2.3"-style version
          string.

    [1]: This is a convention I tend to follow in my projects.
        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.
    """
    dry_run_str = dry_run and " (dry-run)" or ""

    if not version_files:
        log.info("guessing version file")
        candidates = [
            "package.json",
            "VERSION.txt",
            "VERSION",
            "%s.py" % project_name,
            "lib/%s.py" % project_name,
            "%s.js" % project_name,
            "lib/%s.js" % project_name,
        ]
        for candidate in candidates:
            if exists(candidate):
                version_files = [candidate]
                break
        else:
            raise Error("could not find a version file: specify its path or "
                "add one of the following to your project: '%s'"
                % "', '".join(candidates))
        log.info("using '%s' as version file", version_files[0])

    parsed_version_files = [_parse_version_file(f) for f in version_files]
    version_file_type, version_info = parsed_version_files[0]
    version = _version_from_version_info(version_info)

    # Confirm
    if not dry_run:
        answer = query_yes_no("* * *\n"
            "Are you sure you want cut a %s release?\n"
            "This will involved commits and a push." % version,
            default="no")
        print "* * *"
        if answer != "yes":
            log.info("user abort")
            return
    log.info("cutting a %s release%s", version, dry_run_str)

    # Checks: Ensure there is a section in changes for this version.



    changes_path = "CHANGES.md"
    changes_txt, changes, nyr = parse_changelog(changes_path)
    #pprint(changes)
    top_ver = changes[0]["version"]
    if top_ver != version:
        raise Error("changelog '%s' top section says "
            "version %r, expected version %r: aborting"
            % (changes_path, top_ver, version))
    top_verline = changes[0]["verline"]
    if not top_verline.endswith(nyr):
        answer = query_yes_no("\n* * *\n"
            "The changelog '%s' top section doesn't have the expected\n"
            "'%s' marker. Has this been released already?"
            % (changes_path, nyr), default="yes")
        print "* * *"
        if answer != "no":
            log.info("abort")
            return
    top_body = changes[0]["body"]
    if top_body.strip() == "(nothing yet)":
        raise Error("top section body is `(nothing yet)': it looks like "
            "nothing has been added to this release")

    # Commits to prepare release.
    changes_txt_before = changes_txt
    changes_txt = changes_txt.replace(" (not yet released)", "", 1)
    if not dry_run and changes_txt != changes_txt_before:
        log.info("prepare `%s' for release", changes_path)
        f = codecs.open(changes_path, 'w', 'utf-8')
        f.write(changes_txt)
        f.close()
        run('git commit %s -m "prepare for %s release"'
            % (changes_path, version))

    # Tag version and push.
    curr_tags = set(t for t in _capture_stdout(["git", "tag", "-l"]).split('\n') if t)
    if not dry_run and version not in curr_tags:
        log.info("tag the release")
        run('git tag -a "%s" -m "version %s"' % (version, version))
        run('git push --tags')

    # Optionally release.
    if exists("package.json"):
        answer = query_yes_no("\n* * *\nPublish to npm?", default="yes")
        print "* * *"
        if answer == "yes":
            if dry_run:
                log.info("skipping npm publish (dry-run)")
            else:
                run('npm publish')
    elif exists("setup.py"):
        answer = query_yes_no("\n* * *\nPublish to pypi?", default="yes")
        print "* * *"
        if answer == "yes":
            if dry_run:
                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.
    # - update changelog file
    next_version_info = _get_next_version_info(version_info)
    next_version = _version_from_version_info(next_version_info)
    log.info("prepare for future dev (version %s)", next_version)
    marker = "## " + changes[0]["verline"]
    if marker.endswith(nyr):
        marker = marker[0:-len(nyr)]
    if marker not in changes_txt:
        raise Error("couldn't find `%s' marker in `%s' "
            "content: can't prep for subsequent dev" % (marker, changes_path))
    next_verline = "%s %s%s" % (marker.rsplit(None, 1)[0], next_version, nyr)
    changes_txt = changes_txt.replace(marker + '\n',
        "%s\n\n(nothing yet)\n\n\n%s\n" % (next_verline, marker))
    if not dry_run:
        f = codecs.open(changes_path, 'w', 'utf-8')
        f.write(changes_txt)
        f.close()

    # - update version file
    next_version_tuple = _tuple_from_version(next_version)
    for i, ver_file in enumerate(version_files):
        ver_content = codecs.open(ver_file, 'r', 'utf-8').read()
        ver_file_type, ver_info = parsed_version_files[i]
        if ver_file_type == "json":
            marker = '"version": "%s"' % version
            if marker not in ver_content:
                raise Error("couldn't find `%s' version marker in `%s' "
                    "content: can't prep for subsequent dev" % (marker, ver_file))
            ver_content = ver_content.replace(marker,
                '"version": "%s"' % next_version)
        elif ver_file_type == "javascript":
            candidates = [
                ("single", "var VERSION = '%s';" % version),
                ("double", 'var VERSION = "%s";' % version),
            ]
            for quote_type, marker in candidates:
                if marker in ver_content:
                    break
            else:
                raise Error("couldn't find any candidate version marker in "
                    "`%s' content: can't prep for subsequent dev: %r"
                    % (ver_file, candidates))
            if quote_type == "single":
                ver_content = ver_content.replace(marker,
                    "var VERSION = '%s';" % next_version)
            else:
                ver_content = ver_content.replace(marker,
                    'var VERSION = "%s";' % next_version)
        elif ver_file_type == "python":
            marker = "__version_info__ = %r" % (version_info,)
            if marker not in ver_content:
                raise Error("couldn't find `%s' version marker in `%s' "
                    "content: can't prep for subsequent dev" % (marker, ver_file))
            ver_content = ver_content.replace(marker,
                "__version_info__ = %r" % (next_version_tuple,))
        elif ver_file_type == "version":
            ver_content = next_version
        else:
            raise Error("unknown ver_file_type: %r" % ver_file_type)
        if not dry_run:
            log.info("update version to '%s' in '%s'", next_version, ver_file)
            f = codecs.open(ver_file, 'w', 'utf-8')
            f.write(ver_content)
            f.close()

    if not dry_run:
        run('git commit %s %s -m "prep for future dev"' % (
            changes_path, ' '.join(version_files)))
        run('git push')
def cutarelease ( project_name , version_files , dry_run = False ) : dry_run_str = dry_run and " (dry-run)" or "" if not version_files : log . info ( "guessing version file" ) candidates = [ "package.json" , "VERSION.txt" , "VERSION" , "%s.py" % project_name , "lib/%s.py" % project_name , "%s.js" % project_name , "lib/%s.js" % project_name , ] for candidate in candidates : if exists ( candidate ) : version_files = [ candidate ] break else : raise Error ( "could not find a version file: specify its path or " "add one of the following to your project: '%s'" % "', '" . join ( candidates ) ) log . info ( "using '%s' as version file" , version_files [ 0 ] ) parsed_version_files = [ _parse_version_file ( f ) for f in version_files ] version_file_type , version_info = parsed_version_files [ 0 ] version = _version_from_version_info ( version_info ) # Confirm if not dry_run : answer = query_yes_no ( "* * *\n" "Are you sure you want cut a %s release?\n" "This will involved commits and a push." % version , default = "no" ) print "* * *" if answer != "yes" : log . info ( "user abort" ) return log . info ( "cutting a %s release%s" , version , dry_run_str ) # Checks: Ensure there is a section in changes for this version. changes_path = "CHANGES.md" changes_txt , changes , nyr = parse_changelog ( changes_path ) #pprint(changes) top_ver = changes [ 0 ] [ "version" ] if top_ver != version : raise Error ( "changelog '%s' top section says " "version %r, expected version %r: aborting" % ( changes_path , top_ver , version ) ) top_verline = changes [ 0 ] [ "verline" ] if not top_verline . endswith ( nyr ) : answer = query_yes_no ( "\n* * *\n" "The changelog '%s' top section doesn't have the expected\n" "'%s' marker. Has this been released already?" % ( changes_path , nyr ) , default = "yes" ) print "* * *" if answer != "no" : log . info ( "abort" ) return top_body = changes [ 0 ] [ "body" ] if top_body . strip ( ) == "(nothing yet)" : raise Error ( "top section body is `(nothing yet)': it looks like " "nothing has been added to this release" ) # Commits to prepare release. changes_txt_before = changes_txt changes_txt = changes_txt . replace ( " (not yet released)" , "" , 1 ) if not dry_run and changes_txt != changes_txt_before : log . info ( "prepare `%s' for release" , changes_path ) f = codecs . open ( changes_path , 'w' , 'utf-8' ) f . write ( changes_txt ) f . close ( ) run ( 'git commit %s -m "prepare for %s release"' % ( changes_path , version ) ) # Tag version and push. curr_tags = set ( t for t in _capture_stdout ( [ "git" , "tag" , "-l" ] ) . split ( '\n' ) if t ) if not dry_run and version not in curr_tags : log . info ( "tag the release" ) run ( 'git tag -a "%s" -m "version %s"' % ( version , version ) ) run ( 'git push --tags' ) # Optionally release. if exists ( "package.json" ) : answer = query_yes_no ( "\n* * *\nPublish to npm?" , default = "yes" ) print "* * *" if answer == "yes" : if dry_run : log . info ( "skipping npm publish (dry-run)" ) else : run ( 'npm publish' ) elif exists ( "setup.py" ) : answer = query_yes_no ( "\n* * *\nPublish to pypi?" , default = "yes" ) print "* * *" if answer == "yes" : if dry_run : 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. # - update changelog file next_version_info = _get_next_version_info ( version_info ) next_version = _version_from_version_info ( next_version_info ) log . info ( "prepare for future dev (version %s)" , next_version ) marker = "## " + changes [ 0 ] [ "verline" ] if marker . endswith ( nyr ) : marker = marker [ 0 : - len ( nyr ) ] if marker not in changes_txt : raise Error ( "couldn't find `%s' marker in `%s' " "content: can't prep for subsequent dev" % ( marker , changes_path ) ) next_verline = "%s %s%s" % ( marker . rsplit ( None , 1 ) [ 0 ] , next_version , nyr ) changes_txt = changes_txt . replace ( marker + '\n' , "%s\n\n(nothing yet)\n\n\n%s\n" % ( next_verline , marker ) ) if not dry_run : f = codecs . open ( changes_path , 'w' , 'utf-8' ) f . write ( changes_txt ) f . close ( ) # - update version file next_version_tuple = _tuple_from_version ( next_version ) for i , ver_file in enumerate ( version_files ) : ver_content = codecs . open ( ver_file , 'r' , 'utf-8' ) . read ( ) ver_file_type , ver_info = parsed_version_files [ i ] if ver_file_type == "json" : marker = '"version": "%s"' % version if marker not in ver_content : raise Error ( "couldn't find `%s' version marker in `%s' " "content: can't prep for subsequent dev" % ( marker , ver_file ) ) ver_content = ver_content . replace ( marker , '"version": "%s"' % next_version ) elif ver_file_type == "javascript" : candidates = [ ( "single" , "var VERSION = '%s';" % version ) , ( "double" , 'var VERSION = "%s";' % version ) , ] for quote_type , marker in candidates : if marker in ver_content : break else : raise Error ( "couldn't find any candidate version marker in " "`%s' content: can't prep for subsequent dev: %r" % ( ver_file , candidates ) ) if quote_type == "single" : ver_content = ver_content . replace ( marker , "var VERSION = '%s';" % next_version ) else : ver_content = ver_content . replace ( marker , 'var VERSION = "%s";' % next_version ) elif ver_file_type == "python" : marker = "__version_info__ = %r" % ( version_info , ) if marker not in ver_content : raise Error ( "couldn't find `%s' version marker in `%s' " "content: can't prep for subsequent dev" % ( marker , ver_file ) ) ver_content = ver_content . replace ( marker , "__version_info__ = %r" % ( next_version_tuple , ) ) elif ver_file_type == "version" : ver_content = next_version else : raise Error ( "unknown ver_file_type: %r" % ver_file_type ) if not dry_run : log . info ( "update version to '%s' in '%s'" , next_version , ver_file ) f = codecs . open ( ver_file , 'w' , 'utf-8' ) f . write ( ver_content ) f . close ( ) if not dry_run : run ( 'git commit %s %s -m "prep for future dev"' % ( changes_path , ' ' . join ( version_files ) ) ) run ( 'git push' )