Skip to content

Commit e8750f1

Browse files
authored
Folder visibility added (#58)
* Less is more for integration tests * Fix “no to all” integration test * Move optionsparser into its own class * Always output all parameters * Handle nested_visibility * Better violation distribution in sample app * When showing custom title, don’t show command line params * FolderVisibility as new name * Updated diagrams * Bump version * Update diagram
1 parent df1d318 commit e8750f1

29 files changed

Lines changed: 345 additions & 2134 deletions

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
visualize_packs (0.5.21)
4+
visualize_packs (0.5.22)
55
packs-specification
66
parse_packwerk (>= 0.20.0)
77
sorbet-runtime

bin/visualize_packs

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,10 @@
11
#!/usr/bin/env ruby
22
# frozen_string_literal: true
33

4-
require "pathname"
5-
require "optparse"
6-
require "ostruct"
7-
84
require_relative '../lib/visualize_packs'
95

10-
options = Options.new
11-
12-
OptionParser.new do |opt|
13-
opt.on('--no-legend', "Don't show legend") { |o| options.show_legend = false }
14-
15-
opt.on('--no-dependency-arrows', "Don't show accepted dependency arrows") { |o| options.show_dependencies = false }
16-
opt.on('--no-privacy-boxes', "Don't show privacy enforcement box on a pack") { |o| options.show_privacy = false }
17-
opt.on('--no-layers', "Don't show architectural layers") { |o| options.show_layers = false }
18-
opt.on('--no-visibility-arrows', "Don't show visibility arrows") { |o| options.show_visibility = false }
19-
20-
opt.on('--no-todo-edges', "Don't show todos for package relationships") { |o| options.show_relationship_todos = false }
21-
opt.on("--edge-todo-types=STRING", "Show only the selected types of relationship todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.relationship_todo_types = o.to_s.split(",").uniq.map { EdgeTodoTypes.deserialize(_1) } }
22-
opt.on("--use-edge-todos-for-layout", "Show only the selected types of relationship todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.use_relationship_todos_for_layout = true }
23-
24-
opt.on('--no-teams', "Don't show team colors") { |o| options.show_teams = false }
25-
opt.on('--no-node-todos', "Don't show package-based todos") { |o| options.show_node_todos = false }
26-
27-
opt.on('--focus-pack=STRING', "Focus on a specific pack(s). Comma-separated list of packs. Wildcards supported: 'packs/*'") { |o| options.focus_pack = o.to_s.split(",") }
28-
opt.on('--focus-pack-edge-mode=STRING', "If focus-pack is set, this shows only between focussed packs (when set to none) or the edges into / out of / in and out of the focus packs to non-focus packs (which will be re-added to the graph). One of #{FocusPackEdgeDirection.values.map &:serialize}") { |o| options.show_only_edges_to_focus_pack = FocusPackEdgeDirection.deserialize(o) }
29-
opt.on('--exclude-packs=', "Exclude listed packs from diagram. If used with include you will get all included that are not excluded. Wildcards support: 'packs/ignores/*'") { |o| options.exclude_packs = o.to_s.split(",") }
30-
31-
opt.on('--roll-nested-into-parent-packs', "Don't show nested packs (not counting root). Connect edges to top-level pack instead") { |o| options.roll_nested_into_parent_packs = true }
32-
opt.on('--no-nesting-arrows', "Don't draw relationships between parents and nested packs") { |o| options.show_nested_relationships = false }
33-
34-
opt.on('--remote-base-url=STRING', "Link pack packs to a URL (affects graphviz SVG generation)") { |o| options.remote_base_url = o }
35-
36-
opt.on('--title=STRING', "Set a custom diagram title") { |o| options.title = o }
37-
38-
opt.on('-V', '--version', "Show version") do
39-
spec_path = File.expand_path("../visualize_packs.gemspec", __dir__)
40-
spec = Gem::Specification::load(spec_path)
41-
puts "Version #{spec.version}"
42-
exit
43-
end
44-
45-
opt.on_tail("-h", "--help", "Show this message") do
46-
puts opt
47-
exit
48-
end
49-
end.parse!
50-
516
puts VisualizePacks.package_graph!(
52-
options,
7+
ARGV,
538
ParsePackwerk::Configuration.fetch.raw,
549
Packs.all.map { ParsePackwerk.find(_1.name) }
5510
)

diagram_examples.png

-893 KB
Loading

lib/graph.dot.erb

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ digraph package_diagram {
105105
<%= VisualizePacks::ArrowHead::ArchitectureTodo.serialize %>
106106
<%- elsif todo_type == 'visibility' -%>
107107
<%= VisualizePacks::ArrowHead::VisibilityTodo.serialize %>
108+
<%- elsif todo_type == 'folder_visibility' -%>
109+
<%= VisualizePacks::ArrowHead::FolderVisibilityTodo.serialize %>
108110
<%- elsif todo_type == 'dependency' -%>
109111
<%= VisualizePacks::ArrowHead::DependencyTodo.serialize %>
110112
<%- end -%>
@@ -141,14 +143,14 @@ digraph package_diagram {
141143
A -> B [label="accepted dependency" <%= VisualizePacks::ArrowHead::ConfiguredDependency.serialize %>]
142144
<%- end -%>
143145
<%- if options.show_nested_relationships -%>
144-
K [ fontsize=12 shape=box label="package"]
145-
L [ fontsize=12 shape=box label="package"]
146-
K -> L [label="nested package" <%= VisualizePacks::ArrowHead::ConfiguredNested.serialize %>]
147-
<%- end -%>
148-
<%- if options.show_visibility -%>
149146
M [ fontsize=12 shape=box label="package"]
150147
N [ fontsize=12 shape=box label="package"]
151-
M -> N [label="visibile to" <%= VisualizePacks::ArrowHead::ConfiguredVisibileTo.serialize %>]
148+
M -> N [label="nested package" <%= VisualizePacks::ArrowHead::ConfiguredNested.serialize %>]
149+
<%- end -%>
150+
<%- if options.show_visibility -%>
151+
O [ fontsize=12 shape=box label="package"]
152+
P [ fontsize=12 shape=box label="package"]
153+
O -> P [label="visibile to" <%= VisualizePacks::ArrowHead::ConfiguredVisibileTo.serialize %>]
152154
<%- end -%>
153155
<%- if options.show_relationship_todos -%>
154156
<%- if options.relationship_todo_types.include?(EdgeTodoTypes::Privacy) -%>
@@ -166,10 +168,15 @@ digraph package_diagram {
166168
H [ fontsize=12 shape=box label="package"]
167169
G -> H [label="visibility todo" <%= VisualizePacks::ArrowHead::VisibilityTodo.serialize %>]
168170
<%- end -%>
169-
<%- if options.relationship_todo_types.include?(EdgeTodoTypes::Dependency) -%>
171+
<%- if options.relationship_todo_types.include?(EdgeTodoTypes::Folder_Visibility) -%>
170172
I [ fontsize=12 shape=box label="package"]
171173
J [ fontsize=12 shape=box label="package"]
172-
I -> J [label="dependency todo" <%= VisualizePacks::ArrowHead::DependencyTodo.serialize %>]
174+
I -> J [label="folder visibility todo" <%= VisualizePacks::ArrowHead::FolderVisibilityTodo.serialize %>]
175+
<%- end -%>
176+
<%- if options.relationship_todo_types.include?(EdgeTodoTypes::Dependency) -%>
177+
K [ fontsize=12 shape=box label="package"]
178+
L [ fontsize=12 shape=box label="package"]
179+
K -> L [label="dependency todo" <%= VisualizePacks::ArrowHead::DependencyTodo.serialize %>]
173180
<%- end -%>
174181
<%- end -%>
175182
LEGEND_NODE_1 [ label="" peripheries=0 height=0 width=0 style=invis ]

lib/visualize_packs.rb

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require 'digest/md5'
88

99
require 'visualize_packs/options'
10+
require 'visualize_packs/options_parser'
1011

1112
module VisualizePacks
1213
extend T::Sig
@@ -17,14 +18,17 @@ class ArrowHead < T::Enum
1718
PrivacyTodo = new('color=darkred style=dashed arrowhead=crow')
1819
ArchitectureTodo = new('color=darkred style=dashed arrowhead=obox')
1920
VisibilityTodo = new('color=darkred style=dashed arrowhead=tee')
21+
FolderVisibilityTodo = new('color=darkred style=dashed arrowhead=odot')
2022
ConfiguredDependency = new('color=darkgreen')
2123
ConfiguredVisibileTo = new('color=blue')
2224
ConfiguredNested = new('color=purple')
2325
end
2426
end
2527

26-
sig { params(options: Options, raw_config: T::Hash[String, T.untyped], packages: T::Array[ParsePackwerk::Package]).returns(String) }
27-
def self.package_graph!(options, raw_config, packages)
28+
sig { params(args: T::Array[String], raw_config: T::Hash[String, T.untyped], packages: T::Array[ParsePackwerk::Package]).returns(String) }
29+
def self.package_graph!(args, raw_config, packages)
30+
options = OptionsParser.parse(args)
31+
2832
all_packages = filtered(packages, options).compact.sort_by {|x| x.name }
2933
all_packages = remove_nested_packs(all_packages, options)
3034
all_package_names = all_packages.map &:name
@@ -34,7 +38,7 @@ def self.package_graph!(options, raw_config, packages)
3438
node_protection = package_based_todos_text_maker()
3539
max_todo_count = max_todo_count(all_packages, show_edge, options)
3640

37-
title = diagram_title(options, max_todo_count)
41+
title = diagram_title(args, options, max_todo_count)
3842

3943
architecture_layers = (raw_config['architecture_layers'] || []) + ["NotInLayer"]
4044
grouped_packages = architecture_layers.inject({}) do |result, key|
@@ -67,10 +71,24 @@ def self.code_owner(package)
6771
package.config.dig("metadata", "owner") || package.config["owner"]
6872
end
6973

70-
sig { params(options: Options, max_todo_count: T.nilable(Integer)).returns(String) }
71-
def self.diagram_title(options, max_todo_count)
74+
sig { params(args: T::Array[String], options: Options, max_todo_count: T.nilable(Integer)).returns(String) }
75+
def self.diagram_title(args, options, max_todo_count)
7276
return "<<b>#{options.title}</b>>" if options.title
7377

78+
sub_title1_length = 0
79+
options_to_display = args.inject('') do |result, item|
80+
sub_title1_length += item.length
81+
if sub_title1_length > 90
82+
sub_title1_length = 0
83+
result += "<br/>#{item}"
84+
else
85+
result += " #{item}"
86+
end
87+
result
88+
end
89+
sub_title1 = "<br/>#{options_to_display}"
90+
91+
7492
focus_info = if options.focus_pack
7593
"Focus on #{limited_sentence(options.focus_pack)} (Edge mode: #{options.show_only_edges_to_focus_pack.serialize})"
7694
else
@@ -98,9 +116,9 @@ def self.diagram_title(options, max_todo_count)
98116
main_title = [focus_info, hidden_aspects_title, todo_types, exclusions].compact.join('. ')
99117

100118
if options.show_relationship_todos && max_todo_count
101-
sub_title = "<br/><font point-size='12'>Widest todo edge is #{max_todo_count} todo#{max_todo_count > 1 ? 's' : ''}</font>"
119+
sub_title2 = "<br/><font point-size='12'>Widest todo edge is #{max_todo_count} todo#{max_todo_count > 1 ? 's' : ''}</font>"
102120
end
103-
"<<b>#{main_title}</b>#{sub_title}>"
121+
"<<b>#{main_title}</b>#{sub_title1}#{sub_title2}>"
104122
end
105123

106124
sig { params(list: T.nilable(T::Array[String])).returns(T.nilable(String)) }

lib/visualize_packs/options.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ class EdgeTodoTypes < T::Enum
77
Privacy = new
88
Architecture = new
99
Visibility = new
10+
Folder_Visibility = new
1011
end
1112
end
1213

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# frozen_string_literal: true
2+
#typed: strict
3+
4+
require "optparse"
5+
6+
class OptionsParser
7+
extend T::Sig
8+
9+
sig { params(args: T::Array[String]).returns(Options) }
10+
def self.parse(args)
11+
options = Options.new
12+
13+
OptionParser.new do |opt|
14+
opt.on('--no-legend', "Don't show legend") { |o| options.show_legend = false }
15+
16+
opt.on('--no-dependency-arrows', "Don't show accepted dependency arrows") { |o| options.show_dependencies = false }
17+
opt.on('--no-privacy-boxes', "Don't show privacy enforcement box on a pack") { |o| options.show_privacy = false }
18+
opt.on('--no-layers', "Don't show architectural layers") { |o| options.show_layers = false }
19+
opt.on('--no-visibility-arrows', "Don't show visibility arrows") { |o| options.show_visibility = false }
20+
21+
opt.on('--no-todo-edges', "Don't show todos for package relationships") { |o| options.show_relationship_todos = false }
22+
opt.on("--edge-todo-types=STRING", "Show only the selected types of relationship todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.relationship_todo_types = o.to_s.split(",").uniq.map { EdgeTodoTypes.deserialize(_1) } }
23+
opt.on("--use-edge-todos-for-layout", "Show only the selected types of relationship todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.use_relationship_todos_for_layout = true }
24+
25+
opt.on('--no-teams', "Don't show team colors") { |o| options.show_teams = false }
26+
opt.on('--no-node-todos', "Don't show package-based todos") { |o| options.show_node_todos = false }
27+
28+
opt.on('--focus-pack=STRING', "Focus on a specific pack(s). Comma-separated list of packs. Wildcards supported: 'packs/*'") { |o| options.focus_pack = o.to_s.split(",") }
29+
opt.on('--focus-pack-edge-mode=STRING', "If focus-pack is set, this shows only between focussed packs (when set to none) or the edges into / out of / in and out of the focus packs to non-focus packs (which will be re-added to the graph). One of #{FocusPackEdgeDirection.values.map &:serialize}") { |o| options.show_only_edges_to_focus_pack = FocusPackEdgeDirection.deserialize(o) }
30+
opt.on('--exclude-packs=', "Exclude listed packs from diagram. If used with include you will get all included that are not excluded. Wildcards support: 'packs/ignores/*'") { |o| options.exclude_packs = o.to_s.split(",") }
31+
32+
opt.on('--roll-nested-into-parent-packs', "Don't show nested packs (not counting root). Connect edges to top-level pack instead") { |o| options.roll_nested_into_parent_packs = true }
33+
opt.on('--no-nesting-arrows', "Don't draw relationships between parents and nested packs") { |o| options.show_nested_relationships = false }
34+
35+
opt.on('--remote-base-url=STRING', "Link pack packs to a URL (affects graphviz SVG generation)") { |o| options.remote_base_url = o }
36+
37+
opt.on('--title=STRING', "Set a custom diagram title") { |o| options.title = o }
38+
39+
opt.on('-V', '--version', "Show version") do
40+
spec_path = File.expand_path("../visualize_packs.gemspec", __dir__)
41+
spec = Gem::Specification::load(spec_path)
42+
puts "Version #{spec.version}"
43+
exit
44+
end
45+
46+
opt.on_tail("-h", "--help", "Show this message") do
47+
puts opt
48+
exit
49+
end
50+
end.parse(args)
51+
52+
options
53+
end
54+
end
55+

spec/diagram_generation_helper.sh

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,10 @@ URL="--remote-base-url=https://github.com/rubyatscale/visualize_packwerk/tree/ma
1313
declare -a test_names # To keep the list of tests in the defined order
1414
declare -A test_params # To map test names to command parameters
1515

16-
test_names+=("plain"); test_params[${test_names[-1]}]=""
17-
18-
test_names+=("no_legend"); test_params[${test_names[-1]}]="--no-legend"
19-
test_names+=("no_layers"); test_params[${test_names[-1]}]="--no-layers"
20-
test_names+=("no_teams"); test_params[${test_names[-1]}]="--no-teams"
21-
22-
test_names+=("no_dependencies"); test_params[${test_names[-1]}]="--no-dependency-arrows --use-edge-todos-for-layout"
23-
test_names+=("no_todos"); test_params[${test_names[-1]}]="--no-todo-edges"
24-
test_names+=("no_privacy"); test_params[${test_names[-1]}]="--no-privacy-boxes --no-node-todos"
25-
test_names+=("no_nested"); test_params[${test_names[-1]}]="--no-nesting-arrows"
26-
test_names+=("no_visibility"); test_params[${test_names[-1]}]="--no-visibility-arrows"
27-
28-
test_names+=("relationship_todo_types"); test_params[${test_names[-1]}]="--edge-todo-types=architecture,visibility"
16+
test_names+=("plain"); test_params[${test_names[-1]}]="--title='custom title: Without options'"
17+
test_names+=("no_to_all"); test_params[${test_names[-1]}]="--title='custom title: Hide everything' --no-node-todos --no-legend --no-layers --no-visibility --no-dependency-arrows --no-todo-edges --no-privacy-boxes --no-teams --no-nesting-arrows"
18+
test_names+=("relationship_todo_types"); test_params[${test_names[-1]}]="--title='Show only architecture and visibility todos' --edge-todo-types=architecture,visibility"
2919
test_names+=("roll_nested_into_parent_packs"); test_params[${test_names[-1]}]="--roll-nested-into-parent-packs"
30-
test_names+=("only_layers"); test_params[${test_names[-1]}]="--no-dependency-arrows --no-todo-edges --no-privacy-boxes --no-teams --no-nesting-arrows"
31-
test_names+=("no_to_all"); test_params[${test_names[-1]}]="--title='Hide everything' --no-layers --no-visibility --no-dependency-arrows --no-todo-edges --no-privacy-boxes --no-teams --no-nesting-arrows"
3220
test_names+=("focussed_on_packs_ui"); test_params[${test_names[-1]}]="--focus-pack=packs/ui"
3321
test_names+=("focussed_on_packs_ui_focus_edges"); test_params[${test_names[-1]}]="--focus-pack=packs/ui --focus-pack-edge-mode=inout"
3422
test_names+=("focussed_on_packs_ui_focus_edges_in"); test_params[${test_names[-1]}]="--focus-pack=packs/ui --focus-pack-edge-mode=in"

spec/sample_app1/Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: ../..
33
specs:
4-
visualize_packs (0.5.21)
4+
visualize_packs (0.5.22)
55
packs-specification
66
parse_packwerk (>= 0.20.0)
77
sorbet-runtime
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# This file contains a list of dependencies that are not part of the long term plan for the
2+
# '.' package.
3+
# We should generally work to reduce this list over time.
4+
#
5+
# You can regenerate this file using the following command:
6+
#
7+
# bin/packwerk update-todo
8+
---
9+
packs/ui:
10+
"::SomeClass1":
11+
violations:
12+
- dependency
13+
files:
14+
- file_b
15+
packs/models/packs/model_a:
16+
"::SomeClass1":
17+
violations:
18+
- dependency
19+
files:
20+
- file_b

0 commit comments

Comments
 (0)