build-dependency 1.5.1 → 1.6.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/build/dependency/chain.rb +13 -22
  4. data/lib/build/dependency/partial_chain.rb +18 -21
  5. data/lib/build/dependency/provider.rb +28 -27
  6. data/lib/build/dependency/resolver.rb +12 -21
  7. data/lib/build/dependency/set.rb +27 -20
  8. data/lib/build/dependency/version.rb +7 -20
  9. data/lib/build/dependency/visualization.rb +58 -122
  10. data/lib/build/dependency.rb +9 -24
  11. data/license.md +21 -0
  12. data/readme.md +64 -0
  13. data/releases.md +5 -0
  14. data/test/build/dependency/chain.rb +223 -0
  15. data/test/build/dependency/partial_chain.rb +121 -0
  16. data/test/build/dependency/provider.rb +189 -0
  17. data/test/build/dependency/set.rb +122 -0
  18. data/test/build/dependency/visualization.rb +21 -0
  19. data/test/build/dependency/wildcard.rb +38 -0
  20. data.tar.gz.sig +1 -0
  21. metadata +49 -113
  22. metadata.gz.sig +0 -0
  23. data/.gitignore +0 -23
  24. data/.rspec +0 -4
  25. data/.travis.yml +0 -17
  26. data/Gemfile +0 -14
  27. data/Guardfile +0 -9
  28. data/README.md +0 -214
  29. data/Rakefile +0 -8
  30. data/build-dependency.gemspec +0 -24
  31. data/build_system_rules_and_algorithms.pdf +0 -0
  32. data/full.svg +0 -123
  33. data/partial.svg +0 -93
  34. data/spec/build/dependency/chain_spec.rb +0 -218
  35. data/spec/build/dependency/package.rb +0 -89
  36. data/spec/build/dependency/partial_chain_spec.rb +0 -119
  37. data/spec/build/dependency/provider_spec.rb +0 -123
  38. data/spec/build/dependency/set_spec.rb +0 -87
  39. data/spec/build/dependency/visualization_spec.rb +0 -33
  40. data/spec/build/dependency/wildcard_spec.rb +0 -45
  41. data/spec/spec_helper.rb +0 -12
  42. data/visualization.svg +0 -147
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f3c41145e9e5f1251cf1cfb7107861ecf2c40ef3feca4331d24d44076f2f63db
4
- data.tar.gz: e594691242223f8098dbe3963d0dbc66e483e411b8cc1ec6d91c580a9010e90c
3
+ metadata.gz: 22b38e2f183e26fb4db1cfc6e53c91c2cc3fd5efd026b3eb8dd62f225d309bdf
4
+ data.tar.gz: 2594cc69f37835c318a69e058de3b5875be3485f8edd47ba86713c8e316e4ce0
5
5
  SHA512:
6
- metadata.gz: dbe10b950321cd725ae6324071042d319c7b212780db0c93e7cc48da4fa5514dc116c88748718bb2691bee9b005ca6f688f11e9364fa80b30e04d02287c5e110
7
- data.tar.gz: f7db4f8757f8b8f98aff76a6d9f7f43ec765d84f8965b178763c7628c403d34dae8c27713f47d083cbb3707322f039d113aff504f8576e95e96e178a96be7715
6
+ metadata.gz: ca056c3755bde6ba73c490ea7af08e600c51068220d71fe84ace34d430a4ff996b922c25942cc3eb27809f1fe366c78d9ca2e8685d10407546674ab4ac7e7f86
7
+ data.tar.gz: 851a466463008e0e27abe6b576d3e0f9cf0bbe2c33ad5f46a6eb186f73c2cb0131f46b2063dd0fab4725d8f49208538946be712004d419c2f5e7fbbd440a7aec
checksums.yaml.gz.sig ADDED
Binary file
@@ -1,29 +1,15 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require_relative 'resolver'
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2026, by Samuel Williams.
22
5
 
23
- require 'set'
6
+ require_relative "resolver"
7
+
8
+ require "set"
24
9
 
25
10
  module Build
26
11
  module Dependency
12
+ # A chain of dependencies that are resolved from a set of providers.
27
13
  class Chain < Resolver
28
14
  # An `UnresolvedDependencyError` will be thrown if there are any unresolved dependencies.
29
15
  def self.expand(*args)
@@ -38,6 +24,10 @@ module Build
38
24
  return chain
39
25
  end
40
26
 
27
+ # Initialize a dependency chain.
28
+ # @parameter dependencies [Array<String, Depends>] The dependencies to resolve.
29
+ # @parameter providers [Array<Provider>] The providers to use for resolution.
30
+ # @parameter selection [Array<String>] Explicitly selected dependencies for resolving ambiguity.
41
31
  def initialize(dependencies, providers, selection = [])
