parameters: (project_name, version_files, dry_run=False)
docstring: 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.
docstring_summary: Cut a release.
docstring_tokens: Cut a release .
function: 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')
function_tokens: 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' )