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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +98 -0
- data/Rakefile +10 -0
- data/autoproj-stats.gemspec +32 -0
- data/lib/autoproj-stats.rb +13 -0
- data/lib/autoproj/cli/stats.rb +134 -0
- data/lib/autoproj/ops/stats.rb +55 -0
- data/lib/autoproj/stats.rb +5 -0
- data/lib/autoproj/stats/git.rb +62 -0
- data/lib/autoproj/stats/package_stats.rb +20 -0
- data/lib/autoproj/stats/sanitizer.rb +59 -0
- data/lib/autoproj/stats/sloc_counter.rb +21 -0
- data/lib/autoproj/stats/version.rb +5 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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,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
|
+
|
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:
|