def _update_version(self, version): """Find out where to change the version and change it. There are three places where the version can be defined. The first one is an explicitly defined Python file with a ``__version__`` attribute. The second one is some version.txt that gets read by setup.py. The third is directly in setup.py. """ if self.get_python_file_version(): setup_cfg = pypi.SetupConfig() filename = setup_cfg.python_file_with_version() lines = utils.read_text_file(filename).split('\n') for index, line in enumerate(lines): match = UNDERSCORED_VERSION_PATTERN.search(line) if match: lines[index] = "__version__ = '%s'" % version contents = '\n'.join(lines) open(filename, 'w').write(contents) logger.info("Set __version__ in %s to %r", filename, version) return version_filenames = ['version'] for extension in TXT_EXTENSIONS: version_filenames.append('.'.join(['version', extension])) versionfile = self.filefind(version_filenames) if versionfile: # We have a version.txt file but does it match the setup.py # version (if any)? setup_version = self.get_setup_py_version() if not setup_version or (setup_version == self.get_version_txt_version()): open(versionfile, 'w').write(version + '\n') logger.info("Changed %s to %r", versionfile, version) return good_version = "version = '%s'" % version line_number = 0 setup_lines = utils.read_text_file('setup.py').split('\n') for line_number, line in enumerate(setup_lines): match = VERSION_PATTERN.search(line) if match: logger.debug("Matching version line found: %r", line) if line.startswith(' '): # oh, probably ' version = 1.0,' line. indentation = line.split('version')[0] # Note: no spaces around the '='. good_version = indentation + "version='%s'," % version setup_lines[line_number] = good_version contents = '\n'.join(setup_lines) open('setup.py', 'w').write(contents) logger.info("Set setup.py's version to %r", version) return logger.error( "We could read a version from setup.py, but could not write it " + "back. See " + "http://zestreleaser.readthedocs.org/en/latest/versions.html " + "for hints.") raise RuntimeError("Cannot set version")
def _update_history(self): """Update the history file""" version = self.data['new_version'] history = self.vcs.history_file() if not history: logger.warn("No history file found") return history_lines = read_text_file(history).split('\n') headings = utils.extract_headings_from_history(history_lines) if not len(headings): logger.warn("No detectable existing version headings in the " "history file.") inject_location = 0 underline_char = '-' else: first = headings[0] inject_location = first['line'] underline_line = first['line'] + 1 try: underline_char = history_lines[underline_line][0] except IndexError: logger.debug("No character on line below header.") underline_char = '-' header = '%s (unreleased)' % version inject = [header, underline_char * len(header), '', self.data['nothing_changed_yet'], '', ''] history_lines[inject_location:inject_location] = inject contents = '\n'.join(history_lines) write_text_file(history, contents) logger.info("Injected new section into the history: %r", header)
def getHistoryLines(vcs): history = vcs.history_file() if not history: return None, None history_lines, history_encoding = utils.read_text_file(history) history_lines = history_lines.split('\n') return history_lines, history_encoding
def _update_history(self): """Update the history file""" version = self.data['new_version'] history = self.vcs.history_file() if not history: logger.warn("No history file found") return history_lines, history_encoding = read_text_file(history) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not len(headings): logger.warn("No detectable existing version headings in the " "history file.") inject_location = 0 underline_char = '-' else: first = headings[0] inject_location = first['line'] underline_line = first['line'] + 1 try: underline_char = history_lines[underline_line][0] except IndexError: logger.debug("No character on line below header.") underline_char = '-' header = '%s (unreleased)' % version inject = [header, underline_char * len(header), '', self.data['nothing_changed_yet'], '', ''] history_lines[inject_location:inject_location] = inject contents = '\n'.join(history_lines) write_text_file(history, contents, history_encoding) logger.info("Injected new section into the history: %r", header)
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ default_location = None config = self.setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') history_file = self.vcs.history_file(location=default_location) if not history_file: logger.warn("No history file found") self.data['history_lines'] = None self.data['history_file'] = None self.data['history_encoding'] = None return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not len(headings): logger.error("No detectable version heading in the history " "file %s", history_file) sys.exit(1) good_heading = self.data['history_header'] % self.data # ^^^ history_header is a string with %(abc)s replacements. line = headings[0]['line'] previous = history_lines[line] history_lines[line] = good_heading logger.debug("Set heading from %r to %r.", previous, good_heading) history_lines[line + 1] = utils.fix_rst_heading( heading=good_heading, below=history_lines[line + 1]) logger.debug("Set line below heading to %r", history_lines[line + 1]) self.data['history_lines'] = history_lines self.data['history_file'] = history_file self.data['history_encoding'] = history_encoding # TODO: add line number where an extra changelog entry can be # inserted. # Look for 'Nothing changed yet' under the latest header. Not # nice if this text ends up in the changelog. Did nothing happen? start = headings[0]['line'] if len(headings) > 1: end = headings[1]['line'] else: end = -1 for line in history_lines[start:end]: if self.data['nothing_changed_yet'] in line: if not utils.ask( "WARNING: Changelog contains %r. Are you sure you " "want to release?" % self.data['nothing_changed_yet'], default=False): logger.info("You can use the 'lasttaglog' command to " "see the commits since the last tag.") sys.exit(0) break
def prepare_entrypoint_documentation(data): """Place the generated entrypoint doc in the source structure.""" if data['name'] != 'zest.releaser': # We're available everywhere, but we're only intended for # zest.releaser internal usage. return target = os.path.join(data['reporoot'], 'doc', 'source', 'entrypoints.rst') marker = '.. ### AUTOGENERATED FROM HERE ###' result = [] lines, encoding = read_text_file(target) for line in lines.split('\n'): line = line.rstrip() if line == marker: break result.append(line) result.append(marker) result.append('') for name, datadict in ( ('prerelease', prerelease.DATA), ('release', release.DATA), ('postrelease', postrelease.DATA)): heading = '%s data dict items' % name.capitalize() result.append(heading) result.append('-' * len(heading)) result.append('') for key in sorted(datadict.keys()): result.append(key) result.append(' ' + datadict[key]) result.append('') write_text_file(target, '\n'.join(result), encoding) print("Wrote entry point documentation to %s" % target)
def prepare_entrypoint_documentation(data): """Place the generated entrypoint doc in the source structure.""" if data['name'] != 'zest.releaser': # We're available everywhere, but we're only intended for # zest.releaser internal usage. return target = os.path.join(data['reporoot'], 'doc', 'source', 'entrypoints.rst') marker = '.. ### AUTOGENERATED FROM HERE ###' result = [] lines, encoding = read_text_file(target) for line in lines.split('\n'): line = line.rstrip() if line == marker: break result.append(line) result.append(marker) result.append('') for name, datadict in (('prerelease', prerelease.DATA), ('release', release.DATA), ('postrelease', postrelease.DATA)): heading = '%s data dict items' % name.capitalize() result.append(heading) result.append('-' * len(heading)) result.append('') for key in sorted(datadict.keys()): result.append(key) result.append(' ' + datadict[key]) result.append('') write_text_file(target, '\n'.join(result), encoding) print("Wrote entry point documentation to %s" % target)
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ default_location = None config = self.setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') history_file = self.vcs.history_file(location=default_location) if not history_file: logger.warn("No history file found") self.data['history_lines'] = None self.data['history_file'] = None self.data['history_encoding'] = None return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not len(headings): logger.error( "No detectable version heading in the history " "file %s", history_file) sys.exit(1) good_heading = self.data['history_header'] % self.data # ^^^ history_header is a string with %(abc)s replacements. line = headings[0]['line'] previous = history_lines[line] history_lines[line] = good_heading logger.debug("Set heading from %r to %r.", previous, good_heading) history_lines[line + 1] = utils.fix_rst_heading( heading=good_heading, below=history_lines[line + 1]) logger.debug("Set line below heading to %r", history_lines[line + 1]) self.data['history_lines'] = history_lines self.data['history_file'] = history_file self.data['history_encoding'] = history_encoding # TODO: add line number where an extra changelog entry can be # inserted. # Look for 'Nothing changed yet' under the latest header. Not # nice if this text ends up in the changelog. Did nothing happen? start = headings[0]['line'] if len(headings) > 1: end = headings[1]['line'] else: end = -1 for line in history_lines[start:end]: if self.data['nothing_changed_yet'] in line: if not utils.ask( "WARNING: Changelog contains %r. Are you sure you " "want to release?" % self.data['nothing_changed_yet'], default=False): logger.info("You can use the 'lasttaglog' command to " "see the commits since the last tag.") sys.exit(0) break
def cleanup_changelog(data): """Cleanup empty headers. We call this twice: in prereleaser.before and prereleaser.middle. In 'before', we are too early and zest.releaser has not looked for the history file yet. But we try 'CHANGES.rst' ourselves. In 'middle' we are a bit too late, as zest.releaser has already complained when it found the NOTHING_CHANGED_YET value in the history. So we call this twice, which should be fine. """ # The history_file is probably not set yet, as we are called too early. # That might change subtly in future zest.releaser versions, so let's check # it anyway. history_file = data.get('history_file') if history_file: contents = '\n'.join(data['history_lines']) encoding = data['history_encoding'] else: # We do not want to copy the logic from zest.releaser that tries to # find the history file, but we can check the most obvious spot. history_file = 'CHANGES.rst' if not os.path.exists(history_file): print('Cannot cleanup history, will try again later.') return contents, encoding = read_text_file(history_file) orig_contents = contents changed = False for header in ALL_HEADERS: if header in contents: contents = contents.replace(header, '') changed = True if not changed: return write_text_file( history_file, contents, encoding=encoding) print("Cleaned up empty headers from history file {}".format(history_file)) # Update the data, otherwise our work may get overwritten. data['history_lines'] = contents.split('\n') if not os.path.isdir('.git'): print('Not a git checkout, cannot commit.') return g = git.Git('.') message = "Cleaned up empty headers from changelog.\n\n[ci skip]" print(g.diff(history_file)) msg = "Commit changes?" if not ask(msg, default=True): # Restore original contents. write_text_file( history_file, orig_contents, encoding=encoding) sys.exit() print("Committing changes.") print(g.add(history_file)) print(g.commit(message=message))
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ self.data['history_lines'] = [] self.data['history_file'] = None self.data['history_encoding'] = None self.data['headings'] = [] self.data['history_last_release'] = '' self.data['history_insert_line_here'] = 0 default_location = None config = self.setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') history_file = self.vcs.history_file(location=default_location) self.data['history_file'] = history_file if not history_file: logger.warn("No history file found") return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not headings: logger.warn( "No detectable version heading in the history " "file %s", history_file) return self.data['history_lines'] = history_lines self.data['history_encoding'] = history_encoding self.data['headings'] = headings # Grab last header. start = headings[0]['line'] if len(headings) > 1: # Include the next header plus underline, as this is nice # to show in the history_last_release. end = headings[1]['line'] + 2 else: end = len(history_lines) history_last_release = '\n'.join(history_lines[start:end]) self.data['history_last_release'] = history_last_release # Add line number where an extra changelog entry can be inserted. Can # be useful for entry points. 'start' is the header, +1 is the # underline, +2 is probably an empty line, so then we should take +3. # Or rather: the first non-empty line. insert = start + 2 while insert < end: if history_lines[insert].strip(): break insert += 1 self.data['history_insert_line_here'] = insert
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ self.data['history_lines'] = [] self.data['history_file'] = None self.data['history_encoding'] = None self.data['headings'] = [] self.data['history_last_release'] = '' self.data['history_insert_line_here'] = 0 default_location = None config = self.setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') history_file = self.vcs.history_file(location=default_location) self.data['history_file'] = history_file if not history_file: logger.warn("No history file found") return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') headings = utils.extract_headings_from_history(history_lines) if not headings: logger.warn("No detectable version heading in the history " "file %s", history_file) return self.data['history_lines'] = history_lines self.data['history_encoding'] = history_encoding self.data['headings'] = headings # Grab last header. start = headings[0]['line'] if len(headings) > 1: # Include the next header plus underline, as this is nice # to show in the history_last_release. end = headings[1]['line'] + 2 else: end = len(history_lines) history_last_release = '\n'.join(history_lines[start:end]) self.data['history_last_release'] = history_last_release # Add line number where an extra changelog entry can be inserted. Can # be useful for entry points. 'start' is the header, +1 is the # underline, +2 is probably an empty line, so then we should take +3. # Or rather: the first non-empty line. insert = start + 2 while insert < end: if history_lines[insert].strip(): break insert += 1 self.data['history_insert_line_here'] = insert
def _update_python_file_version(self, version): setup_cfg = pypi.SetupConfig() filename = setup_cfg.python_file_with_version() lines, encoding = utils.read_text_file(filename) lines = lines.split('\n') for index, line in enumerate(lines): match = UNDERSCORED_VERSION_PATTERN.search(line) if match: lines[index] = "__version__ = '%s'" % version contents = '\n'.join(lines) utils.write_text_file(filename, contents, encoding) logger.info("Set __version__ in %s to %r", filename, version)
def _update_python_file_version(self, version): filename = self.setup_cfg.python_file_with_version() lines, encoding = utils.read_text_file( filename, fallback_encoding=self.fallback_encoding, ) for index, line in enumerate(lines): match = UNDERSCORED_VERSION_PATTERN.search(line) if match: lines[index] = "__version__ = '%s'" % version contents = '\n'.join(lines) utils.write_text_file(filename, contents, encoding) logger.info("Set __version__ in %s to '%s'", filename, version)
def fillchangelog(context): default_location = None setup_cfg = pypi.SetupConfig() config = setup_cfg.config if config and config.has_option('zest.releaser', 'history_file'): default_location = config.get('zest.releaser', 'history_file') vcs = zest.releaser.choose.version_control() history_file = vcs.history_file(location=default_location) if history_file: try: found = zest.releaser.utils.get_last_tag(vcs) log_command = vcs.cmd_log_since_tag(found) except SystemExit: log_command = get_all_commits_command(vcs) if log_command: data = execute_command(log_command) pretty_data = prettyfy_logs(data, vcs) print('These are all the commits since the last tag:') print('') print('\n'.join(pretty_data)) if zest.releaser.utils.ask('Do you want to add those commits to the CHANGES file?', True): new_history_lines = [] history_lines, history_encoding = read_text_file(history_file) history_lines = history_lines.split('\n') for line in history_lines: current_position = history_lines.index(line) new_history_lines.append(line) if line.lower().find('unreleased') != -1: # current_position + 1 == ---------------- # current_position + 2 == blank # current_position + 3 == - Nothing changed yet. # current_position + 4 == blank new_history_lines.append(history_lines[current_position + 1]) new_history_lines.append(history_lines[current_position + 2]) new_history_lines.extend(pretty_data) new_history_lines.extend(history_lines[current_position + 4:]) break contents = '\n'.join(new_history_lines) write_text_file(history_file, contents) msg = 'Update changelog' commit_cmd = vcs.cmd_commit(msg) commit = execute_command(commit_cmd) print(commit) else: print('History file not found. Skipping.')
def _update_version(self, version): """Find out where to change the version and change it. There are three places where the version can be defined. The first one is an explicitly defined Python file with a ``__version__`` attribute. The second one is some version.txt that gets read by setup.py. The third is directly in setup.py. """ if self.get_python_file_version(): self._update_python_file_version(version) return version_filenames = ['version'] version_filenames.extend( ['.'.join(['version', extension]) for extension in TXT_EXTENSIONS]) versionfile = self.filefind(version_filenames) if versionfile: # We have a version.txt file but does it match the setup.py # version (if any)? setup_version = self.get_setup_py_version() if not setup_version or (setup_version == self.get_version_txt_version()): with open(versionfile, 'w') as f: f.write(version + '\n') logger.info("Changed %s to %r", versionfile, version) return good_version = "version = '%s'" % version line_number = 0 setup_lines, encoding = utils.read_text_file('setup.py') setup_lines = setup_lines.split('\n') for line_number, line in enumerate(setup_lines): if VERSION_PATTERN.search(line): logger.debug("Matching version line found: %r", line) if line.startswith(' '): # oh, probably ' version = 1.0,' line. indentation = line.split('version')[0] # Note: no spaces around the '='. good_version = indentation + "version='%s'," % version setup_lines[line_number] = good_version utils.write_text_file('setup.py', '\n'.join(setup_lines), encoding) logger.info("Set setup.py's version to %r", version) return logger.error( "We could read a version from setup.py, but could not write it " "back. See " "http://zestreleaser.readthedocs.io/en/latest/versions.html " "for hints.") raise RuntimeError("Cannot set version")
def get_python_file_version(self): setup_cfg = pypi.SetupConfig() if not setup_cfg.python_file_with_version(): return lines = utils.read_text_file( setup_cfg.python_file_with_version()).split('\n') for line in lines: match = UNDERSCORED_VERSION_PATTERN.search(line) if match: logger.debug("Matching __version__ line found: %r", line) line = line.lstrip('__version__').strip() line = line.lstrip('=').strip() line = line.replace('"', '').replace("'", "") return utils.strip_version(line)
def prepare_entrypoint_documentation(data): """Place the generated entrypoint doc in the source structure.""" if data['name'] != 'zest.releaser': # We're available everywhere, but we're only intended for # zest.releaser internal usage. return target = os.path.join(data['reporoot'], 'doc', 'source', 'entrypoints.rst') marker = '.. ### AUTOGENERATED FROM HERE ###' result = [] lines, encoding = read_text_file(target) for line in lines: line = line.rstrip() if line == marker: break result.append(line) result.append(marker) result.append('') for name, datadict in ( ('common', baserelease.DATA), ('prerelease', prerelease.DATA), ('release', release.DATA), ('postrelease', postrelease.DATA), ('addchangelogentry', addchangelogentry.DATA), ('bumpversion', bumpversion.DATA), ): if name == 'common': heading = '%s data dict items' % name.capitalize() else: # quote the command name heading = '``%s`` data dict items' % name result.append(heading) result.append('-' * len(heading)) result.append('') if name == 'common': result.append('These items are shared among all commands.') result.append('') for key in sorted(datadict.keys()): if name != 'common' and datadict[key] == baserelease.DATA.get(key): # The key is already in common data, with the same value. continue result.append(key) result.append(' ' + datadict[key]) result.append('') write_text_file(target, '\n'.join(result), encoding) print("Wrote entry point documentation to %s" % target)
def get_python_file_version(self): setup_cfg = pypi.SetupConfig() if not setup_cfg.python_file_with_version(): return lines, encoding = utils.read_text_file( setup_cfg.python_file_with_version()) encoding # noqa, unused variable lines = lines.splitlines() for line in lines: match = UNDERSCORED_VERSION_PATTERN.search(line) if match: logger.debug("Matching __version__ line found: %r", line) line = line.lstrip('__version__').strip() line = line.lstrip('=').strip() line = line.replace('"', '').replace("'", "") return utils.strip_version(line)
def get_python_file_version(self): python_version_file = self.setup_cfg.python_file_with_version() if not python_version_file: return lines, encoding = utils.read_text_file( python_version_file, fallback_encoding=self.fallback_encoding, ) encoding # noqa, unused variable for line in lines: match = UNDERSCORED_VERSION_PATTERN.search(line) if match: logger.debug("Matching __version__ line found: '%s'", line) line = line.lstrip('__version__').strip() line = line.lstrip('=').strip() line = line.replace('"', '').replace("'", "") return utils.strip_version(line)
def _update_python_file_version(self, version): filename = self.setup_cfg.python_file_with_version() lines, encoding = utils.read_text_file( filename, fallback_encoding=self.fallback_encoding, ) good_version = "__version__ = '%s'" % version for index, line in enumerate(lines): if UNDERSCORED_VERSION_PATTERN.search(line): lines[index] = ( good_version.replace("'", '"') if '"' in line else good_version ) contents = '\n'.join(lines) utils.write_text_file(filename, contents, encoding) logger.info("Set __version__ in %s to '%s'", filename, version)
def getBuildoutHistoryLines(): history_lines, history_encoding = utils.read_text_file(BUILDOUTHISTORYFILE) history_lines = history_lines.split('\n') return history_lines, history_encoding
def _grab_history(self): """Calculate the needed history/changelog changes Every history heading looks like '1.0 b4 (1972-12-25)'. Extract them, check if the first one matches the version and whether it has a the current date. """ self.data['history_lines'] = [] self.data['history_file'] = None self.data['history_encoding'] = None self.data['headings'] = [] self.data['history_last_release'] = '' self.data['history_insert_line_here'] = 0 default_location = self.pypiconfig.history_file() fallback_encoding = self.pypiconfig.encoding() history_file = self.vcs.history_file(location=default_location) self.data['history_file'] = history_file if not history_file: logger.warning("No history file found") return logger.debug("Checking %s", history_file) history_lines, history_encoding = read_text_file( history_file, fallback_encoding=fallback_encoding, ) headings = utils.extract_headings_from_history(history_lines) if not headings: logger.warning( "No detectable version heading in the history " "file %s", history_file) return self.data['history_lines'] = history_lines self.data['history_encoding'] = history_encoding self.data['headings'] = headings # Grab last header. start = headings[0]['line'] if len(headings) > 1: # Include the next header plus underline, as this is nice # to show in the history_last_release. end = headings[1]['line'] + 2 else: end = len(history_lines) history_last_release = '\n'.join(history_lines[start:end]) self.data['history_last_release'] = history_last_release # Does the latest release header have a release date in it? # This is useful to know, especially when using tools like towncrier # to handle the changelog. released = DATE_PATTERN.match(headings[0]['date']) self.data['has_released_header'] = released # Add line number where an extra changelog entry can be inserted. Can # be useful for entry points. 'start' is the header, +1 is the # underline, +2 is probably an empty line, so then we should take +3. # Or rather: the first non-empty line. insert = start + 2 while insert < end: if history_lines[insert].strip(): break insert += 1 self.data['history_insert_line_here'] = insert