rigor-module-graph 0.1.2 → 0.1.3
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/CHANGELOG.md +79 -1
- data/README.md +49 -28
- data/lib/rigor/module_graph/cli.rb +48 -4
- data/lib/rigor/module_graph/templates/vendor/CHECKSUMS +59 -0
- data/lib/rigor/module_graph/templates/vendor/cytoscape.min.js +31 -0
- data/lib/rigor/module_graph/templates/viewer.css +63 -0
- data/lib/rigor/module_graph/templates/viewer.html.erb +32 -0
- data/lib/rigor/module_graph/templates/viewer.js +166 -0
- data/lib/rigor/module_graph/version.rb +1 -1
- data/lib/rigor/module_graph/viewer/html.rb +132 -0
- data/lib/rigor-module-graph.rb +1 -0
- metadata +9 -3
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
* { box-sizing: border-box; }
|
|
2
|
+
html, body {
|
|
3
|
+
margin: 0;
|
|
4
|
+
padding: 0;
|
|
5
|
+
height: 100%;
|
|
6
|
+
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Hiragino Sans", "Yu Gothic", sans-serif;
|
|
7
|
+
color: #0f172a;
|
|
8
|
+
}
|
|
9
|
+
body { display: flex; flex-direction: column; }
|
|
10
|
+
|
|
11
|
+
header {
|
|
12
|
+
padding: 0.5rem 1rem;
|
|
13
|
+
background: #f8fafc;
|
|
14
|
+
border-bottom: 1px solid #cbd5e1;
|
|
15
|
+
flex: 0 0 auto;
|
|
16
|
+
}
|
|
17
|
+
header h1 { margin: 0 0 0.25rem; font-size: 1rem; font-weight: 600; }
|
|
18
|
+
header .subtitle { margin: 0 0 0.5rem; color: #64748b; font-size: 0.85rem; }
|
|
19
|
+
|
|
20
|
+
#controls {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
gap: 0.75rem;
|
|
24
|
+
align-items: center;
|
|
25
|
+
}
|
|
26
|
+
fieldset {
|
|
27
|
+
margin: 0;
|
|
28
|
+
padding: 0.2rem 0.5rem;
|
|
29
|
+
border: 1px solid #cbd5e1;
|
|
30
|
+
border-radius: 3px;
|
|
31
|
+
}
|
|
32
|
+
fieldset legend { font-size: 0.7rem; color: #64748b; padding: 0 0.25rem; }
|
|
33
|
+
fieldset label {
|
|
34
|
+
font-size: 0.8rem;
|
|
35
|
+
margin-right: 0.5rem;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
user-select: none;
|
|
38
|
+
}
|
|
39
|
+
fieldset label input { margin-right: 0.15rem; }
|
|
40
|
+
#search {
|
|
41
|
+
padding: 0.25rem 0.5rem;
|
|
42
|
+
border: 1px solid #cbd5e1;
|
|
43
|
+
border-radius: 3px;
|
|
44
|
+
font-size: 0.85rem;
|
|
45
|
+
min-width: 14rem;
|
|
46
|
+
}
|
|
47
|
+
#fit {
|
|
48
|
+
padding: 0.25rem 0.6rem;
|
|
49
|
+
border: 1px solid #cbd5e1;
|
|
50
|
+
background: #fff;
|
|
51
|
+
border-radius: 3px;
|
|
52
|
+
font-size: 0.8rem;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
}
|
|
55
|
+
#counts {
|
|
56
|
+
color: #64748b;
|
|
57
|
+
font-size: 0.8rem;
|
|
58
|
+
margin-left: auto;
|
|
59
|
+
font-variant-numeric: tabular-nums;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main { flex: 1 1 auto; min-height: 0; }
|
|
63
|
+
#cy { width: 100%; height: 100%; background: #fff; }
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<%# CSP: only self + inline (we vetted the inline JS). No
|
|
7
|
+
network fetches at view time — the vendored cytoscape and
|
|
8
|
+
our viewer.js are both inline below. -%>
|
|
9
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:">
|
|
10
|
+
<title><%= ERB::Util.html_escape(title) %></title>
|
|
11
|
+
<style><%= css %></style>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header>
|
|
15
|
+
<h1><%= ERB::Util.html_escape(title) %></h1>
|
|
16
|
+
<% if subtitle %><p class="subtitle"><%= ERB::Util.html_escape(subtitle) %></p><% end %>
|
|
17
|
+
<div id="controls">
|
|
18
|
+
<fieldset id="filter-kind"><legend>kind</legend></fieldset>
|
|
19
|
+
<fieldset id="filter-confidence"><legend>confidence</legend></fieldset>
|
|
20
|
+
<input id="search" type="search" placeholder="search nodes...">
|
|
21
|
+
<button id="fit" type="button">fit</button>
|
|
22
|
+
<span id="counts"></span>
|
|
23
|
+
</div>
|
|
24
|
+
</header>
|
|
25
|
+
<main>
|
|
26
|
+
<div id="cy"></div>
|
|
27
|
+
</main>
|
|
28
|
+
<script type="application/json" id="rmg-data"><%= data_json %></script>
|
|
29
|
+
<script><%= cytoscape %></script>
|
|
30
|
+
<script><%= viewer %></script>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Cytoscape viewer init. Reads the {nodes, edges, options}
|
|
2
|
+
// payload that Viewer::Html emitted into the inline JSON tag,
|
|
3
|
+
// then wires filter / search / click handlers.
|
|
4
|
+
//
|
|
5
|
+
// Kept short on purpose: total review surface for the
|
|
6
|
+
// interactivity layer is this file plus the vendored
|
|
7
|
+
// cytoscape.min.js (sha256-pinned). See docs/plan.md for the
|
|
8
|
+
// supply-chain rationale.
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
const data = JSON.parse(document.getElementById("rmg-data").textContent);
|
|
13
|
+
const options = data.options || {};
|
|
14
|
+
|
|
15
|
+
const cy = cytoscape({
|
|
16
|
+
container: document.getElementById("cy"),
|
|
17
|
+
elements: { nodes: data.nodes, edges: data.edges },
|
|
18
|
+
style: [
|
|
19
|
+
{ selector: "node",
|
|
20
|
+
style: {
|
|
21
|
+
"label": "data(name)",
|
|
22
|
+
"font-size": "10px",
|
|
23
|
+
"background-color": "#f8fafc",
|
|
24
|
+
"border-color": "#94a3b8",
|
|
25
|
+
"border-width": 1,
|
|
26
|
+
"shape": "round-rectangle",
|
|
27
|
+
"padding": "4px",
|
|
28
|
+
"text-valign": "center",
|
|
29
|
+
"text-halign": "center"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{ selector: 'node[kind = "external"]',
|
|
33
|
+
style: { "background-color": "#e2e8f0", "color": "#64748b" }
|
|
34
|
+
},
|
|
35
|
+
{ selector: "edge",
|
|
36
|
+
style: {
|
|
37
|
+
"width": 1,
|
|
38
|
+
"line-color": "#94a3b8",
|
|
39
|
+
"target-arrow-color": "#94a3b8",
|
|
40
|
+
"target-arrow-shape": "triangle",
|
|
41
|
+
"curve-style": "bezier",
|
|
42
|
+
"label": "data(kind)",
|
|
43
|
+
"font-size": "8px",
|
|
44
|
+
"color": "#64748b",
|
|
45
|
+
"text-rotation": "autorotate",
|
|
46
|
+
"text-background-color": "#fff",
|
|
47
|
+
"text-background-padding": "2px",
|
|
48
|
+
"text-background-opacity": 0.9
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
{ selector: 'edge[kind = "inherits"]',
|
|
52
|
+
style: { "line-color": "#0f172a", "target-arrow-color": "#0f172a", "width": 2 }
|
|
53
|
+
},
|
|
54
|
+
{ selector: 'edge[kind = "include"]',
|
|
55
|
+
style: { "line-color": "#1d4ed8", "target-arrow-color": "#1d4ed8" }
|
|
56
|
+
},
|
|
57
|
+
{ selector: 'edge[kind = "prepend"]',
|
|
58
|
+
style: { "line-color": "#9333ea", "target-arrow-color": "#9333ea" }
|
|
59
|
+
},
|
|
60
|
+
{ selector: 'edge[kind = "extend"]',
|
|
61
|
+
style: { "line-color": "#0f766e", "target-arrow-color": "#0f766e", "line-style": "dashed" }
|
|
62
|
+
},
|
|
63
|
+
{ selector: 'edge[kind = "const_ref"]',
|
|
64
|
+
style: { "line-color": "#94a3b8", "target-arrow-color": "#94a3b8", "line-style": "dotted" }
|
|
65
|
+
},
|
|
66
|
+
{ selector: 'edge[kind = "association"]',
|
|
67
|
+
style: { "line-color": "#0891b2", "target-arrow-color": "#0891b2" }
|
|
68
|
+
},
|
|
69
|
+
{ selector: ".filtered-out", style: { "display": "none" } },
|
|
70
|
+
{ selector: ".search-dim", style: { "opacity": 0.15 } }
|
|
71
|
+
],
|
|
72
|
+
layout: { name: "cose", animate: false, nodeDimensionsIncludeLabels: true }
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Distinct kind / confidence values present in this dataset
|
|
76
|
+
// drive the checkbox fieldsets — never hard-coded.
|
|
77
|
+
function uniqValues(attr) {
|
|
78
|
+
return Array.from(new Set(data.edges.map(e => e.data[attr]))).sort();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildCheckboxes(fieldsetId, values) {
|
|
82
|
+
const fs = document.getElementById(fieldsetId);
|
|
83
|
+
values.forEach(v => {
|
|
84
|
+
const label = document.createElement("label");
|
|
85
|
+
const input = document.createElement("input");
|
|
86
|
+
input.type = "checkbox";
|
|
87
|
+
input.value = v;
|
|
88
|
+
input.checked = true;
|
|
89
|
+
input.addEventListener("change", applyFilters);
|
|
90
|
+
label.appendChild(input);
|
|
91
|
+
label.appendChild(document.createTextNode(" " + v));
|
|
92
|
+
fs.appendChild(label);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function selectedValues(fieldsetId) {
|
|
97
|
+
return new Set(
|
|
98
|
+
Array.from(document.querySelectorAll("#" + fieldsetId + " input:checked"))
|
|
99
|
+
.map(i => i.value)
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function applyFilters() {
|
|
104
|
+
const okKinds = selectedValues("filter-kind");
|
|
105
|
+
const okConfs = selectedValues("filter-confidence");
|
|
106
|
+
cy.batch(() => {
|
|
107
|
+
cy.edges().forEach(e => {
|
|
108
|
+
const ok = okKinds.has(e.data("kind")) && okConfs.has(e.data("confidence"));
|
|
109
|
+
e.toggleClass("filtered-out", !ok);
|
|
110
|
+
});
|
|
111
|
+
cy.nodes().forEach(n => {
|
|
112
|
+
// Hide nodes with no visible incident edges so the graph
|
|
113
|
+
// doesn't carry orphaned constants the user can't relate
|
|
114
|
+
// to anything via the current filter.
|
|
115
|
+
const visibleEdges = n.connectedEdges(":not(.filtered-out)");
|
|
116
|
+
n.toggleClass("filtered-out", visibleEdges.length === 0);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
updateCounts();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function applySearch() {
|
|
123
|
+
const q = document.getElementById("search").value.trim().toLowerCase();
|
|
124
|
+
cy.batch(() => {
|
|
125
|
+
if (q === "") {
|
|
126
|
+
cy.elements().removeClass("search-dim");
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
cy.nodes().forEach(n => {
|
|
130
|
+
n.toggleClass("search-dim", !n.data("name").toLowerCase().includes(q));
|
|
131
|
+
});
|
|
132
|
+
cy.edges().forEach(e => {
|
|
133
|
+
const src = e.source().data("name").toLowerCase();
|
|
134
|
+
const tgt = e.target().data("name").toLowerCase();
|
|
135
|
+
e.toggleClass("search-dim", !(src.includes(q) || tgt.includes(q)));
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function updateCounts() {
|
|
141
|
+
const nVisible = cy.nodes(":visible").length;
|
|
142
|
+
const eVisible = cy.edges(":visible").length;
|
|
143
|
+
document.getElementById("counts").textContent =
|
|
144
|
+
nVisible + " nodes, " + eVisible + " edges";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function handleNodeTap(evt) {
|
|
148
|
+
const n = evt.target;
|
|
149
|
+
const path = n.data("path");
|
|
150
|
+
if (!path) return;
|
|
151
|
+
const line = n.data("line");
|
|
152
|
+
const ref = line ? path + ":" + line : path;
|
|
153
|
+
if (options.open_with === "vscode") {
|
|
154
|
+
window.location.href = "vscode://file/" + ref;
|
|
155
|
+
} else if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
156
|
+
navigator.clipboard.writeText(ref).catch(() => {});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
buildCheckboxes("filter-kind", uniqValues("kind"));
|
|
161
|
+
buildCheckboxes("filter-confidence", uniqValues("confidence"));
|
|
162
|
+
document.getElementById("search").addEventListener("input", applySearch);
|
|
163
|
+
document.getElementById("fit").addEventListener("click", () => cy.fit());
|
|
164
|
+
cy.on("tap", "node", handleNodeTap);
|
|
165
|
+
updateCounts();
|
|
166
|
+
})();
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
module ModuleGraph
|
|
8
|
+
# Interactive viewer that replaces the static-Mermaid HTML
|
|
9
|
+
# for `view --output html`. The output is a self-contained
|
|
10
|
+
# HTML file: vendored `cytoscape.min.js` is inlined alongside
|
|
11
|
+
# our ~100-line init script and the node / edge dataset, so
|
|
12
|
+
# the artefact opens in any browser without a network round
|
|
13
|
+
# trip. See `docs/plan.md` "2D interactive viewer" for the
|
|
14
|
+
# supply-chain rationale.
|
|
15
|
+
module Viewer
|
|
16
|
+
module Html
|
|
17
|
+
module_function
|
|
18
|
+
|
|
19
|
+
TEMPLATE_DIR = File.expand_path("../templates", __dir__)
|
|
20
|
+
TEMPLATE_PATH = File.join(TEMPLATE_DIR, "viewer.html.erb")
|
|
21
|
+
CSS_PATH = File.join(TEMPLATE_DIR, "viewer.css")
|
|
22
|
+
VIEWER_JS_PATH = File.join(TEMPLATE_DIR, "viewer.js")
|
|
23
|
+
CYTOSCAPE_JS_PATH = File.join(TEMPLATE_DIR, "vendor", "cytoscape.min.js")
|
|
24
|
+
|
|
25
|
+
# Node kinds that map to top-level Cytoscape nodes.
|
|
26
|
+
# Method / attribute nodes are out of scope for the graph
|
|
27
|
+
# viewer (they belong to the class diagram, not the
|
|
28
|
+
# dependency graph).
|
|
29
|
+
CONSTANT_KINDS = %w[class module].freeze
|
|
30
|
+
|
|
31
|
+
# @param edges [Array<Edge>] dependency edges
|
|
32
|
+
# @param nodes [Array<Node>] node metadata (for click-through)
|
|
33
|
+
# @param title [String] page title
|
|
34
|
+
# @param subtitle [String, nil] optional subtitle line
|
|
35
|
+
# @param path_mode [:relative, :absolute, :none]
|
|
36
|
+
# how `data.path` is reported to click handlers. `:none`
|
|
37
|
+
# strips it entirely so HTML shared externally doesn't
|
|
38
|
+
# leak filesystem layout.
|
|
39
|
+
# @param open_with [Symbol, nil] when `:vscode`, node click
|
|
40
|
+
# opens `vscode://file/<path>:<line>` instead of writing
|
|
41
|
+
# to clipboard.
|
|
42
|
+
# @return [String] complete HTML document
|
|
43
|
+
def render(edges:, nodes:, title:, subtitle: nil, path_mode: :relative, open_with: nil)
|
|
44
|
+
data = build_data(
|
|
45
|
+
edges: edges, nodes: nodes,
|
|
46
|
+
path_mode: path_mode, open_with: open_with
|
|
47
|
+
)
|
|
48
|
+
template = ERB.new(File.read(TEMPLATE_PATH), trim_mode: "-")
|
|
49
|
+
template.result_with_hash(
|
|
50
|
+
title: title,
|
|
51
|
+
subtitle: subtitle,
|
|
52
|
+
data_json: safe_json(data),
|
|
53
|
+
css: File.read(CSS_PATH),
|
|
54
|
+
cytoscape: File.read(CYTOSCAPE_JS_PATH),
|
|
55
|
+
viewer: File.read(VIEWER_JS_PATH)
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Builds the `{nodes:, edges:, options:}` payload the
|
|
60
|
+
# inline init JS reads from
|
|
61
|
+
# `<script type="application/json" id="rmg-data">`.
|
|
62
|
+
def build_data(edges:, nodes:, path_mode:, open_with:)
|
|
63
|
+
node_meta = {}
|
|
64
|
+
nodes.each do |node|
|
|
65
|
+
next unless CONSTANT_KINDS.include?(node.kind)
|
|
66
|
+
|
|
67
|
+
key = fully_qualified(node)
|
|
68
|
+
# First definition wins; class re-opens still resolve
|
|
69
|
+
# to one Cytoscape node, matching the dedup contract
|
|
70
|
+
# in `Edge#dedup_key`.
|
|
71
|
+
node_meta[key] ||= {
|
|
72
|
+
# Cytoscape resolves `edge.source` / `edge.target`
|
|
73
|
+
# against `node.data.id`, so the constant name has
|
|
74
|
+
# to be the id (not just a display field).
|
|
75
|
+
id: key,
|
|
76
|
+
name: key,
|
|
77
|
+
kind: node.kind,
|
|
78
|
+
path: path_for(node.path, path_mode),
|
|
79
|
+
line: node.line
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Every edge endpoint becomes a node, even when the
|
|
84
|
+
# constant has no definition in the analysed paths
|
|
85
|
+
# (e.g. `ApplicationRecord` from a Rails gem). These
|
|
86
|
+
# get the `external` kind so the styling can dim them.
|
|
87
|
+
edges.flat_map { |e| [e.from, e.to] }.uniq.each do |name|
|
|
88
|
+
node_meta[name] ||= { id: name, name: name, kind: "external" }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
{
|
|
92
|
+
nodes: node_meta.values.map { |n| { data: n } },
|
|
93
|
+
edges: edges.each_with_index.map do |edge, i|
|
|
94
|
+
{
|
|
95
|
+
data: {
|
|
96
|
+
id: "e#{i}",
|
|
97
|
+
source: edge.from,
|
|
98
|
+
target: edge.to,
|
|
99
|
+
kind: edge.kind,
|
|
100
|
+
confidence: edge.confidence
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
end,
|
|
104
|
+
options: { open_with: open_with&.to_s }
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def fully_qualified(node)
|
|
109
|
+
owner = node.owner
|
|
110
|
+
owner && !owner.empty? ? "#{owner}::#{node.name}" : node.name
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def path_for(path, mode)
|
|
114
|
+
return nil if path.nil? || mode == :none
|
|
115
|
+
|
|
116
|
+
case mode
|
|
117
|
+
when :absolute then File.expand_path(path)
|
|
118
|
+
when :relative then path
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# JSON embedded in `<script>` must not contain `</` (would
|
|
123
|
+
# break out of the surrounding tag). `JSON.generate` does
|
|
124
|
+
# not escape it by default; rewriting the literal pair
|
|
125
|
+
# `</` → `<\/` is the standard safety pass.
|
|
126
|
+
def safe_json(value)
|
|
127
|
+
JSON.generate(value).gsub("</", "<\\/")
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/rigor-module-graph.rb
CHANGED
|
@@ -30,4 +30,5 @@ require_relative "rigor/module_graph/packwerk_overlay"
|
|
|
30
30
|
require_relative "rigor/module_graph/uml/class_diagram"
|
|
31
31
|
require_relative "rigor/module_graph/html_view"
|
|
32
32
|
require_relative "rigor/module_graph/status_reporter"
|
|
33
|
+
require_relative "rigor/module_graph/viewer/html"
|
|
33
34
|
require_relative "rigor/module_graph/plugin"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rigor-module-graph
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nozomi Hijikata
|
|
@@ -71,9 +71,15 @@ files:
|
|
|
71
71
|
- lib/rigor/module_graph/reachability.rb
|
|
72
72
|
- lib/rigor/module_graph/stats.rb
|
|
73
73
|
- lib/rigor/module_graph/status_reporter.rb
|
|
74
|
+
- lib/rigor/module_graph/templates/vendor/CHECKSUMS
|
|
75
|
+
- lib/rigor/module_graph/templates/vendor/cytoscape.min.js
|
|
74
76
|
- lib/rigor/module_graph/templates/view.html.erb
|
|
77
|
+
- lib/rigor/module_graph/templates/viewer.css
|
|
78
|
+
- lib/rigor/module_graph/templates/viewer.html.erb
|
|
79
|
+
- lib/rigor/module_graph/templates/viewer.js
|
|
75
80
|
- lib/rigor/module_graph/uml/class_diagram.rb
|
|
76
81
|
- lib/rigor/module_graph/version.rb
|
|
82
|
+
- lib/rigor/module_graph/viewer/html.rb
|
|
77
83
|
- lib/rigor/module_graph/visibility_map.rb
|
|
78
84
|
- lib/rigor/module_graph/zeitwerk_resolver.rb
|
|
79
85
|
licenses:
|
|
@@ -89,7 +95,7 @@ rdoc_options:
|
|
|
89
95
|
- "--main"
|
|
90
96
|
- README.md
|
|
91
97
|
- "--markup"
|
|
92
|
-
-
|
|
98
|
+
- markdown
|
|
93
99
|
require_paths:
|
|
94
100
|
- lib
|
|
95
101
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
@@ -106,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
106
112
|
- !ruby/object:Gem::Version
|
|
107
113
|
version: '0'
|
|
108
114
|
requirements: []
|
|
109
|
-
rubygems_version: 4.0.
|
|
115
|
+
rubygems_version: 4.0.10
|
|
110
116
|
specification_version: 4
|
|
111
117
|
summary: Class/module/constant dependency graph for Ruby projects, built on Rigor.
|
|
112
118
|
test_files: []
|