From 34337dec1757a9799c98823e535561e5849d4394 Mon Sep 17 00:00:00 2001 From: Adam Waldenberg Date: Mon, 12 Oct 2015 03:03:07 +0200 Subject: [PATCH] Refactored all outputable modules. This prepares the source code for the changes discussed in issue #24. Note that this is just a quick restructuring in order to see the resulting classes and separation. More work will be done to make it more elegant and with less dependencies between modules. --- gitinspector/blame.py | 108 ----------- gitinspector/changes.py | 132 -------------- gitinspector/extensions.py | 58 ------ gitinspector/filtering.py | 70 ------- gitinspector/gitinspector.py | 24 ++- gitinspector/metrics.py | 100 ---------- gitinspector/output/__init__.py | 20 ++ gitinspector/output/blameoutput.py | 135 ++++++++++++++ gitinspector/output/changesoutput.py | 155 ++++++++++++++++ gitinspector/output/extensionsoutput.py | 80 ++++++++ gitinspector/output/filteringoutput.py | 96 ++++++++++ gitinspector/output/metricsoutput.py | 125 +++++++++++++ gitinspector/{ => output}/outputable.py | 0 gitinspector/output/responsibilitiesoutput.py | 115 ++++++++++++ gitinspector/output/timelineoutput.py | 171 ++++++++++++++++++ gitinspector/responsibilities.py | 86 --------- gitinspector/timeline.py | 145 --------------- 17 files changed, 912 insertions(+), 708 deletions(-) create mode 100644 gitinspector/output/__init__.py create mode 100644 gitinspector/output/blameoutput.py create mode 100644 gitinspector/output/changesoutput.py create mode 100644 gitinspector/output/extensionsoutput.py create mode 100644 gitinspector/output/filteringoutput.py create mode 100644 gitinspector/output/metricsoutput.py rename gitinspector/{ => output}/outputable.py (100%) create mode 100644 gitinspector/output/responsibilitiesoutput.py create mode 100644 gitinspector/output/timelineoutput.py diff --git a/gitinspector/blame.py b/gitinspector/blame.py index 595d79c..4bb172e 100644 --- a/gitinspector/blame.py +++ b/gitinspector/blame.py @@ -20,21 +20,17 @@ from __future__ import print_function from __future__ import unicode_literals from localization import N_ -from outputable import Outputable from changes import FileDiff import comment import datetime import filtering import format -import gravatar import interval -import json import multiprocessing import re import subprocess import sys import terminal -import textwrap import threading NUM_THREADS = multiprocessing.cpu_count() @@ -205,107 +201,3 @@ def get(hard, useweeks, changes): __blame__ = Blame(hard, useweeks, changes) return __blame__ - -BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still " - "intact in the current revision") - -class BlameOutput(Outputable): - def __init__(self, changes, hard, useweeks): - if format.is_interactive_format(): - print("") - - self.changes = changes - self.hard = hard - self.useweeks = useweeks - get(self.hard, self.useweeks, self.changes) - Outputable.__init__(self) - - def output_html(self): - blame_xml = "
" - blame_xml += "

" + _(BLAME_INFO_TEXT) + ".

" - blame_xml += "".format( - _("Author"), _("Rows"), _("Stability"), _("Age"), _("% in comments")) - blame_xml += "" - chart_data = "" - blames = sorted(__blame__.get_summed_blames().items()) - total_blames = 0 - - for i in blames: - total_blames += i[1].rows - - for i, entry in enumerate(blames): - work_percentage = str("{0:.2f}".format(100.0 * entry[1].rows / total_blames)) - blame_xml += "" if i % 2 == 1 else ">") - - if format.get_selected() == "html": - author_email = self.changes.get_latest_email_by_author(entry[0]) - blame_xml += "".format(gravatar.get_url(author_email), entry[0]) - else: - blame_xml += "" - - blame_xml += "" - blame_xml += "") - blame_xml += "" - blame_xml += "" - blame_xml += "" - blame_xml += "" - chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry[0]), work_percentage) - - if blames[-1] != entry: - chart_data += ", " - - blame_xml += "
{0} {1} {2} {3} {4}
{1}" + entry[0] + "" + str(entry[1].rows) + "" + ("{0:.1f}".format(Blame.get_stability(entry[0], entry[1].rows, self.changes)) + "" + "{0:.1f}".format(float(entry[1].skew) / entry[1].rows) + "" + "{0:.2f}".format(100.0 * entry[1].comments / entry[1].rows) + "" + work_percentage + "
 
" - blame_xml += "
" - blame_xml += "
" - - print(blame_xml) - - def output_text(self): - if sys.stdout.isatty() and format.is_interactive_format(): - terminal.clear_row() - - print(textwrap.fill(_(BLAME_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n") - terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"), 15) + - terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20)) - - for i in sorted(__blame__.get_summed_blames().items()): - print(terminal.ljust(i[0], 20)[0:20 - terminal.get_excess_column_count(i[0])], end=" ") - print(str(i[1].rows).rjust(10), end=" ") - print("{0:.1f}".format(Blame.get_stability(i[0], i[1].rows, self.changes)).rjust(14), end=" ") - print("{0:.1f}".format(float(i[1].skew) / i[1].rows).rjust(12), end=" ") - print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19)) - - def output_xml(self): - message_xml = "\t\t" + _(BLAME_INFO_TEXT) + "\n" - blame_xml = "" - - for i in sorted(__blame__.get_summed_blames().items()): - author_email = self.changes.get_latest_email_by_author(i[0]) - - name_xml = "\t\t\t\t" + i[0] + "\n" - gravatar_xml = "\t\t\t\t" + gravatar.get_url(author_email) + "\n" - rows_xml = "\t\t\t\t" + str(i[1].rows) + "\n" - stability_xml = ("\t\t\t\t" + "{0:.1f}".format(Blame.get_stability(i[0], i[1].rows, - self.changes)) + "\n") - age_xml = ("\t\t\t\t" + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + "\n") - percentage_in_comments_xml = ("\t\t\t\t" + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) + - "\n") - blame_xml += ("\t\t\t\n" + name_xml + gravatar_xml + rows_xml + stability_xml + age_xml + - percentage_in_comments_xml + "\t\t\t\n") - - print("\t\n" + message_xml + "\t\t\n" + blame_xml + "\t\t\n\t") diff --git a/gitinspector/changes.py b/gitinspector/changes.py index 073150f..9e54edd 100644 --- a/gitinspector/changes.py +++ b/gitinspector/changes.py @@ -17,22 +17,14 @@ # You should have received a copy of the GNU General Public License # along with gitinspector. If not, see . -from __future__ import print_function from __future__ import unicode_literals -from localization import N_ -from outputable import Outputable import datetime import extensions import filtering -import format -import gravatar import interval -import json import multiprocessing import os import subprocess -import terminal -import textwrap import threading CHANGES_PER_THREAD = 200 @@ -269,127 +261,3 @@ def get(hard): return __changes__ -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") - -class ChangesOutput(Outputable): - def __init__(self, hard): - self.changes = get(hard) - Outputable.__init__(self) - - def output_html(self): - authorinfo_list = self.changes.get_authorinfo_list() - total_changes = 0.0 - changes_xml = "
" - chart_data = "" - - for i in authorinfo_list: - total_changes += authorinfo_list.get(i).insertions - total_changes += authorinfo_list.get(i).deletions - - if authorinfo_list: - changes_xml += "

