philiprehberger-dependency_graph 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2fc52e7f3c4c62be66dc6d19901b9d82c953fa8c5ea32f22b3cdada283c02fd5
4
- data.tar.gz: d334350bfb017e0172ec0145718904875c94fd1568e6114f3a113b8f1a124ae1
3
+ metadata.gz: d893dfbce550ce9ba6022f2f647b4b70cf2306a972b52b93ed096d7bc9384c9c
4
+ data.tar.gz: 57ef5e2ddea71da6b58e15adeb0e1d1d38443b455d99c17aa14a795cfdf7a421
5
5
  SHA512:
6
- metadata.gz: bf8205242047d62b202affb156583a13d89cdeb3db6d59fb7c4eeec1d9fb0b06ea23ffe5da925f37d97dafe0fb1b1cec628deba7bd24b33ac975e049add9758a
7
- data.tar.gz: 5116b0ec4782836b7b74b0f7135b842af9774b2108a9820212f0a73466840769f623688b5384084ca78597fd5299aa4ebb9b9a1dfdf3d4be5c7b5cd5b2c1a11d
6
+ metadata.gz: a3096da9a20d0a487aec73c8f5fccd26483e4ef6d19e237f767892f8ef3c5f1a8ad670d859b137b3c10c7afb4e28a86467e9485f0d081fe4bc1ca2be706eb0af
7
+ data.tar.gz: 657e23b74e72698eefd35515b25ce8f3a8d508107384460fac9d9d608589018d6b11705f49fd2547fe5d879fe5f06e68a2361d28cf2009adb5c560c6a572fef2
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2026-04-21
11
+
12
+ ### Added
13
+ - `Graph#to_dot` — Graphviz DOT export for visualization
14
+
15
+ ### Fixed
16
+ - `bug_report.yml` — require Ruby version; add Gem version input per guide
17
+
10
18
  ## [0.3.0] - 2026-04-09
11
19
 
12
20
  ### Added
data/README.md CHANGED
@@ -110,6 +110,23 @@ graph.add(:a).add(:b, depends_on: [:a]).add(:c, depends_on: [:b])
110
110
  graph.resolve # => [:a, :b, :c]
111
111
  ```
112
112
 
113
+ ### Graphviz Export
114
+
115
+ ```ruby
116
+ graph = Philiprehberger::DependencyGraph.new
117
+ graph.add(:a)
118
+ graph.add(:b, depends_on: [:a])
119
+ graph.add(:c, depends_on: [:b])
120
+
121
+ puts graph.to_dot
122
+ # digraph dependencies {
123
+ # "b" -> "a";
124
+ # "c" -> "b";
125
+ # }
126
+
127
+ graph.to_dot(name: 'MyDeps') # Customize the digraph name
128
+ ```
129
+
113
130
  ## API
114
131
 
115
132
  | Method | Description |
@@ -131,6 +148,7 @@ graph.resolve # => [:a, :b, :c]
131
148
  | `Graph#reverse` | Return a new graph with all edges flipped |
132
149
  | `Graph#all_dependents_of(item)` | All transitive dependents of a node |
133
150
  | `Graph#independent?(a, b)` | Whether two nodes are mutually unreachable |
151
+ | `#to_dot(name:)` | Graphviz DOT representation |
134
152
 
135
153
  ## Development
136
154
 
@@ -298,18 +298,33 @@ module Philiprehberger
298
298
  !all_dependencies_of(node_b).include?(node_a)
299
299
  end
300
300
 
301
- # Export the graph in Graphviz DOT format
301
+ # Export the graph in Graphviz DOT format.
302
302
  #
303
- # @param name [String] the digraph name
304
- # @return [String] DOT source
305
- def to_dot(name: 'G')
306
- lines = ["digraph #{name} {"]
307
- @nodes.each_key { |node| lines << " #{dot_quote(node)};" }
308
- @nodes.each do |node, deps|
309
- deps.each { |dep| lines << " #{dot_quote(node)} -> #{dot_quote(dep)};" }
303
+ # Nodes are emitted in alphabetical order (cast to string for sort key),
304
+ # and edges within a node are sorted alphabetically for deterministic
305
+ # output. Nodes that participate in at least one edge (incoming or
306
+ # outgoing) are emitted implicitly via the edge lines; truly isolated
307
+ # nodes are declared explicitly so they still appear in the rendered
308
+ # graph. Works on graphs containing cycles.
309
+ #
310
+ # @param name [String] the digraph name used in the `digraph` header
311
+ # @return [String] Graphviz DOT source, terminated by a newline
312
+ def to_dot(name: 'dependencies')
313
+ output = "digraph #{name} {\n"
314
+ depended_on = @nodes.each_with_object({}) do |(_node, deps), acc|
315
+ deps.each { |dep| acc[dep] = true }
316
+ end
317
+ sorted_nodes = @nodes.keys.sort_by(&:to_s)
318
+ sorted_nodes.each do |node|
319
+ deps = (@nodes[node] || []).sort_by(&:to_s)
320
+ if deps.empty?
321
+ output << " #{dot_quote(node)};\n" unless depended_on[node]
322
+ else
323
+ deps.each { |dep| output << " #{dot_quote(node)} -> #{dot_quote(dep)};\n" }
324
+ end
310
325
  end
311
- lines << '}'
312
- lines.join("\n")
326
+ output << "}\n"
327
+ output
313
328
  end
314
329
 
315
330
  # Calculate maximum dependency depth for a node (longest path from any root to this node)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module DependencyGraph
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
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.3.0
4
+ version: 0.4.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-04-09 00:00:00.000000000 Z
11
+ date: 2026-04-21 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