Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add csv format with combine change and blame #191

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion gitinspector/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
from .localization import N_
from . import basedir, localization, terminal, version

__available_formats__ = ["html", "htmlembedded", "json", "text", "xml"]
__available_formats__ = ["html", "htmlembedded", "json", "text", "xml", "csv"]

DEFAULT_FORMAT = __available_formats__[3]

__selected_format__ = DEFAULT_FORMAT

__selected_format_tag__ = ""

class InvalidFormatError(Exception):
def __init__(self, msg):
super(InvalidFormatError, self).__init__(msg)
Expand All @@ -47,6 +49,17 @@ def select(format):
def get_selected():
return __selected_format__


def set_tag(format):
global __selected_format_tag__
__selected_format_tag__ = format


def get_tag():
return __selected_format_tag__



def is_interactive_format():
return __selected_format__ == "text"

Expand Down
44 changes: 27 additions & 17 deletions gitinspector/gitinspector.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from .output import outputable
from .output.blameoutput import BlameOutput
from .output.changesoutput import ChangesOutput
from .output.changesblameoutput import ChangesBlameOutput
from .output.extensionsoutput import ExtensionsOutput
from .output.filteringoutput import FilteringOutput
from .output.metricsoutput import MetricsOutput
Expand Down Expand Up @@ -79,29 +80,34 @@ def process(self, repos):
else:
os.chdir(previous_directory)

format.output_header(repos)
outputable.output(ChangesOutput(summed_changes))

if summed_changes.get_commits():
outputable.output(BlameOutput(summed_changes, summed_blames))
# print("current format: " + format.get_selected())
if format.get_selected() == "csv":
# print("output format: " + format.get_selected())
outputable.output(ChangesBlameOutput(summed_changes, summed_blames))
else:
format.output_header(repos)
outputable.output(ChangesOutput(summed_changes))

if self.timeline:
outputable.output(TimelineOutput(summed_changes, self.useweeks))
if summed_changes.get_commits():
outputable.output(BlameOutput(summed_changes, summed_blames))

if self.include_metrics:
outputable.output(MetricsOutput(summed_metrics))
if self.timeline:
outputable.output(TimelineOutput(summed_changes, self.useweeks))

if self.responsibilities:
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))
if self.include_metrics:
outputable.output(MetricsOutput(summed_metrics))

outputable.output(FilteringOutput())
if self.responsibilities:
outputable.output(ResponsibilitiesOutput(summed_changes, summed_blames))

if self.list_file_types:
outputable.output(ExtensionsOutput())
outputable.output(FilteringOutput())

format.output_footer()
if self.list_file_types:
outputable.output(ExtensionsOutput())
format.output_footer()
os.chdir(previous_directory)


def __check_python_version__():
if sys.version_info < (2, 6):
python_version = str(sys.version_info[0]) + "." + str(sys.version_info[1])
Expand Down Expand Up @@ -133,12 +139,13 @@ def main():
repos = []

