From 7f0e2b6fe8ecb2aa55b9af02bfdd3c14e5639f26 Mon Sep 17 00:00:00 2001 From: Adam Waldenberg Date: Sun, 14 Jul 2013 00:07:36 +0200 Subject: [PATCH] Changed the argument parsing back to using getopt! The support for optional boolean arguments is the same; but uses getopt instead of optparse. The whole adventure with optparse was a giant waste of time and just forced us to monkey-patch optparse with some very ugly solutions in order to make it do what we wanted; thus it was better to switch back to the more low-level getopt module. To accomplish this; a optval.gnu_getopt() function was added that is a duplicate of the original getopt.gnu_getopt function but with support for optional arguments. A long option which accepts an optional argument is denoted with arg:default_value in the long_options string. In the end, this solution feels much better than the one with optparse. --- gitinspector/compatibility.py | 34 ------- gitinspector/extensions.py | 2 +- gitinspector/format.py | 3 +- gitinspector/gitinspector.py | 171 ++++++++++++++++++---------------- gitinspector/optval.py | 100 ++++++-------------- 5 files changed, 122 insertions(+), 188 deletions(-) delete mode 100644 gitinspector/compatibility.py diff --git a/gitinspector/compatibility.py b/gitinspector/compatibility.py deleted file mode 100644 index 0b243fe..0000000 --- a/gitinspector/compatibility.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/python -# coding: utf-8 -# -# Copyright © 2013 Ejwa Software. All rights reserved. -# -# This file is part of gitinspector. -# -# gitinspector is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# gitinspector is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with gitinspector. If not, see . - -import sys - -try: - unicode -except NameError: - def unicode(string): - return str(string) - -def convert_command_line(): - try: - for num, arg in enumerate(sys.argv): - sys.argv[num] = unicode(arg.decode("utf-8", "replace")) - except AttributeError: - pass diff --git a/gitinspector/extensions.py b/gitinspector/extensions.py index 0843831..9506820 100644 --- a/gitinspector/extensions.py +++ b/gitinspector/extensions.py @@ -26,7 +26,7 @@ import textwrap DEFAULT_EXTENSIONS = ["java", "c", "cpp", "h", "hpp", "py", "glsl", "rb", "js", "sql"] -__extensions__ = None +__extensions__ = DEFAULT_EXTENSIONS __located_extensions__ = set() def get(): diff --git a/gitinspector/format.py b/gitinspector/format.py index f327fe0..b134883 100644 --- a/gitinspector/format.py +++ b/gitinspector/format.py @@ -26,10 +26,11 @@ import os import zipfile __available_formats__ = ["html", "htmlembedded", "text", "xml"] -__selected_format__ = None DEFAULT_FORMAT = __available_formats__[2] +__selected_format__ = DEFAULT_FORMAT + class InvalidFormatError(Exception): pass diff --git a/gitinspector/gitinspector.py b/gitinspector/gitinspector.py index 8f67170..dd98248 100755 --- a/gitinspector/gitinspector.py +++ b/gitinspector/gitinspector.py @@ -24,20 +24,15 @@ from __future__ import unicode_literals import localization localization.init() -try: - from compatibility import unicode -except: - pass - import blame import changes -import compatibility import config import extensions import filtering import format import help import interval +import getopt import metrics import missing import os @@ -51,11 +46,21 @@ import timeline import version class Runner: - def __init__(self, opts): - self.opts = opts + def __init__(self): + self.hard = False + self.include_metrics = False + self.list_file_types = False + self.localize_output = False self.repo = "." + self.responsibilities = False + self.grading = False + self.timeline = False + self.useweeks = False def output(self): + if not self.localize_output: + localization.disable() + terminal.skip_escapes(not sys.stdout.isatty()) terminal.set_stdout_encoding() previous_directory = os.getcwd() @@ -69,43 +74,25 @@ class Runner: os.chdir(absolute_path[0].decode("utf-8", "replace").strip()) - if not format.select(self.opts.format): - raise format.InvalidFormatError(_("specified output format not supported.")) - - if not self.opts.localize_output: - localization.disable() - - missing.set_checkout_missing(self.opts.checkout_missing) - extensions.define(self.opts.file_types) - - if self.opts.since != None: - interval.set_since(self.opts.since) - - if self.opts.until != None: - interval.set_until(self.opts.until) - - for ex in self.opts.exclude: - filtering.add(ex) - format.output_header() - outputable.output(changes.ChangesOutput(self.opts.hard)) + outputable.output(changes.ChangesOutput(self.hard)) - if changes.get(self.opts.hard).get_commits(): - outputable.output(blame.BlameOutput(self.opts.hard)) + if changes.get(self.hard).get_commits(): + outputable.output(blame.BlameOutput(self.hard)) - if self.opts.timeline: - outputable.output(timeline.Timeline(changes.get(self.opts.hard), self.opts.useweeks)) + if self.timeline: + outputable.output(timeline.Timeline(changes.get(self.hard), self.useweeks)) - if self.opts.metrics: + if self.include_metrics: outputable.output(metrics.Metrics()) - if self.opts.responsibilities: - outputable.output(responsibilities.ResponsibilitiesOutput(self.opts.hard)) + if self.responsibilities: + outputable.output(responsibilities.ResponsibilitiesOutput(self.hard)) outputable.output(missing.Missing()) outputable.output(filtering.Filtering()) - if self.opts.list_file_types: + if self.list_file_types: outputable.output(extensions.Extensions()) format.output_footer() @@ -116,62 +103,82 @@ def __check_python_version__(): python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1]) sys.exit(_("gitinspector requires at leat Python 2.6 to run (version {0} was found).").format(python_version)) -def __handle_help__(__option__, __opt_str__, __value__, __parser__): - help.output() - sys.exit(0) - -def __handle_version__(__option__, __opt_str__, __value__, __parser__): - version.output() - sys.exit(0) - def main(): - compatibility.convert_command_line() - parser = optval.OptionParser(add_help_option=False) + __run__ = Runner() try: - parser.add_option("-c", action="store_true", dest="checkout_missing") - parser.add_option("-H", action="store_true", dest="hard") - parser.add_option("-l", action="store_true", dest="list_file_types") - parser.add_option("-L", action="store_true", dest="localize_output") - parser.add_option("-m", action="store_true", dest="metrics") - parser.add_option("-r", action="store_true", dest="responsibilities") - parser.add_option("-T", action="store_true", dest="timeline") - parser.add_option("-w", action="store_true", dest="useweeks") - - optval.add_option(parser, "--checkout-missing", boolean=True) - parser.add_option( "-f", "--file-types", type="string", default=",".join(extensions.DEFAULT_EXTENSIONS)) - parser.add_option( "-F", "--format", type="string", default=format.DEFAULT_FORMAT) - optval.add_option(parser, "--grading", boolean=True, multidest=["hard", "metrics", "list_file_types", - "responsibilities", "timeline", "useweeks"]) - parser.add_option( "-h", "--help", action="callback", callback=__handle_help__) - optval.add_option(parser, "--hard", boolean=True) - optval.add_option(parser, "--list-file-types", boolean=True) - optval.add_option(parser, "--localize-output", boolean=True) - optval.add_option(parser, "--metrics", boolean=True) - optval.add_option(parser, "--responsibilities", boolean=True) - parser.add_option( "--since", type="string") - optval.add_option(parser, "--timeline", boolean=True) - parser.add_option( "--until", type="string") - parser.add_option( "--version", action="callback", callback=__handle_version__) - optval.add_option(parser, "--weeks", boolean=True, dest="useweeks") - parser.add_option( "-x", "--exclude", action="append", type="string", default=[]) - - (opts, args) = parser.parse_args() - __run__ = Runner(opts) - - for arg in args: + __opts__, __args__ = optval.gnu_getopt(sys.argv[1:], "cf:F:hHlLmrTwx:", ["checkout-missing:true", "exclude=", + "file-types=", "format=", "hard:true", "help", + "list-file-types:true", "localize-output:true", + "metrics:true", "responsibilities:true", "since=", + "grading:true", "timeline:true", "until=", "version", + "weeks:true"]) + for arg in __args__: __run__.repo = arg #We need the repo above to be set before we read the git config. config.init(__run__) - parser.parse_args(values=opts) + for o, a in __opts__: + if o in("-c"): + missing.set_checkout_missing(True) + elif o in("-h", "--help"): + help.output() + sys.exit(0) + elif o in("-f", "--file-types"): + extensions.define(a) + elif o in("-F", "--format"): + if not format.select(a): + raise format.InvalidFormatError(_("specified output format not supported.")) + elif o in("-H"): + __run__.hard = True + elif o in("--hard"): + __run__.hard = optval.get_boolean_argument(a) + elif o in("-l"): + __run__.list_file_types = True + elif o in("--list-file-types"): + __run__.list_file_types = optval.get_boolean_argument(a) + elif o in("-L"): + __run__.localize_output = True + elif o in("--localize-output"): + __run__.localize_output = optval.get_boolean_argument(a) + elif o in("-m"): + __run__.include_metrics = True + elif o in ("--metrics"): + __run__.include_metrics = optval.get_boolean_argument(a) + elif o in("-r"): + __run__.responsibilities = True + elif o in("--responsibilities"): + __run__.responsibilities = optval.get_boolean_argument(a) + elif o in("--since"): + interval.set_since(a) + elif o in("--version"): + version.output() + sys.exit(0) + elif o in("--grading"): + grading = optval.get_boolean_argument(a) + __run__.include_metrics = grading + __run__.list_file_types = grading + __run__.responsibilities = grading + __run__.grading = grading + __run__.hard = grading + __run__.timeline = grading + __run__.useweeks = grading + elif o in("-T"): + __run__.timeline = True + elif o in("--timeline"): + __run__.timeline = optval.get_boolean_argument(a) + elif o in("--until"): + interval.set_until(a) + elif o in("-w"): + __run__.useweeks = True + elif o in("--weeks"): + __run__.useweeks = optval.get_boolean_argument(a) + elif o in("-x", "--exclude"): + filtering.add(a) - except (format.InvalidFormatError, optval.InvalidOptionArgument, optval.OptionParsingError) as msg: - localization.enable() - - print(sys.argv[0], "\b:", end=" ") - print(unicode(msg)) + except (format.InvalidFormatError, optval.InvalidOptionArgument, getopt.error) as msg: + print(sys.argv[0], "\b:", msg) print(_("Try `{0} --help' for more information.").format(sys.argv[0])) sys.exit(2) diff --git a/gitinspector/optval.py b/gitinspector/optval.py index bea66b6..f8086dd 100644 --- a/gitinspector/optval.py +++ b/gitinspector/optval.py @@ -17,87 +17,47 @@ # You should have received a copy of the GNU General Public License from __future__ import unicode_literals - -try: - from compatibility import unicode -except: - pass - -import optparse -import sys +import getopt class InvalidOptionArgument(Exception): pass -class OptionParsingError(RuntimeError): - pass +def __find_arg_in_options__(arg, options): + for opt in options: + if opt[0].find(arg) == 0: + return opt -def __handle_boolean_argument__(option, __opt_str__, value, parser, *__args__, **kwargs): - if isinstance(value, bool): - return value - elif value == None or value.lower() == "false" or value.lower() == "f" or value == "0": - value = False - elif value.lower() == "true" or value.lower() == "t" or value == "1": - value = True - else: - raise InvalidOptionArgument(_("The given option argument is not a valid boolean.")) + return None - if "multidest" in kwargs: - for dest in kwargs["multidest"]: - setattr(parser.values, dest, value) +def __find_options_to_extend__(long_options): + options_to_extend = [] - setattr(parser.values, option.dest, value) + for n, arg in enumerate(long_options): + arg = arg.split(":") + if len(arg) == 2: + long_options[n] = arg[0] + "=" + options_to_extend.append(("--" + arg[0], arg[1])) -# Originaly taken from here (and modified): -# http://stackoverflow.com/questions/1229146/parsing-empty-options-in-python + return options_to_extend -def add_option(parser, *args, **kwargs): - if "multidest" in kwargs: - multidest = kwargs.pop("multidest") - kwargs["callback_kwargs"] = {"multidest": multidest} - if "boolean" in kwargs and kwargs["boolean"] == True: - boolean = kwargs.pop("boolean") - kwargs["type"] = "string" - kwargs["action"] = "callback" - kwargs["callback"] = __handle_boolean_argument__ - kwargs["default"] = not boolean +# This is a duplicate of gnu_getopt, but with support for optional arguments in long options, in the form; "arg:default_value". - for i in range(len(sys.argv) - 1, 0, -1): - arg = sys.argv[i] - if arg in args: - sys.argv.insert(i + 1, "true") +def gnu_getopt(args, options, long_options): + options_to_extend = __find_options_to_extend__(long_options) - parser.add_option(*args, **kwargs) + for n, arg in enumerate(args): + opt = __find_arg_in_options__(arg, options_to_extend) + if opt: + args[n] = arg + "=" + opt[1] -class OptionParser(optparse.OptionParser): - def error(self, msg): - if msg.find("requires") != -1: - variable = msg.split()[0] - raise OptionParsingError(_("option '{0}' requires an argument").format(variable)) - else: - variable = msg.split()[-1] - if variable[1] == "-": - raise OptionParsingError(_("unrecognized option '{0}'").format(variable)) - else: - raise OptionParsingError(_("invalid option -- '{0}'").format(variable[1:])) + return getopt.gnu_getopt(args, options, long_options) - raise OptionParsingError(_("invalid command-line options")) +def get_boolean_argument(arg): + if isinstance(arg, bool): + return arg + elif arg == None or arg.lower() == "false" or arg.lower() == "f" or arg == "0": + return False + elif arg.lower() == "true" or arg.lower() == "t" or arg == "1": + return True - #Originaly taken from the optparse module (and modified). - def parse_args(self, args=None, values=None): - rargs = self._get_args(args) - if values is None: - values = self.get_default_values() - - #Pylint screams about these. However, this was how it was done in the original code. - self.rargs = rargs - self.largs = largs = [] - self.values = values - - try: - self._process_args(largs, rargs, values) - except (optparse.BadOptionError, optparse.OptionValueError) as msg: - self.error(unicode(msg)) - - args = largs + rargs - return self.check_values(values, args) + raise InvalidOptionArgument(_("The given option argument is not a valid boolean."))