ruby-maat 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.
- checksums.yaml +7 -0
- data/.commitlintrc.json +44 -0
- data/.mailmap +3 -0
- data/.overcommit.yml +77 -0
- data/.release-please-config.json +33 -0
- data/.release-please-manifest.json +3 -0
- data/.rspec +3 -0
- data/.rubocop.yml +48 -0
- data/CHANGELOG.md +46 -0
- data/CI_CD_SETUP.md +180 -0
- data/CLAUDE.md +130 -0
- data/Dockerfile +40 -0
- data/README.md +444 -0
- data/README_RUBY.md +300 -0
- data/RELEASE_PLEASE_SETUP.md +198 -0
- data/RUBY_MAAT.md +227 -0
- data/Rakefile +12 -0
- data/doc/imgs/abs_churn_sample.png +0 -0
- data/doc/imgs/code_age_sample.png +0 -0
- data/doc/imgs/coupling_sample.png +0 -0
- data/doc/imgs/crime_cover.jpg +0 -0
- data/doc/imgs/tree_map_sample.png +0 -0
- data/doc/intro.md +3 -0
- data/exe/ruby-maat +6 -0
- data/lib/ruby_maat/analysis/authors.rb +47 -0
- data/lib/ruby_maat/analysis/base_analysis.rb +70 -0
- data/lib/ruby_maat/analysis/churn.rb +255 -0
- data/lib/ruby_maat/analysis/code_age.rb +53 -0
- data/lib/ruby_maat/analysis/commit_messages.rb +58 -0
- data/lib/ruby_maat/analysis/communication.rb +56 -0
- data/lib/ruby_maat/analysis/effort.rb +150 -0
- data/lib/ruby_maat/analysis/entities.rb +40 -0
- data/lib/ruby_maat/analysis/identity.rb +12 -0
- data/lib/ruby_maat/analysis/logical_coupling.rb +134 -0
- data/lib/ruby_maat/analysis/sum_of_coupling.rb +43 -0
- data/lib/ruby_maat/analysis/summary.rb +43 -0
- data/lib/ruby_maat/app.rb +143 -0
- data/lib/ruby_maat/change_record.rb +47 -0
- data/lib/ruby_maat/cli.rb +187 -0
- data/lib/ruby_maat/dataset.rb +205 -0
- data/lib/ruby_maat/groupers/layer_grouper.rb +67 -0
- data/lib/ruby_maat/groupers/team_mapper.rb +51 -0
- data/lib/ruby_maat/groupers/time_grouper.rb +70 -0
- data/lib/ruby_maat/output/csv_output.rb +65 -0
- data/lib/ruby_maat/parsers/base_parser.rb +63 -0
- data/lib/ruby_maat/parsers/git2_parser.rb +72 -0
- data/lib/ruby_maat/parsers/git_parser.rb +66 -0
- data/lib/ruby_maat/parsers/mercurial_parser.rb +64 -0
- data/lib/ruby_maat/parsers/perforce_parser.rb +77 -0
- data/lib/ruby_maat/parsers/svn_parser.rb +76 -0
- data/lib/ruby_maat/parsers/tfs_parser.rb +103 -0
- data/lib/ruby_maat/version.rb +5 -0
- data/lib/ruby_maat.rb +44 -0
- metadata +143 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
module RubyMaat
|
5
|
+
module Parsers
|
6
|
+
# Git2 parser - preferred Git parser (more tolerant and faster)
|
7
|
+
#
|
8
|
+
# Input: git log --all --numstat --date=short --pretty=format:'--%h--%ad--%aN' --no-renames --after=YYYY-MM-DD
|
9
|
+
#
|
10
|
+
# Sample format:
|
11
|
+
# --586b4eb--2015-06-15--Adam Tornhill
|
12
|
+
# 35 0 src/code_maat/mining/vcs.clj
|
13
|
+
# 2 1 test/file.rb
|
14
|
+
#
|
15
|
+
# --abc123--2015-06-16--Jane Doe
|
16
|
+
# 10 5 lib/example.rb
|
17
|
+
class Git2Parser < BaseParser
|
18
|
+
COMMIT_SEPARATOR = /^--([a-z0-9]+)--(\d{4}-\d{2}-\d{2})--(.+)$/
|
19
|
+
CHANGE_PATTERN = /^(-|\d+)\s+(-|\d+)\s+(.*)$/
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def parse_content(content)
|
24
|
+
records = []
|
25
|
+
current_commit = nil
|
26
|
+
|
27
|
+
content.each_line do |line|
|
28
|
+
line.strip!
|
29
|
+
next if line.empty?
|
30
|
+
|
31
|
+
if (commit_match = line.match(COMMIT_SEPARATOR))
|
32
|
+
current_commit = {
|
33
|
+
revision: commit_match[1],
|
34
|
+
date: parse_date(commit_match[2]),
|
35
|
+
author: commit_match[3].strip
|
36
|
+
}
|
37
|
+
elsif current_commit && (change_match = line.match(CHANGE_PATTERN))
|
38
|
+
added = clean_numstat(change_match[1])
|
39
|
+
deleted = clean_numstat(change_match[2])
|
40
|
+
file = change_match[3].strip
|
41
|
+
|
42
|
+
next if file.empty? || file == File::NULL
|
43
|
+
next if added.nil? && deleted.nil?
|
44
|
+
|
45
|
+
records << ChangeRecord.new(
|
46
|
+
entity: file,
|
47
|
+
author: current_commit[:author],
|
48
|
+
date: current_commit[:date],
|
49
|
+
revision: current_commit[:revision],
|
50
|
+
loc_added: added,
|
51
|
+
loc_deleted: deleted
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
records
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_date(date_str)
|
62
|
+
Date.parse(date_str)
|
63
|
+
rescue Date::Error
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
def clean_numstat(value)
|
68
|
+
(value == "-") ? nil : value.to_i
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyMaat
|
4
|
+
module Parsers
|
5
|
+
# Legacy Git parser for backward compatibility
|
6
|
+
#
|
7
|
+
# Input: git log --pretty=format:'[%h] %aN %ad %s' --date=short --numstat --after=YYYY-MM-DD
|
8
|
+
#
|
9
|
+
# Sample format:
|
10
|
+
# [586b4eb] Adam Tornhill 2015-06-15 Add new feature
|
11
|
+
# 35 0 src/code_maat/mining/vcs.clj
|
12
|
+
# 2 1 test/file.rb
|
13
|
+
#
|
14
|
+
# [abc123] Jane Doe 2015-06-16 Fix bug in parser
|
15
|
+
# 10 5 lib/example.rb
|
16
|
+
class GitParser < BaseParser
|
17
|
+
COMMIT_PATTERN = /^\[([a-f0-9]+)\]\s+(.+?)\s+(\d{4}-\d{2}-\d{2})\s+(.*)$/
|
18
|
+
CHANGE_PATTERN = /^(\d+|-)\s+(\d+|-)\s+(.+)$/
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def parse_content(content)
|
23
|
+
entries = []
|
24
|
+
current_commit = nil
|
25
|
+
|
26
|
+
content.each_line do |line|
|
27
|
+
line = line.chomp
|
28
|
+
|
29
|
+
# Skip empty lines
|
30
|
+
next if line.strip.empty?
|
31
|
+
|
32
|
+
if (commit_match = line.match(COMMIT_PATTERN))
|
33
|
+
# New commit header
|
34
|
+
current_commit = {
|
35
|
+
revision: commit_match[1],
|
36
|
+
author: commit_match[2].strip,
|
37
|
+
date: parse_date(commit_match[3]),
|
38
|
+
message: commit_match[4].strip
|
39
|
+
}
|
40
|
+
elsif current_commit && (change_match = line.match(CHANGE_PATTERN))
|
41
|
+
# File change line
|
42
|
+
added = clean_numstat(change_match[1])
|
43
|
+
deleted = clean_numstat(change_match[2])
|
44
|
+
file = change_match[3].strip
|
45
|
+
|
46
|
+
# Skip empty or invalid file names
|
47
|
+
next if file.empty? || file == File::NULL
|
48
|
+
|
49
|
+
entries << ChangeRecord.new(
|
50
|
+
entity: file,
|
51
|
+
author: current_commit[:author],
|
52
|
+
date: current_commit[:date],
|
53
|
+
revision: current_commit[:revision],
|
54
|
+
message: current_commit[:message],
|
55
|
+
loc_added: added,
|
56
|
+
loc_deleted: deleted
|
57
|
+
)
|
58
|
+
end
|
59
|
+
# Ignore unrecognized lines
|
60
|
+
end
|
61
|
+
|
62
|
+
entries
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyMaat
|
4
|
+
module Parsers
|
5
|
+
# Mercurial parser
|
6
|
+
#
|
7
|
+
# Input: hg log --template "rev: {rev} author: {author} date: {date|shortdate} files:\n{files %'{file}\n'}\n" --date ">YYYY-MM-DD"
|
8
|
+
#
|
9
|
+
# Sample format:
|
10
|
+
# rev: 123 author: John Doe date: 2015-06-15 files:
|
11
|
+
# src/main.py
|
12
|
+
# test/test_main.py
|
13
|
+
#
|
14
|
+
# rev: 124 author: Jane Smith date: 2015-06-16 files:
|
15
|
+
# lib/helper.py
|
16
|
+
class MercurialParser < BaseParser
|
17
|
+
ENTRY_PATTERN = /^rev:\s+(\d+)\s+author:\s+(.+?)\s+date:\s+(\d{4}-\d{2}-\d{2})\s+files:$/
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def parse_content(content)
|
22
|
+
entries = []
|
23
|
+
current_commit = nil
|
24
|
+
in_files_section = false
|
25
|
+
|
26
|
+
content.each_line do |line|
|
27
|
+
line = line.chomp
|
28
|
+
|
29
|
+
if line.strip.empty?
|
30
|
+
# Empty line marks end of current commit
|
31
|
+
current_commit = nil
|
32
|
+
in_files_section = false
|
33
|
+
next
|
34
|
+
end
|
35
|
+
|
36
|
+
if (commit_match = line.match(ENTRY_PATTERN))
|
37
|
+
# New commit header
|
38
|
+
current_commit = {
|
39
|
+
revision: commit_match[1],
|
40
|
+
author: commit_match[2].strip,
|
41
|
+
date: parse_date(commit_match[3])
|
42
|
+
}
|
43
|
+
in_files_section = true
|
44
|
+
elsif current_commit && in_files_section && !line.strip.empty?
|
45
|
+
# File line
|
46
|
+
file = line.strip
|
47
|
+
|
48
|
+
# Skip invalid file names
|
49
|
+
next if file.empty? || file == File::NULL
|
50
|
+
|
51
|
+
entries << ChangeRecord.new(
|
52
|
+
entity: file,
|
53
|
+
author: current_commit[:author],
|
54
|
+
date: current_commit[:date],
|
55
|
+
revision: current_commit[:revision]
|
56
|
+
)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
entries
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyMaat
|
4
|
+
module Parsers
|
5
|
+
# Perforce parser
|
6
|
+
#
|
7
|
+
# Input: p4 changes -s submitted -m 5000 //depot/project/... | cut -d ' ' -f 2 | xargs -I commitid -n1 sh -c 'p4 describe -s commitid | grep -v "^\s*$" && echo ""'
|
8
|
+
#
|
9
|
+
# Sample format:
|
10
|
+
# Change 12345 by jdoe@workspace on 2015/06/15 10:30:45
|
11
|
+
#
|
12
|
+
# Fix bug in parser
|
13
|
+
#
|
14
|
+
# Affected files ...
|
15
|
+
#
|
16
|
+
# ... //depot/project/src/main.java#2 edit
|
17
|
+
# ... //depot/project/test/test.java#1 add
|
18
|
+
class PerforceParser < BaseParser
|
19
|
+
CHANGE_PATTERN = %r{^Change\s+(\d+)\s+by\s+([^@]+)@\S+\s+on\s+(\d{4}/\d{2}/\d{2})}
|
20
|
+
FILE_PATTERN = /^\.\.\.\s+(.+?)#\d+\s+(\w+)/
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def parse_content(content)
|
25
|
+
entries = []
|
26
|
+
current_commit = nil
|
27
|
+
in_files_section = false
|
28
|
+
|
29
|
+
content.each_line do |line|
|
30
|
+
line = line.chomp
|
31
|
+
|
32
|
+
if (change_match = line.match(CHANGE_PATTERN))
|
33
|
+
# New changelist header
|
34
|
+
current_commit = {
|
35
|
+
revision: change_match[1],
|
36
|
+
author: change_match[2].strip,
|
37
|
+
date: parse_perforce_date(change_match[3])
|
38
|
+
}
|
39
|
+
in_files_section = false
|
40
|
+
elsif line.include?("Affected files")
|
41
|
+
# Start of files section
|
42
|
+
in_files_section = true
|
43
|
+
elsif current_commit && in_files_section && (file_match = line.match(FILE_PATTERN))
|
44
|
+
# File change line
|
45
|
+
file = file_match[1].strip
|
46
|
+
action = file_match[2]
|
47
|
+
|
48
|
+
# Skip deleted files for most analyses (they don't contribute to current state)
|
49
|
+
next if action == "delete"
|
50
|
+
next if file.empty?
|
51
|
+
|
52
|
+
entries << ChangeRecord.new(
|
53
|
+
entity: file,
|
54
|
+
author: current_commit[:author],
|
55
|
+
date: current_commit[:date],
|
56
|
+
revision: current_commit[:revision]
|
57
|
+
)
|
58
|
+
elsif line.strip.empty? && current_commit
|
59
|
+
# Empty line might end current changelist
|
60
|
+
# But we keep current_commit until we see a new one
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
entries
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def parse_perforce_date(date_str)
|
70
|
+
# Perforce date format: 2015/06/15
|
71
|
+
Date.strptime(date_str, "%Y/%m/%d")
|
72
|
+
rescue Date::Error => e
|
73
|
+
raise ArgumentError, "Invalid Perforce date format: #{date_str} (#{e.message})"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rexml/document"
|
4
|
+
|
5
|
+
module RubyMaat
|
6
|
+
module Parsers
|
7
|
+
# SVN parser for XML log files
|
8
|
+
#
|
9
|
+
# Input: svn log -v --xml > logfile.log -r {YYYYmmDD}:HEAD
|
10
|
+
#
|
11
|
+
# Sample XML format:
|
12
|
+
# <log>
|
13
|
+
# <logentry revision="12345">
|
14
|
+
# <author>jdoe</author>
|
15
|
+
# <date>2015-06-15T10:30:45.123456Z</date>
|
16
|
+
# <paths>
|
17
|
+
# <path action="M">/trunk/src/file.java</path>
|
18
|
+
# <path action="A">/trunk/test/test.java</path>
|
19
|
+
# </paths>
|
20
|
+
# <msg>Fix bug in parser</msg>
|
21
|
+
# </logentry>
|
22
|
+
# </log>
|
23
|
+
class SvnParser < BaseParser
|
24
|
+
protected
|
25
|
+
|
26
|
+
def parse_content(content)
|
27
|
+
entries = []
|
28
|
+
|
29
|
+
begin
|
30
|
+
doc = REXML::Document.new(content)
|
31
|
+
|
32
|
+
doc.elements.each("log/logentry") do |logentry|
|
33
|
+
revision = logentry.attributes["revision"]
|
34
|
+
author = logentry.elements["author"]&.text || "unknown"
|
35
|
+
date_text = logentry.elements["date"]&.text
|
36
|
+
message = logentry.elements["msg"]&.text || ""
|
37
|
+
|
38
|
+
next unless date_text && revision
|
39
|
+
|
40
|
+
date = parse_svn_date(date_text)
|
41
|
+
|
42
|
+
# Extract all path changes
|
43
|
+
logentry.elements.each("paths/path") do |path_element|
|
44
|
+
entity = path_element.text&.strip
|
45
|
+
path_element.attributes["action"]
|
46
|
+
|
47
|
+
next if entity.nil? || entity.empty?
|
48
|
+
|
49
|
+
entries << ChangeRecord.new(
|
50
|
+
entity: entity,
|
51
|
+
author: author,
|
52
|
+
date: date,
|
53
|
+
revision: revision,
|
54
|
+
message: message
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue REXML::ParseException => e
|
59
|
+
raise ArgumentError, "Invalid XML format in SVN log file: #{e.message}"
|
60
|
+
end
|
61
|
+
|
62
|
+
entries
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def parse_svn_date(date_str)
|
68
|
+
# SVN date format: 2015-06-15T10:30:45.123456Z
|
69
|
+
# Convert to Date object
|
70
|
+
DateTime.parse(date_str).to_date
|
71
|
+
rescue => e
|
72
|
+
raise ArgumentError, "Invalid SVN date format: #{date_str} (#{e.message})"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyMaat
|
4
|
+
module Parsers
|
5
|
+
# Team Foundation Server parser
|
6
|
+
#
|
7
|
+
# Input: tf hist /path/to/workspace /noprompt /format:detailed /recursive
|
8
|
+
#
|
9
|
+
# Sample format:
|
10
|
+
# Changeset: 12345
|
11
|
+
# User: DOMAIN\jdoe
|
12
|
+
# Date: Friday, January 15, 2016 1:12:35 PM
|
13
|
+
#
|
14
|
+
# Comment:
|
15
|
+
# Fix bug in parser
|
16
|
+
#
|
17
|
+
# Items:
|
18
|
+
# edit $/Project/src/main.cs
|
19
|
+
# add $/Project/test/test.cs
|
20
|
+
class TfsParser < BaseParser
|
21
|
+
CHANGESET_PATTERN = /^Changeset:\s+(\d+)/
|
22
|
+
USER_PATTERN = /^User:\s+(.+)/
|
23
|
+
DATE_PATTERN = /^Date:\s+(.+)/
|
24
|
+
ITEM_PATTERN = /^\s+(edit|add|delete)\s+(\S.+)/
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def parse_content(content)
|
29
|
+
entries = []
|
30
|
+
current_commit = nil
|
31
|
+
in_items_section = false
|
32
|
+
|
33
|
+
content.each_line do |line|
|
34
|
+
line = line.chomp
|
35
|
+
|
36
|
+
if (changeset_match = line.match(CHANGESET_PATTERN))
|
37
|
+
# New changeset
|
38
|
+
current_commit = {revision: changeset_match[1]}
|
39
|
+
in_items_section = false
|
40
|
+
elsif current_commit && (user_match = line.match(USER_PATTERN))
|
41
|
+
# User line
|
42
|
+
user = user_match[1].strip
|
43
|
+
# Remove domain prefix if present (DOMAIN\user -> user)
|
44
|
+
user = user.split("\\").last if user.include?("\\")
|
45
|
+
current_commit[:author] = user
|
46
|
+
elsif current_commit && (date_match = line.match(DATE_PATTERN))
|
47
|
+
# Date line
|
48
|
+
begin
|
49
|
+
current_commit[:date] = parse_tfs_date(date_match[1])
|
50
|
+
rescue ArgumentError
|
51
|
+
# Skip this changeset if we can't parse the date
|
52
|
+
current_commit = nil
|
53
|
+
next
|
54
|
+
end
|
55
|
+
elsif line.strip == "Items:"
|
56
|
+
# Start of items section
|
57
|
+
in_items_section = true
|
58
|
+
elsif current_commit && in_items_section && (item_match = line.match(ITEM_PATTERN))
|
59
|
+
# Item change line
|
60
|
+
action = item_match[1]
|
61
|
+
file = item_match[2].strip
|
62
|
+
|
63
|
+
# Skip deleted files and invalid paths
|
64
|
+
next if action == "delete"
|
65
|
+
next if file.empty? || file == "$/null"
|
66
|
+
|
67
|
+
# Ensure we have all required fields
|
68
|
+
next unless current_commit[:author] && current_commit[:date]
|
69
|
+
|
70
|
+
entries << ChangeRecord.new(
|
71
|
+
entity: file,
|
72
|
+
author: current_commit[:author],
|
73
|
+
date: current_commit[:date],
|
74
|
+
revision: current_commit[:revision]
|
75
|
+
)
|
76
|
+
elsif line.strip.empty?
|
77
|
+
# Empty line might separate sections
|
78
|
+
in_items_section = false if in_items_section
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
entries
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def parse_tfs_date(date_str)
|
88
|
+
# TFS date format: "Friday, January 15, 2016 1:12:35 PM"
|
89
|
+
# Note: The parser expects en-US locale format
|
90
|
+
begin
|
91
|
+
DateTime.strptime(date_str.strip, "%A, %B %d, %Y %I:%M:%S %p").to_date
|
92
|
+
rescue Date::Error
|
93
|
+
# Try alternative format without day of week
|
94
|
+
DateTime.strptime(date_str.strip, "%B %d, %Y %I:%M:%S %p").to_date
|
95
|
+
end
|
96
|
+
rescue Date::Error => e
|
97
|
+
raise ArgumentError,
|
98
|
+
"Invalid TFS date format: #{date_str}. Expected en-US locale format like " \
|
99
|
+
"'Friday, January 15, 2016 1:12:35 PM' (#{e.message})"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/ruby_maat.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "ruby_maat/version"
|
4
|
+
require_relative "ruby_maat/change_record"
|
5
|
+
require_relative "ruby_maat/dataset"
|
6
|
+
|
7
|
+
# Core parsers
|
8
|
+
require_relative "ruby_maat/parsers/base_parser"
|
9
|
+
require_relative "ruby_maat/parsers/git_parser"
|
10
|
+
require_relative "ruby_maat/parsers/git2_parser"
|
11
|
+
require_relative "ruby_maat/parsers/svn_parser"
|
12
|
+
require_relative "ruby_maat/parsers/mercurial_parser"
|
13
|
+
require_relative "ruby_maat/parsers/perforce_parser"
|
14
|
+
require_relative "ruby_maat/parsers/tfs_parser"
|
15
|
+
|
16
|
+
# Data processors
|
17
|
+
require_relative "ruby_maat/groupers/layer_grouper"
|
18
|
+
require_relative "ruby_maat/groupers/time_grouper"
|
19
|
+
require_relative "ruby_maat/groupers/team_mapper"
|
20
|
+
|
21
|
+
# Analysis modules (load before app.rb since app references them)
|
22
|
+
require_relative "ruby_maat/analysis/base_analysis"
|
23
|
+
require_relative "ruby_maat/analysis/authors"
|
24
|
+
require_relative "ruby_maat/analysis/entities"
|
25
|
+
require_relative "ruby_maat/analysis/logical_coupling"
|
26
|
+
require_relative "ruby_maat/analysis/churn"
|
27
|
+
require_relative "ruby_maat/analysis/effort"
|
28
|
+
require_relative "ruby_maat/analysis/communication"
|
29
|
+
require_relative "ruby_maat/analysis/code_age"
|
30
|
+
require_relative "ruby_maat/analysis/summary"
|
31
|
+
require_relative "ruby_maat/analysis/commit_messages"
|
32
|
+
require_relative "ruby_maat/analysis/sum_of_coupling"
|
33
|
+
require_relative "ruby_maat/analysis/identity"
|
34
|
+
|
35
|
+
# Output
|
36
|
+
require_relative "ruby_maat/output/csv_output"
|
37
|
+
|
38
|
+
# Main app and CLI (load after dependencies)
|
39
|
+
require_relative "ruby_maat/app"
|
40
|
+
require_relative "ruby_maat/cli"
|
41
|
+
|
42
|
+
module RubyMaat
|
43
|
+
class Error < StandardError; end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-maat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Tornhill
|
8
|
+
- Claude Code
|
9
|
+
- Bart Agapinan
|
10
|
+
bindir: exe
|
11
|
+
cert_chain: []
|
12
|
+
date: 2025-08-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: csv
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.2'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rexml
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '3.2'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '3.2'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rover-df
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0.3'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0.3'
|
56
|
+
description: Ruby Maat is a command line tool used to mine and analyze data from version-control
|
57
|
+
systems (VCS). This is a Ruby port of the original Clojure Code Maat.
|
58
|
+
email:
|
59
|
+
- bart@sonic.net
|
60
|
+
executables:
|
61
|
+
- ruby-maat
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- ".commitlintrc.json"
|
66
|
+
- ".mailmap"
|
67
|
+
- ".overcommit.yml"
|
68
|
+
- ".release-please-config.json"
|
69
|
+
- ".release-please-manifest.json"
|
70
|
+
- ".rspec"
|
71
|
+
- ".rubocop.yml"
|
72
|
+
- CHANGELOG.md
|
73
|
+
- CI_CD_SETUP.md
|
74
|
+
- CLAUDE.md
|
75
|
+
- Dockerfile
|
76
|
+
- README.md
|
77
|
+
- README_RUBY.md
|
78
|
+
- RELEASE_PLEASE_SETUP.md
|
79
|
+
- RUBY_MAAT.md
|
80
|
+
- Rakefile
|
81
|
+
- doc/imgs/abs_churn_sample.png
|
82
|
+
- doc/imgs/code_age_sample.png
|
83
|
+
- doc/imgs/coupling_sample.png
|
84
|
+
- doc/imgs/crime_cover.jpg
|
85
|
+
- doc/imgs/tree_map_sample.png
|
86
|
+
- doc/intro.md
|
87
|
+
- exe/ruby-maat
|
88
|
+
- lib/ruby_maat.rb
|
89
|
+
- lib/ruby_maat/analysis/authors.rb
|
90
|
+
- lib/ruby_maat/analysis/base_analysis.rb
|
91
|
+
- lib/ruby_maat/analysis/churn.rb
|
92
|
+
- lib/ruby_maat/analysis/code_age.rb
|
93
|
+
- lib/ruby_maat/analysis/commit_messages.rb
|
94
|
+
- lib/ruby_maat/analysis/communication.rb
|
95
|
+
- lib/ruby_maat/analysis/effort.rb
|
96
|
+
- lib/ruby_maat/analysis/entities.rb
|
97
|
+
- lib/ruby_maat/analysis/identity.rb
|
98
|
+
- lib/ruby_maat/analysis/logical_coupling.rb
|
99
|
+
- lib/ruby_maat/analysis/sum_of_coupling.rb
|
100
|
+
- lib/ruby_maat/analysis/summary.rb
|
101
|
+
- lib/ruby_maat/app.rb
|
102
|
+
- lib/ruby_maat/change_record.rb
|
103
|
+
- lib/ruby_maat/cli.rb
|
104
|
+
- lib/ruby_maat/dataset.rb
|
105
|
+
- lib/ruby_maat/groupers/layer_grouper.rb
|
106
|
+
- lib/ruby_maat/groupers/team_mapper.rb
|
107
|
+
- lib/ruby_maat/groupers/time_grouper.rb
|
108
|
+
- lib/ruby_maat/output/csv_output.rb
|
109
|
+
- lib/ruby_maat/parsers/base_parser.rb
|
110
|
+
- lib/ruby_maat/parsers/git2_parser.rb
|
111
|
+
- lib/ruby_maat/parsers/git_parser.rb
|
112
|
+
- lib/ruby_maat/parsers/mercurial_parser.rb
|
113
|
+
- lib/ruby_maat/parsers/perforce_parser.rb
|
114
|
+
- lib/ruby_maat/parsers/svn_parser.rb
|
115
|
+
- lib/ruby_maat/parsers/tfs_parser.rb
|
116
|
+
- lib/ruby_maat/version.rb
|
117
|
+
homepage: https://github.com/viamin/ruby-maat
|
118
|
+
licenses:
|
119
|
+
- GPL-3.0
|
120
|
+
metadata:
|
121
|
+
allowed_push_host: https://rubygems.org
|
122
|
+
homepage_uri: https://github.com/viamin/ruby-maat
|
123
|
+
source_code_uri: https://github.com/viamin/ruby-maat
|
124
|
+
changelog_uri: https://github.com/viamin/ruby-maat/blob/main/CHANGELOG.md
|
125
|
+
rubygems_mfa_required: 'true'
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - ">="
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 3.2.0
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubygems_version: 3.6.2
|
141
|
+
specification_version: 4
|
142
|
+
summary: A command line tool used to mine and analyze data from version-control systems
|
143
|
+
test_files: []
|