rails_lens 0.0.0 → 0.2.2

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE.txt +2 -2
  4. data/README.md +463 -9
  5. data/exe/rails_lens +25 -0
  6. data/lib/rails_lens/analyzers/association_analyzer.rb +111 -0
  7. data/lib/rails_lens/analyzers/base.rb +35 -0
  8. data/lib/rails_lens/analyzers/best_practices_analyzer.rb +114 -0
  9. data/lib/rails_lens/analyzers/column_analyzer.rb +97 -0
  10. data/lib/rails_lens/analyzers/composite_keys.rb +62 -0
  11. data/lib/rails_lens/analyzers/database_constraints.rb +35 -0
  12. data/lib/rails_lens/analyzers/delegated_types.rb +129 -0
  13. data/lib/rails_lens/analyzers/enums.rb +34 -0
  14. data/lib/rails_lens/analyzers/error_handling.rb +66 -0
  15. data/lib/rails_lens/analyzers/foreign_key_analyzer.rb +47 -0
  16. data/lib/rails_lens/analyzers/generated_columns.rb +56 -0
  17. data/lib/rails_lens/analyzers/index_analyzer.rb +128 -0
  18. data/lib/rails_lens/analyzers/inheritance.rb +212 -0
  19. data/lib/rails_lens/analyzers/notes.rb +325 -0
  20. data/lib/rails_lens/analyzers/performance_analyzer.rb +110 -0
  21. data/lib/rails_lens/annotation_pipeline.rb +87 -0
  22. data/lib/rails_lens/cli.rb +176 -0
  23. data/lib/rails_lens/cli_error_handler.rb +86 -0
  24. data/lib/rails_lens/commands.rb +164 -0
  25. data/lib/rails_lens/connection.rb +133 -0
  26. data/lib/rails_lens/erd/column_type_formatter.rb +32 -0
  27. data/lib/rails_lens/erd/domain_color_mapper.rb +40 -0
  28. data/lib/rails_lens/erd/mysql_column_type_formatter.rb +19 -0
  29. data/lib/rails_lens/erd/postgresql_column_type_formatter.rb +19 -0
  30. data/lib/rails_lens/erd/visualizer.rb +329 -0
  31. data/lib/rails_lens/errors.rb +78 -0
  32. data/lib/rails_lens/extension_loader.rb +261 -0
  33. data/lib/rails_lens/extensions/base.rb +194 -0
  34. data/lib/rails_lens/extensions/closure_tree_ext.rb +157 -0
  35. data/lib/rails_lens/file_insertion_helper.rb +168 -0
  36. data/lib/rails_lens/mailer/annotator.rb +226 -0
  37. data/lib/rails_lens/mailer/extractor.rb +201 -0
  38. data/lib/rails_lens/model_detector.rb +252 -0
  39. data/lib/rails_lens/parsers/class_info.rb +46 -0
  40. data/lib/rails_lens/parsers/module_info.rb +33 -0
  41. data/lib/rails_lens/parsers/parser_result.rb +55 -0
  42. data/lib/rails_lens/parsers/prism_parser.rb +90 -0
  43. data/lib/rails_lens/parsers.rb +10 -0
  44. data/lib/rails_lens/providers/association_notes_provider.rb +11 -0
  45. data/lib/rails_lens/providers/base.rb +37 -0
  46. data/lib/rails_lens/providers/best_practices_notes_provider.rb +11 -0
  47. data/lib/rails_lens/providers/column_notes_provider.rb +11 -0
  48. data/lib/rails_lens/providers/composite_keys_provider.rb +11 -0
  49. data/lib/rails_lens/providers/database_constraints_provider.rb +11 -0
  50. data/lib/rails_lens/providers/delegated_types_provider.rb +11 -0
  51. data/lib/rails_lens/providers/enums_provider.rb +11 -0
  52. data/lib/rails_lens/providers/extension_notes_provider.rb +20 -0
  53. data/lib/rails_lens/providers/extensions_provider.rb +22 -0
  54. data/lib/rails_lens/providers/foreign_key_notes_provider.rb +11 -0
  55. data/lib/rails_lens/providers/generated_columns_provider.rb +11 -0
  56. data/lib/rails_lens/providers/index_notes_provider.rb +20 -0
  57. data/lib/rails_lens/providers/inheritance_provider.rb +23 -0
  58. data/lib/rails_lens/providers/notes_provider_base.rb +25 -0
  59. data/lib/rails_lens/providers/performance_notes_provider.rb +11 -0
  60. data/lib/rails_lens/providers/schema_provider.rb +61 -0
  61. data/lib/rails_lens/providers/section_provider_base.rb +28 -0
  62. data/lib/rails_lens/railtie.rb +17 -0
  63. data/lib/rails_lens/rake_bootstrapper.rb +18 -0
  64. data/lib/rails_lens/route/annotator.rb +268 -0
  65. data/lib/rails_lens/route/extractor.rb +133 -0
  66. data/lib/rails_lens/route/parser.rb +59 -0
  67. data/lib/rails_lens/schema/adapters/base.rb +345 -0
  68. data/lib/rails_lens/schema/adapters/database_info.rb +118 -0
  69. data/lib/rails_lens/schema/adapters/mysql.rb +279 -0
  70. data/lib/rails_lens/schema/adapters/postgresql.rb +197 -0
  71. data/lib/rails_lens/schema/adapters/sqlite3.rb +96 -0
  72. data/lib/rails_lens/schema/annotation.rb +144 -0
  73. data/lib/rails_lens/schema/annotation_manager.rb +202 -0
  74. data/lib/rails_lens/tasks/annotate.rake +35 -0
  75. data/lib/rails_lens/tasks/erd.rake +24 -0
  76. data/lib/rails_lens/tasks/mailers.rake +27 -0
  77. data/lib/rails_lens/tasks/routes.rake +27 -0
  78. data/lib/rails_lens/tasks/schema.rake +108 -0
  79. data/lib/rails_lens/version.rb +5 -0
  80. data/lib/rails_lens.rb +138 -5
  81. metadata +215 -11
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ module Analyzers
5
+ class PerformanceAnalyzer < Base
6
+ def analyze
7
+ notes = []
8
+ notes.concat(analyze_large_text_columns)
9
+ notes.concat(analyze_uuid_indexes)
10
+ notes.concat(analyze_query_performance)
11
+ notes
12
+ end
13
+
14
+ private
15
+
16
+ def analyze_large_text_columns
17
+ notes = []
18
+
19
+ text_columns.each do |column|
20
+ if frequently_queried_column?(column)
21
+ notes << "Large text column '#{column.name}' is frequently queried - consider separate storage"
22
+ end
23
+ end
24
+
25
+ notes
26
+ end
27
+
28
+ def analyze_uuid_indexes
29
+ notes = []
30
+
31
+ uuid_columns.each do |column|
32
+ next if column.name == 'id' # Primary keys are already indexed
33
+
34
+ if should_be_indexed?(column) && !indexed?(column)
35
+ notes << "UUID column '#{column.name}' should be indexed for better query performance"
36
+ end
37
+ end
38
+
39
+ notes
40
+ end
41
+
42
+ def analyze_query_performance
43
+ notes = []
44
+
45
+ # Check for columns that are commonly used in WHERE clauses
46
+ commonly_queried_columns.each do |column|
47
+ next if indexed?(column)
48
+
49
+ notes << "Column '#{column.name}' is commonly used in queries - consider adding an index"
50
+ end
51
+
52
+ # Check for missing indexes on scoped columns
53
+ scoped_columns.each do |column|
54
+ next if indexed?(column)
55
+
56
+ notes << "Scope column '#{column.name}' should be indexed"
57
+ end
58
+
59
+ notes
60
+ end
61
+
62
+ def text_columns
63
+ model_class.columns.select { |c| c.type == :text }
64
+ end
65
+
66
+ def uuid_columns
67
+ model_class.columns.select { |c| c.type == :uuid || (c.type == :string && c.name.match?(/uuid|guid/i)) }
68
+ end
69
+
70
+ def frequently_queried_column?(column)
71
+ # Heuristic: columns with certain names are likely to be queried frequently
72
+ column.name.match?(/title|name|slug|description|summary|content|body/i)
73
+ end
74
+
75
+ def should_be_indexed?(column)
76
+ # UUID columns used as foreign keys or identifiers should be indexed
77
+ column.name.end_with?('_id', '_uuid', '_guid') ||
78
+ column.name.match?(/identifier|reference|token/i)
79
+ end
80
+
81
+ def commonly_queried_columns
82
+ model_class.columns.select do |column|
83
+ column.name.match?(/email|username|slug|token|code|status|state|type/i) ||
84
+ column.name.end_with?('_type', '_kind', '_category')
85
+ end
86
+ end
87
+
88
+ def scoped_columns
89
+ model_class.columns.select do |column|
90
+ column.name.match?(/scope|tenant|company|organization|account|workspace/i) &&
91
+ column.name.end_with?('_id')
92
+ end
93
+ end
94
+
95
+ def indexed?(column)
96
+ connection.indexes(table_name).any? do |index|
97
+ index.columns.include?(column.name)
98
+ end
99
+ end
100
+
101
+ def connection
102
+ model_class.connection
103
+ end
104
+
105
+ def table_name
106
+ model_class.table_name
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ # Manages the pipeline of content providers for generating annotations
5
+ class AnnotationPipeline
6
+ attr_reader :providers
7
+
8
+ def initialize
9
+ @providers = []
10
+ register_default_providers
11
+ end
12
+
13
+ def register(provider)
14
+ @providers << provider
15
+ end
16
+
17
+ def unregister(provider_class)
18
+ @providers.reject! { |p| p.is_a?(provider_class) }
19
+ end
20
+
21
+ delegate :clear, to: :@providers
22
+
23
+ def process(model_class)
24
+ results = {
25
+ schema: nil,
26
+ sections: [],
27
+ notes: []
28
+ }
29
+
30
+ @providers.each do |provider|
31
+ next unless provider.applicable?(model_class)
32
+
33
+ begin
34
+ result = provider.process(model_class)
35
+
36
+ case provider.type
37
+ when :schema
38
+ results[:schema] = result
39
+ when :section
40
+ results[:sections] << result if result
41
+ when :notes
42
+ results[:notes].concat(Array(result))
43
+ end
44
+ rescue ActiveRecord::StatementInvalid => e
45
+ warn "Provider #{provider.class} database error for #{model_class}: #{e.message}"
46
+ rescue ActiveRecord::ConnectionNotDefined => e
47
+ warn "Provider #{provider.class} connection error for #{model_class}: #{e.message}"
48
+ rescue NameError, NoMethodError => e
49
+ warn "Provider #{provider.class} method error for #{model_class}: #{e.message}"
50
+ rescue RailsLens::Error => e
51
+ warn "Provider #{provider.class} rails_lens error for #{model_class}: #{e.message}"
52
+ rescue StandardError => e
53
+ warn "Provider #{provider.class} unexpected error for #{model_class}: #{e.message}"
54
+ end
55
+ end
56
+
57
+ results
58
+ end
59
+
60
+ private
61
+
62
+ def register_default_providers
63
+ # Schema provider (primary content)
64
+ register(Providers::SchemaProvider.new)
65
+
66
+ # Section providers (additional structured content)
67
+ register(Providers::ExtensionsProvider.new) if RailsLens.config.extensions[:enabled]
68
+ register(Providers::InheritanceProvider.new)
69
+ register(Providers::EnumsProvider.new)
70
+ register(Providers::DelegatedTypesProvider.new)
71
+ register(Providers::CompositeKeysProvider.new)
72
+ register(Providers::DatabaseConstraintsProvider.new)
73
+ register(Providers::GeneratedColumnsProvider.new)
74
+
75
+ # Notes providers (analysis and recommendations)
76
+ return unless RailsLens.config.schema[:include_notes]
77
+
78
+ register(Providers::IndexNotesProvider.new)
79
+ register(Providers::ForeignKeyNotesProvider.new)
80
+ register(Providers::AssociationNotesProvider.new)
81
+ register(Providers::ColumnNotesProvider.new)
82
+ register(Providers::PerformanceNotesProvider.new)
83
+ register(Providers::BestPracticesNotesProvider.new)
84
+ register(Providers::ExtensionNotesProvider.new) if RailsLens.config.extensions[:enabled]
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative 'commands'
5
+ require_relative 'cli_error_handler'
6
+
7
+ module RailsLens
8
+ class CLI < Thor
9
+ include CLIErrorHandler
10
+
11
+ # Thor configuration: exit with proper status codes on failure (modern behavior)
12
+ def self.exit_on_failure?
13
+ true
14
+ end
15
+
16
+ class_option :config, type: :string, default: '.rails-lens.yml', desc: 'Configuration file path'
17
+ class_option :dry_run, type: :boolean, desc: 'Show what would be done without making changes'
18
+ class_option :verbose, type: :boolean, desc: 'Verbose output'
19
+ class_option :debug, type: :boolean, desc: 'Debug output with full backtraces'
20
+
21
+ desc 'annotate', 'Annotate Rails models with schema information'
22
+ option :models, type: :array, desc: 'Specific models to annotate'
23
+ option :include_abstract, type: :boolean, desc: 'Include abstract classes'
24
+ option :position, type: :string, enum: %w[before after top bottom], desc: 'Annotation position'
25
+ option :routes, type: :boolean, desc: 'Annotate controller routes'
26
+ option :mailers, type: :boolean, desc: 'Annotate mailer methods'
27
+ option :all, type: :boolean, desc: 'Annotate models, routes, and mailers'
28
+ def annotate
29
+ with_error_handling do
30
+ setup_environment
31
+
32
+ results = {}
33
+ commands = Commands.new(self)
34
+
35
+ # Annotate models (default behavior or when --all is specified)
36
+ results[:models] = commands.annotate_models(options) if should_annotate_models?
37
+
38
+ # Annotate routes
39
+ results[:routes] = commands.annotate_routes(options) if should_annotate_routes?
40
+
41
+ # Annotate mailers
42
+ results[:mailers] = commands.annotate_mailers(options) if should_annotate_mailers?
43
+
44
+ results
45
+ end
46
+ end
47
+
48
+ desc 'remove', 'Remove annotations from Rails files'
49
+ option :routes, type: :boolean, desc: 'Remove controller route annotations'
50
+ option :mailers, type: :boolean, desc: 'Remove mailer annotations'
51
+ option :all, type: :boolean, desc: 'Remove all annotations'
52
+ def remove
53
+ with_error_handling do
54
+ setup_environment
55
+
56
+ results = {}
57
+ commands = Commands.new(self)
58
+
59
+ # Remove model annotations (default behavior or when --all is specified)
60
+ results[:models] = commands.remove_models(options) if should_remove_models?
61
+
62
+ # Remove route annotations
63
+ results[:routes] = commands.remove_routes(options) if should_remove_routes?
64
+
65
+ # Remove mailer annotations
66
+ results[:mailers] = commands.remove_mailers(options) if should_remove_mailers?
67
+
68
+ results
69
+ end
70
+ end
71
+
72
+ desc 'erd', 'Generate Entity Relationship Diagram (Mermaid format)'
73
+ option :output, type: :string, desc: 'Output directory'
74
+ option :verbose, type: :boolean, desc: 'Verbose output'
75
+ option :group_by_database, type: :boolean, desc: 'Group models by database connection instead of domain'
76
+ def erd
77
+ with_error_handling do
78
+ setup_environment
79
+
80
+ # Transform CLI options to visualizer options
81
+ visualizer_options = options.dup
82
+ visualizer_options[:output_dir] = options[:output] || 'output'
83
+
84
+ commands = Commands.new(self)
85
+ commands.generate_erd(visualizer_options)
86
+ end
87
+ end
88
+
89
+ desc 'version', 'Show Rails Lens version'
90
+ def version
91
+ say "Rails Lens #{RailsLens::VERSION}"
92
+ end
93
+
94
+ desc 'lint', 'Lint Rails Lens configuration and model annotations'
95
+ option :domains, type: :array, desc: 'Specific domains to lint (models, routes, mailers)'
96
+ def lint
97
+ with_error_handling do
98
+ setup_environment
99
+
100
+ commands = Commands.new(self)
101
+ commands.lint(options)
102
+ end
103
+ end
104
+
105
+ desc 'check', 'Check Rails Lens configuration validity'
106
+ def check
107
+ with_error_handling do
108
+ setup_environment
109
+
110
+ commands = Commands.new(self)
111
+ commands.check(options)
112
+ end
113
+ end
114
+
115
+ desc 'config SUBCOMMAND', 'Manage Rails Lens configuration'
116
+ option :key, type: :string, desc: 'Configuration key'
117
+ option :value, type: :string, desc: 'Configuration value'
118
+ def config(subcommand = 'show')
119
+ with_error_handling do
120
+ setup_environment
121
+
122
+ commands = Commands.new(self)
123
+ commands.config(subcommand, options)
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def setup_environment
130
+ RakeBootstrapper.call
131
+ load_configuration(options[:config])
132
+ configure_error_reporting
133
+ end
134
+
135
+ def load_configuration(config_file)
136
+ if File.exist?(config_file)
137
+ RailsLens.load_config_file(config_file)
138
+ say "Loaded configuration from #{config_file}", :green if options[:verbose]
139
+ elsif config_file != '.rails-lens.yml'
140
+ raise ConfigurationError, "Configuration file not found: #{config_file}"
141
+ elsif options[:verbose]
142
+ say "Using default configuration (#{config_file} not found)", :yellow
143
+ end
144
+ end
145
+
146
+ def configure_error_reporting
147
+ RailsLens.config.verbose = options[:verbose]
148
+ RailsLens.config.debug = options[:debug]
149
+ end
150
+
151
+ # Helper methods to determine what to annotate/remove
152
+ def should_annotate_models?
153
+ (!options[:routes] && !options[:mailers]) || options[:all]
154
+ end
155
+
156
+ def should_annotate_routes?
157
+ options[:routes] || options[:all]
158
+ end
159
+
160
+ def should_annotate_mailers?
161
+ options[:mailers] || options[:all]
162
+ end
163
+
164
+ def should_remove_models?
165
+ (!options[:routes] && !options[:mailers]) || options[:all]
166
+ end
167
+
168
+ def should_remove_routes?
169
+ options[:routes] || options[:all]
170
+ end
171
+
172
+ def should_remove_mailers?
173
+ options[:mailers] || options[:all]
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ # Provides consistent error handling for CLI commands
5
+ module CLIErrorHandler
6
+ def with_error_handling
7
+ yield
8
+ rescue Interrupt
9
+ say "\nOperation cancelled by user", :yellow
10
+ exit 1
11
+ rescue ConfigurationError => e
12
+ handle_configuration_error(e)
13
+ rescue ModelDetectionError => e
14
+ handle_model_error(e)
15
+ rescue DatabaseError => e
16
+ handle_database_error(e)
17
+ rescue AnnotationError => e
18
+ handle_annotation_error(e)
19
+ rescue ExtensionError => e
20
+ handle_extension_error(e)
21
+ rescue StandardError => e
22
+ handle_unexpected_error(e)
23
+ end
24
+
25
+ private
26
+
27
+ def handle_configuration_error(error)
28
+ say "Configuration Error: #{error.message}", :red
29
+ say 'Please check your .rails-lens.yml file', :yellow
30
+ exit 1
31
+ end
32
+
33
+ def handle_model_error(error)
34
+ say "Model Error: #{error.message}", :red
35
+ if options[:verbose]
36
+ say 'Make sure your Rails application is properly loaded', :yellow
37
+ say 'Try running: bundle exec rails_lens annotate', :yellow
38
+ end
39
+ exit 1
40
+ end
41
+
42
+ def handle_database_error(error)
43
+ say "Database Error: #{error.message}", :red
44
+ if options[:verbose]
45
+ say 'Possible causes:', :yellow
46
+ say ' - Database server is not running', :yellow
47
+ say ' - Invalid database credentials', :yellow
48
+ say ' - Table does not exist', :yellow
49
+ say ' - Permission denied', :yellow
50
+ end
51
+ exit 1
52
+ end
53
+
54
+ def handle_annotation_error(error)
55
+ say "Annotation Error: #{error.message}", :red
56
+ if options[:verbose]
57
+ say 'Failed to annotate one or more files', :yellow
58
+ say 'Check file permissions and syntax', :yellow
59
+ end
60
+ exit 1
61
+ end
62
+
63
+ def handle_extension_error(error)
64
+ say "Extension Error: #{error.message}", :red
65
+ return unless options[:verbose]
66
+
67
+ say 'An extension failed to load or execute', :yellow
68
+ say 'You can disable extensions in .rails-lens.yml', :yellow
69
+
70
+ # Don't exit - extensions are optional
71
+ end
72
+
73
+ def handle_unexpected_error(error)
74
+ say "Unexpected Error: #{error.class.name}", :red
75
+ say error.message, :red
76
+
77
+ if options[:verbose] || options[:debug]
78
+ say "\nBacktrace:", :yellow
79
+ say error.backtrace.first(10).join("\n"), :yellow
80
+ end
81
+
82
+ say "\nPlease report this issue at: https://github.com/your-org/rails_lens/issues", :cyan
83
+ exit 1
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLens
4
+ # Handles the actual execution of CLI commands
5
+ class Commands
6
+ attr_reader :output
7
+
8
+ def initialize(output = $stdout)
9
+ @output = output
10
+ end
11
+
12
+ def annotate_models(options = {})
13
+ results = Schema::AnnotationManager.annotate_all(options)
14
+
15
+ output.say "Annotated #{results[:annotated].length} models", :green
16
+ output.say "Skipped #{results[:skipped].length} models", :yellow if results[:skipped].any?
17
+
18
+ if results[:failed].any?
19
+ output.say "Failed to annotate #{results[:failed].length} models:", :red
20
+ results[:failed].each do |failure|
21
+ output.say " - #{failure[:model]}: #{failure[:error]}", :red
22
+ end
23
+ end
24
+
25
+ results
26
+ end
27
+
28
+ def annotate_routes(options = {})
29
+ annotator = Route::Annotator.new(dry_run: options[:dry_run])
30
+ changed_files = annotator.annotate_all
31
+
32
+ output.say "Annotated #{changed_files.length} controller files with routes", :green
33
+ changed_files.each { |file| output.say " - #{file}", :blue } if options[:verbose] && changed_files.any?
34
+
35
+ { changed_files: changed_files }
36
+ end
37
+
38
+ def annotate_mailers(options = {})
39
+ annotator = Mailer::Annotator.new(dry_run: options[:dry_run])
40
+ changed_files = annotator.annotate_all
41
+
42
+ output.say "Annotated #{changed_files.length} mailer files", :green
43
+ changed_files.each { |file| output.say " - #{file}", :blue } if options[:verbose] && changed_files.any?
44
+
45
+ { changed_files: changed_files }
46
+ end
47
+
48
+ def remove_models(options = {})
49
+ results = Schema::AnnotationManager.remove_all(options)
50
+ output.say "Removed annotations from #{results[:removed].length} models", :green
51
+ results
52
+ end
53
+
54
+ def remove_routes(options = {})
55
+ annotator = Route::Annotator.new(dry_run: options[:dry_run])
56
+ changed_files = annotator.remove_all
57
+ output.say "Removed route annotations from #{changed_files.length} controller files", :green
58
+ { changed_files: changed_files }
59
+ end
60
+
61
+ def remove_mailers(options = {})
62
+ annotator = Mailer::Annotator.new(dry_run: options[:dry_run])
63
+ changed_files = annotator.remove_all
64
+ output.say "Removed mailer annotations from #{changed_files.length} mailer files", :green
65
+ { changed_files: changed_files }
66
+ end
67
+
68
+ def generate_erd(options = {})
69
+ visualizer = ERD::Visualizer.new(options: options)
70
+ filename = visualizer.generate
71
+ output.say "Entity Relationship Diagram generated at #{filename}", :green
72
+ filename
73
+ end
74
+
75
+ def lint(options = {})
76
+ output.say 'Linting Rails Lens configuration and annotations...', :blue
77
+
78
+ issues = []
79
+ warnings = []
80
+ domains = options[:domains] || %w[models routes mailers]
81
+
82
+ domains.each do |domain|
83
+ case domain
84
+ when 'models'
85
+ # Check for inconsistent model annotations
86
+ output.say ' Checking models domain...', :blue if options[:verbose]
87
+ when 'routes'
88
+ # Check for route annotation consistency
89
+ output.say ' Checking routes domain...', :blue if options[:verbose]
90
+ when 'mailers'
91
+ # Check for mailer annotation consistency
92
+ output.say ' Checking mailers domain...', :blue if options[:verbose]
93
+ end
94
+ end
95
+
96
+ if issues.empty? && warnings.empty?
97
+ output.say 'No linting issues found', :green
98
+ else
99
+ output.say "Found #{issues.length} issues and #{warnings.length} warnings", :yellow
100
+ end
101
+
102
+ { issues: issues, warnings: warnings }
103
+ end
104
+
105
+ def check(_options = {})
106
+ output.say 'Checking Rails Lens configuration validity...', :blue
107
+
108
+ # Check if Rails is properly loaded
109
+ unless defined?(Rails)
110
+ output.say 'Rails environment not detected', :red
111
+ return { valid: false, errors: ['Rails environment not detected'] }
112
+ end
113
+
114
+ # Check database connections
115
+ errors = []
116
+ begin
117
+ ApplicationRecord.connection.execute('SELECT 1')
118
+ output.say 'Primary database connection: OK', :green
119
+ rescue StandardError => e
120
+ errors << "Primary database connection failed: #{e.message}"
121
+ output.say 'Primary database connection: FAILED', :red
122
+ end
123
+
124
+ if errors.empty?
125
+ output.say 'Configuration is valid', :green
126
+ { valid: true, errors: [] }
127
+ else
128
+ output.say 'Configuration has errors', :red
129
+ { valid: false, errors: errors }
130
+ end
131
+ end
132
+
133
+ def config(subcommand, options = {})
134
+ case subcommand
135
+ when 'show'
136
+ output.say 'Rails Lens Configuration:', :blue
137
+ output.say " Config file: #{RailsLens.config_file || 'default'}"
138
+ output.say " Verbose: #{RailsLens.config.verbose || false}"
139
+ output.say " Debug: #{RailsLens.config.debug || false}"
140
+
141
+ if RailsLens.config.respond_to?(:position)
142
+ output.say " Default position: #{RailsLens.config.position || 'before'}"
143
+ end
144
+
145
+ when 'set'
146
+ if options[:key] && options[:value]
147
+ output.say "Setting #{options[:key]} = #{options[:value]}", :green
148
+ # NOTE: This would require implementing config persistence
149
+ output.say 'Note: Configuration changes are not persisted yet', :yellow
150
+ else
151
+ output.say 'Usage: config set --key KEY --value VALUE', :red
152
+ end
153
+
154
+ when 'reset'
155
+ output.say 'Resetting configuration to defaults', :yellow
156
+ # NOTE: This would reset to defaults
157
+
158
+ else
159
+ output.say "Unknown config subcommand: #{subcommand}", :red
160
+ output.say 'Available: show, set, reset'
161
+ end
162
+ end
163
+ end
164
+ end