aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfuzzyray <fuzzyray@gentoo.org>2009-05-05 17:39:24 +0000
committerfuzzyray <fuzzyray@gentoo.org>2009-05-05 17:39:24 +0000
commitc819d146be6bce86d97019494173253e71b85d2f (patch)
tree200d00c2b9a420540ff9c4e0d8b3080b762fb562 /pym/gentoolkit/equery/changes.py
parentAdd some useful informations when using $EDITOR. (diff)
downloadgentoolkit-c819d146be6bce86d97019494173253e71b85d2f.tar.gz
gentoolkit-c819d146be6bce86d97019494173253e71b85d2f.tar.bz2
gentoolkit-c819d146be6bce86d97019494173253e71b85d2f.zip
Rearrange trunk to support gentoolkit version 0.3. Split into gentoolkit, gentoolkit-dev, and deprecated. Import djanderson's work on the gentoolkit library and equery
svn path=/trunk/gentoolkit/; revision=589
Diffstat (limited to 'pym/gentoolkit/equery/changes.py')
-rw-r--r--pym/gentoolkit/equery/changes.py336
1 files changed, 336 insertions, 0 deletions
diff --git a/pym/gentoolkit/equery/changes.py b/pym/gentoolkit/equery/changes.py
new file mode 100644
index 0000000..b7644be
--- /dev/null
+++ b/pym/gentoolkit/equery/changes.py
@@ -0,0 +1,336 @@
+# Copyright(c) 2009, Gentoo Foundation
+#
+# Licensed under the GNU General Public License, v2 or higher
+#
+# $Header: $
+
+"""Display the Gentoo ChangeLog entry for the latest installable version of a
+given package
+"""
+
+# Move to Imports sections when Python 2.6 is stable
+from __future__ import with_statement
+
+__author__ = 'Douglas Anderson'
+__docformat__ = 'epytext'
+
+# =======
+# Imports
+# =======
+
+import os
+import sys
+from getopt import gnu_getopt, GetoptError
+
+from portage.versions import pkgsplit
+
+import gentoolkit.pprinter as pp
+from gentoolkit import errors
+from gentoolkit.equery import format_options, mod_usage
+from gentoolkit.helpers2 import find_best_match, find_packages
+from gentoolkit.package import Package, VersionMatch
+
+# =======
+# Globals
+# =======
+
+QUERY_OPTS = {
+ 'onlyLatest': False,
+ 'showFullLog': False,
+ 'limit': None,
+ 'from': None,
+ 'to': None
+}
+
+# =========
+# Functions
+# =========
+
+def print_help(with_description=True):
+ """Print description, usage and a detailed help message.
+
+ @type with_description: bool
+ @param with_description: if true, print module's __doc__ string
+ """
+
+ if with_description:
+ print __doc__.strip()
+ print
+ print mod_usage(mod_name="changes")
+ print
+ print pp.emph("examples")
+ print (" c portage # show latest visible "
+ "version's entry")
+ print " c portage --full --limit=3 # show 3 latest entries"
+ print " c '=sys-apps/portage-2.1.6*' # use atom syntax"
+ print " c portage --from=2.2_rc20 --to=2.2_rc30 # use version ranges"
+ print
+ print pp.command("options")
+ print format_options((
+ (" -h, --help", "display this help message"),
+ (" -l, --latest", "display only the latest ChangeLog entry"),
+ (" -f, --full", "display the full ChangeLog"),
+ (" --limit=NUM",
+ "limit the number of entries displayed (with --full)"),
+ (" --from=VER", "set which version to display from"),
+ (" --to=VER", "set which version to display to"),
+ ))
+
+
+def get_logpath(pkg):
+ """Test that the package's ChangeLog path is valid and readable, else
+ die.
+
+ @type pkg: gentoolkit.package.Package
+ @param pkg: package to find logpath for
+ @rtype: str
+ @return: a path to a readable ChangeLog
+ """
+
+ logpath = os.path.join(pkg.get_package_path(), 'ChangeLog')
+ if not os.path.isfile(logpath) or not os.access(logpath, os.R_OK):
+ pp.die(1, "%s does not exist or is unreadable"
+ % pp.path(logpath))
+
+ return logpath
+
+
+def get_match(query):
+ """Find a valid package to get the ChangeLog path from or raise
+ GentoolkitNoMatches.
+ """
+
+ match = matches = None
+ match = find_best_match(query)
+
+ if not match:
+ matches = find_packages(query, include_masked=True)
+ else:
+ matches = [match]
+
+ if not matches:
+ pp.print_warn("Try using an unversioned query with "
+ "--from and --to.")
+ raise errors.GentoolkitNoMatches(query)
+
+ return matches[0]
+
+
+def index_changelog(entries):
+ """Convert the list from split_changelog into a dict with VersionMatch
+ instance as the index.
+
+ @todo: UPDATE THIS
+ @type entries: list
+ @param entries: output of split_changelog
+ @rtype: dict
+ @return: dict with gentoolkit.package.Package instances as keys and the
+ corresponding ChangeLog entree as its value
+ """
+
+ result = []
+ for entry in entries:
+ # Extract the package name from the entry, ex:
+ # *xterm-242 (07 Mar 2009) => xterm-242
+ pkg_name = entry.split(' ', 1)[0].lstrip('*')
+ pkg_split = pkgsplit(pkg_name)
+ result.append(
+ (VersionMatch(op="=", ver=pkg_split[1], rev=pkg_split[2]), entry))
+
+ return result
+
+
+def is_ranged(atom):
+ """Return True if an atom string appears to be ranged, else False."""
+
+ return atom.startswith(('~', '<', '>')) or atom.endswith('*')
+
+
+def parse_module_options(module_opts):
+ """Parse module options and update GLOBAL_OPTS"""
+
+ opts = (x[0] for x in module_opts)
+ posargs = (x[1] for x in module_opts)
+ for opt, posarg in zip(opts, posargs):
+ if opt in ('-h', '--help'):
+ print_help()
+ sys.exit(0)
+ elif opt in ('-f', '--full'):
+ QUERY_OPTS['showFullLog'] = True
+ elif opt in ('-l', '--latest'):
+ QUERY_OPTS['onlyLatest'] = True
+ elif opt in ('--limit',):
+ set_limit(posarg)
+ elif opt in ('--from',):
+ set_from(posarg)
+ elif opt in ('--to',):
+ set_to(posarg)
+
+
+def print_matching_entries(indexed_entries, pkg, first_run):
+ """Print only the entries which interect with the pkg version."""
+
+ from_restriction = QUERY_OPTS['from']
+ to_restriction = QUERY_OPTS['to']
+
+ for entry_set in indexed_entries:
+ i, entry = entry_set
+ # a little hackery, since versionmatch doesn't store the
+ # package key, but intersects checks that it matches.
+ i.key = pkg.key
+ if from_restriction or to_restriction:
+ if from_restriction and not from_restriction.match(i):
+ continue
+ if to_restriction and not to_restriction.match(i):
+ continue
+ elif not pkg.intersects(i):
+ continue
+
+ if not first_run:
+ print "\n"
+ print entry.strip()
+ first_run = False
+
+ return first_run
+
+
+def set_from(posarg):
+ """Set a starting version to filter the ChangeLog with or die if posarg
+ is not a valid version.
+ """
+
+ pkg_split = pkgsplit('null-%s' % posarg)
+
+ if pkg_split and not is_ranged(posarg):
+ ver_match = VersionMatch(
+ op=">=",
+ ver=pkg_split[1],
+ rev=pkg_split[2] if pkg_split[2] != 'r0' else '')
+ QUERY_OPTS['from'] = ver_match
+ else:
+ err = "Module option --from requires valid unranged version (got '%s')"
+ pp.print_error(err % posarg)
+ print
+ print_help(with_description=False)
+ sys.exit(2)
+
+
+def set_limit(posarg):
+ """Set a limit in QUERY_OPTS on how many ChangeLog entries to display or
+ die if posarg is not an integer.
+ """
+
+ if posarg.isdigit():
+ QUERY_OPTS['limit'] = int(posarg)
+ else:
+ err = "Module option --limit requires integer (got '%s')"
+ pp.print_error(err % posarg)
+ print
+ print_help(with_description=False)
+ sys.exit(2)
+
+
+def set_to(posarg):
+ """Set an ending version to filter the ChangeLog with or die if posarg
+ is not a valid version.
+ """
+
+ pkg_split = pkgsplit('null-%s' % posarg)
+ if pkg_split and not is_ranged(posarg):
+ ver_match = VersionMatch(
+ op="<=",
+ ver=pkg_split[1],
+ rev=pkg_split[2] if pkg_split[2] != 'r0' else '')
+ QUERY_OPTS['to'] = ver_match
+ else:
+ err = "Module option --to requires valid unranged version (got '%s')"
+ pp.print_error(err % posarg)
+ print
+ print_help(with_description=False)
+ sys.exit(2)
+
+
+def split_changelog(logpath):
+ """Split the changelog up into individual entries.
+
+ @type logpath: str
+ @param logpath: valid path to ChangeLog file
+ @rtype: list
+ @return: individual ChangeLog entrees
+ """
+
+ result = []
+ partial_entries = []
+ with open(logpath) as log:
+ for line in log:
+ if line.startswith('#'):
+ continue
+ elif line.startswith('*'):
+ # Append last entry to result...
+ entry = ''.join(partial_entries)
+ if entry and not entry.isspace():
+ result.append(entry)
+ # ... and start a new entry
+ partial_entries = [line]
+ else:
+ partial_entries.append(line)
+ else:
+ # Append the final entry
+ entry = ''.join(partial_entries)
+ result.append(entry)
+
+ return result
+
+
+def main(input_args):
+ """Parse input and run the program"""
+
+ short_opts = "hlf"
+ long_opts = ('help', 'full', 'from=', 'latest', 'limit=', 'to=')
+
+ try:
+ module_opts, queries = gnu_getopt(input_args, short_opts, long_opts)
+ except GetoptError, err:
+ pp.print_error("Module %s" % err)
+ print
+ print_help(with_description=False)
+ sys.exit(2)
+
+ parse_module_options(module_opts)
+
+ if not queries:
+ print_help()
+ sys.exit(2)
+
+ first_run = True
+ for query in queries:
+ if not first_run:
+ print
+
+ ranged_query = None
+ if is_ranged(query):
+ # Raises GentoolkitInvalidCPV here if invalid
+ ranged_query = Package(query)
+
+ pkg = get_match(query)
+ logpath = get_logpath(pkg)
+ log_entries = split_changelog(logpath)
+ indexed_entries = index_changelog(log_entries)
+
+ #
+ # Output
+ #
+
+ if QUERY_OPTS['onlyLatest']:
+ print log_entries[0].strip()
+ elif QUERY_OPTS['showFullLog']:
+ end = QUERY_OPTS['limit'] or len(log_entries)
+ for entry in log_entries[:end]:
+ print entry
+ first_run = False
+ else:
+ if ranged_query:
+ pkg = ranged_query
+ first_run = print_matching_entries(indexed_entries, pkg, first_run)
+
+ first_run = False