" + _(HISTORICAL_INFO_TEXT) + ".

" - changes_xml += "".format( - _("Author"), _("Commits"), _("Insertions"), _("Deletions"), _("% of changes")) - changes_xml += "" - - for i, entry in enumerate(sorted(authorinfo_list)): - authorinfo = authorinfo_list.get(entry) - percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100 - - changes_xml += "" if i % 2 == 1 else ">") - - if format.get_selected() == "html": - changes_xml += "".format( - gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry) - else: - changes_xml += "" - - changes_xml += "" - changes_xml += "" - changes_xml += "" - changes_xml += "" - changes_xml += "" - chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry), "{0:.2f}".format(percentage)) - - if sorted(authorinfo_list)[-1] != entry: - chart_data += ", " - - changes_xml += ("
{0} {1} {2} {3} {4}
{1}" + entry + "" + str(authorinfo.commits) + "" + str(authorinfo.insertions) + "" + str(authorinfo.deletions) + "" + "{0:.2f}".format(percentage) + "
 
") - changes_xml += "
" - changes_xml += "" - else: - changes_xml += "

" + _(NO_COMMITED_FILES_TEXT) + ".

" - - changes_xml += "
" - print(changes_xml) - - def output_text(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: - print(textwrap.fill(_(HISTORICAL_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n") - terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Commits"), 13) + - terminal.rjust(_("Insertions"), 14) + terminal.rjust(_("Deletions"), 15) + - terminal.rjust(_("% of changes"), 16)) - - 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 - - print(terminal.ljust(i, 20)[0:20 - terminal.get_excess_column_count(i)], end=" ") - print(str(authorinfo.commits).rjust(13), end=" ") - print(str(authorinfo.insertions).rjust(13), end=" ") - print(str(authorinfo.deletions).rjust(14), end=" ") - print("{0:.2f}".format(percentage).rjust(15)) - else: - print(_(NO_COMMITED_FILES_TEXT) + ".") - - def output_xml(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" + _(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" + i + "\n" - gravatar_xml = "\t\t\t\t" + gravatar.get_url(self.changes.get_latest_email_by_author(i)) + "\n" - commits_xml = "\t\t\t\t" + str(authorinfo.commits) + "\n" - insertions_xml = "\t\t\t\t" + str(authorinfo.insertions) + "\n" - deletions_xml = "\t\t\t\t" + str(authorinfo.deletions) + "\n" - percentage_xml = "\t\t\t\t" + "{0:.2f}".format(percentage) + "\n" - - changes_xml += ("\t\t\t\n" + name_xml + gravatar_xml + commits_xml + insertions_xml + - deletions_xml + percentage_xml + "\t\t\t\n") - - print("\t\n" + message_xml + "\t\t\n" + changes_xml + "\t\t\n\t") - else: - print("\t\n\t\t" + _(NO_COMMITED_FILES_TEXT) + "\n\t") diff --git a/gitinspector/extensions.py b/gitinspector/extensions.py index 5084250..f565494 100644 --- a/gitinspector/extensions.py +++ b/gitinspector/extensions.py @@ -17,12 +17,7 @@ # You should have received a copy of the GNU General Public License # along with gitinspector. If not, see . -from __future__ import print_function from __future__ import unicode_literals -from localization import N_ -from outputable import Outputable -import terminal -import textwrap DEFAULT_EXTENSIONS = ["java", "c", "cc", "cpp", "h", "hh", "hpp", "py", "glsl", "rb", "js", "sql"] @@ -41,56 +36,3 @@ def add_located(string): __located_extensions__.add("*") else: __located_extensions__.add(string) - -EXTENSIONS_INFO_TEXT = N_("The extensions below were found in the repository history") -EXTENSIONS_MARKED_TEXT = N_("(extensions used during statistical analysis are marked)") - -class Extensions(Outputable): - @staticmethod - def is_marked(extension): - if extension in __extensions__ or "**" in __extensions__: - return True - - return False - - def output_html(self): - if __located_extensions__: - extensions_xml = "
" - extensions_xml += "

{0} {1}.

".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)) - - for i in sorted(__located_extensions__): - if Extensions.is_marked(i): - extensions_xml += "" + i + "" - else: - extensions_xml += i - extensions_xml += " " - - extensions_xml += "

" - print(extensions_xml) - - def output_text(self): - if __located_extensions__: - print("\n" + textwrap.fill("{0} {1}:".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)), - width=terminal.get_size()[0])) - - for i in sorted(__located_extensions__): - if Extensions.is_marked(i): - print("[" + terminal.__bold__ + i + terminal.__normal__ + "]", end=" ") - else: - print (i, end=" ") - print("") - - def output_xml(self): - if __located_extensions__: - message_xml = "\t\t" + _(EXTENSIONS_INFO_TEXT) + "\n" - used_extensions_xml = "" - unused_extensions_xml = "" - - for i in sorted(__located_extensions__): - if Extensions.is_marked(i): - used_extensions_xml += "\t\t\t" + i + "\n" - else: - unused_extensions_xml += "\t\t\t" + i + "\n" - - print("\t\n" + message_xml + "\t\t\n" + used_extensions_xml + "\t\t\n" + - "\t\t\n" + unused_extensions_xml + "\t\t\n" + "\t") diff --git a/gitinspector/filtering.py b/gitinspector/filtering.py index 22f8079..2e93349 100644 --- a/gitinspector/filtering.py +++ b/gitinspector/filtering.py @@ -19,8 +19,6 @@ from __future__ import print_function from __future__ import unicode_literals -from localization import N_ -from outputable import Outputable import re import subprocess import terminal @@ -92,71 +90,3 @@ def set_filtered(string, filter_type="file"): except: raise InvalidRegExpError(_("invalid regular expression specified")) return False - -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 " \ - "exclusion patterns") -FILTERING_COMMIT_INFO_TEXT = N_("The following commit revisions were excluded from the statistics due to the specified " \ - "exclusion patterns") - -class Filtering(Outputable): - @staticmethod - def __output_html_section__(info_string, filtered): - filtering_xml = "" - - if filtered: - filtering_xml += "

" + info_string + "."+ "

" - - for i in filtered: - filtering_xml += "

" + i + "

