gitstats-ruby 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/LICENSE +674 -0
- data/README.markdown +53 -0
- data/bin/gitstats +176 -0
- data/gitstats-ruby.gemspec +22 -0
- data/lib/gitstats.rb +18 -0
- data/lib/gitstats/author.rb +41 -0
- data/lib/gitstats/git.rb +158 -0
- data/lib/gitstats/renderer.rb +30 -0
- data/lib/gitstats/renderer/gnuplot.rb +150 -0
- data/lib/gitstats/renderer/haml.rb +70 -0
- data/lib/gitstats/renderer/sass.rb +33 -0
- data/lib/gitstats/statgen.rb +116 -0
- data/lib/gitstats/stats.rb +12 -0
- data/lib/gitstats/stats/commit.rb +48 -0
- data/lib/gitstats/stats/commit/author.rb +17 -0
- data/lib/gitstats/stats/commit/time.rb +92 -0
- data/lib/gitstats/stats/file.rb +15 -0
- data/lib/gitstats/stats/file/filetype.rb +14 -0
- data/lib/gitstats/yearmonth.rb +39 -0
- data/template/activity.haml +3 -0
- data/template/asc.gif +0 -0
- data/template/authors.haml +2 -0
- data/template/bg.gif +0 -0
- data/template/commits_per_hour.plot +14 -0
- data/template/commits_per_month.plot +15 -0
- data/template/commits_per_wday.plot +15 -0
- data/template/commits_per_year.plot +11 -0
- data/template/commits_per_yearmonth.plot +11 -0
- data/template/desc.gif +0 -0
- data/template/filechanges_by_yearmonth.plot +17 -0
- data/template/files.haml +2 -0
- data/template/files_by_yearmonth.plot +11 -0
- data/template/helpers/block.rb +26 -0
- data/template/helpers/config.rb +12 -0
- data/template/helpers/names.rb +76 -0
- data/template/helpers/utils.rb +16 -0
- data/template/index.haml +2 -0
- data/template/jquery.js +4 -0
- data/template/jquery.tablesorter.js +4 -0
- data/template/lastweeks.plot +14 -0
- data/template/layouts/default.haml +43 -0
- data/template/linechanges_by_yearmonth.plot +17 -0
- data/template/lines.haml +2 -0
- data/template/lines_by_yearmonth.plot +11 -0
- data/template/partials/authors.haml +30 -0
- data/template/partials/blockheader.haml +2 -0
- data/template/partials/blocktoc.haml +7 -0
- data/template/partials/commits_per_month.haml +27 -0
- data/template/partials/commits_per_year.haml +27 -0
- data/template/partials/commits_per_yearmonth.haml +27 -0
- data/template/partials/day_of_week.haml +28 -0
- data/template/partials/filechanges_by_yearmonth.haml +3 -0
- data/template/partials/files_by_yearmonth.haml +3 -0
- data/template/partials/filetypes.haml +23 -0
- data/template/partials/general.haml +34 -0
- data/template/partials/hour_of_day.haml +28 -0
- data/template/partials/hour_of_week.haml +24 -0
- data/template/partials/lastweeks.haml +24 -0
- data/template/partials/linechanges_by_yearmonth.haml +3 -0
- data/template/partials/lines_by_yearmonth.haml +3 -0
- data/template/partials/repos.haml +13 -0
- data/template/partials/top_authors_of_year.haml +30 -0
- data/template/partials/top_authors_of_yearmonth.haml +30 -0
- data/template/style.scss +132 -0
- metadata +187 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
class Renderer
|
2
|
+
def self.register(cls)
|
3
|
+
@@renderers ||= Array.new
|
4
|
+
@@renderers << cls
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(templatedir, outdir, verbose)
|
8
|
+
@templatedir = templatedir
|
9
|
+
@outdir = outdir
|
10
|
+
@verbose = verbose
|
11
|
+
@renderers = @@renderers.map { |x| x.new(templatedir, outdir, verbose) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def render(stats)
|
15
|
+
Dir.chdir(@templatedir) { Dir.glob('*').sort }.each do |file|
|
16
|
+
next unless File.file?(File.join(@templatedir, file))
|
17
|
+
|
18
|
+
r = @renderers.find { |r| r.handle?(file) }
|
19
|
+
|
20
|
+
if r.nil?
|
21
|
+
puts "copying '#{file}' ..." if @verbose
|
22
|
+
File.copy(File.join(@templatedir, file), @outdir)
|
23
|
+
else
|
24
|
+
puts "rendering '#{file}' using #{r.name} ..." if @verbose
|
25
|
+
r.render(file, stats)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,150 @@
|
|
1
|
+
class GnuplotRenderer
|
2
|
+
class PlotHelper
|
3
|
+
class Plotter
|
4
|
+
attr_reader :plot
|
5
|
+
|
6
|
+
def initialize(helper)
|
7
|
+
@helper = helper
|
8
|
+
@plot = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
Gnuplot.open do |gp|
|
13
|
+
Gnuplot::Plot.new(gp) do |plot|
|
14
|
+
@plot = plot
|
15
|
+
|
16
|
+
plot.terminal 'png transparent size 640,240'
|
17
|
+
plot.size '1.0,1.0'
|
18
|
+
plot.output File.join(@helper.outdir, File.basename(@helper.filename, '.plot') + '.png')
|
19
|
+
plot.nokey
|
20
|
+
plot.xtics 'rotate'
|
21
|
+
plot.ytics 'autofreq'
|
22
|
+
plot.grid 'y'
|
23
|
+
|
24
|
+
yield self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_boxes(args = {})
|
30
|
+
args = {
|
31
|
+
:setrange => true,
|
32
|
+
:limitlabels => true,
|
33
|
+
:labelcount => 15
|
34
|
+
}.merge(args)
|
35
|
+
|
36
|
+
x = Array.new
|
37
|
+
l = Array.new
|
38
|
+
y = Array.new
|
39
|
+
|
40
|
+
yield x, l, y
|
41
|
+
|
42
|
+
limitlabels(l, args[:labelcount]) if args[:limitlabels]
|
43
|
+
|
44
|
+
@plot.xrange "[\"#{x.first - 1}\":\"#{x.last + 1}\"]" if args[:setrange]
|
45
|
+
|
46
|
+
@plot.data << Gnuplot::DataSet.new([x, l, y]) do |ds|
|
47
|
+
ds.using = '1:3:(0.5):xtic(2)'
|
48
|
+
ds.with = 'boxes fs solid'
|
49
|
+
ds.notitle
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_steps(args = {})
|
54
|
+
args = {
|
55
|
+
:setrange => true,
|
56
|
+
:limitlabels => true,
|
57
|
+
:labelcount => 15,
|
58
|
+
:firstlabel => '""'
|
59
|
+
}.merge(args)
|
60
|
+
|
61
|
+
x = Array.new
|
62
|
+
l = Array.new
|
63
|
+
y = Array.new
|
64
|
+
|
65
|
+
yield x, l, y
|
66
|
+
|
67
|
+
limitlabels(l, args[:labelcount]) if args[:limitlabels]
|
68
|
+
|
69
|
+
@plot.xrange "[\"#{x.first - 1}\":\"#{x.last + 1}\"]" if args[:setrange]
|
70
|
+
|
71
|
+
unless args[:firstlabel].nil?
|
72
|
+
x.insert(0, x.first - 1)
|
73
|
+
l.insert(0, args[:firstlabel])
|
74
|
+
y.insert(0, 0)
|
75
|
+
end
|
76
|
+
|
77
|
+
@plot.data << Gnuplot::DataSet.new([x, l, y]) do |ds|
|
78
|
+
ds.using = '1:3:xtic(2)'
|
79
|
+
ds.with = 'steps'
|
80
|
+
ds.notitle
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def limitlabels(l, maxcount)
|
86
|
+
cnt = l.size
|
87
|
+
step = (cnt > maxcount) ? (cnt / maxcount + 0.5).round.to_i : 1
|
88
|
+
|
89
|
+
i = 0
|
90
|
+
l.map! do |e|
|
91
|
+
unless ((i % step) == 0) || (i == (cnt - 1))
|
92
|
+
e = '""'
|
93
|
+
end
|
94
|
+
i += 1
|
95
|
+
e
|
96
|
+
end
|
97
|
+
l
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
attr_reader :filename
|
102
|
+
attr_reader :outdir
|
103
|
+
attr_reader :stats
|
104
|
+
attr_reader :verbose
|
105
|
+
|
106
|
+
def initialize(templatedir, filename, outdir, stats, verbose)
|
107
|
+
@filename = filename
|
108
|
+
@outdir = outdir
|
109
|
+
@stats = stats
|
110
|
+
@verbose = verbose
|
111
|
+
|
112
|
+
Dir.glob(File.join(templatedir, 'helpers', '*.rb')).sort.each do |file|
|
113
|
+
eval(IO::readlines(file).join(''))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def run(lines)
|
118
|
+
eval(lines)
|
119
|
+
end
|
120
|
+
|
121
|
+
def defplot(&block)
|
122
|
+
plotter = Plotter.new(self)
|
123
|
+
plotter.run(&block)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def initialize(templatedir, outdir, verbose)
|
128
|
+
@templatedir = templatedir
|
129
|
+
@outdir = outdir
|
130
|
+
@verbose = verbose
|
131
|
+
end
|
132
|
+
|
133
|
+
def name
|
134
|
+
'gnuplot'
|
135
|
+
end
|
136
|
+
|
137
|
+
def handle?(file)
|
138
|
+
file =~ /\.plot$/
|
139
|
+
end
|
140
|
+
|
141
|
+
def render(file, stats)
|
142
|
+
ifile = File.join(@templatedir, file)
|
143
|
+
|
144
|
+
lines = IO::readlines(ifile).join('')
|
145
|
+
|
146
|
+
PlotHelper.new(@templatedir, file, @outdir, stats, @verbose).run(lines)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
Renderer.register GnuplotRenderer
|
@@ -0,0 +1,70 @@
|
|
1
|
+
class HamlRenderer
|
2
|
+
class HamlHelper
|
3
|
+
attr_reader :stats
|
4
|
+
|
5
|
+
def initialize(templatedir, stats, verbose)
|
6
|
+
@templatedir = templatedir
|
7
|
+
@stats = stats
|
8
|
+
@verbose = verbose
|
9
|
+
@layout = nil
|
10
|
+
|
11
|
+
Dir.glob(File.join(templatedir, 'helpers', '*.rb')).sort.each do |file|
|
12
|
+
eval(IO::readlines(file).join(''))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def partial(name, hash = {})
|
17
|
+
name = name.to_s
|
18
|
+
puts "rendering partial '#{name}' ..." if @verbose
|
19
|
+
hash = hash.to_h unless hash.is_a? Hash
|
20
|
+
lines = IO::readlines(File.join(@templatedir, 'partials', "#{name}.haml")).join('')
|
21
|
+
engine = Haml::Engine.new(lines)
|
22
|
+
engine.render(self, hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
def layout(name)
|
26
|
+
@layout = name.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_layout
|
30
|
+
@layout
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(templatedir, outdir, verbose)
|
35
|
+
@templatedir = templatedir
|
36
|
+
@outdir = outdir
|
37
|
+
@verbose = verbose
|
38
|
+
end
|
39
|
+
|
40
|
+
def name
|
41
|
+
'haml'
|
42
|
+
end
|
43
|
+
|
44
|
+
def handle?(file)
|
45
|
+
file =~ /\.haml$/
|
46
|
+
end
|
47
|
+
|
48
|
+
def render(file, stats)
|
49
|
+
ifile = File.join(@templatedir, file)
|
50
|
+
ofile = File.join(@outdir, File.basename(file, '.haml') + '.html')
|
51
|
+
|
52
|
+
lines = IO::readlines(ifile).join('')
|
53
|
+
|
54
|
+
helper = HamlHelper.new(@templatedir, stats, @verbose)
|
55
|
+
|
56
|
+
engine = Haml::Engine.new(lines)
|
57
|
+
lines = engine.render(helper)
|
58
|
+
|
59
|
+
if !helper.get_layout.nil?
|
60
|
+
puts "rendering layout '#{helper.get_layout}' ..." if @verbose
|
61
|
+
layout = IO::readlines(File.join(@templatedir, 'layouts', helper.get_layout + '.haml')).join('')
|
62
|
+
engine = Haml::Engine.new(layout)
|
63
|
+
lines = engine.render(Object.new, :content => lines)
|
64
|
+
end
|
65
|
+
|
66
|
+
File.new(ofile, 'w').write(lines)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Renderer.register HamlRenderer
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class SassRenderer
|
2
|
+
def initialize(templatedir, outdir, verbose)
|
3
|
+
@templatedir = templatedir
|
4
|
+
@outdir = outdir
|
5
|
+
@verbose = verbose
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
'sass/compass'
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle?(file)
|
13
|
+
(file =~ /\.sass$/) || (file =~ /\.scss$/)
|
14
|
+
end
|
15
|
+
|
16
|
+
def render(file, stats)
|
17
|
+
scss = file =~ /\.scss$/
|
18
|
+
|
19
|
+
ifile = File.join(@templatedir, file)
|
20
|
+
ofile = File.join(@outdir, File.basename(file, scss ? '.scss' : '.sass') + '.css')
|
21
|
+
|
22
|
+
lines = IO::readlines(ifile).join('')
|
23
|
+
|
24
|
+
options = Compass.sass_engine_options
|
25
|
+
options[:syntax] = scss ? :scss : :sass
|
26
|
+
engine = Sass::Engine.new(lines, options)
|
27
|
+
lines = engine.render
|
28
|
+
|
29
|
+
File.new(ofile, 'w').write(lines)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Renderer.register SassRenderer
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class StatGen
|
2
|
+
attr_accessor :verbose
|
3
|
+
attr_accessor :debug
|
4
|
+
attr_accessor :quiet
|
5
|
+
attr_accessor :future
|
6
|
+
attr_accessor :maxage
|
7
|
+
attr_accessor :commitcache
|
8
|
+
attr_accessor :include_mail
|
9
|
+
attr_reader :repos
|
10
|
+
|
11
|
+
attr_reader :num_commits
|
12
|
+
|
13
|
+
attr_reader :general_stats
|
14
|
+
attr_reader :author_stats
|
15
|
+
attr_reader :year_stats
|
16
|
+
attr_reader :month_stats
|
17
|
+
attr_reader :yearmonth_stats
|
18
|
+
attr_reader :hour_stats
|
19
|
+
attr_reader :wday_stats
|
20
|
+
attr_reader :lastweeks_stats
|
21
|
+
|
22
|
+
attr_reader :file_stats
|
23
|
+
attr_reader :filetype_stats
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@repos = Array.new
|
27
|
+
@repostate = Hash.new
|
28
|
+
|
29
|
+
@verbose = false
|
30
|
+
@debug = false
|
31
|
+
@quiet = false
|
32
|
+
@future = true
|
33
|
+
@maxage = 0
|
34
|
+
@commitcache = nil
|
35
|
+
@include_mail = false
|
36
|
+
|
37
|
+
@num_commits = 0
|
38
|
+
@general_stats = CommitStats.new
|
39
|
+
@author_stats = AuthorsCommitStats.new
|
40
|
+
@year_stats = YearCommitStats.new
|
41
|
+
@month_stats = MonthCommitStats.new
|
42
|
+
@yearmonth_stats = YearMonthCommitStats.new
|
43
|
+
@hour_stats = HourCommitStats.new
|
44
|
+
@wday_stats = DayOfWeekCommitStats.new
|
45
|
+
@lastweeks_stats = LastWeeksCommitStats.new
|
46
|
+
|
47
|
+
@file_stats = FileStats.new
|
48
|
+
@filetype_stats = FileTypeFileStats.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def num_authors
|
52
|
+
@author_stats.size
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear_repos
|
56
|
+
@repos = Array.new
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_repostate
|
60
|
+
@repostate.keys.each do |name|
|
61
|
+
return false if @repos.find { |x| x.name == name }.nil?
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
def add(name, directory, ref = 'HEAD')
|
67
|
+
cache = nil
|
68
|
+
unless @commitcache.nil?
|
69
|
+
cache = File.join(@commitcache, ".commitcache.#{name.tr('^a-zA-Z0-9_', '_')}")
|
70
|
+
end
|
71
|
+
@repos << Git.new(name, directory, ref, @debug, cache)
|
72
|
+
end
|
73
|
+
|
74
|
+
def <<(value)
|
75
|
+
add(value[0], value[1], value[2])
|
76
|
+
end
|
77
|
+
|
78
|
+
def calc
|
79
|
+
# reset because of caching for now
|
80
|
+
@file_stats = FileStats.new
|
81
|
+
@filetype_stats = FileTypeFileStats.new
|
82
|
+
|
83
|
+
@repos.each do |repo|
|
84
|
+
@repostate[repo.name] ||= {
|
85
|
+
:last => nil
|
86
|
+
}
|
87
|
+
|
88
|
+
puts " repository '#{repo.name}' ..." unless @quiet
|
89
|
+
|
90
|
+
repo.get_commits(@repostate[repo.name][:last]) do |commit|
|
91
|
+
next if !@future && (commit[:time] > Time.now)
|
92
|
+
next if (@maxage > 0) && ((Time.now - commit[:time]) > @maxage)
|
93
|
+
|
94
|
+
puts " commit #{@num_commits} ..." if @verbose && ((@num_commits % 100) == 0)
|
95
|
+
|
96
|
+
@num_commits += 1
|
97
|
+
@general_stats.update(commit)
|
98
|
+
@author_stats.update(commit)
|
99
|
+
@year_stats.update(commit)
|
100
|
+
@month_stats.update(commit)
|
101
|
+
@yearmonth_stats.update(commit)
|
102
|
+
@hour_stats.update(commit)
|
103
|
+
@wday_stats.update(commit)
|
104
|
+
@lastweeks_stats.update(commit)
|
105
|
+
|
106
|
+
@repostate[repo.name][:last] = commit[:hash]
|
107
|
+
end
|
108
|
+
|
109
|
+
repo.get_files do |file|
|
110
|
+
@file_stats.update(file)
|
111
|
+
@filetype_stats.update(file)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class CommitStats
|
2
|
+
attr_reader :commits
|
3
|
+
attr_reader :files_added
|
4
|
+
attr_reader :files_deleted
|
5
|
+
attr_reader :lines_added
|
6
|
+
attr_reader :lines_deleted
|
7
|
+
attr_reader :files
|
8
|
+
attr_reader :lines
|
9
|
+
attr_reader :first_commit
|
10
|
+
attr_reader :last_commit
|
11
|
+
|
12
|
+
def days
|
13
|
+
@days.size
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@commits = 0
|
18
|
+
@files_added = 0
|
19
|
+
@files_deleted = 0
|
20
|
+
@lines_added = 0
|
21
|
+
@lines_deleted = 0
|
22
|
+
@files = 0
|
23
|
+
@lines = 0
|
24
|
+
@first_commit = nil
|
25
|
+
@last_commit = nil
|
26
|
+
@days = Array.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(commit)
|
30
|
+
@commits += 1
|
31
|
+
@files_added += commit[:files_added]
|
32
|
+
@files_deleted += commit[:files_deleted]
|
33
|
+
@lines_added += commit[:lines_added]
|
34
|
+
@lines_deleted += commit[:lines_deleted]
|
35
|
+
@files = @files_added - @files_deleted
|
36
|
+
@lines = @lines_added - @lines_deleted
|
37
|
+
|
38
|
+
@first_commit ||= commit[:time]
|
39
|
+
@last_commit ||= commit[:time]
|
40
|
+
|
41
|
+
@first_commit = commit[:time] if commit[:time] < @first_commit
|
42
|
+
@last_commit = commit[:time] if commit[:time] > @last_commit
|
43
|
+
|
44
|
+
day = commit[:time].year * 10000 + commit[:time].month * 100 + commit[:time].day
|
45
|
+
@days << day unless @days.include? day
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|