visualize_packs 0.5.19 → 0.5.21

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: 21c174ca9487c1d11560c585972f1da5ff9b8ddc53148537753af82d1fb66a25
4
- data.tar.gz: 2b910b38ad69c3df0631a98b21ec4ca6651190ab973bfc4ba4692e0f1fb7d740
3
+ metadata.gz: 72b6b3b6121cd9e3a2f30a4b927882be78fab2bbff55d2168100552cdf5b8276
4
+ data.tar.gz: 2281e4d89e00c73e18ed025b8b636d2cbafef28addc4955ff4f82ea22ddc442d
5
5
  SHA512:
6
- metadata.gz: 883ddfb826a9f9051b0431d289fd98b6a92d41c41a42f4e560c0fae89a9432e741c5d9fddd2802a9eae64c9c1a6073d541f26ca8269c40d7f27b946908881088
7
- data.tar.gz: 6136452d922cefa21486b40c18b2df6fda1838f25030d66019edfd291101dcae503777f7ecc9581ccf07d0c894f5800adc44248a18b931359d2ab2e1eb65d727
6
+ metadata.gz: 3b9affb4f63a85e56125c00e412373d60c705f793c8da118d0e11899b1dcff0e143cade00d4d2562d23d075e3f2a58d9be594cc751e6ee67038a5d591e6c1c27
7
+ data.tar.gz: e99623cfaaa59016939a675a500f12357da8401d2fb3ac6e87c37b1a1f861c5976665e59e830f2fc44fe38c33e7cc6926721e264a9516e2dd57df20e1bff95dc
data/bin/visualize_packs CHANGED
@@ -17,11 +17,12 @@ OptionParser.new do |opt|
17
17
  opt.on('--no-layers', "Don't show architectural layers") { |o| options.show_layers = false }
18
18
  opt.on('--no-visibility-arrows', "Don't show visibility arrows") { |o| options.show_visibility = false }
19
19
 
