Przeglądaj źródła

Merge pull request #2 from christhoval06/main

add tailwindcss, total branch and tags
Christhoval Barba 1 rok temu
rodzic
commit
4d7e71d969
Brak konta powiązanego z e-mailem autora
3 zmienionych plików z 306 dodań i 211 usunięć
  1. 301
    202
      gitstats
  2. 0
    4
      gitstats.css
  3. 5
    5
      sortable.js

+ 301
- 202
gitstats Wyświetl plik

@@ -151,6 +151,8 @@ class DataCollector:
151 151
 	def __init__(self):
152 152
 		self.stamp_created = time.time()
153 153
 		self.cache = {}
154
+		self.total_branches = 0
155
+		self.total_tags = 0
154 156
 		self.total_authors = 0
155 157
 		self.activity_by_hour_of_day = {} # hour -> commits
156 158
 		self.activity_by_day_of_week = {} # day -> commits
@@ -318,6 +320,8 @@ class GitDataCollector(DataCollector):
318 320
 		DataCollector.collect(self, dir)
319 321
 
320 322
 		self.total_authors += int(getpipeoutput(['git shortlog -s %s' % getlogrange(), 'wc -l']))
323
+		self.total_branches += int(getpipeoutput(['git branch -r', 'wc -l']))
324
+		self.total_tags += int(getpipeoutput(['git tag', 'wc -l']))
321 325
 		#self.total_lines = int(getoutput('git-ls-files -z |xargs -0 cat |wc -l'))
322 326
 
323 327
 		# tags
@@ -862,40 +866,44 @@ class HTMLReportCreator(ReportCreator):
862 866
 		format = '%Y-%m-%d %H:%M:%S'
863 867
 		self.printHeader(f)
864 868
 
865
-		f.write('<h1>GitStats - %s</h1>' % data.projectname)
866
-
867 869
 		self.printNav(f)
868 870
 
869
-		f.write('<div><div class="card-body"><dl>')
870
-		f.write('<dt>Project name</dt><dd>%s</dd>' % (data.projectname))
871
-		f.write('<dt>Generated</dt><dd>%s (in %d seconds)</dd>' % (datetime.datetime.now().strftime(format), time.time() - data.getStampCreated()))
872
-		f.write('<dt>Generator</dt><dd><a href="http://gitstats.sourceforge.net/">GitStats</a> (version %s), %s, %s</dd>' % (getversion(), getgitversion(), getgnuplotversion()))
873
-		f.write('<dt>Report Period</dt><dd>%s to %s</dd>' % (data.getFirstCommitDate().strftime(format), data.getLastCommitDate().strftime(format)))
874
-		f.write('<dt>Age</dt><dd>%d days, %d active days (%3.2f%%)</dd>' % (data.getCommitDeltaDays(), len(data.getActiveDays()), (100.0 * len(data.getActiveDays()) / data.getCommitDeltaDays())))
875
-		f.write('<dt>Total Files</dt><dd>%s</dd>' % data.getTotalFiles())
876
-		f.write('<dt>Total Lines of Code</dt><dd>%s (%d added, %d removed)</dd>' % (data.getTotalLOC(), data.total_lines_added, data.total_lines_removed))
877
-		f.write('<dt>Total Commits</dt><dd>%s (average %.1f commits per active day, %.1f per all days)</dd>' % (data.getTotalCommits(), float(data.getTotalCommits()) / len(data.getActiveDays()), float(data.getTotalCommits()) / data.getCommitDeltaDays()))
878
-		f.write('<dt>Authors</dt><dd>%s (average %.1f commits per author)</dd>' % (data.getTotalAuthors(), (1.0 * data.getTotalCommits()) / data.getTotalAuthors()))
879
-		f.write('</dl></div></div>')
880
-
881
-		f.write('</body>\n</html>')
871
+		general_content = ['<h1 class="text-3xl font-bold underline">GitStats - %s</h1>' % data.projectname]
872
+
873
+		general_content.append('<div><div class="card-body"><dl>')
874
+		general_content.append('<dt>Project name</dt><dd>%s (%s branches, %s tags)</dd>' % (data.projectname, data.total_branches, data.total_tags))
875
+		general_content.append('<dt>Generated</dt><dd>%s (in %d seconds)</dd>' % (datetime.datetime.now().strftime(format), time.time() - data.getStampCreated()))
876
+		general_content.append('<dt>Generator</dt><dd><a href="http://gitstats.sourceforge.net/">GitStats</a> (version %s), %s, %s</dd>' % (getversion(), getgitversion(), getgnuplotversion()))
877
+		general_content.append('<dt>Report Period</dt><dd>%s to %s</dd>' % (data.getFirstCommitDate().strftime(format), data.getLastCommitDate().strftime(format)))
878
+		general_content.append('<dt>Age</dt><dd>%d days, %d active days (%3.2f%%)</dd>' % (data.getCommitDeltaDays(), len(data.getActiveDays()), (100.0 * len(data.getActiveDays()) / data.getCommitDeltaDays())))
879
+		general_content.append('<dt>Total Files</dt><dd>%s</dd>' % data.getTotalFiles())
880
+		general_content.append('<dt>Total Lines of Code</dt><dd>%s (%d added, %d removed)</dd>' % (data.getTotalLOC(), data.total_lines_added, data.total_lines_removed))
881
+		general_content.append('<dt>Total Commits</dt><dd>%s (average %.1f commits per active day, %.1f per all days)</dd>' % (data.getTotalCommits(), float(data.getTotalCommits()) / len(data.getActiveDays()), float(data.getTotalCommits()) / data.getCommitDeltaDays()))
882
+		general_content.append('<dt>Authors</dt><dd>%s (average %.1f commits per author)</dd>' % (data.getTotalAuthors(), (1.0 * data.getTotalCommits()) / data.getTotalAuthors()))
883
+		general_content.append('</dl></div></div>')
884
+
885
+		self.printContent(f, general_content)
886
+
887
+		f.write('</div></body>\n</html>')
882 888
 		f.close()
883 889
 
884 890
 		###
885 891
 		# Activity
886 892
 		f = open(path + '/activity.html', 'w')
887 893
 		self.printHeader(f)
888
-		f.write('<h1>Activity</h1>')
894
+		
889 895
 		self.printNav(f)
890 896
 
891
-		#f.write('<h2>Last 30 days</h2>')
897
+		activity_content = []
898
+
899
+		#activity_content.append('<h2>Last 30 days</h2>')
892 900
 
893
-		#f.write('<h2>Last 12 months</h2>')
901
+		#activity_content.append('<h2>Last 12 months</h2>')
894 902
 
895 903
 		# Weekly activity
896 904
 		WEEKS = 32
897
-		f.write(html_header(2, 'Weekly activity'))
898
-		f.write('<p>Last %d weeks</p>' % WEEKS)
905
+		activity_content.append(html_header(2, 'Weekly activity'))
906
+		activity_content.append('<p>Last %d weeks</p>' % WEEKS)
899 907
 
900 908
 		# generate weeks to show (previous N weeks from now)
901 909
 		now = datetime.datetime.now()
@@ -907,7 +915,7 @@ class HTMLReportCreator(ReportCreator):
907 915
 			stampcur -= deltaweek
908 916
 
909 917
 		# top row: commits & bar
910
-		f.write('<div><div class="card-body"><table class="noborders"><tr>')
918
+		activity_content.append('<div><div class="card-body"><table class="noborders"><tr>')
911 919
 		for i in range(0, WEEKS):
912 920
 			commits = 0
913 921
 			if weeks[i] in data.activity_by_year_week:
@@ -917,41 +925,41 @@ class HTMLReportCreator(ReportCreator):
917 925
 			if weeks[i] in data.activity_by_year_week:
918 926
 				percentage = float(data.activity_by_year_week[weeks[i]]) / data.activity_by_year_week_peak
919 927
 			height = max(1, int(200 * percentage))
920
-			f.write('<td style="text-align: center; vertical-align: bottom">%d<div style="display: block; background-color: red; width: 20px; height: %dpx"></div></td>' % (commits, height))
928
+			activity_content.append('<td style="text-align: center; vertical-align: bottom">%d<div style="display: block; background-color: red; width: 20px; height: %dpx"></div></td>' % (commits, height))
921 929
 
922 930
 		# bottom row: year/week
923
-		f.write('</tr><tr>')
931
+		activity_content.append('</tr><tr>')
924 932
 		for i in range(0, WEEKS):
925
-			f.write('<td>%s</td>' % (WEEKS - i))
926
-		f.write('</tr></table></div></div>')
933
+			activity_content.append('<td>%s</td>' % (WEEKS - i))
934
+		activity_content.append('</tr></table></div></div>')
927 935
 
928 936
 		# Hour of Day
929
-		f.write(html_header(2, 'Hour of Day'))
937
+		activity_content.append(html_header(2, 'Hour of Day'))
930 938
 		hour_of_day = data.getActivityByHourOfDay()
931
-		f.write('<div><div class="card-body"><table><tr><th>Hour</th>')
939
+		activity_content.append('<div><div class="card-body"><table><tr><th>Hour</th>')
932 940
 		for i in range(0, 24):
933
-			f.write('<th>%d</th>' % i)
934
-		f.write('</tr>\n<tr><th>Commits</th>')
941
+			activity_content.append('<th>%d</th>' % i)
942
+		activity_content.append('</tr>\n<tr><th>Commits</th>')
935 943
 		fp = open(path + '/hour_of_day.dat', 'w')
936 944
 		for i in range(0, 24):
937 945
 			if i in hour_of_day:
938 946
 				r = 127 + int((float(hour_of_day[i]) / data.activity_by_hour_of_day_busiest) * 128)
939
-				f.write('<td style="background-color: rgb(%d, 0, 0)">%d</td>' % (r, hour_of_day[i]))
947
+				activity_content.append('<td style="background-color: rgb(%d, 0, 0)">%d</td>' % (r, hour_of_day[i]))
940 948
 				fp.write('%d %d\n' % (i, hour_of_day[i]))
941 949
 			else:
942
-				f.write('<td>0</td>')
950
+				activity_content.append('<td>0</td>')
943 951
 				fp.write('%d 0\n' % i)
944 952
 		fp.close()
945
-		f.write('</tr>\n<tr><th>%</th>')
953
+		activity_content.append('</tr>\n<tr><th>%</th>')
946 954
 		totalcommits = data.getTotalCommits()
947 955
 		for i in range(0, 24):
948 956
 			if i in hour_of_day:
949 957
 				r = 127 + int((float(hour_of_day[i]) / data.activity_by_hour_of_day_busiest) * 128)
950
-				f.write('<td style="background-color: rgb(%d, 0, 0)">%.2f</td>' % (r, (100.0 * hour_of_day[i]) / totalcommits))
958
+				activity_content.append('<td style="background-color: rgb(%d, 0, 0)">%.2f</td>' % (r, (100.0 * hour_of_day[i]) / totalcommits))
951 959
 			else:
952
-				f.write('<td>0.00</td>')
953
-		f.write('</tr></table>')
954
-		f.write('<img src="hour_of_day.png" alt="Hour of Day">')
960
+				activity_content.append('<td>0.00</td>')
961
+		activity_content.append('</tr></table>')
962
+		activity_content.append('<img src="hour_of_day.png" alt="Hour of Day">')
955 963
 		fg = open(path + '/hour_of_day.dat', 'w')
956 964
 		for i in range(0, 24):
957 965
 			if i in hour_of_day:
@@ -961,106 +969,108 @@ class HTMLReportCreator(ReportCreator):
961 969
 		fg.close()
962 970
 
963 971
 		# Day of Week
964
-		f.write(html_header(2, 'Day of Week'))
972
+		activity_content.append(html_header(2, 'Day of Week'))
965 973
 		day_of_week = data.getActivityByDayOfWeek()
966
-		f.write('<div class="vtable"><table>')
967
-		f.write('<tr><th>Day</th><th>Total (%)</th></tr>')
974
+		activity_content.append('<div class="vtable"><table>')
975
+		activity_content.append('<tr><th>Day</th><th>Total (%)</th></tr>')
968 976
 		fp = open(path + '/day_of_week.dat', 'w')
969 977
 		for d in range(0, 7):
970 978
 			commits = 0
971 979
 			if d in day_of_week:
972 980
 				commits = day_of_week[d]
973 981
 			fp.write('%d %s %d\n' % (d + 1, WEEKDAYS[d], commits))
974
-			f.write('<tr>')
975
-			f.write('<th>%s</th>' % (WEEKDAYS[d]))
982
+			activity_content.append('<tr>')
983
+			activity_content.append('<th>%s</th>' % (WEEKDAYS[d]))
976 984
 			if d in day_of_week:
977
-				f.write('<td>%d (%.2f%%)</td>' % (day_of_week[d], (100.0 * day_of_week[d]) / totalcommits))
985
+				activity_content.append('<td>%d (%.2f%%)</td>' % (day_of_week[d], (100.0 * day_of_week[d]) / totalcommits))
978 986
 			else:
979
-				f.write('<td>0</td>')
980
-			f.write('</tr>')
981
-		f.write('</table></div>')
982
-		f.write('<img src="day_of_week.png" alt="Day of Week">')
987
+				activity_content.append('<td>0</td>')
988
+			activity_content.append('</tr>')
989
+		activity_content.append('</table></div>')
990
+		activity_content.append('<img src="day_of_week.png" alt="Day of Week">')
983 991
 		fp.close()
984 992
 
985 993
 		# Hour of Week
986
-		f.write(html_header(2, 'Hour of Week'))
987
-		f.write('<table>')
994
+		activity_content.append(html_header(2, 'Hour of Week'))
995
+		activity_content.append('<table>')
988 996
 
989
-		f.write('<tr><th>Weekday</th>')
997
+		activity_content.append('<tr><th>Weekday</th>')
990 998
 		for hour in range(0, 24):
991
-			f.write('<th>%d</th>' % (hour))
992
-		f.write('</tr>')
999
+			activity_content.append('<th>%d</th>' % (hour))
1000
+		activity_content.append('</tr>')
993 1001
 
994 1002
 		for weekday in range(0, 7):
995
-			f.write('<tr><th>%s</th>' % (WEEKDAYS[weekday]))
1003
+			activity_content.append('<tr><th>%s</th>' % (WEEKDAYS[weekday]))
996 1004
 			for hour in range(0, 24):
997 1005
 				try:
998 1006
 					commits = data.activity_by_hour_of_week[weekday][hour]
999 1007
 				except KeyError:
1000 1008
 					commits = 0
1001 1009
 				if commits != 0:
1002
-					f.write('<td')
1010
+					activity_content.append('<td')
1003 1011
 					r = 127 + int((float(commits) / data.activity_by_hour_of_week_busiest) * 128)
1004
-					f.write(' style="background-color: rgb(%d, 0, 0)"' % r)
1005
-					f.write('>%d</td>' % commits)
1012
+					activity_content.append(' style="background-color: rgb(%d, 0, 0)"' % r)
1013
+					activity_content.append('>%d</td>' % commits)
1006 1014
 				else:
1007
-					f.write('<td></td>')
1008
-			f.write('</tr>')
1015
+					activity_content.append('<td></td>')
1016
+			activity_content.append('</tr>')
1009 1017
 
1010
-		f.write('</table></div></div>')
1018
+		activity_content.append('</table></div></div>')
1011 1019
 
1012 1020
 		# Month of Year