try:
opts, args = optval.gnu_getopt(argv[1:], "f:F:hHlLmrTwx:", ["exclude=", "file-types=", "format=",
opts, args = optval.gnu_getopt(argv[1:], "f:t:F:hHlLmrTwx:", ["exclude=", "file-types=", "tag=" ,"format=",
"hard:true", "help", "list-file-types:true", "localize-output:true",
"metrics:true", "responsibilities:true", "since=", "grading:true",
"timeline:true", "until=", "version", "weeks:true"])
# print("begin git clone")
repos = __get_validated_git_repos__(set(args))

# print("end git clone")
#We need the repos above to be set before we read the git config.
GitConfig(run, repos[-1].location).read()
clear_x_on_next_pass = True
Expand All @@ -149,6 +156,8 @@ def main():
sys.exit(0)
elif o in("-f", "--file-types"):
extensions.define(a)
elif o in("-t", "--tag"):
format.set_tag(a)
elif o in("-F", "--format"):
if not format.select(a):
raise format.InvalidFormatError(_("specified output format not supported."))
Expand Down Expand Up @@ -213,6 +222,7 @@ def main():
@atexit.register
def cleanup():
clone.delete()
# pass

if __name__ == "__main__":
main()
239 changes: 239 additions & 0 deletions gitinspector/output/changesblameoutput.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# 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 <http://www.gnu.org/licenses/>.

from __future__ import print_function
from __future__ import unicode_literals
import json
import textwrap
from ..localization import N_
from .. import format, gravatar, terminal
from .outputable import Outputable
from ..blame import Blame

HISTORICAL_INFO_TEXT = N_("The following historical commit information, by author, was found")
NO_COMMITED_FILES_TEXT = N_("No commited files with the specified extensions were found")

class ChangesBlameOutput(Outputable):
def __init__(self, changes, blame):
if format.is_interactive_format():
print("")

self.changes = changes
self.blame = blame
Outputable.__init__(self)

def output_html(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0
changes_xml = "<div><div class=\"box\">"
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 += "<p>" + _(HISTORICAL_INFO_TEXT) + ".</p><div><table id=\"changes\" class=\"git\">"
changes_xml += "<thead><tr> <th>{0}</th> <th>{1}</th> <th>{2}</th> <th>{3}</th> <th>{4}</th>".format(
_("Author"), _("Commits"), _("Insertions"), _("Deletions"), _("% of changes"))
changes_xml += "</tr></thead><tbody>"

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 += "<tr " + ("class=\"odd\">" if i % 2 == 1 else ">")

if format.get_selected() == "html":
changes_xml += "<td><img src=\"{0}\"/>{1}</td>".format(
gravatar.get_url(self.changes.get_latest_email_by_author(entry)), entry)
else:
changes_xml += "<td>" + entry + "</td>"

changes_xml += "<td>" + str(authorinfo.commits) + "</td>"
changes_xml += "<td>" + str(authorinfo.insertions) + "</td>"
changes_xml += "<td>" + str(authorinfo.deletions) + "</td>"
changes_xml += "<td>" + "{0:.2f}".format(percentage) + "</td>"
changes_xml += "</tr>"
chart_data += "{{label: {0}, data: {1}}}".format(json.dumps(entry), "{0:.2f}".format(percentage))

if sorted(authorinfo_list)[-1] != entry:
chart_data += ", "

changes_xml += ("<tfoot><tr> <td colspan=\"5\">&nbsp;</td> </tr></tfoot></tbody></table>")
changes_xml += "<div class=\"chart\" id=\"changes_chart\"></div></div>"
changes_xml += "<script type=\"text/javascript\">"
changes_xml += " changes_plot = $.plot($(\"#changes_chart\"), [{0}], {{".format(chart_data)
changes_xml += " series: {"
changes_xml += " pie: {"
changes_xml += " innerRadius: 0.4,"
changes_xml += " show: true,"
changes_xml += " combine: {"
changes_xml += " threshold: 0.01,"
changes_xml += " label: \"" + _("Minor Authors") + "\""
changes_xml += " }"
changes_xml += " }"
changes_xml += " }, grid: {"
changes_xml += " hoverable: true"
changes_xml += " }"
changes_xml += " });"
changes_xml += "</script>"
else:
changes_xml += "<p>" + _(NO_COMMITED_FILES_TEXT) + ".</p>"

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_json = "\t\t\t\"message\": \"" + _(HISTORICAL_INFO_TEXT) + "\",\n"
changes_json = ""

for i in sorted(authorinfo_list):
author_email = self.changes.get_latest_email_by_author(i)
authorinfo = authorinfo_list.get(i)

percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
name_json = "\t\t\t\t\"name\": \"" + i + "\",\n"
email_json = "\t\t\t\t\"email\": \"" + author_email + "\",\n"
gravatar_json = "\t\t\t\t\"gravatar\": \"" + gravatar.get_url(author_email) + "\",\n"
commits_json = "\t\t\t\t\"commits\": " + str(authorinfo.commits) + ",\n"
insertions_json = "\t\t\t\t\"insertions\": " + str(authorinfo.insertions) + ",\n"
deletions_json = "\t\t\t\t\"deletions\": " + str(authorinfo.deletions) + ",\n"
percentage_json = "\t\t\t\t\"percentage_of_changes\": " + "{0:.2f}".format(percentage) + "\n"

changes_json += ("{\n" + name_json + email_json + gravatar_json + commits_json +
insertions_json + deletions_json + percentage_json + "\t\t\t}")
changes_json += ","
else:
changes_json = changes_json[:-1]

print("\t\t\"changes\": {\n" + message_json + "\t\t\t\"authors\": [\n\t\t\t" + changes_json + "]\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

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) +
terminal.rjust(_("Author"), 21) + terminal.rjust(_("Rows"), 10) + terminal.rjust(_("Stability"),
15) +
terminal.rjust(_("Age"), 13) + terminal.rjust(_("% in comments"), 20))

for i,j in zip(sorted(authorinfo_list),sorted(self.blame.get_summed_blames().items())):
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), end=" ")
# blame
#print(terminal.ljust(j[0], 20)[0:20 - terminal.get_excess_column_count(j[0])], end=" ")
print(j[0].rjust(21), end=" ")
print(str(j[1].rows).rjust(10), end=" ")
print("{0:.1f}".format(Blame.get_stability(j[0], j[1].rows, self.changes)).rjust(14), end=" ")
print("{0:.1f}".format(float(j[1].skew) / j[1].rows).rjust(12), end=" ")
print("{0:.2f}".format(100.0 * j[1].comments / j[1].rows).rjust(19))
else:
print(_(NO_COMMITED_FILES_TEXT) + ".")

