grimoire 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ef5df1a91ba2a0ba8d102c0566e2eccca125678c
4
+ data.tar.gz: 7f2faf3d48426e22fee9ddf77d2c7845d1897071
5
+ SHA512:
6
+ metadata.gz: 67788092dff047186519fff5efe33869c3395bda3769f49de24dca62d8a5e2a466d11278fbd770ed0cb14b212f782321a5c90c37ff8c9e6f555bfb68201a50cf
7
+ data.tar.gz: 548afdfb3c061bb89591113404c438e97d9ecf4ed7cb74b4cf5b843c4c2a820e998218bb3e3f2b72fbc5a262cf99c0ed61168f9f354cb7abd6c3ab76fb3f6125
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # v0.1.0
2
+ * Initial commit
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ ## Branches
4
+
5
+ ### `master` branch
6
+
7
+ The master branch is the current stable released version.
8
+
9
+ ### `develop` branch
10
+
11
+ The develop branch is the current edge of development.
12
+
13
+ ## Pull requests
14
+
15
+ * https://github.com/spox/grimoire
16
+
17
+ Please base all pull requests of the `develop` branch. Merges to
18
+ `master` only occur through the `develop` branch. Pull requests
19
+ based on `master` will likely be cherry picked.
20
+
21
+ ## Issues
22
+
23
+ Need to report an issue? Use the github issues:
24
+
25
+ * https://github.com/spox/grimoire
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2015 Chris Roberts
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Grimoire
2
+
3
+ ## Info
4
+
5
+ * Repository: https://github.com/spox/grimoire
data/grimoire.gemspec ADDED
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'grimoire/version'
3
+ Gem::Specification.new do |s|
4
+ s.name = 'grimoire'
5
+ s.version = Grimoire::VERSION.version
6
+ s.summary = 'Constraint solver'
7
+ s.author = 'Chris Roberts'
8
+ s.email = 'code@chrisroberts.org'
9
+ s.homepage = 'https://github.com/spox/grimoire'
10
+ s.description = 'Specialized constraint solver allowing weighted results'
11
+ s.require_path = 'lib'
12
+ s.license = 'Apache 2.0'
13
+ s.add_runtime_dependency 'bogo', '~> 0.1.10'
14
+ s.add_development_dependency 'minitest'
15
+ s.add_development_dependency 'pry'
16
+ s.files = Dir['{lib}/**/**/*'] + %w(grimoire.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
17
+ end
data/lib/grimoire.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'bogo'
2
+
3
+ module Grimoire
4
+ # @todo Provide abstract interfaces for these and run validation on
5
+ # defined classes to ensure expected methods
6
+
7
+ # Class used to define version information
8
+ VERSION_CLASS = Gem::Version
9
+ # Class used to define dependency information
10
+ DEPENDENCY_CLASS = Gem::Dependency
11
+ # Class used to define requirement
12
+ REQUIREMENT_CLASS = Gem::Requirement
13
+
14
+ autoload :Error, 'grimoire/error'
15
+ autoload :Path, 'grimoire/path'
16
+ autoload :RequirementList, 'grimoire/requirement_list'
17
+ autoload :Solver, 'grimoire/solver'
18
+ autoload :System, 'grimoire/system'
19
+ autoload :Unit, 'grimoire/unit'
20
+ autoload :Utility, 'grimoire/utility'
21
+
22
+ end
23
+
24
+ require 'grimoire/version'
@@ -0,0 +1,19 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+
5
+ # General error
6
+ class Error < StandardError
7
+ # Requested unit is unavailable
8
+ class UnitUnavailable < Error
9
+ attr_accessor :unit_name
10
+ end
11
+ # Resolution path is not valid within constraints
12
+ class ResolutionPathInvalid < Error; end
13
+ # Too many loops run during path generation
14
+ class MaximumGenerationLoopsExceeded < Error; end
15
+ # No valid solution available
16
+ class NoSolution < Error; end
17
+ end
18
+
19
+ end
@@ -0,0 +1,14 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+ # Constraint resolution path
5
+ class Path < Utility
6
+
7
+ attribute :units, Unit, :multiple => true, :default => []
8
+
9
+ def dependencies
10
+ units.map(&:dependencies).flatten.uniq!
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+
5
+ # Requirment list for solver
6
+ class RequirementList < Utility
7
+ attribute :name, String, :required => true, :coerce => lambda{|val| val.to_s}
8
+ attribute :requirements, DEPENDENCY_CLASS, :multiple => true, :default => [], :coerce => lambda{|val| DEPENDENCY_CLASS.new(val.first, *val.last)}
9
+ end
10
+
11
+ end
@@ -0,0 +1,186 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+ # Requirement solver
5
+ class Solver < Utility
6
+
7
+ # Ceiling for number of loops allowed during path generation
8
+ MAX_GENERATION_LOOPS = 100000
9
+
10
+ include Bogo::Memoization
11
+
12
+ attribute :requirements, RequirementList, :required => true
13
+ attribute :system, System, :required => true
14
+
15
+ # @return [System] subset of full system based on requirements
16
+ attr_reader :world
17
+
18
+ def initialize(*_)
19
+ super
20
+ @world = System.new
21
+ build_world(requirements.requirements, world, system)
22
+ world.scrub!
23
+ end
24
+
25
+ # Build the world required for the solver (the subset of the
26
+ # entire system required by the requirements)
27
+ #
28
+ # @param deps [DEPENDENCY_CLASS] dependencies required to resolve
29
+ # @param my_world [System] system to populate with units
30
+ # @param root [System] superset system to extract units
31
+ def build_world(deps=nil, my_world=nil, root = nil)
32
+ deps = requirement.requirements unless deps
33
+ my_world = world unless my_world
34
+ root = system unless root
35
+ deps.each do |dep|
36
+ units = root.subset(dep.name, dep.requirement)
37
+ units.each do |unit|
38
+ build_world(unit.dependencies, my_world, root)
39
+ end
40
+ my_world.add_unit(units)
41
+ end
42
+ end
43
+
44
+ # @return [Smash<{Unit#name:Bogo::PriorityQueue}>]
45
+ def queues
46
+ memoize(:queues) do
47
+ Smash[
48
+ world.units.map do |name, items|
49
+ queue = Bogo::PriorityQueue.new
50
+ populate_queue(queue, items)
51
+ [name, queue]
52
+ end
53
+ ]
54
+ end
55
+ end
56
+
57
+ # Populate queue with units
58
+ #
59
+ # @param p_queue [Bogo::PriorityQueue]
60
+ # @param units [Array<Unit>]
61
+ # @return [Bogo::PriorityQueue]
62
+ def populate_queue(p_queue, units)
63
+ units.each_with_index do |unit, index|
64
+ p_queue.push(unit, score_unit(unit, index))
65
+ end
66
+ p_queue
67
+ end
68
+
69
+ # Provide score for given unit
70
+ #
71
+ # @param unit [Unit]
72
+ # @param score [Integer] current score
73
+ # @return [Integer] score
74
+ def score_unit(unit, score)
75
+ score
76
+ end
77
+
78
+ # Repopulate the given queue
79
+ #
80
+ # @param name [String]
81
+ # @return [self]
82
+ def reset_queue(name)
83
+ queue = populate_queue(
84
+ Bogo::PriorityQueue.new,
85
+ world.units[name]
86
+ )
87
+ queues[name] = queue
88
+ self
89
+ end
90
+
91
+ # Provide Unit acceptable for given dependency
92
+ #
93
+ # @param dep [DEPENDENCY_CLASS]
94
+ # @return [Unit]
95
+ # @raises [Error::UnitUnavailable]
96
+ def unit_for(dep)
97
+ unit = nil
98
+ until(unit || queues[dep.name].empty?)
99
+ unit = queues[dep.name].pop
100
+ unit = nil unless dep.requirement.satisfied_by?(unit.version)
101
+ end
102
+ unless(unit)
103
+ error = Error::UnitUnavailable.new("Failed to locate valid unit for: #{dep.inspect}")
104
+ error.unit_name = dep.name
105
+ raise error
106
+ end
107
+ unit
108
+ end
109
+
110
+ # Resolve path for a given dependency
111
+ #
112
+ # @param dep [DEPENDENCY_CLASS]
113
+ # @param given [DEPENDENCY_CLASS]
114
+ # @return [Array<Unit>]
115
+ def resolve(dep, given=nil)
116
+ unit = given || unit_for(dep)
117
+ if(unit.dependencies.empty?)
118
+ [unit]
119
+ else
120
+ deps = [unit]
121
+ begin
122
+ unit.dependencies.map do |u_dep|
123
+ existing = deps.detect{|d| d.name == u_dep.name}
124
+ if(existing)
125
+ if(u_dep.requirement.satisfied_by?(existing.version))
126
+ next
127
+ else
128
+ deps.delete(existing)
129
+ reset_queue(u_dep.name) unless given
130
+ end
131
+ else
132
+ reset_queue(u_dep.name) unless given
133
+ end
134
+ deps += resolve(u_dep)
135
+ deps.compact!
136
+ u_dep
137
+ end.compact.map do |u_dep| # validator
138
+ existing = deps.detect{|d| d.name == u_dep.name}
139
+ if(existing)
140
+ unless(u_dep.requirement.satisfied_by?(existing.version))
141
+ deps.delete(existing)
142
+ reset_queue(u_dep.name)
143
+ raise Error::ResolutionPathInvalid.new("Unit <#{existing.inspect}> does not satisfy <#{u_dep.inspect}>")
144
+ end
145
+ end
146
+ end
147
+ rescue Error::ResolutionPathInvalid
148
+ retry
149
+ end
150
+ deps
151
+ end
152
+ end
153
+
154
+ # Generate valid constraint paths
155
+ #
156
+ # @return [Bogo::PriorityQueue<Path>]
157
+ def generate!
158
+ custom_unit = Unit.new(
159
+ :name => '~_SOLVER_UNIT_~',
160
+ :version => '1.0.0',
161
+ :dependencies => requirements.requirements
162
+ )
163
+ count = 0
164
+ results = Bogo::PriorityQueue.new
165
+ begin
166
+ until(count > MAX_GENERATION_LOOPS)
167
+ result = resolve(nil, custom_unit)
168
+ results.push(Path.new(:units => result.slice(1, result.size)), count)
169
+ count += 1
170
+ end
171
+ rescue Error::UnitUnavailable
172
+ count = nil
173
+ end
174
+ unless(count.nil?)
175
+ raise Error::MaximumGenerationLoopsExceeded.new("Exceeded maximum allowed loops for path generation: #{MAX_GENERATION_LOOPS}")
176
+ else
177
+ if(results.empty?)
178
+ raise Error::NoSolution.new("Failed to generate valid path for requirements: `#{custom_unit.dependencies.inspect}`")
179
+ else
180
+ results
181
+ end
182
+ end
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,67 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+ # Contains all available Units
5
+ class System
6
+
7
+ # @return [Smash]
8
+ attr_reader :units
9
+
10
+ # Create new system
11
+ #
12
+ # @return [self]
13
+ def initialize(*_)
14
+ @units = Smash.new
15
+ end
16
+
17
+ # Register new unit
18
+ #
19
+ # @param unit [Unit]
20
+ # @return [self]
21
+ def add_unit(*unit)
22
+ [unit].flatten.compact.each do |u|
23
+ unless(units[u.name])
24
+ units[u.name] = []
25
+ end
26
+ units[u.name].push(u) unless units[u.name].include?(u)
27
+ end
28
+ self
29
+ end
30
+
31
+ # Remove registered unit
32
+ #
33
+ # @param unit [Unit]
34
+ # @param deps
35
+ # @return [self]
36
+ def remove_unit(unit)
37
+ if(units[unit.name])
38
+ units[unit.name].delete(unit)
39
+ end
40
+ self
41
+ end
42
+
43
+ # Provide all available units that satisfy the constraint
44
+ #
45
+ # @param unit_name [String]
46
+ # @param constraint [REQUIREMENT_CLASS]
47
+ # @return [Array<Unit>]
48
+ def subset(unit_name, constraint)
49
+ units[unit_name].find_all do |unit|
50
+ constraint.satisfied_by?(unit.version)
51
+ end
52
+ end
53
+
54
+ # Removes any duplicate units registered
55
+ # and sorts all unit lists
56
+ #
57
+ # @return [self]
58
+ def scrub!
59
+ units.values.map do |items|
60
+ items.sort!{|x,y| y.version <=> x.version}
61
+ items.uniq!
62
+ end
63
+ self
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,14 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+
5
+ # Defines a specific unit in the system
6
+ class Unit < Utility
7
+
8
+ attribute :name, String, :required => true
9
+ attribute :dependencies, DEPENDENCY_CLASS, :multiple => true, :default => [], :coerce => lambda{|val| DEPENDENCY_CLASS.new(val.first, *val.last)}
10
+ attribute :version, VERSION_CLASS, :required => true, :coerce => lambda{|val| VERSION_CLASS.new(val)}
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ require 'grimoire'
2
+
3
+ module Grimoire
4
+
5
+ # Base class for building utility objects
6
+ class Utility
7
+
8
+ # Provide lazy setup helpers
9
+ include Bogo::Lazy
10
+
11
+ # Disable data state
12
+ always_clean!
13
+
14
+ # Force load on init to enforce rules
15
+ def initialize(args={})
16
+ load_data(args)
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,4 @@
1
+ module Grimoire
2
+ # Current library version
3
+ VERSION = Gem::Version.new('0.1.0')
4
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grimoire
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Roberts
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bogo
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.1.10
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.1.10
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Specialized constraint solver allowing weighted results
56
+ email: code@chrisroberts.org
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - CHANGELOG.md
62
+ - CONTRIBUTING.md
63
+ - LICENSE
64
+ - README.md
65
+ - grimoire.gemspec
66
+ - lib/grimoire.rb
67
+ - lib/grimoire/error.rb
68
+ - lib/grimoire/path.rb
69
+ - lib/grimoire/requirement_list.rb
70
+ - lib/grimoire/solver.rb
71
+ - lib/grimoire/system.rb
72
+ - lib/grimoire/unit.rb
73
+ - lib/grimoire/utility.rb
74
+ - lib/grimoire/version.rb
75
+ homepage: https://github.com/spox/grimoire
76
+ licenses:
77
+ - Apache 2.0
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.2.2
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Constraint solver
99
+ test_files: []
100
+ has_rdoc: