Added support for JSON output format (Fixes #50).

This commit is contained in:
Adam Waldenberg 2015-10-24 02:44:45 +02:00
parent cff8dd109b
commit bbe07f061d
11 changed files with 220 additions and 11 deletions

View File

@ -26,9 +26,9 @@ import time
import zipfile
from . import basedir, localization, terminal, version
__available_formats__ = ["html", "htmlembedded", "text", "xml"]
__available_formats__ = ["html", "htmlembedded", "json", "text", "xml"]
DEFAULT_FORMAT = __available_formats__[2]
DEFAULT_FORMAT = __available_formats__[3]
__selected_format__ = DEFAULT_FORMAT
@ -53,7 +53,6 @@ def __output_html_template__(name):
template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), name)
file_r = open(template_path, "rb")
return file_r.read().decode("utf-8", "replace")
def __get_zip_file_content__(name, file_name="/html/flot.zip"):
zip_file = zipfile.ZipFile(basedir.get_basedir() + file_name, "r")
content = zip_file.read(name)
@ -98,6 +97,8 @@ def output_header():
hide_minor_authors=_("Hide minor authors"),
show_minor_rows=_("Show rows with minor work"),
hide_minor_rows=_("Hide rows with minor work")))
elif __selected_format__ == "json":
print("{\n\t\"gitinspector\": {")
elif __selected_format__ == "xml":
print("<gitinspector>")
print("\t<version>" + version.__version__ + "</version>")
@ -112,5 +113,7 @@ def output_footer():
base = basedir.get_basedir()
html_footer = __output_html_template__(base + "/html/html.footer")
print(html_footer)
elif __selected_format__ == "json":
print("\n\t}\n}")
elif __selected_format__ == "xml":
print("</gitinspector>")

1
gitinspector/gitinspector.py Executable file → Normal file
View File

@ -36,7 +36,6 @@ from .output.timelineoutput import TimelineOutput
localization.init()
class Runner(object):
def __init__(self):
self.hard = False

View File

@ -32,7 +32,7 @@ def get_url(email, size=20):
base_url = "https://www.gravatar.com/avatar/" + md5hash
params = None
if format.get_selected() == "html":
if format.get_selected() == "html" or format.get_selected() == "json":
params = {"default": "identicon", "size": size}
elif format.get_selected() == "xml":
params = {"default": "identicon"}

View File

@ -26,7 +26,6 @@ from ..localization import N_
from .. import blame, format, gravatar, terminal
from .outputable import Outputable
BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still "
"intact in the current revision")
@ -96,6 +95,28 @@ class BlameOutput(Outputable):
print(blame_xml)
def output_json(self):
message_xml = "\t\t\t\"message\": \"" + _(BLAME_INFO_TEXT) + "\",\n"
blame_xml = ""
for i in sorted(blame.__blame__.get_summed_blames().items()):
author_email = self.changes.get_latest_email_by_author(i[0])
name_xml = "\t\t\t\t\"name\": \"" + i[0] + "\",\n"
gravatar_xml = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
rows_xml = "\t\t\t\t\"rows\": " + str(i[1].rows) + ",\n"
stability_xml = ("\t\t\t\t\"stability\": " + "{0:.1f}".format(blame.Blame.get_stability(i[0], i[1].rows,
self.changes)) + ",\n")
age_xml = ("\t\t\t\t\"age\": " + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + ",\n")
percentage_in_comments_xml = ("\t\t\t\t\"percentage-in-comments\": " + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) +
"\n")
blame_xml += ("{\n" + name_xml + gravatar_xml + rows_xml + stability_xml + age_xml +
percentage_in_comments_xml + "\t\t\t},")
else:
blame_xml = blame_xml[:-1]
print(",\n\t\t\"blame\": {\n" + message_xml + "\t\t\t\"authors\": [\n\t\t\t" + blame_xml + "]\n\t\t}", end="")
def output_text(self):
if sys.stdout.isatty() and format.is_interactive_format():
terminal.clear_row()

View File