def output_csv(self):
authorinfo_list = self.changes.get_authorinfo_list()
total_changes = 0.0

#format.get_tag().split(',')
tagstr = format.get_tag()
if tagstr <> "":
tagstr += ","


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(tagstr + "Author,Commits,Insertions,Deletions,% of changes,Author,Rows,Stability,Age,% in comments")

for i,j in zip(sorted(authorinfo_list),sorted(self.blame.get_summed_blames().items())):
authorinfo = authorinfo_list.get(i)
percentage = 0 if total_changes == 0 else (authorinfo.insertions + authorinfo.deletions) / total_changes * 100
line = i \
+ "," + str(authorinfo.commits) \
+ "," + str(authorinfo.insertions) \
+ "," + str(authorinfo.deletions) \
+ "," + str(percentage) \
+ "," + j[0] \
+ "," + str(j[1].rows) \
+ "," + str(Blame.get_stability(j[0], j[1].rows, self.changes)) \
+ "," + str(float(j[1].skew) / j[1].rows) \
+ "," + str(100.0 * j[1].comments / j[1].rows)
print(tagstr + line)
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<message>" + _(HISTORICAL_INFO_TEXT) + "</message>\n"
changes_xml = ""

for i in sorted(authorinfo_list):
author_email = self.changes.get_latest_email_by_author(i)
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 + "</name>\n"
email_xml = "\t\t\t\t<email>" + author_email + "</email>\n"
gravatar_xml = "\t\t\t\t<gravatar>" + gravatar.get_url(author_email) + "</gravatar>\n"
commits_xml = "\t\t\t\t<commits>" + str(authorinfo.commits) + "</commits>\n"
insertions_xml = "\t\t\t\t<insertions>" + str(authorinfo.insertions) + "</insertions>\n"
deletions_xml = "\t\t\t\t<deletions>" + str(authorinfo.deletions) + "</deletions>\n"
percentage_xml = "\t\t\t\t<percentage-of-changes>" + "{0:.2f}".format(percentage) + "</percentage-of-changes>\n"

changes_xml += ("\t\t\t<author>\n" + name_xml + email_xml + gravatar_xml + commits_xml +
insertions_xml + deletions_xml + percentage_xml + "\t\t\t</author>\n")

print("\t<changes>\n" + message_xml + "\t\t<authors>\n" + changes_xml + "\t\t</authors>\n\t</changes>")
else:
print("\t<changes>\n\t\t<exception>" + _(NO_COMMITED_FILES_TEXT) + "</exception>\n\t</changes>")
15 changes: 15 additions & 0 deletions gitinspector/output/filteringoutput.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,18 @@ def output_xml(self):
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</filtering>")

@staticmethod
def __output_csv_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_csv(self):
FilteringOutput.__output_csv_section__(_(FILTERING_INFO_TEXT), __filters__["file"][1])
FilteringOutput.__output_csv_section__(_(FILTERING_AUTHOR_INFO_TEXT), __filters__["author"][1])
FilteringOutput.__output_csv_section__(_(FILTERING_EMAIL_INFO_TEXT), __filters__["email"][1])
FilteringOutput.__output_csv_section__(_(FILTERING_COMMIT_INFO_TEXT), __filters__["revision"][1])
Loading