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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06613b520a9c2df95a6bef04aeef2fc1658b6e9ac6ce073db977db6bc72170af
4
- data.tar.gz: 82f9e53a88d7e094b424a9e58f228172afcc621e38dc0d154609df26cbd9129e
3
+ metadata.gz: 1c97e3f2997e104fa0359775892adfb5e8cc40d22a85d886bf0f8a1d608398a8
4
+ data.tar.gz: 7b933103509243b81126bc29cd193daf9536a46860d36ad40f1d988df29d77b3
5
5
  SHA512:
6
- metadata.gz: 7da1041d6cb907ba9f7e2da5e5d411344fd28d83bbc8045f7c6c0b5a0becc88ad090a1a3ffb8b8c4fdcbb5837e912bb867c9bf11651887d2ca7335d884df36ee
7
- data.tar.gz: f6a7b5915af1499829ab9bfde65eb202ba638885e3a233e0048a14eea84830360feb5969101a79b8e943d13e50a5afa372a24524a105dd0a0e5a888d3bcd0f7c
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-on=PACKAGE', "Focus on a specific package") { |o| options.focus_package = o }
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('--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
- opt.on('--exclude-packs=pack1,pack2,etc', "Exclude listed packs from diagram. Allows filname matching style wildcards like 'packs/ignores/*'") { |o| options.exclude_packs = o.to_s.split(",") }
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 && package.name == options.focus_package ? 18.0 : 12.0 -%>
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.nilable(String)
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
@@ -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.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_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
- [start_node, end_node].include?(options.focus_package)
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.group_by(&:to_package_name)
120
- todos_by_package.keys.each do |todos_to_package|
121
- todo_types = todos_by_package[todos_to_package].group_by(&:type)
122
- todo_types.keys.each do |todo_type|
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].count
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
- def self.filtered(packages, filter_package, filter_folder, exclude_packs)
154
- return packages unless filter_package || filter_folder || exclude_packs.any?
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 filter_package
159
- result = [filter_package]
160
- result += packages.select{ |p| p.dependencies.include? filter_package }.map { |pack| pack.name }
161
- result += ParsePackwerk.find(filter_package).dependencies
162
- result += packages.select{ |p| p.violations.map(&:to_package_name).include? filter_package }.map { |pack| pack.name }
163
- result += ParsePackwerk.find(filter_package).violations.map(&:to_package_name)
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 filter_folder
168
- result = result.select { |p| p.include? filter_folder }
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| exclude_pack?(p, exclude_packs) }
210
+ result = result.reject { |p| match_packs?(p, exclude_packs) }
173
211
  end
174
212
 
175
- result.map { |pack_name| ParsePackwerk.find(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
- def self.remove_nested_packs(packages)
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
- def self.exclude_pack?(pack, exclude_packs)
251
- exclude_packs.any? {|p| File.fnmatch(p, pack)}
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.9
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-08-31 00:00:00.000000000 Z
11
+ date: 2023-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler