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"] DEFAULT_EXTENSIONS = ["java", "c", "cpp", "h", "hpp", "py", "glsl", "rb", "js", "sql"]
__extensions__ = None __extensions__ = DEFAULT_EXTENSIONS
__located_extensions__ = set() __located_extensions__ = set()
def get(): def get():

View file

@ -26,10 +26,11 @@ import os
import zipfile import zipfile
__available_formats__ = ["html", "htmlembedded", "text", "xml"] __available_formats__ = ["html", "htmlembedded", "text", "xml"]
__selected_format__ = None
DEFAULT_FORMAT = __available_formats__[2] DEFAULT_FORMAT = __available_formats__[2]
__selected_format__ = DEFAULT_FORMAT
class InvalidFormatError(Exception): class InvalidFormatError(Exception):
pass pass

View file

@ -24,20 +24,15 @@ from __future__ import unicode_literals
import localization import localization
localization.init() localization.init()
try:
from compatibility import unicode
except:
pass
import blame import blame
import changes import changes
import compatibility
import config import config
import extensions import extensions
import filtering import filtering
import format import format
import help import help
import interval import interval
import getopt
import metrics import metrics
import missing import missing
import os import os
@ -51,11 +46,21 @@ import timeline
import version import version
class Runner: class Runner:
def __init__(self, opts): def __init__(self):
self.opts = opts self.hard = False
self.include_metrics = False
self.list_file_types = False
self.localize_output = False
self.repo = "." self.repo = "."
self.responsibilities = False
self.grading = False
self.timeline = False
self.useweeks = False
def output(self): def output(self):
if not self.localize_output:
localization.disable()
terminal.skip_escapes(not sys.stdout.isatty()) terminal.skip_escapes(not sys.stdout.isatty())
terminal.set_stdout_encoding() terminal.set_stdout_encoding()
previous_directory = os.getcwd() previous_directory = os.getcwd()
@ -69,43 +74,25 @@ class Runner:
os.chdir(absolute_path[0].decode("utf-8", "replace").strip()) 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() format.output_header()
outputable.output(changes.ChangesOutput(self.opts.hard)) outputable.output(changes.ChangesOutput(self.hard))
if changes.get(self.opts.hard).get_commits(): if changes.get(self.hard).get_commits():
outputable.output(blame.BlameOutput(self.opts.hard)) outputable.output(blame.BlameOutput(self.hard))
if self.opts.timeline: if self.timeline:
outputable.output(timeline.Timeline(changes.get(self.opts.hard), self.opts.useweeks)) outputable.output(timeline.Timeline(changes.get(self.hard), self.useweeks))
if self.opts.metrics: if self.include_metrics:
outputable.output(metrics.Metrics()) outputable.output(metrics.Metrics())
if self.opts.responsibilities: if self.responsibilities:
outputable.output(responsibilities.ResponsibilitiesOutput(self.opts.hard)) outputable.output(responsibilities.ResponsibilitiesOutput(self.hard))
outputable.output(missing.Missing()) outputable.output(missing.Missing())
outputable.output(filtering.Filtering()) outputable.output(filtering.Filtering())
if self.opts.list_file_types: if self.list_file_types:
outputable.output(extensions.Extensions()) outputable.output(extensions.Extensions())
format.output_footer() format.output_footer()
@ -116,62 +103,82 @@ def __check_python_version__():
python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1]) 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)) 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(): def main():
compatibility.convert_command_line() __run__ = Runner()
parser = optval.OptionParser(add_help_option=False)
try: try:
parser.add_option("-c", action="store_true", dest="checkout_missing") __opts__, __args__ = optval.gnu_getopt(sys.argv[1:], "cf:F:hHlLmrTwx:", ["checkout-missing:true", "exclude=",
parser.add_option("-H", action="store_true", dest="hard") "file-types=", "format=", "hard:true", "help",
parser.add_option("-l", action="store_true", dest="list_file_types") "list-file-types:true", "localize-output:true",
parser.add_option("-L", action="store_true", dest="localize_output") "metrics:true", "responsibilities:true", "since=",
parser.add_option("-m", action="store_true", dest="metrics") "grading:true", "timeline:true", "until=", "version",
parser.add_option("-r", action="store_true", dest="responsibilities") "weeks:true"])
parser.add_option("-T", action="store_true", dest="timeline") for arg in __args__:
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:
__run__.repo = arg __run__.repo = arg
#We need the repo above to be set before we read the git config. #We need the repo above to be set before we read the git config.
config.init(__run__) 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: except (format.InvalidFormatError, optval.InvalidOptionArgument, getopt.error) as msg:
localization.enable() print(sys.argv[0], "\b:", msg)
print(sys.argv[0], "\b:", end=" ")
print(unicode(msg))
print(_("Try `{0} --help' for more information.").format(sys.argv[0])) print(_("Try `{0} --help' for more information.").format(sys.argv[0]))
sys.exit(2) sys.exit(2)

View file

@ -17,87 +17,47 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
from __future__ import unicode_literals from __future__ import unicode_literals
import getopt
try:
from compatibility import unicode
except:
pass
import optparse
import sys
class InvalidOptionArgument(Exception): class InvalidOptionArgument(Exception):
pass pass
class OptionParsingError(RuntimeError): def __find_arg_in_options__(arg, options):
pass for opt in options:
if opt[0].find(arg) == 0:
return opt
return None
def __find_options_to_extend__(long_options):
options_to_extend = []
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]))
return options_to_extend
# This is a duplicate of gnu_getopt, but with support for optional arguments in long options, in the form; "arg:default_value".
def gnu_getopt(args, options, long_options):
options_to_extend = __find_options_to_extend__(long_options)
for n, arg in enumerate(args):
opt = __find_arg_in_options__(arg, options_to_extend)
if opt:
args[n] = arg + "=" + opt[1]
return getopt.gnu_getopt(args, options, long_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
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.")) raise InvalidOptionArgument(_("The given option argument is not a valid boolean."))
if "multidest" in kwargs:
for dest in kwargs["multidest"]:
setattr(parser.values, dest, value)
setattr(parser.values, option.dest, value)
# Originaly taken from here (and modified):
# http://stackoverflow.com/questions/1229146/parsing-empty-options-in-python
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
for i in range(len(sys.argv) - 1, 0, -1):
arg = sys.argv[i]
if arg in args:
sys.argv.insert(i + 1, "true")
parser.add_option(*args, **kwargs)
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:]))
raise OptionParsingError(_("invalid command-line options"))
#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)