42
32
  super()
43
33
 
@@ -57,8 +47,9 @@ module Build
57
47
  # @attr [Array] The available providers which will be used to satisfy he required dependencies.
58
48
  attr :providers
59
49
 
50
+ # Freeze the chain and all its dependencies.
60
51
  def freeze
61
- return unless frozen?
52
+ return self if frozen?
62
53
 
63
54
  @selection.freeze
64
55
  @dependencies.freeze
@@ -1,33 +1,22 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require_relative 'chain'
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
+
6
+ require_relative "chain"
22
7
 
23
8
  module Build
24
9
  module Dependency
25
10
  class Chain
11
+ # Create a partial chain for a specific provider.
12
+ # @parameter provider [Provider] The provider to create a partial chain for.
13
+ # @returns [PartialChain] A partial chain containing only the provider's dependencies.
26
14
  def partial(provider)
27
15
  PartialChain.expand(self, provider.dependencies)
28
16
  end
29
17
  end
30
18
 
19
+ # A partial dependency chain that resolves only a subset of dependencies.
31
20
  class PartialChain < Resolver
32
21
  # An `UnresolvedDependencyError` will be thrown if there are any unresolved dependencies.
33
22
  def self.expand(*args)
@@ -38,6 +27,9 @@ module Build
38
27
  return chain
39
28
  end
40
29
 
30
+ # Initialize a partial chain.
31
+ # @parameter chain [Chain] The parent chain to use for resolution.
32
+ # @parameter dependencies [Array<Depends>] The dependencies to resolve.
41
33
  def initialize(chain, dependencies)
42
34
  super()
43
35
 
@@ -48,6 +40,8 @@ module Build
48
40
  expand_top
49
41
  end
50
42
 
43
+ # Get the selection from the parent chain.
44
+ # @returns [Set<String>] The explicitly selected dependencies.
51
45
  def selection
52
46
  @chain.selection
53
47
  end
@@ -55,12 +49,15 @@ module Build
55
49
  # @attr [Array<Depends>] The list of dependencies that needs to be satisfied.
56
50
  attr :dependencies
57
51
 
52
+ # Get the providers from the parent chain.
53
+ # @returns [Array<Provider>] The available providers.
58
54
  def providers
59
55
  @chain.providers
60
56
  end
61
57
 
58
+ # Freeze the partial chain.
62
59
  def freeze
63
- return unless frozen?
60
+ return self if frozen?
64
61
 
65
62
  @chain.freeze
66
63
  @dependencies.freeze
@@ -1,27 +1,14 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require_relative 'set'
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
+
6
+ require_relative "set"
22
7
 
23
8
  module Build
24
9
  module Dependency
10
+ # Include the Provider module when Build::Dependency is included in a class.
11
+ # @parameter klass [Class] The class that is including Build::Dependency.
25
12
  def self.included(klass)
26
13
  klass.include(Provider)
27
14
  end
@@ -79,7 +66,7 @@ module Build
79
66
  end
80
67
 
81
68
  def wildcard?
82
- self.name.is_a?(String) and self.name.include?('*')
69
+ self.name.is_a?(String) and self.name.include?("*")
83
70
  end
84
71
 
85
72
  def match?(name)
@@ -119,9 +106,11 @@ module Build
119
106
  end
120
107
  end
121
108
 
109
+ # A provider that can satisfy dependencies by providing named provisions.
122
110
  module Provider
111
+ # Freeze the provider and all its provisions and dependencies.
123
112
  def freeze
124
- return unless frozen?
113
+ return self if frozen?
125
114
 
126
115
  provisions.freeze
127
116
  dependencies.freeze
@@ -139,16 +128,19 @@ module Build
139
128
  @priority ||= 0
140
129
  end
141
130
 
142
- # @return Hash<String, Provision> a table of named provisions.
131
+ # @returns Hash<String, Provision> a table of named provisions.
143
132
  def provisions
144
133
  @provisions ||= {}
145
134
  end
146
135
 
147
- # @return [IdentitySet<Dependency>]
136
+ # @returns [IdentitySet<Dependency>]
148
137
  def dependencies
149
138
  @dependencies ||= Set.new
150
139
  end
151
140
 
141
+ # Filter provisions that match a given dependency.
142
+ # @parameter dependency [Depends] The dependency to match against.
143
+ # @returns [Hash<String, Provision>] Provisions that match the dependency.
152
144
  def filter(dependency)
153
145
  provisions.select{|name, provision| dependency.match?(name)}
