rails-flow-map 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.
@@ -0,0 +1,310 @@
1
+ require "set"
2
+ require "json"
3
+ require "yaml"
4
+ require "cgi"
5
+ require "rails_flow_map/version"
6
+ require "rails_flow_map/configuration"
7
+ require "rails_flow_map/logging"
8
+ require "rails_flow_map/errors"
9
+ require "rails_flow_map/engine" if defined?(Rails)
10
+
11
+ # RailsFlowMap is a comprehensive tool for visualizing data flows in Rails applications
12
+ #
13
+ # It analyzes your Rails application's structure and generates various visualization
14
+ # formats to help understand the architecture, dependencies, and data flow patterns.
15
+ #
16
+ # @example Basic usage
17
+ # # Analyze the entire application
18
+ # graph = RailsFlowMap.analyze
19
+ #
20
+ # # Export to different formats
21
+ # RailsFlowMap.export(graph, format: :mermaid, output: 'architecture.md')
22
+ # RailsFlowMap.export(graph, format: :d3js, output: 'interactive.html')
23
+ #
24
+ # @example Analyzing specific endpoints
25
+ # graph = RailsFlowMap.analyze_endpoint('/api/v1/users')
26
+ # RailsFlowMap.export(graph, format: :sequence, endpoint: '/api/v1/users')
27
+ #
28
+ # @example Comparing versions
29
+ # before = RailsFlowMap.analyze_at('v1.0.0')
30
+ # after = RailsFlowMap.analyze
31
+ # diff = RailsFlowMap.diff(before, after)
32
+ #
33
+ module RailsFlowMap
34
+
35
+ autoload :ModelAnalyzer, "rails_flow_map/analyzers/model_analyzer"
36
+ autoload :ControllerAnalyzer, "rails_flow_map/analyzers/controller_analyzer"
37
+ autoload :FlowNode, "rails_flow_map/models/flow_node"
38
+ autoload :FlowEdge, "rails_flow_map/models/flow_edge"
39
+ autoload :FlowGraph, "rails_flow_map/models/flow_graph"
40
+
41
+ autoload :MermaidFormatter, "rails_flow_map/formatters/mermaid_formatter"
42
+ autoload :PlantUMLFormatter, "rails_flow_map/formatters/plantuml_formatter"
43
+ autoload :GraphVizFormatter, "rails_flow_map/formatters/graphviz_formatter"
44
+ autoload :ErdFormatter, "rails_flow_map/formatters/erd_formatter"
45
+ autoload :MetricsFormatter, "rails_flow_map/formatters/metrics_formatter"
46
+ autoload :D3jsFormatter, "rails_flow_map/formatters/d3js_formatter"
47
+ autoload :OpenapiFormatter, "rails_flow_map/formatters/openapi_formatter"
48
+ autoload :SequenceFormatter, "rails_flow_map/formatters/sequence_formatter"
49
+ autoload :GitDiffFormatter, "rails_flow_map/formatters/git_diff_formatter"
50
+
51
+ module Formatters
52
+ # For backwards compatibility
53
+ end
54
+
55
+ class << self
56
+ def configuration
57
+ @configuration ||= Configuration.new
58
+ end
59
+
60
+ # Configures RailsFlowMap
61
+ #
62
+ # @yield [Configuration] The configuration object
63
+ # @example
64
+ # RailsFlowMap.configure do |config|
65
+ # config.include_models = true
66
+ # config.include_controllers = true
67
+ # config.output_dir = 'doc/flow_maps'
68
+ # end
69
+ def configure
70
+ yield(configuration)
71
+ end
72
+
73
+ # Analyzes the Rails application and builds a flow graph
74
+ #
75
+ # @param options [Hash] Analysis options
76
+ # @option options [Boolean] :models Include model analysis (default: true)
77
+ # @option options [Boolean] :controllers Include controller analysis (default: true)
78
+ # @option options [Boolean] :routes Include route analysis (default: true)
79
+ # @option options [Boolean] :services Include service analysis (default: true)
80
+ # @return [FlowGraph] The analyzed application graph
81
+ # @raise [GraphParseError] If analysis fails
82
+ def analyze(options = {})
83
+ ErrorHandler.with_error_handling("analyze", context: { options: options }) do
84
+ Logging.time_operation("graph_analysis", { analyzers: options.keys }) do
85
+ graph = FlowGraph.new
86
+
87
+ analyzers_run = []
88
+
89
+ if options[:models] != false
90
+ analyzers_run << "models"
91
+ ModelAnalyzer.new(graph).analyze
92
+ end
93
+
94
+ if options[:controllers] != false
95
+ analyzers_run << "controllers"
96
+ ControllerAnalyzer.new(graph).analyze
97
+ end
98
+
99
+ Logging.log_graph_analysis("completed", {
100
+ nodes: graph.nodes.size,
101
+ edges: graph.edges.size,
102
+ analyzers: analyzers_run.join(", ")
103
+ })
104
+
105
+ graph
106
+ end
107
+ end
108
+ end
109
+
110
+ # Exports a flow graph to various visualization formats
111
+ #
112
+ # @param graph [FlowGraph] The graph to export
113
+ # @param format [Symbol] The output format (:mermaid, :plantuml, :graphviz, :erd, :metrics, :d3js, :openapi, :sequence)
114
+ # @param output [String, nil] The output file path (returns string if nil)
115
+ # @param options [Hash] Format-specific options
116
+ # @return [String] The formatted output
117
+ # @raise [FormatterError] If formatting fails
118
+ # @raise [FileOperationError] If file writing fails
119
+ # @raise [InvalidInputError] If input parameters are invalid
120
+ # @raise [SecurityError] If security validation fails
121
+ # @example Export to Mermaid
122
+ # RailsFlowMap.export(graph, format: :mermaid, output: 'flow.md')
123
+ # @example Export to interactive HTML
124
+ # RailsFlowMap.export(graph, format: :d3js, output: 'interactive.html')
125
+ def export(graph, format: :mermaid, output: nil, **options)
126
+ ErrorHandler.with_error_handling("export", context: { format: format, output: output }) do
127
+ # Input validation
128
+ ErrorHandler.validate_input!({
129
+ graph: -> { graph.is_a?(FlowGraph) || graph.nil? },
130
+ format: -> { format.is_a?(Symbol) },
131
+ output: -> { output.nil? || output.is_a?(String) }
132
+ }, context: { operation: "export" })
133
+
134
+ # Security validation for output path
135
+ if output
136
+ validate_output_path!(output)
137
+ end
138
+
139
+ Logging.time_operation("export", { format: format, nodes: graph&.nodes&.size || 0 }) do
140
+ formatter = create_formatter(format, graph, options)
141
+
142
+ Logging.with_context(formatter: formatter.class.name) do
143
+ result = formatter.format(graph)
144
+
145
+ if output
146
+ write_output_file(output, result)
147
+ Logging.logger.info("Successfully exported to #{output}")
148
+ end
149
+
150
+ result
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Compares two graphs and generates a diff visualization
157
+ #
158
+ # @param before_graph [FlowGraph] The "before" state graph
159
+ # @param after_graph [FlowGraph] The "after" state graph
160
+ # @param format [Symbol] The output format (:mermaid, :html, :text)
161
+ # @param options [Hash] Additional options for the formatter
162
+ # @return [String] The formatted diff
163
+ # @example
164
+ # before = RailsFlowMap.analyze_at('main')
165
+ # after = RailsFlowMap.analyze
166
+ # diff = RailsFlowMap.diff(before, after, format: :mermaid)
167
+ def diff(before_graph, after_graph, format: :mermaid, **options)
168
+ formatter = GitDiffFormatter.new(before_graph, after_graph, options.merge(format: format))
169
+ formatter.format
170
+ end
171
+
172
+ # Analyzes the application at a specific Git reference
173
+ #
174
+ # @param ref [String] The Git reference (branch, tag, or commit SHA)
175
+ # @return [FlowGraph] The analyzed graph at the specified reference
176
+ # @note This is currently a placeholder implementation that returns the current state.
177
+ # A full implementation would require Git integration to checkout the ref,
178
+ # analyze, and return to the original state.
179
+ # @example
180
+ # graph = RailsFlowMap.analyze_at('v1.0.0')
181
+ # graph = RailsFlowMap.analyze_at('main')
182
+ # graph = RailsFlowMap.analyze_at('abc123f')
183
+ def analyze_at(ref)
184
+ # Note: This is a placeholder implementation
185
+ # In a real implementation, you would:
186
+ # 1. Save current changes
187
+ # 2. Checkout the specified ref
188
+ # 3. Run analysis
189
+ # 4. Return to original state
190
+ #
191
+ # For now, returns current state
192
+ analyze
193
+ end
194
+
195
+ private
196
+
197
+ # Creates the appropriate formatter instance
198
+ #
199
+ # @param format [Symbol] The format to create
200
+ # @param graph [FlowGraph] The graph to format
201
+ # @param options [Hash] Format-specific options
202
+ # @return [Object] The formatter instance
203
+ # @raise [InvalidInputError] If the format is unsupported
204
+ def create_formatter(format, graph, options)
205
+ case format
206
+ when :mermaid
207
+ MermaidFormatter.new
208
+ when :plantuml
209
+ PlantUMLFormatter.new
210
+ when :graphviz, :dot
211
+ GraphVizFormatter.new
212
+ when :erd
213
+ ErdFormatter.new(graph)
214
+ when :metrics
215
+ MetricsFormatter.new(graph)
216
+ when :d3js
217
+ D3jsFormatter.new(graph, options)
218
+ when :openapi
219
+ OpenapiFormatter.new(graph, options)
220
+ when :sequence
221
+ SequenceFormatter.new(graph, options)
222
+ when :git_diff
223
+ raise InvalidInputError.new(
224
+ "Git diff requires two graphs. Use RailsFlowMap.diff instead",
225
+ context: { format: format }
226
+ )
227
+ else
228
+ raise InvalidInputError.new(
229
+ "Unsupported format: #{format}",
230
+ context: { format: format, supported_formats: [:mermaid, :plantuml, :graphviz, :erd, :metrics, :d3js, :openapi, :sequence] }
231
+ )
232
+ end
233
+ rescue => e
234
+ raise FormatterError.new(
235
+ "Failed to create formatter for format #{format}: #{e.message}",
236
+ context: { format: format, original_error: e.class.name }
237
+ )
238
+ end
239
+
240
+ # Validates that the output path is safe to write to
241
+ #
242
+ # @param output_path [String] The path to validate
243
+ # @raise [SecurityError] If the path is unsafe
244
+ # @raise [FileOperationError] If the path is invalid
245
+ def validate_output_path!(output_path)
246
+ # Check for path traversal attempts
247
+ if output_path.include?('..') && File.absolute_path(output_path).include?('..')
248
+ Logging.log_security("Path traversal attempt blocked", details: { path: output_path })
249
+ raise SecurityError.new(
250
+ "Path traversal detected in output path",
251
+ context: { path: output_path }
252
+ )
253
+ end
254
+
255
+ # Check for dangerous file paths
256
+ dangerous_paths = ['/etc/', '/bin/', '/usr/bin/', '/sbin/', '/usr/sbin/', '/var/', '/boot/', '/proc/', '/sys/', '/dev/']
257
+ absolute_path = File.absolute_path(output_path)
258
+
259
+ if dangerous_paths.any? { |dangerous| absolute_path.start_with?(dangerous) }
260
+ Logging.log_security("Dangerous output path blocked", details: { path: output_path })
261
+ raise SecurityError.new(
262
+ "Output path points to a system directory",
263
+ context: { path: output_path, absolute_path: absolute_path }
264
+ )
265
+ end
266
+
267
+ # Check parent directory exists and is writable
268
+ parent_dir = File.dirname(output_path)
269
+ unless File.directory?(parent_dir)
270
+ raise FileOperationError.new(
271
+ "Parent directory does not exist: #{parent_dir}",
272
+ context: { path: output_path, parent_dir: parent_dir }
273
+ )
274
+ end
275
+
276
+ unless File.writable?(parent_dir)
277
+ raise FileOperationError.new(
278
+ "Parent directory is not writable: #{parent_dir}",
279
+ context: { path: output_path, parent_dir: parent_dir }
280
+ )
281
+ end
282
+ end
283
+
284
+ # Safely writes content to the output file
285
+ #
286
+ # @param output_path [String] The path to write to
287
+ # @param content [String] The content to write
288
+ # @raise [FileOperationError] If writing fails
289
+ def write_output_file(output_path, content)
290
+ begin
291
+ File.write(output_path, content)
292
+ rescue Errno::ENOSPC
293
+ raise FileOperationError.new(
294
+ "No space left on device when writing to #{output_path}",
295
+ context: { path: output_path, content_size: content.bytesize }
296
+ )
297
+ rescue Errno::EACCES
298
+ raise FileOperationError.new(
299
+ "Permission denied when writing to #{output_path}",
300
+ context: { path: output_path }
301
+ )
302
+ rescue => e
303
+ raise FileOperationError.new(
304
+ "Failed to write to #{output_path}: #{e.message}",
305
+ context: { path: output_path, original_error: e.class.name }
306
+ )
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,70 @@
1
+ namespace :rails_flow_map do
2
+ desc "Generate a flow map of your Rails application"
3
+ task :generate, [:format, :output] => :environment do |t, args|
4
+ format = (args[:format] || 'mermaid').to_sym
5
+ output = args[:output]
6
+
7
+ puts "Analyzing Rails application..."
8
+ graph = RailsFlowMap.analyze
9
+
10
+ puts "Found #{graph.nodes.size} nodes and #{graph.edges.size} relationships"
11
+
12
+ result = RailsFlowMap.export(graph, format: format, output: output)
13
+
14
+ if output
15
+ puts "Flow map saved to: #{output}"
16
+ else
17
+ puts "\n#{result}"
18
+ end
19
+ end
20
+
21
+ desc "Generate flow map for models only"
22
+ task :models, [:format, :output] => :environment do |t, args|
23
+ format = (args[:format] || 'mermaid').to_sym
24
+ output = args[:output]
25
+
26
+ puts "Analyzing Rails models..."
27
+ graph = RailsFlowMap.analyze(controllers: false)
28
+
29
+ puts "Found #{graph.nodes.size} models and #{graph.edges.size} relationships"
30
+
31
+ result = RailsFlowMap.export(graph, format: format, output: output)
32
+
33
+ if output
34
+ puts "Model flow map saved to: #{output}"
35
+ else
36
+ puts "\n#{result}"
37
+ end
38
+ end
39
+
40
+ desc "Generate flow map for controllers only"
41
+ task :controllers, [:format, :output] => :environment do |t, args|
42
+ format = (args[:format] || 'mermaid').to_sym
43
+ output = args[:output]
44
+
45
+ puts "Analyzing Rails controllers..."
46
+ graph = RailsFlowMap.analyze(models: false)
47
+
48
+ puts "Found #{graph.nodes.size} controllers/actions"
49
+
50
+ result = RailsFlowMap.export(graph, format: format, output: output)
51
+
52
+ if output
53
+ puts "Controller flow map saved to: #{output}"
54
+ else
55
+ puts "\n#{result}"
56
+ end
57
+ end
58
+
59
+ desc "List all available formats"
60
+ task :formats do
61
+ puts "Available output formats:"
62
+ puts " - mermaid : Mermaid diagram format (default)"
63
+ puts " - plantuml : PlantUML diagram format"
64
+ puts " - graphviz : GraphViz DOT format"
65
+ puts "\nExample usage:"
66
+ puts " rake rails_flow_map:generate[mermaid,flow.md]"
67
+ puts " rake rails_flow_map:models[plantuml,models.puml]"
68
+ puts " rake rails_flow_map:controllers[graphviz,controllers.dot]"
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,156 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-flow-map
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rails Flow Map Team
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-07-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '6.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '13.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '13.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: yard
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.21'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.21'
97
+ description: A comprehensive tool for analyzing and visualizing data flows, dependencies,
98
+ and architecture in Rails applications with multiple output formats.
99
+ email:
100
+ - team@railsflowmap.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - CHANGELOG.md
106
+ - LICENSE
107
+ - README.md
108
+ - README_ja.md
109
+ - README_zh.md
110
+ - lib/rails_flow_map.rb
111
+ - lib/rails_flow_map/analyzers/controller_analyzer.rb
112
+ - lib/rails_flow_map/analyzers/model_analyzer.rb
113
+ - lib/rails_flow_map/configuration.rb
114
+ - lib/rails_flow_map/engine.rb
115
+ - lib/rails_flow_map/errors.rb
116
+ - lib/rails_flow_map/formatters/d3js_formatter.rb
117
+ - lib/rails_flow_map/formatters/erd_formatter.rb
118
+ - lib/rails_flow_map/formatters/git_diff_formatter.rb
119
+ - lib/rails_flow_map/formatters/graphviz_formatter.rb
120
+ - lib/rails_flow_map/formatters/mermaid_formatter.rb
121
+ - lib/rails_flow_map/formatters/metrics_formatter.rb
122
+ - lib/rails_flow_map/formatters/openapi_formatter.rb
123
+ - lib/rails_flow_map/formatters/plantuml_formatter.rb
124
+ - lib/rails_flow_map/formatters/sequence_formatter.rb
125
+ - lib/rails_flow_map/generators/install/templates/rails_flow_map.rb
126
+ - lib/rails_flow_map/generators/install_generator.rb
127
+ - lib/rails_flow_map/logging.rb
128
+ - lib/rails_flow_map/models/flow_edge.rb
129
+ - lib/rails_flow_map/models/flow_graph.rb
130
+ - lib/rails_flow_map/models/flow_node.rb
131
+ - lib/rails_flow_map/version.rb
132
+ - lib/tasks/rails_flow_map.rake
133
+ homepage: https://github.com/railsflowmap/rails-flow-map
134
+ licenses:
135
+ - MIT
136
+ metadata: {}
137
+ post_install_message:
138
+ rdoc_options: []
139
+ require_paths:
140
+ - lib
141
+ required_ruby_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.7.0
146
+ required_rubygems_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ requirements: []
152
+ rubygems_version: 3.0.3.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: Visualize data flows in Rails applications
156
+ test_files: []