" - - return filtering_xml - - def output_html(self): - if has_filtered(): - filtering_xml = "
" - Filtering.__output_html_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1]) - Filtering.__output_html_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1]) - Filtering.__output_html_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1]) - Filtering.__output_html_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1]) - filtering_xml += "
" - - print(filtering_xml) - - @staticmethod - def __output_text_section__(info_string, filtered): - if filtered: - print("\n" + textwrap.fill(info_string + ":", width=terminal.get_size()[0])) - - for i in filtered: - (width, _unused) = terminal.get_size() - print("...%s" % i[-width+3:] if len(i) > width else i) - - def output_text(self): - Filtering.__output_text_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1]) - Filtering.__output_text_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1]) - Filtering.__output_text_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1]) - Filtering.__output_text_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1]) - - @staticmethod - def __output_xml_section__(info_string, filtered, container_tagname): - if filtered: - message_xml = "\t\t\t" +info_string + "\n" - filtering_xml = "" - - for i in filtered: - filtering_xml += "\t\t\t\t".format(container_tagname) + i + "\n".format(container_tagname) - - print("\t\t<{0}>".format(container_tagname)) - print(message_xml + "\t\t\t\n" + filtering_xml + "\t\t\t\n") - print("\t\t".format(container_tagname)) - - def output_xml(self): - if has_filtered(): - print("\t") - Filtering.__output_xml_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files") - Filtering.__output_xml_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors") - Filtering.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails") - Filtering.__output_xml_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1], "revision") - print("\t") diff --git a/gitinspector/gitinspector.py b/gitinspector/gitinspector.py index 2263b31..447ebcc 100755 --- a/gitinspector/gitinspector.py +++ b/gitinspector/gitinspector.py @@ -20,10 +20,17 @@ from __future__ import print_function from __future__ import unicode_literals - import localization localization.init() +from output import outputable +from output.blameoutput import BlameOutput +from output.changesoutput import ChangesOutput +from output.extensionsoutput import ExtensionsOutput +from output.filteringoutput import FilteringOutput +from output.metricsoutput import MetricsOutput +from output.responsibilitiesoutput import ResponsibilitiesOutput +from output.timelineoutput import TimelineOutput import atexit import basedir import blame @@ -39,7 +46,6 @@ import getopt import metrics import os import optval -import outputable import responsibilities import sys import terminal @@ -72,24 +78,24 @@ class Runner: absolute_path = basedir.get_basedir_git() os.chdir(absolute_path) format.output_header() - outputable.output(changes.ChangesOutput(self.hard)) + outputable.output(ChangesOutput(self.hard)) if changes.get(self.hard).get_commits(): - outputable.output(blame.BlameOutput(changes.get(self.hard), self.hard, self.useweeks)) + outputable.output(BlameOutput(changes.get(self.hard), self.hard, self.useweeks)) if self.timeline: - outputable.output(timeline.Timeline(changes.get(self.hard), self.useweeks)) + outputable.output(TimelineOutput(changes.get(self.hard), self.useweeks)) if self.include_metrics: - outputable.output(metrics.Metrics()) + outputable.output(MetricsOutput()) if self.responsibilities: - outputable.output(responsibilities.ResponsibilitiesOutput(self.hard, self.useweeks)) + outputable.output(ResponsibilitiesOutput(self.hard, self.useweeks)) - outputable.output(filtering.Filtering()) + outputable.output(FilteringOutput()) if self.list_file_types: - outputable.output(extensions.Extensions()) + outputable.output(ExtensionsOutput()) format.output_footer() os.chdir(previous_directory) diff --git a/gitinspector/metrics.py b/gitinspector/metrics.py index 67bac58..f430fcb 100644 --- a/gitinspector/metrics.py +++ b/gitinspector/metrics.py @@ -116,103 +116,3 @@ class MetricsLogic: eloc_counter += 1 return eloc_counter - -ELOC_INFO_TEXT = N_("The following files are suspiciously big (in order of severity)") -CYCLOMATIC_COMPLEXITY_TEXT = N_("The following files have an elevated cyclomatic complexity (in order of severity)") -CYCLOMATIC_COMPLEXITY_DENSITY_TEXT = N_("The following files have an elevated cyclomatic complexity density " \ - "(in order of severity)") -METRICS_MISSING_INFO_TEXT = N_("No metrics violations were found in the repository") - -METRICS_VIOLATION_SCORES = [[1.0, "minimal"], [1.25, "minor"], [1.5, "medium"], [2.0, "bad"], [3.0, "severe"]] - -def __get_metrics_score__(ceiling, value): - for i in reversed(METRICS_VIOLATION_SCORES): - if value > ceiling * i[0]: - return i[1] - -class Metrics(Outputable): - def output_text(self): - metrics_logic = MetricsLogic() - - if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density: - print("\n" + _(METRICS_MISSING_INFO_TEXT) + ".") - - if metrics_logic.eloc: - print("\n" + _(ELOC_INFO_TEXT) + ":") - for i in sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse = True): - print(_("{0} ({1} estimated lines of code)").format(i[1], str(i[0]))) - - if metrics_logic.cyclomatic_complexity: - print("\n" + _(CYCLOMATIC_COMPLEXITY_TEXT) + ":") - for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse = True): - print(_("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0]))) - - if metrics_logic.cyclomatic_complexity_density: - print("\n" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + ":") - for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse = True): - print(_("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0])) - - def output_html(self): - metrics_logic = MetricsLogic() - metrics_xml = "
" - - if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density: - metrics_xml += "

" + _(METRICS_MISSING_INFO_TEXT) + ".

" - - if metrics_logic.eloc: - metrics_xml += "

" + _(ELOC_INFO_TEXT) + ".

" - for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse = True)): - metrics_xml += "
" if num % 2 == 1 else "\">") + \ - _("{0} ({1} estimated lines of code)").format(i[1], str(i[0])) + "
" - metrics_xml += "
" - - if metrics_logic.cyclomatic_complexity: - metrics_xml += "

" + _(CYCLOMATIC_COMPLEXITY_TEXT) + "

