gitstats-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/.gitignore +3 -0
  2. data/LICENSE +674 -0
  3. data/README.markdown +53 -0
  4. data/bin/gitstats +176 -0
  5. data/gitstats-ruby.gemspec +22 -0
  6. data/lib/gitstats.rb +18 -0
  7. data/lib/gitstats/author.rb +41 -0
  8. data/lib/gitstats/git.rb +158 -0
  9. data/lib/gitstats/renderer.rb +30 -0
  10. data/lib/gitstats/renderer/gnuplot.rb +150 -0
  11. data/lib/gitstats/renderer/haml.rb +70 -0
  12. data/lib/gitstats/renderer/sass.rb +33 -0
  13. data/lib/gitstats/statgen.rb +116 -0
  14. data/lib/gitstats/stats.rb +12 -0
  15. data/lib/gitstats/stats/commit.rb +48 -0
  16. data/lib/gitstats/stats/commit/author.rb +17 -0
  17. data/lib/gitstats/stats/commit/time.rb +92 -0
  18. data/lib/gitstats/stats/file.rb +15 -0
  19. data/lib/gitstats/stats/file/filetype.rb +14 -0
  20. data/lib/gitstats/yearmonth.rb +39 -0
  21. data/template/activity.haml +3 -0
  22. data/template/asc.gif +0 -0
  23. data/template/authors.haml +2 -0
  24. data/template/bg.gif +0 -0
  25. data/template/commits_per_hour.plot +14 -0
  26. data/template/commits_per_month.plot +15 -0
  27. data/template/commits_per_wday.plot +15 -0
  28. data/template/commits_per_year.plot +11 -0
  29. data/template/commits_per_yearmonth.plot +11 -0
  30. data/template/desc.gif +0 -0
  31. data/template/filechanges_by_yearmonth.plot +17 -0
  32. data/template/files.haml +2 -0
  33. data/template/files_by_yearmonth.plot +11 -0
  34. data/template/helpers/block.rb +26 -0
  35. data/template/helpers/config.rb +12 -0
  36. data/template/helpers/names.rb +76 -0
  37. data/template/helpers/utils.rb +16 -0
  38. data/template/index.haml +2 -0
  39. data/template/jquery.js +4 -0
  40. data/template/jquery.tablesorter.js +4 -0
  41. data/template/lastweeks.plot +14 -0
  42. data/template/layouts/default.haml +43 -0
  43. data/template/linechanges_by_yearmonth.plot +17 -0
  44. data/template/lines.haml +2 -0
  45. data/template/lines_by_yearmonth.plot +11 -0
  46. data/template/partials/authors.haml +30 -0
  47. data/template/partials/blockheader.haml +2 -0
  48. data/template/partials/blocktoc.haml +7 -0
  49. data/template/partials/commits_per_month.haml +27 -0
  50. data/template/partials/commits_per_year.haml +27 -0
  51. data/template/partials/commits_per_yearmonth.haml +27 -0
  52. data/template/partials/day_of_week.haml +28 -0
  53. data/template/partials/filechanges_by_yearmonth.haml +3 -0
  54. data/template/partials/files_by_yearmonth.haml +3 -0
  55. data/template/partials/filetypes.haml +23 -0
  56. data/template/partials/general.haml +34 -0
  57. data/template/partials/hour_of_day.haml +28 -0
  58. data/template/partials/hour_of_week.haml +24 -0
  59. data/template/partials/lastweeks.haml +24 -0
  60. data/template/partials/linechanges_by_yearmonth.haml +3 -0
  61. data/template/partials/lines_by_yearmonth.haml +3 -0
  62. data/template/partials/repos.haml +13 -0
  63. data/template/partials/top_authors_of_year.haml +30 -0
  64. data/template/partials/top_authors_of_yearmonth.haml +30 -0
  65. data/template/style.scss +132 -0
  66. 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,12 @@
1
+ module StatsHash
2
+ def method_missing(method, *args, &block)
3
+ @hash.send(method, *args, &block)
4
+ end
5
+
6
+ def each_sorted
7
+ @hash.keys.sort.each do |key|
8
+ yield key, @hash[key]
9
+ end
10
+ end
11
+ end
12
+
@@ -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
+