solve 0.2.1 → 0.3.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.
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ script: "bundle exec thor spec"
2
+ language: ruby
3
+ rvm:
4
+ - 1.9.2
5
+ - 1.9.3
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  # Solve
2
+ [![Build Status](https://secure.travis-ci.org/reset/solve.png?branch=master)](http://travis-ci.org/reset/solve)
3
+ [![Dependency Status](https://gemnasium.com/reset/solve.png?travis)](https://gemnasium.com/reset/solve)
4
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/reset/solve)
2
5
 
3
6
  A Ruby constraint solver
4
7
 
@@ -20,20 +23,18 @@ Now add another artifact that has a dependency
20
23
 
21
24
  graph.artifacts("mysql", "1.2.4").depends("openssl", "~> 1.0.0")
22
25
 
23
- Setup some demands
26
+ Dependencies can be chained, too
24
27
 
25
- graph.demands('nginx', '>= 0.100.0')
28
+ graph.artifacts("ntp", "1.0.0").depends("build-essential").depends("yum")
26
29
 
27
- And now solve the graph
30
+ And now solve the graph with some demands
28
31
 
29
- Solve.it!(graph)
32
+ Solve.it!(graph, ['nginx', '>= 0.100.0'])
30
33
 
31
- ### Removing an artifact, demand, or dependency
34
+ ### Removing an artifact, or dependency from the graph
32
35
 
33
36
  graph.artifacts("nginx", "1.0.0").delete
34
37
 
35
- graph.demands('nginx', '>= 0.100.0').delete
36
-
37
38
  artifact.dependencies("nginx", "~> 1.0.0").delete
38
39
 
39
40
  ## Authors
data/lib/solve.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'solve/errors'
2
- require 'solve/core_ext'
3
- require 'dep_selector'
4
2
 
3
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
4
  module Solve
6
5
  autoload :Version, 'solve/version'
7
6
  autoload :Artifact, 'solve/artifact'
@@ -9,39 +8,21 @@ module Solve
9
8
  autoload :Dependency, 'solve/dependency'
10
9
  autoload :Graph, 'solve/graph'
11
10
  autoload :Demand, 'solve/demand'
11
+ autoload :Solver, 'solve/solver'
12
12
 
13
13
  class << self
14
- include DepSelector
15
-
16
- # @param [Solve::Graph] graph
14
+ # A quick solve. Given the "world" as we know it (the graph) and a list of
15
+ # requirements (demands) which must be met. Return me the best solution of
16
+ # artifacts and verisons that I should use.
17
17
  #
18
- # @return [Hash]
19
- def it(graph)
20
- it!(graph)
21
- rescue NoSolutionError
22
- nil
23
- end
24
-
25
18
  # @param [Solve::Graph] graph
19
+ # @param [Array<Solve::Demand>, Array<String, String>] demands
20
+ #
21
+ # @raise [NoSolutionError]
26
22
  #
27
23
  # @return [Hash]
28
- def it!(graph)
29
- dep_graph = graph.send(:dep_graph)
30
- selector = Selector.new(dep_graph)
31
-
32
- solution_constraints = graph.demands.collect do |demand|
33
- SolutionConstraint.new(dep_graph.package(demand.name), DepSelector::VersionConstraint.new(demand.constraint.to_s))
34
- end
35
-
36
- solution = quietly { selector.find_solution(solution_constraints) }
37
-
38
- {}.tap do |artifacts|
39
- solution.each do |name, constraint|
40
- artifacts[name] = constraint.to_s
41
- end
42
- end
43
- rescue DepSelector::Exceptions::InvalidSolutionConstraints
44
- raise NoSolutionError
24
+ def it!(graph, demands)
25
+ Solver.new(graph, demands).resolve
45
26
  end
46
27
  end
47
28
  end
@@ -1,7 +1,21 @@
1
1
  module Solve
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
2
3
  class Artifact
4
+ include Comparable
5
+
6
+ # A reference to the graph this artifact belongs to
7
+ #
8
+ # @return [Solve::Graph]
3
9
  attr_reader :graph
10
+
11
+ # The name of the artifact
12
+ #
13
+ # @return [String]
4
14
  attr_reader :name
15
+
16
+ # The version of this artifact
17
+ #
18
+ # @return [Solve::Version]
5
19
  attr_reader :version
6
20
 
7
21
  # @param [Solve::Graph] graph
@@ -14,73 +28,50 @@ module Solve
14
28
  @dependencies = Hash.new
15
29
  end
16
30
 
17
- # @overload dependencies(name, constraint)
18
- # Return the Solve::Dependency from the collection of
19
- # dependencies with the given name and constraint.
31
+ # Return the Solve::Dependency from the collection of
32
+ # dependencies with the given name and constraint.
20
33
  #
21
- # @param [#to_s]
22
- # @param [Solve::Constraint, #to_s]
34
+ # @param [#to_s] name
35
+ # @param [Solve::Constraint, #to_s] constraint
23
36
  #
24
- # @return [Solve::Dependency]
25
- # @overload dependencies
26
- # Return the collection of dependencies
37
+ # @example adding dependencies
38
+ # artifact.depends("nginx") => <#Dependency: @name="nginx", @constraint=">= 0.0.0">
39
+ # artifact.depends("ntp", "= 1.0.0") => <#Dependency: @name="ntp", @constraint="= 1.0.0">
27
40
  #
28
- # @return [Array<Solve::Dependency>]
29
- def dependencies(*args)
30
- if args.empty?
31
- return dependency_collection
32
- end
33
- if args.length > 2
34
- raise ArgumentError, "Unexpected number of arguments. You gave: #{args.length}. Expected: 2 or less."
35
- end
36
-
37
- name, constraint = args
38
- constraint ||= ">= 0.0.0"
39
-
41
+ # @example chaining dependencies
42
+ # artifact.depends("nginx").depends("ntp")
43
+ #
44
+ # @return [Solve::Artifact]
45
+ def depends(name, constraint = ">= 0.0.0")
40
46
  if name.nil?
41
47
  raise ArgumentError, "A name must be specified. You gave: #{args}."
42
48
  end
43
49
 
44
50
  dependency = Dependency.new(self, name, constraint)
45
51
  add_dependency(dependency)
46
- end
47
- alias_method :depends, :dependencies
48
52
 
49
- # Add a Solve::Dependency to the collection of dependencies
50
- # and return the added Solve::Dependency. No change will be
51
- # made if the dependency is already a member of the collection.
52
- #
53
- # @param [Solve::Dependency] dependency
54
- #
55
- # @return [Solve::Dependency]
56
- def add_dependency(dependency)
57
- unless has_dependency?(dependency)
58
- dep_graph = graph.send(:dep_graph)
59
- a = dep_graph.package(self.name).add_version(DepSelector::Version.new(self.version.to_s))
60
- dep_pack = dep_graph.package(dependency.name)
61
- a.dependencies << DepSelector::Dependency.new(dep_pack, DepSelector::VersionConstraint.new(dependency.constraint.to_s))
62
- @dependencies[dependency.to_s] = dependency
63
- end
64
-
65
- dependency
53
+ self
66
54
  end
67
55
 
68
- # @param [Solve::Dependency] dependency
56
+ # Return the collection of dependencies on this instance of artifact
69
57
  #
70
- # @return [Solve::Dependency, nil]
71
- def remove_dependency(dependency)
72
- if has_dependency?(dependency)
73
- @dependencies.delete(dependency.to_s)
74
- end
58
+ # @return [Array<Solve::Dependency>]
59
+ def dependencies
60
+ @dependencies.collect { |name, dependency| dependency }
75
61
  end
76
62
 
77
- # @param [Solve::Dependency] dependency
63
+ # Retrieve the dependency from the artifact with the matching name and constraint
78
64
  #
79
- # @return [Boolean]
80
- def has_dependency?(dependency)
81
- @dependencies.has_key?(dependency.to_s)
65
+ # @param [#to_s] name
66
+ # @param [#to_s] constraint
67
+ #
68
+ # @return [Solve::Artifact, nil]
69
+ def get_dependency(name, constraint)
70
+ @dependencies.fetch(Graph.dependency_key(name, constraint), nil)
82
71
  end
83
72
 
73
+ # Remove this artifact from the graph it belongs to
74
+ #
84
75
  # @return [Solve::Artifact, nil]
85
76
  def delete
86
77
  unless graph.nil?
@@ -94,11 +85,59 @@ module Solve
94
85
  "#{name}-#{version}"
95
86
  end
96
87
 
88
+ # @param [Object] other
89
+ #
90
+ # @return [Boolean]
91
+ def ==(other)
92
+ other.is_a?(self.class) &&
93
+ self.name == other.name &&
94
+ self.version == other.version
95
+ end
96
+ alias_method :eql?, :==
97
+
98
+ # @param [Solve::Version] other
99
+ #
100
+ # @return [Integer]
101
+ def <=>(other)
102
+ self.version <=> other.version
103
+ end
104
+
97
105
  private
98
106
 
99
- # @return [Array<Solve::Dependency>]
100
- def dependency_collection
101
- @dependencies.collect { |name, dependency| dependency }
107
+ # Add a Solve::Dependency to the collection of dependencies
108
+ # and return the added Solve::Dependency. No change will be
109
+ # made if the dependency is already a member of the collection.
110
+ #
111
+ # @param [Solve::Dependency] dependency
112
+ #
113
+ # @return [Solve::Dependency]
114
+ def add_dependency(dependency)
115
+ unless has_dependency?(dependency.name, dependency.constraint)
116
+ @dependencies[Graph.key_for(dependency)] = dependency
117
+ end
118
+
119
+ get_dependency(dependency.name, dependency.constraint)
120
+ end
121
+
122
+ # Remove the matching dependency from the artifact
123
+ #
124
+ # @param [Solve::Dependency] dependency
125
+ #
126
+ # @return [Solve::Dependency, nil]
127
+ def remove_dependency(dependency)
128
+ if has_dependency?(dependency)
129
+ @dependencies.delete(Graph.key_for(dependency))
130
+ end
131
+ end
132
+
133
+ # Check if the artifact has a dependency with the matching name and constraint
134
+ #
135
+ # @param [#to_s] name
136
+ # @param [#to_s] constraint
137
+ #
138
+ # @return [Boolean]
139
+ def has_dependency?(name, constraint)
140
+ @dependencies.has_key?(Graph.dependency_key(name, constraint))
102
141
  end
103
142
  end
104
143
  end
@@ -1,10 +1,29 @@
1
1
  module Solve
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
2
3
  class Constraint
3
4
  class << self
5
+ # Split a constraint string into an Array of two elements. The first
6
+ # element being the operator and second being the version string.
7
+ #
8
+ # If the given string does not contain a constraint operator then (=)
9
+ # will be used.
10
+ #
11
+ # If the given string does not contain a valid version string then
12
+ # nil will be returned.
13
+ #
4
14
  # @param [#to_s] string
5
15
  #
16
+ # @example splitting a string with a constraint operator and valid version string
17
+ # Constraint.split(">= 1.0.0") => [ ">=", "1.0.0" ]
18
+ #
19
+ # @example splitting a string without a constraint operator
20
+ # Constraint.split("0.0.0") => [ "=", "1.0.0" ]
21
+ #
22
+ # @example splitting a string without a valid version string
23
+ # Constraint.split("hello") => nil
24
+ #
6
25
  # @return [Array, nil]
7
- def parse(string)
26
+ def split(string)
8
27
  if string =~ /^[0-9]/
9
28
  op = "="
10
29
  ver = string
@@ -16,40 +35,108 @@ module Solve
16
35
 
17
36
  [ op, ver ]
18
37
  end
38
+
39
+ # @param [Solve::Constraint] constraint
40
+ # @param [Solve::Version] target_version
41
+ #
42
+ # @return [Boolean]
43
+ def compare_equal(constraint, target_version)
44
+ target_version == constraint.version
45
+ end
46
+
47
+ # @param [Solve::Constraint] constraint
48
+ # @param [Solve::Version] target_version
49
+ #
50
+ # @return [Boolean]
51
+ def compare_gt(constraint, target_version)
52
+ target_version > constraint.version
53
+ end
54
+
55
+ # @param [Solve::Constraint] constraint
56
+ # @param [Solve::Version] target_version
57
+ #
58
+ # @return [Boolean]
59
+ def compare_lt(constraint, target_version)
60
+ target_version < constraint.version
61
+ end
62
+
63
+ # @param [Solve::Constraint] constraint
64
+ # @param [Solve::Version] target_version
65
+ #
66
+ # @return [Boolean]
67
+ def compare_gte(constraint, target_version)
68
+ target_version >= constraint.version
69
+ end
70
+
71
+ # @param [Solve::Constraint] constraint
72
+ # @param [Solve::Version] target_version
73
+ #
74
+ # @return [Boolean]
75
+ def compare_lte(constraint, target_version)
76
+ target_version <= constraint.version
77
+ end
78
+
79
+ # @param [Solve::Constraint] constraint
80
+ # @param [Solve::Version] target_version
81
+ #
82
+ # @return [Boolean]
83
+ def compare_aprox(constraint, target_version)
84
+ unless constraint.patch.nil?
85
+ target_version.patch >= constraint.patch &&
86
+ target_version.minor == constraint.minor &&
87
+ target_version.major == constraint.major
88
+ else
89
+ target_version.minor >= constraint.minor &&
90
+ target_version.major == constraint.major
91
+ end
92
+ end
19
93
  end
20
94
 
21
- OPERATORS = [
22
- "=",
23
- ">",
24
- "<",
25
- ">=",
26
- "<=",
27
- "~>"
28
- ]
29
- REGEXP = /^(#{OPERATORS.join('|')}) (.+)$/
95
+ OPERATORS = {
96
+ "=" => method(:compare_equal),
97
+ ">" => method(:compare_gt),
98
+ "<" => method(:compare_lt),
99
+ ">=" => method(:compare_gte),
100
+ "<=" => method(:compare_lte),
101
+ "~>" => method(:compare_aprox)
102
+ }.freeze
103
+
104
+ REGEXP = /^(#{OPERATORS.keys.join('|')}) (.+)$/
30
105
 
31
106
  attr_reader :operator
32
- attr_reader :version
107
+ attr_reader :major
108
+ attr_reader :minor
109
+ attr_reader :patch
33
110
 
34
111
  # @param [#to_s] constraint
35
112
  def initialize(constraint = ">= 0.0.0")
36
- @operator, ver_str = self.class.parse(constraint)
113
+ @operator, ver_str = self.class.split(constraint)
37
114
  if @operator.nil? || ver_str.nil?
38
- raise InvalidConstraintFormat.new(constraint)
115
+ raise Errors::InvalidConstraintFormat.new(constraint)
39
116
  end
40
117
 
41
- @version = Version.new(ver_str)
42
- @dep_constraint = DepSelector::VersionConstraint.new(constraint)
118
+ @major, @minor, @patch = Version.split(ver_str)
119
+ @compare_fun = OPERATORS.fetch(self.operator)
120
+ end
121
+
122
+ # Return the Solve::Version representation of the major, minor, and patch
123
+ # attributes of this instance
124
+ #
125
+ # @return [Solve::Version]
126
+ def version
127
+ @version ||= Version.new([self.major, self.minor, self.patch])
43
128
  end
44
129
 
45
130
  # Returns true or false if the given version would be satisfied by
46
131
  # the version constraint.
47
132
  #
48
- # @param [Version, String] version
133
+ # @param [#to_s] target_version
49
134
  #
50
135
  # @return [Boolean]
51
- def satisfies?(version)
52
- dep_constraint.include?(version)
136
+ def satisfies?(target_version)
137
+ target_version = Version.new(target_version.to_s)
138
+
139
+ @compare_fun.call(self, target_version)
53
140
  end
54
141
 
55
142
  # @param [Object] other
@@ -58,16 +145,14 @@ module Solve
58
145
  def ==(other)
59
146
  other.is_a?(self.class) &&
60
147
  self.operator == other.operator &&
61
- self.version == other.version
148
+ self.major == other.minor &&
149
+ self.minor == other.minor &&
150
+ self.patch == other.patch
62
151
  end
63
152
  alias_method :eql?, :==
64
153
 
65
154
  def to_s
66
- "#{operator} #{version}"
155
+ "#{operator} #{major}.#{minor}.#{patch}"
67
156
  end
68
-
69
- private
70
-
71
- attr_reader :dep_constraint
72
157
  end
73
158
  end