solve 0.2.1
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.
- data/.gitignore +17 -0
- data/.rbenv-version +1 -0
- data/Gemfile +3 -0
- data/Guardfile +17 -0
- data/LICENSE +201 -0
- data/README.md +43 -0
- data/Thorfile +31 -0
- data/lib/solve.rb +47 -0
- data/lib/solve/artifact.rb +104 -0
- data/lib/solve/constraint.rb +73 -0
- data/lib/solve/core_ext.rb +3 -0
- data/lib/solve/core_ext/kernel.rb +33 -0
- data/lib/solve/demand.rb +38 -0
- data/lib/solve/dependency.rb +40 -0
- data/lib/solve/errors.rb +31 -0
- data/lib/solve/gem_version.rb +3 -0
- data/lib/solve/graph.rb +152 -0
- data/lib/solve/version.rb +47 -0
- data/solve.gemspec +32 -0
- data/spec/acceptance/solutions_spec.rb +25 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/unit/solve/artifact_spec.rb +128 -0
- data/spec/unit/solve/constraint_spec.rb +201 -0
- data/spec/unit/solve/demand_spec.rb +57 -0
- data/spec/unit/solve/dependency_spec.rb +49 -0
- data/spec/unit/solve/graph_spec.rb +273 -0
- data/spec/unit/solve/version_spec.rb +11 -0
- data/spec/unit/solve_spec.rb +19 -0
- metadata +278 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Solve
|
2
|
+
class Constraint
|
3
|
+
class << self
|
4
|
+
# @param [#to_s] string
|
5
|
+
#
|
6
|
+
# @return [Array, nil]
|
7
|
+
def parse(string)
|
8
|
+
if string =~ /^[0-9]/
|
9
|
+
op = "="
|
10
|
+
ver = string
|
11
|
+
else
|
12
|
+
_, op, ver = REGEXP.match(string).to_a
|
13
|
+
end
|
14
|
+
|
15
|
+
return nil unless op || ver
|
16
|
+
|
17
|
+
[ op, ver ]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
OPERATORS = [
|
22
|
+
"=",
|
23
|
+
">",
|
24
|
+
"<",
|
25
|
+
">=",
|
26
|
+
"<=",
|
27
|
+
"~>"
|
28
|
+
]
|
29
|
+
REGEXP = /^(#{OPERATORS.join('|')}) (.+)$/
|
30
|
+
|
31
|
+
attr_reader :operator
|
32
|
+
attr_reader :version
|
33
|
+
|
34
|
+
# @param [#to_s] constraint
|
35
|
+
def initialize(constraint = ">= 0.0.0")
|
36
|
+
@operator, ver_str = self.class.parse(constraint)
|
37
|
+
if @operator.nil? || ver_str.nil?
|
38
|
+
raise InvalidConstraintFormat.new(constraint)
|
39
|
+
end
|
40
|
+
|
41
|
+
@version = Version.new(ver_str)
|
42
|
+
@dep_constraint = DepSelector::VersionConstraint.new(constraint)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns true or false if the given version would be satisfied by
|
46
|
+
# the version constraint.
|
47
|
+
#
|
48
|
+
# @param [Version, String] version
|
49
|
+
#
|
50
|
+
# @return [Boolean]
|
51
|
+
def satisfies?(version)
|
52
|
+
dep_constraint.include?(version)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [Object] other
|
56
|
+
#
|
57
|
+
# @return [Boolean]
|
58
|
+
def ==(other)
|
59
|
+
other.is_a?(self.class) &&
|
60
|
+
self.operator == other.operator &&
|
61
|
+
self.version == other.version
|
62
|
+
end
|
63
|
+
alias_method :eql?, :==
|
64
|
+
|
65
|
+
def to_s
|
66
|
+
"#{operator} #{version}"
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
attr_reader :dep_constraint
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# From ActiveSupport: https://github.com/rails/rails/blob/c2c8ef57d6f00d1c22743dc43746f95704d67a95/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L39
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
|
5
|
+
module Kernel
|
6
|
+
# Silences any stream for the duration of the block.
|
7
|
+
#
|
8
|
+
# silence_stream(STDOUT) do
|
9
|
+
# puts 'This will never be seen'
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# puts 'But this will'
|
13
|
+
def silence_stream(stream)
|
14
|
+
old_stream = stream.dup
|
15
|
+
stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
|
16
|
+
stream.sync = true
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
stream.reopen(old_stream)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Silences both STDOUT and STDERR, even for subprocesses.
|
23
|
+
#
|
24
|
+
# quietly { system 'bundle install' }
|
25
|
+
#
|
26
|
+
def quietly
|
27
|
+
silence_stream(STDOUT) do
|
28
|
+
silence_stream(STDERR) do
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/solve/demand.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Solve
|
2
|
+
class Demand
|
3
|
+
attr_reader :graph
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :constraint
|
6
|
+
|
7
|
+
# @param [Solve::Graph] graph
|
8
|
+
# @param [#to_s] name
|
9
|
+
# @param [Solve::Constraint, #to_s] constraint
|
10
|
+
def initialize(graph, name, constraint = nil)
|
11
|
+
@graph = graph
|
12
|
+
@name = name
|
13
|
+
|
14
|
+
if constraint
|
15
|
+
@constraint = if constraint.is_a?(Solve::Constraint)
|
16
|
+
constraint
|
17
|
+
else
|
18
|
+
Constraint.new(constraint.to_s)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Solve::Demand, nil]
|
24
|
+
def delete
|
25
|
+
unless graph.nil?
|
26
|
+
result = graph.remove_demand(self)
|
27
|
+
@graph = nil
|
28
|
+
result
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
s = "#{name}"
|
34
|
+
s << "(#{constraint})" if constraint
|
35
|
+
s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Solve
|
2
|
+
class Dependency
|
3
|
+
attr_reader :artifact
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :constraint
|
6
|
+
|
7
|
+
# @param [Solve::Artifact] artifact
|
8
|
+
# @param [#to_s] name
|
9
|
+
# @param [Solve::Constraint, #to_s] constraint
|
10
|
+
def initialize(artifact, name, constraint)
|
11
|
+
@artifact = artifact
|
12
|
+
@name = name
|
13
|
+
@constraint = case constraint
|
14
|
+
when Solve::Constraint
|
15
|
+
constraint
|
16
|
+
else
|
17
|
+
Constraint.new(constraint)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Solve::Dependency, nil]
|
22
|
+
def delete
|
23
|
+
unless artifact.nil?
|
24
|
+
result = artifact.remove_dependency(self)
|
25
|
+
@artifact = nil
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Object] other
|
31
|
+
#
|
32
|
+
# @return [Boolean]
|
33
|
+
def ==(other)
|
34
|
+
other.is_a?(self.class) &&
|
35
|
+
self.artifact == other.artifact &&
|
36
|
+
self.constraint == other.constraint
|
37
|
+
end
|
38
|
+
alias_method :eql?, :==
|
39
|
+
end
|
40
|
+
end
|
data/lib/solve/errors.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Solve
|
2
|
+
class SolveError < StandardError; end
|
3
|
+
|
4
|
+
class InvalidVersionFormat < SolveError
|
5
|
+
attr_reader :version
|
6
|
+
|
7
|
+
# @param [#to_s] version
|
8
|
+
def initialize(version)
|
9
|
+
@version = version
|
10
|
+
end
|
11
|
+
|
12
|
+
def message
|
13
|
+
"'#{version}' did not contain a valid version string: 'x.y.z' or 'x.y'."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class InvalidConstraintFormat < SolveError
|
18
|
+
attr_reader :constraint
|
19
|
+
|
20
|
+
# @param [#to_s] constraint
|
21
|
+
def initialize(constraint)
|
22
|
+
@constraint = constraint
|
23
|
+
end
|
24
|
+
|
25
|
+
def message
|
26
|
+
"'#{constraint}' did not contain a valid operator or a valid version string."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class NoSolutionError < SolveError; end
|
31
|
+
end
|
data/lib/solve/graph.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
module Solve
|
2
|
+
class Graph
|
3
|
+
def initialize
|
4
|
+
@artifacts = Hash.new
|
5
|
+
@demands = Hash.new
|
6
|
+
@dep_graph = DepSelector::DependencyGraph.new
|
7
|
+
end
|
8
|
+
|
9
|
+
# @overload artifacts(name, version)
|
10
|
+
# Return the Solve::Artifact from the collection of artifacts
|
11
|
+
# with the given name and version.
|
12
|
+
#
|
13
|
+
# @param [#to_s]
|
14
|
+
# @param [Solve::Version, #to_s]
|
15
|
+
#
|
16
|
+
# @return [Solve::Artifact]
|
17
|
+
# @overload artifacts
|
18
|
+
# Return the collection of artifacts
|
19
|
+
#
|
20
|
+
# @return [Array<Solve::Artifact>]
|
21
|
+
def artifacts(*args)
|
22
|
+
if args.empty?
|
23
|
+
return artifact_collection
|
24
|
+
end
|
25
|
+
unless args.length == 2
|
26
|
+
raise ArgumentError, "Unexpected number of arguments. You gave: #{args.length}. Expected: 0 or 2."
|
27
|
+
end
|
28
|
+
|
29
|
+
name, version = args
|
30
|
+
|
31
|
+
if name.nil? || version.nil?
|
32
|
+
raise ArgumentError, "A name and version must be specified. You gave: #{args}."
|
33
|
+
end
|
34
|
+
|
35
|
+
artifact = Artifact.new(self, name, version)
|
36
|
+
add_artifact(artifact)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add a Solve::Artifact to the collection of artifacts and
|
40
|
+
# return the added Solve::Artifact. No change will be made
|
41
|
+
# if the artifact is already a member of the collection.
|
42
|
+
#
|
43
|
+
# @param [Solve::Artifact] artifact
|
44
|
+
#
|
45
|
+
# @return [Solve::Artifact]
|
46
|
+
def add_artifact(artifact)
|
47
|
+
unless has_artifact?(artifact)
|
48
|
+
@dep_graph.package(artifact.name).add_version(DepSelector::Version.new(artifact.version.to_s))
|
49
|
+
@artifacts[artifact.to_s] = artifact
|
50
|
+
end
|
51
|
+
|
52
|
+
artifact
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [Solve::Artifact, nil] artifact
|
56
|
+
def remove_artifact(artifact)
|
57
|
+
if has_artifact?(artifact)
|
58
|
+
@dep_graph.packages.delete(artifact.to_s)
|
59
|
+
@artifacts.delete(artifact.to_s)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Solve::Artifact] artifact
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
def has_artifact?(artifact)
|
67
|
+
@artifacts.has_key?(artifact.to_s)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @overload demands(name, constraint)
|
71
|
+
# Return the Solve::Demand from the collection of demands
|
72
|
+
# with the given name and constraint.
|
73
|
+
#
|
74
|
+
# @param [#to_s]
|
75
|
+
# @param [Solve::Constraint, #to_s]
|
76
|
+
#
|
77
|
+
# @return [Solve::Demand]
|
78
|
+
# @overload demands(name)
|
79
|
+
# Return the Solve::Demand from the collection of demands
|
80
|
+
# with the given name.
|
81
|
+
#
|
82
|
+
# @param [#to_s]
|
83
|
+
#
|
84
|
+
# @return [Solve::Demand]
|
85
|
+
# @overload demands
|
86
|
+
# Return the collection of demands
|
87
|
+
#
|
88
|
+
# @return [Array<Solve::Demand>]
|
89
|
+
def demands(*args)
|
90
|
+
if args.empty?
|
91
|
+
return demand_collection
|
92
|
+
end
|
93
|
+
if args.length > 2
|
94
|
+
raise ArgumentError, "Unexpected number of arguments. You gave: #{args.length}. Expected: 2 or less."
|
95
|
+
end
|
96
|
+
|
97
|
+
name, constraint = args
|
98
|
+
constraint ||= ">= 0.0.0"
|
99
|
+
|
100
|
+
if name.nil?
|
101
|
+
raise ArgumentError, "A name must be specified. You gave: #{args}."
|
102
|
+
end
|
103
|
+
|
104
|
+
demand = Demand.new(self, name, constraint)
|
105
|
+
add_demand(demand)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Add a Solve::Demand to the collection of demands and
|
109
|
+
# return the added Solve::Demand. No change will be made
|
110
|
+
# if the demand is already a member of the collection.
|
111
|
+
#
|
112
|
+
# @param [Solve::Demand] demand
|
113
|
+
#
|
114
|
+
# @return [Solve::Demand]
|
115
|
+
def add_demand(demand)
|
116
|
+
unless has_demand?(demand)
|
117
|
+
@demands[demand.to_s] = demand
|
118
|
+
end
|
119
|
+
|
120
|
+
demand
|
121
|
+
end
|
122
|
+
alias_method :demand, :add_demand
|
123
|
+
|
124
|
+
# @param [Solve::Demand, nil] demand
|
125
|
+
def remove_demand(demand)
|
126
|
+
if has_demand?(demand)
|
127
|
+
@demands.delete(demand.to_s)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param [Solve::Demand] demand
|
132
|
+
#
|
133
|
+
# @return [Boolean]
|
134
|
+
def has_demand?(demand)
|
135
|
+
@demands.has_key?(demand.to_s)
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
attr_reader :dep_graph
|
141
|
+
|
142
|
+
# @return [Array<Solve::Artifact>]
|
143
|
+
def artifact_collection
|
144
|
+
@artifacts.collect { |name, artifact| artifact }
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Array<Solve::Demand>]
|
148
|
+
def demand_collection
|
149
|
+
@demands.collect { |name, demand| demand }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Solve
|
2
|
+
class Version
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_reader :major
|
6
|
+
attr_reader :minor
|
7
|
+
attr_reader :patch
|
8
|
+
|
9
|
+
# @param [#to_s] ver_str
|
10
|
+
def initialize(ver_str = String.new)
|
11
|
+
@major, @minor, @patch = parse(ver_str)
|
12
|
+
end
|
13
|
+
|
14
|
+
def <=>(other)
|
15
|
+
[:major, :minor, :patch].each do |method|
|
16
|
+
ans = (self.send(method) <=> other.send(method))
|
17
|
+
return ans if ans != 0
|
18
|
+
end
|
19
|
+
0
|
20
|
+
end
|
21
|
+
|
22
|
+
def eql?(other)
|
23
|
+
other.is_a?(Version) && self == other
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
"#{major}.#{minor}.#{patch}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def parse(ver_str = String.new)
|
37
|
+
@major, @minor, @patch = case ver_str.to_s
|
38
|
+
when /^(\d+)\.(\d+)\.(\d+)$/
|
39
|
+
[ $1.to_i, $2.to_i, $3.to_i ]
|
40
|
+
when /^(\d+)\.(\d+)$/
|
41
|
+
[ $1.to_i, $2.to_i, 0 ]
|
42
|
+
else
|
43
|
+
raise InvalidVersionFormat.new(ver_str)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/solve.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/solve/gem_version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.authors = ["Jamie Winsor"]
|
6
|
+
s.email = ["jamie@vialstudios.com"]
|
7
|
+
s.description = %q{A Ruby constraint solver}
|
8
|
+
s.summary = s.description
|
9
|
+
s.homepage = "https://github.com/reset/solve"
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split($\)
|
12
|
+
s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
s.test_files = s.files.grep(%r{^spec/})
|
14
|
+
s.name = "solve"
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.version = Solve::VERSION
|
17
|
+
s.required_ruby_version = ">= 1.9.1"
|
18
|
+
|
19
|
+
s.add_runtime_dependency 'dep_selector', '~> 0.0.8'
|
20
|
+
|
21
|
+
s.add_development_dependency 'thor'
|
22
|
+
s.add_development_dependency 'rspec'
|
23
|
+
s.add_development_dependency 'fuubar'
|
24
|
+
s.add_development_dependency 'spork'
|
25
|
+
s.add_development_dependency 'yard'
|
26
|
+
s.add_development_dependency 'redcarpet'
|
27
|
+
s.add_development_dependency 'guard'
|
28
|
+
s.add_development_dependency 'guard-rspec'
|
29
|
+
s.add_development_dependency 'guard-spork'
|
30
|
+
s.add_development_dependency 'guard-yard'
|
31
|
+
s.add_development_dependency 'coolline'
|
32
|
+
end
|