class-metrix 1.0.0 → 1.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 +4 -4
- data/.prettierrc.json +41 -0
- data/.qlty/.gitignore +7 -0
- data/.qlty/configs/.yamllint.yaml +8 -0
- data/.qlty/qlty.toml +108 -0
- data/.rubocop.yml +31 -25
- data/.vscode/README.md +255 -47
- data/.vscode/extensions.json +8 -13
- data/.vscode/keybindings.json +0 -0
- data/.vscode/settings.json +81 -11
- data/.vscode/tasks.json +231 -0
- data/CHANGELOG.md +33 -1
- data/README.md +107 -23
- data/Rakefile +64 -1
- data/Steepfile +26 -0
- data/config/brakeman.yml +37 -0
- data/docs/ARCHITECTURE.md +90 -48
- data/docs/CHANGELOG_EVOLUTION_EXAMPLE.md +95 -0
- data/docs/QLTY_INTEGRATION.md +181 -0
- data/docs/RELEASE_GUIDE.md +318 -0
- data/docs/SLACK_INTEGRATION.md +227 -0
- data/examples/README.md +23 -17
- data/examples/basic_usage.rb +19 -19
- data/examples/debug_levels_demo.rb +15 -16
- data/examples/debug_mode_demo.rb +12 -13
- data/examples/inheritance_and_modules.rb +45 -45
- data/lib/class_metrix/extractor.rb +1 -1
- data/lib/class_metrix/extractors/constants_extractor.rb +1 -1
- data/lib/class_metrix/extractors/methods_extractor.rb +1 -1
- data/lib/class_metrix/extractors/multi_type_extractor.rb +2 -2
- data/lib/class_metrix/formatters/base/base_formatter.rb +3 -3
- data/lib/class_metrix/formatters/components/footer_component.rb +3 -3
- data/lib/class_metrix/formatters/components/generic_header_component.rb +2 -2
- data/lib/class_metrix/formatters/components/header_component.rb +4 -4
- data/lib/class_metrix/formatters/components/missing_behaviors_component.rb +7 -7
- data/lib/class_metrix/formatters/components/table_component/row_processor.rb +8 -5
- data/lib/class_metrix/formatters/components/table_component/table_data_extractor.rb +4 -1
- data/lib/class_metrix/formatters/components/table_component/table_renderer.rb +2 -2
- data/lib/class_metrix/formatters/components/table_component.rb +5 -4
- data/lib/class_metrix/formatters/csv_formatter.rb +3 -3
- data/lib/class_metrix/formatters/markdown_formatter.rb +3 -4
- data/lib/class_metrix/formatters/shared/markdown_table_builder.rb +2 -2
- data/lib/class_metrix/formatters/shared/table_builder.rb +8 -6
- data/lib/class_metrix/version.rb +1 -1
- data/sig/class_metrix.rbs +8 -0
- data/sig/extractor.rbs +54 -0
- data/sig/extractors.rbs +84 -0
- data/sig/formatters_base.rbs +59 -0
- data/sig/formatters_components.rbs +133 -0
- data/sig/formatters_main.rbs +20 -0
- data/sig/formatters_shared.rbs +102 -0
- data/sig/manifest.yaml +32 -0
- data/sig/utils.rbs +57 -0
- data/sig/value_processor.rbs +11 -0
- data/sig/version.rbs +4 -0
- metadata +94 -4
- data/RELEASE_GUIDE.md +0 -158
- data/sig/class/metrix.rbs +0 -6
@@ -11,11 +11,11 @@ puts "=" * 50
|
|
11
11
|
|
12
12
|
module Configurable
|
13
13
|
DEFAULT_TIMEOUT = 30
|
14
|
-
|
14
|
+
|
15
15
|
def self.included(base)
|
16
16
|
base.extend(ClassMethods)
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
module ClassMethods
|
20
20
|
def configuration
|
21
21
|
{ timeout: DEFAULT_TIMEOUT, enabled: true }
|
@@ -25,11 +25,11 @@ end
|
|
25
25
|
|
26
26
|
module Cacheable
|
27
27
|
CACHE_TTL = 3600
|
28
|
-
|
28
|
+
|
29
29
|
def self.included(base)
|
30
30
|
base.extend(ClassMethods)
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
module ClassMethods
|
34
34
|
def cache_config
|
35
35
|
{ ttl: CACHE_TTL, enabled: true }
|
@@ -39,11 +39,11 @@ end
|
|
39
39
|
|
40
40
|
class BaseService
|
41
41
|
SERVICE_VERSION = "1.0"
|
42
|
-
|
42
|
+
|
43
43
|
def self.service_type
|
44
44
|
"base"
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def self.health_check
|
48
48
|
{ status: "ok", version: SERVICE_VERSION }
|
49
49
|
end
|
@@ -51,14 +51,14 @@ end
|
|
51
51
|
|
52
52
|
class DatabaseService < BaseService
|
53
53
|
include Configurable
|
54
|
-
|
54
|
+
|
55
55
|
SERVICE_NAME = "database"
|
56
56
|
CONNECTION_POOL_SIZE = 5
|
57
|
-
|
57
|
+
|
58
58
|
def self.service_type
|
59
59
|
"database"
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
def self.connection_config
|
63
63
|
{ pool_size: CONNECTION_POOL_SIZE, timeout: 60 }
|
64
64
|
end
|
@@ -67,14 +67,14 @@ end
|
|
67
67
|
class CacheService < BaseService
|
68
68
|
include Configurable
|
69
69
|
include Cacheable
|
70
|
-
|
71
|
-
SERVICE_NAME = "cache"
|
70
|
+
|
71
|
+
SERVICE_NAME = "cache"
|
72
72
|
MAX_MEMORY = "512MB"
|
73
|
-
|
73
|
+
|
74
74
|
def self.service_type
|
75
75
|
"cache"
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
def self.memory_config
|
79
79
|
{ max_memory: MAX_MEMORY, eviction_policy: "lru" }
|
80
80
|
end
|
@@ -86,43 +86,43 @@ services = [DatabaseService, CacheService]
|
|
86
86
|
puts "\n1. Basic Analysis (Own Constants Only)"
|
87
87
|
puts "-" * 40
|
88
88
|
result = ClassMetrix.extract(:constants)
|
89
|
-
|
90
|
-
|
89
|
+
.from(services)
|
90
|
+
.to_markdown
|
91
91
|
puts result
|
92
92
|
|
93
93
|
puts "\n2. With Inheritance (Own + Parent Constants)"
|
94
94
|
puts "-" * 40
|
95
95
|
result = ClassMetrix.extract(:constants)
|
96
|
-
|
97
|
-
|
98
|
-
|
96
|
+
.from(services)
|
97
|
+
.include_inherited
|
98
|
+
.to_markdown
|
99
99
|
puts result
|
100
100
|
|
101
101
|
puts "\n3. With Modules (Own + Module Constants)"
|
102
102
|
puts "-" * 40
|
103
103
|
result = ClassMetrix.extract(:constants)
|
104
|
-
|
105
|
-
|
106
|
-
|
104
|
+
.from(services)
|
105
|
+
.include_modules
|
106
|
+
.to_markdown
|
107
107
|
puts result
|
108
108
|
|
109
109
|
puts "\n4. Complete Analysis (Own + Inherited + Modules)"
|
110
110
|
puts "-" * 40
|
111
111
|
result = ClassMetrix.extract(:constants, :class_methods)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
112
|
+
.from(services)
|
113
|
+
.include_all
|
114
|
+
.handle_errors
|
115
|
+
.to_markdown
|
116
116
|
puts result
|
117
117
|
|
118
118
|
puts "\n5. Filtered Configuration Analysis"
|
119
119
|
puts "-" * 40
|
120
120
|
result = ClassMetrix.extract(:constants, :class_methods)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
.from(services)
|
122
|
+
.include_all
|
123
|
+
.filter(/config|timeout|service/i)
|
124
|
+
.expand_hashes
|
125
|
+
.to_markdown
|
126
126
|
puts result
|
127
127
|
|
128
128
|
puts "\n6. Hash Expansion Modes"
|
@@ -130,26 +130,26 @@ puts "-" * 40
|
|
130
130
|
|
131
131
|
puts "\n6a. Default: Show Only Main Rows (Collapsed Hashes)"
|
132
132
|
result = ClassMetrix.extract(:class_methods)
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
133
|
+
.from([CacheService])
|
134
|
+
.filter(/config/)
|
135
|
+
.expand_hashes
|
136
|
+
.to_markdown
|
137
137
|
puts result
|
138
138
|
|
139
139
|
puts "\n6b. Show Only Key Rows (Expanded Details)"
|
140
140
|
result = ClassMetrix.extract(:class_methods)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
141
|
+
.from([CacheService])
|
142
|
+
.filter(/config/)
|
143
|
+
.expand_hashes
|
144
|
+
.show_only_keys
|
145
|
+
.to_markdown
|
146
146
|
puts result
|
147
147
|
|
148
148
|
puts "\n6c. Show Both Main and Key Rows"
|
149
149
|
result = ClassMetrix.extract(:class_methods)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
puts result
|
150
|
+
.from([CacheService])
|
151
|
+
.filter(/config/)
|
152
|
+
.expand_hashes
|
153
|
+
.show_expanded_details
|
154
|
+
.to_markdown
|
155
|
+
puts result
|
@@ -48,7 +48,7 @@ module ClassMetrix
|
|
48
48
|
@debug_mode = true
|
49
49
|
@debug_level = level
|
50
50
|
@logger = Utils::DebugLogger.new("Extractor", @debug_mode, level)
|
51
|
-
@logger
|
51
|
+
@logger&.log("Debug mode enabled (level: #{level})")
|
52
52
|
self
|
53
53
|
end
|
54
54
|
|
@@ -20,7 +20,7 @@ module ClassMetrix
|
|
20
20
|
# Build headers: ["Type", "Behavior", "Class1", "Class2", ...]
|
21
21
|
headers = %w[Type Behavior] + @classes.map(&:name)
|
22
22
|
|
23
|
-
all_rows = []
|
23
|
+
all_rows = [] # : Array[Array[untyped]]
|
24
24
|
|
25
25
|
@types.each do |type|
|
26
26
|
type_data = extract_single_type(type)
|
@@ -28,7 +28,7 @@ module ClassMetrix
|
|
28
28
|
# Add rows with type prefix
|
29
29
|
type_data[:rows].each do |row|
|
30
30
|
behavior_name = row[0]
|
31
|
-
values = row[1..]
|
31
|
+
values = row[1..] || []
|
32
32
|
|
33
33
|
new_row = [type_label(type), behavior_name] + values
|
34
34
|
all_rows << new_row
|
@@ -26,7 +26,7 @@ module ClassMetrix
|
|
26
26
|
# Common options for all formatters
|
27
27
|
title: nil,
|
28
28
|
show_metadata: true,
|
29
|
-
extraction_types: []
|
29
|
+
extraction_types: [] # : Array[Symbol]
|
30
30
|
}
|
31
31
|
end
|
32
32
|
|
@@ -52,11 +52,11 @@ module ClassMetrix
|
|
52
52
|
|
53
53
|
def collect_all_hash_keys(rows, _headers)
|
54
54
|
value_start_idx = value_start_index
|
55
|
-
all_keys = {} # behavior_name => Set of keys
|
55
|
+
all_keys = {} # : Hash[String, Set[String]] # behavior_name => Set of keys
|
56
56
|
|
57
57
|
rows.each do |row|
|
58
58
|
behavior_name = row[behavior_column_index]
|
59
|
-
values = row[value_start_idx..]
|
59
|
+
values = row[value_start_idx..] || []
|
60
60
|
|
61
61
|
values.each do |value|
|
62
62
|
if value.is_a?(Hash)
|
@@ -16,7 +16,7 @@ module ClassMetrix
|
|
16
16
|
def generate
|
17
17
|
return [] unless @show_footer
|
18
18
|
|
19
|
-
output = []
|
19
|
+
output = [] # : Array[String]
|
20
20
|
|
21
21
|
# Add separator line
|
22
22
|
output << "---" if @show_separator
|
@@ -36,7 +36,7 @@ module ClassMetrix
|
|
36
36
|
private
|
37
37
|
|
38
38
|
def generate_default_footer
|
39
|
-
output = []
|
39
|
+
output = [] # : Array[String]
|
40
40
|
|
41
41
|
output << (@custom_footer || "*Report generated by ClassMetrix gem*")
|
42
42
|
|
@@ -49,7 +49,7 @@ module ClassMetrix
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def generate_detailed_footer
|
52
|
-
output = []
|
52
|
+
output = [] # : Array[String]
|
53
53
|
|
54
54
|
output << "## Report Information"
|
55
55
|
output << ""
|
@@ -30,7 +30,7 @@ module ClassMetrix
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def generate_markdown_header
|
33
|
-
output = []
|
33
|
+
output = [] # : Array[String]
|
34
34
|
|
35
35
|
# Add title
|
36
36
|
if @title
|
@@ -58,7 +58,7 @@ module ClassMetrix
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def generate_csv_header
|
61
|
-
output = []
|
61
|
+
output = [] # : Array[String]
|
62
62
|
comment_char = @options.fetch(:comment_char, "#")
|
63
63
|
|
64
64
|
# Add title as comment
|
@@ -15,7 +15,7 @@ module ClassMetrix
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def generate
|
18
|
-
output = []
|
18
|
+
output = [] # : Array[String]
|
19
19
|
|
20
20
|
# Add title
|
21
21
|
output.concat(generate_title) if @title || @show_metadata
|
@@ -32,7 +32,7 @@ module ClassMetrix
|
|
32
32
|
private
|
33
33
|
|
34
34
|
def generate_title
|
35
|
-
output = []
|
35
|
+
output = [] # : Array[String]
|
36
36
|
|
37
37
|
if @title
|
38
38
|
output << "# #{@title}"
|
@@ -46,7 +46,7 @@ module ClassMetrix
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def generate_classes_section
|
49
|
-
output = []
|
49
|
+
output = [] # : Array[String]
|
50
50
|
|
51
51
|
has_type_column = @data[:headers].first == "Type"
|
52
52
|
class_headers = if has_type_column
|
@@ -66,7 +66,7 @@ module ClassMetrix
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def generate_extraction_info
|
69
|
-
output = []
|
69
|
+
output = [] # : Array[String]
|
70
70
|
|
71
71
|
output << "## Extraction Types"
|
72
72
|
output << ""
|
@@ -9,7 +9,7 @@ module ClassMetrix
|
|
9
9
|
@options = options
|
10
10
|
@show_missing_summary = options.fetch(:show_missing_summary, false)
|
11
11
|
@summary_style = options.fetch(:summary_style, :grouped) # :grouped, :flat, :detailed
|
12
|
-
@missing_behaviors = {}
|
12
|
+
@missing_behaviors = {} # : Hash[String, Hash[String, String]]
|
13
13
|
end
|
14
14
|
|
15
15
|
def generate
|
@@ -30,7 +30,7 @@ module ClassMetrix
|
|
30
30
|
end
|
31
31
|
|
32
32
|
# Initialize missing behaviors tracking with hash to store behavior name and error message
|
33
|
-
class_headers.each { |class_name| @missing_behaviors[class_name] = {} }
|
33
|
+
class_headers.each { |class_name| @missing_behaviors[class_name] = {} } # : Hash[String, Hash[String, String]]
|
34
34
|
|
35
35
|
@data[:rows].each do |row|
|
36
36
|
behavior_name = has_type_column ? row[1] : row[0]
|
@@ -63,7 +63,7 @@ module ClassMetrix
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def generate_grouped_summary
|
66
|
-
output = []
|
66
|
+
output = [] # : Array[String]
|
67
67
|
|
68
68
|
output << "## Missing Behaviors Summary"
|
69
69
|
output << ""
|
@@ -84,12 +84,12 @@ module ClassMetrix
|
|
84
84
|
end
|
85
85
|
|
86
86
|
def generate_flat_summary
|
87
|
-
output = []
|
87
|
+
output = [] # : Array[String]
|
88
88
|
|
89
89
|
output << "## Missing Behaviors"
|
90
90
|
output << ""
|
91
91
|
|
92
|
-
all_missing = []
|
92
|
+
all_missing = [] # : Array[String]
|
93
93
|
@missing_behaviors.each do |class_name, behaviors_hash|
|
94
94
|
behaviors_hash.each do |behavior_name, error_message|
|
95
95
|
all_missing << "- **#{class_name}**: `#{behavior_name}` - #{error_message}"
|
@@ -103,7 +103,7 @@ module ClassMetrix
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def generate_detailed_summary
|
106
|
-
output = []
|
106
|
+
output = [] # : Array[String]
|
107
107
|
|
108
108
|
total_missing = @missing_behaviors.values.map(&:size).sum
|
109
109
|
total_classes = @missing_behaviors.keys.size
|
@@ -114,7 +114,7 @@ module ClassMetrix
|
|
114
114
|
output << ""
|
115
115
|
|
116
116
|
# Group by error type
|
117
|
-
by_error_type = {}
|
117
|
+
by_error_type = {} # : Hash[String, Array[Hash[Symbol, String]]]
|
118
118
|
@missing_behaviors.each do |class_name, behaviors_hash|
|
119
119
|
behaviors_hash.each do |behavior_name, error_message|
|
120
120
|
error_type = error_message.split.first(2).join(" ") # e.g., "🚫 Not", "⚠️ Error:"
|
@@ -16,7 +16,8 @@ module ClassMetrix
|
|
16
16
|
def process_simple_rows(rows)
|
17
17
|
rows.map do |row|
|
18
18
|
processed_row = [row[0]] # Keep the behavior name as-is
|
19
|
-
row[1..]
|
19
|
+
rest_values = row[1..] || []
|
20
|
+
rest_values.each do |value|
|
20
21
|
processed_row << ValueProcessor.process(value)
|
21
22
|
end
|
22
23
|
processed_row
|
@@ -24,7 +25,7 @@ module ClassMetrix
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def process_expanded_rows(rows)
|
27
|
-
expanded_rows = []
|
28
|
+
expanded_rows = [] # : Array[Array[String]]
|
28
29
|
|
29
30
|
rows.each do |row|
|
30
31
|
if @data_extractor.row_has_expandable_hash?(row)
|
@@ -41,9 +42,11 @@ module ClassMetrix
|
|
41
42
|
|
42
43
|
def process_non_hash_row(row)
|
43
44
|
if @data_extractor.has_type_column?
|
44
|
-
|
45
|
+
rest_values = row[2..] || []
|
46
|
+
[row[0], row[1]] + rest_values.map { |value| ValueProcessor.process(value) }
|
45
47
|
else
|
46
|
-
|
48
|
+
rest_values = row[1..] || []
|
49
|
+
[row[0]] + rest_values.map { |value| ValueProcessor.process(value) }
|
47
50
|
end
|
48
51
|
end
|
49
52
|
|
@@ -57,7 +60,7 @@ module ClassMetrix
|
|
57
60
|
end
|
58
61
|
|
59
62
|
def build_expanded_rows(row_data, all_hash_keys, original_row)
|
60
|
-
expanded_rows = []
|
63
|
+
expanded_rows = [] # : Array[Array[String]]
|
61
64
|
|
62
65
|
# Add main row if configured to show
|
63
66
|
expanded_rows << build_main_expanded_row(row_data) if should_show_main_row?
|
@@ -37,7 +37,10 @@ module ClassMetrix
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def row_has_expandable_hash?(row)
|
40
|
-
row[value_start_index..]
|
40
|
+
values = row[value_start_index..] || []
|
41
|
+
return false if values.nil?
|
42
|
+
|
43
|
+
values.any? { |cell| cell.is_a?(Hash) }
|
41
44
|
end
|
42
45
|
|
43
46
|
def collect_hash_keys(values)
|
@@ -11,7 +11,7 @@ module ClassMetrix
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def render_table(headers, rows, column_widths)
|
14
|
-
output = []
|
14
|
+
output = [] # : Array[String]
|
15
15
|
output << build_row(headers, column_widths)
|
16
16
|
output << build_separator(column_widths)
|
17
17
|
|
@@ -19,7 +19,7 @@ module ClassMetrix
|
|
19
19
|
output << build_row(row, column_widths)
|
20
20
|
end
|
21
21
|
|
22
|
-
output
|
22
|
+
output
|
23
23
|
end
|
24
24
|
|
25
25
|
private
|
@@ -21,9 +21,10 @@ module ClassMetrix
|
|
21
21
|
|
22
22
|
# Initialize helper objects
|
23
23
|
@data_extractor = TableDataExtractor.new(@data[:headers])
|
24
|
-
@row_processor = RowProcessor.new(@data_extractor,
|
25
|
-
|
26
|
-
|
24
|
+
@row_processor = RowProcessor.new(@data_extractor, {
|
25
|
+
hide_main_row: @hide_main_row,
|
26
|
+
hide_key_rows: @hide_key_rows
|
27
|
+
})
|
27
28
|
@width_calculator = ColumnWidthCalculator.new(
|
28
29
|
table_style: @table_style,
|
29
30
|
min_column_width: @min_column_width,
|
@@ -36,7 +37,7 @@ module ClassMetrix
|
|
36
37
|
end
|
37
38
|
|
38
39
|
def generate
|
39
|
-
return "" if @data[:headers].empty? || @data[:rows].empty?
|
40
|
+
return [""] if @data[:headers].empty? || @data[:rows].empty?
|
40
41
|
|
41
42
|
headers = @data[:headers]
|
42
43
|
rows = if @expand_hashes
|
@@ -15,7 +15,7 @@ module ClassMetrix
|
|
15
15
|
def format
|
16
16
|
return "" if @data[:headers].empty? || @data[:rows].empty?
|
17
17
|
|
18
|
-
output_lines = []
|
18
|
+
output_lines = [] # : Array[String]
|
19
19
|
|
20
20
|
# Add CSV header comments
|
21
21
|
if @options.fetch(:show_metadata, true)
|
@@ -65,7 +65,7 @@ module ClassMetrix
|
|
65
65
|
|
66
66
|
return [] if headers.empty?
|
67
67
|
|
68
|
-
output = []
|
68
|
+
output = [] # : Array[String]
|
69
69
|
separator = @options.fetch(:separator, ",")
|
70
70
|
quote_char = @options.fetch(:quote_char, '"')
|
71
71
|
|
@@ -75,7 +75,7 @@ module ClassMetrix
|
|
75
75
|
csv_rows.each do |row|
|
76
76
|
# Ensure all row values are strings and properly quoted
|
77
77
|
formatted_row = row.map { |cell| format_csv_cell(cell) }
|
78
|
-
csv_line = CSV.
|
78
|
+
csv_line = CSV.generate(col_sep: separator, quote_char: quote_char) { |csv| csv << formatted_row }.chomp
|
79
79
|
output << csv_line
|
80
80
|
end
|
81
81
|
|
@@ -16,7 +16,7 @@ module ClassMetrix
|
|
16
16
|
def format
|
17
17
|
return "" if @data[:headers].empty? || @data[:rows].empty?
|
18
18
|
|
19
|
-
output = []
|
19
|
+
output = [] # : Array[String]
|
20
20
|
|
21
21
|
# Add header sections (title, classes, extraction info)
|
22
22
|
if @options.fetch(:show_metadata, true)
|
@@ -77,7 +77,7 @@ module ClassMetrix
|
|
77
77
|
|
78
78
|
widths = calculate_column_widths(headers, rows)
|
79
79
|
|
80
|
-
output = []
|
80
|
+
output = [] # : Array[String]
|
81
81
|
output << build_header_row(headers, widths)
|
82
82
|
output << build_separator_row(widths)
|
83
83
|
output.concat(build_data_rows(rows, headers, widths))
|
@@ -170,8 +170,7 @@ module ClassMetrix
|
|
170
170
|
if max_width >= 15
|
171
171
|
# Look for the first complete key-value pair (handle :symbol=>value format)
|
172
172
|
match = text.match(/\{(:[^,}]+=>[^,}]+)/)
|
173
|
-
if match
|
174
|
-
first_pair = match[1]
|
173
|
+
if match && (first_pair = match[1])
|
175
174
|
needed_length = first_pair.length + 6 # For "{", ", ...}"
|
176
175
|
return "{#{first_pair}, ...}" if needed_length <= max_width
|
177
176
|
end
|
@@ -19,7 +19,7 @@ module ClassMetrix
|
|
19
19
|
# Create proper flat table structure for hash expansion
|
20
20
|
def expand_row(row, _headers)
|
21
21
|
behavior_name = row[behavior_column_index]
|
22
|
-
values = row[value_start_index..]
|
22
|
+
values = row[value_start_index..] || []
|
23
23
|
|
24
24
|
all_hash_keys = collect_unique_hash_keys(values)
|
25
25
|
return [process_row(row)] if all_hash_keys.empty?
|
@@ -36,7 +36,7 @@ module ClassMetrix
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def build_expanded_row_structure(row, behavior_name, values, all_hash_keys)
|
39
|
-
expanded_rows = []
|
39
|
+
expanded_rows = [] # : Array[Array[String]]
|
40
40
|
|
41
41
|
# Add main row if configured to show
|
42
42
|
expanded_rows << build_main_row(row, behavior_name, values) if should_show_main_row?
|
@@ -63,7 +63,7 @@ module ClassMetrix
|
|
63
63
|
private
|
64
64
|
|
65
65
|
def process_rows_for_expansion(headers)
|
66
|
-
expanded_rows = []
|
66
|
+
expanded_rows = [] # : Array[Array[String]]
|
67
67
|
expandable_count = 0
|
68
68
|
|
69
69
|
@data[:rows].each_with_index do |row, index|
|
@@ -88,6 +88,8 @@ module ClassMetrix
|
|
88
88
|
@logger.log_hash_detection_summary(values)
|
89
89
|
|
90
90
|
# Only consider real Hash objects as expandable
|
91
|
+
return false if values.nil?
|
92
|
+
|
91
93
|
result = values.any? { |cell| cell.is_a?(Hash) && cell.instance_of?(Hash) }
|
92
94
|
@logger.log_decision("Row expandable", "#{result ? "Has" : "No"} real Hash objects", :detailed)
|
93
95
|
result
|
@@ -130,7 +132,7 @@ module ClassMetrix
|
|
130
132
|
|
131
133
|
def expand_row(row, _headers)
|
132
134
|
behavior_name = row[behavior_column_index]
|
133
|
-
values = row[value_start_index..]
|
135
|
+
values = row[value_start_index..] || []
|
134
136
|
|
135
137
|
@logger.log("Expanding row for behavior '#{behavior_name}'")
|
136
138
|
|
@@ -161,7 +163,7 @@ module ClassMetrix
|
|
161
163
|
end
|
162
164
|
|
163
165
|
def build_expanded_row_set(row, behavior_name, values, all_hash_keys)
|
164
|
-
expanded_rows = []
|
166
|
+
expanded_rows = [] # : Array[Array[String]]
|
165
167
|
|
166
168
|
# Add main row if configured to show
|
167
169
|
expanded_rows << build_main_row(row, behavior_name, values) if should_show_main_row?
|
@@ -232,7 +234,7 @@ module ClassMetrix
|
|
232
234
|
|
233
235
|
def collect_all_hash_keys(rows, _headers)
|
234
236
|
value_start_idx = value_start_index
|
235
|
-
all_keys = {} # behavior_name => Set of keys
|
237
|
+
all_keys = {} # : Hash[String, Set[String]] # behavior_name => Set of keys
|
236
238
|
total_hash_count = 0
|
237
239
|
|
238
240
|
rows.each_with_index do |row, index|
|
@@ -247,7 +249,7 @@ module ClassMetrix
|
|
247
249
|
|
248
250
|
def collect_hash_keys_for_row(row, value_start_idx, all_keys)
|
249
251
|
behavior_name = row[behavior_column_index]
|
250
|
-
values = row[value_start_idx..]
|
252
|
+
values = row[value_start_idx..] || []
|
251
253
|
hash_count = 0
|
252
254
|
|
253
255
|
values.each_with_index do |value, index|
|
@@ -299,7 +301,7 @@ module ClassMetrix
|
|
299
301
|
def add_flattened_hash_values(flattened, row, behavior_name, all_hash_keys)
|
300
302
|
return unless all_hash_keys[behavior_name]
|
301
303
|
|
302
|
-
values = row[value_start_index..]
|
304
|
+
values = row[value_start_index..] || []
|
303
305
|
|
304
306
|
all_hash_keys[behavior_name].to_a.sort.each do |key|
|
305
307
|
add_flattened_values_for_key(flattened, values, key)
|
data/lib/class_metrix/version.rb
CHANGED