activerecord_callback_lens 0.2.1 → 0.3.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/README.md +24 -1
- data/lib/activerecord_callback_lens/cli/cli.rb +12 -5
- data/lib/activerecord_callback_lens/renderer/graphviz_renderer.rb +95 -0
- data/lib/activerecord_callback_lens/tasks/callback_lens.rake +8 -0
- data/lib/activerecord_callback_lens/tasks/callback_lens_helpers.rb +20 -0
- data/lib/activerecord_callback_lens/version.rb +1 -1
- data/lib/activerecord_callback_lens.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 93034e63e1e99dfe1e6ea64193460d4a206bdf88a01e1c6606385e48228e292c
|
|
4
|
+
data.tar.gz: d5fae7891b074b7459d7127d9b34befbf8345bd6ee27cef938e0cf0215adddc5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a29d788fe927d7c24b82e70965219b3dfdebdd04a6fa36d1b05d0b968f9265a4b280a0e5e8f67436681cd71a80cbddc0ee993f1fe76befcc3bd698aa7371daf
|
|
7
|
+
data.tar.gz: 0dfa3cc6f1ebac81b1b8469ce70a1a35482ff948bab5462d3ed777d64843ee948dbeea89fd208d32b8efa24fe54f91a59ff50b7116b1d7fdc25141e9e3b1ba8e
|
data/README.md
CHANGED
|
@@ -76,6 +76,29 @@ rake callback_lens:mermaid MODEL=User EXPAND=true
|
|
|
76
76
|
Only the exact value `EXPAND=true` (case-insensitive) enables expansion; any
|
|
77
77
|
other value (`EXPAND=1`, `EXPAND=yes`, empty, or absent) leaves it off.
|
|
78
78
|
|
|
79
|
+
#### Graphviz DOT output
|
|
80
|
+
|
|
81
|
+
Pass `--graphviz` (CLI) or use `callback_lens:graphviz` (Rake) to output a
|
|
82
|
+
[Graphviz DOT](https://graphviz.org/) diagram to stdout. Pipe it to `dot` to
|
|
83
|
+
generate an image:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# CLI — print DOT to stdout
|
|
87
|
+
callback_lens analyze User --graphviz
|
|
88
|
+
|
|
89
|
+
# CLI — combine with Mermaid output
|
|
90
|
+
callback_lens analyze User --mermaid --graphviz
|
|
91
|
+
|
|
92
|
+
# CLI — pipe to dot for a PNG
|
|
93
|
+
callback_lens analyze User --graphviz | dot -Tpng -o callbacks.png
|
|
94
|
+
|
|
95
|
+
# Rake
|
|
96
|
+
rake callback_lens:graphviz MODEL=User
|
|
97
|
+
rake callback_lens:graphviz MODEL=User EXPAND=true
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Requires [Graphviz](https://graphviz.org/download/) only when piping to `dot`.
|
|
101
|
+
|
|
79
102
|
### Programmatic API
|
|
80
103
|
|
|
81
104
|
```ruby
|
|
@@ -119,7 +142,7 @@ puts ActiverecordCallbackLens::Renderer::MermaidRenderer.render(graph)
|
|
|
119
142
|
|---|---|
|
|
120
143
|
| v0.1 | CallbackCollector, Prism condition parser, Mermaid renderer, CLI, Rake task |
|
|
121
144
|
| **v0.2** | MethodResolver — recursive expansion of Symbol conditions (`--expand` / `EXPAND=true`) |
|
|
122
|
-
| v0.3 | Graphviz / DOT renderer |
|
|
145
|
+
| **v0.3** | Graphviz / DOT renderer (`--graphviz` / `callback_lens:graphviz`) |
|
|
123
146
|
| v0.4 | HTML report (callback list, execution flow, embedded diagram) |
|
|
124
147
|
| v1.0 | Runtime tracer via `ActiveSupport::Notifications` |
|
|
125
148
|
| v2.0 | RBS analysis, cross-model dependency graph |
|
|
@@ -7,15 +7,19 @@ require_relative "../parser/condition_parser"
|
|
|
7
7
|
require_relative "../resolver/method_resolver"
|
|
8
8
|
require_relative "../graph/graph_builder"
|
|
9
9
|
require_relative "../renderer/mermaid_renderer"
|
|
10
|
+
require_relative "../renderer/graphviz_renderer"
|
|
10
11
|
|
|
11
12
|
module ActiverecordCallbackLens
|
|
12
13
|
module CLI
|
|
13
14
|
# Thor application exposing the callback_lens command-line interface.
|
|
14
15
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
16
|
+
# It provides a single command, +analyze+, which runs the full pipeline
|
|
17
|
+
# (collect -> parse -> build graph -> render) and prints the result to
|
|
18
|
+
# stdout. Output format is selectable per invocation: a Mermaid diagram
|
|
19
|
+
# (+--mermaid+, on by default) and/or a Graphviz DOT graph (+--graphviz+);
|
|
20
|
+
# the two flags are independent and may be combined. An unknown model name
|
|
21
|
+
# is reported with a friendly message and a non-zero exit status rather than
|
|
22
|
+
# a Ruby backtrace.
|
|
19
23
|
class App < Thor
|
|
20
24
|
# Tells Thor to exit with a non-zero status when a command raises, so the
|
|
21
25
|
# +exit 1+ paths below propagate a failure code to the shell.
|
|
@@ -25,8 +29,10 @@ module ActiverecordCallbackLens
|
|
|
25
29
|
true
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
desc "analyze MODEL", "Analyze callbacks for a model class and print a Mermaid diagram"
|
|
32
|
+
desc "analyze MODEL", "Analyze callbacks for a model class and print a Mermaid and/or Graphviz diagram"
|
|
29
33
|
option :mermaid, type: :boolean, default: true, desc: "Output a Mermaid diagram to stdout"
|
|
34
|
+
option :graphviz, type: :boolean, default: false,
|
|
35
|
+
desc: "Output DOT graph via Graphviz to stdout"
|
|
30
36
|
option :expand, type: :boolean, default: false,
|
|
31
37
|
desc: "Expand method conditions recursively (up to depth 5)"
|
|
32
38
|
# Runs the analysis pipeline for +model_name+ and prints the result.
|
|
@@ -37,6 +43,7 @@ module ActiverecordCallbackLens
|
|
|
37
43
|
model_class = resolve_model(model_name)
|
|
38
44
|
graph = build_graph(model_class, expand: options[:expand])
|
|
39
45
|
puts Renderer::MermaidRenderer.render(graph) if options[:mermaid]
|
|
46
|
+
puts Renderer::GraphvizRenderer.render(graph) if options[:graphviz]
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
private
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../graph/nodes"
|
|
4
|
+
|
|
5
|
+
module ActiverecordCallbackLens
|
|
6
|
+
module Renderer
|
|
7
|
+
# Renders a Graph::Graph as a Graphviz DOT language string.
|
|
8
|
+
#
|
|
9
|
+
# The output is a `digraph` block with a left-to-right rank direction, one
|
|
10
|
+
# declaration per node and one arrow per edge:
|
|
11
|
+
#
|
|
12
|
+
# digraph callback_lens {
|
|
13
|
+
# rankdir=LR;
|
|
14
|
+
# n0 [label="before_save"];
|
|
15
|
+
# n1 [label="active?"];
|
|
16
|
+
# n1 -> n0;
|
|
17
|
+
# }
|
|
18
|
+
#
|
|
19
|
+
# Node labels are derived from the node type (see #node_label) using the same
|
|
20
|
+
# four-case logic as MermaidRenderer. Double quotes in a label are escaped as
|
|
21
|
+
# `\"` so they cannot break the surrounding DOT label syntax.
|
|
22
|
+
class GraphvizRenderer
|
|
23
|
+
# @param graph [Graph::Graph]
|
|
24
|
+
# @return [String] DOT language string
|
|
25
|
+
def self.render(graph)
|
|
26
|
+
new(graph).render
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param graph [Graph::Graph]
|
|
30
|
+
def initialize(graph)
|
|
31
|
+
@graph = graph
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @return [String] DOT language string
|
|
35
|
+
def render
|
|
36
|
+
lines = ["digraph callback_lens {", " rankdir=LR;"]
|
|
37
|
+
lines.concat(node_declarations)
|
|
38
|
+
lines.concat(edge_declarations)
|
|
39
|
+
lines << "}"
|
|
40
|
+
lines.join("\n")
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Shells out to the `dot` binary and returns the rendered SVG string.
|
|
44
|
+
# Returns nil when Graphviz is not installed (the `dot` binary is absent
|
|
45
|
+
# from PATH) rather than raising.
|
|
46
|
+
#
|
|
47
|
+
# @return [String, nil]
|
|
48
|
+
def to_svg
|
|
49
|
+
IO.popen(["dot", "-Tsvg"], "r+") do |io|
|
|
50
|
+
io.write(render)
|
|
51
|
+
io.close_write
|
|
52
|
+
io.read
|
|
53
|
+
end
|
|
54
|
+
rescue Errno::ENOENT
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# @return [Array<String>]
|
|
61
|
+
def node_declarations
|
|
62
|
+
@graph.nodes.map { |node| " #{node.id} [label=\"#{escape(node_label(node))}\"];" }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Array<String>]
|
|
66
|
+
def edge_declarations
|
|
67
|
+
@graph.edges.map { |edge| " #{edge.from_id} -> #{edge.to_id};" }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Derives the human-readable label for a graph node from its type.
|
|
71
|
+
#
|
|
72
|
+
# @param node [Graph::CallbackNode, Graph::PredicateNode, Graph::MethodNode, Graph::ConditionNode]
|
|
73
|
+
# @return [String]
|
|
74
|
+
def node_label(node)
|
|
75
|
+
case node
|
|
76
|
+
when Graph::CallbackNode then "#{node.definition.phase}_#{node.definition.event}"
|
|
77
|
+
when Graph::PredicateNode then node.predicate_name
|
|
78
|
+
when Graph::MethodNode then node.method_name
|
|
79
|
+
when Graph::ConditionNode then node.tree_node.class.name.split("::").last
|
|
80
|
+
else node.id
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Escapes characters that would otherwise break a `label="..."` DOT label.
|
|
85
|
+
# Double quotes become a backslash-escaped quote so the label stays a
|
|
86
|
+
# single, valid DOT token. (DOT uses `\"`, unlike Mermaid's `"`.)
|
|
87
|
+
#
|
|
88
|
+
# @param label [String]
|
|
89
|
+
# @return [String]
|
|
90
|
+
def escape(label)
|
|
91
|
+
label.to_s.gsub("\"", "\\\"")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -17,4 +17,12 @@ namespace :callback_lens do
|
|
|
17
17
|
expand = CallbackLensRakeHelpers.expand?(ENV.fetch("EXPAND", nil))
|
|
18
18
|
puts CallbackLensRakeHelpers.render_mermaid(model_class, expand: expand)
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
desc "Write Graphviz DOT to STDOUT for MODEL " \
|
|
22
|
+
"(e.g. rake callback_lens:graphviz MODEL=User EXPAND=true)"
|
|
23
|
+
task graphviz: :environment do
|
|
24
|
+
model_class = CallbackLensRakeHelpers.resolve_model!(ENV.fetch("MODEL", nil))
|
|
25
|
+
expand = CallbackLensRakeHelpers.expand?(ENV.fetch("EXPAND", nil))
|
|
26
|
+
puts CallbackLensRakeHelpers.render_graphviz(model_class, expand: expand)
|
|
27
|
+
end
|
|
20
28
|
end
|
|
@@ -46,6 +46,26 @@ module CallbackLensRakeHelpers
|
|
|
46
46
|
ActiverecordCallbackLens::Renderer::MermaidRenderer.render(graph)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Runs the full pipeline (collect -> parse -> [expand] -> build -> render) for
|
|
50
|
+
# a model and returns a Graphviz DOT language string. Mirrors +render_mermaid+
|
|
51
|
+
# but uses the GraphvizRenderer; +expand+ is threaded through identically so
|
|
52
|
+
# the +EXPAND=true+ rake convention applies to the graphviz task too.
|
|
53
|
+
#
|
|
54
|
+
# @param model_class [Class]
|
|
55
|
+
# @param expand [Boolean]
|
|
56
|
+
# @return [String] DOT language string
|
|
57
|
+
def render_graphviz(model_class, expand: false)
|
|
58
|
+
definitions = ActiverecordCallbackLens::Collector::CallbackCollector.collect(model_class)
|
|
59
|
+
definitions = definitions.map { |definition| ActiverecordCallbackLens::Parser::ConditionParser.parse(definition) }
|
|
60
|
+
if expand
|
|
61
|
+
definitions = definitions.map do |definition|
|
|
62
|
+
ActiverecordCallbackLens::Resolver::MethodResolver.expand(definition, model_class)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
graph = ActiverecordCallbackLens::Graph::GraphBuilder.build(definitions)
|
|
66
|
+
ActiverecordCallbackLens::Renderer::GraphvizRenderer.render(graph)
|
|
67
|
+
end
|
|
68
|
+
|
|
49
69
|
# Parses the EXPAND environment variable using the strict truthy rule: only the
|
|
50
70
|
# exact string "true" (case-insensitive, surrounding whitespace stripped)
|
|
51
71
|
# enables expansion. Any other value ("1", "yes", "", nil) leaves it off.
|
|
@@ -10,6 +10,7 @@ require "activerecord_callback_lens/resolver/method_resolver"
|
|
|
10
10
|
require "activerecord_callback_lens/graph/nodes"
|
|
11
11
|
require "activerecord_callback_lens/graph/graph_builder"
|
|
12
12
|
require "activerecord_callback_lens/renderer/mermaid_renderer"
|
|
13
|
+
require "activerecord_callback_lens/renderer/graphviz_renderer"
|
|
13
14
|
require "activerecord_callback_lens/cli/cli"
|
|
14
15
|
require "activerecord_callback_lens/railtie" if defined?(Rails::Railtie)
|
|
15
16
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord_callback_lens
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Eraxel.Dev
|
|
@@ -81,6 +81,7 @@ files:
|
|
|
81
81
|
- lib/activerecord_callback_lens/parser/condition_parser.rb
|
|
82
82
|
- lib/activerecord_callback_lens/parser/condition_tree.rb
|
|
83
83
|
- lib/activerecord_callback_lens/railtie.rb
|
|
84
|
+
- lib/activerecord_callback_lens/renderer/graphviz_renderer.rb
|
|
84
85
|
- lib/activerecord_callback_lens/renderer/mermaid_renderer.rb
|
|
85
86
|
- lib/activerecord_callback_lens/resolver/method_resolver.rb
|
|
86
87
|
- lib/activerecord_callback_lens/tasks/callback_lens.rake
|