def setup_initial_release(self): """ """ if not choice(""" do you want keybump to setup the initial release?"""): fail('aborting, initial release not created.') info(""" ok, you asked for it.. """) release = self.create_initial_release() self.changelog.write(release.format_changelog_summary()) self.releases.append(release)
def new_release(self): """ :returns: instance of a `Release` object. """ rv = Release(self, self.last_release.version_num, datestr=self.today_str()) rv.bump() if rv.version_num in self.tags: fail("version `{}` is already tagged", rv.version_num) rv.set_summaries(self.get_changelog_summaries_since(self.current_tag)) return rv
def parse_releases(self): """ sets the releases property, parsed from the changelog summaries. """ self.releases = self.parse_changelog_to_releases() if len(self.releases) < 1: msg = "could not parse release from changelog history in {}.".format( self.changelog.path) # fail and exit.. if self.config.skip_interactive: fail(msg) else: # don't fail, set to initial version.. info(msg) self.setup_initial_release()
def _bump_num(self, version_num, bump_type=PATCH_BUMP): """ :param version_num: string version name. :param bump_type: version bump type. one of: major [x].0.0 minor x.[x].0 patch x.x.[x] :returns: concatenated string of the incremented version name. """ # split the version number into a list of ints.. try: version = [int(v) for v in version_num.split(".")] switch = { "major": lambda: [version[0] + 1, 0, 0], "minor": lambda: [version[0], version[1] + 1, 0], "patch": lambda: [version[0], version[1], version[2] + 1]} return ".".join(map(str, switch.get(bump_type)())) except ValueError: fail("version string: {} is an invalid format..", version_num)
def _bump_num(self, version_num, bump_type=PATCH_BUMP): """ :param version_num: string version name. :param bump_type: version bump type. one of: major [x].0.0 minor x.[x].0 patch x.x.[x] :returns: concatenated string of the incremented version name. """ # split the version number into a list of ints.. try: version = [int(v) for v in version_num.split(".")] switch = { "major": lambda: [version[0] + 1, 0, 0], "minor": lambda: [version[0], version[1] + 1, 0], "patch": lambda: [version[0], version[1], version[2] + 1] } return ".".join(map(str, switch.get(bump_type)())) except ValueError: fail("version string: {} is an invalid format..", version_num)
def parse_git_tags(self): """ loads the repo's git tags, sets the `current_tag` + `latest_tag` attrs. """ # what to do on first time run? no tags yet.. if not self.has_initial_tag: fail(""" looks as though the project has not been initialized yet for releases. create a tag for version: 0.0.0 and try again. sorry, we're still ghetto-riggin this script along.. workin on it..""") # todo: implement condition for new project without tags / version. # https://github.com/gregorynicholas/keybump/issues/2 self.latest_tag = get_latest_git_tag() # logger.info 'get_latest_git_tag:', get_latest_git_tag() self.current_tag = self._current_or_last_git_tag()
def set_version_in_file(filename, version_number, pattern): """ :param filename: :param version_number: :param pattern: """ changed = [] def inject_version(match): before, old, after = match.groups() changed.append(True) return before + version_number + after with open(filename, "r") as f: data_str = re.sub(r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern, inject_version, f.read()) if len(changed) < 1: fail("could not set init file version. pattern {} not found in {}", pattern, filename) write(filename, data_str)
def set_version_in_file(filename, version_number, pattern): """ :param filename: :param version_number: :param pattern: """ changed = [] def inject_version(match): before, old, after = match.groups() changed.append(True) return before + version_number + after with open(filename, "r") as f: data_str = re.sub( r"^(\s*%s\s*=\s*')(.+?)(')(?sm)" % pattern, inject_version, f.read()) if len(changed) < 1: fail( "could not set init file version. pattern {} not found in {}", pattern, filename) write(filename, data_str)
def ensure_clean_index(skip_interactive=False, callback=None): """ ensures the current git staging index has no uncommitted or stashed changes. :param skip_interactive: boolean flag to skip getting input from a cli. :param callback: recursive callback function. """ if not has_unstaged_changes() and not has_uncommitted_changes(): if callback: return callback(skip_interactive, callback) return True if callback is None: callback = ensure_clean_index files = git_diff_files() msg = """ aborting.. un[stashed/committed] changes. fix uncommitted files by stashing, committing, or resetting the following files: {} """.format("\n ".join(files)) if skip_interactive: fail(msg) # clean the index.. info(msg) if not choice("want keybump to snort ..achem stash.. your changes?"): fail("aborting.. un[stashed/committed] changes..") info(""" ok, you asked for it.. """) git_stash() if callback: return callback(skip_interactive, callback)
def parse_changelog_to_releases(self): """ parses the contents of the changelog file, and returns a list of `Release` objects from the changelog summary. :returns: list of instance of a `Release` objects. """ result = [] # todo: need to handle encoding.. with self.changelog.open() as f: lineiter = iter(f) hasdata = False version_num = None for line in lineiter: logger.debug('line: {}'.format(line)) hasdata = True # parse the last version.. ver_match = version.VERSION_RE.search(line.strip()) if ver_match is None: logger.warn('continuing..!') continue version_num = ver_match.group(1).strip() # logger.debug('version_num: {}'.format(version_num)) value = lineiter.next() # logger.debug('value: {}'.format(value)) if not value.count(self.config.summary_separator): # TODO: this is currently breaking things.. # logger.warn('continuing..!') # continue pass # parse the release data and codename.. while 1: release_header = lineiter.next().strip() if release_header: logger.debug( 'release_header: {}'.format(release_header)) break rel_match = self.release_header_re.search(release_header) if rel_match is None: # TODO: raise exception here? logger.warn('no release header found!') # continue datestr, codename = rel_match.groups() logger.debug('datestr: {}'.format(datestr)) # parse the change summary messages.. summaries = [] while 1: try: summary = lineiter.next() except StopIteration, e: break if summary: if len(summary.strip()) > 0: # strip summary_item_fmt front beginning of item.. total = len(self.config.summaryitem_fmt) if summary[0:total] == self.config.summaryitem_fmt: summary = summary[total:] # remove newline char at end.. summaries.append(summary[:-1]) else: break release = Release(self, version_num, datestr=datestr, summaries=summaries) logger.debug('release: {}'.format(release)) logger.debug('summaries: {}'.format(summaries)) result.append(release) if len(result) == 0 and hasdata: fail("""unable to parse the changelog contents. format not recognized by parser: {}""".format(self.changelog.contents())) return result