154
146
  end
@@ -158,17 +150,23 @@ module Build
158
150
  provisions.key?(dependency.name)
159
151
  end
160
152
 
153
+ # Get the provision for a given dependency.
154
+ # @parameter dependency [Depends] The dependency to get the provision for.
155
+ # @returns [Provision, nil] The provision, or nil if not found.
161
156
  def provision_for(dependency)
162
157
  return provisions[dependency.name]
163
158
  end
164
159
 
160
+ # Get a resolution for a given dependency.
161
+ # @parameter dependency [Depends] The dependency to get the resolution for.
162
+ # @returns [Resolution] The resolution combining the provision and dependency.
165
163
  def resolution_for(dependency)
166
164
  return Resolution.new(provision_for(dependency), dependency)
167
165
  end
168
166
 
169
167
  # Add one or more provisions to the provider.
170
- # @param [Array<String>] names the named provisions to add.
171
- # @param [Hash<Symbol, Array>] aliases the aliases to add.
168
+ # @parameter names [Array<String>] the named provisions to add.
169
+ # @parameter aliases [Hash<Symbol, Array>] the aliases to add.
172
170
  # @example A named provision.
173
171
  # target.provides "Compiler/clang" do
174
172
  # cxx "clang"
@@ -186,7 +184,7 @@ module Build
186
184
  end
187
185
 
188
186
  # Add one or more dependencies to the provider.
189
- # @param [Array<String>] names the dependency names to add.
187
+ # @parameter names [Array<String>] the dependency names to add.
190
188
  # @example A named dependency.
191
189
  # target.depends "Compiler/clang"
192
190
  # @example A symbolic dependency.
@@ -197,6 +195,9 @@ module Build
197
195
  end
198
196
  end
199
197
 
198
+ # Check if this provider depends on a given name.
199
+ # @parameter name [String] The name to check.
200
+ # @returns [Boolean] True if this provider depends on the given name.
200
201
  def depends?(name)
201
202
  dependencies.include?(name)
202
203
  end
@@ -1,28 +1,16 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require 'set'
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
+
6
+ require "set"
22
7
 
23
8
  module Build
24
9
  module Dependency
10
+ # An error raised when dependencies cannot be resolved.
25
11
  class UnresolvedDependencyError < StandardError
12
+ # Initialize the error with the unresolved chain.
13
+ # @parameter chain [Chain] The chain with unresolved dependencies.
26
14
  def initialize(chain)
27
15
  super "Unresolved dependency chain: #{chain.unresolved.inspect}!"
28
16
 
@@ -34,7 +22,9 @@ module Build
34
22
 
35
23
  TOP = Depends.new("<top>").freeze
36
24
 
25
+ # Base class for resolving dependencies.
37
26
  class Resolver
27
+ # Initialize an empty resolver.
38
28
  def initialize
39
29
  @resolved = {}
40
30
  @ordered = []
@@ -49,8 +39,9 @@ module Build
49
39
  attr :unresolved
50
40
  attr :conflicts
51
41
 
42
+ # Freeze the resolver and all its internal state.
52
43
  def freeze
53
- return unless frozen?
44
+ return self if frozen?
54
45
 
55
46
  @resolved.freeze
56
47
  @ordered.freeze
@@ -1,24 +1,9 @@
1
- # Copyright, 2015, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require 'forwardable'
3
+ # Released under the MIT License.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
+
6
+ require "forwardable"
22
7
 
23
8
  module Build
24
9
  module Dependency
@@ -26,6 +11,8 @@ module Build
26
11
  class Set
27
12
  include Enumerable
28
13
 
14
+ # Initialize a new set with optional initial contents.
15
+ # @parameter contents [Array] Initial objects to add to the set.
29
16
  def initialize(contents = [])
30
17
  @table = {}
31
18
 
@@ -40,6 +27,7 @@ module Build
40
27
 
41
28
  def_delegators :@table, :size, :empty?, :clear, :count, :[], :to_s, :inspect
42
29
 
30
+ # Freeze the set.
43
31
  def freeze
44
32
  return self if frozen?
45
33
 
@@ -48,14 +36,22 @@ module Build
48
36
  super
49
37
  end
50
38
 
39
+ # Initialize a duplicate of another set.
40
+ # @parameter other [Set] The set to duplicate.
51
41
  def initialize_dup(other)
52
42
  @table = other.table.dup
53
43
  end
54
44
 
45
+ # Get the identity of an object for use as a hash key.
46
+ # @parameter object [Object] The object to get the identity for.
47
+ # @returns [String] The object's name.
55
48
  def identity(object)
56
49
  object.name
57
50
  end
58
51
 
52
+ # Add an object to the set.
53
+ # @parameter object [Object] The object to add.
54
+ # @raises [KeyError] If an object with the same identity already exists.
59
55
  def add(object)
60
56
  if include?(object)
61
57
  raise KeyError, "Object #{identity(object)} already exists!"
@@ -66,18 +62,29 @@ module Build
66
62
 
67
63
  alias << add
68
64
 
65
+ # Delete an object from the set.
66
+ # @parameter object [Object] The object to delete.
67
+ # @returns [Object, nil] The deleted object, or nil if not found.
69
68
  def delete(object)
70
69
  @table.delete(identity(object))
71
70
  end
72
71
 
72
+ # Check if the set includes an object.
73
+ # @parameter object [Object] The object to check for.
74
+ # @returns [Boolean] True if the set includes the object.
73
75
  def include?(object)
74
76
  @table.include?(identity(object))
75
77
  end
76
78
 
79
+ # Iterate over each object in the set.
80
+ # @yields [Object] Each object in the set.
77
81
  def each(&block)
78
82
  @table.each_value(&block)
79
83
  end
80
84
 
85
+ # Get a subset of objects by their names.
86
+ # @parameter names [Array<String>] The names of objects to retrieve.
87
+ # @returns [Array<Object>] The objects with the given names.
81
88
  def slice(names)
82
89
  names.collect{|name| @table[name]}
83
90
  end
@@ -1,25 +1,12 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
+
6
+ # @namespace
21
7
  module Build
8
+ # @namespace
22
9
  module Dependency
23
- VERSION = "1.5.1"
10
+ VERSION = "1.6.0"
24
11
  end
25
12
  end
@@ -1,158 +1,94 @@
1
- # Copyright, 20127, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining a copy
4
- # of this software and associated documentation files (the "Software"), to deal
5
- # in the Software without restriction, including without limitation the rights
6
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
- # copies of the Software, and to permit persons to whom the Software is
8
- # furnished to do so, subject to the following conditions:
9
- #
10
- # The above copyright notice and this permission notice shall be included in
11
- # all copies or substantial portions of the Software.
12
- #
13
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
- # THE SOFTWARE.
1
+ # frozen_string_literal: true
20
2
 
21
- require 'graphviz'
3
+ # Released under the MIT License.
4
+ # Copyright, 2017-2026, by Samuel Williams.
22
5
 
23
6
  module Build
24
7
  module Dependency
8
+ # Generates Mermaid flowchart visualizations of dependency chains.
25
9
  class Visualization
26
- def initialize
27
- @base_attributes = {
28
- :shape => 'box',
29
- :style => 'filled',
30
- :fillcolor => 'white',
31
- :fontname => 'Monaco',
32
- }
33
-
34
- @provision_attributes = @base_attributes.dup
35
-
36
- @alias_attributes = @base_attributes.merge(
37
- :fillcolor => 'lightgrey',
38
- )
39
-
40
- @dependency_attributes = @base_attributes.merge(
41
- :fillcolor => 'orange',
42
- )
43
-
44
- @selection_attributes = {
45
- :fillcolor => 'lightbrown',
46
- }
47
-
48
- @private_edge_attributes = {
49
- :arrowhead => 'empty',
50
- :color => '#0000005f'
51
- }
52
-
53
- @provider_attributes = {
54
- :fillcolor => 'lightblue',
55
- }
56
-
57
- @provider_edge_attributes = {
58
- :arrowhead => 'none',
59
- }
10
+ # Convert a name to a valid Mermaid node ID.
11
+ # @parameter name [String] The name to sanitize.
12
+ # @returns [String] A sanitized identifier safe for use in Mermaid diagrams.
13
+ def sanitize_id(name)
14
+ # Convert name to a valid Mermaid node ID
15
+ name.to_s.gsub(/[^a-zA-Z0-9_]/, "_")
60
16
  end
61
17
 
62
- attr :base_attributes
63
- attr :provision_attributes
64
-
65
- attr :provider_attributes
66
- attr :provider_edge_attributes
67
-
68
- attr :alias_attributes
69
-
70
- attr :dependency_attributes
71
- attr :selection_attributes
72
- attr :private_edge_attributes
73
-
18
+ # Generate a Mermaid flowchart diagram for a dependency chain.
19
+ # @parameter chain [Chain] The dependency chain to visualize.
20
+ # @returns [String] A Mermaid flowchart diagram in text format.
74
21
  def generate(chain)
75
- graph = Graphviz::Graph.new
76
- graph.attributes[:ratio] = :auto
22
+ lines = ["flowchart LR"]
77
23
 
