solve 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].sort.each do |path|
2
+ require "solve/core_ext/#{File.basename(path, '.rb')}"
3
+ 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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Solve
2
+ VERSION = "0.2.1"
3
+ end
@@ -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