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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93422667173f599ef3700a5a98451ea6fc00ae8269544e6b66138034fc743f22
4
- data.tar.gz: 9c238ea399cfcd9426f6de268a9b25796ac04995c39bd7ace459961a7e21d502
3
+ metadata.gz: 8017d316d5d179efe32200f2152bb0abb1c8a3b3299ba8f777a604c07165660e
4
+ data.tar.gz: 5f306bd08bdc5cb70eee69ea23a9233ba03ad8d13bd7c6c75c92a39a18f3f1f6
5
5
  SHA512:
6
- metadata.gz: 556726fd4072b0426afa6c7e65271dc06d25f159d70c44245b5058bac0543fbfde80e0cfa205046b02428b856c0ecc2926828c85eb4988f9378b2e8dce5c609b
7
- data.tar.gz: c4d7c4fc2a64f278e9b5e6fae2afce66c1729eb47617807de29f7b6b7518ec091cb4da8a83ac6b945ecbb45b5bf2b2910e839c39c10d8dee3ab69dc817724605
6
+ metadata.gz: e9c0773394b6f17466e75e00c825dfcca7a1d359922988b257a08e59fb1ce76e2c373cfe968783d87f3ec8da5b9f500d53d64dcfc74946a3ac804376e22bdeb3
7
+ data.tar.gz: aff8ae19a21ca89a91331c2c2679fc64e15c5151e5f59a72a7dc35b7d5df72680e0627d6462ec6b5cc519098263210c60994445a563fc6aa95149be7a4d4c684
data/README.md CHANGED
@@ -48,3 +48,5 @@ Once you are ready, run the following and commit the new `diagram_examples.png`
48
48
  ```
49
49
  ./spec/update_cassettes.sh
50
50
  ```
51
+
52
+ Please check in a new `diagram_examples.png` only if there are actual visual changes.
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-on=PACKAGE', "Focus on a specific package") { |o| options.focus_package = o }
36
- 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 }
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('--focus_folder=FOLDER', "Draw package diagram only for packages in FOLDER") { |o| options.focus_folder = o }
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.focus_package && package.name == options.focus_package ? 18.0 : 12.0 -%>
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 :focus_package, T.nilable(String)
16
- prop :show_only_edges_to_focus_package, T::Boolean, default: false
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: []
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- # typed: false
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
- raise ArgumentError, "Package #{options.focus_package} does not exist. Found packages #{packages.map(&:name).join(", ")}" if options.focus_package && !packages.map(&:name).include?(options.focus_package)
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) if options.roll_nested_into_parent_packs
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_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.show_only_edges_to_focus_package &&
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.show_only_edges_to_focus_package &&
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
- [start_node, end_node].include?(options.focus_package)
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.group_by(&:to_package_name)
121
- todos_by_package.keys.each do |todos_to_package|
122
- todo_types = todos_by_package[todos_to_package].group_by(&:type)
123
- todo_types.keys.each do |todo_type|
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].count
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
- def self.filtered(packages, filter_package, filter_folder, include_packs, exclude_packs)
155
- return packages unless filter_package || filter_folder || include_packs || exclude_packs.any?
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 filter_package
160
- result = [filter_package]
161
- result += packages.select{ |p| p.dependencies.include? filter_package }.map { |pack| pack.name }
162
- result += ParsePackwerk.find(filter_package).dependencies
163
- result += packages.select{ |p| p.violations.map(&:to_package_name).include? filter_package }.map { |pack| pack.name }
164
- result += ParsePackwerk.find(filter_package).violations.map(&:to_package_name)
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 filter_folder
169
- result = result.select { |p| p.include? filter_folder }
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| ParsePackwerk.find(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
- def self.remove_nested_packs(packages)
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
- def self.match_packs?(pack, packs)
256
- packs.any? {|p| File.fnmatch(p, pack)}
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.10
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-01 00:00:00.000000000 Z
11
+ date: 2023-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler