visualize_packs 0.5.9 → 0.5.11
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/bin/visualize_packs +5 -4
- data/lib/graph.dot.erb +1 -1
- data/lib/options.rb +2 -1
- data/lib/visualize_packs.rb +76 -35
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1c97e3f2997e104fa0359775892adfb5e8cc40d22a85d886bf0f8a1d608398a8
|
|
4
|
+
data.tar.gz: 7b933103509243b81126bc29cd193daf9536a46860d36ad40f1d988df29d77b3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0e0c1ca8ea26ee9cbd57b3e4066960868ea9362de4bae5fad1517481376ba8f3dd476ff8ad1f43c186d9b9c9d291a7332c203b877e37d5e9d6e07f4277d70899
|
|
7
|
+
data.tar.gz: 632e641dc9e1c45dd744bf9d09bff3cc9723138bc8eb27c1d6771afdc51ad3aef2de988801420b64a1c7f27155fb530fcbfe66cd3c4c65e7eb3b4c6de5e220e6
|
data/bin/visualize_packs
CHANGED
|
@@ -32,14 +32,15 @@ OptionParser.new do |opt|
|
|
|
32
32
|
opt.on('--no-privacy', "Don't show privacy enforcement") { |o| options.show_privacy = false }
|
|
33
33
|
opt.on('--no-teams', "Don't show team colors") { |o| options.show_teams = false }
|
|
34
34
|
|
|
35
|
-
opt.on('--focus-
|
|
35
|
+
opt.on('--focus-folder=FOLDER', "Draw package diagram only for packages in FOLDER. Matches with 'include' for partial matches") { |o| options.focus_folder = o.empty? ? nil : o }
|
|
36
|
+
opt.on('--focus-pack=pack1,pack2', "Focus on a specific package(s). Wildcards support: 'packs/*'") { |o| options.focus_package = o.to_s.split(",") }
|
|
36
37
|
opt.on('--only-edges-to-focus', "If focus is set, this shows only the edges to/from the focus node instead of all edges in the focussed graph. This only has effect when --focus-on is set.") { |o| options.show_only_edges_to_focus_package = true }
|
|
37
38
|
|
|
38
39
|
opt.on('--roll-nested-into-parent-packs', "Don't show nested packages (not counting root). Connect edges to top-level package instead") { |o| options.roll_nested_into_parent_packs = true }
|
|
39
|
-
opt.on('--
|
|
40
|
-
opt.on('--no_nested_relationships', "Don't draw relationships between parents and nested packs") { |o| options.show_nested_relationships = false }
|
|
40
|
+
opt.on('--no-nested-relationships', "Don't draw relationships between parents and nested packs") { |o| options.show_nested_relationships = false }
|
|
41
41
|
|
|
42
|
-
opt.on('--exclude-packs=pack1,pack2,etc', "Exclude listed packs from diagram.
|
|
42
|
+
opt.on('--exclude-packs=pack1,pack2,etc', "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(",") }
|
|
43
|
+
opt.on('--include-packs=pack1,pack2,etc', "Include only listed packs in diagram. If used with exclude you will get all included that are not excluded. Wildcards support: 'packs/ignores/*'") { |o| options.include_packs = o.to_s.split(",") }
|
|
43
44
|
|
|
44
45
|
opt.on('--remote-base-url=PACKAGE', "Link package nodes to an URL (affects graphviz SVG generation)") { |o| options.remote_base_url = o }
|
|
45
46
|
|
data/lib/graph.dot.erb
CHANGED
|
@@ -33,7 +33,7 @@ digraph package_diagram {
|
|
|
33
33
|
<%- end -%>
|
|
34
34
|
<%- grouped_packages[layer_name].each do |package| -%>
|
|
35
35
|
"<%= package.name -%>" [
|
|
36
|
-
fontsize=<%= options.focus_package &&
|
|
36
|
+
fontsize=<%= options.focus_package.any? && options.focus_package.any? {|p| File.fnmatch(p, package.name)} ? 18.0 : 12.0 -%>
|
|
37
37
|
<%- if options.remote_base_url %>
|
|
38
38
|
URL="<%= options.remote_base_url %>/<%= package.name == '.' ? '' : package.name -%>"
|
|
39
39
|
<%- end %>
|
data/lib/options.rb
CHANGED
|
@@ -12,7 +12,7 @@ class Options < T::Struct
|
|
|
12
12
|
prop :show_privacy, T::Boolean, default: true
|
|
13
13
|
prop :show_teams, T::Boolean, default: true
|
|
14
14
|
|
|
15
|
-
prop :focus_package, T
|
|
15
|
+
prop :focus_package, T::Array[String], default: []
|
|
16
16
|
prop :show_only_edges_to_focus_package, T::Boolean, default: false
|
|
17
17
|
|
|
18
18
|
prop :roll_nested_into_parent_packs, T::Boolean, default: false
|
|
@@ -20,6 +20,7 @@ class Options < T::Struct
|
|
|
20
20
|
prop :show_nested_relationships, T::Boolean, default: true
|
|
21
21
|
|
|
22
22
|
prop :exclude_packs, T::Array[String], default: []
|
|
23
|
+
prop :include_packs, T.nilable(T::Array[String]), default: nil
|
|
23
24
|
|
|
24
25
|
prop :remote_base_url, T.nilable(String)
|
|
25
26
|
end
|
data/lib/visualize_packs.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# typed:
|
|
2
|
+
# typed: strict
|
|
3
3
|
|
|
4
4
|
require 'erb'
|
|
5
5
|
require 'packs-specification'
|
|
@@ -7,14 +7,14 @@ require 'parse_packwerk'
|
|
|
7
7
|
require 'digest/md5'
|
|
8
8
|
|
|
9
9
|
module VisualizePacks
|
|
10
|
+
extend T::Sig
|
|
10
11
|
|
|
12
|
+
sig { params(options: Options, raw_config: T::Hash[String, T.untyped], packages: T::Array[ParsePackwerk::Package]).returns(String) }
|
|
11
13
|
def self.package_graph!(options, raw_config, packages)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
all_packages = filtered(packages, options.focus_package, options.focus_folder, options.exclude_packs).sort_by {|x| x.name }
|
|
14
|
+
all_packages = filtered(packages, options).compact.sort_by {|x| x.name }
|
|
15
15
|
all_package_names = all_packages.map &:name
|
|
16
16
|
|
|
17
|
-
all_packages = remove_nested_packs(all_packages
|
|
17
|
+
all_packages = remove_nested_packs(all_packages, options)
|
|
18
18
|
|
|
19
19
|
show_edge = show_edge_builder(options, all_package_names)
|
|
20
20
|
node_color = node_color_builder()
|
|
@@ -48,14 +48,16 @@ module VisualizePacks
|
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
|
+
sig { params(package: ParsePackwerk::Package).returns(T.nilable(String)) }
|
|
51
52
|
def self.code_owner(package)
|
|
52
53
|
package.config.dig("metadata", "owner") || package.config["owner"]
|
|
53
54
|
end
|
|
54
55
|
|
|
56
|
+
sig { params(options: Options, max_todo_count: T.nilable(Integer)).returns(String) }
|
|
55
57
|
def self.diagram_title(options, max_todo_count)
|
|
56
58
|
app_name = File.basename(Dir.pwd)
|
|
57
|
-
focus_edge_info = options.focus_package && options.show_only_edges_to_focus_package ? "showing only edges to/from focus pack" : "showing all edges between visible packs"
|
|
58
|
-
focus_info = options.focus_package || options.focus_folder ? "Focus on #{[options.focus_package, options.focus_folder].compact.join(' and ')} (#{focus_edge_info})" : "All packs"
|
|
59
|
+
focus_edge_info = options.focus_package.any? && options.show_only_edges_to_focus_package ? "showing only edges to/from focus pack" : "showing all edges between visible packs"
|
|
60
|
+
focus_info = options.focus_package.any? || options.focus_folder ? "Focus on #{[limited_sentence(options.focus_package), options.focus_folder].compact.join(' and ')} (#{focus_edge_info})" : "All packs"
|
|
59
61
|
skipped_info =
|
|
60
62
|
[
|
|
61
63
|
options.show_legend ? nil : "hiding legend",
|
|
@@ -67,6 +69,7 @@ module VisualizePacks
|
|
|
67
69
|
options.show_teams ? nil : "hiding teams",
|
|
68
70
|
options.roll_nested_into_parent_packs ? "hiding nested packs" : nil,
|
|
69
71
|
options.show_nested_relationships ? nil : "hiding nested relationships",
|
|
72
|
+
options.include_packs ? "including only: #{limited_sentence(options.include_packs)}" : nil,
|
|
70
73
|
options.exclude_packs.empty? ? nil : "excluding pack#{options.exclude_packs.size > 1 ? 's' : ''}: #{limited_sentence(options.exclude_packs)}",
|
|
71
74
|
].compact.join(', ').strip
|
|
72
75
|
main_title = "#{app_name}: #{focus_info}#{skipped_info != '' ? ' - ' + skipped_info : ''}"
|
|
@@ -77,14 +80,18 @@ module VisualizePacks
|
|
|
77
80
|
"<<b>#{main_title}</b>#{sub_title}>"
|
|
78
81
|
end
|
|
79
82
|
|
|
83
|
+
sig { params(list: T.nilable(T::Array[String])).returns(T.nilable(String)) }
|
|
80
84
|
def self.limited_sentence(list)
|
|
85
|
+
return nil if !list || list.empty?
|
|
86
|
+
|
|
81
87
|
if list.size <= 2
|
|
82
88
|
list.join(" and ")
|
|
83
89
|
else
|
|
84
|
-
"#{list[0, 2].join(", ")}, and #{list.size - 2} more"
|
|
90
|
+
"#{T.must(list[0, 2]).join(", ")}, and #{list.size - 2} more"
|
|
85
91
|
end
|
|
86
92
|
end
|
|
87
93
|
|
|
94
|
+
sig { params(options: Options, all_package_names: T::Array[String]).returns(T.proc.params(arg0: String, arg1: String).returns(T::Boolean)) }
|
|
88
95
|
def self.show_edge_builder(options, all_package_names)
|
|
89
96
|
return lambda do |start_node, end_node|
|
|
90
97
|
(
|
|
@@ -96,11 +103,15 @@ module VisualizePacks
|
|
|
96
103
|
options.show_only_edges_to_focus_package &&
|
|
97
104
|
all_package_names.include?(start_node) &&
|
|
98
105
|
all_package_names.include?(end_node) &&
|
|
99
|
-
|
|
106
|
+
(
|
|
107
|
+
match_packs?(start_node, options.focus_package) ||
|
|
108
|
+
match_packs?(end_node, options.focus_package)
|
|
109
|
+
)
|
|
100
110
|
)
|
|
101
111
|
end
|
|
102
112
|
end
|
|
103
113
|
|
|
114
|
+
sig { returns(T.nilable(T.proc.params(arg0: String).returns(String))) }
|
|
104
115
|
def self.node_color_builder
|
|
105
116
|
return lambda do |text|
|
|
106
117
|
return unless text
|
|
@@ -113,17 +124,17 @@ module VisualizePacks
|
|
|
113
124
|
end
|
|
114
125
|
end
|
|
115
126
|
|
|
127
|
+
sig { params(all_packages: T::Array[ParsePackwerk::Package], show_edge: T.proc.params(arg0: String, arg1: String).returns(T::Boolean)).returns(T.nilable(Integer)) }
|
|
116
128
|
def self.max_todo_count(all_packages, show_edge)
|
|
117
129
|
todo_counts = {}
|
|
118
130
|
all_packages.each do |package|
|
|
119
|
-
todos_by_package = package.violations
|
|
120
|
-
todos_by_package
|
|
121
|
-
todo_types = todos_by_package[todos_to_package]
|
|
122
|
-
todo_types
|
|
131
|
+
todos_by_package = package.violations&.group_by(&:to_package_name)
|
|
132
|
+
todos_by_package&.keys&.each do |todos_to_package|
|
|
133
|
+
todo_types = todos_by_package&& todos_by_package[todos_to_package]&.group_by(&:type)
|
|
134
|
+
todo_types&.keys&.each do |todo_type|
|
|
123
135
|
if show_edge.call(package.name, todos_to_package)
|
|
124
136
|
key = "#{package.name}->#{todos_to_package}:#{todo_type}"
|
|
125
|
-
todo_counts[key] = todo_types[todo_type]
|
|
126
|
-
# todo_counts[key] += 1
|
|
137
|
+
todo_counts[key] = todo_types && todo_types[todo_type]&.count
|
|
127
138
|
end
|
|
128
139
|
end
|
|
129
140
|
end
|
|
@@ -131,6 +142,7 @@ module VisualizePacks
|
|
|
131
142
|
todo_counts.values.max
|
|
132
143
|
end
|
|
133
144
|
|
|
145
|
+
sig { params(todo_count: Integer, max_count: Integer).returns(T.any(Float, Integer)) }
|
|
134
146
|
def self.todo_edge_width(todo_count, max_count)
|
|
135
147
|
# Limits
|
|
136
148
|
min_width = 1
|
|
@@ -150,31 +162,58 @@ module VisualizePacks
|
|
|
150
162
|
edge_width.round(2)
|
|
151
163
|
end
|
|
152
164
|
|
|
153
|
-
|
|
154
|
-
|
|
165
|
+
sig { params(packages: T::Array[ParsePackwerk::Package], options: Options).returns(T::Array[ParsePackwerk::Package]) }
|
|
166
|
+
def self.filtered(packages, options)
|
|
167
|
+
focus_package = options.focus_package
|
|
168
|
+
focus_folder = options.focus_folder
|
|
169
|
+
include_packs = options.include_packs
|
|
170
|
+
exclude_packs = options.exclude_packs
|
|
171
|
+
|
|
172
|
+
return packages unless focus_package.any? || focus_folder || include_packs || exclude_packs.any?
|
|
173
|
+
|
|
174
|
+
nested_packages = all_nested_packages(packages.map { |p| p.name })
|
|
155
175
|
|
|
176
|
+
packages_by_name = packages.inject({}) do |res, p|
|
|
177
|
+
res[p.name] = p
|
|
178
|
+
res
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
result = T.let([], T::Array[T.nilable(String)])
|
|
156
182
|
result = packages.map { |pack| pack.name }
|
|
157
183
|
|
|
158
|
-
if
|
|
159
|
-
result = [
|
|
160
|
-
result += packages.
|
|
161
|
-
result +=
|
|
162
|
-
result += packages.select{ |p| p.violations
|
|
163
|
-
|
|
184
|
+
if !focus_package.empty?
|
|
185
|
+
result = []
|
|
186
|
+
result += packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_package) }
|
|
187
|
+
result += packages.select{ |p| p.dependencies.any? { |d| match_packs?(d, focus_package) }}.map { |pack| pack.name }
|
|
188
|
+
result += packages.select{ |p| p.violations&.map(&:to_package_name)&.any? { |v| match_packs?(v, focus_package) }}.map { |pack| pack.name }
|
|
189
|
+
packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_package) }.each do |p|
|
|
190
|
+
result += packages_by_name[p].dependencies
|
|
191
|
+
result += packages_by_name[p].violations.map(&:to_package_name)
|
|
192
|
+
end
|
|
164
193
|
result = result.uniq
|
|
194
|
+
parent_packs = result.inject([]) do |res, package_name|
|
|
195
|
+
res << nested_packages[package_name]
|
|
196
|
+
res
|
|
197
|
+
end
|
|
198
|
+
result = (result + parent_packs).uniq.compact
|
|
165
199
|
end
|
|
166
200
|
|
|
167
|
-
if
|
|
168
|
-
result = result.select { |p| p.include?
|
|
201
|
+
if focus_folder
|
|
202
|
+
result = result.select { |p| p.include? focus_folder }
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
if include_packs
|
|
206
|
+
result = result.select { |p| match_packs?(p, include_packs) }
|
|
169
207
|
end
|
|
170
208
|
|
|
171
209
|
if exclude_packs.any?
|
|
172
|
-
result = result.reject { |p|
|
|
210
|
+
result = result.reject { |p| match_packs?(p, exclude_packs) }
|
|
173
211
|
end
|
|
174
212
|
|
|
175
|
-
result.map { |pack_name|
|
|
213
|
+
result.map { |pack_name| packages_by_name[pack_name] }
|
|
176
214
|
end
|
|
177
215
|
|
|
216
|
+
sig { params(all_package_names: T::Array[String]).returns(T::Hash[String, String]) }
|
|
178
217
|
def self.all_nested_packages(all_package_names)
|
|
179
218
|
all_package_names.reject { |p| p == '.' }.inject({}) do |result, package|
|
|
180
219
|
package_map_tally = all_package_names.map { |other_package| Pathname.new(package).parent.to_s.include?(other_package) }
|
|
@@ -188,7 +227,10 @@ module VisualizePacks
|
|
|
188
227
|
end
|
|
189
228
|
end
|
|
190
229
|
|
|
191
|
-
|
|
230
|
+
sig { params(packages: T::Array[ParsePackwerk::Package], options: Options).returns(T::Array[ParsePackwerk::Package]) }
|
|
231
|
+
def self.remove_nested_packs(packages, options)
|
|
232
|
+
return packages unless options.roll_nested_into_parent_packs
|
|
233
|
+
|
|
192
234
|
nested_packages = all_nested_packages(packages.map { |p| p.name })
|
|
193
235
|
|
|
194
236
|
# top-level packages
|
|
@@ -207,9 +249,9 @@ module VisualizePacks
|
|
|
207
249
|
enforce_privacy: package.enforce_privacy,
|
|
208
250
|
public_path: package.public_path,
|
|
209
251
|
metadata: package.metadata,
|
|
210
|
-
dependencies: package.dependencies + nested_package.dependencies,
|
|
252
|
+
dependencies: package.dependencies + T.must(nested_package).dependencies,
|
|
211
253
|
config: package.config,
|
|
212
|
-
violations: package.violations + nested_package.violations
|
|
254
|
+
violations: T.must(package.violations) + T.must(T.must(nested_package).violations)
|
|
213
255
|
)
|
|
214
256
|
end
|
|
215
257
|
end
|
|
@@ -219,7 +261,7 @@ module VisualizePacks
|
|
|
219
261
|
nested_packages[d] || d
|
|
220
262
|
end.uniq.reject { |p| p == package.name }
|
|
221
263
|
|
|
222
|
-
morphed_todos = package.violations.map do |v|
|
|
264
|
+
morphed_todos = T.must(package.violations).map do |v|
|
|
223
265
|
ParsePackwerk::Violation.new(
|
|
224
266
|
type: v.type,
|
|
225
267
|
to_package_name: nested_packages[v.to_package_name] || v.to_package_name,
|
|
@@ -239,15 +281,14 @@ module VisualizePacks
|
|
|
239
281
|
config: package.config,
|
|
240
282
|
violations: morphed_todos
|
|
241
283
|
)
|
|
242
|
-
# add dependencies TO nested packages to top-level package
|
|
243
|
-
# add todos TO nested packages to top-level package
|
|
244
284
|
end
|
|
245
285
|
end
|
|
246
286
|
|
|
247
287
|
morphed_packages.reject { |p| nested_packages.keys.include?(p.name) }
|
|
248
288
|
end
|
|
249
289
|
|
|
250
|
-
|
|
251
|
-
|
|
290
|
+
sig { params(pack: String, packs_name_with_wildcards: T::Array[String]).returns(T::Boolean) }
|
|
291
|
+
def self.match_packs?(pack, packs_name_with_wildcards)
|
|
292
|
+
packs_name_with_wildcards.any? {|p| File.fnmatch(p, pack)}
|
|
252
293
|
end
|
|
253
294
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: visualize_packs
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gusto Engineers
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-
|
|
11
|
+
date: 2023-09-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|