visualize_packs 0.5.10 → 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: 93422667173f599ef3700a5a98451ea6fc00ae8269544e6b66138034fc743f22
4
- data.tar.gz: 9c238ea399cfcd9426f6de268a9b25796ac04995c39bd7ace459961a7e21d502
3
+ metadata.gz: 1c97e3f2997e104fa0359775892adfb5e8cc40d22a85d886bf0f8a1d608398a8
4
+ data.tar.gz: 7b933103509243b81126bc29cd193daf9536a46860d36ad40f1d988df29d77b3
5
5
  SHA512:
6
- metadata.gz: 556726fd4072b0426afa6c7e65271dc06d25f159d70c44245b5058bac0543fbfde80e0cfa205046b02428b856c0ecc2926828c85eb4988f9378b2e8dce5c609b
7
- data.tar.gz: c4d7c4fc2a64f278e9b5e6fae2afce66c1729eb47617807de29f7b6b7518ec091cb4da8a83ac6b945ecbb45b5bf2b2910e839c39c10d8dee3ab69dc817724605
6
+ metadata.gz: 0e0c1ca8ea26ee9cbd57b3e4066960868ea9362de4bae5fad1517481376ba8f3dd476ff8ad1f43c186d9b9c9d291a7332c203b877e37d5e9d6e07f4277d70899
7
+ data.tar.gz: 632e641dc9e1c45dd744bf9d09bff3cc9723138bc8eb27c1d6771afdc51ad3aef2de988801420b64a1c7f27155fb530fcbfe66cd3c4c65e7eb3b4c6de5e220e6
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 }
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
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_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
@@ -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_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",
@@ -78,14 +80,18 @@ 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
  (
@@ -97,11 +103,15 @@ module VisualizePacks
97
103
  options.show_only_edges_to_focus_package &&
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
+ match_packs?(start_node, options.focus_package) ||
108
+ match_packs?(end_node, options.focus_package)
109
+ )
101
110
  )
102
111
  end
103
112
  end
104
113
 
114
+ sig { returns(T.nilable(T.proc.params(arg0: String).returns(String))) }
105
115
  def self.node_color_builder
106
116
  return lambda do |text|
107
117
  return unless text
@@ -114,17 +124,17 @@ module VisualizePacks
114
124
  end
115
125
  end
116
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)) }
117
128
  def self.max_todo_count(all_packages, show_edge)
118
129
  todo_counts = {}
119
130
  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|
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|
124
135
  if show_edge.call(package.name, todos_to_package)
125
136
  key = "#{package.name}->#{todos_to_package}:#{todo_type}"
126
- todo_counts[key] = todo_types[todo_type].count
127
- # todo_counts[key] += 1
137
+ todo_counts[key] = todo_types && todo_types[todo_type]&.count
128
138
  end
129
139
  end
130
140
  end
@@ -132,6 +142,7 @@ module VisualizePacks
132
142
  todo_counts.values.max
133
143
  end
134
144
 
145
+ sig { params(todo_count: Integer, max_count: Integer).returns(T.any(Float, Integer)) }
135
146
  def self.todo_edge_width(todo_count, max_count)
136
147
  # Limits
137
148
  min_width = 1
@@ -151,22 +162,44 @@ module VisualizePacks
151
162
  edge_width.round(2)
152
163
  end
153
164
 
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?
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 })
156
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)])
157
182
  result = packages.map { |pack| pack.name }
158
183
 
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)
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
165
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
166
199
  end
167
200
 
168
- if filter_folder
169
- result = result.select { |p| p.include? filter_folder }
201
+ if focus_folder
202
+ result = result.select { |p| p.include? focus_folder }
170
203
  end
171
204
 
172
205
  if include_packs
@@ -177,9 +210,10 @@ module VisualizePacks
177
210
  result = result.reject { |p| match_packs?(p, exclude_packs) }
178
211
  end
179
212
 
180
- result.map { |pack_name| ParsePackwerk.find(pack_name) }
213
+ result.map { |pack_name| packages_by_name[pack_name] }
181
214
  end
182
215
 
216
+ sig { params(all_package_names: T::Array[String]).returns(T::Hash[String, String]) }
183
217
  def self.all_nested_packages(all_package_names)
184
218
  all_package_names.reject { |p| p == '.' }.inject({}) do |result, package|
185
219
  package_map_tally = all_package_names.map { |other_package| Pathname.new(package).parent.to_s.include?(other_package) }
@@ -193,7 +227,10 @@ module VisualizePacks
193
227
  end
194
228
  end
195
229
 
196
- 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
+
197
234
  nested_packages = all_nested_packages(packages.map { |p| p.name })
198
235
 
199
236
  # top-level packages
@@ -212,9 +249,9 @@ module VisualizePacks
212
249
  enforce_privacy: package.enforce_privacy,
213
250
  public_path: package.public_path,
214
251
  metadata: package.metadata,
215
- dependencies: package.dependencies + nested_package.dependencies,
252
+ dependencies: package.dependencies + T.must(nested_package).dependencies,
216
253
  config: package.config,
217
- violations: package.violations + nested_package.violations
254
+ violations: T.must(package.violations) + T.must(T.must(nested_package).violations)
218
255
  )
219
256
  end
220
257
  end
@@ -224,7 +261,7 @@ module VisualizePacks
224
261
  nested_packages[d] || d
225
262
  end.uniq.reject { |p| p == package.name }
226
263
 
227
- morphed_todos = package.violations.map do |v|
264
+ morphed_todos = T.must(package.violations).map do |v|
228
265
  ParsePackwerk::Violation.new(
229
266
  type: v.type,
230
267
  to_package_name: nested_packages[v.to_package_name] || v.to_package_name,
@@ -244,15 +281,14 @@ module VisualizePacks
244
281
  config: package.config,
245
282
  violations: morphed_todos
246
283
  )
247
- # add dependencies TO nested packages to top-level package
248
- # add todos TO nested packages to top-level package
249
284
  end
250
285
  end
251
286
 
252
287
  morphed_packages.reject { |p| nested_packages.keys.include?(p.name) }
253
288
  end
254
289
 
255
- def self.match_packs?(pack, packs)
256
- 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)}
257
293
  end
258
294
  end
metadata CHANGED
@@ -1,7 +1,7 @@
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.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gusto Engineers