" - for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse = True)): - metrics_xml += "
" if num % 2 == 1 else "\">") + \ - _("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0])) + "
" - metrics_xml += "
" - - if metrics_logic.cyclomatic_complexity_density: - metrics_xml += "

" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + "

" - for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse = True)): - metrics_xml += "
" if num % 2 == 1 else "\">") + \ - _("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0]) + "
" - metrics_xml += "
" - - metrics_xml += "
" - print(metrics_xml) - - def output_xml(self): - metrics_logic = MetricsLogic() - - if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density: - print("\t\n\t\t" + _(METRICS_MISSING_INFO_TEXT) + "\n\t") - 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 += "\t\t\t\n" - eloc_xml += "\t\t\t\t" + i[1] + "\n" - eloc_xml += "\t\t\t\t" + str(i[0]) + "\n" - eloc_xml += "\t\t\t\n" - - 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 += "\t\t\t\n" - eloc_xml += "\t\t\t\t" + i[1] + "\n" - eloc_xml += "\t\t\t\t" + str(i[0]) + "\n" - eloc_xml += "\t\t\t\n" - - 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 += "\t\t\t\n" - eloc_xml += "\t\t\t\t" + i[1] + "\n" - eloc_xml += "\t\t\t\t{0:.3f}\n".format(i[0]) - eloc_xml += "\t\t\t\n" - - print("\t\n\t\t\n" + eloc_xml + "\t\t\n\t") diff --git a/gitinspector/output/__init__.py b/gitinspector/output/__init__.py new file mode 100644 index 0000000..354686a --- /dev/null +++ b/gitinspector/output/__init__.py @@ -0,0 +1,20 @@ +# 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 . + +# This file was intentionally left blank. diff --git a/gitinspector/output/blameoutput.py b/gitinspector/output/blameoutput.py new file mode 100644 index 0000000..bc64881 --- /dev/null +++ b/gitinspector/output/blameoutput.py @@ -0,0 +1,135 @@ +# coding: utf-8 +# +# Copyright © 2012-2015 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +from localization import N_ +from .outputable import Outputable +import blame +import format +import gravatar +import json +import sys +import terminal +import textwrap + +BLAME_INFO_TEXT = N_("Below are the number of rows from each author that have survived and are still " + "intact in the current revision") + +class BlameOutput(Outputable): + def __init__(self, changes, hard, useweeks): + if format.is_interactive_format(): + print("") + + self.changes = changes + self.hard = hard + self.useweeks = useweeks + blame.get(self.hard, self.useweeks, self.changes) + Outputable.__init__(self) + + def output_html(self): + blame_xml = "
" + blame_xml += "

" + _(BLAME_INFO_TEXT) + ".

" + blame_xml += "".format( + _("Author"), _("Rows"), _("Stability"), _("Age"), _("% in comments")) + blame_xml += "" + chart_data = "" + blames = sorted(blame.__blame__.get_summed_blames().items()) + total_blames = 0 + + for i in blames: + total_blames += i[1].rows + + for i, entry in enumerate(blames): + work_percentage = str("{0:.2f}".format(100.0 * entry[1].rows / total_blames)) + blame_xml += "" if i % 2 == 1 else ">") + + if format.get_selected() == "html": + author_email = self.changes.get_latest_email_by_author(entry[0]) + blame_xml += "".format(gravatar.get_url(author_email), entry[0]) + else: + blame_xml += "" + + blame_xml += "" + blame_xml += "") + blame_xml += "" + blame_xml += "" + blame_xml += "" + blame_xml += "" + chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry[0]), work_percentage) + + if blames[-1] != entry: + chart_data += ", " + + blame_xml += "
{0} {1} {2} {3} {4}
{1}" + entry[0] + "" + str(entry[1].rows) + "" + ("{0:.1f}".format(blame.Blame.get_stability(entry[0], entry[1].rows, self.changes)) + "" + "{0:.1f}".format(float(entry[1].skew) / entry[1].rows) + "" + "{0:.2f}".format(100.0 * entry[1].comments / entry[1].rows) + "" + work_percentage + "
 
" + blame_xml += "
" + blame_xml += "
" + + print(blame_xml) + + def output_text(self): + if sys.stdout.isatty() and format.is_interactive_format(): + terminal.clear_row() + + print(textwrap.fill(_(BLAME_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n") + terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"), 15) + + terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20)) + + for i in sorted(blame.__blame__.get_summed_blames().items()): + print(terminal.ljust(i[0], 20)[0:20 - terminal.get_excess_column_count(i[0])], end=" ") + print(str(i[1].rows).rjust(10), end=" ") + print("{0:.1f}".format(blame.Blame.get_stability(i[0], i[1].rows, self.changes)).rjust(14), end=" ") + print("{0:.1f}".format(float(i[1].skew) / i[1].rows).rjust(12), end=" ") + print("{0:.2f}".format(100.0 * i[1].comments / i[1].rows).rjust(19)) + + def output_xml(self): + message_xml = "\t\t" + _(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" + i[0] + "\n" + gravatar_xml = "\t\t\t\t" + gravatar.get_url(author_email) + "\n" + rows_xml = "\t\t\t\t" + str(i[1].rows) + "\n" + stability_xml = ("\t\t\t\t" + "{0:.1f}".format(blame.Blame.get_stability(i[0], i[1].rows, + self.changes)) + "\n") + age_xml = ("\t\t\t\t" + "{0:.1f}".format(float(i[1].skew) / i[1].rows) + "\n") + percentage_in_comments_xml = ("\t\t\t\t" + "{0:.2f}".format(100.0 * i[1].comments / i[1].rows) + + "\n") + blame_xml += ("\t\t\t\n" + name_xml + gravatar_xml + rows_xml + stability_xml + age_xml + + percentage_in_comments_xml + "\t\t\t\n") + + print("\t\n" + message_xml + "\t\t\n" + blame_xml + "\t\t\n\t") diff --git a/gitinspector/output/changesoutput.py b/gitinspector/output/changesoutput.py new file mode 100644 index 0000000..03331aa --- /dev/null +++ b/gitinspector/output/changesoutput.py @@ -0,0 +1,155 @@ +# coding: utf-8 +# +# Copyright © 2012-2015 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +from localization import N_ +from .outputable import Outputable +import changes +import format +import gravatar +import json +import terminal +import textwrap + +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") + +class ChangesOutput(Outputable): + def __init__(self, hard): + self.changes = changes.get(hard) + Outputable.__init__(self) + + def output_html(self): + authorinfo_list = self.changes.get_authorinfo_list() + total_changes = 0.0 + changes_xml = "
" + chart_data = "" + + for i in authorinfo_list: + total_changes += authorinfo_list.get(i).insertions + total_changes += authorinfo_list.get(i).deletions + + if authorinfo_list: + changes_xml += "

" + _(HISTORICAL_INFO_TEXT) + ".

" + changes_xml += "".format( + _("Author"), _("Commits"), _("Insertions"), _("Deletions"), _("% of changes")) + changes_xml += "" + + for i, entry in enumerate(sorted(authorinfo_list)): + authorinfo = authorinfo_list.get(entry) + percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100 + + changes_xml += "" if i % 2 == 1 else ">") + + if format.get_selected() == "html": + changes_xml += "".format( + gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry) + else: + changes_xml += "" + + changes_xml += "" + changes_xml += "" + changes_xml += "" + changes_xml += "" + changes_xml += "" + chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry), "{0:.2f}".format(percentage)) + + if sorted(authorinfo_list)[-1] != entry: + chart_data += ", " + + changes_xml += ("
{0} {1} {2} {3} {4}
{1}" + entry + "" + str(authorinfo.commits) + "" + str(authorinfo.insertions) + "" + str(authorinfo.deletions) + "" + "{0:.2f}".format(percentage) + "
 
") + changes_xml += "
" + changes_xml += "" + else: + changes_xml += "

" + _(NO_COMMITED_FILES_TEXT) + ".

