Skip to content

Commit 7637351

Browse files
committed
Update visualization command.
1 parent 2191d51 commit 7637351

11 files changed

Lines changed: 288 additions & 48 deletions

File tree

.gitignore

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,3 @@
55
/gems.locked
66
/.covered.db
77
/external
8-
9-
kurocha
10-
dependency.svg
11-
12-
# Test fixtures:
13-
fixtures/teapot/command/clone/
14-
fixtures/teapot/command/fetch/test-project/merged-lock.yml
15-
fixtures/teapot/command/fetch/test-project/teapot/
16-
fixtures/teapot/command/fetch/test-project/test-lock.yml
17-
fixtures/teapot/run/
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
teapot_version "2.0"
7+
8+
define_project "test-project" do |project|
9+
project.title = "Test Project"
10+
project.license = "MIT"
11+
end
12+
13+
define_configuration "development" do |configuration|
14+
configuration.targets[:build] << "test-target"
15+
end
16+
17+
define_target "test-target" do |target|
18+
target.depends "library"
19+
target.depends "utilities"
20+
end
21+
22+
define_target "library" do |target|
23+
target.depends "base"
24+
end
25+
26+
define_target "utilities" do |target|
27+
end
28+
29+
define_target "base" do |target|
30+
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
teapot_version "2.0"
7+
8+
define_project "base-package" do |project|
9+
project.title = "Base Package"
10+
project.license = "MIT"
11+
end
12+
13+
define_configuration "development" do |configuration|
14+
end
15+
16+
define_target "base-target" do |target|
17+
target.provides "Library/Base"
18+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
teapot_version "2.0"
7+
8+
define_project "external-library" do |project|
9+
project.title = "External Library"
10+
project.license = "MIT"
11+
end
12+
13+
define_configuration "development" do |configuration|
14+
configuration.require "base-package"
15+
end
16+
17+
define_target "external-library-target" do |target|
18+
target.provides "Library/External"
19+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
teapot_version "2.0"
7+
8+
define_project "utilities-package" do |project|
9+
project.title = "Utilities Package"
10+
project.license = "MIT"
11+
end
12+
13+
define_configuration "development" do |configuration|
14+
configuration.require "base-package"
15+
end
16+
17+
define_target "utilities-target" do |target|
18+
target.provides "Library/Utilities"
19+
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
require "sus/shared"
7+
require "build/files"
8+
9+
require "sus/fixtures/temporary_directory_context"
10+
11+
module Teapot
12+
module Command
13+
VisualizeContext = Sus::Shared("a visualize") do
14+
include Sus::Fixtures::TemporaryDirectoryContext
15+
16+
let(:project_path) {::Build::Files::Path.new(File.expand_path("visualize/test-project", __dir__))}
17+
end
18+
end
19+
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
define_project "test-project" do |project|
4+
project.title = "Test Project"
5+
project.license = "MIT"
6+
end
7+
8+
define_target "test-target" do |target|
9+
target.depends "library"
10+
end
11+
12+
define_target "library" do |target|
13+
end

lib/teapot/command/visualize.rb

Lines changed: 101 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,120 @@
33
# Released under the MIT License.
44
# Copyright, 2017-2026, by Samuel Williams.
55

6+
require "samovar"
67
require_relative "selection"
7-
require "graphviz"
8+
require "build/controller"
89

910
module Teapot
1011
module Command
11-
# A command to visualize the dependency graph.
12-
class Visualize < Selection
13-
self.description = "Generate a picture of the dependency graph."
12+
# A command to visualize dependency graphs.
13+
class Visualize < Samovar::Command
14+
self.description = "Generate visualizations of package and target dependencies."
1415

15-
options do
16-
option "-o/--output-path <path>", "The output path for the visualization.", default: "dependency.svg"
17-
option "-d/--dependency-name <name>", "Show the partial chain for the given named dependency."
18-
end
19-
20-
# Get the dependency names to visualize.
21-
# @returns [Array(String)] The dependency names.
22-
def dependency_names
23-
@targets || []
24-
end
25-
26-
# Get the specific dependency name to visualize.
27-
# @returns [String | Nil] The dependency name.
28-
def dependency_name
29-
@options[:dependency_name]
16+
# Visualize package-level dependencies.
17+
class Packages < Selection
18+
self.description = "Visualize package-level dependencies from configuration.require."
19+
20+
options do
21+
option "-o/--output-path <path>", "The output path for the visualization."
22+
option "-d/--dependency-name <name>", "Show the partial chain for the given named dependency."
23+
end
24+
25+
# Get the specific dependency name to visualize.
26+
# @returns [String | Nil] The dependency name.
27+
def dependency_name
28+
@options[:dependency_name]
29+
end
30+
31+
# Process and generate the package dependency visualization.
32+
# @parameter selection [Select] The selection to visualize.
33+
# @returns [String] The generated Mermaid diagram.
34+
def process(selection)
35+
chain = selection.chain
36+
37+
if dependency_name
38+
provider = selection.dependencies[dependency_name]
39+
chain = chain.partial(provider)
40+
end
41+
42+
visualization = ::Build::Dependency::Visualization.new
43+
diagram = visualization.generate(chain)
44+
45+
if output_path = @options[:output_path]
46+
File.write(output_path, diagram)
47+
else
48+
$stdout.puts diagram
49+
end
50+
51+
return diagram
52+
end
3053
end
3154

