chef-zero 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README.rdoc +79 -0
- data/Rakefile +19 -0
- data/bin/chef-zero +40 -0
- data/lib/chef_zero.rb +5 -0
- data/lib/chef_zero/cookbook_data.rb +110 -0
- data/lib/chef_zero/data_normalizer.rb +129 -0
- data/lib/chef_zero/endpoints/actor_endpoint.rb +68 -0
- data/lib/chef_zero/endpoints/actors_endpoint.rb +32 -0
- data/lib/chef_zero/endpoints/authenticate_user_endpoint.rb +21 -0
- data/lib/chef_zero/endpoints/cookbook_endpoint.rb +39 -0
- data/lib/chef_zero/endpoints/cookbook_version_endpoint.rb +106 -0
- data/lib/chef_zero/endpoints/cookbooks_base.rb +59 -0
- data/lib/chef_zero/endpoints/cookbooks_endpoint.rb +12 -0
- data/lib/chef_zero/endpoints/data_bag_endpoint.rb +50 -0
- data/lib/chef_zero/endpoints/data_bag_item_endpoint.rb +25 -0
- data/lib/chef_zero/endpoints/data_bags_endpoint.rb +21 -0
- data/lib/chef_zero/endpoints/environment_cookbook_endpoint.rb +24 -0
- data/lib/chef_zero/endpoints/environment_cookbook_versions_endpoint.rb +114 -0
- data/lib/chef_zero/endpoints/environment_cookbooks_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_endpoint.rb +33 -0
- data/lib/chef_zero/endpoints/environment_nodes_endpoint.rb +23 -0
- data/lib/chef_zero/endpoints/environment_recipes_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/environment_role_endpoint.rb +35 -0
- data/lib/chef_zero/endpoints/file_store_file_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/node_endpoint.rb +17 -0
- data/lib/chef_zero/endpoints/not_found_endpoint.rb +9 -0
- data/lib/chef_zero/endpoints/principal_endpoint.rb +30 -0
- data/lib/chef_zero/endpoints/rest_list_endpoint.rb +41 -0
- data/lib/chef_zero/endpoints/rest_object_endpoint.rb +65 -0
- data/lib/chef_zero/endpoints/role_endpoint.rb +16 -0
- data/lib/chef_zero/endpoints/role_environments_endpoint.rb +14 -0
- data/lib/chef_zero/endpoints/sandbox_endpoint.rb +22 -0
- data/lib/chef_zero/endpoints/sandboxes_endpoint.rb +44 -0
- data/lib/chef_zero/endpoints/search_endpoint.rb +139 -0
- data/lib/chef_zero/endpoints/searches_endpoint.rb +18 -0
- data/lib/chef_zero/rest_base.rb +82 -0
- data/lib/chef_zero/rest_error_response.rb +11 -0
- data/lib/chef_zero/rest_request.rb +42 -0
- data/lib/chef_zero/router.rb +26 -0
- data/lib/chef_zero/server.rb +255 -0
- data/lib/chef_zero/solr/query/binary_operator.rb +53 -0
- data/lib/chef_zero/solr/query/phrase.rb +23 -0
- data/lib/chef_zero/solr/query/range_query.rb +34 -0
- data/lib/chef_zero/solr/query/regexpable_query.rb +29 -0
- data/lib/chef_zero/solr/query/subquery.rb +35 -0
- data/lib/chef_zero/solr/query/term.rb +45 -0
- data/lib/chef_zero/solr/query/unary_operator.rb +43 -0
- data/lib/chef_zero/solr/solr_doc.rb +62 -0
- data/lib/chef_zero/solr/solr_parser.rb +194 -0
- data/lib/chef_zero/version.rb +3 -0
- metadata +132 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rack/request'
|
2
|
+
|
3
|
+
module ChefZero
|
4
|
+
class RestRequest
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :env
|
10
|
+
|
11
|
+
def base_uri
|
12
|
+
@base_uri ||= "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{env['SCRIPT_NAME']}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def method
|
16
|
+
@env['REQUEST_METHOD']
|
17
|
+
end
|
18
|
+
|
19
|
+
def rest_path
|
20
|
+
@rest_path ||= env['PATH_INFO'].split('/').select { |part| part != "" }
|
21
|
+
end
|
22
|
+
|
23
|
+
def body=(body)
|
24
|
+
@body = body
|
25
|
+
end
|
26
|
+
|
27
|
+
def body
|
28
|
+
@body ||= env['rack.input'].read
|
29
|
+
end
|
30
|
+
|
31
|
+
def query_params
|
32
|
+
@query_params ||= begin
|
33
|
+
params = Rack::Request.new(env).GET
|
34
|
+
params.keys.each do |key|
|
35
|
+
params[key] = URI.unescape(params[key])
|
36
|
+
end
|
37
|
+
params
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'chef/log'
|
2
|
+
|
3
|
+
module ChefZero
|
4
|
+
class Router
|
5
|
+
def initialize(routes)
|
6
|
+
@routes = routes.map do |route, endpoint|
|
7
|
+
pattern = Regexp.new("^#{route.gsub('*', '[^/]*')}$")
|
8
|
+
[ pattern, endpoint ]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :routes
|
13
|
+
attr_accessor :not_found
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
Chef::Log.debug "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}#{env['QUERY_STRING'] != '' ? "?" + env['QUERY_STRING'] : ''}"
|
17
|
+
clean_path = "/" + env['PATH_INFO'].split('/').select { |part| part != "" }.join("/")
|
18
|
+
routes.each do |route, endpoint|
|
19
|
+
if route.match(clean_path)
|
20
|
+
return endpoint.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
not_found.call(env)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
#
|
2
|
+
# Author:: John Keiser (<jkeiser@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2012 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'rubygems'
|
20
|
+
require 'thin'
|
21
|
+
require 'openssl'
|
22
|
+
require 'chef_zero'
|
23
|
+
require 'chef_zero/router'
|
24
|
+
require 'timeout'
|
25
|
+
require 'chef_zero/cookbook_data'
|
26
|
+
|
27
|
+
require 'chef_zero/endpoints/authenticate_user_endpoint'
|
28
|
+
require 'chef_zero/endpoints/actors_endpoint'
|
29
|
+
require 'chef_zero/endpoints/actor_endpoint'
|
30
|
+
require 'chef_zero/endpoints/cookbooks_endpoint'
|
31
|
+
require 'chef_zero/endpoints/cookbook_endpoint'
|
32
|
+
require 'chef_zero/endpoints/cookbook_version_endpoint'
|
33
|
+
require 'chef_zero/endpoints/data_bags_endpoint'
|
34
|
+
require 'chef_zero/endpoints/data_bag_endpoint'
|
35
|
+
require 'chef_zero/endpoints/data_bag_item_endpoint'
|
36
|
+
require 'chef_zero/endpoints/rest_list_endpoint'
|
37
|
+
require 'chef_zero/endpoints/environment_endpoint'
|
38
|
+
require 'chef_zero/endpoints/environment_cookbooks_endpoint'
|
39
|
+
require 'chef_zero/endpoints/environment_cookbook_endpoint'
|
40
|
+
require 'chef_zero/endpoints/environment_cookbook_versions_endpoint'
|
41
|
+
require 'chef_zero/endpoints/environment_nodes_endpoint'
|
42
|
+
require 'chef_zero/endpoints/environment_recipes_endpoint'
|
43
|
+
require 'chef_zero/endpoints/environment_role_endpoint'
|
44
|
+
require 'chef_zero/endpoints/node_endpoint'
|
45
|
+
require 'chef_zero/endpoints/principal_endpoint'
|
46
|
+
require 'chef_zero/endpoints/role_endpoint'
|
47
|
+
require 'chef_zero/endpoints/role_environments_endpoint'
|
48
|
+
require 'chef_zero/endpoints/sandboxes_endpoint'
|
49
|
+
require 'chef_zero/endpoints/sandbox_endpoint'
|
50
|
+
require 'chef_zero/endpoints/searches_endpoint'
|
51
|
+
require 'chef_zero/endpoints/search_endpoint'
|
52
|
+
require 'chef_zero/endpoints/file_store_file_endpoint'
|
53
|
+
require 'chef_zero/endpoints/not_found_endpoint'
|
54
|
+
|
55
|
+
module ChefZero
|
56
|
+
class Server
|
57
|
+
def initialize(options = {})
|
58
|
+
@options = options
|
59
|
+
options[:host] ||= '127.0.0.1'
|
60
|
+
options[:port] ||= 80
|
61
|
+
options[:generate_real_keys] = true if !options.has_key?(:generate_real_keys)
|
62
|
+
@server = Thin::Server.new(options[:host], options[:port], make_app)
|
63
|
+
@generate_real_keys = options[:generate_real_keys]
|
64
|
+
@data = {
|
65
|
+
'clients' => {
|
66
|
+
'chef-validator' => '{ "validator": true }',
|
67
|
+
'chef-webui' => '{ "admin": true }'
|
68
|
+
},
|
69
|
+
'cookbooks' => {},
|
70
|
+
'data' => {},
|
71
|
+
'environments' => {
|
72
|
+
'_default' => '{ "description": "The default Chef environment" }'
|
73
|
+
},
|
74
|
+
'file_store' => {},
|
75
|
+
'nodes' => {},
|
76
|
+
'roles' => {},
|
77
|
+
'sandboxes' => {},
|
78
|
+
'users' => {
|
79
|
+
'admin' => '{ "admin": true }'
|
80
|
+
}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_reader :server
|
85
|
+
attr_reader :data
|
86
|
+
attr_reader :options
|
87
|
+
attr_reader :generate_real_keys
|
88
|
+
|
89
|
+
include ChefZero::Endpoints
|
90
|
+
|
91
|
+
def url
|
92
|
+
"http://#{options[:host]}:#{options[:port]}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def start
|
96
|
+
server.start
|
97
|
+
end
|
98
|
+
|
99
|
+
def start_background(timeout = 5)
|
100
|
+
@thread = Thread.new do
|
101
|
+
begin
|
102
|
+
server.start
|
103
|
+
rescue
|
104
|
+
@server_error = $!
|
105
|
+
Chef::Log.error("#{$!.message}\n#{$!.backtrace.join("\n")}")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
Timeout::timeout(timeout) do
|
109
|
+
until server.running? || @server_error
|
110
|
+
sleep(0.01)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def running?
|
116
|
+
server.running?
|
117
|
+
end
|
118
|
+
|
119
|
+
def stop(timeout = 5)
|
120
|
+
Timeout::timeout(timeout) do
|
121
|
+
server.stop
|
122
|
+
until !server.running? || !server_error
|
123
|
+
sleep(0.01)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
if @thread
|
127
|
+
@thread.kill
|
128
|
+
@thread = nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def gen_key_pair
|
133
|
+
if generate_real_keys
|
134
|
+
private_key = OpenSSL::PKey::RSA.new(2048)
|
135
|
+
public_key = private_key.public_key.to_s
|
136
|
+
public_key.sub!(/^-----BEGIN RSA PUBLIC KEY-----/, '-----BEGIN PUBLIC KEY-----')
|
137
|
+
public_key.sub!(/-----END RSA PUBLIC KEY-----(\s+)$/, '-----END PUBLIC KEY-----\1')
|
138
|
+
[private_key.to_s, public_key]
|
139
|
+
else
|
140
|
+
[PRIVATE_KEY, PUBLIC_KEY]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Load data in a nice, friendly form:
|
145
|
+
# {
|
146
|
+
# 'roles' => {
|
147
|
+
# 'desert' => '{ "description": "Hot and dry"' },
|
148
|
+
# 'rainforest' => { "description" => 'Wet and humid' }
|
149
|
+
# },
|
150
|
+
# 'cookbooks' => {
|
151
|
+
# 'apache2-1.0.1' => {
|
152
|
+
# 'templates' => { 'default' => { 'blah.txt' => 'hi' }}
|
153
|
+
# 'recipes' => { 'default.rb' => 'template "blah.txt"' }
|
154
|
+
# 'metadata.rb' => 'depends "mysql"'
|
155
|
+
# },
|
156
|
+
# 'apache2-1.2.0' => {
|
157
|
+
# 'templates' => { 'default' => { 'blah.txt' => 'lo' }}
|
158
|
+
# 'recipes' => { 'default.rb' => 'template "blah.txt"' }
|
159
|
+
# 'metadata.rb' => 'depends "mysql"'
|
160
|
+
# },
|
161
|
+
# 'mysql' => {
|
162
|
+
# 'recipes' => { 'default.rb' => 'file { contents "hi" }' },
|
163
|
+
# 'metadata.rb' => 'version "1.0.0"'
|
164
|
+
# }
|
165
|
+
# }
|
166
|
+
# }
|
167
|
+
def load_data(contents)
|
168
|
+
%w(clients environments nodes roles users).each do |data_type|
|
169
|
+
if contents[data_type]
|
170
|
+
dejsonize_children!(contents[data_type])
|
171
|
+
data[data_type].merge!(contents[data_type])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
if contents['data']
|
175
|
+
contents['data'].values.each do |data_bag|
|
176
|
+
dejsonize_children!(data_bag)
|
177
|
+
end
|
178
|
+
data['data'].merge!(contents['data'])
|
179
|
+
end
|
180
|
+
if contents['cookbooks']
|
181
|
+
contents['cookbooks'].each_pair do |name_version, cookbook|
|
182
|
+
if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
|
183
|
+
cookbook_data = CookbookData.to_hash(cookbook, $1, $2)
|
184
|
+
else
|
185
|
+
cookbook_data = CookbookData.to_hash(cookbook, name_version)
|
186
|
+
end
|
187
|
+
raise "No version specified" if !cookbook_data[:version]
|
188
|
+
data['cookbooks'][cookbook_data[:cookbook_name]] = {} if !data['cookbooks'][cookbook_data[:cookbook_name]]
|
189
|
+
data['cookbooks'][cookbook_data[:cookbook_name]][cookbook_data[:version]] = JSON.pretty_generate(cookbook_data)
|
190
|
+
cookbook_data.values.each do |files|
|
191
|
+
next unless files.is_a? Array
|
192
|
+
files.each do |file|
|
193
|
+
data['file_store'][file[:checksum]] = get_file(cookbook, file[:path])
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def make_app
|
203
|
+
router = Router.new([
|
204
|
+
[ '/authenticate_user', AuthenticateUserEndpoint.new(self) ],
|
205
|
+
[ '/clients', ActorsEndpoint.new(self) ],
|
206
|
+
[ '/clients/*', ActorEndpoint.new(self) ],
|
207
|
+
[ '/cookbooks', CookbooksEndpoint.new(self) ],
|
208
|
+
[ '/cookbooks/*', CookbookEndpoint.new(self) ],
|
209
|
+
[ '/cookbooks/*/*', CookbookVersionEndpoint.new(self) ],
|
210
|
+
[ '/data', DataBagsEndpoint.new(self) ],
|
211
|
+
[ '/data/*', DataBagEndpoint.new(self) ],
|
212
|
+
[ '/data/*/*', DataBagItemEndpoint.new(self) ],
|
213
|
+
[ '/environments', RestListEndpoint.new(self) ],
|
214
|
+
[ '/environments/*', EnvironmentEndpoint.new(self) ],
|
215
|
+
[ '/environments/*/cookbooks', EnvironmentCookbooksEndpoint.new(self) ],
|
216
|
+
[ '/environments/*/cookbooks/*', EnvironmentCookbookEndpoint.new(self) ],
|
217
|
+
[ '/environments/*/cookbook_versions', EnvironmentCookbookVersionsEndpoint.new(self) ],
|
218
|
+
[ '/environments/*/nodes', EnvironmentNodesEndpoint.new(self) ],
|
219
|
+
[ '/environments/*/recipes', EnvironmentRecipesEndpoint.new(self) ],
|
220
|
+
[ '/environments/*/roles/*', EnvironmentRoleEndpoint.new(self) ],
|
221
|
+
[ '/nodes', RestListEndpoint.new(self) ],
|
222
|
+
[ '/nodes/*', NodeEndpoint.new(self) ],
|
223
|
+
[ '/principals/*', PrincipalEndpoint.new(self) ],
|
224
|
+
[ '/roles', RestListEndpoint.new(self) ],
|
225
|
+
[ '/roles/*', RoleEndpoint.new(self) ],
|
226
|
+
[ '/roles/*/environments', RoleEnvironmentsEndpoint.new(self) ],
|
227
|
+
[ '/roles/*/environments/*', EnvironmentRoleEndpoint.new(self) ],
|
228
|
+
[ '/sandboxes', SandboxesEndpoint.new(self) ],
|
229
|
+
[ '/sandboxes/*', SandboxEndpoint.new(self) ],
|
230
|
+
[ '/search', SearchesEndpoint.new(self) ],
|
231
|
+
[ '/search/*', SearchEndpoint.new(self) ],
|
232
|
+
[ '/users', ActorsEndpoint.new(self) ],
|
233
|
+
[ '/users/*', ActorEndpoint.new(self) ],
|
234
|
+
|
235
|
+
[ '/file_store/*', FileStoreFileEndpoint.new(self) ],
|
236
|
+
])
|
237
|
+
router.not_found = NotFoundEndpoint.new
|
238
|
+
router
|
239
|
+
end
|
240
|
+
|
241
|
+
def dejsonize_children!(hash)
|
242
|
+
hash.each_pair do |key, value|
|
243
|
+
hash[key] = JSON.pretty_generate(value) if value.is_a?(Hash)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def get_file(directory, path)
|
248
|
+
value = directory
|
249
|
+
path.split('/').each do |part|
|
250
|
+
value = value[part]
|
251
|
+
end
|
252
|
+
value
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
module Query
|
4
|
+
class BinaryOperator
|
5
|
+
def initialize(left, operator, right)
|
6
|
+
@left = left
|
7
|
+
@operator = operator
|
8
|
+
@right = right
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"(#{left} #{operator} #{right})"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :left
|
16
|
+
attr_reader :operator
|
17
|
+
attr_reader :right
|
18
|
+
|
19
|
+
def matches_doc?(doc)
|
20
|
+
case @operator
|
21
|
+
when 'AND'
|
22
|
+
left.matches_doc?(doc) && right.matches_doc?(doc)
|
23
|
+
when 'OR'
|
24
|
+
left.matches_doc?(doc) || right.matches_doc?(doc)
|
25
|
+
when '^'
|
26
|
+
left.matches_doc?(doc)
|
27
|
+
when ':'
|
28
|
+
if left.respond_to?(:literal_string) && left.literal_string
|
29
|
+
value = doc[left.literal_string]
|
30
|
+
right.matches_values?([value])
|
31
|
+
else
|
32
|
+
values = doc.matching_values { |key| left.matches_values?([key]) }
|
33
|
+
right.matches_values?(values)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def matches_values?(values)
|
39
|
+
case @operator
|
40
|
+
when 'AND'
|
41
|
+
left.matches_values?(values) && right.matches_values?(values)
|
42
|
+
when 'OR'
|
43
|
+
left.matches_values?(values) || right.matches_values?(values)
|
44
|
+
when '^'
|
45
|
+
left.matches_values?(values)
|
46
|
+
when ':'
|
47
|
+
raise ": does not work inside a : or term"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'chef_zero/solr/query/regexpable_query'
|
2
|
+
|
3
|
+
module ChefZero
|
4
|
+
module Solr
|
5
|
+
module Query
|
6
|
+
class Phrase < RegexpableQuery
|
7
|
+
def initialize(terms)
|
8
|
+
# Phrase is terms separated by whitespace
|
9
|
+
if terms.size == 0 && terms[0].literal_string
|
10
|
+
literal_string = terms[0].literal_string
|
11
|
+
else
|
12
|
+
literal_string = nil
|
13
|
+
end
|
14
|
+
super(terms.map { |term| term.regexp_string }.join("#{NON_WORD_CHARACTER}+"), literal_string)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"Phrase(\"#{@regexp_string}\")"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ChefZero
|
2
|
+
module Solr
|
3
|
+
module Query
|
4
|
+
class RangeQuery
|
5
|
+
def initialize(from, to, from_inclusive, to_inclusive)
|
6
|
+
@from = from
|
7
|
+
@to = to
|
8
|
+
@from_inclusive = from_inclusive
|
9
|
+
@to_inclusive = to_inclusive
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
"#{@from_inclusive ? '[' : '{'}#{@from} TO #{@to}#{@to_inclusive ? '[' : '{'}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def matches?(key, value)
|
17
|
+
case @from <=> value
|
18
|
+
when -1
|
19
|
+
return false
|
20
|
+
when 0
|
21
|
+
return false if !@from_inclusive
|
22
|
+
end
|
23
|
+
case @to <=> value
|
24
|
+
when 1
|
25
|
+
return false
|
26
|
+
when 0
|
27
|
+
return false if !@to_inclusive
|
28
|
+
end
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|