philiprehberger-dependency_graph 0.3.0 → 0.5.0
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +23 -0
- data/lib/philiprehberger/dependency_graph/graph.rb +47 -10
- data/lib/philiprehberger/dependency_graph/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b91c3f882d45402ff3d62528249734aaa0ff45b1f56051cf3775a9b765538ba3
|
|
4
|
+
data.tar.gz: 7533cce9223239659948ad073b5c2f5e9b358c53b8c3d44d80760efb274f3335
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 561ce49d415c0c222b6813489a5f8e454690431a37447ee333ee83ebca3e8a90f662a9fcec2dc17faa3bbfb6a3bfb64aac4a50c9cb3e83e843bb5af9aee7d2cd
|
|
7
|
+
data.tar.gz: ebfb34c46ab091ff1f998100ac8141719c0bcc2e8b261db9e4d5ba375747f3fa7511ed4261eb52e0ed3d972b9d84df32c4b0f9a99c6ba9174334b72066102db6
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-05-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Graph#in_degree(item)` — count of direct dependents (how many items depend on this one)
|
|
14
|
+
- `Graph#out_degree(item)` — count of direct dependencies for an item
|
|
15
|
+
|
|
16
|
+
## [0.4.0] - 2026-04-21
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `Graph#to_dot` — Graphviz DOT export for visualization
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- `bug_report.yml` — require Ruby version; add Gem version input per guide
|
|
23
|
+
|
|
10
24
|
## [0.3.0] - 2026-04-09
|
|
11
25
|
|
|
12
26
|
### Added
|
data/README.md
CHANGED
|
@@ -77,6 +77,9 @@ graph.add(:d, depends_on: [:b, :c])
|
|
|
77
77
|
graph.dependencies_of(:d) # => [:b, :c]
|
|
78
78
|
graph.all_dependencies_of(:d) # => [:b, :c, :a]
|
|
79
79
|
graph.dependents_of(:b) # => [:c, :d]
|
|
80
|
+
|
|
81
|
+
graph.out_degree(:d) # => 2
|
|
82
|
+
graph.in_degree(:b) # => 2
|
|
80
83
|
```
|
|
81
84
|
|
|
82
85
|
### Path Finding
|
|
@@ -110,6 +113,23 @@ graph.add(:a).add(:b, depends_on: [:a]).add(:c, depends_on: [:b])
|
|
|
110
113
|
graph.resolve # => [:a, :b, :c]
|
|
111
114
|
```
|
|
112
115
|
|
|
116
|
+
### Graphviz Export
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
graph = Philiprehberger::DependencyGraph.new
|
|
120
|
+
graph.add(:a)
|
|
121
|
+
graph.add(:b, depends_on: [:a])
|
|
122
|
+
graph.add(:c, depends_on: [:b])
|
|
123
|
+
|
|
124
|
+
puts graph.to_dot
|
|
125
|
+
# digraph dependencies {
|
|
126
|
+
# "b" -> "a";
|
|
127
|
+
# "c" -> "b";
|
|
128
|
+
# }
|
|
129
|
+
|
|
130
|
+
graph.to_dot(name: 'MyDeps') # Customize the digraph name
|
|
131
|
+
```
|
|
132
|
+
|
|
113
133
|
## API
|
|
114
134
|
|
|
115
135
|
| Method | Description |
|
|
@@ -123,6 +143,8 @@ graph.resolve # => [:a, :b, :c]
|
|
|
123
143
|
| `Graph#dependencies_of(item)` | Direct dependencies of an item |
|
|
124
144
|
| `Graph#all_dependencies_of(item)` | All transitive dependencies |
|
|
125
145
|
| `Graph#dependents_of(item)` | Items that directly depend on an item |
|
|
146
|
+
| `Graph#in_degree(item)` | Count of direct dependents for an item |
|
|
147
|
+
| `Graph#out_degree(item)` | Count of direct dependencies for an item |
|
|
126
148
|
| `Graph#path(from, to)` | Shortest dependency path (BFS), or nil |
|
|
127
149
|
| `Graph#subgraph(*items)` | Extract a new graph with specified nodes |
|
|
128
150
|
| `Graph#roots` | Nodes with no dependencies |
|
|
@@ -131,6 +153,7 @@ graph.resolve # => [:a, :b, :c]
|
|
|
131
153
|
| `Graph#reverse` | Return a new graph with all edges flipped |
|
|
132
154
|
| `Graph#all_dependents_of(item)` | All transitive dependents of a node |
|
|
133
155
|
| `Graph#independent?(a, b)` | Whether two nodes are mutually unreachable |
|
|
156
|
+
| `#to_dot(name:)` | Graphviz DOT representation |
|
|
134
157
|
|
|
135
158
|
## Development
|
|
136
159
|
|
|
@@ -146,6 +146,28 @@ module Philiprehberger
|
|
|
146
146
|
end
|
|
147
147
|
end
|
|
148
148
|
|
|
149
|
+
# Number of direct dependencies for an item.
|
|
150
|
+
#
|
|
151
|
+
# Returns 0 for unknown items, matching the defensive behavior of
|
|
152
|
+
# {#dependencies_of}.
|
|
153
|
+
#
|
|
154
|
+
# @param item [Object] the item to query
|
|
155
|
+
# @return [Integer] count of direct dependencies
|
|
156
|
+
def out_degree(item)
|
|
157
|
+
(@nodes[item] || []).size
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Number of direct dependents for an item (how many items depend on it).
|
|
161
|
+
#
|
|
162
|
+
# Returns 0 for unknown items, matching the defensive behavior of
|
|
163
|
+
# {#dependents_of}.
|
|
164
|
+
#
|
|
165
|
+
# @param item [Object] the item to query
|
|
166
|
+
# @return [Integer] count of direct dependents
|
|
167
|
+
def in_degree(item)
|
|
168
|
+
@nodes.count { |_node, deps| deps.include?(item) }
|
|
169
|
+
end
|
|
170
|
+
|
|
149
171
|
# Find shortest dependency path between two nodes using BFS
|
|
150
172
|
#
|
|
151
173
|
# @param from [Object] the starting node
|
|
@@ -298,18 +320,33 @@ module Philiprehberger
|
|
|
298
320
|
!all_dependencies_of(node_b).include?(node_a)
|
|
299
321
|
end
|
|
300
322
|
|
|
301
|
-
# Export the graph in Graphviz DOT format
|
|
323
|
+
# Export the graph in Graphviz DOT format.
|
|
302
324
|
#
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
325
|
+
# Nodes are emitted in alphabetical order (cast to string for sort key),
|
|
326
|
+
# and edges within a node are sorted alphabetically for deterministic
|
|
327
|
+
# output. Nodes that participate in at least one edge (incoming or
|
|
328
|
+
# outgoing) are emitted implicitly via the edge lines; truly isolated
|
|
329
|
+
# nodes are declared explicitly so they still appear in the rendered
|
|
330
|
+
# graph. Works on graphs containing cycles.
|
|
331
|
+
#
|
|
332
|
+
# @param name [String] the digraph name used in the `digraph` header
|
|
333
|
+
# @return [String] Graphviz DOT source, terminated by a newline
|
|
334
|
+
def to_dot(name: 'dependencies')
|
|
335
|
+
output = "digraph #{name} {\n"
|
|
336
|
+
depended_on = @nodes.each_with_object({}) do |(_node, deps), acc|
|
|
337
|
+
deps.each { |dep| acc[dep] = true }
|
|
338
|
+
end
|
|
339
|
+
sorted_nodes = @nodes.keys.sort_by(&:to_s)
|
|
340
|
+
sorted_nodes.each do |node|
|
|
341
|
+
deps = (@nodes[node] || []).sort_by(&:to_s)
|
|
342
|
+
if deps.empty?
|
|
343
|
+
output << " #{dot_quote(node)};\n" unless depended_on[node]
|
|
344
|
+
else
|
|
345
|
+
deps.each { |dep| output << " #{dot_quote(node)} -> #{dot_quote(dep)};\n" }
|
|
346
|
+
end
|
|
310
347
|
end
|
|
311
|
-
|
|
312
|
-
|
|
348
|
+
output << "}\n"
|
|
349
|
+
output
|
|
313
350
|
end
|
|
314
351
|
|
|
315
352
|
# Calculate maximum dependency depth for a node (longest path from any root to this node)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-dependency_graph
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Build and resolve dependency graphs using topological sort, detect cycles,
|
|
14
14
|
generate parallel execution batches, query dependencies and dependents, find shortest
|