uml-viewer 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +16 -0
- data/LICENSE +21 -0
- data/README.md +58 -0
- data/exe/uml-viewer +4 -0
- data/lib/uml/viewer/cli.rb +133 -0
- data/lib/uml/viewer/commands.rb +51 -0
- data/lib/uml/viewer/complexity.rb +28 -0
- data/lib/uml/viewer/coverage.rb +67 -0
- data/lib/uml/viewer/graph.rb +113 -0
- data/lib/uml/viewer/inflector.rb +19 -0
- data/lib/uml/viewer/mailbox.rb +79 -0
- data/lib/uml/viewer/operations.rb +105 -0
- data/lib/uml/viewer/overlay.rb +165 -0
- data/lib/uml/viewer/policy.rb +59 -0
- data/lib/uml/viewer/policy_file.rb +124 -0
- data/lib/uml/viewer/policy_init.rb +51 -0
- data/lib/uml/viewer/proposals.rb +78 -0
- data/lib/uml/viewer/rescanner.rb +61 -0
- data/lib/uml/viewer/resolver.rb +50 -0
- data/lib/uml/viewer/scanner.rb +349 -0
- data/lib/uml/viewer/server.rb +226 -0
- data/lib/uml/viewer/skill/SKILL.md.erb +44 -0
- data/lib/uml/viewer/skill_installer.rb +29 -0
- data/lib/uml/viewer/smoke.rb +61 -0
- data/lib/uml/viewer/version.rb +5 -0
- data/lib/uml/viewer/views.rb +94 -0
- data/lib/uml/viewer/watcher.rb +66 -0
- data/lib/uml/viewer/web/LICENSE-EPL-2.0 +263 -0
- data/lib/uml/viewer/web/app.js +526 -0
- data/lib/uml/viewer/web/elk.bundled.js +6605 -0
- data/lib/uml/viewer/web/index.html +65 -0
- data/lib/uml/viewer/workspace.rb +75 -0
- data/lib/uml/viewer.rb +23 -0
- metadata +110 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>uml-viewer</title>
|
|
6
|
+
<style>
|
|
7
|
+
:root { --bg: #f7f7f4; --fg: #1d2021; --box: #ffffff; --line: #6b7075; --red: #c0392b; --muted: #8a8f94; --hl: #fff3c4; --grade-l: 86%; }
|
|
8
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #1b1d1f; --fg: #e6e6e3; --box: #26292c; --line: #9aa0a6; --red: #ff6b5b; --muted: #7b8186; --hl: #4a4020; --grade-l: 30%; } }
|
|
9
|
+
[hidden] { display: none !important; }
|
|
10
|
+
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--fg); font: 13px/1.3 ui-monospace, Menlo, monospace; }
|
|
11
|
+
#bar { position: fixed; top: 0; left: 0; right: 0; display: flex; gap: 16px; align-items: center; padding: 8px 12px; background: var(--bg); border-bottom: 1px solid var(--line); z-index: 2; }
|
|
12
|
+
#crumbs { display: flex; gap: 4px; }
|
|
13
|
+
button.crumb { font: inherit; font-weight: bold; background: none; color: var(--fg); border: 0; padding: 0; cursor: pointer; }
|
|
14
|
+
button.crumb:disabled { cursor: default; }
|
|
15
|
+
button.crumb + button.crumb::before { content: "› "; color: var(--muted); font-weight: normal; }
|
|
16
|
+
#banner { color: var(--red); }
|
|
17
|
+
#banner:empty { display: none; }
|
|
18
|
+
svg { position: absolute; top: 36px; left: 0; }
|
|
19
|
+
g.box rect, g.box ellipse { fill: var(--box); stroke: var(--line); }
|
|
20
|
+
g.box.foreign ellipse { stroke-dasharray: 4 3; }
|
|
21
|
+
g.box.folder text.title { font-style: italic; }
|
|
22
|
+
g.box.component rect { stroke-width: 2; }
|
|
23
|
+
text { fill: var(--fg); }
|
|
24
|
+
text.level, text.more { fill: var(--muted); }
|
|
25
|
+
path.arrow { fill: none; stroke: var(--line); stroke-width: 1.2; marker-end: url(#head); }
|
|
26
|
+
path.arrow.violating { stroke: var(--red); marker-end: url(#head-red); }
|
|
27
|
+
path.arrow.both { marker-start: url(#head); }
|
|
28
|
+
path.arrow.both.violating { marker-start: url(#head-red); }
|
|
29
|
+
svg.triangles path.arrow { stroke: transparent; }
|
|
30
|
+
text.note { fill: var(--muted); }
|
|
31
|
+
#menu { position: fixed; display: none; flex-direction: column; background: var(--box); border: 1px solid var(--line); z-index: 3; }
|
|
32
|
+
#menu.open { display: flex; }
|
|
33
|
+
#menu button { font: inherit; background: none; color: var(--fg); border: 0; padding: 6px 14px; text-align: left; cursor: pointer; }
|
|
34
|
+
#toast { position: fixed; bottom: 12px; left: 12px; color: var(--red); }
|
|
35
|
+
#regen, #propose, #proposal, #declutter { font: inherit; background: none; color: var(--fg); border: 1px solid var(--line); border-radius: 3px; padding: 2px 8px; cursor: pointer; }
|
|
36
|
+
aside { position: fixed; top: 36px; bottom: 0; overflow: auto; background: var(--box); border-left: 1px solid var(--line); padding: 8px 12px; z-index: 2; }
|
|
37
|
+
aside h2 { font-size: 14px; margin: 4px 0 8px; }
|
|
38
|
+
#card { right: 0; width: 360px; }
|
|
39
|
+
#source { right: 385px; width: min(720px, 50vw); }
|
|
40
|
+
#card .error { color: var(--red); }
|
|
41
|
+
#card .note, #card .file { color: var(--muted); }
|
|
42
|
+
#card ul { padding: 0; }
|
|
43
|
+
li.op { list-style: none; cursor: pointer; white-space: pre; }
|
|
44
|
+
li.op:hover { text-decoration: underline; }
|
|
45
|
+
#source pre { margin: 0; }
|
|
46
|
+
.line { white-space: pre; }
|
|
47
|
+
.line.current { background: var(--hl); }
|
|
48
|
+
#popup { position: fixed; background: var(--box); border: 1px solid var(--line); padding: 6px 8px; z-index: 3; pointer-events: none; }
|
|
49
|
+
#popup .violating { color: var(--red); }
|
|
50
|
+
path.hit { fill: none; stroke: transparent; stroke-width: 10; }
|
|
51
|
+
circle.dot { stroke: var(--line); stroke-width: 0.5; }
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<div id="bar"><span id="crumbs"></span><select id="proposal" aria-label="diagram"></select><button id="propose">New proposal</button><button id="regen">Regen</button><button id="declutter">declutter: none</button><span id="status"></span><span id="banner"></span></div>
|
|
56
|
+
<svg id="diagram"></svg>
|
|
57
|
+
<div id="menu"><button data-op="explain">Explain</button><button data-op="omit">Omit</button></div>
|
|
58
|
+
<div id="toast"></div>
|
|
59
|
+
<aside id="card" hidden></aside>
|
|
60
|
+
<aside id="source" hidden></aside>
|
|
61
|
+
<div id="popup" hidden></div>
|
|
62
|
+
<script src="/elk.js"></script>
|
|
63
|
+
<script src="/app.js"></script>
|
|
64
|
+
</body>
|
|
65
|
+
</html>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "digest"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
require "json"
|
|
4
|
+
require "socket"
|
|
5
|
+
|
|
6
|
+
module Uml
|
|
7
|
+
module Viewer
|
|
8
|
+
class Workspace
|
|
9
|
+
class MissingProject < StandardError; end
|
|
10
|
+
|
|
11
|
+
attr_reader :root, :dir
|
|
12
|
+
|
|
13
|
+
def self.home
|
|
14
|
+
File.expand_path(ENV.fetch("UML_VIEWER_HOME") { File.join(Dir.home, ".uml-viewer") })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.pid_alive?(pid)
|
|
18
|
+
pid = Integer(pid)
|
|
19
|
+
return false unless pid.positive?
|
|
20
|
+
|
|
21
|
+
Process.kill(0, pid)
|
|
22
|
+
true
|
|
23
|
+
rescue Errno::ESRCH, ArgumentError, TypeError
|
|
24
|
+
false
|
|
25
|
+
rescue Errno::EPERM
|
|
26
|
+
true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.port_open?(port)
|
|
30
|
+
Socket.tcp("127.0.0.1", Integer(port), connect_timeout: 0.5).close
|
|
31
|
+
true
|
|
32
|
+
rescue SystemCallError, SocketError, ArgumentError, TypeError
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def initialize(project_path)
|
|
37
|
+
raise MissingProject, "no such project folder: #{project_path}" unless File.directory?(project_path)
|
|
38
|
+
|
|
39
|
+
@root = File.realpath(project_path)
|
|
40
|
+
digest = Digest::SHA256.hexdigest(@root)[0, 8]
|
|
41
|
+
@dir = File.join(self.class.home, "projects", "#{File.basename(@root)}-#{digest}")
|
|
42
|
+
FileUtils.mkdir_p(@dir, mode: 0o700)
|
|
43
|
+
File.chmod(0o700, @dir)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def path(name)
|
|
47
|
+
File.join(@dir, name)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_atomic(name, string)
|
|
51
|
+
target = path(name)
|
|
52
|
+
temp = "#{target}.#{Process.pid}.#{Thread.current.object_id}.tmp"
|
|
53
|
+
File.open(temp, File::WRONLY | File::CREAT | File::TRUNC, 0o600) { |file| file.write(string) }
|
|
54
|
+
File.rename(temp, target)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def read_json(name, default)
|
|
58
|
+
JSON.parse(File.read(path(name)))
|
|
59
|
+
rescue Errno::ENOENT
|
|
60
|
+
default
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def server_info
|
|
64
|
+
read_json("server.json", nil)
|
|
65
|
+
rescue JSON::ParserError
|
|
66
|
+
nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def server_alive?
|
|
70
|
+
info = server_info
|
|
71
|
+
info.is_a?(Hash) && self.class.pid_alive?(info["pid"]) && self.class.port_open?(info["port"])
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/uml/viewer.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require_relative "viewer/version"
|
|
2
|
+
require_relative "viewer/workspace"
|
|
3
|
+
require_relative "viewer/resolver"
|
|
4
|
+
require_relative "viewer/complexity"
|
|
5
|
+
require_relative "viewer/operations"
|
|
6
|
+
require_relative "viewer/inflector"
|
|
7
|
+
require_relative "viewer/scanner"
|
|
8
|
+
require_relative "viewer/policy_file"
|
|
9
|
+
require_relative "viewer/policy_init"
|
|
10
|
+
require_relative "viewer/policy"
|
|
11
|
+
require_relative "viewer/views"
|
|
12
|
+
require_relative "viewer/proposals"
|
|
13
|
+
require_relative "viewer/coverage"
|
|
14
|
+
require_relative "viewer/overlay"
|
|
15
|
+
require_relative "viewer/graph"
|
|
16
|
+
require_relative "viewer/mailbox"
|
|
17
|
+
require_relative "viewer/watcher"
|
|
18
|
+
require_relative "viewer/commands"
|
|
19
|
+
require_relative "viewer/rescanner"
|
|
20
|
+
require_relative "viewer/server"
|
|
21
|
+
require_relative "viewer/skill_installer"
|
|
22
|
+
require_relative "viewer/smoke"
|
|
23
|
+
require_relative "viewer/cli"
|
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: uml-viewer
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Robert Hopman
|
|
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: prism
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.9'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.9'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: webrick
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.9'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.9'
|
|
40
|
+
description: uml-viewer scans a Ruby or Rails project with Prism and shows it as a
|
|
41
|
+
UML diagram in the browser. Red arrows show dependencies that break the Dependency
|
|
42
|
+
Rule between Rails layers. Box colors show CRAP from a SimpleCov run. A right-click
|
|
43
|
+
sends a command to a Claude Code session, which explains the code, changes the policy
|
|
44
|
+
or writes a proposal. Nothing is written into the viewed project.
|
|
45
|
+
executables:
|
|
46
|
+
- uml-viewer
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- CHANGELOG.md
|
|
51
|
+
- LICENSE
|
|
52
|
+
- README.md
|
|
53
|
+
- exe/uml-viewer
|
|
54
|
+
- lib/uml/viewer.rb
|
|
55
|
+
- lib/uml/viewer/cli.rb
|
|
56
|
+
- lib/uml/viewer/commands.rb
|
|
57
|
+
- lib/uml/viewer/complexity.rb
|
|
58
|
+
- lib/uml/viewer/coverage.rb
|
|
59
|
+
- lib/uml/viewer/graph.rb
|
|
60
|
+
- lib/uml/viewer/inflector.rb
|
|
61
|
+
- lib/uml/viewer/mailbox.rb
|
|
62
|
+
- lib/uml/viewer/operations.rb
|
|
63
|
+
- lib/uml/viewer/overlay.rb
|
|
64
|
+
- lib/uml/viewer/policy.rb
|
|
65
|
+
- lib/uml/viewer/policy_file.rb
|
|
66
|
+
- lib/uml/viewer/policy_init.rb
|
|
67
|
+
- lib/uml/viewer/proposals.rb
|
|
68
|
+
- lib/uml/viewer/rescanner.rb
|
|
69
|
+
- lib/uml/viewer/resolver.rb
|
|
70
|
+
- lib/uml/viewer/scanner.rb
|
|
71
|
+
- lib/uml/viewer/server.rb
|
|
72
|
+
- lib/uml/viewer/skill/SKILL.md.erb
|
|
73
|
+
- lib/uml/viewer/skill_installer.rb
|
|
74
|
+
- lib/uml/viewer/smoke.rb
|
|
75
|
+
- lib/uml/viewer/version.rb
|
|
76
|
+
- lib/uml/viewer/views.rb
|
|
77
|
+
- lib/uml/viewer/watcher.rb
|
|
78
|
+
- lib/uml/viewer/web/LICENSE-EPL-2.0
|
|
79
|
+
- lib/uml/viewer/web/app.js
|
|
80
|
+
- lib/uml/viewer/web/elk.bundled.js
|
|
81
|
+
- lib/uml/viewer/web/index.html
|
|
82
|
+
- lib/uml/viewer/workspace.rb
|
|
83
|
+
homepage: https://github.com/roberthopman/uml-viewer
|
|
84
|
+
licenses:
|
|
85
|
+
- MIT
|
|
86
|
+
- EPL-2.0
|
|
87
|
+
metadata:
|
|
88
|
+
source_code_uri: https://github.com/roberthopman/uml-viewer
|
|
89
|
+
changelog_uri: https://github.com/roberthopman/uml-viewer/blob/main/CHANGELOG.md
|
|
90
|
+
allowed_push_host: https://rubygems.org
|
|
91
|
+
rubygems_mfa_required: 'true'
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '3.3'
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 4.0.16
|
|
107
|
+
specification_version: 4
|
|
108
|
+
summary: A live UML diagram of a Ruby or Rails project in the browser, with a Claude
|
|
109
|
+
Code agent loop.
|
|
110
|
+
test_files: []
|