" + + changes_xml += "
" + print(changes_xml) + + def output_text(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: + print(textwrap.fill(_(HISTORICAL_INFO_TEXT) + ":", width=terminal.get_size()[0]) + "\n") + terminal.printb(terminal.ljust(_("Author"), 21) + terminal.rjust(_("Commits"), 13) + + terminal.rjust(_("Insertions"), 14) + terminal.rjust(_("Deletions"), 15) + + terminal.rjust(_("% of changes"), 16)) + + 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 + + print(terminal.ljust(i, 20)[0:20 - terminal.get_excess_column_count(i)], end=" ") + print(str(authorinfo.commits).rjust(13), end=" ") + print(str(authorinfo.insertions).rjust(13), end=" ") + print(str(authorinfo.deletions).rjust(14), end=" ") + print("{0:.2f}".format(percentage).rjust(15)) + else: + print(_(NO_COMMITED_FILES_TEXT) + ".") + + def output_xml(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" + _(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" + i + "\n" + gravatar_xml = "\t\t\t\t" + gravatar.get_url(self.changes.get_latest_email_by_author(i)) + "\n" + commits_xml = "\t\t\t\t" + str(authorinfo.commits) + "\n" + insertions_xml = "\t\t\t\t" + str(authorinfo.insertions) + "\n" + deletions_xml = "\t\t\t\t" + str(authorinfo.deletions) + "\n" + percentage_xml = "\t\t\t\t" + "{0:.2f}".format(percentage) + "\n" + + changes_xml += ("\t\t\t\n" + name_xml + gravatar_xml + commits_xml + insertions_xml + + deletions_xml + percentage_xml + "\t\t\t\n") + + print("\t\n" + message_xml + "\t\t\n" + changes_xml + "\t\t\n\t") + else: + print("\t\n\t\t" + _(NO_COMMITED_FILES_TEXT) + "\n\t") diff --git a/gitinspector/output/extensionsoutput.py b/gitinspector/output/extensionsoutput.py new file mode 100644 index 0000000..9c47ebf --- /dev/null +++ b/gitinspector/output/extensionsoutput.py @@ -0,0 +1,80 @@ +# coding: utf-8 +# +# Copyright © 2012-2015 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +import extensions +from localization import N_ +from .outputable import Outputable +import terminal +import textwrap + +EXTENSIONS_INFO_TEXT = N_("The extensions below were found in the repository history") +EXTENSIONS_MARKED_TEXT = N_("(extensions used during statistical analysis are marked)") + +class ExtensionsOutput(Outputable): + @staticmethod + def is_marked(extension): + if extension in extensions.__extensions__ or "**" in extensions.__extensions__: + return True + + return False + + def output_html(self): + if extensions.__located_extensions__: + extensions_xml = "
" + extensions_xml += "

{0} {1}.

".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)) + + for i in sorted(extensions.__located_extensions__): + if ExtensionsOutput.is_marked(i): + extensions_xml += "" + i + "" + else: + extensions_xml += i + extensions_xml += " " + + extensions_xml += "

" + print(extensions_xml) + + def output_text(self): + if extensions.__located_extensions__: + print("\n" + textwrap.fill("{0} {1}:".format(_(EXTENSIONS_INFO_TEXT), _(EXTENSIONS_MARKED_TEXT)), + width=terminal.get_size()[0])) + + for i in sorted(extensions.__located_extensions__): + if ExtensionsOutput.is_marked(i): + print("[" + terminal.__bold__ + i + terminal.__normal__ + "]", end=" ") + else: + print (i, end=" ") + print("") + + def output_xml(self): + if extensions.__located_extensions__: + message_xml = "\t\t" + _(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 += "\t\t\t" + i + "\n" + else: + unused_extensions_xml += "\t\t\t" + i + "\n" + + print("\t\n" + message_xml + "\t\t\n" + used_extensions_xml + "\t\t\n" + + "\t\t\n" + unused_extensions_xml + "\t\t\n" + "\t") diff --git a/gitinspector/output/filteringoutput.py b/gitinspector/output/filteringoutput.py new file mode 100644 index 0000000..5cb5287 --- /dev/null +++ b/gitinspector/output/filteringoutput.py @@ -0,0 +1,96 @@ +# coding: utf-8 +# +# Copyright © 2012-2015 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +from filtering import __filters__ +from filtering import has_filtered +from localization import N_ +from .outputable import Outputable +import terminal +import textwrap + +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 " \ + "exclusion patterns") +FILTERING_COMMIT_INFO_TEXT = N_("The following commit revisions were excluded from the statistics due to the specified " \ + "exclusion patterns") + +class FilteringOutput(Outputable): + @staticmethod + def __output_html_section__(info_string, filtered): + filtering_xml = "" + + if filtered: + filtering_xml += "

" + info_string + "."+ "

" + + for i in filtered: + filtering_xml += "

" + i + "

" + + return filtering_xml + + def output_html(self): + if has_filtered(): + filtering_xml = "
" + FilteringOutput.__output_html_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1]) + FilteringOutput.__output_html_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1]) + FilteringOutput.__output_html_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1]) + FilteringOutput.__output_html_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1]) + filtering_xml += "
" + + print(filtering_xml) + + @staticmethod + def __output_text_section__(info_string, filtered): + if filtered: + print("\n" + textwrap.fill(info_string + ":", width=terminal.get_size()[0])) + + for i in filtered: + (width, _unused) = terminal.get_size() + print("...%s" % i[-width+3:] if len(i) > width else i) + + def output_text(self): + FilteringOutput.__output_text_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1]) + FilteringOutput.__output_text_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1]) + FilteringOutput.__output_text_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1]) + FilteringOutput.__output_text_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1]) + + @staticmethod + def __output_xml_section__(info_string, filtered, container_tagname): + if filtered: + message_xml = "\t\t\t" +info_string + "\n" + filtering_xml = "" + + for i in filtered: + filtering_xml += "\t\t\t\t".format(container_tagname) + i + "\n".format(container_tagname) + + print("\t\t<{0}>".format(container_tagname)) + print(message_xml + "\t\t\t\n" + filtering_xml + "\t\t\t\n") + print("\t\t".format(container_tagname)) + + def output_xml(self): + if has_filtered(): + print("\t") + FilteringOutput.__output_xml_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1], "files") + FilteringOutput.__output_xml_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1], "authors") + FilteringOutput.__output_xml_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1], "emails") + FilteringOutput.__output_xml_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1], "revision") + print("\t") diff --git a/gitinspector/output/metricsoutput.py b/gitinspector/output/metricsoutput.py new file mode 100644 index 0000000..8414ba2 --- /dev/null +++ b/gitinspector/output/metricsoutput.py @@ -0,0 +1,125 @@ +# coding: utf-8 +# +# Copyright © 2012-2015 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +from localization import N_ +from .outputable import Outputable +import metrics + +ELOC_INFO_TEXT = N_("The following files are suspiciously big (in order of severity)") +CYCLOMATIC_COMPLEXITY_TEXT = N_("The following files have an elevated cyclomatic complexity (in order of severity)") +CYCLOMATIC_COMPLEXITY_DENSITY_TEXT = N_("The following files have an elevated cyclomatic complexity density " \ + "(in order of severity)") +METRICS_MISSING_INFO_TEXT = N_("No metrics violations were found in the repository") + +METRICS_VIOLATION_SCORES = [[1.0, "minimal"], [1.25, "minor"], [1.5, "medium"], [2.0, "bad"], [3.0, "severe"]] + +def __get_metrics_score__(ceiling, value): + for i in reversed(METRICS_VIOLATION_SCORES): + if value > ceiling * i[0]: + return i[1] + +class MetricsOutput(Outputable): + def output_text(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" + _(METRICS_MISSING_INFO_TEXT) + ".") + + if metrics_logic.eloc: + print("\n" + _(ELOC_INFO_TEXT) + ":") + for i in sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse=True): + print(_("{0} ({1} estimated lines of code)").format(i[1], str(i[0]))) + + if metrics_logic.cyclomatic_complexity: + print("\n" + _(CYCLOMATIC_COMPLEXITY_TEXT) + ":") + for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse=True): + print(_("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0]))) + + if metrics_logic.cyclomatic_complexity_density: + print("\n" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + ":") + for i in sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse=True): + print(_("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0])) + + def output_html(self): + metrics_logic = metrics.MetricsLogic() + metrics_xml = "
" + + if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density: + metrics_xml += "

