diff --git a/git_stats.gemspec b/git_stats.gemspec index 22fc15f8e..1065760e9 100644 --- a/git_stats.gemspec +++ b/git_stats.gemspec @@ -11,7 +11,6 @@ Gem::Specification.new do |gem| gem.description = %q{Git history statistics generator} gem.summary = %q{HTML statistics generator from git repository} gem.homepage = "https://github.com/tomgi/git_stats" - gem.executables = "git_stats" gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } @@ -25,5 +24,6 @@ Gem::Specification.new do |gem| gem.add_dependency('launchy') gem.add_dependency('lazy_high_charts') + gem.add_development_dependency('rake') gem.add_development_dependency('pry') end diff --git a/lib/git_stats.rb b/lib/git_stats.rb index 93260f1ee..8b1572f5d 100644 --- a/lib/git_stats.rb +++ b/lib/git_stats.rb @@ -4,6 +4,7 @@ end require 'active_support/all' require 'action_pack' require 'action_view' +require 'fileutils' require 'pathname' require 'tilt' require 'lazy_high_charts' diff --git a/lib/git_stats/generator.rb b/lib/git_stats/generator.rb index 0b8032d01..40b3466e8 100644 --- a/lib/git_stats/generator.rb +++ b/lib/git_stats/generator.rb @@ -1,16 +1,18 @@ -class GitStats::Generator - def initialize(repo_path, out_path) - @repo_path, @out_path = repo_path, out_path +module GitStats + class Generator + def initialize(repo_path, out_path) + @repo_path, @out_path = repo_path, out_path + end + + def generate + repo = GitData::Repo.new(@repo_path) + repo.gather_all_data + + view_data = StatsView::ViewData.new(repo) + view_data.generate_charts + + StatsView::View.render_all(view_data, @out_path) + end end - def generate - repo = GitStats::GitRepo.new(@repo_path) - data = GitStats::GitData.new(repo) - data.gather_all - - view_data = GitStats::ViewData.new(data) - view_data.generate_charts - - GitStats::View.render_all(view_data, @out_path) - end -end +end \ No newline at end of file diff --git a/lib/git_stats/git_data/activity.rb b/lib/git_stats/git_data/activity.rb new file mode 100644 index 000000000..3b9aeca8d --- /dev/null +++ b/lib/git_stats/git_data/activity.rb @@ -0,0 +1,44 @@ +module GitStats + module GitData + class Activity + + def add_commit(commit) + add_commit_at(commit.date) + end + + def add_commit_at(date) + self.by_hour[date.hour] += 1 + self.by_wday[date.wday] += 1 + self.by_wday_hour[date.wday][date.hour] += 1 + self.by_month[date.month] += 1 + self.by_year[date.year] += 1 + self.by_year_week[date.year][date.cweek] += 1 + end + + def by_hour + @by_hour ||= Hash.new(0) + end + + def by_wday + @by_wday ||= Hash.new(0) + end + + def by_wday_hour + @by_wday_hour ||= Hash.new { |h, k| h[k] = Hash.new(0) } + end + + def by_month + @by_month ||= Hash.new(0) + end + + def by_year + @by_year ||= Hash.new(0) + end + + def by_year_week + @by_year_week ||= Hash.new { |h, k| h[k] = Hash.new(0) } + end + + end + end +end \ No newline at end of file diff --git a/lib/git_stats/git_data/author.rb b/lib/git_stats/git_data/author.rb new file mode 100644 index 000000000..b750ed344 --- /dev/null +++ b/lib/git_stats/git_data/author.rb @@ -0,0 +1,24 @@ +require 'git_stats/hash_initializable' + +module GitStats + module GitData + class Author + include HashInitializable + + attr_accessor :name, :email + + def add_commit(commit) + commits << commit + activity.add_commit(commit) + end + + def commits + @commits ||= [] + end + + def activity + @activity ||= Activity.new + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/git_data/command.rb b/lib/git_stats/git_data/command.rb new file mode 100644 index 000000000..fe431c255 --- /dev/null +++ b/lib/git_stats/git_data/command.rb @@ -0,0 +1,19 @@ +module GitStats + module GitData + class Command + def initialize(repo, command) + @repo = repo + @command = command + end + + def run + puts "running #@command" + in_repo { %x[#@command] } + end + + def in_repo + Dir.chdir(@repo.path) { yield } + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/git_data/commit.rb b/lib/git_stats/git_data/commit.rb new file mode 100644 index 000000000..a76bebd31 --- /dev/null +++ b/lib/git_stats/git_data/commit.rb @@ -0,0 +1,24 @@ +require 'git_stats/hash_initializable' + +module GitStats + module GitData + class Commit + include HashInitializable + + attr_reader :repo, :hash, :stamp, :date, :author + + def gather_all_data + files_count + short_stat + end + + def files_count + @files_count ||= Command.new(repo, "git ls-tree -r --name-only #{self.hash} | wc -l").run.to_i + end + + def short_stat + @short_stat ||= ShortStat.new(self) + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/git_data/git_activity.rb b/lib/git_stats/git_data/git_activity.rb deleted file mode 100644 index bc089a69c..000000000 --- a/lib/git_stats/git_data/git_activity.rb +++ /dev/null @@ -1,45 +0,0 @@ -class GitStats::GitActivity - - def add_commit(commit) - add_commit_at(commit.date) - files_count[commit.date] = commit.files.size - end - - def add_commit_at(date) - self.by_hour[date.hour] += 1 - self.by_wday[date.wday] += 1 - self.by_wday_hour[date.wday][date.hour] += 1 - self.by_month[date.month] += 1 - self.by_year[date.year] += 1 - self.by_year_week[date.year][date.cweek] += 1 - end - - def by_hour - @by_hour ||= Array.new(24, 0) - end - - def by_wday - @by_wday ||= Array.new(7, 0) - end - - def by_wday_hour - @by_wday_hour ||= Array.new(7) { Array.new(24, 0) } - end - - def by_month - @by_month ||= Array.new(12, 0) - end - - def by_year - @by_year ||= Hash.new(0) - end - - def by_year_week - @by_year_week ||= Hash.new { |h, k| h[k] = Hash.new(0) } - end - - def files_count - @files_count ||= {} - end - -end \ No newline at end of file diff --git a/lib/git_stats/git_data/git_author.rb b/lib/git_stats/git_data/git_author.rb deleted file mode 100644 index dcdfbe6de..000000000 --- a/lib/git_stats/git_data/git_author.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'git_stats/hash_initializable' - -class GitStats::GitAuthor - include HashInitializable - - attr_accessor :name, :email - - def activity - @activity ||= GitStats::GitActivity.new - end -end \ No newline at end of file diff --git a/lib/git_stats/git_data/git_command.rb b/lib/git_stats/git_data/git_command.rb deleted file mode 100644 index 4ba557260..000000000 --- a/lib/git_stats/git_data/git_command.rb +++ /dev/null @@ -1,14 +0,0 @@ -class GitStats::GitCommand - def initialize(repo, command) - @repo = repo - @command = command - end - - def run - in_repo { %x[#@command] } - end - - def in_repo - Dir.chdir(@repo.path) { yield } - end -end \ No newline at end of file diff --git a/lib/git_stats/git_data/git_commit.rb b/lib/git_stats/git_data/git_commit.rb deleted file mode 100644 index 3f661725e..000000000 --- a/lib/git_stats/git_data/git_commit.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'git_stats/hash_initializable' - -class GitStats::GitCommit - include HashInitializable - - attr_accessor :repo, :hash, :stamp, :date, :author - - def files - @files ||= GitStats::GitCommand.new(repo, "git ls-tree -r --name-only #{self.hash}").run.split(/\r?\n/).map(&:strip) - end -end \ No newline at end of file diff --git a/lib/git_stats/git_data/git_data.rb b/lib/git_stats/git_data/git_data.rb deleted file mode 100644 index f84146c87..000000000 --- a/lib/git_stats/git_data/git_data.rb +++ /dev/null @@ -1,53 +0,0 @@ -class GitStats::GitData - attr_reader :repo - - def initialize(repo) - @repo = repo - end - - def gather_all - gather_authors - gather_commits - end - - def gather_authors - GitStats::GitCommand.new(repo, 'git shortlog -se HEAD').run.each_line do |author| - name, email = author.split(/\t/)[1].strip.scan(/(.*)<(.*)>/).first.map(&:strip) - authors[email] = GitStats::GitAuthor.new(name: name, email: email) - end - end - - def gather_commits - GitStats::GitCommand.new(repo, 'git rev-list --pretty=format:"%h|%at|%ai|%aE" HEAD | grep -v commit').run.each_line do |commit_line| - hash, stamp, date, author_email = commit_line.split('|').map(&:strip) - author = authors[author_email] - - date = DateTime.parse(date) - commit = commits[hash] = GitStats::GitCommit.new(repo: repo, hash: hash, stamp: stamp, date: date, author: author) - - activity.add_commit(commit) - author.activity.add_commit(commit) - end - end - - def authors - @authors ||= {} - end - - def commits - @commits ||= {} - end - - def activity - @activity ||= GitStats::GitActivity.new - end - - def project_version - @project_version ||= GitStats::GitCommand.new(repo, 'git rev-parse --short HEAD').run - end - - def project_name - @project_name ||= Pathname(repo.path).basename.to_s - end - -end diff --git a/lib/git_stats/git_data/git_repo.rb b/lib/git_stats/git_data/git_repo.rb deleted file mode 100644 index 41854cb65..000000000 --- a/lib/git_stats/git_data/git_repo.rb +++ /dev/null @@ -1,7 +0,0 @@ -class GitStats::GitRepo - attr_reader :path - - def initialize(path) - @path = path - end -end \ No newline at end of file diff --git a/lib/git_stats/git_data/repo.rb b/lib/git_stats/git_data/repo.rb new file mode 100644 index 000000000..7282690da --- /dev/null +++ b/lib/git_stats/git_data/repo.rb @@ -0,0 +1,59 @@ +module GitStats + module GitData + class Repo + attr_reader :path + + def initialize(path) + @path = path + end + + def gather_all_data + project_version + project_name + gather_authors + gather_commits + end + + def gather_authors + Command.new(self, 'git shortlog -se HEAD').run.each_line do |author| + name, email = author.split(/\t/)[1].strip.scan(/(.*)<(.*)>/).first.map(&:strip) + authors[email] = Author.new(name: name, email: email) + end + end + + def gather_commits + Command.new(self, 'git rev-list --pretty=format:"%h|%at|%ai|%aE" HEAD | grep -v commit').run.lines.each_with_index do |commit_line, i| + hash, stamp, date, author_email = commit_line.split('|').map(&:strip) + author = authors[author_email] + + date = DateTime.parse(date) + commit = commits[hash] = Commit.new(repo: self, hash: hash, stamp: stamp, date: date, author: author) + commit.gather_all_data + + activity.add_commit(commit) + author.add_commit(commit) + end + end + + def authors + @authors ||= {} + end + + def commits + @commits ||= {} + end + + def activity + @activity ||= Activity.new + end + + def project_version + @project_version ||= Command.new(self, 'git rev-parse --short HEAD').run + end + + def project_name + @project_name ||= Pathname(path).basename.to_s + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/git_data/short_stat.rb b/lib/git_stats/git_data/short_stat.rb new file mode 100644 index 000000000..757219dc2 --- /dev/null +++ b/lib/git_stats/git_data/short_stat.rb @@ -0,0 +1,23 @@ +module GitStats + module GitData + class ShortStat + attr_reader :commit, :files_changed, :insertions, :deletions + + def initialize(commit) + @commit = commit + calculate_stat + end + + def calculate_stat + stat_line = Command.new(commit.repo, "git show --shortstat --oneline #{commit.hash}").run.lines.to_a[1] + if stat_line.blank? + @files_changed, @insertions, @deletions = 0 + else + @files_changed = stat_line[/(\d+) files? changed/, 1].to_i + @insertions = stat_line[/(\d+) insertions?/, 1].to_i + @deletions = stat_line[/(\d+) deletions?/, 1].to_i + end + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/stats_view/template.rb b/lib/git_stats/stats_view/template.rb new file mode 100644 index 000000000..ce1e16b5e --- /dev/null +++ b/lib/git_stats/stats_view/template.rb @@ -0,0 +1,15 @@ +module GitStats + module StatsView + class Template + def initialize(name, layout) + @name = name + @layout = layout + @template = Tilt.new("templates/#@name.haml") + end + + def render(data) + @layout.render { @template.render(data) } + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/stats_view/view.rb b/lib/git_stats/stats_view/view.rb new file mode 100644 index 000000000..e1993e606 --- /dev/null +++ b/lib/git_stats/stats_view/view.rb @@ -0,0 +1,17 @@ +module GitStats + module StatsView + class View + def self.render_all(data, out_path) + prepare_assets(out_path) + + layout = Tilt.new("templates/layout.haml") + output = Template.new('index', layout).render(data) + File.open("#{out_path}/index.html", 'w') { |f| f.write output } + end + + def self.prepare_assets(out_path) + FileUtils.cp_r('templates/assets', out_path) + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/stats_view/view_data.rb b/lib/git_stats/stats_view/view_data.rb new file mode 100644 index 000000000..937ad3319 --- /dev/null +++ b/lib/git_stats/stats_view/view_data.rb @@ -0,0 +1,36 @@ +module GitStats + module StatsView + class ViewData + include ActionView::Helpers::TagHelper + include LazyHighCharts::LayoutHelper + + attr_reader :repo + + def initialize(repo) + @repo = repo + end + + def generate_charts + @h = LazyHighCharts::HighChart.new('graph') do |f| + f.chart(type: "column") + f.title(text: "Commits") + f.xAxis(categories: Date::ABBR_DAYNAMES) + f.yAxis(min: 0, title: {text: 'Commits'}) + f.legend( + layout: 'vertical', + backgroundColor: '#FFFFFF', + align: 'left', + verticalAlign: 'top', + x: 100, + y: 70, + floating: true, + shadow: true + ) + repo.authors.each do |email, author| + f.series(:name => email, :data => author.activity.by_wday.inject([]) { |acc, (k, v)| acc[k] = v; acc }) + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/git_stats/view/template.rb b/lib/git_stats/view/template.rb deleted file mode 100644 index 3c6c9592a..000000000 --- a/lib/git_stats/view/template.rb +++ /dev/null @@ -1,11 +0,0 @@ -class GitStats::Template - def initialize(name, layout) - @name = name - @layout = layout - @template = Tilt.new("templates/#@name.haml") - end - - def render(data) - @layout.render { @template.render(data) } - end -end \ No newline at end of file diff --git a/lib/git_stats/view/view.rb b/lib/git_stats/view/view.rb deleted file mode 100644 index 34896003f..000000000 --- a/lib/git_stats/view/view.rb +++ /dev/null @@ -1,13 +0,0 @@ -class GitStats::View - def self.render_all(data, out_path) - prepare_assets(out_path) - - layout = Tilt.new("templates/layout.haml") - output = GitStats::Template.new('index', layout).render(data) - File.open("#{out_path}/index.html", 'w') { |f| f.write output } - end - - def self.prepare_assets(out_path) - FileUtils.cp_r('templates/assets', out_path) - end -end \ No newline at end of file diff --git a/lib/git_stats/view/view_data.rb b/lib/git_stats/view/view_data.rb deleted file mode 100644 index 4cc5d08e6..000000000 --- a/lib/git_stats/view/view_data.rb +++ /dev/null @@ -1,32 +0,0 @@ -class GitStats::ViewData - include ActionView::Helpers::TagHelper - include LazyHighCharts::LayoutHelper - - attr_reader :git_data - - def initialize(git_data) - @git_data = git_data - end - - def generate_charts - @h = LazyHighCharts::HighChart.new('graph') do |f| - f.chart(type: "column") - f.title(text: "Commits") - f.xAxis(categories: Date::ABBR_DAYNAMES) - f.yAxis(min: 0, title: {text: 'Commits'}) - f.legend( - layout: 'vertical', - backgroundColor: '#FFFFFF', - align: 'left', - verticalAlign: 'top', - x: 100, - y: 70, - floating: true, - shadow: true - ) - git_data.authors.each do |email, author| - f.series(:name => email, :data => author.activity.by_wday) - end - end - end -end \ No newline at end of file diff --git a/templates/index.haml b/templates/index.haml index 282309751..9c2c20035 100644 --- a/templates/index.haml +++ b/templates/index.haml @@ -1,4 +1,4 @@ -%p= git_data.project_name -%p= git_data.project_version +%p= repo.project_name +%p= repo.project_version = high_chart("my_id", @h) \ No newline at end of file