@ -25,7 +25,6 @@ from ..localization import N_
from .. import changes, format, gravatar, terminal
from .outputable import Outputable
HISTORICAL_INFO_TEXT = N_("The following historical commit information, by author, was found in the repository")
NO_COMMITED_FILES_TEXT = N_("No commited files with the specified extensions were found")
@ -96,6 +95,38 @@ class ChangesOutput(Outputable):
changes_xml += "</div></div>"
print(changes_xml)
def output_json(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
for i in authorinfo_list:
total_changes += authorinfo_list.get(i).insertions
total_changes += authorinfo_list.get(i).deletions
if authorinfo_list:
message_xml = "\t\t\t\"message\": \"" + _(HISTORICAL_INFO_TEXT) + "\",\n"
changes_xml = ""
for i in sorted(authorinfo_list):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_xml = "\t\t\t\t\"name\": \"" + i + "\",\n"
gravatar_xml = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(self.changes.get_latest_email_by_author(i)) + "\",\n"
commits_xml = "\t\t\t\t\"commits\": " + str(authorinfo.commits) + ",\n"
insertions_xml = "\t\t\t\t\"insertions\": " + str(authorinfo.insertions) + ",\n"
deletions_xml = "\t\t\t\t\"deletions\": " + str(authorinfo.deletions) + ",\n"
percentage_xml = "\t\t\t\t\"percentage-of-changes\": " + "{0:.2f}".format(percentage) + "\n"
changes_xml += ("{\n" + name_xml + gravatar_xml + commits_xml + insertions_xml +
deletions_xml + percentage_xml + "\t\t\t}")
changes_xml += ","
else:
changes_xml = changes_xml[:-1]
print("\t\t\"changes\": {\n" + message_xml + "\t\t\t\"authors\": [\n\t\t\t" + changes_xml + "]\n\t\t}", end="")
else:
print("\t\t\"exception\": \"" + _(NO_COMMITED_FILES_TEXT) + "\"")
def output_text(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0

View File

@ -51,6 +51,24 @@ class ExtensionsOutput(Outputable):
extensions_xml += "</p></div></div>"
print(extensions_xml)
def output_json(self):
if extensions.__located_extensions__:
message_xml = "\t\t\t\"message\": \"" + _(EXTENSIONS_INFO_TEXT) + "\",\n"
used_extensions_xml = ""
unused_extensions_xml = ""
for i in sorted(extensions.__located_extensions__):
if ExtensionsOutput.is_marked(i):
used_extensions_xml += "\"" + i + "\", "
else:
unused_extensions_xml += "\"" + i + "\", "
used_extensions_xml = used_extensions_xml[:-2]
unused_extensions_xml = unused_extensions_xml[:-2]
print(",\n\t\t\"extensions\": {\n" + message_xml + "\t\t\t\"used\": [ " + used_extensions_xml + " ],\n" +
"\t\t\t\"unused\": [ " + unused_extensions_xml + " ]\n" + "\t\t}", end="")
def output_text(self):
if extensions.__located_extensions__:
print("\n" + textwrap.fill("{0} {1}:".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)),

View File

@ -25,7 +25,6 @@ from ..filtering import __filters__, has_filtered
from .. import terminal
from .outputable import Outputable
FILTERING_INFO_TEXT = N_("The following files were excluded from the statistics due to the specified exclusion patterns")
FILTERING_AUTHOR_INFO_TEXT = N_("The following authors were excluded from the statistics due to the specified exclusion patterns")
FILTERING_EMAIL_INFO_TEXT = N_("The authors with the following emails were excluded from the statistics due to the specified " \
@ -57,6 +56,33 @@ class FilteringOutput(Outputable):
print(filtering_xml)
@staticmethod
def __output_json_section__(info_string, filtered, container_tagname):
if filtered:
message_xml = "\t\t\t\t\"message\": \"" + info_string + "\",\n"
filtering_xml = ""
for i in filtered:
filtering_xml += "\t\t\t\t\t\"" + i + "\",\n"
else:
filtering_xml = filtering_xml[:-3]
return "\n\t\t\t\"{0}\": {{\n".format(container_tagname) + message_xml + \
"\t\t\t\t\"entries\": [\n" + filtering_xml + "\"\n\t\t\t\t]\n\t\t\t},"
return ""
def output_json(self):
if has_filtered():
output = ",\n\t\t\"filtering\": {"
output += FilteringOutput.__output_json_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files")
output += FilteringOutput.__output_json_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors")
output += FilteringOutput.__output_json_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails")
output += FilteringOutput.__output_json_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1], "revision")
output = output[:-1]
output += "\n\t\t}"
print(output, end="")
@staticmethod
def __output_text_section__(info_string, filtered):
if filtered:

View File

@ -92,6 +92,42 @@ class MetricsOutput(Outputable):
metrics_xml += "</div></div>"
print(metrics_xml)
def output_json(self):
metrics_logic = metrics.MetricsLogic()
if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density:
print(",\n\t\t\"metrics\": {\n\t\t\t\"message\": \"" + _(METRICS_MISSING_INFO_TEXT) + "\"\n\t\t}", end="")
else:
eloc_xml = ""
if metrics_logic.eloc:
for i in sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse=True):
eloc_xml += "{\n\t\t\t\t\"type\": \"estimated-lines-of-code\",\n"
eloc_xml += "\t\t\t\t\"file-name\": \"" + i[1] + "\",\n"
eloc_xml += "\t\t\t\t\"value\": " + str(i[0]) + "\n"
eloc_xml += "\t\t\t},"
else:
eloc_xml = eloc_xml[:-1]
if metrics_logic.cyclomatic_complexity:
for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse=True):
eloc_xml += "{\n\t\t\t\t\"type\": \"cyclomatic-complexity\",\n"
eloc_xml += "\t\t\t\t\"file-name\": \"" + i[1] + "\",\n"
eloc_xml += "\t\t\t\t\"value\": " + str(i[0]) + "\n"
eloc_xml += "\t\t\t},"
else:
eloc_xml = eloc_xml[:-1]
if metrics_logic.cyclomatic_complexity_density:
for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse=True):
eloc_xml += "{\n\t\t\t\t\"type\": \"cyclomatic-complexity-density\",\n"
eloc_xml += "\t\t\t\t\"file-name\": \"" + i[1] + "\",\n"
eloc_xml += "\t\t\t\t\"value\": {0:.3f} \"\n".format(i[0])
eloc_xml += "\t\t\t},"
else:
eloc_xml = eloc_xml[:-1]
print(",\n\t\t\"metrics\": {\n\t\t\t\"violations\": [\n\t\t\t" + eloc_xml + "]\n\t\t}", end="")
def output_xml(self):
metrics_logic = metrics.MetricsLogic()