" + _(METRICS_MISSING_INFO_TEXT) + ".

" + + if metrics_logic.eloc: + metrics_xml += "

" + _(ELOC_INFO_TEXT) + ".

" + for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.eloc.items()]), reverse=True)): + metrics_xml += "
" if num % 2 == 1 else "\">") + \ + _("{0} ({1} estimated lines of code)").format(i[1], str(i[0])) + "
" + metrics_xml += "
" + + if metrics_logic.cyclomatic_complexity: + metrics_xml += "

" + _(CYCLOMATIC_COMPLEXITY_TEXT) + "

" + for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity.items()]), reverse=True)): + metrics_xml += "
" if num % 2 == 1 else "\">") + \ + _("{0} ({1} in cyclomatic complexity)").format(i[1], str(i[0])) + "
" + metrics_xml += "
" + + if metrics_logic.cyclomatic_complexity_density: + metrics_xml += "

" + _(CYCLOMATIC_COMPLEXITY_DENSITY_TEXT) + "

" + for num, i in enumerate(sorted(set([(j, i) for (i, j) in metrics_logic.cyclomatic_complexity_density.items()]), reverse=True)): + metrics_xml += "
" if num % 2 == 1 else "\">") + \ + _("{0} ({1:.3f} in cyclomatic complexity density)").format(i[1], i[0]) + "
" + metrics_xml += "
" + + metrics_xml += "
" + print(metrics_xml) + + def output_xml(self): + metrics_logic = metrics.MetricsLogic() + + if not metrics_logic.eloc and not metrics_logic.cyclomatic_complexity and not metrics_logic.cyclomatic_complexity_density: + print("\t\n\t\t" + _(METRICS_MISSING_INFO_TEXT) + "\n\t") + 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 += "\t\t\t\n" + eloc_xml += "\t\t\t\t" + i[1] + "\n" + eloc_xml += "\t\t\t\t" + str(i[0]) + "\n" + eloc_xml += "\t\t\t\n" + + 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 += "\t\t\t\n" + eloc_xml += "\t\t\t\t" + i[1] + "\n" + eloc_xml += "\t\t\t\t" + str(i[0]) + "\n" + eloc_xml += "\t\t\t\n" + + 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 += "\t\t\t\n" + eloc_xml += "\t\t\t\t" + i[1] + "\n" + eloc_xml += "\t\t\t\t{0:.3f}\n".format(i[0]) + eloc_xml += "\t\t\t\n" + + print("\t\n\t\t\n" + eloc_xml + "\t\t\n\t") diff --git a/gitinspector/outputable.py b/gitinspector/output/outputable.py similarity index 100% rename from gitinspector/outputable.py rename to gitinspector/output/outputable.py diff --git a/gitinspector/output/responsibilitiesoutput.py b/gitinspector/output/responsibilitiesoutput.py new file mode 100644 index 0000000..8791ed7 --- /dev/null +++ b/gitinspector/output/responsibilitiesoutput.py @@ -0,0 +1,115 @@ +# coding: utf-8 +# +# Copyright © 2012-2014 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +from localization import N_ +from .outputable import Outputable +import blame +import changes +import format +import gravatar +import responsibilities as resp +import terminal +import textwrap + +RESPONSIBILITIES_INFO_TEXT = N_("The following repsonsibilties, by author, were found in the current " + "revision of the repository (comments are exluded from the line count, " + "if possible)") +MOSTLY_RESPONSIBLE_FOR_TEXT = N_("is mostly responsible for") + +class ResponsibilitiesOutput(Outputable): + def __init__(self, hard, useweeks): + self.hard = hard + self.useweeks = useweeks + Outputable.__init__(self) + self.changes = changes.get(hard) + + def output_text(self): + print("\n" + textwrap.fill(_(RESPONSIBILITIES_INFO_TEXT) + ":", width=terminal.get_size()[0])) + + 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: + print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":") + + for j, entry in enumerate(responsibilities): + (width, _unused) = terminal.get_size() + width -= 7 + + print(str(entry[0]).rjust(6), end=" ") + print("...%s" % entry[1][-width+3:] if len(entry[1]) > width else entry[1]) + + if j >= 9: + break + + def output_html(self): + resp_xml = "
" + resp_xml += "

" + _(RESPONSIBILITIES_INFO_TEXT) + ".

" + + 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: + resp_xml += "
" + + if format.get_selected() == "html": + author_email = self.changes.get_latest_email_by_author(i) + resp_xml += "

{1} {2}

".format(gravatar.get_url(author_email, size=32), + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT)) + else: + resp_xml += "

{0} {1}

