visualize_packs 0.5.10 → 0.5.12
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/README.md +2 -0
- data/bin/visualize_packs +4 -4
- data/lib/graph.dot.erb +1 -1
- data/lib/options.rb +11 -3
- data/lib/visualize_packs.rb +78 -36
- 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: 8017d316d5d179efe32200f2152bb0abb1c8a3b3299ba8f777a604c07165660e
|
4
|
+
data.tar.gz: 5f306bd08bdc5cb70eee69ea23a9233ba03ad8d13bd7c6c75c92a39a18f3f1f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9c0773394b6f17466e75e00c825dfcca7a1d359922988b257a08e59fb1ce76e2c373cfe968783d87f3ec8da5b9f500d53d64dcfc74946a3ac804376e22bdeb3
|
7
|
+
data.tar.gz: aff8ae19a21ca89a91331c2c2679fc64e15c5151e5f59a72a7dc35b7d5df72680e0627d6462ec6b5cc519098263210c60994445a563fc6aa95149be7a4d4c684
|
data/README.md
CHANGED
data/bin/visualize_packs
CHANGED
@@ -32,12 +32,12 @@ 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-
|
36
|
-
opt.on('--
|
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_pack = o.to_s.split(",") }
|
37
|
+
opt.on('--only-edges-to-focus=[in,out,inout]', "If focus-pack is set, this shows only the edges into / out of / in and out of the focus node instead of all edges in the focussed graph.") { |o| options.show_only_edges_to_focus_pack = FocusPackEdgeDirection.deserialize(o) }
|
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
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
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(",") }
|
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.
|
36
|
+
fontsize=<%= options.focus_pack.any? && options.focus_pack.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
@@ -1,6 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
# typed: strict
|
3
3
|
|
4
|
+
class FocusPackEdgeDirection < T::Enum
|
5
|
+
enums do
|
6
|
+
In = new
|
7
|
+
Out = new
|
8
|
+
InOut = new
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
4
12
|
class Options < T::Struct
|
5
13
|
extend T::Sig
|
6
14
|
|
@@ -12,11 +20,11 @@ class Options < T::Struct
|
|
12
20
|
prop :show_privacy, T::Boolean, default: true
|
13
21
|
prop :show_teams, T::Boolean, default: true
|
14
22
|
|
15
|
-
prop :
|
16
|
-
prop :
|
23
|
+
prop :focus_folder, T.nilable(String)
|
24
|
+
prop :focus_pack, T::Array[String], default: []
|
25
|
+
prop :show_only_edges_to_focus_pack, T.nilable(FocusPackEdgeDirection), default: nil
|
17
26
|
|
18
27
|
prop :roll_nested_into_parent_packs, T::Boolean, default: false
|
19
|
-
prop :focus_folder, T.nilable(String)
|
20
28
|
prop :show_nested_relationships, T::Boolean, default: true
|
21
29
|
|
22
30
|
prop :exclude_packs, T::Array[String], default: []
|
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.include_packs, 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.
|
58
|
-
focus_info = options.
|
59
|
+
focus_edge_info = options.focus_pack.any? && options.show_only_edges_to_focus_pack ? "showing only edges to/from focus pack" : "showing all edges between visible packs"
|
60
|
+
focus_info = options.focus_pack.any? || options.focus_folder ? "Focus on #{[limited_sentence(options.focus_pack), 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",
|
@@ -78,30 +80,44 @@ module VisualizePacks
|
|
78
80
|
"<<b>#{main_title}</b>#{sub_title}>"
|
79
81
|
end
|
80
82
|
|
83
|
+
sig { params(list: T.nilable(T::Array[String])).returns(T.nilable(String)) }
|
81
84
|
def self.limited_sentence(list)
|
85
|
+
return nil if !list || list.empty?
|
86
|
+
|
82
87
|
if list.size <= 2
|
83
88
|
list.join(" and ")
|
84
89
|
else
|
85
|
-
"#{list[0, 2].join(", ")}, and #{list.size - 2} more"
|
90
|
+
"#{T.must(list[0, 2]).join(", ")}, and #{list.size - 2} more"
|
86
91
|
end
|
87
92
|
end
|
88
93
|
|
94
|
+
sig { params(options: Options, all_package_names: T::Array[String]).returns(T.proc.params(arg0: String, arg1: String).returns(T::Boolean)) }
|
89
95
|
def self.show_edge_builder(options, all_package_names)
|
90
96
|
return lambda do |start_node, end_node|
|
91
97
|
(
|
92
|
-
!options.
|
98
|
+
!options.show_only_edges_to_focus_pack &&
|
93
99
|
all_package_names.include?(start_node) &&
|
94
100
|
all_package_names.include?(end_node)
|
95
101
|
) ||
|
96
102
|
(
|
97
|
-
options.
|
103
|
+
options.show_only_edges_to_focus_pack &&
|
98
104
|
all_package_names.include?(start_node) &&
|
99
105
|
all_package_names.include?(end_node) &&
|
100
|
-
|
106
|
+
(
|
107
|
+
case options.show_only_edges_to_focus_pack
|
108
|
+
when FocusPackEdgeDirection::InOut then
|
109
|
+
match_packs?(start_node, options.focus_pack) || match_packs?(end_node, options.focus_pack)
|
110
|
+
when FocusPackEdgeDirection::In then
|
111
|
+
match_packs?(end_node, options.focus_pack)
|
112
|
+
when FocusPackEdgeDirection::Out then
|
113
|
+
match_packs?(start_node, options.focus_pack)
|
114
|
+
end
|
115
|
+
)
|
101
116
|
)
|
102
117
|
end
|
103
118
|
end
|
104
119
|
|
120
|
+
sig { returns(T.nilable(T.proc.params(arg0: String).returns(String))) }
|
105
121
|
def self.node_color_builder
|
106
122
|
return lambda do |text|
|
107
123
|
return unless text
|
@@ -114,17 +130,17 @@ module VisualizePacks
|
|
114
130
|
end
|
115
131
|
end
|
116
132
|
|
133
|
+
sig { params(all_packages: T::Array[ParsePackwerk::Package], show_edge: T.proc.params(arg0: String, arg1: String).returns(T::Boolean)).returns(T.nilable(Integer)) }
|
117
134
|
def self.max_todo_count(all_packages, show_edge)
|
118
135
|
todo_counts = {}
|
119
136
|
all_packages.each do |package|
|
120
|
-
todos_by_package = package.violations
|
121
|
-
todos_by_package
|
122
|
-
todo_types = todos_by_package[todos_to_package]
|
123
|
-
todo_types
|
137
|
+
todos_by_package = package.violations&.group_by(&:to_package_name)
|
138
|
+
todos_by_package&.keys&.each do |todos_to_package|
|
139
|
+
todo_types = todos_by_package&& todos_by_package[todos_to_package]&.group_by(&:type)
|
140
|
+
todo_types&.keys&.each do |todo_type|
|
124
141
|
if show_edge.call(package.name, todos_to_package)
|
125
142
|
key = "#{package.name}->#{todos_to_package}:#{todo_type}"
|
126
|
-
todo_counts[key] = todo_types[todo_type]
|
127
|
-
# todo_counts[key] += 1
|
143
|
+
todo_counts[key] = todo_types && todo_types[todo_type]&.count
|
128
144
|
end
|
129
145
|
end
|
130
146
|
end
|
@@ -132,6 +148,7 @@ module VisualizePacks
|
|
132
148
|
todo_counts.values.max
|
133
149
|
end
|
134
150
|
|
151
|
+
sig { params(todo_count: Integer, max_count: Integer).returns(T.any(Float, Integer)) }
|
135
152
|
def self.todo_edge_width(todo_count, max_count)
|
136
153
|
# Limits
|
137
154
|
min_width = 1
|
@@ -151,22 +168,44 @@ module VisualizePacks
|
|
151
168
|
edge_width.round(2)
|
152
169
|
end
|
153
170
|
|
154
|
-
|
155
|
-
|
171
|
+
sig { params(packages: T::Array[ParsePackwerk::Package], options: Options).returns(T::Array[ParsePackwerk::Package]) }
|
172
|
+
def self.filtered(packages, options)
|
173
|
+
focus_pack = options.focus_pack
|
174
|
+
focus_folder = options.focus_folder
|
175
|
+
include_packs = options.include_packs
|
176
|
+
exclude_packs = options.exclude_packs
|
177
|
+
|
178
|
+
return packages unless focus_pack.any? || focus_folder || include_packs || exclude_packs.any?
|
156
179
|
|
180
|
+
nested_packages = all_nested_packages(packages.map { |p| p.name })
|
181
|
+
|
182
|
+
packages_by_name = packages.inject({}) do |res, p|
|
183
|
+
res[p.name] = p
|
184
|
+
res
|
185
|
+
end
|
186
|
+
|
187
|
+
result = T.let([], T::Array[T.nilable(String)])
|
157
188
|
result = packages.map { |pack| pack.name }
|
158
189
|
|
159
|
-
if
|
160
|
-
result = [
|
161
|
-
result += packages.
|
162
|
-
result +=
|
163
|
-
result += packages.select{ |p| p.violations
|
164
|
-
|
190
|
+
if !focus_pack.empty?
|
191
|
+
result = []
|
192
|
+
result += packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }
|
193
|
+
result += packages.select{ |p| p.dependencies.any? { |d| match_packs?(d, focus_pack) }}.map { |pack| pack.name }
|
194
|
+
result += packages.select{ |p| p.violations&.map(&:to_package_name)&.any? { |v| match_packs?(v, focus_pack) }}.map { |pack| pack.name }
|
195
|
+
packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }.each do |p|
|
196
|
+
result += packages_by_name[p].dependencies
|
197
|
+
result += packages_by_name[p].violations.map(&:to_package_name)
|
198
|
+
end
|
165
199
|
result = result.uniq
|
200
|
+
parent_packs = result.inject([]) do |res, package_name|
|
201
|
+
res << nested_packages[package_name]
|
202
|
+
res
|
203
|
+
end
|
204
|
+
result = (result + parent_packs).uniq.compact
|
166
205
|
end
|
167
206
|
|
168
|
-
if
|
169
|
-
result = result.select { |p| p.include?
|
207
|
+
if focus_folder
|
208
|
+
result = result.select { |p| p.include? focus_folder }
|
170
209
|
end
|
171
210
|
|
172
211
|
if include_packs
|
@@ -177,9 +216,10 @@ module VisualizePacks
|
|
177
216
|
result = result.reject { |p| match_packs?(p, exclude_packs) }
|
178
217
|
end
|
179
218
|
|
180
|
-
result.map { |pack_name|
|
219
|
+
result.map { |pack_name| packages_by_name[pack_name] }
|
181
220
|
end
|
182
221
|
|
222
|
+
sig { params(all_package_names: T::Array[String]).returns(T::Hash[String, String]) }
|
183
223
|
def self.all_nested_packages(all_package_names)
|
184
224
|
all_package_names.reject { |p| p == '.' }.inject({}) do |result, package|
|
185
225
|
package_map_tally = all_package_names.map { |other_package| Pathname.new(package).parent.to_s.include?(other_package) }
|
@@ -193,7 +233,10 @@ module VisualizePacks
|
|
193
233
|
end
|
194
234
|
end
|
195
235
|
|
196
|
-
|
236
|
+
sig { params(packages: T::Array[ParsePackwerk::Package], options: Options).returns(T::Array[ParsePackwerk::Package]) }
|
237
|
+
def self.remove_nested_packs(packages, options)
|
238
|
+
return packages unless options.roll_nested_into_parent_packs
|
239
|
+
|
197
240
|
nested_packages = all_nested_packages(packages.map { |p| p.name })
|
198
241
|
|
199
242
|
# top-level packages
|
@@ -212,9 +255,9 @@ module VisualizePacks
|
|
212
255
|
enforce_privacy: package.enforce_privacy,
|
213
256
|
public_path: package.public_path,
|
214
257
|
metadata: package.metadata,
|
215
|
-
dependencies: package.dependencies + nested_package.dependencies,
|
258
|
+
dependencies: package.dependencies + T.must(nested_package).dependencies,
|
216
259
|
config: package.config,
|
217
|
-
violations: package.violations + nested_package.violations
|
260
|
+
violations: T.must(package.violations) + T.must(T.must(nested_package).violations)
|
218
261
|
)
|
219
262
|
end
|
220
263
|
end
|
@@ -224,7 +267,7 @@ module VisualizePacks
|
|
224
267
|
nested_packages[d] || d
|
225
268
|
end.uniq.reject { |p| p == package.name }
|
226
269
|
|
227
|
-
morphed_todos = package.violations.map do |v|
|
270
|
+
morphed_todos = T.must(package.violations).map do |v|
|
228
271
|
ParsePackwerk::Violation.new(
|
229
272
|
type: v.type,
|
230
273
|
to_package_name: nested_packages[v.to_package_name] || v.to_package_name,
|
@@ -244,15 +287,14 @@ module VisualizePacks
|
|
244
287
|
config: package.config,
|
245
288
|
violations: morphed_todos
|
246
289
|
)
|
247
|
-
# add dependencies TO nested packages to top-level package
|
248
|
-
# add todos TO nested packages to top-level package
|
249
290
|
end
|
250
291
|
end
|
251
292
|
|
252
293
|
morphed_packages.reject { |p| nested_packages.keys.include?(p.name) }
|
253
294
|
end
|
254
295
|
|
255
|
-
|
256
|
-
|
296
|
+
sig { params(pack: String, packs_name_with_wildcards: T::Array[String]).returns(T::Boolean) }
|
297
|
+
def self.match_packs?(pack, packs_name_with_wildcards)
|
298
|
+
packs_name_with_wildcards.any? {|p| File.fnmatch(p, pack)}
|
257
299
|
end
|
258
300
|
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.12
|
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-09-
|
11
|
+
date: 2023-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|