autoproj-stats 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bddd0ba07ea493877bd8a87c2e34bd1b413e976c
4
+ data.tar.gz: 5204ab40ecfe8b5fe447f61ea8ad0d3d16cfa846
5
+ SHA512:
6
+ metadata.gz: 4402d0f9d750e8d0f8e7393773585551863648d3c54a13642b572baeacc21bce628f341d50979deec37e4300b69fadbdf8e1a08f37e6f98f2ba486385dec9ad0
7
+ data.tar.gz: b2ede3bcb23664ed294b431da28ef2f7b444c1309250ab0390e1939ac3cff4f678b9adc09f1c2414e350f23252e2b28357555cfd7f77ee711e988811411ebe58
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.2
4
+ before_install: gem install bundler -v 1.10.6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in autoproj-stats.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Sylvain Joyeux
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,98 @@
1
+ # Autoproj::Stats
2
+
3
+ An Autoproj plugin to generate authorship/copyright statistics
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'autoproj-stats'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install autoproj-stats
20
+
21
+ ## Usage
22
+
23
+ The plugin can be accessed from the autoproj command line with
24
+
25
+ ~~~
26
+ autoproj help stats
27
+ ~~~
28
+
29
+ In its simplest form, it's going to compute authorship information for all
30
+ supported VCS types in the autoproj installation, and display summary
31
+ information at the end.
32
+
33
+ ~~~
34
+ autoproj stats
35
+ ~~~
36
+
37
+ The statistics can be saved in a text file with --save
38
+
39
+ ~~~
40
+ autoproj stats --save=stats.txt
41
+ ~~~
42
+
43
+ The first thing you may want to improve is sanitizing author name(s). This is
44
+ done by providing a YAML config file with an 'aliases' section. The method is
45
+ usually to comb through the list of names in the summary generated by autoproj
46
+ stats and replace dummy names by actual ones.
47
+
48
+ ~~~
49
+ aliases:
50
+ Name As Reported By Autoproj Stats: Actual Name
51
+ ~~~
52
+
53
+ This config file (let's call it stats.yml) can then be passed to autoproj stats
54
+ with the --config option
55
+
56
+ ~~~
57
+ autoproj stats --config=stats.yml
58
+ ~~~
59
+
60
+ Then, you probably want to add affiliation information (a.k.a. copyright
61
+ assignation). The simplest is to provide an institution/copyright holder name
62
+ per author name:
63
+
64
+ ~~~
65
+ affiliations:
66
+ Author Name: Company Name
67
+ ~~~
68
+
69
+ However, some authors change affiliations over time, in which case you provide a
70
+ list of company and start dates:
71
+
72
+ ~~~
73
+ affiliations:
74
+ Author Name:
75
+ - [Company1, 2008-02-01]
76
+ - [Company2, 2012-03-01]
77
+ ~~~
78
+
79
+ Finally, you can provide per-package license information. No way to give
80
+ fine-grained stuff there.
81
+
82
+ ~~~
83
+ licenses:
84
+ package_name: License Name
85
+ ~~~
86
+
87
+ ## Development
88
+
89
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake test` to run the tests.
90
+
91
+ ## Contributing
92
+
93
+ Bug reports and pull requests are welcome on GitHub at https://github.com/doudou/autoproj-stats.
94
+
95
+ ## License
96
+
97
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
98
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'autoproj/stats/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "autoproj-stats"
8
+ spec.version = Autoproj::Stats::VERSION
9
+ spec.authors = ["Sylvain Joyeux"]
10
+ spec.email = ["sylvain.joyeux@m4x.org"]
11
+
12
+ spec.summary = "authorship and copyright statistics for an autoproj workspace"
13
+ spec.description =<<-EOD
14
+ This autoproj plugin adds the 'stats' subcommand to autoproj, which allows to
15
+ compute per-package and aggregated statistics information about authorship, for
16
+ all packages within an autoproj workspace
17
+ EOD
18
+ spec.homepage = "https://github.com/doudou/autoproj-stats"
19
+ spec.license = "MIT"
20
+
21
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "autoproj", "~> 2.0"
27
+ spec.add_dependency "tty-table"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.10"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "minitest", '>= 5.0', '~> 5.0'
32
+ end
@@ -0,0 +1,13 @@
1
+ require 'autoproj/cli/stats'
2
+
3
+ class Autoproj::CLI::Main
4
+ desc 'stats [PKG]', 'compute ownership information about the given package(s)'
5
+ option 'config', desc: 'configuration file that specifies name mappings and copyright info'
6
+ option 'parallel', desc: 'compute stats with that many parallel processes',
7
+ type: :numeric, default: 1
8
+ option 'save', desc: 'save in the provided file instead of outputting on STDOUT'
9
+ def stats(*packages)
10
+ run_autoproj_cli(:stats, :Stats, Hash[], *Array(packages))
11
+ end
12
+ end
13
+
@@ -0,0 +1,134 @@
1
+ require 'autoproj'
2
+ require 'autoproj/cli/inspection_tool'
3
+ require 'autoproj/ops/stats'
4
+ require 'concurrent'
5
+
6
+ require 'tty-table'
7
+
8
+ module Autoproj
9
+ module CLI
10
+ class Stats < InspectionTool
11
+ def run(user_selection, options = Hash.new)
12
+ initialize_and_load
13
+ source_packages, * =
14
+ finalize_setup(user_selection,
15
+ ignore_non_imported_packages: true)
16
+ source_packages = source_packages.map { |pkg_name| ws.manifest.find_autobuild_package(pkg_name) }
17
+
18
+ config = YAML.load(File.read(options[:config]))
19
+ excluded = (config['exclude'] || Set.new).to_set
20
+ sanitizer = Autoproj::Stats::Sanitizer.new
21
+ if options[:config]
22
+ sanitizer.load(options[:config])
23
+ end
24
+
25
+ total_sloc = 0
26
+ overall_per_author = Hash.new
27
+ overall_per_copyright = Hash.new
28
+ overall_copyright_per_license = Hash.new
29
+
30
+ ops = Ops::Stats.new(sanitizer: sanitizer)
31
+ stats = ops.process(source_packages, parallel: options[:parallel])
32
+ per_package_table = TTY::Table.new
33
+ stats.each do |pkg, pkg_stats|
34
+ next if excluded.include?(pkg.name)
35
+ row_count = [1, pkg_stats.authors.size, pkg_stats.copyright.size].max
36
+
37
+ # Compute per-package info
38
+ sloc = pkg_stats.sloc
39
+ author_info = line_count_summary(pkg_stats.authors, sloc)
40
+ copyright_info = line_count_summary(pkg_stats.copyright, sloc)
41
+ license = sanitizer.license_of(pkg)
42
+
43
+ # Compute aggregated info
44
+ total_sloc += sloc
45
+ overall_per_author.merge!(pkg_stats.authors) { |_, v1, v2| v1 + v2 }
46
+ overall_per_copyright.merge!(pkg_stats.copyright) { |_, v1, v2| v1 + v2 }
47
+ copyrights_for_this_license =
48
+ (overall_copyright_per_license[license || "Unknown"] ||= Hash.new)
49
+ copyrights_for_this_license.merge!(pkg_stats.copyright) { |_, v1, v2| v1 + v2 }
50
+
51
+ Array.new(row_count).zip([pkg.name], [sloc], [license], *author_info, *copyright_info) do |line|
52
+ per_package_table << line[1..-1].map { |v| v || '' }
53
+ end
54
+ end
55
+
56
+ no_license = stats.find_all do |pkg, pkg_stats|
57
+ !sanitizer.license_of(pkg)
58
+ end
59
+
60
+ copyright_per_license_table = TTY::Table.new
61
+ overall_copyright_per_license.sort_by(&:first).each do |license_name, copyrights|
62
+ first_col = Array.new(copyrights.size)
63
+ first_col[0] = license_name
64
+ copyright_info = line_count_summary(copyrights, total_sloc)
65
+ first_col.zip(*copyright_info) do |line|
66
+ copyright_per_license_table << line.map { |v| v || "" }
67
+ end
68
+ end
69
+
70
+ no_stats = (source_packages.to_set - stats.keys.to_set)
71
+ puts "could not compute stats for #{no_stats.size} packages: #{no_stats.map(&:name).sort.join(", ")}"
72
+ puts "#{no_license.size} packages without known license: #{no_license.map { |pkg, _| pkg.name }.sort.join(", ")}"
73
+ puts "#{stats.size} Packages"
74
+ puts "#{total_sloc} SLOC counted"
75
+ puts "#{overall_per_author.size} unique authors"
76
+
77
+ begin
78
+ io = if path = options[:save]
79
+ File.open(path, 'w')
80
+ else
81
+ STDOUT
82
+ end
83
+
84
+ io.puts "== Overall stats per author (sorted by contribution)"
85
+ author_names, *author_info = line_count_summary(overall_per_author, total_sloc)
86
+ io.puts TTY::Table.new(author_names.zip(*author_info)).render(:ascii)
87
+ io.puts
88
+ io.puts "== Overall stats per author (sorted alphabetically)"
89
+ author_names, *author_info = line_count_summary(overall_per_author, total_sloc, &:first)
90
+ io.puts TTY::Table.new(author_names.zip(*author_info)).render(:ascii)
91
+ io.puts
92
+ io.puts "== Overall stats per copyright"
93
+ copyright_names, *copyright_info = line_count_summary(overall_per_copyright, total_sloc)
94
+ io.puts TTY::Table.new(copyright_names.zip(*copyright_info)).render(:ascii)
95
+ io.puts
96
+ io.puts "== Breakdown of copyright per license"
97
+ io.puts copyright_per_license_table.render(:ascii)
98
+ io.puts
99
+ io.puts "== Per-package stats"
100
+ io.puts per_package_table.render(:ascii)
101
+ ensure
102
+ if io && io != STDOUT
103
+ io.close
104
+ end
105
+ end
106
+ end
107
+
108
+ def line_count_summary(count_per_category, sloc, reverse: true, &block)
109
+ ordered =
110
+ if block_given?
111
+ count_per_category.sort_by(&block)
112
+ else
113
+ count_per_category.sort_by { |_, count| count }
114
+ end
115
+ ordered = ordered.reverse if reverse
116
+ names = ordered.map(&:first)
117
+ counts = ordered.map(&:last)
118
+ ratios = count_to_ratios(counts, sloc)
119
+ return names, counts, ratios
120
+ end
121
+
122
+ def count_to_ratios(counts, total)
123
+ counts.map do |v|
124
+ ratio = (Float(v) / total)
125
+ if ratio < 0.01
126
+ "< 1%"
127
+ else
128
+ "#{Integer(ratio * 100)}%"
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,55 @@
1
+ require 'autoproj/stats'
2
+
3
+ module Autoproj
4
+ module Ops
5
+ # Computes per-package statistics
6
+ class Stats
7
+ attr_reader :sanitizer
8
+
9
+ def initialize(sanitizer: Autoproj::Stats::Sanitizer.new)
10
+ @sanitizer = sanitizer
11
+ end
12
+
13
+ def process(packages, parallel: 1)
14
+ executor = Concurrent::FixedThreadPool.new(parallel, max_length: 0)
15
+ result = Hash.new
16
+
17
+ futures = packages.map do |pkg|
18
+ [pkg, Concurrent::Future.execute(executor: executor) { compute_package_stats(pkg) }]
19
+ end
20
+ futures.inject(Hash.new) do |h, (pkg, future)|
21
+ if stats = future.value
22
+ h[pkg] = stats
23
+ elsif future.reason.kind_of?(Exception)
24
+ raise future.reason
25
+ else
26
+ pkg.error "%s: failed, #{future.reason}"
27
+ end
28
+ h
29
+ end
30
+
31
+ ensure
32
+ executor.shutdown
33
+ executor.wait_for_termination
34
+ end
35
+
36
+ def compute_package_stats(pkg)
37
+ if pkg.importer
38
+ if stats_generator = find_generator_for_importer(pkg.importer)
39
+ stats_generator.(pkg)
40
+ else
41
+ Autoproj.warn "no stats generator for #{pkg.name} (#{pkg.importer.class})"
42
+ end
43
+ else
44
+ Autoproj.warn "no importer for #{pkg.name}"
45
+ end
46
+ end
47
+
48
+ def find_generator_for_importer(importer)
49
+ if importer.class == Autobuild::Git
50
+ return Autoproj::Stats::Git.new(sanitizer: sanitizer)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ require 'autoproj/stats/version'
2
+ require 'autoproj/stats/sanitizer'
3
+ require 'autoproj/stats/sloc_counter'
4
+ require 'autoproj/stats/package_stats'
5
+ require 'autoproj/stats/git'
@@ -0,0 +1,62 @@
1
+ module Autoproj
2
+ module Stats
3
+ # Statistic generator for git-based packages
4
+ class Git
5
+ attr_reader :sloc_counter
6
+ attr_reader :sanitizer
7
+ def initialize(sloc_counter: SLOCCounter.new, sanitizer: Sanitizer.new)
8
+ @sloc_counter = sloc_counter
9
+ @sanitizer = sanitizer
10
+ end
11
+
12
+ def call(pkg)
13
+ pkg.progress_start "processing %s", done_message: "processed %s" do
14
+ importer = pkg.importer
15
+ entries = importer.run_git(pkg, 'ls-tree', '-r', 'HEAD', encoding: 'UTF-8')
16
+
17
+ progress_counter = 0
18
+ entries.inject(PackageStats.new) do |aggregate, line|
19
+ mode, type, sha, path = line.split(/\s+/)
20
+ if file_counter = sloc_counter.find_counter_for_path(path)
21
+ aggregate += process_file(pkg, path, sloc: file_counter)
22
+ else
23
+ pkg.warn "%s: no SLOC count counter available for #{path}"
24
+ end
25
+ progress_counter += 1
26
+ pkg.progress "processing %s (#{progress_counter}/#{entries.size})"
27
+ aggregate
28
+ end
29
+ end
30
+ end
31
+
32
+ def process_file(pkg, path, sloc: proc { true })
33
+ blamed = pkg.importer.run_git(pkg, 'blame', '-w', '-C', '-C', '-C', '-M', '--minimal', 'HEAD', '--', path, encoding: 'UTF-8')
34
+
35
+ authors = Hash.new(0)
36
+ copyrights = Hash.new(0)
37
+
38
+ line_matcher = Regexp.new(/^\^?[0-9a-f]+.*\((.*) (\d{4})-(\d{2})-(\d{2}).*\d+\) (.*)$/)
39
+ blamed.each do |line|
40
+ line = line.encode('UTF-8', invalid: :replace, undef: :replace)
41
+ if m = line_matcher.match(line)
42
+ _, name, y, m, d, code = *m
43
+ code = code.strip
44
+ next if code.empty? || !sloc.(code)
45
+
46
+ name = sanitizer.sanitize_author_name(name.strip)
47
+ copyright = sanitizer.compute_copyright_of(
48
+ name, Date.new(y.to_i, m.to_i, d.to_i))
49
+ authors[name] += 1
50
+ copyrights[copyright] += 1
51
+ else
52
+ raise "cannot match #{line}"
53
+ end
54
+ end
55
+
56
+ sloc = authors.values.inject(0, &:+)
57
+ PackageStats.new(sloc, authors, copyrights)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
@@ -0,0 +1,20 @@
1
+ module Autoproj
2
+ module Stats
3
+ class PackageStats
4
+ attr_reader :sloc
5
+ attr_reader :authors
6
+ attr_reader :copyright
7
+ def initialize(sloc = 0, authors = Hash.new, copyright = Hash.new)
8
+ @sloc, @authors, @copyright = sloc, authors, copyright
9
+ end
10
+
11
+ def +(other)
12
+ PackageStats.new(
13
+ sloc + other.sloc,
14
+ authors.merge(other.authors) { |_, v1, v2| v1 + v2 },
15
+ copyright.merge(other.copyright) { |_, v1, v2| v1 + v2 })
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,59 @@
1
+ module Autoproj
2
+ module Stats
3
+ # Class passed to the stats generators to sanitize names and compute
4
+ # copyright
5
+ class Sanitizer
6
+ attr_reader :aliases
7
+ attr_reader :simple_copyrights
8
+ attr_reader :timeline_affiliations
9
+ attr_reader :licenses
10
+
11
+ def initialize(aliases: Hash.new)
12
+ @simple_copyrights = Hash.new
13
+ @timeline_affiliations = Hash.new
14
+ @aliases = aliases
15
+ end
16
+
17
+ def load(path)
18
+ config = YAML.load(File.read(path))
19
+ @aliases = config['aliases']
20
+ @licenses = config['licenses'] || Hash.new
21
+ if affiliations = config['affiliations']
22
+ affiliations.each do |name, entry|
23
+ if entry.respond_to?(:to_str)
24
+ simple_copyrights[name] = entry
25
+ else
26
+ timeline_affiliations[name] = entry.sort_by(&:last)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def license_of(pkg)
33
+ licenses[pkg.name]
34
+ end
35
+
36
+ def sanitize_author_name(name)
37
+ aliases[name] || name
38
+ end
39
+
40
+ def compute_copyright_of(author_name, date)
41
+ if affiliation = simple_copyrights[author_name]
42
+ affiliation
43
+ elsif timeline = timeline_affiliations[author_name]
44
+ timeline.inject("Unknown (#{author_name})") do |aff, (new_aff, start_date)|
45
+ if date < start_date
46
+ return aff
47
+ else
48
+ new_aff
49
+ end
50
+ end
51
+ else
52
+ "Unknown (#{author_name})"
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+
@@ -0,0 +1,21 @@
1
+ module Autoproj
2
+ module Stats
3
+ class SLOCCounter
4
+ def find_counter_for_path(path)
5
+ case path
6
+ when /\.rb$/, /\.orogen$/
7
+ lambda { |line| line !~ /^(?:#|end)$/ }
8
+ when /\.py$/
9
+ lambda { |line| line !~ /^#/ }
10
+ when /\.(?:[ch]pp|cc?|hh?)$/
11
+ lambda { |line| line !~ /^(?:[{}]+|\/\/)$/ }
12
+ when /CMakeLists.txt/, /\.cmake$/
13
+ lambda { |line| line !~ /^#/ }
14
+ when /\.sdf$/
15
+ lambda { |line| true }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,5 @@
1
+ module Autoproj
2
+ module Stats
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: autoproj-stats
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sylvain Joyeux
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: autoproj
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-table
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '5.0'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '5.0'
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.0'
89
+ description: |
90
+ This autoproj plugin adds the 'stats' subcommand to autoproj, which allows to
91
+ compute per-package and aggregated statistics information about authorship, for
92
+ all packages within an autoproj workspace
93
+ email:
94
+ - sylvain.joyeux@m4x.org
95
+ executables: []
96
+ extensions: []
97
+ extra_rdoc_files: []
98
+ files:
99
+ - ".gitignore"
100
+ - ".travis.yml"
101
+ - Gemfile
102
+ - LICENSE.txt
103
+ - README.md
104
+ - Rakefile
105
+ - autoproj-stats.gemspec
106
+ - lib/autoproj-stats.rb
107
+ - lib/autoproj/cli/stats.rb
108
+ - lib/autoproj/ops/stats.rb
109
+ - lib/autoproj/stats.rb
110
+ - lib/autoproj/stats/git.rb
111
+ - lib/autoproj/stats/package_stats.rb
112
+ - lib/autoproj/stats/sanitizer.rb
113
+ - lib/autoproj/stats/sloc_counter.rb
114
+ - lib/autoproj/stats/version.rb
115
+ homepage: https://github.com/doudou/autoproj-stats
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.2.3
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: authorship and copyright statistics for an autoproj workspace
139
+ test_files: []
140
+ has_rdoc: