rigor-module-graph 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,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module ModuleGraph
5
+ # Converts a Ruby source path into the fully-qualified constant
6
+ # the Zeitwerk convention says it should define.
7
+ #
8
+ # Pure function, no I/O. The plugin instantiates one per run from
9
+ # `.rigor.yml` config and asks for `resolve(path)` per file. Two
10
+ # configuration knobs:
11
+ #
12
+ # - `autoload_paths`: roots stripped from the path before
13
+ # camelising. Defaults to the standard Rails layout.
14
+ # - `concern_dirs`: directories that act as transparent
15
+ # namespaces under Zeitwerk (`app/models/concerns/auditable.rb`
16
+ # resolves to `Auditable`, not `Concerns::Auditable`).
17
+ #
18
+ # The resolver is order-sensitive: longer / more specific roots
19
+ # MUST be tried before their parents so `app/models/concerns/foo.rb`
20
+ # picks up the concern root, not `app/models`. We sort by length
21
+ # descending at construction time, so config order does not matter.
22
+ class ZeitwerkResolver
23
+ DEFAULT_AUTOLOAD_PATHS = %w[
24
+ app/models
25
+ app/controllers
26
+ app/services
27
+ app/jobs
28
+ app/mailers
29
+ app/helpers
30
+ app/channels
31
+ app/workers
32
+ lib
33
+ ].freeze
34
+
35
+ DEFAULT_CONCERN_DIRS = %w[
36
+ app/models/concerns
37
+ app/controllers/concerns
38
+ ].freeze
39
+
40
+ attr_reader :autoload_paths, :concern_dirs
41
+
42
+ def initialize(autoload_paths: DEFAULT_AUTOLOAD_PATHS,
43
+ concern_dirs: DEFAULT_CONCERN_DIRS,
44
+ project_root: nil)
45
+ @project_root = project_root && File.expand_path(project_root)
46
+ @autoload_paths = normalise_roots(autoload_paths)
47
+ @concern_dirs = normalise_roots(concern_dirs)
48
+ @sorted_roots = (@concern_dirs + @autoload_paths).sort_by { |r| -r.length }.uniq
49
+ end
50
+
51
+ # @param path [String] either relative to the project root or
52
+ # absolute. Both `app/models/billing/invoice.rb` and the
53
+ # `realpath` form work.
54
+ # @return [String, nil] the inferred constant name, or nil when
55
+ # the path is not under any configured root or has no .rb
56
+ # extension.
57
+ def resolve(path)
58
+ return nil unless path
59
+
60
+ rel = relativise(path)
61
+ return nil unless rel
62
+ return nil unless rel.end_with?(".rb")
63
+
64
+ root = @sorted_roots.find { |r| rel.start_with?(r + "/") }
65
+ return nil unless root
66
+
67
+ suffix = rel[(root.length + 1)..]
68
+ camelise_path(suffix.delete_suffix(".rb"))
69
+ end
70
+
71
+ # True when `inferred` matches the (probably syntax-derived)
72
+ # `actual` constant under Zeitwerk's conventions. We compare
73
+ # ignoring leading "::" since absolute / relative are not a
74
+ # meaningful distinction here.
75
+ def matches?(actual, inferred)
76
+ return false if actual.nil? || inferred.nil?
77
+
78
+ strip_leading(actual) == strip_leading(inferred)
79
+ end
80
+
81
+ def relativise(path)
82
+ absolute = File.expand_path(path)
83
+ if @project_root && absolute.start_with?(@project_root + "/")
84
+ absolute[(@project_root.length + 1)..]
85
+ elsif path.start_with?("/")
86
+ # Absolute path with no project root configured: try every
87
+ # autoload root as a suffix match. Used by integration runs
88
+ # where files live in a tmpdir.
89
+ suffix = @sorted_roots.find { |r| absolute.include?("/" + r + "/") }
90
+ if suffix
91
+ idx = absolute.rindex("/" + suffix + "/")
92
+ absolute[(idx + 1)..]
93
+ end
94
+ else
95
+ path
96
+ end
97
+ end
98
+
99
+ def normalise_roots(roots)
100
+ Array(roots).map { |r| r.to_s.sub(%r{/+\z}, "") }.reject(&:empty?).freeze
101
+ end
102
+
103
+ def strip_leading(name)
104
+ name.sub(/\A::/, "")
105
+ end
106
+
107
+ def camelise_path(rel_no_ext)
108
+ rel_no_ext.split("/").map { |seg| camelise_segment(seg) }.join("::")
109
+ end
110
+
111
+ def camelise_segment(segment)
112
+ segment.split("_").map(&:capitalize).join
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # == rigor-module-graph
4
+ #
5
+ # Class/module/constant dependency graph for Ruby projects, built
6
+ # on Rigor[https://rigor.typedduck.fail/]. Loading this file pulls
7
+ # in every public piece of the gem: the Edge value type, the
8
+ # Analyzer, the renderers (Dot/Mermaid/CycleDetector), and — when
9
+ # +Rigor::Plugin::Base+ is already defined — the Rigor plugin that
10
+ # wires the node rules.
11
+ #
12
+ # Most users interact with this gem through the +rigor-module-graph+
13
+ # command-line wrapper (see Rigor::ModuleGraph::CLI), not by
14
+ # requiring it directly.
15
+
16
+ require_relative "rigor/module_graph/version"
17
+ require_relative "rigor/module_graph/edge"
18
+ require_relative "rigor/module_graph/node"
19
+ require_relative "rigor/module_graph/constant_name"
20
+ require_relative "rigor/module_graph/zeitwerk_resolver"
21
+ require_relative "rigor/module_graph/inflector"
22
+ require_relative "rigor/module_graph/visibility_map"
23
+ require_relative "rigor/module_graph/analyzer"
24
+ require_relative "rigor/module_graph/dot"
25
+ require_relative "rigor/module_graph/mermaid"
26
+ require_relative "rigor/module_graph/cycle_detector"
27
+ require_relative "rigor/module_graph/reachability"
28
+ require_relative "rigor/module_graph/stats"
29
+ require_relative "rigor/module_graph/packwerk_overlay"
30
+ require_relative "rigor/module_graph/uml/class_diagram"
31
+ require_relative "rigor/module_graph/html_view"
32
+ require_relative "rigor/module_graph/plugin"
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rigor-module-graph
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nozomi Hijikata
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rigortype
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.2.1
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.2.1
26
+ - !ruby/object:Gem::Dependency
27
+ name: rbs
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '4.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '4.0'
40
+ description: |
41
+ Rigor plugin and CLI that extract Ruby class/module/constant dependencies
42
+ (inheritance, include/prepend/extend, constant references) and emit
43
+ Graphviz DOT, SVG, and Mermaid output. The class/module-level
44
+ counterpart to Packwerk/Graphwerk.
45
+ email:
46
+ - b8yukifsukeo999n@gmail.com
47
+ executables:
48
+ - rigor-module-graph
49
+ extensions: []
50
+ extra_rdoc_files:
51
+ - README.md
52
+ files:
53
+ - CHANGELOG.md
54
+ - LICENSE.txt
55
+ - README.md
56
+ - exe/rigor-module-graph
57
+ - lib/rigor-module-graph.rb
58
+ - lib/rigor/module_graph/analyzer.rb
59
+ - lib/rigor/module_graph/cli.rb
60
+ - lib/rigor/module_graph/constant_name.rb
61
+ - lib/rigor/module_graph/cycle_detector.rb
62
+ - lib/rigor/module_graph/dot.rb
63
+ - lib/rigor/module_graph/edge.rb
64
+ - lib/rigor/module_graph/html_view.rb
65
+ - lib/rigor/module_graph/inflector.rb
66
+ - lib/rigor/module_graph/mermaid.rb
67
+ - lib/rigor/module_graph/node.rb
68
+ - lib/rigor/module_graph/packwerk_overlay.rb
69
+ - lib/rigor/module_graph/plugin.rb
70
+ - lib/rigor/module_graph/plugin/rigor_plugin.rb
71
+ - lib/rigor/module_graph/reachability.rb
72
+ - lib/rigor/module_graph/stats.rb
73
+ - lib/rigor/module_graph/templates/view.html.erb
74
+ - lib/rigor/module_graph/uml/class_diagram.rb
75
+ - lib/rigor/module_graph/version.rb
76
+ - lib/rigor/module_graph/visibility_map.rb
77
+ - lib/rigor/module_graph/zeitwerk_resolver.rb
78
+ licenses:
79
+ - MIT
80
+ metadata:
81
+ documentation_uri: https://rubydoc.info/gems/rigor-module-graph
82
+ changelog_uri: https://github.com/nozomemein/rigor-module-graph/blob/main/CHANGELOG.md
83
+ source_code_uri: https://github.com/nozomemein/rigor-module-graph
84
+ bug_tracker_uri: https://github.com/nozomemein/rigor-module-graph/issues
85
+ homepage_uri: https://github.com/nozomemein/rigor-module-graph
86
+ rubygems_mfa_required: 'true'
87
+ rdoc_options:
88
+ - "--main"
89
+ - README.md
90
+ - "--markup"
91
+ - rdoc
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: 4.0.0
99
+ - - "<"
100
+ - !ruby/object:Gem::Version
101
+ version: '4.1'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 4.0.3
109
+ specification_version: 4
110
+ summary: Class/module/constant dependency graph for Ruby projects, built on Rigor.
111
+ test_files: []