diff --git a/bin/euscan b/bin/euscan index e7708e3..c4da8a4 100755 --- a/bin/euscan +++ b/bin/euscan @@ -6,51 +6,58 @@ Distributed under the terms of the GNU General Public License v2 from __future__ import print_function -""" Meta """ + +# Meta + __author__ = "Corentin Chary (iksaif)" __email__ = "corentin.chary@gmail.com" __version__ = "git" __productname__ = "euscan" __description__ = "A tool to detect new upstream releases." -__version__ = "git" -""" Imports """ + +# Imports import sys import getopt import errno import httplib -from portage.output import white, yellow, turquoise, green, EOutput +from portage.output import white, yellow, turquoise, green from portage.exception import AmbiguousPackageName from gentoolkit import pprinter as pp from gentoolkit.eclean.search import (port_settings) from gentoolkit.errors import GentoolkitException -import euscan -from euscan import CONFIG +from euscan import CONFIG, output from euscan.scan import scan_upstream -""" Globals """ + +# Globals + +def exit_helper(status): + if CONFIG["format"]: + print(output.get_formatted_output()) + sys.exit(status) -def setupSignals(): - """ This block ensures that ^C interrupts are handled quietly. """ +def setup_signals(): + """This block ensures that ^C interrupts are handled quietly.""" import signal def exithandler(signum, frame): signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, signal.SIG_IGN) print() - sys.exit(errno.EINTR) + exit_helper(errno.EINTR) signal.signal(signal.SIGINT, exithandler) signal.signal(signal.SIGTERM, exithandler) signal.signal(signal.SIGPIPE, signal.SIG_DFL) -def printVersion(): +def print_version(): """Output the version info.""" print("%s (%s) - %s" \ % (__productname__, __version__, __description__)) @@ -60,23 +67,26 @@ def printVersion(): print("Distributed under the terms of the GNU General Public License v2") -def printUsage(_error=None, help=None): +def print_usage(_error=None, help=None): """Print help message. May also print partial help to stderr if an error from {'options'} is specified.""" out = sys.stdout if _error: out = sys.stderr + if not _error in ('global-options', 'packages',): _error = None + if not _error and not help: help = 'all' + if _error in ('global-options',): - print(pp.error("Wrong option on command line."), file=out) - print(file=out) + output.eerror("Wrong option on command line.\n") + if _error in ('packages',): - print(pp.error("You need to specify exactly one package."), file=out) - print(file=out) + output.eerror("You need to specify exactly one package.\n") + print(white("Usage:"), file=out) if _error in ('global-options', 'packages',) or help == 'all': print(" " + turquoise(__productname__), @@ -106,14 +116,19 @@ def printUsage(_error=None, help=None): " (default: 2)\n" + " " * 29 + "bigger levels will generate more versions numbers\n" + " " * 29 + "0 means disabled", file=out) + print(yellow(" -f, --format=") + + " - define the output " + yellow("") + + " (available: json)", file=out) print(file=out) + if _error in ('packages',) or help: print(green(" package") + " - the packages (or ebuilds) you want to scan", file=out) print(file=out) - '''print( "More detailed instruction can be found in", - turquoise("`man %s`" % __productname__), file=out)''' + + #print( "More detailed instruction can be found in", + #turquoise("`man %s`" % __productname__), file=out) class ParseArgsException(Exception): @@ -125,12 +140,12 @@ class ParseArgsException(Exception): return repr(self.value) -def parseArgs(): +def parse_args(): """Parse the command line arguments. Raise exceptions on - errors. Returns package and affect the CONFIG dict. + errors. Returns packages and affects the CONFIG dict. """ - def optionSwitch(opts): + def option_switch(opts): """local function for interpreting command line options and setting options accordingly""" return_code = True @@ -150,30 +165,36 @@ def parseArgs(): elif o in ("-b", "--brute-force"): CONFIG['brute-force'] = int(a) elif o in ("-v", "--verbose") and not CONFIG['quiet']: - CONFIG['verbose'] += 1 + CONFIG['verbose'] += 1 + elif o in ("-f", "--format"): + CONFIG['format'] = a + CONFIG['nocolor'] = True + pp.output.nocolor() else: return_code = False return return_code - ' here are the different allowed command line options (getopt args) ' + # here are the different allowed command line options (getopt args) getopt_options = {'short': {}, 'long': {}} - getopt_options['short']['global'] = "hVCqv1b:" - getopt_options['long']['global'] = ["help", "version", "nocolor", "quiet", - "verbose", "oneshot", "brute-force="] + getopt_options['short']['global'] = "hVCqv1bf:" + getopt_options['long']['global'] = [ + "help", "version", "nocolor", "quiet", "verbose", "oneshot", + "brute-force=", "format=" + ] short_opts = getopt_options['short']['global'] long_opts = getopt_options['long']['global'] opts_mode = 'global' - ' apply getopts to command line, show partial help on failure ' + # apply getopts to command line, show partial help on failure try: opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) except: raise ParseArgsException(opts_mode + '-options') - ' set options accordingly ' - optionSwitch(opts) + # set options accordingly + option_switch(opts) if len(args) < 1: raise ParseArgsException('packages') @@ -183,29 +204,32 @@ def parseArgs(): def main(): """Parse command line and execute all actions.""" - CONFIG['nocolor'] = (port_settings["NOCOLOR"] in ('yes', 'true') - or not sys.stdout.isatty()) + CONFIG['nocolor'] = ( + port_settings["NOCOLOR"] in ('yes', 'true') or not sys.stdout.isatty() + ) if CONFIG['nocolor']: pp.output.nocolor() - ' parse command line options and actions ' + + # parse command line options and actions try: - packages = parseArgs() + packages = parse_args() except ParseArgsException as e: if e.value == 'help': - printUsage(help='all') - sys.exit(0) - elif e.value[:5] == 'help-': - printUsage(help=e.value[5:]) - sys.exit(0) - elif e.value == 'version': - printVersion() - sys.exit(0) - else: - printUsage(e.value) - sys.exit(errno.EINVAL) + print_usage(help='all') + exit_helper(0) + + elif e.value[:5] == 'help-': + print_usage(help=e.value[5:]) + exit_helper(0) + + elif e.value == 'version': + print_version() + exit_helper(0) + + else: + print_usage(e.value) + exit_helper(errno.EINVAL) - """ Change euscan's output """ - euscan.output = EOutput(CONFIG['quiet']) if CONFIG['verbose'] > 2: httplib.HTTPConnection.debuglevel = 1 @@ -216,48 +240,39 @@ def main(): ret = scan_upstream(package) except AmbiguousPackageName as e: pkgs = e.args[0] - for candidate in pkgs: - print(candidate) + output.eerror("\n".join(pkgs)) from os.path import basename # To get the short name - print(file=sys.stderr) - print( - pp.error( - "The short ebuild name '%s' is ambiguous. Please specify" \ - % basename(pkgs[0]) - ), - file=sys.stderr, end="" + output.error( + "The short ebuild name '%s' is ambiguous. Please specify" % + basename(pkgs[0]), + "one of the above fully-qualified ebuild names instead." ) - pp.die(1, "one of the above fully-qualified ebuild names instead.") - except GentoolkitException as err: - pp.die(1, '%s: %s' % (package, str(err))) - except Exception as err: - pp.die(1, '%s: %s' % (package, str(err))) + exit_helper(1) - if not CONFIG['quiet']: + except GentoolkitException as err: + output.eerror('%s: %s' % (package, str(err))) + exit_helper(1) + + except Exception as err: + output.eerror('%s: %s' % (package, str(err))) + exit_helper(1) + + if not CONFIG['quiet'] and not CONFIG['format']: print() for cp, url, version in ret: - if not CONFIG['quiet']: - print("Upstream Version: " - + pp.number("%s" % version) - + pp.path(" %s" % url)) - else: - print(pp.cpv("%s-%s" % (cp, version)) - + ": " + pp.path(url)) + output.result(cp, version, url) if not len(ret) and not CONFIG['quiet']: - print(pp.warn("Didn't find any new version, " - + "check package's homepage for " - + "more informations")) + output.ewarn( + "Didn't find any new version, check package's homepage for " + + "more informations" + ) if __name__ == "__main__": - try: - setupSignals() - main() - except KeyboardInterrupt: - print("Aborted.") - sys.exit(errno.EINTR) - sys.exit(0) + setup_signals() + main() + exit_helper(0) diff --git a/pym/euscan/__init__.py b/pym/euscan/__init__.py index 391a7a3..ef7e4ed 100644 --- a/pym/euscan/__init__.py +++ b/pym/euscan/__init__.py @@ -3,11 +3,17 @@ # Copyright 2011 Corentin Chary # Distributed under the terms of the GNU General Public License v2 +from io import StringIO +from collections import defaultdict +import json + +from gentoolkit import pprinter as pp +from portage.output import EOutput + + __version__ = "git" -from portage.output import EOutput - CONFIG = { 'nocolor': False, 'quiet': False, @@ -20,11 +26,11 @@ CONFIG = { 'oneshot': True, 'user-agent': 'escan (http://euscan.iksaif.net)', 'skip-robots-txt': False, - 'cache': False + 'cache': False, + 'format': None, + 'indent': 2 } -output = EOutput(CONFIG['quiet']) - BLACKLIST_VERSIONS = [ # Compatibility package for running binaries linked against a # pre gcc 3.4 libstdc++, won't be updated @@ -67,3 +73,67 @@ ROBOTS_TXT_BLACKLIST_DOMAINS = [ '(.*)chromium.org(.*)', '(.*)nodejs.org(.*)', ] + + +class EOutputFile(EOutput): + """ + Override of EOutput, allows to specify an output file for writes + """ + def __init__(self, out_file=None, *args, **kwargs): + super(EOutputFile, self).__init__(*args, **kwargs) + self.out_file = out_file + + def _write(self, f, msg): + if self.out_file is None: + super(EOutputFile, self)._write(f, msg) + else: + super(EOutputFile, self)._write(self.out_file, msg) + + +class EuscanOutput(object): + """ + Class that handles output for euscan + """ + def __init__(self, config): + self.config = config + self.data = defaultdict(StringIO) + self.packages = defaultdict(list) + + def get_formatted_output(self): + data = {} + for key in self.data: + if key not in ("ebegin", "eend"): + val = [x for x in self.data[key].getvalue().split("\n") if x] + data[key] = val + + data["result"] = self.packages + + if self.config["format"].lower() == "json": + return json.dumps(data, indent=self.config["indent"]) + else: + raise TypeError("Invalid output format") + + def result(self, cp, version, url): + if self.config['format']: + self.packages[cp].append({"version": version, "url": url}) + else: + if not self.config['quiet']: + print "Upstream Version:", pp.number("%s" % version), + print pp.path(" %s" % url) + else: + print pp.cpv("%s-%s" % (cp, version)) + ":", pp.path(url) + + def __getattr__(self, key): + output_file = self.data[key] if self.config["format"] else None + + if output_file: + _output = EOutputFile(out_file=self.data[key], + quiet=self.config['quiet']) + ret = getattr(_output, key) + else: + ret = getattr(EOutputFile(quiet=self.config['quiet']), key) + + return ret + + +output = EuscanOutput(CONFIG) diff --git a/pym/euscan/scan.py b/pym/euscan/scan.py index 17bd938..a1f63aa 100644 --- a/pym/euscan/scan.py +++ b/pym/euscan/scan.py @@ -39,7 +39,7 @@ def scan_upstream_urls(cpv, urls): for filename in urls: for url in urls[filename]: - if not CONFIG['quiet']: + if not CONFIG['quiet'] and not CONFIG['format']: pp.uprint() euscan.output.einfo("SRC_URI is '%s'" % url) @@ -101,7 +101,7 @@ def scan_upstream(query): ) return [] - if not CONFIG['quiet']: + if not CONFIG['quiet'] and not CONFIG['format']: pp.uprint( " * %s [%s]" % (pp.cpv(pkg.cpv), pp.section(pkg.repo_name())) )