solve 0.6.1 → 0.7.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
  SHA1:
3
- metadata.gz: b23c8ac10998066c9ecea0480dfa83bc74266a20
4
- data.tar.gz: ec933cf811cffa4abcb405ab6d9092e8a34b980b
3
+ metadata.gz: 868022031f083b30e7c32a6d424896fb2fb08367
4
+ data.tar.gz: 5d50e154ab203dbddd6888616295fc683f26672c
5
5
  SHA512:
6
- metadata.gz: 4b19ad0f653833739926bd31dde7d774ff134728855ec43be35eeec4945721e798d0e75e04c28071baaacaad191f61115ffcb22623bf2140fdbba770a7e5eaec
7
- data.tar.gz: a9bf1c0f0160e9a2d2dbe0489d25a99a7bed8df34c6d9fce67958731cb734d6427b46101f3668e84b91954edcb4ff9a6881de5252715b9b791a8d3b0b94744cd
6
+ metadata.gz: 56abef15a6d5da9a0e31e1ed88cc46ce9acb7aab8526ec7aaf0c615e8f09cfa1fbb0b361349282640813328fecc954f9eb75fea5d73764176964b771c1a467ec
7
+ data.tar.gz: 0a079122af8824f23e23c69649092b4f32c7cbb44f72e4fc9d754350c45a48580280e758bb8e94269d6edba99ed026c379399d1edd38fe49afa53b70acfbc84c
data/README.md CHANGED
@@ -31,6 +31,11 @@ And now solve the graph with some demands
31
31
 
32
32
  Solve.it!(graph, ['nginx', '>= 0.100.0'])
33
33
 
34
+ Or, if you want a topologically sorted solution
35
+ NOTE: This will raise Solve::Errors::UnsortableSolutionError if the solution contains a cycle (which can happen with ruby packages)
36
+
37
+ Solve.it!(graph, ['nginx', '>= 0.100.0'], sorted: true)
38
+
34
39
  ### Removing an artifact, or dependency from the graph
35
40
 
36
41
  graph.artifacts("nginx", "1.0.0").delete
data/lib/solve.rb CHANGED
@@ -18,13 +18,17 @@ module Solve
18
18
  #
19
19
  # @param [Solve::Graph] graph
20
20
  # @param [Array<Solve::Demand>, Array<String, String>] demands
21
- # @param [#say, nil] ui (nil)
21
+ #
22
+ # @option options [#say] :ui (nil)
23
+ # a ui object for output
24
+ # @option options [Boolean] :sorted (false)
25
+ # should the output be a sorted list rather than a Hash
22
26
  #
23
27
  # @raise [NoSolutionError]
24
28
  #
25
29
  # @return [Hash]
26
- def it!(graph, demands, ui = nil)
27
- Solver.new(graph, demands, ui).resolve
30
+ def it!(graph, demands, options = {})
31
+ Solver.new(graph, demands, options[:ui]).resolve(options)
28
32
  end
29
33
  end
30
34
  end
data/lib/solve/errors.rb CHANGED
@@ -31,5 +31,19 @@ module Solve
31
31
  end
32
32
 
33
33
  class NoSolutionError < SolveError; end
34
+
35
+ class UnsortableSolutionError < SolveError
36
+ attr_reader :internal_exception
37
+ attr_reader :unsorted_solution
38
+
39
+ def initialize(internal_exception, unsorted_solution)
40
+ @internal_exception = internal_exception
41
+ @unsorted_solution = unsorted_solution
42
+ end
43
+
44
+ def to_s
45
+ "The solution contains a cycle and cannot be topologically sorted. See #unsorted_solution on this exception for the unsorted solution"
46
+ end
47
+ end
34
48
  end
35
49
  end
@@ -1,3 +1,3 @@
1
1
  module Solve
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
data/lib/solve/solver.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'tsort'
1
2
  require_relative 'solver/variable_table'
2
3
  require_relative 'solver/variable_row'
3
4
  require_relative 'solver/constraint_table'
@@ -87,8 +88,12 @@ module Solve
87
88
  end
88
89
  end
89
90
 
90
- # @return [Hash]
91
- def resolve
91
+ # @option options [Boolean] :sorted
92
+ # return the solution as a sorted list instead of a Hash
93
+ #
94
+ # @return [Hash, List] Returns a hash like { "Artifact Name" => "Version",... }
95
+ # unless options[:sorted], then it returns a list like [["Artifact Name", "Version],...]
96
+ def resolve(options = {})
92
97
  trace("Attempting to find a solution")
93
98
  seed_demand_dependencies
94
99
 
@@ -119,16 +124,46 @@ module Solve
119
124
  end
120
125
  end
121
126
 