78
- dependencies = dependencies_by_name(chain.dependencies)
24
+ # Track nodes and their styles
25
+ nodes = {}
26
+ providers = ::Set.new
27
+ provisions_in_chain = ::Set.new
28
+
29
+ # Collect all provisions in the chain
30
+ chain.provisions.each do |provision|
31
+ provisions_in_chain.add(provision.name.to_s)
32
+ end
79
33
 
34
+ # Build the graph
80
35
  chain.ordered.each do |resolution|
81
36
  provider = resolution.provider
82
- name = provider.name
83
-
84
- # Provider is the dependency that provides the dependency referred to by name.
85
- node = graph.add_node(name.to_s, @base_attributes.dup)
37
+ name = provider.name.to_s
38
+ node_id = sanitize_id(name)
86
39
 
87
- if dependencies.include?(resolution.dependency.name)
88
- node.attributes.update(@dependency_attributes)
89
- elsif chain.selection.include?(provider.name)
90
- node.attributes.update(@selection_attributes)
91
- end
40
+ # Track this node
41
+ nodes[name] = node_id
92
42
 
93
43
  # A provision has dependencies...
94
44
  provider.dependencies.each do |dependency|
95
- if dependency_node = graph.nodes[dependency.name.to_s]
96
- edge = node.connect(dependency_node)
97
-
98
- if dependency.private?
99
- edge.attributes.update(@private_edge_attributes)
100
- end
45
+ dep_name = dependency.name.to_s
46
+ dep_id = sanitize_id(dep_name)
47
+
48
+ nodes[dep_name] ||= dep_id
49
+
50
+ # Create edge from provider to dependency
51
+ if dependency.private?
52
+ lines << " #{node_id}[#{name}] -.-> #{dep_id}[#{dep_name}]"
53
+ else
54
+ lines << " #{node_id}[#{name}] --> #{dep_id}[#{dep_name}]"
101
55
  end
102
56
  end
103
57
 
104
58
  # A provision provides other provisions...
105
59
  provider.provisions.each do |provision_name, provision|
106
- next if name == provision_name
60
+ next if name == provision_name.to_s
107
61
 
108
- provides_node = graph.nodes[provision_name.to_s] || graph.add_node(provision_name.to_s, @provision_attributes)
62
+ provision_str = provision_name.to_s
63
+ provision_id = sanitize_id(provision_str)
109
64
 
110
- if provision.alias?
111
- provides_node.attributes = @alias_attributes
112
- end
65
+ nodes[provision_str] ||= provision_id
113
66
 
114
- node.attributes.update(@provider_attributes)
67
+ # Mark this node as a provider
68
+ providers.add(name)
115
69
 
116
- unless provides_node.connected?(node)
117
- edge = provides_node.connect(node)
118
-
119
- edge.attributes.update(@provider_edge_attributes)
120
- end
70
+ # Create edge from provision to provider (undirected)
71
+ lines << " #{provision_id}[#{provision_str}] --- #{node_id}[#{name}]"
121
72
  end
122
73
  end
123
74
 
124
- chain.provisions.each do |provision|
125
- node = graph.nodes[provision.name.to_s]
126
-
127
- node.attributes.update(penwidth: 2.0)
75
+ # Add styling
76
+ lines << ""
77
+ lines << " %% Styles"
78
+
79
+ # Style providers with light blue
80
+ providers.each do |name|
81
+ lines << " style #{sanitize_id(name)} fill:#add8e6"
128
82
  end
129
83
 
130
- # Put all dependencies at the same level so as to not make the graph too confusingraph.
131
- done = Set.new
132
- chain.ordered.each do |resolution|
133
- provider = resolution.provider
134
- name = "subgraph-#{provider.name}"
135
-
136
- subgraph = graph.nodes[name] || graph.add_subgraph(name, :rank => :same)
137
-
138
- provider.dependencies.each do |dependency|
139
- next if done.include? dependency
140
-
141
- done << dependency
142
-
143
- if dependency_node = graph.nodes[dependency.name.to_s]
144
- subgraph.add_node(dependency_node.name)
145
- end
84
+ # Highlight provisions in the chain with a thicker border
85
+ provisions_in_chain.each do |name|
86
+ if node_id = nodes[name]
87
+ lines << " style #{node_id} stroke-width:3px"
146
88
  end
147
89
  end
148
90
 
149
- return graph
150
- end
151
-
152
- private
153
-
154
- def dependencies_by_name(dependencies)
155
- dependencies.map{|depends| [depends.name, depends]}.to_h
91
+ return lines.join("\n")
156
92
  end
157
93
  end
158
94
  end