1013
-		f.write(html_header(2, 'Month of Year'))
1014
-		f.write('<div class="vtable"><table>')
1015
-		f.write('<tr><th>Month</th><th>Commits (%)</th></tr>')
1021
+		activity_content.append(html_header(2, 'Month of Year'))
1022
+		activity_content.append('<div class="vtable"><table>')
1023
+		activity_content.append('<tr><th>Month</th><th>Commits (%)</th></tr>')
1016 1024
 		fp = open (path + '/month_of_year.dat', 'w')
1017 1025
 		for mm in range(1, 13):
1018 1026
 			commits = 0
1019 1027
 			if mm in data.activity_by_month_of_year:
1020 1028
 				commits = data.activity_by_month_of_year[mm]
1021
-			f.write('<tr><td>%d</td><td>%d (%.2f %%)</td></tr>' % (mm, commits, (100.0 * commits) / data.getTotalCommits()))
1029
+			activity_content.append('<tr><td>%d</td><td>%d (%.2f %%)</td></tr>' % (mm, commits, (100.0 * commits) / data.getTotalCommits()))
1022 1030
 			fp.write('%d %d\n' % (mm, commits))
1023 1031
 		fp.close()
1024
-		f.write('</table></div>')
1025
-		f.write('<img src="month_of_year.png" alt="Month of Year">')
1032
+		activity_content.append('</table></div>')
1033
+		activity_content.append('<img src="month_of_year.png" alt="Month of Year">')
1026 1034
 
1027 1035
 		# Commits by year/month
1028
-		f.write(html_header(2, 'Commits by year/month'))
1029
-		f.write('<div class="vtable"><table><tr><th>Month</th><th>Commits</th><th>Lines added</th><th>Lines removed</th></tr>')
1036
+		activity_content.append(html_header(2, 'Commits by year/month'))
1037
+		activity_content.append('<div class="vtable"><table><tr><th>Month</th><th>Commits</th><th>Lines added</th><th>Lines removed</th></tr>')
1030 1038
 		for yymm in reversed(sorted(data.commits_by_month.keys())):
1031
-			f.write('<tr><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>' % (yymm, data.commits_by_month.get(yymm,0), data.lines_added_by_month.get(yymm,0), data.lines_removed_by_month.get(yymm,0)))
1032
-		f.write('</table></div>')
1033
-		f.write('<img src="commits_by_year_month.png" alt="Commits by year/month">')
1039
+			activity_content.append('<tr><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>' % (yymm, data.commits_by_month.get(yymm,0), data.lines_added_by_month.get(yymm,0), data.lines_removed_by_month.get(yymm,0)))
1040
+		activity_content.append('</table></div>')
1041
+		activity_content.append('<img src="commits_by_year_month.png" alt="Commits by year/month">')
1034 1042
 		fg = open(path + '/commits_by_year_month.dat', 'w')
1035 1043
 		for yymm in sorted(data.commits_by_month.keys()):
1036 1044
 			fg.write('%s %s\n' % (yymm, data.commits_by_month[yymm]))
1037 1045
 		fg.close()
1038 1046
 
1039 1047
 		# Commits by year
1040
-		f.write(html_header(2, 'Commits by Year'))
1041
-		f.write('<div class="vtable"><table><tr><th>Year</th><th>Commits (% of all)</th><th>Lines added</th><th>Lines removed</th></tr>')
1048
+		activity_content.append(html_header(2, 'Commits by Year'))
1049
+		activity_content.append('<div class="vtable"><table><tr><th>Year</th><th>Commits (% of all)</th><th>Lines added</th><th>Lines removed</th></tr>')
1042 1050
 		for yy in reversed(sorted(data.commits_by_year.keys())):
1043
-			f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d</td><td>%d</td></tr>' % (yy, data.commits_by_year.get(yy,0), (100.0 * data.commits_by_year.get(yy,0)) / data.getTotalCommits(), data.lines_added_by_year.get(yy,0), data.lines_removed_by_year.get(yy,0)))
1044
-		f.write('</table></div>')
1045
-		f.write('<img src="commits_by_year.png" alt="Commits by Year">')
1051
+			activity_content.append('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d</td><td>%d</td></tr>' % (yy, data.commits_by_year.get(yy,0), (100.0 * data.commits_by_year.get(yy,0)) / data.getTotalCommits(), data.lines_added_by_year.get(yy,0), data.lines_removed_by_year.get(yy,0)))
1052
+		activity_content.append('</table></div>')
1053
+		activity_content.append('<img src="commits_by_year.png" alt="Commits by Year">')
1046 1054
 		fg = open(path + '/commits_by_year.dat', 'w')
1047 1055
 		for yy in sorted(data.commits_by_year.keys()):
1048 1056
 			fg.write('%d %d\n' % (yy, data.commits_by_year[yy]))
1049 1057
 		fg.close()
1050 1058
 
1051 1059
 		# Commits by timezone
1052
-		f.write(html_header(2, 'Commits by Timezone'))
1053
-		f.write('<div><div class="card-body"><table><tr>')
1054
-		f.write('<th>Timezone</th><th>Commits</th>')
1055
-		f.write('</tr>')
1060
+		activity_content.append(html_header(2, 'Commits by Timezone'))
1061
+		activity_content.append('<div><div class="card-body"><table><tr>')
1062
+		activity_content.append('<th>Timezone</th><th>Commits</th>')
1063
+		activity_content.append('</tr>')
1056 1064
 		max_commits_on_tz = max(data.commits_by_timezone.values())
1057 1065
 		for i in sorted(data.commits_by_timezone.keys(), key = lambda n : int(n)):
1058 1066
 			commits = data.commits_by_timezone[i]
1059 1067
 			r = 127 + int((float(commits) / max_commits_on_tz) * 128)
1060
-			f.write('<tr><th>%s</th><td style="background-color: rgb(%d, 0, 0)">%d</td></tr>' % (i, r, commits))
1061
-		f.write('</table></div>')
1068
+			activity_content.append('<tr><th>%s</th><td style="background-color: rgb(%d, 0, 0)">%d</td></tr>' % (i, r, commits))
1069
+		activity_content.append('</table></div>')
1062 1070
 
1063
-		f.write('</body></html>')
1071
+		self.printContent(f, activity_content, 'Activity')
1072
+
1073
+		f.write('</div></body></html>')
1064 1074
 		f.close()
1065 1075
 
1066 1076
 		###
@@ -1068,33 +1078,34 @@ class HTMLReportCreator(ReportCreator):
1068 1078
 		f = open(path + '/authors.html', 'w')
1069 1079
 		self.printHeader(f)
1070 1080
 
1071
-		f.write('<h1>Authors</h1>')
1072 1081
 		self.printNav(f)
1073 1082
 
1083
+		authors_content = []
1084
+
1074 1085
 		# Authors :: List of authors
1075
-		f.write(html_header(2, 'List of Authors'))
1086
+		authors_content.append(html_header(2, 'List of Authors'))
1076 1087
 
1077
-		f.write('<div><div class="card-body"><table class="authors sortable" id="authors">')
1078
-		f.write('<tr><th>Author</th><th>Commits (%)</th><th>+ lines</th><th>- lines</th><th>First commit</th><th>Last commit</th><th class="unsortable">Age</th><th>Active days</th><th># by commits</th></tr>')
1088
+		authors_content.append('<div><div class="card-body"><table class="authors sortable" id="authors">')
1089
+		authors_content.append('<tr><th>Author</th><th>Commits (%)</th><th>+ lines</th><th>- lines</th><th>First commit</th><th>Last commit</th><th class="unsortable">Age</th><th>Active days</th><th># by commits</th></tr>')
1079 1090
 		for author in data.getAuthors(conf['max_authors']):
1080 1091
 			info = data.getAuthorInfo(author)
1081
-			f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td></tr>' % (author, info['commits'], info['commits_frac'], info['lines_added'], info['lines_removed'], info['date_first'], info['date_last'], info['timedelta'], len(info['active_days']), info['place_by_commits']))
1082
-		f.write('</table></div></div>')
1092
+			authors_content.append('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%d</td></tr>' % (author, info['commits'], info['commits_frac'], info['lines_added'], info['lines_removed'], info['date_first'], info['date_last'], info['timedelta'], len(info['active_days']), info['place_by_commits']))
1093
+		authors_content.append('</table></div></div>')
1083 1094
 
1084 1095
 		allauthors = data.getAuthors()
1085 1096
 		if len(allauthors) > conf['max_authors']:
1086 1097
 			rest = allauthors[conf['max_authors']:]
1087
-			f.write('<p class="moreauthors">These didn\'t make it to the top: %s</p>' % ', '.join(rest))
1098
+			authors_content.append('<p class="moreauthors">These didn\'t make it to the top: %s</p>' % ', '.join(rest))
1088 1099
 
1089
-		f.write(html_header(2, 'Cumulated Added Lines of Code per Author'))
1090
-		f.write('<img src="lines_of_code_by_author.png" alt="Lines of code per Author">')
1100
+		authors_content.append(html_header(2, 'Cumulated Added Lines of Code per Author'))
1101
+		authors_content.append('<img src="lines_of_code_by_author.png" alt="Lines of code per Author">')
1091 1102
 		if len(allauthors) > conf['max_authors']:
1092
-			f.write('<p class="moreauthors">Only top %d authors shown</p>' % conf['max_authors'])
1103
+			authors_content.append('<p class="moreauthors">Only top %d authors shown</p>' % conf['max_authors'])
1093 1104
 
1094
-		f.write(html_header(2, 'Commits per Author'))
1095
-		f.write('<img src="commits_by_author.png" alt="Commits per Author">')
1105
+		authors_content.append(html_header(2, 'Commits per Author'))
1106
+		authors_content.append('<img src="commits_by_author.png" alt="Commits per Author">')
1096 1107
 		if len(allauthors) > conf['max_authors']:
1097
-			f.write('<p class="moreauthors">Only top %d authors shown</p>' % conf['max_authors'])
1108
+			authors_content.append('<p class="moreauthors">Only top %d authors shown</p>' % conf['max_authors'])
1098 1109
 
1099 1110
 		fgl = open(path + '/lines_of_code_by_author.dat', 'w')
1100 1111
 		fgc = open(path + '/commits_by_author.dat', 'w')
@@ -1129,36 +1140,36 @@ class HTMLReportCreator(ReportCreator):
1129 1140
 		fgc.close()
1130 1141
 
1131 1142
 		# Authors :: Author of Month
1132
-		f.write(html_header(2, 'Author of Month'))
1133
-		f.write('<table class="sortable" id="aom">')
1134
-		f.write('<tr><th>Month</th><th>Author</th><th>Commits (%%)</th><th class="unsortable">Next top %d</th><th>Number of authors</th></tr>' % conf['authors_top'])
1143
+		authors_content.append(html_header(2, 'Author of Month'))
1144
+		authors_content.append('<table class="sortable" id="aom">')
1145
+		authors_content.append('<tr><th>Month</th><th>Author</th><th>Commits (%%)</th><th class="unsortable">Next top %d</th><th>Number of authors</th></tr>' % conf['authors_top'])
1135 1146
 		for yymm in reversed(sorted(data.author_of_month.keys())):
1136 1147
 			authordict = data.author_of_month[yymm]
1137 1148
 			authors = getkeyssortedbyvalues(authordict)
1138 1149
 			authors.reverse()
1139 1150
 			commits = data.author_of_month[yymm][authors[0]]
1140 1151
 			next = ', '.join(authors[1:conf['authors_top']+1])
1141
-			f.write('<tr><td>%s</td><td>%s</td><td>%d (%.2f%% of %d)</td><td>%s</td><td>%d</td></tr>' % (yymm, authors[0], commits, (100.0 * commits) / data.commits_by_month[yymm], data.commits_by_month[yymm], next, len(authors)))
1152
+			authors_content.append('<tr><td>%s</td><td>%s</td><td>%d (%.2f%% of %d)</td><td>%s</td><td>%d</td></tr>' % (yymm, authors[0], commits, (100.0 * commits) / data.commits_by_month[yymm], data.commits_by_month[yymm], next, len(authors)))
1142 1153
 
1143
-		f.write('</table>')
1154
+		authors_content.append('</table>')
1144 1155
 
1145
-		f.write(html_header(2, 'Author of Year'))
1146
-		f.write('<table class="sortable" id="aoy"><tr><th>Year</th><th>Author</th><th>Commits (%%)</th><th class="unsortable">Next top %d</th><th>Number of authors</th></tr>' % conf['authors_top'])
1156
+		authors_content.append(html_header(2, 'Author of Year'))
1157
+		authors_content.append('<table class="sortable" id="aoy"><tr><th>Year</th><th>Author</th><th>Commits (%%)</th><th class="unsortable">Next top %d</th><th>Number of authors</th></tr>' % conf['authors_top'])
1147 1158
 		for yy in reversed(sorted(data.author_of_year.keys())):
1148 1159
 			authordict = data.author_of_year[yy]
1149 1160
 			authors = getkeyssortedbyvalues(authordict)
1150 1161
 			authors.reverse()
1151 1162
 			commits = data.author_of_year[yy][authors[0]]
1152 1163
 			next = ', '.join(authors[1:conf['authors_top']+1])
1153
-			f.write('<tr><td>%s</td><td>%s</td><td>%d (%.2f%% of %d)</td><td>%s</td><td>%d</td></tr>' % (yy, authors[0], commits, (100.0 * commits) / data.commits_by_year[yy], data.commits_by_year[yy], next, len(authors)))
1154
-		f.write('</table>')
1164
+			authors_content.append('<tr><td>%s</td><td>%s</td><td>%d (%.2f%% of %d)</td><td>%s</td><td>%d</td></tr>' % (yy, authors[0], commits, (100.0 * commits) / data.commits_by_year[yy], data.commits_by_year[yy], next, len(authors)))
1165
+		authors_content.append('</table>')
1155 1166
 
1156 1167
 		# Domains
1157
-		f.write(html_header(2, 'Commits by Domains'))
1168
+		authors_content.append(html_header(2, 'Commits by Domains'))
1158 1169
 		domains_by_commits = getkeyssortedbyvaluekey(data.domains, 'commits')
1159 1170
 		domains_by_commits.reverse() # most first
1160
-		f.write('<div class="vtable"><table>')
1161
-		f.write('<tr><th>Domains</th><th>Total (%)</th></tr>')
1171
+		authors_content.append('<div class="vtable"><table>')
1172
+		authors_content.append('<tr><th>Domains</th><th>Total (%)</th></tr>')
1162 1173
 		fp = open(path + '/domains.dat', 'w')
1163 1174
 		n = 0
1164 1175
 		for domain in domains_by_commits:
@@ -1168,32 +1179,36 @@ class HTMLReportCreator(ReportCreator):
1168 1179
 			n += 1
1169 1180
 			info = data.getDomainInfo(domain)
1170 1181
 			fp.write('%s %d %d\n' % (domain, n , info['commits']))
1171
-			f.write('<tr><th>%s</th><td>%d (%.2f%%)</td></tr>' % (domain, info['commits'], (100.0 * info['commits'] / totalcommits)))
1172
-		f.write('</table></div>')
1173
-		f.write('<img src="domains.png" alt="Commits by Domains">')
1182
+			authors_content.append('<tr><th>%s</th><td>%d (%.2f%%)</td></tr>' % (domain, info['commits'], (100.0 * info['commits'] / totalcommits)))
1183
+		authors_content.append('</table></div>')
1184
+		authors_content.append('<img src="domains.png" alt="Commits by Domains">')
1174 1185
 		fp.close()
1175 1186
 
1176
-		f.write('</body></html>')
1187
+		self.printContent(f, authors_content, 'Authors')
1188
+		f.write('</div></body></html>')
1177 1189
 		f.close()
1178 1190
 
1179 1191
 		###
1180 1192
 		# Files
1181 1193
 		f = open(path + '/files.html', 'w')
1182 1194
 		self.printHeader(f)
1183
-		f.write('<h1>Files</h1>')
1195
+
1184 1196
 		self.printNav(f)
1185 1197
 
1186
-		f.write('<dl>\n')
1187
-		f.write('<dt>Total files</dt><dd>%d</dd>' % data.getTotalFiles())
1188
-		f.write('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
1198
+
1199
+		files_content = []
1200
+
1201
+		files_content.append('<dl>\n')
1202
+		files_content.append('<dt>Total files</dt><dd>%d</dd>' % data.getTotalFiles())
1203
+		files_content.append('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
1189 1204
 		try:
1190
-			f.write('<dt>Average file size</dt><dd>%.2f bytes</dd>' % (float(data.getTotalSize()) / data.getTotalFiles()))
1205
+			files_content.append('<dt>Average file size</dt><dd>%.2f bytes</dd>' % (float(data.getTotalSize()) / data.getTotalFiles()))
1191 1206
 		except ZeroDivisionError:
1192 1207
 			pass
1193
-		f.write('</dl>\n')
1208
+		files_content.append('</dl>\n')
1194 1209
 
1195 1210
 		# Files :: File count by date
1196
-		f.write(html_header(2, 'File count by date'))
1211
+		files_content.append(html_header(2, 'File count by date'))
1197 1212
 
1198 1213
 		# use set to get rid of duplicate/unnecessary entries
1199 1214
 		files_by_date = set()
@@ -1207,13 +1222,13 @@ class HTMLReportCreator(ReportCreator):
1207 1222
 		#	fg.write('%s %d\n' % (datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d'), data.files_by_stamp[stamp]))
1208 1223
 		fg.close()
1209 1224
 			
1210
-		f.write('<img src="files_by_date.png" alt="Files by Date">')
1225
+		files_content.append('<img src="files_by_date.png" alt="Files by Date">')
1211 1226
 
1212
-		#f.write('<h2>Average file size by date</h2>')
1227
+		#files_content.append('<h2>Average file size by date</h2>')
1213 1228
 
1214 1229
 		# Files :: Extensions
1215
-		f.write(html_header(2, 'Extensions'))
1216
-		f.write('<table class="sortable" id="ext"><tr><th>Extension</th><th>Files (%)</th><th>Lines (%)</th><th>Lines/file</th></tr>')
1230
+		files_content.append(html_header(2, 'Extensions'))
1231
+		files_content.append('<table class="sortable" id="ext"><tr><th>Extension</th><th>Files (%)</th><th>Lines (%)</th><th>Lines/file</th></tr>')
1217 1232
 		for ext in sorted(data.extensions.keys()):
1218 1233
 			files = data.extensions[ext]['files']
1219 1234
 			lines = data.extensions[ext]['lines']
@@ -1221,25 +1236,28 @@ class HTMLReportCreator(ReportCreator):
1221 1236
 				loc_percentage = (100.0 * lines) / data.getTotalLOC()
1222 1237
 			except ZeroDivisionError:
1223 1238
 				loc_percentage = 0
1224
-			f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d (%.2f%%)</td><td>%d</td></tr>' % (ext, files, (100.0 * files) / data.getTotalFiles(), lines, loc_percentage, lines / files))
1225
-		f.write('</table>')
1239
+			files_content.append('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d (%.2f%%)</td><td>%d</td></tr>' % (ext, files, (100.0 * files) / data.getTotalFiles(), lines, loc_percentage, lines / files))
1240
+		files_content.append('</table>')
1226 1241
 
1227
-		f.write('</body></html>')
1242
+		self.printContent(f, files_content, 'Files')
1243
+
1244
+		f.write('</div></body></html>')
1228 1245
 		f.close()
1229 1246
 
1230 1247
 		###
1231 1248
 		# Lines
1232 1249
 		f = open(path + '/lines.html', 'w')
1233 1250
 		self.printHeader(f)
1234
-		f.write('<h1>Lines</h1>')
1251
+		
1235 1252
 		self.printNav(f)
1253
+		lines_content=[]
1236 1254
 
1237
-		f.write('<dl>\n')
1238
-		f.write('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
1239
-		f.write('</dl>\n')
1255
+		lines_content.append('<dl>\n')
1256
+		lines_content.append('<dt>Total lines</dt><dd>%d</dd>' % data.getTotalLOC())
1257
+		lines_content.append('</dl>\n')
1240 1258
 
1241
-		f.write(html_header(2, 'Lines of Code'))
1242
-		f.write('<img src="lines_of_code.png" alt="Lines of Code">')
1259
+		lines_content.append(html_header(2, 'Lines of Code'))
1260
+		lines_content.append('<img src="lines_of_code.png" alt="Lines of Code">')
1243 1261
 
1244 1262
 		fg = open(path + '/lines_of_code.dat', 'w')
1245 1263
 		for stamp in sorted(data.changes_by_date.keys()):
@@ -1248,8 +1266,8 @@ class HTMLReportCreator(ReportCreator):
1248 1266
 
1249 1267
 		# Weekly activity
1250 1268
 		WEEKS = 32
1251
-		f.write(html_header(2, 'Weekly activity'))
1252
-		f.write('<p>Last %d weeks</p>' % WEEKS)
1269
+		lines_content.append(html_header(2, 'Weekly activity'))
1270
+		lines_content.append('<p>Last %d weeks</p>' % WEEKS)
1253 1271
 
1254 1272
 		# generate weeks to show (previous N weeks from now)
1255 1273
 		now = datetime.datetime.now()
@@ -1261,7 +1279,7 @@ class HTMLReportCreator(ReportCreator):
1261 1279
 			stampcur -= deltaweek
1262 1280
 
1263 1281
 		# top row: commits & bar
1264
-		f.write('<table class="noborders"><tr>')
1282
+		lines_content.append('<table class="noborders"><tr>')
1265 1283
 		for i in range(0, WEEKS):
1266 1284
 			commits = 0
1267 1285
 			if weeks[i] in data.lineactivity_by_year_week:
@@ -1271,41 +1289,41 @@ class HTMLReportCreator(ReportCreator):
1271 1289
 			if weeks[i] in data.lineactivity_by_year_week:
1272 1290
 				percentage = float(data.lineactivity_by_year_week[weeks[i]]) / data.lineactivity_by_year_week_peak
1273 1291
 			height = max(1, int(200 * percentage))
1274
-			f.write('<td style="text-align: center; vertical-align: bottom">%d<div style="display: block; background-color: red; width: 20px; height: %dpx"></div></td>' % (commits, height))
1292
+			lines_content.append('<td style="text-align: center; vertical-align: bottom">%d<div style="display: block; background-color: red; width: 20px; height: %dpx"></div></td>' % (commits, height))
1275 1293
 
1276 1294
 		# bottom row: year/week
1277
-		f.write('</tr><tr>')
1295
+		lines_content.append('</tr><tr>')
1278 1296
 		for i in range(0, WEEKS):
1279
-			f.write('<td>%s</td>' % (WEEKS - i))
1280
-		f.write('</tr></table>')
1297
+			lines_content.append('<td>%s</td>' % (WEEKS - i))
1298
+		lines_content.append('</tr></table>')
1281 1299
 
1282 1300
 		# Hour of Day
1283
-		f.write(html_header(2, 'Hour of Day'))
1301
+		lines_content.append(html_header(2, 'Hour of Day'))
1284 1302
 		hour_of_day = data.getLineActivityByHourOfDay()
1285
-		f.write('<table><tr><th>Hour</th>')
1303
+		lines_content.append('<table><tr><th>Hour</th>')
1286 1304
 		for i in range(0, 24):
1287
-			f.write('<th>%d</th>' % i)
1288
-		f.write('</tr>\n<tr><th>Lines</th>')
1305
+			lines_content.append('<th>%d</th>' % i)
1306
+		lines_content.append('</tr>\n<tr><th>Lines</th>')
1289 1307
 		fp = open(path + '/line_hour_of_day.dat', 'w')
1290 1308
 		for i in range(0, 24):
1291 1309
 			if i in hour_of_day:
1292 1310
 				r = 127 + int((float(hour_of_day[i]) / data.lineactivity_by_hour_of_day_busiest) * 128)
1293
-				f.write('<td style="background-color: rgb(%d, 0, 0)">%d</td>' % (r, hour_of_day[i]))
1311
+				lines_content.append('<td style="background-color: rgb(%d, 0, 0)">%d</td>' % (r, hour_of_day[i]))
1294 1312
 				fp.write('%d %d\n' % (i, hour_of_day[i]))
1295 1313
 			else:
1296
-				f.write('<td>0</td>')
1314
+				lines_content.append('<td>0</td>')
1297 1315
 				fp.write('%d 0\n' % i)
1298 1316
 		fp.close()
1299
-		f.write('</tr>\n<tr><th>%</th>')
1317
+		lines_content.append('</tr>\n<tr><th>%</th>')
1300 1318
 		totallines = data.getTotalLines()
1301 1319
 		for i in range(0, 24):
1302 1320
 			if i in hour_of_day:
1303 1321
 				r = 127 + int((float(hour_of_day[i]) / data.lineactivity_by_hour_of_day_busiest) * 128)
1304
-				f.write('<td style="background-color: rgb(%d, 0, 0)">%.2f</td>' % (r, (100.0 * hour_of_day[i]) / totallines))
1322
+				lines_content.append('<td style="background-color: rgb(%d, 0, 0)">%.2f</td>' % (r, (100.0 * hour_of_day[i]) / totallines))
1305 1323
 			else:
1306
-				f.write('<td>0.00</td>')
1307
-		f.write('</tr></table>')
1308
-		f.write('<img src="line_hour_of_day.png" alt="Hour of Day" />')
1324
+				lines_content.append('<td>0.00</td>')
1325
+		lines_content.append('</tr></table>')
1326
+		lines_content.append('<img src="line_hour_of_day.png" alt="Hour of Day" />')
1309 1327
 		fg = open(path + '/line_hour_of_day.dat', 'w')
1310 1328
 		for i in range(0, 24):
1311 1329
 			if i in hour_of_day:
@@ -1315,122 +1333,127 @@ class HTMLReportCreator(ReportCreator):
1315 1333
 		fg.close()
1316 1334
 
1317 1335
 		# Day of Week
1318
-		f.write(html_header(2, 'Day of Week'))
1336
+		lines_content.append(html_header(2, 'Day of Week'))
1319 1337
 		day_of_week = data.getLineActivityByDayOfWeek()
1320
-		f.write('<div class="vtable"><table>')
1321
-		f.write('<tr><th>Day</th><th>Total (%)</th></tr>')
1338
+		lines_content.append('<div class="vtable"><table>')
1339
+		lines_content.append('<tr><th>Day</th><th>Total (%)</th></tr>')
1322 1340
 		fp = open(path + '/line_day_of_week.dat', 'w')
1323 1341
 		for d in range(0, 7):
1324 1342
 			commits = 0
1325 1343
 			if d in day_of_week:
1326 1344
 				commits = day_of_week[d]
1327 1345
 			fp.write('%d %s %d\n' % (d + 1, WEEKDAYS[d], commits))
1328
-			f.write('<tr>')
1329
-			f.write('<th>%s</th>' % (WEEKDAYS[d]))
1346
+			lines_content.append('<tr>')
1347
+			lines_content.append('<th>%s</th>' % (WEEKDAYS[d]))
1330 1348
 			if d in day_of_week:
1331
-				f.write('<td>%d (%.2f%%)</td>' % (day_of_week[d], (100.0 * day_of_week[d]) / totallines))
1349
+				lines_content.append('<td>%d (%.2f%%)</td>' % (day_of_week[d], (100.0 * day_of_week[d]) / totallines))
1332 1350
 			else:
1333
-				f.write('<td>0</td>')
1334
-			f.write('</tr>')
1335
-		f.write('</table></div>')
1336
-		f.write('<img src="line_day_of_week.png" alt="Day of Week" />')
1351
+				lines_content.append('<td>0</td>')
1352
+			lines_content.append('</tr>')
1353
+		lines_content.append('</table></div>')
1354
+		lines_content.append('<img src="line_day_of_week.png" alt="Day of Week" />')
1337 1355
 		fp.close()
1338 1356
 
1339 1357
 		# Hour of Week
1340
-		f.write(html_header(2, 'Hour of Week'))
1341
-		f.write('<table>')
1358
+		lines_content.append(html_header(2, 'Hour of Week'))
1359
+		lines_content.append('<table>')
1342 1360
 
1343
-		f.write('<tr><th>Weekday</th>')
1361
+		lines_content.append('<tr><th>Weekday</th>')
1344 1362
 		for hour in range(0, 24):
1345
-			f.write('<th>%d</th>' % (hour))
1346
-		f.write('</tr>')
1363
+			lines_content.append('<th>%d</th>' % (hour))
1364
+		lines_content.append('</tr>')
1347 1365
 
1348 1366
 		for weekday in range(0, 7):
1349
-			f.write('<tr><th>%s</th>' % (WEEKDAYS[weekday]))
1367
+			lines_content.append('<tr><th>%s</th>' % (WEEKDAYS[weekday]))
1350 1368
 			for hour in range(0, 24):
1351 1369
 				try:
1352 1370
 					commits = data.lineactivity_by_hour_of_week[weekday][hour]
1353 1371
 				except KeyError:
1354 1372
 					commits = 0
1355 1373
 				if commits != 0:
1356
-					f.write('<td')
1374
+					lines_content.append('<td')
1357 1375
 					r = 127 + int((float(commits) / data.lineactivity_by_hour_of_week_busiest) * 128)
1358
-					f.write(' style="background-color: rgb(%d, 0, 0)"' % r)
1359
-					f.write('>%d</td>' % commits)
1376
+					lines_content.append(' style="background-color: rgb(%d, 0, 0)"' % r)
1377
+					lines_content.append('>%d</td>' % commits)
1360 1378
 				else:
1361
-					f.write('<td></td>')
1362
-			f.write('</tr>')
1379
+					lines_content.append('<td></td>')
1380
+			lines_content.append('</tr>')
1363 1381
 
1364
-		f.write('</table>')
1382
+		lines_content.append('</table>')
1365 1383
 
1366 1384
 		# Month of Year
1367
-		f.write(html_header(2, 'Month of Year'))
1368
-		f.write('<div class="vtable"><table>')
1369
-		f.write('<tr><th>Month</th><th>Lines (%)</th></tr>')
1385
+		lines_content.append(html_header(2, 'Month of Year'))
1386
+		lines_content.append('<div class="vtable"><table>')
1387
+		lines_content.append('<tr><th>Month</th><th>Lines (%)</th></tr>')
1370 1388
 		fp = open (path + '/line_month_of_year.dat', 'w')
1371 1389
 		for mm in range(1, 13):
1372 1390
 			commits = 0
1373 1391
 			if mm in data.lineactivity_by_month_of_year:
1374 1392
 				commits = data.lineactivity_by_month_of_year[mm]
1375
-			f.write('<tr><td>%d</td><td>%d (%.2f %%)</td></tr>' % (mm, commits, (100.0 * commits) / data.getTotalLines()))
1393
+			lines_content.append('<tr><td>%d</td><td>%d (%.2f %%)</td></tr>' % (mm, commits, (100.0 * commits) / data.getTotalLines()))
1376 1394
 			fp.write('%d %d\n' % (mm, commits))
1377 1395
 		fp.close()
1378
-		f.write('</table></div>')
1379
-		f.write('<img src="line_month_of_year.png" alt="Month of Year" />')
1396
+		lines_content.append('</table></div>')
1397
+		lines_content.append('<img src="line_month_of_year.png" alt="Month of Year" />')
1380 1398
 
1381 1399
 		# Lines by year/month
1382
-		f.write(html_header(2, 'Lines by year/month'))
1383
-		f.write('<div class="vtable"><table><tr><th>Month</th><th>Commits</th><th>Lines added</th><th>Lines removed</th></tr>')
1400
+		lines_content.append(html_header(2, 'Lines by year/month'))
1401
+		lines_content.append('<div class="vtable"><table><tr><th>Month</th><th>Commits</th><th>Lines added</th><th>Lines removed</th></tr>')
1384 1402
 		for yymm in reversed(sorted(data.commits_by_month.keys())):
1385
-			f.write('<tr><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>' % (yymm, data.commits_by_month.get(yymm,0), data.lines_added_by_month.get(yymm,0), data.lines_removed_by_month.get(yymm,0)))
1386
-		f.write('</table></div>')
1387
-		f.write('<img src="line_commits_by_year_month.png" alt="Commits by year/month" />')
1403
+			lines_content.append('<tr><td>%s</td><td>%d</td><td>%d</td><td>%d</td></tr>' % (yymm, data.commits_by_month.get(yymm,0), data.lines_added_by_month.get(yymm,0), data.lines_removed_by_month.get(yymm,0)))
1404
+		lines_content.append('</table></div>')
1405
+		lines_content.append('<img src="line_commits_by_year_month.png" alt="Commits by year/month" />')
1388 1406
 		fg = open(path + '/line_commits_by_year_month.dat', 'w')
1389 1407
 		for yymm in sorted(data.commits_by_month.keys()):
1390 1408
 			fg.write('%s %s\n' % (yymm, data.lines_added_by_month.get(yymm, 0) + data.lines_removed_by_month.get(yymm, 0)))
1391 1409
 		fg.close()
1392 1410
 
1393 1411
 		# Lines by year
1394
-		f.write(html_header(2, 'Lines by Year'))
1395
-		f.write('<div class="vtable"><table><tr><th>Year</th><th>Commits (% of all)</th><th>Lines added</th><th>Lines removed</th></tr>')
1412
+		lines_content.append(html_header(2, 'Lines by Year'))
1413
+		lines_content.append('<div class="vtable"><table><tr><th>Year</th><th>Commits (% of all)</th><th>Lines added</th><th>Lines removed</th></tr>')
1396 1414
 		for yy in reversed(sorted(data.commits_by_year.keys())):
1397
-			f.write('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d</td><td>%d</td></tr>' % (yy, data.commits_by_year.get(yy,0), (100.0 * data.commits_by_year.get(yy,0)) / data.getTotalCommits(), data.lines_added_by_year.get(yy,0), data.lines_removed_by_year.get(yy,0)))
1398
-		f.write('</table></div>')
1399
-		f.write('<img src="line_commits_by_year.png" alt="Commits by Year" />')
1415
+			lines_content.append('<tr><td>%s</td><td>%d (%.2f%%)</td><td>%d</td><td>%d</td></tr>' % (yy, data.commits_by_year.get(yy,0), (100.0 * data.commits_by_year.get(yy,0)) / data.getTotalCommits(), data.lines_added_by_year.get(yy,0), data.lines_removed_by_year.get(yy,0)))
1416
+		lines_content.append('</table></div>')
1417
+		lines_content.append('<img src="line_commits_by_year.png" alt="Commits by Year" />')
1400 1418
 		fg = open(path + '/line_commits_by_year.dat', 'w')
1401 1419
 		for yy in sorted(data.commits_by_year.keys()):
1402 1420
 			fg.write('%d %d\n' % (yy, data.lines_added_by_year.get(yy,0) + data.lines_removed_by_year.get(yy,0)))
1403 1421
 		fg.close()
1404 1422
 
1405 1423
 		# Commits by timezone
1406
-		f.write(html_header(2, 'Commits by Timezone'))
1407
-		f.write('<table><tr>')
1408
-		f.write('<th>Timezone</th><th>Commits</th>')
1424
+		lines_content.append(html_header(2, 'Commits by Timezone'))
1425
+		lines_content.append('<table><tr>')
1426
+		lines_content.append('<th>Timezone</th><th>Commits</th>')
1409 1427
 		max_commits_on_tz = max(data.commits_by_timezone.values())
1410 1428
 		for i in sorted(data.commits_by_timezone.keys(), key = lambda n : int(n)):
1411 1429
 			commits = data.commits_by_timezone[i]
1412 1430
 			r = 127 + int((float(commits) / max_commits_on_tz) * 128)
1413
-			f.write('<tr><th>%s</th><td style="background-color: rgb(%d, 0, 0)">%d</td></tr>' % (i, r, commits))
1414
-		f.write('</tr></table>')
1431
+			lines_content.append('<tr><th>%s</th><td style="background-color: rgb(%d, 0, 0)">%d</td></tr>' % (i, r, commits))
1432
+		lines_content.append('</tr></table>')
1433
+
1434
+		self.printContent(f, lines_content, 'Lines')
1415 1435
 
1416
-		f.write('</body></html>')
1436
+		f.write('</div></body></html>')
1417 1437
 		f.close()
1418 1438
 
1419 1439
 		###
1420 1440
 		# tags.html
1421 1441
 		f = open(path + '/tags.html', 'w')
1422 1442
 		self.printHeader(f)
1423
-		f.write('<h1>Tags</h1>')
1443
+		
1424 1444
 		self.printNav(f)
1425 1445
 
1426
-		f.write('<dl>')
1427
-		f.write('<dt>Total tags</dt><dd>%d</dd>' % len(data.tags))
1446
+		tags_content = []
1447
+		
1448
+
1449
+		tags_content.append('<dl>')
1450
+		tags_content.append('<dt>Total tags</dt><dd>%d</dd>' % len(data.tags))
1428 1451
 		if len(data.tags) > 0:
1429
-			f.write('<dt>Average commits per tag</dt><dd>%.2f</dd>' % (1.0 * data.getTotalCommits() / len(data.tags)))
1430
-		f.write('</dl>')
1452
+			tags_content.append('<dt>Average commits per tag</dt><dd>%.2f</dd>' % (1.0 * data.getTotalCommits() / len(data.tags)))
1453
+		tags_content.append('</dl>')
1431 1454
 
1432
-		f.write('<table class="tags">')
1433
-		f.write('<tr><th>Name</th><th>Date</th><th>Commits</th><th>Authors</th></tr>')
1455
+		tags_content.append('<table class="tags">')
1456
+		tags_content.append('<tr><th>Name</th><th>Date</th><th>Commits</th><th>Authors</th></tr>')
1434 1457
 		# sort the tags by date desc
1435 1458
 		tags_sorted_by_date_desc = [el[1] for el in reversed(sorted([(el[1]['date'], el[0]) for el in list(data.tags.items())]))]
1436 1459
 		for tag in tags_sorted_by_date_desc:
@@ -1438,10 +1461,12 @@ class HTMLReportCreator(ReportCreator):
1438 1461
 			self.authors_by_commits = getkeyssortedbyvalues(data.tags[tag]['authors'])
1439 1462
 			for i in reversed(self.authors_by_commits):
1440 1463
 				authorinfo.append('%s (%d)' % (i, data.tags[tag]['authors'][i]))
1441
-			f.write('<tr><td>%s</td><td>%s</td><td>%d</td><td>%s</td></tr>' % (tag, data.tags[tag]['date'], data.tags[tag]['commits'], ', '.join(authorinfo)))
1442
-		f.write('</table>')
1464
+			tags_content.append('<tr><td>%s</td><td>%s</td><td>%d</td><td>%s</td></tr>' % (tag, data.tags[tag]['date'], data.tags[tag]['commits'], ', '.join(authorinfo)))
1465
+		tags_content.append('</table>')
1443 1466
 
1444
-		f.write('</body></html>')
1467
+		self.printContent(f, tags_content, 'Tags')
1468
+
1469
+		f.write('</div></body></html>')
1445 1470
 		f.close()
1446 1471
 
1447 1472
 		self.createGraphs(path)
@@ -1754,11 +1779,85 @@ plot """
1754 1779
 	<link rel="stylesheet" href="%s" type="text/css" />
1755 1780
 	<meta name="generator" content="GitStats %s" />
1756 1781
 	<script type="text/javascript" src="sortable.js"></script>
1782
+	<script src="https://cdn.tailwindcss.com"></script>
1757 1783
 </head>
1758 1784
 <body>
1785
+<div class="flex h-screen antialiased text-gray-900 bg-gray-100 dark:bg-dark dark:text-light">
1759 1786
 """ % (self.title, conf['style'], getversion()))
1787
+		
1760 1788
 
1789
+	def printContent(self, f, content, title="Front's Stats"):
1790
+		content = '\n'.join(content)
1791
+		f.write(f'''
1792
+		  <div class="flex-1 h-full overflow-x-hidden overflow-y-auto">
1793
+			<!-- Navbar -->
1794
+			<header class="relative flex-shrink-0 bg-white dark:bg-darker">
1795
+				<div class="flex items-center justify-between p-2 border-b dark:border-primary-darker">
1796
+			<a href="#" class="inline-block text-2xl font-bold tracking-wider uppercase text-primary-dark dark:text-light">
1797
+					{title}
1798
+				</a>
1799
+				</div>
1800
+			</header>
1801
+
1802
+			<!-- Main content -->
1803
+			<main class="p-2">{content}</main>
1804
+		  </div>
1805
+		  ''')
1806
+		
1761 1807
 	def printNav(self, f):
1808
+
1809
+		menu = ''.join([f'''
1810
+<a
1811
+                  href="{href}"
1812
+                  class="flex items-center p-2 text-gray-500 transition-colors rounded-md dark:text-light hover:bg-primary-100 dark:hover:bg-primary"
1813
+                  role="button"
1814
+                >
1815
+                  <span aria-hidden="true">
1816
+                    <svg
1817
+                      class="w-5 h-5"
1818
+                      xmlns="http://www.w3.org/2000/svg"
1819
+                      fill="none"
1820
+                      viewBox="0 0 24 24"
1821
+                      stroke="currentColor"
1822
+                    >
1823
+                      <path
1824
+                        stroke-linecap="round"
1825
+                        stroke-linejoin="round"
1826
+                        stroke-width="2"
1827
+                        d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
1828
+                      />
1829
+                    </svg>
1830
+                  </span>
1831
+                  <span class="ml-2 text-sm">{label}</span>
1832
+                </a>
1833
+''' for (label, href) in [
1834
+			("General", "index.html"),
1835
+			("Activity", "activity.html"),
1836
+			("Authors","authors.html"),
1837
+			("Files","files.html"),
1838
+			("Lines", "lines.html"),
1839
+			("Tags", "tags.html")
1840
+			]])
1841
+
1842
+		f.write(f'''
1843
+<!-- Sidebar -->
1844
+<aside class="flex-shrink-0 hidden w-64 bg-white border-r dark:border-primary-darker dark:bg-darker md:block">
1845
+	<div class="flex flex-col h-full">
1846
+		  <!-- Sidebar links -->
1847
+		  <nav aria-label="Main" class="flex-1 px-2 py-4 space-y-2 overflow-y-hidden hover:overflow-y-auto">
1848
+		  {menu}
1849
+		  </nav>
1850
+		  <!-- Sidebar footer -->
1851
+		  <div class="flex-shrink-0 px-2 py-4 space-y-2">
1852
+			<button type="button" class="flex items-center justify-center w-full px-4 py-2 text-sm text-white rounded-md bg-primary hover:bg-primary-dark focus:outline-none focus:ring focus:ring-primary-dark focus:ring-offset-1 focus:ring-offset-white dark:focus:ring-offset-dark">
1853
+				<span>Customize</span>
1854
+			</button>
1855
+		  </div>
1856
+	</div>
1857
+</aside>
1858
+''')
1859
+		
1860
+	def printNav_old(self, f):
1762 1861
 		f.write("""
1763 1862
 <div class="nav">
1764 1863
 <ul>

+ 0
- 4
gitstats.css Wyświetl plik

@@ -1,10 +1,6 @@
1 1
 /**
2 2
  * GitStats - default style
3 3
  */
4
-body {
5
-	color: black;
6
-	background-color: #dfd;
7
-}
8 4
 
9 5
 dt {
10 6
 	font-weight: bold;

+ 5
- 5
sortable.js Wyświetl plik

@@ -50,7 +50,7 @@ function ts_makeSortable(t) {
50 50
 		var cell = firstRow.cells[i];
51 51
 		var txt = ts_getInnerText(cell);
52 52
 		if (cell.className != "unsortable" && cell.className.indexOf("unsortable") == -1) {
53
-			cell.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this, '+i+');return false;">'+txt+'<span class="sortarrow">&nbsp;&nbsp;<img src="'+ image_path + image_none + '" alt="&darr;"/></span></a>';
53
+			cell.innerHTML = '<a href="#" class="sortheader flex flex-column items-center justify-between" onclick="ts_resortTable(this, '+i+');return false;">'+txt+'<span class="sortarrow"><img src="'+ image_path + image_none + '" alt="&darr;"/></span></a>';
54 54
 		}
55 55
 	}
56 56
 	if (alternate_row_colors) {
@@ -104,7 +104,7 @@ function ts_resortTable(lnk, clid) {
104 104
 	sortfn = ts_sort_caseinsensitive;
105 105
 	if (itm.match(/^\d\d[\/\.-][a-zA-z][a-zA-Z][a-zA-Z][\/\.-]\d\d\d\d$/)) sortfn = ts_sort_date;
106 106
 	if (itm.match(/^\d\d[\/\.-]\d\d[\/\.-]\d\d\d{2}?$/)) sortfn = ts_sort_date;
107
-	if (itm.match(/^-?[£$€Û¢´]\d/)) sortfn = ts_sort_numeric;
107
+	if (itm.match(/^-?[�$�ۢ�]\d/)) sortfn = ts_sort_numeric;
108 108
 	// ignore stuff in () after the numbers.
109 109
 	if (itm.match(/^-?(\d+[,\.]?)+(E[-+][\d]+)?%?( \(.*\))?$/)) sortfn = ts_sort_numeric;
110 110
 	SORT_COLUMN_INDEX = column;
@@ -130,11 +130,11 @@ function ts_resortTable(lnk, clid) {
130 130
 	}
131 131
 	newRows.sort(sortfn);
132 132
 	if (span.getAttribute("sortdir") == 'down') {
133
-			ARROW = '&nbsp;&nbsp;<img src="'+ image_path + image_down + '" alt="&darr;"/>';
133
+			ARROW = '<img src="'+ image_path + image_down + '" alt="&darr;"/>';
134 134
 			newRows.reverse();
135 135
 			span.setAttribute('sortdir','up');
136 136
 	} else {
137
-			ARROW = '&nbsp;&nbsp;<img src="'+ image_path + image_up + '" alt="&uarr;"/>';
137
+			ARROW = '<img src="'+ image_path + image_up + '" alt="&uarr;"/>';
138 138
 			span.setAttribute('sortdir','down');
139 139
 	} 
140 140
     // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
@@ -154,7 +154,7 @@ function ts_resortTable(lnk, clid) {
154 154
 	for (var ci=0;ci<allspans.length;ci++) {
155 155
 		if (allspans[ci].className == 'sortarrow') {
156 156
 			if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
157
-				allspans[ci].innerHTML = '&nbsp;&nbsp;<img src="'+ image_path + image_none + '" alt="&darr;"/>';
157
+				allspans[ci].innerHTML = '<img src="'+ image_path + image_none + '" alt="&darr;"/>';
158 158
 			}
159 159
 		}
160 160
 	}