32-
# Process and generate the visualization.
33-
# @parameter selection [Select] The selection to visualize.
34-
# @returns [Graphviz::Graph] The generated graph.
35-
def process(selection)
36-
context = selection.context
37-
chain = selection.chain
55+
# Visualize target-level dependencies.
56+
class Targets < Selection
57+
self.description = "Visualize target-level dependencies from target.depends."
3858

39-
if dependency_name
40-
provider = selection.dependencies[dependency_name]
41-
42-
chain = chain.partial(provider)
59+
options do
60+
option "-o/--output-path <path>", "The output path for the visualization."
4361
end
4462

45-
visualization = ::Build::Dependency::Visualization.new
63+
# Process and generate the target dependency visualization.
64+
# @parameter selection [Select] The selection to visualize.
65+
# @returns [String] The generated Mermaid diagram.
66+
def process(selection)
67+
lines = ["flowchart LR"]
68+
lines << ""
69+
70+
# Build the graph from all targets in the selection
71+
# The selection contains all targets loaded from packages
72+
selection.targets.each do |name, target|
73+
target.dependencies.each do |dependency|
74+
dependency_name = dependency.name.to_s
75+
76+
# Create edge from target to its dependency
77+
if dependency.private?
78+
lines << " #{sanitize_id(name)}[#{name}] -.-> #{sanitize_id(dependency_name)}[#{dependency_name}]"
79+
else
80+
lines << " #{sanitize_id(name)}[#{name}] --> #{sanitize_id(dependency_name)}[#{dependency_name}]"
81+
end
82+
end
83+
end
84+
85+
diagram = lines.join("\n")
86+
87+
if output_path = @options[:output_path]
88+
File.write(output_path, diagram)
89+
else
90+
$stdout.puts diagram
91+
end
92+
93+
return diagram
94+
end
4695

47-
graph = visualization.generate(chain)
96+
private
4897

49-
if output_path = @options[:output_path]
50-
Graphviz.output(graph, path: output_path, format: :svg)
51-
else
52-
$stdout.puts graph.to_dot
98+
# Convert a name to a valid Mermaid node ID.
99+
# @parameter name [String] The name to sanitize.
100+
# @returns [String] A sanitized identifier safe for use in Mermaid diagrams.
101+
def sanitize_id(name)
102+
name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
53103
end
54-
55-
return graph
104+
end
105+
106+
nested :command, {
107+
"packages" => Packages,
108+
"targets" => Targets,
109+
}, default: "packages"
110+
111+
# Delegate context to parent Top command.
112+
# @returns [Context] The project context.
113+
def context
114+
parent.context
115+
end
116+
117+
# Execute the visualize command.
118+
def call
119+
@command.call
56120
end
57121
end
58122
end

releases.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Releases
22

3+
## Unreleased
4+
5+
- `teapot visualize` has two new sub-commands: `packages` and `targets`. The former visualizes the dependency graph of packages, while the latter visualizes the dependency graph of targets. Both sub-commands output Mermaid diagrams, which can be rendered using Mermaid Live Editor or other Mermaid-compatible tools.
6+
37
## v3.6.0
48

59
- Update dependencies.

teapot.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
2626
spec.required_ruby_version = ">= 3.3"
2727

2828
spec.add_dependency "build", "~> 2.4"
29-
spec.add_dependency "build-dependency", "~> 1.4"
29+
spec.add_dependency "build-dependency", "~> 1.6"
3030
spec.add_dependency "build-environment", "~> 1.10"
3131
spec.add_dependency "build-files", "~> 1.8"
3232
spec.add_dependency "build-files-monitor", "~> 0.4"

0 commit comments

Comments
 (0)