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.
This commit is contained in:
Adam Waldenberg 2013-07-14 00:07:36 +02:00
parent cae99cb3f7
commit 7f0e2b6fe8
5 changed files with 122 additions and 188 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
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

View file

@ -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():

View file

@ -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

View file

@ -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)

View file

@ -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."))