visualize_packs 0.5.9 → 0.5.11

Sign up to get free protection for your applications and to get access to all the features.
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