".format(i, _(MOSTLY_RESPONSIBLE_FOR_TEXT)) + + for j, entry in enumerate(responsibilities): + resp_xml += "" if j % 2 == 1 else ">") + entry[1] + \ + " (" + str(entry[0]) + " eloc)
" + if j >= 9: + break + + resp_xml += "
" + resp_xml += "
" + print(resp_xml) + + def output_xml(self): + message_xml = "\t\t" + _(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 += "\t\t\t\n" + resp_xml += "\t\t\t\t" + i + "\n" + resp_xml += "\t\t\t\t" + gravatar.get_url(author_email) + "\n" + resp_xml += "\t\t\t\t\n" + + for j, entry in enumerate(responsibilities): + resp_xml += "\t\t\t\t\t\n" + resp_xml += "\t\t\t\t\t\t" + entry[1] + "\n" + resp_xml += "\t\t\t\t\t\t" + str(entry[0]) + "\n" + resp_xml += "\t\t\t\t\t\n" + + if j >= 9: + break + + resp_xml += "\t\t\t\t\n" + resp_xml += "\t\t\t\n" + + print("\t\n" + message_xml + "\t\t\n" + resp_xml + "\t\t\n\t") diff --git a/gitinspector/output/timelineoutput.py b/gitinspector/output/timelineoutput.py new file mode 100644 index 0000000..f030962 --- /dev/null +++ b/gitinspector/output/timelineoutput.py @@ -0,0 +1,171 @@ +# coding: utf-8 +# +# Copyright © 2012-2014 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 . + +from __future__ import absolute_import +from __future__ import print_function +from __future__ import unicode_literals +from localization import N_ +from .outputable import Outputable +import format +import gravatar +import terminal +import textwrap +import timeline + +TIMELINE_INFO_TEXT = N_("The following history timeline has been gathered from the repository") +MODIFIED_ROWS_TEXT = N_("Modified Rows:") + +def __output_row__text__(timeline_data, periods, names): + print("\n" + terminal.__bold__ + terminal.ljust(_("Author"), 20), end=" ") + + for period in periods: + print(terminal.rjust(period, 10), end=" ") + + print(terminal.__normal__) + + for name in names: + if timeline_data.is_author_in_periods(periods, name[0]): + print(terminal.ljust(name[0], 20)[0:20 - terminal.get_excess_column_count(name[0])], end=" ") + + for period in periods: + multiplier = timeline_data.get_multiplier(period, 9) + signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier) + signs_str = (signs[1] * "-" + signs[0] * "+") + print (("." if timeline_data.is_author_in_period(period, name[0]) and + len(signs_str) == 0 else signs_str).rjust(10), end=" ") + print("") + + print(terminal.__bold__ + terminal.ljust(_(MODIFIED_ROWS_TEXT), 20) + terminal.__normal__, end=" ") + + for period in periods: + total_changes = str(timeline_data.get_total_changes_in_period(period)[2]) + + if hasattr(total_changes, 'decode'): + total_changes = total_changes.decode("utf-8", "replace") + + print(terminal.rjust(total_changes, 10), end=" ") + + print("") + +def __output_row__html__(timeline_data, periods, names): + timeline_xml = "" + + for period in periods: + timeline_xml += "" + + timeline_xml += "" + i = 0 + + for name in names: + if timeline_data.is_author_in_periods(periods, name[0]): + timeline_xml += "" if i % 2 == 1 else ">") + + if format.get_selected() == "html": + timeline_xml += "".format(gravatar.get_url(name[1]), name[0]) + else: + timeline_xml += "" + + for period in periods: + multiplier = timeline_data.get_multiplier(period, 18) + signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier) + signs_str = (signs[1] * "
 
" + signs[0] * "
 
") + + timeline_xml += "" + timeline_xml += "" + i = i + 1 + + timeline_xml += "" + + for period in periods: + total_changes = timeline_data.get_total_changes_in_period(period) + timeline_xml += "" + + timeline_xml += "
" + _("Author") + "" + str(period) + "
{1}" + name[0] + "" + ("." if timeline_data.is_author_in_period(period, name[0]) and len(signs_str) == 0 else signs_str) + timeline_xml += "
" + _(MODIFIED_ROWS_TEXT) + "" + str(total_changes[2]) + "
" + print(timeline_xml) + +class TimelineOutput(Outputable): + def __init__(self, changes, useweeks): + self.changes = changes + self.useweeks = useweeks + Outputable.__init__(self) + + def output_text(self): + if self.changes.get_commits(): + print("\n" + textwrap.fill(_(TIMELINE_INFO_TEXT) + ":", width=terminal.get_size()[0])) + + timeline_data = timeline.TimelineData(self.changes, self.useweeks) + periods = timeline_data.get_periods() + names = timeline_data.get_authors() + (width, _unused) = terminal.get_size() + max_periods_per_row = int((width - 21) / 11) + + for i in range(0, len(periods), max_periods_per_row): + __output_row__text__(timeline_data, periods[i:i+max_periods_per_row], names) + + def output_html(self): + if self.changes.get_commits(): + timeline_data = timeline.TimelineData(self.changes, self.useweeks) + periods = timeline_data.get_periods() + names = timeline_data.get_authors() + max_periods_per_row = 8 + + timeline_xml = "
" + timeline_xml += "

" + _(TIMELINE_INFO_TEXT) + ".

" + print(timeline_xml) + + for i in range(0, len(periods), max_periods_per_row): + __output_row__html__(timeline_data, periods[i:i+max_periods_per_row], names) + + timeline_xml = "
" + print(timeline_xml) + + def output_xml(self): + if self.changes.get_commits(): + message_xml = "\t\t" + _(TIMELINE_INFO_TEXT) + "\n" + timeline_xml = "" + periods_xml = "\t\t\n".format("week" if self.useweeks else "month") + + 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" + str(period) + "\n" + authors_xml = "\t\t\t\t\n" + + 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 += "\t\t\t\t\t\n\t\t\t\t\t\t" + name[0] + "\n" + authors_xml += "\t\t\t\t\t\t" + gravatar.get_url(name[1]) + "\n" + authors_xml += "\t\t\t\t\t\t" + signs_str + "\n\t\t\t\t\t\n" + + authors_xml += "\t\t\t\t\n" + modified_rows_xml = "\t\t\t\t" + \ + str(timeline_data.get_total_changes_in_period(period)[2]) + "\n" + timeline_xml += "\t\t\t\n" + name_xml + authors_xml + modified_rows_xml + "\t\t\t\n" + + print("\t\n" + message_xml + periods_xml + timeline_xml + "\t\t\n\t") diff --git a/gitinspector/responsibilities.py b/gitinspector/responsibilities.py index f92334c..6a95e58 100644 --- a/gitinspector/responsibilities.py +++ b/gitinspector/responsibilities.py @@ -19,8 +19,6 @@ from __future__ import print_function from __future__ import unicode_literals -from localization import N_ -from outputable import Outputable import blame import changes import format @@ -43,87 +41,3 @@ class Responsibilities: author_blames[i[0][1]] = total_rows return sorted(author_blames.items()) - -RESPONSIBILITIES_INFO_TEXT = N_("The following repsonsibilties, by author, were found in the current " - "revision of the repository (comments are exluded from the line count, " - "if possible)") -MOSTLY_RESPONSIBLE_FOR_TEXT = N_("is mostly responsible for") - -class ResponsibilitiesOutput(Outputable): - def __init__(self, hard, useweeks): - self.hard = hard - self.useweeks = useweeks - Outputable.__init__(self) - self.changes = changes.get(hard) - - def output_text(self): - print("\n" + textwrap.fill(_(RESPONSIBILITIES_INFO_TEXT) + ":", width=terminal.get_size()[0])) - - 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 Responsibilities.get(self.hard, self.useweeks, i)), reverse=True) - if responsibilities: - print("\n" + i, _(MOSTLY_RESPONSIBLE_FOR_TEXT) + ":") - - for j, entry in enumerate(responsibilities): - (width, _unused) = terminal.get_size() - width -= 7 - - print(str(entry[0]).rjust(6), end=" ") - print("...%s" % entry[1][-width+3:] if len(entry[1]) > width else entry[1]) - - if j >= 9: - break - - def output_html(self): - resp_xml = "
" - resp_xml += "

" + _(RESPONSIBILITIES_INFO_TEXT) + ".

" - - 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 Responsibilities.get(self.hard, self.useweeks, i)), reverse=True) - if responsibilities: - resp_xml += "
" - - if format.get_selected() == "html": - author_email = self.changes.get_latest_email_by_author(i) - resp_xml += "

