visualize_packs 0.5.10 → 0.5.12

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: 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