visualize_packs 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +35 -0
- data/lib/visualize_packs/graph_interface.rb +17 -0
- data/lib/visualize_packs/legend.png +0 -0
- data/lib/visualize_packs/node_interface.rb +29 -0
- data/lib/visualize_packs/package_graph.rb +54 -0
- data/lib/visualize_packs/package_node.rb +28 -0
- data/lib/visualize_packs/package_relationships.rb +170 -0
- data/lib/visualize_packs/team_graph.rb +56 -0
- data/lib/visualize_packs/team_node.rb +27 -0
- data/lib/visualize_packs.rb +29 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d1394594cbe0bf263a019593de9fd39ce2c191fb4c56d6be47fa0a52b2d20e6c
|
4
|
+
data.tar.gz: 7e81457c38334432d358d5e2b87e12330b1bc5ec90221a6d96626a71e9bf386f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f5f8114ea83f715bb3d8539788f69b433818c22428aee06d2c2e43e721059d64b3a01006eb5d0b1dcf7bed358178fbb7fa6f931a406cf566c6a04a5a7c41fd14
|
7
|
+
data.tar.gz: 98896ac828d1b2b3e6062b8f0acda397bfa7f1c219c6fadb5d7bbec5eb3697599c615334470207abe9579fe5463aaa11e1ac272a234fd1a7bc674b4c619c0c9d
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# visualize_packs
|
2
|
+
|
3
|
+
This gem helps visualize relationships between packs.
|
4
|
+
|
5
|
+
![Example of visualization](docs/example.png)
|
6
|
+
|
7
|
+
# CLI Usage
|
8
|
+
## bin/packs
|
9
|
+
For simpler use, add `bin/packs` via `use_packs` (https://github.com/rubyatscale/use_packs)
|
10
|
+
```
|
11
|
+
bin/packs visualize # all packs
|
12
|
+
bin/packs visualize packs/a packs/b # subset of packs
|
13
|
+
bin/packs # enter interactive mode to select what packs to visualize
|
14
|
+
```
|
15
|
+
|
16
|
+
# Ruby API Usage
|
17
|
+
## Building a package graph for a selection of packages
|
18
|
+
```ruby
|
19
|
+
# Select the packs you want to include
|
20
|
+
selected_packs = Packs.all
|
21
|
+
selected_packs = Packs.all.select{ |p| ['packs/my_pack_1', 'packs/my_pack_2'].include?(p.name) }
|
22
|
+
selected_packs = Packs.all.select{ |p| ['Team 1', 'Team 2'].include?(CodeOwnership.for_package(p)&.name) }
|
23
|
+
VisualizePacks.package_graph!(selected_packs)
|
24
|
+
```
|
25
|
+
|
26
|
+
## Building a team graph for specific teams
|
27
|
+
```ruby
|
28
|
+
# Select the teams you want to include
|
29
|
+
selected_teams = CodeTeams.all
|
30
|
+
selected_teams = CodeTeams.all.select{ |t| ['Team 1', 'Team 2'].include?(t.name) }
|
31
|
+
VisualizePacks.team_graph!(selected_teams)
|
32
|
+
```
|
33
|
+
|
34
|
+
# Want to change something or add a feature?
|
35
|
+
Submit a PR or post an issue!
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
# This stores graphviz-independent views of our package graph.
|
5
|
+
# It should be optimized for fast lookup (leveraging internal indexes, which are stable due to the immutability of the package nodes)
|
6
|
+
# A `TeamGraph` should be able to consume this and basically just create a reduced version
|
7
|
+
# Lastly, each one should implement a common interface, and graphviz should use that interface and take in either types of graph via the interface
|
8
|
+
module GraphInterface
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
interface!
|
12
|
+
|
13
|
+
sig { abstract.returns(T::Set[NodeInterface]) }
|
14
|
+
def nodes
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
Binary file
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
module NodeInterface
|
5
|
+
extend T::Sig
|
6
|
+
extend T::Helpers
|
7
|
+
interface!
|
8
|
+
|
9
|
+
sig { abstract.returns(String) }
|
10
|
+
def name
|
11
|
+
end
|
12
|
+
|
13
|
+
sig { abstract.returns(String) }
|
14
|
+
def group_name
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { abstract.returns(T::Hash[String, Integer]) }
|
18
|
+
def violations_by_node_name
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { abstract.returns(T::Array[String]) }
|
22
|
+
def dependencies
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { abstract.params(node_name: String).returns(T::Boolean) }
|
26
|
+
def depends_on?(node_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
class PackageGraph
|
5
|
+
extend T::Sig
|
6
|
+
include GraphInterface
|
7
|
+
|
8
|
+
sig { returns(T::Set[PackageNode]) }
|
9
|
+
attr_reader :package_nodes
|
10
|
+
|
11
|
+
sig { override.returns(T::Set[NodeInterface]) }
|
12
|
+
def nodes
|
13
|
+
package_nodes
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(package_nodes: T::Set[PackageNode]).void }
|
17
|
+
def initialize(package_nodes:)
|
18
|
+
@package_nodes = package_nodes
|
19
|
+
@index_by_name = T.let({}, T::Hash[String, T.nilable(PackageNode)])
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { returns(PackageGraph) }
|
23
|
+
def self.construct
|
24
|
+
package_nodes = Set.new
|
25
|
+
Packs.all.each do |p|
|
26
|
+
owner = CodeOwnership.for_package(p)
|
27
|
+
|
28
|
+
# Here we need to load the package violations and dependencies,
|
29
|
+
# so we need to use ParsePackwerk to parse that information.
|
30
|
+
package_info = ParsePackwerk.find(p.name)
|
31
|
+
next unless package_info # This should not happen unless packs/parse_packwerk change implementation
|
32
|
+
|
33
|
+
violations = package_info.violations
|
34
|
+
violations_by_package = violations.group_by(&:to_package_name).transform_values(&:count)
|
35
|
+
|
36
|
+
dependencies = package_info.dependencies
|
37
|
+
|
38
|
+
package_nodes << PackageNode.new(
|
39
|
+
name: p.name,
|
40
|
+
team_name: owner&.name || 'Unknown',
|
41
|
+
violations_by_package: violations_by_package,
|
42
|
+
dependencies: Set.new(dependencies)
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
PackageGraph.new(package_nodes: package_nodes)
|
47
|
+
end
|
48
|
+
|
49
|
+
sig { params(name: String).returns(T.nilable(PackageNode)) }
|
50
|
+
def package_by_name(name)
|
51
|
+
@index_by_name[name] ||= package_nodes.find { |node| node.name == name }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
class PackageNode < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
include NodeInterface
|
7
|
+
|
8
|
+
const :name, String
|
9
|
+
const :team_name, String
|
10
|
+
const :violations_by_package, T::Hash[String, Integer]
|
11
|
+
const :dependencies, T::Set[String]
|
12
|
+
|
13
|
+
sig { override.returns(T::Hash[String, Integer]) }
|
14
|
+
def violations_by_node_name
|
15
|
+
violations_by_package
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { override.returns(String) }
|
19
|
+
def group_name
|
20
|
+
team_name
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { override.params(node_name: String).returns(T::Boolean) }
|
24
|
+
def depends_on?(node_name)
|
25
|
+
dependencies.include?(node_name) || (violations_by_package[node_name] || 0) > 0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
class PackageRelationships
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
OUTPUT_FILENAME = T.let('packwerk.png'.freeze, String)
|
8
|
+
|
9
|
+
sig { params(teams: T::Array[CodeTeams::Team]).void }
|
10
|
+
def create_package_graph_for_teams!(teams)
|
11
|
+
packages = Packs.all.select do |package|
|
12
|
+
teams.map(&:name).include?(CodeOwnership.for_package(package)&.name)
|
13
|
+
end
|
14
|
+
|
15
|
+
create_package_graph!(packages)
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(teams: T::Array[CodeTeams::Team], show_all_teams: T::Boolean).void }
|
19
|
+
def create_team_graph!(teams, show_all_teams: false)
|
20
|
+
package_graph = PackageGraph.construct
|
21
|
+
team_graph = TeamGraph.from_package_graph(package_graph)
|
22
|
+
node_names = teams.map(&:name)
|
23
|
+
|
24
|
+
draw_graph!(team_graph, node_names, show_all_nodes: show_all_teams)
|
25
|
+
end
|
26
|
+
|
27
|
+
sig { params(packages: T::Array[Packs::Pack]).void }
|
28
|
+
def create_package_graph!(packages)
|
29
|
+
graph = PackageGraph.construct
|
30
|
+
node_names = packages.map(&:name)
|
31
|
+
draw_graph!(graph, node_names)
|
32
|
+
end
|
33
|
+
|
34
|
+
sig { params(packages: T::Array[Packs::Pack], show_all_nodes: T::Boolean).void }
|
35
|
+
def create_graph!(packages, show_all_nodes: false)
|
36
|
+
graph = PackageGraph.construct
|
37
|
+
node_names = packages.map(&:name)
|
38
|
+
draw_graph!(graph, node_names, show_all_nodes: show_all_nodes)
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(graph: GraphInterface, node_names: T::Array[String], show_all_nodes: T::Boolean).void }
|
42
|
+
def draw_graph!(graph, node_names, show_all_nodes: false)
|
43
|
+
# SFDP looks better than dot in some cases, but less good in other cases.
|
44
|
+
# If your visualization looks bad, change the layout to other_layout!
|
45
|
+
# https://graphviz.org/docs/layouts/
|
46
|
+
default_layout = :dot
|
47
|
+
# other_layout = :sfdp
|
48
|
+
graphviz_graph = GraphViz.new(
|
49
|
+
:G,
|
50
|
+
type: :digraph,
|
51
|
+
dpi: 100,
|
52
|
+
layout: default_layout,
|
53
|
+
label: "Visualization of #{node_names.count} packs, generated using `bin/packs`",
|
54
|
+
fontsize: 24,
|
55
|
+
labelloc: "t",
|
56
|
+
)
|
57
|
+
|
58
|
+
# Create graph nodes
|
59
|
+
graphviz_nodes = T.let({}, T::Hash[String, GraphViz::Node])
|
60
|
+
|
61
|
+
nodes_to_draw = graph.nodes.select{|n| node_names.include?(n.name) }
|
62
|
+
|
63
|
+
nodes_to_draw.each do |node|
|
64
|
+
graphviz_nodes[node.name] = add_node(node, graphviz_graph)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Draw all edges
|
68
|
+
nodes_to_draw.each do |node|
|
69
|
+
node.dependencies.each do |to_node|
|
70
|
+
next unless node_names.include?(to_node)
|
71
|
+
|
72
|
+
add_dependency(
|
73
|
+
graph: graphviz_graph,
|
74
|
+
node1: T.must(graphviz_nodes[node.name]),
|
75
|
+
node2: T.must(graphviz_nodes[to_node]),
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
node.violations_by_node_name.each do |to_node_name, violation_count|
|
80
|
+
next unless node_names.include?(to_node_name)
|
81
|
+
|
82
|
+
add_violation(
|
83
|
+
graph: graphviz_graph,
|
84
|
+
node1: T.must(graphviz_nodes[node.name]),
|
85
|
+
node2: T.must(graphviz_nodes[to_node_name]),
|
86
|
+
violation_count: violation_count
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
add_legend(graphviz_graph)
|
92
|
+
|
93
|
+
# Save graph to filesystem
|
94
|
+
puts "Outputting to: #{OUTPUT_FILENAME}"
|
95
|
+
graphviz_graph.output(png: OUTPUT_FILENAME)
|
96
|
+
puts 'Finished!'
|
97
|
+
end
|
98
|
+
|
99
|
+
sig { params(node: NodeInterface, graph: GraphViz).returns(GraphViz::Node) }
|
100
|
+
def add_node(node, graph)
|
101
|
+
node_options = {
|
102
|
+
fontsize: 26.0,
|
103
|
+
fontcolor: 'black',
|
104
|
+
fillcolor: 'white',
|
105
|
+
color: 'black',
|
106
|
+
height: 1.0,
|
107
|
+
style: 'filled, rounded',
|
108
|
+
shape: 'box',
|
109
|
+
}
|
110
|
+
|
111
|
+
graph.add_nodes(node.name, **node_options)
|
112
|
+
end
|
113
|
+
|
114
|
+
sig { params(graph: GraphViz).void }
|
115
|
+
def add_legend(graph)
|
116
|
+
legend = graph.add_graph("legend")
|
117
|
+
|
118
|
+
# This commented out code was used to generate an image that I edited by hand.
|
119
|
+
# I was unable to figure out how to:
|
120
|
+
# - put a box around the legend
|
121
|
+
# - layout the node pairs in vertical order
|
122
|
+
# - give it a title
|
123
|
+
# So I just generated this using graphviz and then pulled the image in.
|
124
|
+
# a_node = legend.add_nodes("packs/a")
|
125
|
+
# b_node = legend.add_nodes("packs/b")
|
126
|
+
# c_node = legend.add_nodes("packs/c")
|
127
|
+
# d_node = legend.add_nodes("packs/d")
|
128
|
+
# e_node = legend.add_nodes("packs/e")
|
129
|
+
# f_node = legend.add_nodes("packs/f")
|
130
|
+
|
131
|
+
# add_dependency(graph: legend, node1: a_node, node2: b_node, label: 'Dependency in package.yml')
|
132
|
+
# add_violation(graph: legend, node1: c_node, node2: d_node, violation_count: 1, label: 'Violations (few)')
|
133
|
+
# add_violation(graph: legend, node1: e_node, node2: f_node, violation_count: 30, label: 'Violations (many)')
|
134
|
+
|
135
|
+
image = legend.add_node("",
|
136
|
+
shape: "image",
|
137
|
+
image: Pathname.new(__dir__).join("./legend.png").to_s,
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
sig { params(graph: GraphViz, node1: GraphViz::Node, node2: GraphViz::Node, violation_count: Integer, label: T.nilable(String)).void }
|
142
|
+
def add_violation(graph:, node1:, node2:, violation_count:, label: nil)
|
143
|
+
max_edge_width = 10
|
144
|
+
|
145
|
+
edge_width = [
|
146
|
+
[(violation_count / 5).to_i, 1].max, # rubocop:disable Lint/NumberConversion
|
147
|
+
max_edge_width,
|
148
|
+
].min
|
149
|
+
|
150
|
+
opts = { color: 'red', style: 'dashed', penwidth: edge_width }
|
151
|
+
if label
|
152
|
+
opts.merge!(label: label)
|
153
|
+
end
|
154
|
+
|
155
|
+
graph.add_edges(node1, node2, opts)
|
156
|
+
end
|
157
|
+
|
158
|
+
sig { params(graph: GraphViz, node1: GraphViz::Node, node2: GraphViz::Node, label: T.nilable(String)).void }
|
159
|
+
def add_dependency(graph:, node1:, node2:, label: nil)
|
160
|
+
opts = { color: 'darkgreen' }
|
161
|
+
if label
|
162
|
+
opts.merge!(label: label)
|
163
|
+
end
|
164
|
+
|
165
|
+
graph.add_edges(node1, node2, opts)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private_constant :PackageRelationships
|
170
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
#
|
5
|
+
# A team graph reduces a PackageGraph by aggregating over packages owned by teams
|
6
|
+
#
|
7
|
+
class TeamGraph
|
8
|
+
extend T::Sig
|
9
|
+
include GraphInterface
|
10
|
+
|
11
|
+
sig { override.returns(T::Set[NodeInterface]) }
|
12
|
+
def nodes
|
13
|
+
@team_nodes
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(team_nodes: T::Set[TeamNode]).void }
|
17
|
+
def initialize(team_nodes:)
|
18
|
+
@team_nodes = team_nodes
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(package_graph: PackageGraph).returns(TeamGraph) }
|
22
|
+
def self.from_package_graph(package_graph)
|
23
|
+
team_nodes = T.let(Set.new, T::Set[TeamNode])
|
24
|
+
package_graph.package_nodes.group_by(&:team_name).each do |team, package_nodes_for_team|
|
25
|
+
violations_by_team = {}
|
26
|
+
package_nodes_for_team.map(&:violations_by_package).each do |new_violations_by_package|
|
27
|
+
new_violations_by_package.each do |pack_name, count|
|
28
|
+
# We first get the pack owner of the violated package
|
29
|
+
other_package = package_graph.package_by_name(pack_name)
|
30
|
+
next if other_package.nil?
|
31
|
+
other_team = other_package.team_name
|
32
|
+
violations_by_team[other_team] ||= 0
|
33
|
+
# Then we add the violations on that team together
|
34
|
+
# TODO: We may want to ignore this if team == other_team to avoid arrows pointing to self, but maybe not!
|
35
|
+
violations_by_team[other_team] += count
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
dependencies = Set.new
|
40
|
+
package_nodes_for_team.map(&:dependencies).reduce(Set.new, :+).each do |dependency|
|
41
|
+
other_pack = package_graph.package_by_name(dependency)
|
42
|
+
next if other_pack.nil?
|
43
|
+
dependencies << other_pack.team_name
|
44
|
+
end
|
45
|
+
|
46
|
+
team_nodes << TeamNode.new(
|
47
|
+
name: team,
|
48
|
+
violations_by_team: violations_by_team,
|
49
|
+
dependencies: dependencies
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
TeamGraph.new(team_nodes: team_nodes)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
module VisualizePacks
|
4
|
+
class TeamNode < T::Struct
|
5
|
+
extend T::Sig
|
6
|
+
include NodeInterface
|
7
|
+
|
8
|
+
const :name, String
|
9
|
+
const :violations_by_team, T::Hash[String, Integer]
|
10
|
+
const :dependencies, T::Set[String]
|
11
|
+
|
12
|
+
sig { override.returns(T::Hash[String, Integer]) }
|
13
|
+
def violations_by_node_name
|
14
|
+
violations_by_team
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.returns(String) }
|
18
|
+
def group_name
|
19
|
+
name
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { override.params(node_name: String).returns(T::Boolean) }
|
23
|
+
def depends_on?(node_name)
|
24
|
+
dependencies.include?(node_name) || (violations_by_node_name[node_name] || 0) > 0
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require 'packs-specification'
|
4
|
+
require 'parse_packwerk'
|
5
|
+
require 'code_ownership'
|
6
|
+
require 'graphviz'
|
7
|
+
require 'sorbet-runtime'
|
8
|
+
|
9
|
+
require 'visualize_packs/node_interface'
|
10
|
+
require 'visualize_packs/graph_interface'
|
11
|
+
require 'visualize_packs/team_node'
|
12
|
+
require 'visualize_packs/package_node'
|
13
|
+
require 'visualize_packs/team_graph'
|
14
|
+
require 'visualize_packs/package_graph'
|
15
|
+
require 'visualize_packs/package_relationships'
|
16
|
+
|
17
|
+
module VisualizePacks
|
18
|
+
extend T::Sig
|
19
|
+
|
20
|
+
sig { params(packages: T::Array[Packs::Pack]).void }
|
21
|
+
def self.package_graph!(packages)
|
22
|
+
PackageRelationships.new.create_package_graph!(packages)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(teams: T::Array[CodeTeams::Team]).void }
|
26
|
+
def self.team_graph!(teams)
|
27
|
+
PackageRelationships.new.create_team_graph!(teams)
|
28
|
+
end
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,197 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: visualize_packs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gusto Engineers
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-08-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sorbet-runtime
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: packs
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: parse_packwerk
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: code_ownership
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: ruby-graphviz
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.2.16
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.2.16
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '3.0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '3.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: sorbet
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: tapioca
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: A gem to visualize connections in a Ruby app that uses packs
|
154
|
+
email:
|
155
|
+
- dev@gusto.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files: []
|
159
|
+
files:
|
160
|
+
- README.md
|
161
|
+
- lib/visualize_packs.rb
|
162
|
+
- lib/visualize_packs/graph_interface.rb
|
163
|
+
- lib/visualize_packs/legend.png
|
164
|
+
- lib/visualize_packs/node_interface.rb
|
165
|
+
- lib/visualize_packs/package_graph.rb
|
166
|
+
- lib/visualize_packs/package_node.rb
|
167
|
+
- lib/visualize_packs/package_relationships.rb
|
168
|
+
- lib/visualize_packs/team_graph.rb
|
169
|
+
- lib/visualize_packs/team_node.rb
|
170
|
+
homepage: https://github.com/rubyatscale/visualize_packs
|
171
|
+
licenses:
|
172
|
+
- MIT
|
173
|
+
metadata:
|
174
|
+
homepage_uri: https://github.com/rubyatscale/visualize_packs
|
175
|
+
source_code_uri: https://github.com/rubyatscale/visualize_packs
|
176
|
+
changelog_uri: https://github.com/rubyatscale/visualize_packs/releases
|
177
|
+
allowed_push_host: https://rubygems.org
|
178
|
+
post_install_message:
|
179
|
+
rdoc_options: []
|
180
|
+
require_paths:
|
181
|
+
- lib
|
182
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
183
|
+
requirements:
|
184
|
+
- - ">="
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: '2.6'
|
187
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
|
+
requirements:
|
189
|
+
- - ">="
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0'
|
192
|
+
requirements: []
|
193
|
+
rubygems_version: 3.1.6
|
194
|
+
signing_key:
|
195
|
+
specification_version: 4
|
196
|
+
summary: A gem to visualize connections in a Ruby app that uses packs
|
197
|
+
test_files: []
|