{1} {2}

".format(gravatar.get_url(author_email, size=32), - i, _(MOSTLY_RESPONSIBLE_FOR_TEXT)) - else: - resp_xml += "

{0} {1}

".format(i, _(MOSTLY_RESPONSIBLE_FOR_TEXT)) - - for j, entry in enumerate(responsibilities): - resp_xml += "" if j % 2 == 1 else ">") + entry[1] + \ - " (" + str(entry[0]) + " eloc)
" - if j >= 9: - break - - resp_xml += "
" - resp_xml += "
" - print(resp_xml) - - def output_xml(self): - message_xml = "\t\t" + _(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 Responsibilities.get(self.hard, self.useweeks, i)), reverse=True) - if responsibilities: - author_email = self.changes.get_latest_email_by_author(i) - - resp_xml += "\t\t\t\n" - resp_xml += "\t\t\t\t" + i + "\n" - resp_xml += "\t\t\t\t" + gravatar.get_url(author_email) + "\n" - resp_xml += "\t\t\t\t\n" - - for j, entry in enumerate(responsibilities): - resp_xml += "\t\t\t\t\t\n" - resp_xml += "\t\t\t\t\t\t" + entry[1] + "\n" - resp_xml += "\t\t\t\t\t\t" + str(entry[0]) + "\n" - resp_xml += "\t\t\t\t\t\n" - - if j >= 9: - break - - resp_xml += "\t\t\t\t\n" - resp_xml += "\t\t\t\n" - - print("\t\n" + message_xml + "\t\t\n" + resp_xml + "\t\t\n\t") diff --git a/gitinspector/timeline.py b/gitinspector/timeline.py index b908ec1..ea80d07 100644 --- a/gitinspector/timeline.py +++ b/gitinspector/timeline.py @@ -19,8 +19,6 @@ from __future__ import print_function from __future__ import unicode_literals -from localization import N_ -from outputable import Outputable import datetime import format import gravatar @@ -105,146 +103,3 @@ class TimelineData: if self.is_author_in_period(period, author): return True return False - - -TIMELINE_INFO_TEXT = N_("The following history timeline has been gathered from the repository") -MODIFIED_ROWS_TEXT = N_("Modified Rows:") - -def __output_row__text__(timeline_data, periods, names): - print("\n" + terminal.__bold__ + terminal.ljust(_("Author"), 20), end=" ") - - for period in periods: - print(terminal.rjust(period, 10), end=" ") - - print(terminal.__normal__) - - for name in names: - if timeline_data.is_author_in_periods(periods, name[0]): - print(terminal.ljust(name[0], 20)[0:20 - terminal.get_excess_column_count(name[0])], end=" ") - - for period in periods: - multiplier = timeline_data.get_multiplier(period, 9) - signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier) - signs_str = (signs[1] * "-" + signs[0] * "+") - print (("." if timeline_data.is_author_in_period(period, name[0]) and - len(signs_str) == 0 else signs_str).rjust(10), end=" ") - print("") - - print(terminal.__bold__ + terminal.ljust(_(MODIFIED_ROWS_TEXT), 20) + terminal.__normal__, end=" ") - - for period in periods: - total_changes = str(timeline_data.get_total_changes_in_period(period)[2]) - - if hasattr(total_changes, 'decode'): - total_changes = total_changes.decode("utf-8", "replace") - - print(terminal.rjust(total_changes, 10), end=" ") - - print("") - -def __output_row__html__(timeline_data, periods, names): - timeline_xml = "" - - for period in periods: - timeline_xml += "" - - timeline_xml += "" - i = 0 - - for name in names: - if timeline_data.is_author_in_periods(periods, name[0]): - timeline_xml += "" if i % 2 == 1 else ">") - - if format.get_selected() == "html": - timeline_xml += "".format(gravatar.get_url(name[1]), name[0]) - else: - timeline_xml += "" - - for period in periods: - multiplier = timeline_data.get_multiplier(period, 18) - signs = timeline_data.get_author_signs_in_period(name[0], period, multiplier) - signs_str = (signs[1] * "
 
" + signs[0] * "
 
") - - timeline_xml += "" - timeline_xml += "" - i = i + 1 - - timeline_xml += "" - - for period in periods: - total_changes = timeline_data.get_total_changes_in_period(period) - timeline_xml += "" - - timeline_xml += "
" + _("Author") + "" + str(period) + "
{1}" + name[0] + "" + ("." if timeline_data.is_author_in_period(period, name[0]) and len(signs_str) == 0 else signs_str) - timeline_xml += "
" + _(MODIFIED_ROWS_TEXT) + "" + str(total_changes[2]) + "
" - print(timeline_xml) - -class Timeline(Outputable): - def __init__(self, changes, useweeks): - self.changes = changes - self.useweeks = useweeks - Outputable.__init__(self) - - def output_text(self): - if self.changes.get_commits(): - print("\n" + textwrap.fill(_(TIMELINE_INFO_TEXT) + ":", width=terminal.get_size()[0])) - - timeline_data = TimelineData(self.changes, self.useweeks) - periods = timeline_data.get_periods() - names = timeline_data.get_authors() - (width, _unused) = terminal.get_size() - max_periods_per_row = int((width - 21) / 11) - - for i in range(0, len(periods), max_periods_per_row): - __output_row__text__(timeline_data, periods[i:i+max_periods_per_row], names) - - def output_html(self): - if self.changes.get_commits(): - timeline_data = TimelineData(self.changes, self.useweeks) - periods = timeline_data.get_periods() - names = timeline_data.get_authors() - max_periods_per_row = 8 - - timeline_xml = "
" - timeline_xml += "

" + _(TIMELINE_INFO_TEXT) + ".

" - print(timeline_xml) - - for i in range(0, len(periods), max_periods_per_row): - __output_row__html__(timeline_data, periods[i:i+max_periods_per_row], names) - - timeline_xml = "
" - print(timeline_xml) - - def output_xml(self): - if self.changes.get_commits(): - message_xml = "\t\t" + _(TIMELINE_INFO_TEXT) + "\n" - timeline_xml = "" - periods_xml = "\t\t\n".format("week" if self.useweeks else "month") - - timeline_data = 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" + str(period) + "\n" - authors_xml = "\t\t\t\t\n" - - 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 += "\t\t\t\t\t\n\t\t\t\t\t\t" + name[0] + "\n" - authors_xml += "\t\t\t\t\t\t" + gravatar.get_url(name[1]) + "\n" - authors_xml += "\t\t\t\t\t\t" + signs_str + "\n\t\t\t\t\t\n" - - authors_xml += "\t\t\t\t\n" - modified_rows_xml = "\t\t\t\t" + \ - str(timeline_data.get_total_changes_in_period(period)[2]) + "\n" - timeline_xml += "\t\t\t\n" + name_xml + authors_xml + modified_rows_xml + "\t\t\t\n" - - print("\t\n" + message_xml + periods_xml + timeline_xml + "\t\t\n\t")