20
- opt.on('--no-todo-arrows', "Don't show pack todos") { |o| options.show_todos = false }
21
- opt.on("--only-todo-types=STRING", "Show only the selected types of todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.only_todo_types = o.to_s.split(",").uniq.map { EdgeTodoTypes.deserialize(_1) } }
22
- opt.on("--use-todos-for-layout", "Show only the selected types of todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.use_todos_for_layout = true }
20
+ opt.on('--no-todo-edges', "Don't show todos for package relationships") { |o| options.show_relationship_todos = false }
21
+ opt.on("--edge-todo-types=STRING", "Show only the selected types of relationship todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.relationship_todo_types = o.to_s.split(",").uniq.map { EdgeTodoTypes.deserialize(_1) } }
22
+ opt.on("--use-edge-todos-for-layout", "Show only the selected types of relationship todos. Comma-separated list of #{EdgeTodoTypes.values.map &:serialize}") { |o| options.use_relationship_todos_for_layout = true }
23
23
 
24
24
  opt.on('--no-teams', "Don't show team colors") { |o| options.show_teams = false }
25
+ opt.on('--no-node-todos', "Don't show package-based todos") { |o| options.show_node_todos = false }
25
26
 
26
27
  opt.on('--focus-pack=STRING', "Focus on a specific pack(s). Comma-separated list of packs. Wildcards supported: 'packs/*'") { |o| options.focus_pack = o.to_s.split(",") }
27
28
  opt.on('--focus-pack-edge-mode=STRING', "If focus-pack is set, this shows only between focussed packs (when set to none) or the edges into / out of / in and out of the focus packs to non-focus packs (which will be re-added to the graph). One of #{FocusPackEdgeDirection.values.map &:serialize}") { |o| options.show_only_edges_to_focus_pack = FocusPackEdgeDirection.deserialize(o) }
@@ -34,6 +35,13 @@ OptionParser.new do |opt|
34
35
 
35
36
  opt.on('--title=STRING', "Set a custom diagram title") { |o| options.title = o }
36
37
 
38
+ opt.on('-V', '--version', "Show version") do
39
+ spec_path = File.expand_path("../visualize_packs.gemspec", __dir__)
40
+ spec = Gem::Specification::load(spec_path)
41
+ puts "Version #{spec.version}"
42
+ exit
43
+ end
44
+
37
45
  opt.on_tail("-h", "--help", "Show this message") do
38
46
  puts opt
39
47
  exit
data/lib/graph.dot.erb CHANGED
@@ -46,11 +46,18 @@ digraph package_diagram {
46
46
  <table border='0' cellborder='1' cellspacing='0' cellpadding='4'>
47
47
  <tr> <td port='private'> <%= package.name -%> </td> </tr>
48
48
  </table>
49
- </td></tr></table>
49
+ </td></tr>
50
+ <%- if options.show_node_todos && node_protection.(package.name) != "" -%>
51
+ <tr><td CELLPADDING='1' ALIGN='RIGHT'><%= node_protection.(package.name) %></td></tr>
52
+ <%- end -%>
53
+ </table>
50
54
  >
51
55
  <%- else -%><
52
56
  <table border='0' cellborder='1' cellspacing='0' cellpadding='4'>
53
57
  <tr> <td align='left'> <%= package.name -%> </td> </tr>
58
+ <%- if options.show_node_todos && node_protection.(package.name) != "" -%>
59
+ <tr><td CELLPADDING='1' ALIGN='RIGHT'><%= node_protection.(package.name) %></td></tr>
60
+ <%- end -%>
54
61
  </table>
55
62
  >
56
63
  <%- end -%>
@@ -79,16 +86,16 @@ digraph package_diagram {
79
86
  <%- end -%>
80
87
  <%- end -%>
81
88
  <%- end -%>
82
- <%- if options.show_todos -%>
89
+ <%- if options.show_relationship_todos -%>
83
90
  <%- all_packages.each do |package| -%>
84
- <%- filtered_todos = package.violations.select { options.only_todo_types.include?(EdgeTodoTypes.deserialize(_1.type)) } -%>
91
+ <%- filtered_todos = package.violations.select { options.relationship_todo_types.include?(EdgeTodoTypes.deserialize(_1.type)) } -%>
85
92
  <%- todos_by_package = filtered_todos.group_by(&:to_package_name) -%>
86
93
  <%- todos_by_package.keys.each do |todos_to_package| -%>
87
94
  <%- todo_types = todos_by_package[todos_to_package].group_by(&:type) -%>
88
95
  <%- todo_types.keys.each do |todo_type| -%>
89
96
  <%- if show_edge.call(package.name, todos_to_package) -%>
90
97
  "<%= package.name -%>" -> "<%= todos_to_package -%>"<%= todo_type == 'privacy' ? ':private' : '' -%> [
91
- <%- if !options.use_todos_for_layout -%>
98
+ <%- if !options.use_relationship_todos_for_layout -%>
92
99
  constraint=false
93
100
  <%- end -%>
94
101
  # headlabel="<%= todo_type -%>"
@@ -143,23 +150,23 @@ digraph package_diagram {
143
150
  N [ fontsize=12 shape=box label="package"]
144
151
  M -> N [label="visibile to" <%= VisualizePacks::ArrowHead::ConfiguredVisibileTo.serialize %>]
145
152
  <%- end -%>
146
- <%- if options.show_todos -%>
147
- <%- if options.only_todo_types.include?(EdgeTodoTypes::Privacy) -%>
153
+ <%- if options.show_relationship_todos -%>
154
+ <%- if options.relationship_todo_types.include?(EdgeTodoTypes::Privacy) -%>
148
155
  C [ fontsize=12 shape=box label="package"]
149
156
  D [ fontsize=12 shape=box label="package"]
150
157
  C -> D [label="privacy todo" <%= VisualizePacks::ArrowHead::PrivacyTodo.serialize %>]
151
158
  <%- end -%>
152
- <%- if options.only_todo_types.include?(EdgeTodoTypes::Architecture) -%>
159
+ <%- if options.relationship_todo_types.include?(EdgeTodoTypes::Architecture) -%>
153
160
  E [ fontsize=12 shape=box label="package"]
154
161
  F [ fontsize=12 shape=box label="package"]
155
162
  E -> F [label="architecture todo" <%= VisualizePacks::ArrowHead::ArchitectureTodo.serialize %>]
156
163
  <%- end -%>
157
- <%- if options.only_todo_types.include?(EdgeTodoTypes::Visibility) -%>
164
+ <%- if options.relationship_todo_types.include?(EdgeTodoTypes::Visibility) -%>
158
165
  G [ fontsize=12 shape=box label="package"]
159
166
  H [ fontsize=12 shape=box label="package"]
160
167
  G -> H [label="visibility todo" <%= VisualizePacks::ArrowHead::VisibilityTodo.serialize %>]
161
168
  <%- end -%>
162
- <%- if options.only_todo_types.include?(EdgeTodoTypes::Dependency) -%>
169
+ <%- if options.relationship_todo_types.include?(EdgeTodoTypes::Dependency) -%>
163
170
  I [ fontsize=12 shape=box label="package"]
164
171
  J [ fontsize=12 shape=box label="package"]
165
172
  I -> J [label="dependency todo" <%= VisualizePacks::ArrowHead::DependencyTodo.serialize %>]
@@ -30,11 +30,12 @@ class Options < T::Struct
30
30
  prop :show_layers, T::Boolean, default: true
31
31
  prop :show_visibility, T::Boolean, default: true
32
32
 
33
- prop :show_todos, T::Boolean, default: true
34
- prop :only_todo_types, T::Array[EdgeTodoTypes], default: EdgeTodoTypes.values
35
- prop :use_todos_for_layout, T::Boolean, default: false
33
+ prop :show_relationship_todos, T::Boolean, default: true
34
+ prop :relationship_todo_types, T::Array[EdgeTodoTypes], default: EdgeTodoTypes.values
35
+ prop :use_relationship_todos_for_layout, T::Boolean, default: false
36
36
 
37
37
  prop :show_teams, T::Boolean, default: true
38
+ prop :show_node_todos, T::Boolean, default: true
38
39
 
39
40
  prop :focus_pack, T.nilable(T::Array[String]), default: nil
40
41
  prop :show_only_edges_to_focus_pack, FocusPackEdgeDirection, default: FocusPackEdgeDirection::All
@@ -31,6 +31,7 @@ module VisualizePacks
31
31
 
32
32
  show_edge = show_edge_builder(options, all_package_names)
33
33
  node_color = node_color_builder()
34
+ node_protection = package_based_todos_text_maker()
34
35
  max_todo_count = max_todo_count(all_packages, show_edge, options)
35
36
 
36
37
  title = diagram_title(options, max_todo_count)
@@ -70,26 +71,33 @@ module VisualizePacks
70
71
  def self.diagram_title(options, max_todo_count)
71
72
  return "<<b>#{options.title}</b>>" if options.title
72
73
 
73
- app_name = File.basename(Dir.pwd)
74
- focus_edge_info = options.focus_pack && options.show_only_edges_to_focus_pack != FocusPackEdgeDirection::All ? "showing only edges to/from focus pack" : "showing all edges between visible packs"
75
- focus_info = options.focus_pack ? "Focus on #{limited_sentence(options.focus_pack)} (#{focus_edge_info})" : "All packs"
76
- skipped_info =
77
- [
78
- options.show_legend ? nil : "hiding legend",
79
- options.show_layers ? nil : "hiding layers",
80
- options.show_dependencies ? nil : "hiding dependencies",
81
- options.show_todos ? nil : "hiding todos",
82
- EdgeTodoTypes.values.size == options.only_todo_types.size ? nil : "only #{limited_sentence(options.only_todo_types.map &:serialize)} todos",
83
- options.show_privacy ? nil : "hiding privacy",
84
- options.show_teams ? nil : "hiding teams",
85
- options.show_visibility ? nil : "hiding visibility",
86
- options.roll_nested_into_parent_packs ? "hiding nested packs" : nil,
87
- options.show_nested_relationships ? nil : "hiding nested relationships",
88
- options.exclude_packs.empty? ? nil : "excluding pack#{options.exclude_packs.size > 1 ? 's' : ''}: #{limited_sentence(options.exclude_packs)}",
74
+ focus_info = if options.focus_pack
75
+ "Focus on #{limited_sentence(options.focus_pack)} (Edge mode: #{options.show_only_edges_to_focus_pack.serialize})"
76
+ else
77
+ "All packs"
78
+ end
79
+
80
+ hidden_aspects = [
81
+ options.show_legend ? nil : "legend",
82
+ options.show_layers ? nil : "layers",
83
+ options.show_dependencies ? nil : "dependencies",
84
+ options.show_relationship_todos ? nil : "edge todos",
85
+ options.show_privacy ? nil : "privacy",
86
+ options.show_teams ? nil : "teams",
87
+ options.show_node_todos ? nil : "node todos",
88
+ options.show_visibility ? nil : "visibility",
89
+ options.roll_nested_into_parent_packs ? "nested packs" : nil,
90
+ options.show_nested_relationships ? nil : "nested relationships",
89
91
  ].compact.join(', ').strip
90
- main_title = "#{app_name}: #{focus_info}#{skipped_info != '' ? ' - ' + skipped_info : ''}"
91
- sub_title = ""
92
- if options.show_todos && max_todo_count
92
+ hidden_aspects_title = hidden_aspects != '' ? "Hiding #{hidden_aspects}" : nil
93
+
94
+ todo_types = EdgeTodoTypes.values.size == options.relationship_todo_types.size ? nil : "Only #{options.relationship_todo_types.map &:serialize} todos",
95
+
96
+ exclusions = options.exclude_packs.empty? ? nil : "Excluding pack#{options.exclude_packs.size > 1 ? 's' : ''}: #{limited_sentence(options.exclude_packs)}",
97
+
98
+ main_title = [focus_info, hidden_aspects_title, todo_types, exclusions].compact.join('. ')
99
+
100
+ if options.show_relationship_todos && max_todo_count
93
101
  sub_title = "<br/><font point-size='12'>Widest todo edge is #{max_todo_count} todo#{max_todo_count > 1 ? 's' : ''}</font>"
94
102
  end
95
103
  "<<b>#{main_title}</b>#{sub_title}>"
@@ -115,6 +123,8 @@ module VisualizePacks
115
123
  case options.show_only_edges_to_focus_pack
116
124
  when FocusPackEdgeDirection::All then
117
125
  true
126
+ when FocusPackEdgeDirection::None then
127
+ match_packs?(start_node, options.focus_pack) && match_packs?(end_node, options.focus_pack)
118
128
  when FocusPackEdgeDirection::InOut then
119
129
  match_packs?(start_node, options.focus_pack) || match_packs?(end_node, options.focus_pack)
120
130
  when FocusPackEdgeDirection::In then
@@ -142,13 +152,13 @@ module VisualizePacks
142
152
  sig { params(all_packages: T::Array[ParsePackwerk::Package], show_edge: T.proc.params(arg0: String, arg1: String).returns(T::Boolean), options: Options).returns(T.nilable(Integer)) }
143
153
  def self.max_todo_count(all_packages, show_edge, options)
144
154
  todo_counts = {}
145
- if options.show_todos
155
+ if options.show_relationship_todos
146
156
  all_packages.each do |package|
147
157
  todos_by_package = package.violations&.group_by(&:to_package_name)
148
158
  todos_by_package&.keys&.each do |todos_to_package|
149
159
  todo_types = todos_by_package&& todos_by_package[todos_to_package]&.group_by(&:type)
150
160
  todo_types&.keys&.each do |todo_type|
151
- if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo_type))
161
+ if options.relationship_todo_types.include?(EdgeTodoTypes.deserialize(todo_type))
152
162
  if show_edge.call(package.name, todos_to_package)
153
163
  key = "#{package.name}->#{todos_to_package}:#{todo_type}"
154
164
  todo_counts[key] = todo_types && todo_types[todo_type]&.count
@@ -200,36 +210,32 @@ module VisualizePacks
200
210
  result = T.let([], T::Array[T.nilable(String)])
201
211
  result = packages.map { |pack| pack.name }
202
212
 
203
- if focus_pack && !focus_pack.empty?
213
+ if focus_pack
204
214
  result = []
205
- result += packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }
206
- if options.show_dependencies
207
- result += packages.select { |p| p.dependencies.any? { |d| match_packs?(d, focus_pack) }}.map { |pack| pack.name }
208
- end
209
- if options.show_todos && [FocusPackEdgeDirection::All, FocusPackEdgeDirection::In, FocusPackEdgeDirection::InOut].include?(options.show_only_edges_to_focus_pack)
210
- result += packages.select do
211
- |p| (p.violations || []).inject([]) do |res, todo|
212
- res << todo.to_package_name if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
213
- res
214
- end.any? { |v| match_packs?(v, focus_pack) }
215
- end.map { |pack| pack.name }
215
+ focus_pack_name = packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }
216
+ result += focus_pack_name
217
+
218
+ dependents = options.show_dependencies ? dependents_on(packages, focus_pack_name) : []
219
+ dependencies = options.show_dependencies ? dependencies_of(packages, focus_pack_name) : []
220
+ todos_out = options.show_relationship_todos ? todos_out(packages, focus_pack_name, options) : []
221
+ todos_in = options.show_relationship_todos ? todos_in(packages, focus_pack_name, options) : []
222
+
223
+ case options.show_only_edges_to_focus_pack
224
+ when FocusPackEdgeDirection::All, FocusPackEdgeDirection::InOut then
225
+ result += dependents + dependencies + todos_out + todos_in
226
+ when FocusPackEdgeDirection::In then
227
+ result += dependents + todos_in
228
+ when FocusPackEdgeDirection::Out then
229
+ result += dependencies + todos_out
230
+ when FocusPackEdgeDirection::None then
231
+ # nothing to do
216
232
  end
217
- packages.map { |pack| pack.name }.select { |p| match_packs?(p, focus_pack) }.each do |p|
218
- if options.show_dependencies
219
- result += packages_by_name[p].dependencies
220
- end
221
- if options.show_todos && [FocusPackEdgeDirection::All, FocusPackEdgeDirection::Out, FocusPackEdgeDirection::InOut].include?(options.show_only_edges_to_focus_pack)
222
- result += (packages_by_name[p].violations || []).inject([]) do |res, todo|
223
- res << todo.to_package_name if options.only_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
224
- res
225
- end
226
- end
227
- end
228
- result = result.uniq
233
+
229
234
  parent_packs = result.inject([]) do |res, package_name|
230
235
  res << nested_packages[package_name]
231
236
  res
232
237
  end
238
+
233
239
  result = (result + parent_packs).uniq.compact
234
240
  end
235
241
 
@@ -318,4 +324,73 @@ module VisualizePacks
318
324
  def self.match_packs?(pack, packs_name_with_wildcards)
319
325
  !packs_name_with_wildcards || packs_name_with_wildcards.any? {|p| File.fnmatch(p, pack)}
320
326
  end
327
+
328
+ sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String]).returns(T::Array[String]) }
329
+ def self.dependencies_of(all_packages, focus_packs_names)
330
+ focus_packs = all_packages.select { focus_packs_names.include?(_1.name)}
331
+
332
+ focus_packs.inject([]) do |result, pack|
333
+ result += pack.dependencies
334
+ result
335
+ end.uniq
336
+ end
337
+
338
+ sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String]).returns(T::Array[String]) }
339
+ def self.dependents_on(all_packages, focus_packs_names)
340
+ all_packages.select { |pack| pack.dependencies.any? { focus_packs_names.include?(_1) }}.map &:name
341
+ end
342
+
343
+ sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String], options: Options).returns(T::Array[String]) }
344
+ def self.todos_in(all_packages, focus_packs_names, options)
345
+ all_packages.select do |p|
346
+ (p.violations || []).inject([]) do |res, todo|
347
+ res << todo.to_package_name if options.relationship_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
348
+ res
349
+ end.any? { |v| focus_packs_names.include?(v) }
350
+ end.map { |pack| pack.name }
351
+ end
352
+
353
+ sig { params(all_packages: T::Array[ParsePackwerk::Package], focus_packs_names: T::Array[String], options: Options).returns(T::Array[String]) }
354
+ def self.todos_out(all_packages, focus_packs_names, options)
355
+ all_packages.inject([]) do |result, p|
356
+ focus_packs_names.include?(p.name) && (p.violations || []).each do |todo|
357
+ result << todo.to_package_name if options.relationship_todo_types.include?(EdgeTodoTypes.deserialize(todo.type))
358
+ end
359
+ result
360
+ end
361
+ end
362
+
363
+ sig { params(protection: String, package_name: String, rubocop_config: T.any(NilClass, T::Boolean, T::Hash[String, T.untyped]), rubocop_todo: T.any(NilClass, T::Boolean, T::Hash[String, T.untyped])).returns(T.nilable(Integer)) }
364
+ def self.package_based_todos_for(protection, package_name, rubocop_config, rubocop_todo)
365
+ rubocop_config = {} if rubocop_config.is_a?(TrueClass) || rubocop_config.is_a?(FalseClass) || rubocop_config.is_a?(NilClass)
366
+ rubocop_todo = {} if rubocop_todo.is_a?(TrueClass) || rubocop_todo.is_a?(FalseClass) || rubocop_todo.is_a?(NilClass)
367
+
368
+ raise ArgumentError unless ['Packs/ClassMethodsAsPublicApis', 'Packs/DocumentedPublicApis', 'Packs/RootNamespaceIsPackName', 'Packs/TypedPublicApis'].include?(protection)
369
+ return nil unless (rubocop_config.dig(protection)&.dig('Enabled'))
370
+
371
+ (rubocop_todo.dig(protection)&.dig('Exclude') || []).inject(0) do |result, todo|
372
+ result += 1 if todo.start_with?("#{package_name}/")
373
+ result
374
+ end
375
+ end
376
+
377
+ sig { returns(T.untyped) }
378
+ def self.package_based_todos_text_maker
379
+ ->(package_name) {
380
+ [
381
+ 'Packs/ClassMethodsAsPublicApis',
382
+ 'Packs/DocumentedPublicApis',
383
+ 'Packs/RootNamespaceIsPackName',
384
+ 'Packs/TypedPublicApis'
385
+ ].map do |protection|
386
+ rubocop_config = File.exist?("#{package_name}/.rubocop.yml") ? YAML.load_file("#{package_name}/.rubocop.yml") : {}
387
+ rubocop_todo = File.exist?(".rubocop_todo.yml") ? YAML.load_file(".rubocop_todo.yml") : {}
388
+
389
+ todo_value = package_based_todos_for(protection, package_name, rubocop_config, rubocop_todo)
390
+ abbreviation = T.must(protection.split('/')[1]).chars[0]
391
+
392
+ "#{abbreviation}: #{todo_value}" if todo_value
393
+ end.compact.join(", ")
394
+ }
395
+ end
321
396
  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.19
4
+ version: 0.5.21
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-08 00:00:00.000000000 Z
11
+ date: 2023-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler