rails_console_pro 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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +12 -0
  3. data/.rspec +4 -0
  4. data/.rspec_status +240 -0
  5. data/.rubocop.yml +26 -0
  6. data/CHANGELOG.md +24 -0
  7. data/CONTRIBUTING.md +76 -0
  8. data/LICENSE.txt +22 -0
  9. data/QUICK_START.md +112 -0
  10. data/README.md +124 -0
  11. data/Rakefile +13 -0
  12. data/config/database.yml +3 -0
  13. data/docs/ASSOCIATION_NAVIGATION.md +85 -0
  14. data/docs/EXPORT.md +95 -0
  15. data/docs/FORMATTING.md +86 -0
  16. data/docs/MODEL_STATISTICS.md +72 -0
  17. data/docs/OBJECT_DIFFING.md +87 -0
  18. data/docs/SCHEMA_INSPECTION.md +60 -0
  19. data/docs/SQL_EXPLAIN.md +70 -0
  20. data/lib/generators/rails_console_pro/install_generator.rb +16 -0
  21. data/lib/generators/rails_console_pro/templates/rails_console_pro.rb +44 -0
  22. data/lib/rails_console_pro/active_record_extensions.rb +113 -0
  23. data/lib/rails_console_pro/association_navigator.rb +273 -0
  24. data/lib/rails_console_pro/base_printer.rb +74 -0
  25. data/lib/rails_console_pro/color_helper.rb +36 -0
  26. data/lib/rails_console_pro/commands/base_command.rb +17 -0
  27. data/lib/rails_console_pro/commands/diff_command.rb +135 -0
  28. data/lib/rails_console_pro/commands/explain_command.rb +118 -0
  29. data/lib/rails_console_pro/commands/export_command.rb +16 -0
  30. data/lib/rails_console_pro/commands/schema_command.rb +20 -0
  31. data/lib/rails_console_pro/commands/stats_command.rb +93 -0
  32. data/lib/rails_console_pro/commands.rb +34 -0
  33. data/lib/rails_console_pro/configuration.rb +219 -0
  34. data/lib/rails_console_pro/diff_result.rb +56 -0
  35. data/lib/rails_console_pro/error_handler.rb +60 -0
  36. data/lib/rails_console_pro/explain_result.rb +47 -0
  37. data/lib/rails_console_pro/format_exporter.rb +403 -0
  38. data/lib/rails_console_pro/global_methods.rb +42 -0
  39. data/lib/rails_console_pro/initializer.rb +176 -0
  40. data/lib/rails_console_pro/model_validator.rb +219 -0
  41. data/lib/rails_console_pro/paginator.rb +204 -0
  42. data/lib/rails_console_pro/printers/active_record_printer.rb +30 -0
  43. data/lib/rails_console_pro/printers/collection_printer.rb +34 -0
  44. data/lib/rails_console_pro/printers/diff_printer.rb +97 -0
  45. data/lib/rails_console_pro/printers/explain_printer.rb +151 -0
  46. data/lib/rails_console_pro/printers/relation_printer.rb +25 -0
  47. data/lib/rails_console_pro/printers/schema_printer.rb +188 -0
  48. data/lib/rails_console_pro/printers/stats_printer.rb +129 -0
  49. data/lib/rails_console_pro/pry_commands.rb +241 -0
  50. data/lib/rails_console_pro/pry_integration.rb +9 -0
  51. data/lib/rails_console_pro/railtie.rb +29 -0
  52. data/lib/rails_console_pro/schema_inspector_result.rb +43 -0
  53. data/lib/rails_console_pro/serializers/active_record_serializer.rb +18 -0
  54. data/lib/rails_console_pro/serializers/array_serializer.rb +31 -0
  55. data/lib/rails_console_pro/serializers/base_serializer.rb +25 -0
  56. data/lib/rails_console_pro/serializers/diff_serializer.rb +24 -0
  57. data/lib/rails_console_pro/serializers/explain_serializer.rb +35 -0
  58. data/lib/rails_console_pro/serializers/relation_serializer.rb +25 -0
  59. data/lib/rails_console_pro/serializers/schema_serializer.rb +121 -0
  60. data/lib/rails_console_pro/serializers/stats_serializer.rb +24 -0
  61. data/lib/rails_console_pro/services/column_stats_calculator.rb +64 -0
  62. data/lib/rails_console_pro/services/index_analyzer.rb +110 -0
  63. data/lib/rails_console_pro/services/stats_calculator.rb +40 -0
  64. data/lib/rails_console_pro/services/table_size_calculator.rb +43 -0
  65. data/lib/rails_console_pro/stats_result.rb +66 -0
  66. data/lib/rails_console_pro/version.rb +6 -0
  67. data/lib/rails_console_pro.rb +14 -0
  68. data/lib/tasks/rails_console_pro.rake +10 -0
  69. data/rails_console_pro.gemspec +60 -0
  70. metadata +240 -0
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Printers
5
+ # Printer for SQL explain results
6
+ class ExplainPrinter < BasePrinter
7
+ SCAN_PATTERNS = {
8
+ 'Seq Scan' => { color: :red, icon: 'āš ļø' },
9
+ 'Index Scan' => { color: :green, icon: 'āœ…' },
10
+ 'Index Only Scan' => { color: :green, icon: 'āœ…' },
11
+ 'Hash Join' => { color: :yellow, icon: 'šŸ”„' },
12
+ 'Nested Loop' => { color: :yellow, icon: 'šŸ”„' },
13
+ 'Sort' => { color: :cyan, icon: 'šŸ“‘' }
14
+ }.freeze
15
+
16
+ def print
17
+ print_header
18
+ print_query
19
+ print_execution_time
20
+ print_query_plan
21
+ print_index_analysis
22
+ print_recommendations
23
+ print_statistics
24
+ print_footer
25
+ end
26
+
27
+ private
28
+
29
+ def print_header
30
+ header_color = config.get_color(:header)
31
+ output.puts bold_color(header_color, "═" * config.header_width)
32
+ output.puts bold_color(header_color, "šŸ”¬ SQL EXPLAIN ANALYSIS")
33
+ output.puts bold_color(header_color, "═" * config.header_width)
34
+ end
35
+
36
+ def print_query
37
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“ Query:")
38
+ output.puts color(config.get_color(:attribute_value_string), value.sql)
39
+ end
40
+
41
+ def print_execution_time
42
+ return unless value.execution_time
43
+
44
+ time_str = format_execution_time(value.execution_time)
45
+ output.puts bold_color(config.get_color(:warning), "\nā±ļø Execution Time: ") + time_str
46
+ end
47
+
48
+ def format_execution_time(time_ms)
49
+ case time_ms
50
+ when 0...10
51
+ color(config.get_color(:success), "#{time_ms.round(2)}ms")
52
+ when 10...100
53
+ color(config.get_color(:warning), "#{time_ms.round(2)}ms")
54
+ else
55
+ color(config.get_color(:error), "#{time_ms.round(2)}ms")
56
+ end
57
+ end
58
+
59
+ def print_query_plan
60
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“Š Query Plan:")
61
+
62
+ case value.explain_output
63
+ when String
64
+ print_postgresql_plan(value.explain_output)
65
+ when Array
66
+ print_mysql_plan(value.explain_output)
67
+ else
68
+ output.puts color(config.get_color(:attribute_value_string), value.explain_output.inspect)
69
+ end
70
+ end
71
+
72
+ def print_postgresql_plan(explain_output)
73
+ explain_output.each_line do |line|
74
+ pattern_match = SCAN_PATTERNS.find { |pattern, _| line.include?(pattern) }
75
+
76
+ if pattern_match
77
+ pattern, pattern_config = pattern_match
78
+ output.puts " #{color(pattern_config[:color], pattern_config[:icon] + ' ' + line.strip)}"
79
+ else
80
+ output.puts " #{color(config.get_color(:attribute_value_string), line.strip)}"
81
+ end
82
+ end
83
+ end
84
+
85
+ def print_mysql_plan(explain_output)
86
+ explain_output.each do |row|
87
+ next unless row.is_a?(Hash)
88
+
89
+ row_id = row['id'] || row[:id] || '?'
90
+ output.puts color(config.get_color(:info), "\n Row #{row_id}:")
91
+
92
+ row.each do |key, val|
93
+ next if key.to_s == 'id'
94
+ key_color = config.get_color(:attribute_key)
95
+ output.puts " #{bold_color(key_color, key.to_s.ljust(15))} #{format_mysql_value(key, val)}"
96
+ end
97
+ end
98
+ end
99
+
100
+ def format_mysql_value(key, val)
101
+ case key.to_s
102
+ when 'type', 'select_type'
103
+ val.to_s.downcase.include?('all') ? color(config.get_color(:error), val.to_s) : color(config.get_color(:success), val.to_s)
104
+ when 'possible_keys', 'key'
105
+ val.nil? ? color(config.get_color(:error), 'NULL') : color(config.get_color(:success), val.to_s)
106
+ when 'rows'
107
+ val.to_i > 1000 ? color(config.get_color(:warning), val.to_s) : color(config.get_color(:success), val.to_s)
108
+ else
109
+ color(config.get_color(:attribute_value_string), val.to_s)
110
+ end
111
+ end
112
+
113
+ def print_index_analysis
114
+ output.puts bold_color(config.get_color(:warning), "\nšŸ” Index Analysis:")
115
+
116
+ if value.has_indexes?
117
+ output.puts color(config.get_color(:success), " āœ… Indexes used:")
118
+ value.indexes_used.each do |index|
119
+ output.puts " • #{color(config.get_color(:attribute_value_string), index)}"
120
+ end
121
+ else
122
+ output.puts color(config.get_color(:error), " āš ļø No indexes used - consider adding indexes for better performance")
123
+ end
124
+ end
125
+
126
+ def print_recommendations
127
+ return if value.recommendations.empty?
128
+
129
+ output.puts bold_color(config.get_color(:warning), "\nšŸ’” Recommendations:")
130
+ value.recommendations.each do |rec|
131
+ output.puts " • #{color(config.get_color(:attribute_value_string), rec)}"
132
+ end
133
+ end
134
+
135
+ def print_statistics
136
+ return if value.statistics.empty?
137
+
138
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“ˆ Statistics:")
139
+ value.statistics.each do |key, val|
140
+ key_color = config.get_color(:attribute_key)
141
+ output.puts " #{bold_color(key_color, key.to_s.ljust(20))} #{color(config.get_color(:attribute_value_string), val.to_s)}"
142
+ end
143
+ end
144
+
145
+ def print_footer
146
+ footer_color = config.get_color(:footer)
147
+ output.puts bold_color(footer_color, "═" * config.header_width)
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Printers
5
+ # Printer for ActiveRecord::Relation instances
6
+ class RelationPrinter < BasePrinter
7
+ def print
8
+ model_name = value.klass.name
9
+
10
+ # Use count for efficiency (doesn't load all records)
11
+ total_count = value.count
12
+
13
+ if total_count.zero?
14
+ output.puts color(config.get_color(:warning), "Empty #{model_name} collection")
15
+ return
16
+ end
17
+
18
+ # Use pagination with lazy loading - don't convert to array!
19
+ record_printer = proc { |record| ActiveRecordPrinter.new(output, record, pry_instance).print }
20
+
21
+ Paginator.new(output, value, total_count, config, record_printer).paginate
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,188 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Printers
5
+ # Printer for schema inspection results
6
+ class SchemaPrinter < BasePrinter
7
+
8
+ def print
9
+ model = value.model
10
+ print_header(model)
11
+ print_table_info(model)
12
+ print_columns(model)
13
+ print_indexes(model)
14
+ print_associations(model)
15
+ print_validations(model)
16
+ print_scopes(model)
17
+ print_database_info(model)
18
+ print_footer
19
+ end
20
+
21
+ private
22
+
23
+ def print_header(model)
24
+ header_color = config.get_color(:header)
25
+ output.puts bold_color(header_color, "═" * config.header_width)
26
+ output.puts bold_color(header_color, "šŸ“Š SCHEMA INSPECTOR: #{model.name}")
27
+ output.puts bold_color(header_color, "═" * config.header_width)
28
+ end
29
+
30
+ def print_table_info(model)
31
+ table_name = ModelValidator.safe_table_name(model)
32
+ if table_name
33
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“‹ Table: ") + color(config.get_color(:attribute_value_string), table_name)
34
+
35
+ # Show STI indicator if applicable
36
+ if ModelValidator.sti_model?(model)
37
+ output.puts color(config.get_color(:info), " (Single Table Inheritance model - uses #{model.inheritance_column} column)")
38
+ end
39
+ else
40
+ output.puts bold_color(config.get_color(:error), "\nāš ļø No table information available")
41
+ end
42
+ end
43
+
44
+ def print_columns(model)
45
+ columns = ModelValidator.safe_columns(model)
46
+
47
+ output.puts bold_color(config.get_color(:warning), "\nšŸ”§ Columns:")
48
+ if columns.any?
49
+ columns.each do |column|
50
+ type_color = config.get_type_color(column.type)
51
+ type_str = color(type_color, column.type.to_s.ljust(15))
52
+ null_str = column.null ? color(:dim, " (nullable)") : color(config.get_color(:error), " (NOT NULL)")
53
+ default_str = column.default ? color(:dim, " default: #{column.default}") : ""
54
+
55
+ key_color = config.get_color(:attribute_key)
56
+ output.puts " #{bold_color(key_color, column.name.to_s.ljust(25))} #{type_str}#{null_str}#{default_str}"
57
+ end
58
+ else
59
+ output.puts color(:dim, " No columns available")
60
+ end
61
+ end
62
+
63
+ def print_indexes(model)
64
+ output.puts bold_color(config.get_color(:warning), "\nšŸ” Indexes:")
65
+ indexes = ModelValidator.safe_indexes(model)
66
+
67
+ if indexes.any?
68
+ indexes.each do |index|
69
+ unique_str = index.unique ? color(config.get_color(:success), " UNIQUE") : ""
70
+ columns_str = color(config.get_color(:attribute_value_string), "(#{index.columns.join(', ')})")
71
+ key_color = config.get_color(:attribute_key)
72
+ output.puts " #{bold_color(key_color, index.name.ljust(30))}#{columns_str}#{unique_str}"
73
+ end
74
+ else
75
+ output.puts color(:dim, " No indexes defined")
76
+ end
77
+ end
78
+
79
+ def print_associations(model)
80
+ output.puts bold_color(config.get_color(:warning), "\nšŸ”— Associations:")
81
+
82
+ print_association_group(model, :belongs_to, "belongs_to", "ā†–ļø")
83
+ print_association_group(model, :has_one, "has_one", "→")
84
+ print_association_group(model, :has_many, "has_many", "⇒")
85
+ print_association_group(model, :has_and_belongs_to_many, "has_and_belongs_to_many", "⇔")
86
+ end
87
+
88
+ def print_association_group(model, macro, label, icon)
89
+ associations = ModelValidator.safe_associations(model, macro)
90
+ return if associations.empty?
91
+
92
+ output.puts color(config.get_color(:info), " #{label}:")
93
+ associations.each do |assoc|
94
+ # Validate association before displaying
95
+ unless ModelValidator.valid_associations?(model, assoc.name)
96
+ key_color = config.get_color(:attribute_key)
97
+ output.puts " #{icon} #{bold_color(key_color, assoc.name.to_s)} → #{color(config.get_color(:error), 'INVALID (associated class not found)')}"
98
+ next
99
+ end
100
+
101
+ class_name = assoc.class_name
102
+ details = format_association_details(assoc)
103
+ key_color = config.get_color(:attribute_key)
104
+ output.puts " #{icon} #{bold_color(key_color, assoc.name.to_s)} → #{color(config.get_color(:attribute_value_string), class_name)}#{details}"
105
+ end
106
+ end
107
+
108
+ def format_association_details(assoc)
109
+ details = []
110
+ details << color(:dim, " [#{assoc.foreign_key}]") if assoc.respond_to?(:foreign_key)
111
+ details << color(:dim, " (optional)") if assoc.options[:optional]
112
+ details << color(:yellow, " (#{assoc.options[:dependent]})") if assoc.options[:dependent]
113
+ details << color(:magenta, " through :#{assoc.options[:through]}") if assoc.options[:through]
114
+ details << color(:dim, " [#{assoc.join_table}]") if assoc.respond_to?(:join_table) && assoc.join_table
115
+ details.join
116
+ end
117
+
118
+ def print_validations(model)
119
+ output.puts bold_color(config.get_color(:warning), "\nāœ… Validations:")
120
+ validators = model.validators
121
+
122
+ if validators.any?
123
+ validators_by_attr = group_validators_by_attribute(validators)
124
+ validators_by_attr.sort.each do |attr, attrs_validators|
125
+ validation_str = format_validations(attrs_validators)
126
+ key_color = config.get_color(:attribute_key)
127
+ output.puts " #{bold_color(key_color, attr.to_s.ljust(25))} #{validation_str}"
128
+ end
129
+ else
130
+ output.puts color(:dim, " No validations defined")
131
+ end
132
+ end
133
+
134
+ def group_validators_by_attribute(validators)
135
+ validators.each_with_object({}) do |validator, hash|
136
+ validator.attributes.each do |attr|
137
+ (hash[attr] ||= []) << validator
138
+ end
139
+ end
140
+ end
141
+
142
+ def format_validations(validators)
143
+ validators.map do |validator|
144
+ validator_type = extract_validator_type(validator)
145
+ color_method = config.get_validator_color(validator_type)
146
+ options_str = format_validator_options(validator)
147
+ color(color_method, "#{validator_type.downcase}#{options_str}")
148
+ end.join(', ')
149
+ end
150
+
151
+ def extract_validator_type(validator)
152
+ validator.class.name.split('::').last.gsub('Validator', '')
153
+ end
154
+
155
+ def format_validator_options(validator)
156
+ options = []
157
+ options << "min: #{validator.options[:minimum]}" if validator.options[:minimum]
158
+ options << "max: #{validator.options[:maximum]}" if validator.options[:maximum]
159
+ options.any? ? "(#{options.join(', ')})" : ""
160
+ end
161
+
162
+ def print_scopes(model)
163
+ return unless model.respond_to?(:scopes) && model.scopes.any?
164
+
165
+ output.puts bold_color(config.get_color(:warning), "\nšŸŽÆ Scopes:")
166
+ model.scopes.keys.each do |scope|
167
+ key_color = config.get_color(:attribute_key)
168
+ output.puts " #{bold_color(key_color, scope.to_s)}"
169
+ end
170
+ end
171
+
172
+ def print_database_info(model)
173
+ output.puts bold_color(config.get_color(:warning), "\nšŸ—„ļø Database:")
174
+ connection = model.connection
175
+ output.puts " #{color(:dim, 'Adapter:')} #{color(config.get_color(:attribute_value_string), connection.adapter_name)}"
176
+
177
+ if connection.respond_to?(:current_database)
178
+ output.puts " #{color(:dim, 'Database:')} #{color(config.get_color(:attribute_value_string), connection.current_database)}"
179
+ end
180
+ end
181
+
182
+ def print_footer
183
+ footer_color = config.get_color(:footer)
184
+ output.puts bold_color(footer_color, "═" * config.header_width)
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsConsolePro
4
+ module Printers
5
+ # Printer for model statistics
6
+ class StatsPrinter < BasePrinter
7
+ def print
8
+ print_header
9
+ print_record_count
10
+ print_growth_rate
11
+ print_table_size
12
+ print_index_usage
13
+ print_column_stats
14
+ print_footer
15
+ end
16
+
17
+ private
18
+
19
+ def print_header
20
+ model = value.model
21
+ header_color = config.get_color(:header)
22
+ output.puts bold_color(header_color, "═" * config.header_width)
23
+ output.puts bold_color(header_color, "šŸ“Š MODEL STATISTICS: #{model.name}")
24
+ output.puts bold_color(header_color, "═" * config.header_width)
25
+ end
26
+
27
+ def print_record_count
28
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“ˆ Record Count:")
29
+ count = value.record_count
30
+ count_color = count > 0 ? config.get_color(:success) : config.get_color(:info)
31
+ output.puts " #{bold_color(count_color, count.to_s)} #{pluralize(count, 'record')}"
32
+ end
33
+
34
+ def print_growth_rate
35
+ return unless value.has_growth_data?
36
+
37
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“‰ Growth Rate:")
38
+ rate = value.growth_rate
39
+ rate_str = format_percentage(rate)
40
+ rate_color = rate > 0 ? config.get_color(:success) : rate < 0 ? config.get_color(:error) : config.get_color(:info)
41
+ trend = rate > 0 ? "↑" : rate < 0 ? "↓" : "→"
42
+ output.puts " #{bold_color(rate_color, "#{trend} #{rate_str}")}"
43
+ end
44
+
45
+ def print_table_size
46
+ return unless value.has_table_size?
47
+
48
+ output.puts bold_color(config.get_color(:warning), "\nšŸ’¾ Table Size:")
49
+ size = value.table_size
50
+ size_str = format_bytes(size)
51
+ output.puts " #{bold_color(config.get_color(:attribute_value_numeric), size_str)}"
52
+ end
53
+
54
+ def print_index_usage
55
+ return unless value.has_index_data?
56
+
57
+ output.puts bold_color(config.get_color(:warning), "\nšŸ” Index Usage:")
58
+ value.index_usage.each do |index_name, usage_info|
59
+ key_color = config.get_color(:attribute_key)
60
+ usage_str = format_index_usage(usage_info)
61
+ output.puts " #{bold_color(key_color, index_name.to_s.ljust(30))} #{usage_str}"
62
+ end
63
+ end
64
+
65
+ def print_column_stats
66
+ return if value.column_stats.empty?
67
+
68
+ output.puts bold_color(config.get_color(:warning), "\nšŸ“‹ Column Statistics:")
69
+ value.column_stats.each do |column_name, stats|
70
+ key_color = config.get_color(:attribute_key)
71
+ stats_str = format_column_stats(stats)
72
+ output.puts " #{bold_color(key_color, column_name.to_s.ljust(25))} #{stats_str}"
73
+ end
74
+ end
75
+
76
+ def print_footer
77
+ footer_color = config.get_color(:footer)
78
+ output.puts bold_color(footer_color, "\n" + "═" * config.header_width)
79
+ output.puts color(:dim, "Generated: #{value.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
80
+ end
81
+
82
+ def pluralize(count, word)
83
+ count == 1 ? word : "#{word}s"
84
+ end
85
+
86
+ def format_percentage(value)
87
+ sign = value > 0 ? "+" : ""
88
+ "#{sign}#{value.round(2)}%"
89
+ end
90
+
91
+ def format_bytes(bytes)
92
+ return "0 B" if bytes.nil? || bytes == 0
93
+
94
+ units = ['B', 'KB', 'MB', 'GB', 'TB']
95
+ index = 0
96
+ size = bytes.to_f
97
+
98
+ while size >= 1024 && index < units.length - 1
99
+ size /= 1024
100
+ index += 1
101
+ end
102
+
103
+ "#{size.round(2)} #{units[index]}"
104
+ end
105
+
106
+ def format_index_usage(usage_info)
107
+ case usage_info
108
+ when Hash
109
+ parts = []
110
+ parts << color(config.get_color(:success), "used") if usage_info[:used]
111
+ parts << color(config.get_color(:info), "#{usage_info[:scans]} scans") if usage_info[:scans]
112
+ parts << color(config.get_color(:warning), "#{usage_info[:rows]} rows") if usage_info[:rows]
113
+ parts.any? ? parts.join(", ") : color(:dim, "not used")
114
+ when String
115
+ color(config.get_color(:attribute_value_string), usage_info)
116
+ else
117
+ color(:dim, "unknown")
118
+ end
119
+ end
120
+
121
+ def format_column_stats(stats)
122
+ parts = []
123
+ parts << color(config.get_color(:info), "nulls: #{stats[:null_count]}") if stats[:null_count]
124
+ parts << color(config.get_color(:attribute_value_numeric), "distinct: #{stats[:distinct_count]}") if stats[:distinct_count]
125
+ parts.any? ? parts.join(", ") : color(:dim, "no stats")
126
+ end
127
+ end
128
+ end
129
+ end