mirror of
https://github.com/ejwa/gitinspector.git
synced 2024-12-22 13:42:14 +01:00
Implemented optional boolean arguments to some command-line options.
Just like in many GNU tools, it is now possible to pass an optional boolean to some of the flags of gitinspector in the form; --flag[=BOOL] This gives us the ability to override options set via git-config. For example; say we did the following: git-config --global inspector.timeline true We could then override this setting when running gitinspector by supplying: ./gitinspector.py --timeline=false Implementing this was not a trivial task, as no command-line parser in Python supports this by default (getopt, optparse, argparse). In order to properly handle optional boolean arguments; some clever patching had to be done to the command-line in combination with a callback function that can handle boolean strings. To maintain compatibility with Python 2.6, this was implemented using optparse (instead of argparse).
This commit is contained in:
parent
663493fd41
commit
0d2bf9b0a8
6 changed files with 202 additions and 159 deletions
|
@ -18,17 +18,15 @@
|
|||
# along with gitinspector. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import extensions
|
||||
import filtering
|
||||
import format
|
||||
import interval
|
||||
import missing
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
def __read_git_config__(repo, variable, default_value):
|
||||
def __read_git_config__(run, variable, destination=None):
|
||||
if destination == None:
|
||||
destination = variable
|
||||
|
||||
previous_directory = os.getcwd()
|
||||
os.chdir(repo)
|
||||
os.chdir(run.repo)
|
||||
setting = subprocess.Popen("git config inspector." + variable, shell=True, bufsize=1,
|
||||
stdout=subprocess.PIPE).stdout
|
||||
os.chdir(previous_directory)
|
||||
|
@ -37,55 +35,33 @@ def __read_git_config__(repo, variable, default_value):
|
|||
setting = setting.readlines()[0]
|
||||
setting = setting.decode("utf-8", "replace").strip()
|
||||
|
||||
if default_value == True or default_value == False:
|
||||
if setting == "True" or setting == "true" or setting == "1":
|
||||
return True
|
||||
elif setting == "False" or setting == "false" or setting == "0":
|
||||
return False
|
||||
|
||||
return False
|
||||
elif setting == "":
|
||||
return default_value
|
||||
|
||||
return setting
|
||||
if setting == "True" or setting == "true" or setting == "t" or setting == "1":
|
||||
run.opts[destination] = True
|
||||
elif setting == "False" or setting == "false" or setting == "f" or setting == "0":
|
||||
run.opts[destination] = False
|
||||
return True
|
||||
|
||||
except IndexError:
|
||||
return default_value
|
||||
return False
|
||||
|
||||
def init(run):
|
||||
missing.set_checkout_missing(__read_git_config__(run.repo, "checkout-missing", False))
|
||||
extensions.define(__read_git_config__(run.repo, "file-types", ",".join(extensions.get())))
|
||||
__read_git_config__(run, "checkout-missing", "checkout_missing")
|
||||
__read_git_config__(run, "file-types", "file_types")
|
||||
__read_git_config__(run, "exclude")
|
||||
__read_git_config__(run, "format")
|
||||
__read_git_config__(run, "hard")
|
||||
__read_git_config__(run, "list-file-types", "list_file_types")
|
||||
__read_git_config__(run, "metrics")
|
||||
__read_git_config__(run, "responsibilities")
|
||||
__read_git_config__(run, "weeks", "useweeks")
|
||||
__read_git_config__(run, "since")
|
||||
__read_git_config__(run, "until")
|
||||
__read_git_config__(run, "timeline")
|
||||
|
||||
exclude = __read_git_config__(run.repo, "exclude", None)
|
||||
if exclude != None:
|
||||
filtering.add(exclude)
|
||||
|
||||
output_format = __read_git_config__(run.repo, "format", None)
|
||||
if output_format != None:
|
||||
if not format.select(output_format):
|
||||
raise format.InvalidFormatError(_("specified output format not supported."))
|
||||
|
||||
run.hard = __read_git_config__(run.repo, "hard", False)
|
||||
run.list_file_types = __read_git_config__(run.repo, "list-file-types", False)
|
||||
run.include_metrics = __read_git_config__(run.repo, "metrics", False)
|
||||
run.responsibilities = __read_git_config__(run.repo, "responsibilities", False)
|
||||
run.useweeks = __read_git_config__(run.repo, "weeks", False)
|
||||
|
||||
since = __read_git_config__(run.repo, "since", None)
|
||||
if since != None:
|
||||
interval.set_since(since)
|
||||
|
||||
until = __read_git_config__(run.repo, "until", None)
|
||||
if until != None:
|
||||
interval.set_until(until)
|
||||
|
||||
run.timeline = __read_git_config__(run.repo, "timeline", False)
|
||||
|
||||
if __read_git_config__(run.repo, "grading", False):
|
||||
run.include_metrics = True
|
||||
run.list_file_types = True
|
||||
run.responsibilities = True
|
||||
run.grading = True
|
||||
run.hard = True
|
||||
run.timeline = True
|
||||
run.useweeks = True
|
||||
if __read_git_config__(run, "grading"):
|
||||
run.opts.hard = True
|
||||
run.opts.list_file_types = True
|
||||
run.opts.metrics = True
|
||||
run.opts.responsibilities = True
|
||||
run.opts.timeline = True
|
||||
run.opts.useweeks = True
|
||||
|
|
|
@ -23,8 +23,9 @@ from outputable import Outputable
|
|||
import terminal
|
||||
import textwrap
|
||||
|
||||
__default_extensions__ = ["java", "c", "cpp", "h", "hpp", "py", "glsl", "rb", "js", "sql"]
|
||||
__extensions__ = __default_extensions__
|
||||
DEFAULT_EXTENSIONS = ["java", "c", "cpp", "h", "hpp", "py", "glsl", "rb", "js", "sql"]
|
||||
|
||||
__extensions__ = None
|
||||
__located_extensions__ = set()
|
||||
|
||||
def get():
|
||||
|
|
|
@ -26,8 +26,9 @@ import os
|
|||
import zipfile
|
||||
|
||||
__available_formats__ = ["html", "htmlembedded", "text", "xml"]
|
||||
__default_format__ = __available_formats__[2]
|
||||
__selected_format__ = __default_format__
|
||||
__selected_format__ = None
|
||||
|
||||
DEFAULT_FORMAT = __available_formats__[2]
|
||||
|
||||
class InvalidFormatError(Exception):
|
||||
pass
|
||||
|
|
|
@ -30,12 +30,13 @@ import config
|
|||
import extensions
|
||||
import filtering
|
||||
import format
|
||||
import getopt
|
||||
import help
|
||||
import interval
|
||||
import metrics
|
||||
import missing
|
||||
import os
|
||||
import optparse
|
||||
import optval
|
||||
import outputable
|
||||
import responsibilities
|
||||
import sys
|
||||
|
@ -44,40 +45,50 @@ import timeline
|
|||
import version
|
||||
|
||||
class Runner:
|
||||
def __init__(self):
|
||||
self.hard = False
|
||||
self.include_metrics = False
|
||||
self.list_file_types = False
|
||||
def __init__(self, opts):
|
||||
self.opts = opts
|
||||
self.repo = "."
|
||||
self.responsibilities = False
|
||||
self.grading = False
|
||||
self.timeline = False
|
||||
self.useweeks = False
|
||||
|
||||
def output(self):
|
||||
terminal.skip_escapes(not sys.stdout.isatty())
|
||||
terminal.set_stdout_encoding()
|
||||
previous_directory = os.getcwd()
|
||||
os.chdir(self.repo)
|
||||
|
||||
if not format.select(self.opts.format):
|
||||
raise format.InvalidFormatError(_("specified output format not supported."))
|
||||
|
||||
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.hard))
|
||||
outputable.output(changes.ChangesOutput(self.opts.hard))
|
||||
|
||||
if changes.get(self.hard).get_commits():
|
||||
outputable.output(blame.BlameOutput(self.hard))
|
||||
if changes.get(self.opts.hard).get_commits():
|
||||
outputable.output(blame.BlameOutput(self.opts.hard))
|
||||
|
||||
if self.timeline:
|
||||
outputable.output(timeline.Timeline(changes.get(self.hard), self.useweeks))
|
||||
if self.opts.timeline:
|
||||
outputable.output(timeline.Timeline(changes.get(self.opts.hard), self.opts.useweeks))
|
||||
|
||||
if self.include_metrics:
|
||||
if self.opts.metrics:
|
||||
outputable.output(metrics.Metrics())
|
||||
|
||||
if self.responsibilities:
|
||||
outputable.output(responsibilities.ResponsibilitiesOutput(self.hard))
|
||||
if self.opts.responsibilities:
|
||||
outputable.output(responsibilities.ResponsibilitiesOutput(self.opts.hard))
|
||||
|
||||
outputable.output(missing.Missing())
|
||||
outputable.output(filtering.Filtering())
|
||||
|
||||
if self.list_file_types:
|
||||
if self.opts.list_file_types:
|
||||
outputable.output(extensions.Extensions())
|
||||
|
||||
format.output_footer()
|
||||
|
@ -88,63 +99,54 @@ 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():
|
||||
__run__ = Runner()
|
||||
parser = optparse.OptionParser(add_help_option=False)
|
||||
|
||||
try:
|
||||
__opts__, __args__ = getopt.gnu_getopt(sys.argv[1:], "cf:F:hHlmrTwx:", ["checkout-missing", "exclude=",
|
||||
"file-types=", "format=", "hard", "help", "list-file-types",
|
||||
"metrics", "responsibilities", "since=", "grading",
|
||||
"timeline", "until=", "version", "weeks"])
|
||||
for arg in __args__:
|
||||
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("-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, "--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
|
||||
|
||||
#We need the repo above to be set before we read the git config.
|
||||
config.init(__run__)
|
||||
|
||||
for o, a in __opts__:
|
||||
if o in("-c", "--checkout-missing"):
|
||||
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", "--hard"):
|
||||
__run__.hard = True
|
||||
elif o in("-l", "--list-file-types"):
|
||||
__run__.list_file_types = True
|
||||
elif o in("-m", "--metrics"):
|
||||
__run__.include_metrics = True
|
||||
elif o in("-r", "--responsibilities"):
|
||||
__run__.responsibilities = True
|
||||
elif o in("--since"):
|
||||
interval.set_since(a)
|
||||
elif o in("--version"):
|
||||
version.output()
|
||||
sys.exit(0)
|
||||
elif o in("--grading"):
|
||||
__run__.include_metrics = True
|
||||
__run__.list_file_types = True
|
||||
__run__.responsibilities = True
|
||||
__run__.grading = True
|
||||
__run__.hard = True
|
||||
__run__.timeline = True
|
||||
__run__.useweeks = True
|
||||
elif o in("-T", "--timeline"):
|
||||
__run__.timeline = True
|
||||
elif o in("--until"):
|
||||
interval.set_until(a)
|
||||
elif o in("-w", "--weeks"):
|
||||
__run__.useweeks = True
|
||||
elif o in("-x", "--exclude"):
|
||||
filtering.add(a)
|
||||
|
||||
except (format.InvalidFormatError, getopt.error) as msg:
|
||||
print(sys.argv[0], "\b:", msg)
|
||||
except (format.InvalidFormatError, optval.InvalidOptionArgument) as msg:
|
||||
print(sys.argv[0], "\b:", unicode(msg))
|
||||
print(_("Try `{0} --help' for more information.").format(sys.argv[0]))
|
||||
sys.exit(2)
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from extensions import __default_extensions__
|
||||
from extensions import DEFAULT_EXTENSIONS
|
||||
from format import __available_formats__
|
||||
import sys
|
||||
|
||||
|
@ -29,38 +29,40 @@ specified, the current directory is used. If multiple directories are
|
|||
given, information will be fetched from the last directory specified.
|
||||
|
||||
Mandatory arguments to long options are mandatory for short options too.
|
||||
-c, --checkout-missing try to checkout any missing files
|
||||
-f, --file-types=EXTENSIONS a comma separated list of file extensions to
|
||||
include when computing statistics. The
|
||||
default extensions used are:
|
||||
{1}
|
||||
-F, --format=FORMAT define in which format output should be
|
||||
generated; the default format is 'text' and
|
||||
the available formats are:
|
||||
{2}
|
||||
--grading show statistics and information in a way that
|
||||
is formatted for grading of student projects;
|
||||
this is the same as supplying -HlmrTw
|
||||
-H, --hard track rows and look for duplicates harder;
|
||||
this can be quite slow with big repositories
|
||||
-l, --list-file-types list all the file extensions available in the
|
||||
current branch of the repository
|
||||
-m --metrics include checks for certain metrics during the
|
||||
analysis of commits
|
||||
-r --responsibilities show which files the different authors seem
|
||||
most responsible for
|
||||
--since=DATE only show statistics for commits more recent
|
||||
than a specific date
|
||||
-T, --timeline show commit timeline, including author names
|
||||
--until=DATE only show statistics for commits older than a
|
||||
specific date
|
||||
-w, --weeks show all statistical information in weeks
|
||||
instead of in months
|
||||
-x, --exclude=PATTERN an exclusion pattern describing file names that
|
||||
should be excluded from the statistics; can
|
||||
be specified multiple times
|
||||
-h, --help display this help and exit
|
||||
--version output version information and exit
|
||||
Boolean arguments can only be given to long options.
|
||||
-c, --checkout-missing[=BOOL] try to checkout any missing files
|
||||
-f, --file-types=EXTENSIONS a comma separated list of file extensions to
|
||||
include when computing statistics. The
|
||||
default extensions used are:
|
||||
{1}
|
||||
-F, --format=FORMAT define in which format output should be
|
||||
generated; the default format is 'text' and
|
||||
the available formats are:
|
||||
{2}
|
||||
--grading[=BOOL] show statistics and information in a way that
|
||||
is formatted for grading of student
|
||||
projects; this is the same as supplying the
|
||||
options -HlmrTw
|
||||
-H, --hard[=BOOL] track rows and look for duplicates harder;
|
||||
this can be quite slow with big repositories
|
||||
-l, --list-file-types[=BOOL] list all the file extensions available in the
|
||||
current branch of the repository
|
||||
-m --metrics[=BOOL] include checks for certain metrics during the
|
||||
analysis of commits
|
||||
-r --responsibilities[=BOOL] show which files the different authors seem
|
||||
most responsible for
|
||||
--since=DATE only show statistics for commits more recent
|
||||
than a specific date
|
||||
-T, --timeline[=BOOL] show commit timeline, including author names
|
||||
--until=DATE only show statistics for commits older than a
|
||||
specific date
|
||||
-w, --weeks[=BOOL] show all statistical information in weeks
|
||||
instead of in months
|
||||
-x, --exclude=PATTERN an exclusion pattern describing file names
|
||||
that should be excluded from the statistics;
|
||||
can be specified multiple times
|
||||
-h, --help display this help and exit
|
||||
--version output version information and exit
|
||||
|
||||
gitinspector will filter statistics to only include commits that modify,
|
||||
add or remove one of the specified extensions, see -f or --file-types for
|
||||
|
@ -70,4 +72,4 @@ gitinspector requires that the git executable is available in your PATH.
|
|||
Report gitinspector bugs to gitinspector@ejwa.se.""")
|
||||
|
||||
def output():
|
||||
print(__doc__.format(sys.argv[0], ",".join(__default_extensions__), ",".join(__available_formats__)))
|
||||
print(__doc__.format(sys.argv[0], ",".join(DEFAULT_EXTENSIONS), ",".join(__available_formats__)))
|
||||
|
|
61
gitinspector/optval.py
Normal file
61
gitinspector/optval.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
# 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
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
class InvalidOptionArgument(Exception):
|
||||
pass
|
||||
|
||||
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.")
|
||||
|
||||
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)
|
Loading…
Reference in a new issue