View File

@ -23,17 +23,23 @@ from .. import format
class Outputable(object):
def output_html(self):
print(_("HTML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
raise NotImplementedError(_("HTML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_json(self):
raise NotImplementedError(_("JSON output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_text(self):
print(_("Text output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
raise NotImplementedError(_("Text output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output_xml(self):
print(_("XML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
NotImplementedError
raise NotImplementedError(_("XML output not yet supported in") + " \"" + self.__class__.__name__ + "\".")
def output(outputable):
if format.get_selected() == "html" or format.get_selected() == "htmlembedded":
outputable.output_html()
elif format.get_selected() == "json":
outputable.output_json()
elif format.get_selected() == "text":
outputable.output_text()
else:

View File

@ -81,6 +81,36 @@ class ResponsibilitiesOutput(Outputable):
resp_xml += "</div></div>"
print(resp_xml)
def output_json(self):
message_xml = "\t\t\t\"message\": \"" + _(RESPONSIBILITIES_INFO_TEXT) + "\",\n"
resp_xml = ""
for i in sorted(set(i[0] for i in blame.get(self.hard, self.useweeks, self.changes).blames)):
responsibilities = sorted(((i[1], i[0]) for i in resp.Responsibilities.get(self.hard, self.useweeks, i)), reverse=True)
if responsibilities:
author_email = self.changes.get_latest_email_by_author(i)
resp_xml += "{\n"
resp_xml += "\t\t\t\t\"name\": \"" + i + "\",\n"
resp_xml += "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
resp_xml += "\t\t\t\t\"files\": [\n\t\t\t\t"
for j, entry in enumerate(responsibilities):
resp_xml += "{\n"
resp_xml += "\t\t\t\t\t\"name\": \"" + entry[1] + "\",\n"
resp_xml += "\t\t\t\t\t\"rows\": " + str(entry[0]) + "\n"
resp_xml += "\t\t\t\t},"
if j >= 9:
break
resp_xml = resp_xml[:-1]
resp_xml += "]\n"
resp_xml += "\t\t\t},"
resp_xml = resp_xml[:-1]
print(",\n\t\t\"responsibilities\": {\n" + message_xml + "\t\t\t\"authors\": [\n\t\t\t" + resp_xml + "]\n\t\t}", end="")
def output_xml(self):
message_xml = "\t\t<message>" + _(RESPONSIBILITIES_INFO_TEXT) + "</message>\n"
resp_xml = ""

View File

@ -132,6 +132,45 @@ class TimelineOutput(Outputable):
timeline_xml = "</div></div>"
print(timeline_xml)
def output_json(self):
if self.changes.get_commits():
message_xml = "\t\t\t\"message\": \"" + _(TIMELINE_INFO_TEXT) + "\",\n"
timeline_xml = ""
periods_xml = "\t\t\t\"period-length\": \"{0}\",\n".format("week" if self.useweeks else "month")
periods_xml += "\t\t\t\"periods\": [\n\t\t\t"
timeline_data = timeline.TimelineData(self.changes, self.useweeks)
periods = timeline_data.get_periods()
names = timeline_data.get_authors()
for period in periods:
name_xml = "\t\t\t\t\"name\": \"" + str(period) + "\",\n"
authors_xml = "\t\t\t\t\"authors\": [\n\t\t\t\t"
for name in names:
if timeline_data.is_author_in_period(period, name[0]):
multiplier = timeline_data.get_multiplier(period, 24)
signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier)
signs_str = (signs[1] * "-" + signs[0] * "+")
if len(signs_str) == 0:
signs_str = "."
authors_xml += "{\n\t\t\t\t\t\"name\": \"" + name[0] + "\",\n"
authors_xml += "\t\t\t\t\t\"gravatar\": \"" + gravatar.get_url(name[1]) + "\",\n"
authors_xml += "\t\t\t\t\t\"work\": \"" + signs_str + "\"\n\t\t\t\t},"
else:
authors_xml = authors_xml[:-1]
authors_xml += "],\n"
modified_rows_xml = "\t\t\t\t\"modified_rows\": " + \
str(timeline_data.get_total_changes_in_period(period)[2]) + "\n"
timeline_xml += "{\n" + name_xml + authors_xml + modified_rows_xml + "\t\t\t},"
else:
timeline_xml = timeline_xml[:-1]
print(",\n\t\t\"timeline\": {\n" + message_xml + periods_xml + timeline_xml + "]\n\t\t}", end="")
def output_xml(self):
if self.changes.get_commits():
message_xml = "\t\t<message>" + _(TIMELINE_INFO_TEXT) + "</message>\n"