122
- solution = {}.tap do |solution|
127
+ solution = (options[:sorted]) ? build_sorted_solution : build_unsorted_solution
128
+
129
+ trace("Found Solution")
130
+ trace(solution)
131
+
132
+ solution
133
+ end
134
+
135
+ def build_unsorted_solution
136
+ {}.tap do |solution|
123
137
  variable_table.rows.each do |variable|
124
138
  solution[variable.artifact] = variable.value.version.to_s
125
139
  end
126
140
  end
141
+ end
142
+
143
+ def build_sorted_solution
144
+ unsorted_solution = build_unsorted_solution
145
+ nodes = Hash.new
146
+ unsorted_solution.each do |name, version|
147
+ nodes[name] = @graph.get_artifact(name, version).dependencies.map(&:name)
148
+ end
127
149
 
128
- trace("Found Solution")
129
- trace(solution)
150
+ # Modified from http://ruby-doc.org/stdlib-1.9.3/libdoc/tsort/rdoc/TSort.html
151
+ class << nodes
152
+ include TSort
153
+ alias tsort_each_node each_key
154
+ def tsort_each_child(node, &block)
155
+ fetch(node).each(&block)
156
+ end
157
+ end
158
+ begin
159
+ sorted_names = nodes.tsort
160
+ rescue TSort::Cyclic => e
161
+ raise Solve::Errors::UnsortableSolutionError.new(e, unsorted_solution)
162
+ end
130
163
 
131
- solution
164
+ sorted_names.map do |artifact|
165
+ [artifact, unsorted_solution[artifact]]
166
+ end
132
167
  end
133
168
 
134
169
  # @overload demands(name, constraint)
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Solutions" do
4
-
5
4
  it "chooses the correct artifact for the demands" do
6
5
  graph = Solve::Graph.new
7
6
  graph.artifacts("mysql", "2.0.0")
@@ -191,4 +190,69 @@ describe "Solutions" do
191
190
  "old-bottom" => "2.0.0"
192
191
  })
193
192
  end
193
+
194
+ describe "when options[:sorted] is true" do
195
+ describe "with a simple list of dependencies" do
196
+ it "returns a sorted list of dependencies" do
197
+ graph = Solve::Graph.new
198
+
199
+ graph.artifacts("A", "1.0.0").depends("B", "= 1.0.0")
200
+ graph.artifacts("B", "1.0.0").depends("C", "= 1.0.0")
201
+ graph.artifacts("C", "1.0.0")
202
+
203
+ demands = [["A"]]
204
+
205
+ result = Solve.it!(graph, demands, { :sorted => true })
206
+
207
+ result.should eql([
208
+ ["C", "1.0.0"],
209
+ ["B", "1.0.0"],
210
+ ["A", "1.0.0"]
211
+ ])
212
+ end
213
+ end
214
+
215
+ # The order that the demands come in determines the order of artifacts
216
+ # in the solver's variable_table. This must not determine the sort order
217
+ describe "with a constraint that depends upon an earlier constrained artifact" do
218
+ it "returns a sorted list of dependencies" do
219
+ graph = Solve::Graph.new
220
+
221
+ graph.artifacts("B", "1.0.0").depends("A", "= 1.0.0")
222
+ graph.artifacts("A", "1.0.0").depends("C", "= 1.0.0")
223
+ graph.artifacts("C", "1.0.0")
224
+
225
+ demands = [["A"],["B"]]
226
+
227
+ result = Solve.it!(graph, demands, { :sorted => true } )
228
+
229
+ result.should eql([
230
+ ["C", "1.0.0"],
231
+ ["A", "1.0.0"],
232
+ ["B", "1.0.0"]
233
+ ])
234
+ end
235
+ end
236
+
237
+ describe "when the solution is cyclic" do
238
+ it "raises a Solve::Errors::UnsortableSolutionError which contains the unsorted solution" do
239
+ graph = Solve::Graph.new
240
+
241
+ graph.artifacts("A", "1.0.0").depends("B", "= 1.0.0")
242
+ graph.artifacts("B", "1.0.0").depends("C", "= 1.0.0")
243
+ graph.artifacts("C", "1.0.0").depends("A", "= 1.0.0")
244
+
245
+ demands = [["A"]]
246
+
247
+ expect { Solve.it!(graph, demands, { :sorted => true } ) }.to raise_error { |error|
248
+ error.should be_a(Solve::Errors::UnsortableSolutionError)
249
+ error.unsorted_solution.should eql({
250
+ "A" => "1.0.0",
251
+ "B" => "1.0.0",
252
+ "C" => "1.0.0",
253
+ })
254
+ }
255
+ end
256
+ end
257
+ end
194
258
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie Winsor
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-07-16 00:00:00.000000000 Z
13
+ date: 2013-07-29 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: A Ruby version constraint solver
16
16
  email: