ki-repo 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +26 -12
- data/VERSION +1 -1
- data/bin/ki +21 -0
- data/docs/backlog.md +35 -0
- data/docs/development.md +45 -0
- data/docs/development_setup.md +49 -0
- data/{lib/ki-repo.rb → docs/images/for_git.txt} +0 -0
- data/docs/ki_commands.md +202 -0
- data/docs/repository_basics.md +171 -0
- data/docs/writing_extensions.md +50 -0
- data/lib/cmd/cmd.rb +224 -0
- data/lib/cmd/user_pref_cmd.rb +122 -0
- data/lib/cmd/version_cmd.rb +483 -0
- data/lib/data_access/repository_finder.rb +200 -0
- data/lib/data_access/repository_info.rb +153 -0
- data/lib/data_access/version_helpers.rb +242 -0
- data/lib/data_access/version_iterators.rb +145 -0
- data/lib/data_access/version_operations.rb +80 -0
- data/lib/data_storage/dir_base.rb +106 -0
- data/lib/data_storage/ki_home.rb +44 -0
- data/lib/data_storage/ki_json.rb +153 -0
- data/lib/data_storage/repository.rb +91 -0
- data/lib/data_storage/version_metadata.rb +141 -0
- data/lib/ki_repo_all.rb +42 -0
- data/lib/util/attr_chain.rb +258 -0
- data/lib/util/exception_catcher.rb +118 -0
- data/lib/util/hash.rb +46 -0
- data/lib/util/hash_cache.rb +31 -0
- data/lib/util/ruby_extensions.rb +137 -0
- data/lib/util/service_registry.rb +88 -0
- data/lib/util/simple_optparse.rb +103 -0
- data/lib/util/test.rb +323 -0
- metadata +69 -13
- data/.document +0 -5
- data/.rspec +0 -1
- data/Gemfile +0 -14
- data/Gemfile.lock +0 -38
- data/Rakefile +0 -42
- data/spec/ki-repo_spec.rb +0 -6
- data/spec/spec_helper.rb +0 -12
@@ -0,0 +1,258 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
# Attaches configurable behaviour to accessor methods
|
18
|
+
#
|
19
|
+
# class Foo
|
20
|
+
# attr_chain :name, :require
|
21
|
+
# attr_chain :email, -> {""}
|
22
|
+
# attr_chain :birth_day, :immutable, :valid => lambda { |i| (1870..Time.now.year+1).include?(i) }, :require => true
|
23
|
+
# attr_chain :children, :convert => lambda {|s| s.to_i}
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Sets up public methods variable_name and variable_name= which both can be used to access the fields. Giving any parameters
|
27
|
+
# for the method makes it a "set" operation and giving no parameters makes it a "get" operation. "Set" stores the value
|
28
|
+
# and returns self, so set calls can be chained. "Get" returns the stored value.
|
29
|
+
#
|
30
|
+
# foo.email("test@email.com").name("test")
|
31
|
+
# foo.email => "test@email.com"
|
32
|
+
# foo.name => "test"
|
33
|
+
#
|
34
|
+
# Parameters can be given in short and long format. Short format works by identifying parameter types,
|
35
|
+
# long format works by given the name and value as hash parameters:
|
36
|
+
# * <tt>:require=>"You need to define xxx first"</tt>, <tt>:require=>true</tt>, short: <tt>:require</tt> - an exception is thrown if target field is not defined
|
37
|
+
# * <tt>:default=> -> {true}</tt>, short: <tt>-> {Array.new}</tt> - if target field has not been defined, executes proc and stores value. proc is executed using object.instance_exec: object's fields & methds are available
|
38
|
+
# * <tt>:immutable=>true</tt>, short: <tt>:immutable</tt> - an exception is thrown if target field is defined a second time
|
39
|
+
# * <tt>:valid=>[1,2,3,"a", lambda {|s| s.include?("b")}]</tt>, <tt>:valid => lambda {|s| s.include?("b")}</tt>, short: <tt>[1,2,3,"a"]</tt> - List of valid values. If any matches, sets value. If none matches, raises exception. Long form wraps single arguments to a list.
|
40
|
+
# * <tt>:convert=> ->(s) { s+1 }</tt> - Converts input value using the defined proc
|
41
|
+
# * <tt>:accessor=>InstanceVariableAccessor.new</tt> - Makes it possible to set values in other source, for example a hash. By default uses InstanceVariableAccessor
|
42
|
+
#
|
43
|
+
# Advantages for using attr_chain
|
44
|
+
# * attr_chain has a compact syntax for many important programming concepts -> less manually written boilerplate code is needed
|
45
|
+
# * :default makes it easy to isolate functionality to a default value while still making it easy to override the default behaviour
|
46
|
+
# * :default adds easy lazy evalution and memoization to the attribute, default value is evaluated only if needed
|
47
|
+
# * Testing becomes easier when objects have more exposed fields
|
48
|
+
# * :require converts tricky nil exceptions in to useful errors. Instead of the "undefined method `bar' for nil:NilClass" you get a good error message that states which field was not defined
|
49
|
+
# foo.name.bar # if name has not been defined, raises "'name' has not been set" exception
|
50
|
+
# * :immutable, :valid and :convert make complex validations and converts easy
|
51
|
+
#
|
52
|
+
# Warnings about attr_chain
|
53
|
+
# * Performance has not been measured and attr_chain is probably not efficient. If there are tight inner loops, it's better to cache the value and store it afterwards
|
54
|
+
# * There has not been tests for memory leaks. It's plain ruby so GC should take care of everything
|
55
|
+
# * Excessive attr_chain usage makes classes a mess. Try to keep your classes short and attr_chain count below 10.
|
56
|
+
# @see InstanceVariableAccessor
|
57
|
+
# @see Object.attr_chain
|
58
|
+
# @see Module#attr_chain
|
59
|
+
class AttrChain
|
60
|
+
# Parses parameters with parse_short_syntax and set_parameters and configures class methods
|
61
|
+
# * each attr_chain definition uses one instance of AttrChain which holds the configuration for the definition
|
62
|
+
# * Object::define_method is used to add two methods to target class and when called both of these methods call attr_chain with their parameters
|
63
|
+
# @see Object.attr_chain
|
64
|
+
# @see Module#attr_chain
|
65
|
+
def initialize(clazz, variable_name, attr_configs)
|
66
|
+
@variable_name = variable_name
|
67
|
+
@accessor = InstanceVariableAccess
|
68
|
+
set_parameters(variable_name, parse_short_syntax(variable_name, attr_configs))
|
69
|
+
me = self
|
70
|
+
attr_call = lambda { |*args| me.attr_chain(self, args) }
|
71
|
+
[variable_name, "#{variable_name}="].each do |method_name|
|
72
|
+
if clazz.method_defined?(method_name)
|
73
|
+
clazz.send(:undef_method, method_name)
|
74
|
+
end
|
75
|
+
clazz.send(:define_method, method_name, attr_call)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Converts short syntax entries in attr_configs to long syntax
|
80
|
+
# * warns about not supported values and already defined values
|
81
|
+
def parse_short_syntax(variable_name, attr_configs)
|
82
|
+
params = {}
|
83
|
+
attr_configs.each do |attr_config|
|
84
|
+
key_values = if [:require, :immutable].include?(attr_config)
|
85
|
+
[[attr_config, true]]
|
86
|
+
elsif attr_config.kind_of?(Proc)
|
87
|
+
[[:default, attr_config]]
|
88
|
+
elsif attr_config.kind_of?(Array)
|
89
|
+
[[:valid, attr_config]]
|
90
|
+
elsif attr_config.kind_of?(Hash)
|
91
|
+
all = []
|
92
|
+
attr_config.each_pair do |pair|
|
93
|
+
all << pair
|
94
|
+
end
|
95
|
+
all
|
96
|
+
else
|
97
|
+
raise "attr_chain :#{variable_name} unsupported parameter: '#{attr_config.inspect}'"
|
98
|
+
end
|
99
|
+
key_values.each do |key, value|
|
100
|
+
if params.include?(key)
|
101
|
+
raise "attr_chain :#{variable_name}, :#{key} was already defined to '#{params[key]}' (new value: '#{value}')"
|
102
|
+
end
|
103
|
+
params[key]=value
|
104
|
+
end
|
105
|
+
end
|
106
|
+
params
|
107
|
+
end
|
108
|
+
|
109
|
+
# Parses long syntax values and sets configuration for this field
|
110
|
+
def set_parameters(variable_name, params)
|
111
|
+
params.each_pair do |key, value|
|
112
|
+
case key
|
113
|
+
when :require
|
114
|
+
@require = value
|
115
|
+
when :default
|
116
|
+
if !value.kind_of?(Proc)
|
117
|
+
raise "attr_chain :#{variable_name}, :default needs to be a Proc, not '#{value.inspect}'"
|
118
|
+
end
|
119
|
+
@default = value
|
120
|
+
when :immutable
|
121
|
+
@immutable = value
|
122
|
+
when :valid
|
123
|
+
if !value.kind_of?(Array)
|
124
|
+
value = [value]
|
125
|
+
end
|
126
|
+
value.each do |valid|
|
127
|
+
if valid.kind_of?(Proc)
|
128
|
+
@valid_procs ||= []
|
129
|
+
@valid_procs << valid
|
130
|
+
else
|
131
|
+
@valid_items ||= {}
|
132
|
+
@valid_items[valid]=valid
|
133
|
+
end
|
134
|
+
end
|
135
|
+
when :convert
|
136
|
+
if !value.kind_of?(Proc)
|
137
|
+
raise "attr_chain :#{variable_name}, :convert needs to be a Proc, not '#{value.inspect}'"
|
138
|
+
end
|
139
|
+
@convert = value
|
140
|
+
when :accessor
|
141
|
+
@accessor = value
|
142
|
+
else
|
143
|
+
raise "attr_chain :#{variable_name} unsupported parameter: '#{key.inspect}'"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Handles incoming methods for "get" and "set"
|
149
|
+
# * called by methods defined to class
|
150
|
+
# * configuration is stored as instance variables, the class knows which variable is being handled
|
151
|
+
# * method call parameters come as list of parameters
|
152
|
+
def attr_chain(object, args)
|
153
|
+
if args.empty?
|
154
|
+
if !@accessor.defined?(object, @variable_name)
|
155
|
+
if defined? @default
|
156
|
+
@accessor.set(object, @variable_name, object.instance_exec(&@default))
|
157
|
+
elsif defined? @require
|
158
|
+
if @require.kind_of?(String)
|
159
|
+
raise "'#{@variable_name}' has not been set: #{@require}"
|
160
|
+
else
|
161
|
+
raise "'#{@variable_name}' has not been set"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
@accessor.get(object, @variable_name)
|
166
|
+
else
|
167
|
+
if defined?(@immutable) && @accessor.defined?(object, @variable_name)
|
168
|
+
raise "'#{@variable_name}' has been set once already"
|
169
|
+
end
|
170
|
+
value_to_set = if args.size == 1
|
171
|
+
args.first
|
172
|
+
else
|
173
|
+
args
|
174
|
+
end
|
175
|
+
if defined? @convert
|
176
|
+
value_to_set = object.instance_exec(value_to_set, &@convert)
|
177
|
+
end
|
178
|
+
if defined?(@valid_items) || defined?(@valid_procs)
|
179
|
+
is_valid = false
|
180
|
+
if defined?(@valid_items) && @valid_items.include?(value_to_set)
|
181
|
+
is_valid = true
|
182
|
+
end
|
183
|
+
if is_valid == false && defined?(@valid_procs)
|
184
|
+
@valid_procs.each do |valid_proc|
|
185
|
+
if is_valid=object.instance_exec(value_to_set, &valid_proc)
|
186
|
+
break
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
if is_valid == false
|
191
|
+
raise "invalid value for '#{@variable_name}'"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
@accessor.set(object, @variable_name, value_to_set)
|
195
|
+
object
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Wrapper for Object::instance_variable_get, Object::instance_variable_set and Object::instance_variable_defined?
|
200
|
+
class InstanceVariableAccessor
|
201
|
+
def edit_name(variable_name)
|
202
|
+
"@#{variable_name}".to_sym
|
203
|
+
end
|
204
|
+
|
205
|
+
def get(object, name)
|
206
|
+
n = edit_name(name)
|
207
|
+
if object.instance_variable_defined?(n)
|
208
|
+
object.instance_variable_get(n)
|
209
|
+
else
|
210
|
+
nil
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def set(object, name, value)
|
215
|
+
object.instance_variable_set(edit_name(name), value)
|
216
|
+
end
|
217
|
+
|
218
|
+
def defined?(object, name)
|
219
|
+
object.instance_variable_defined?(edit_name(name))
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
InstanceVariableAccess = InstanceVariableAccessor.new
|
224
|
+
|
225
|
+
class HashAccessor
|
226
|
+
def get(object, name)
|
227
|
+
object[name.to_s]
|
228
|
+
end
|
229
|
+
|
230
|
+
def set(object, name, value)
|
231
|
+
object[name.to_s] = value
|
232
|
+
end
|
233
|
+
|
234
|
+
def defined?(object, name)
|
235
|
+
object.include?(name.to_s)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
HashAccess = HashAccessor.new
|
240
|
+
end
|
241
|
+
|
242
|
+
class Object
|
243
|
+
# Configurable accessor methods
|
244
|
+
# @see AttrChain
|
245
|
+
# @return [void]
|
246
|
+
def self.attr_chain(variable_name, *attr_configs)
|
247
|
+
AttrChain.new(self, variable_name, attr_configs)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class Module
|
252
|
+
# When a module defines an attr_chain, the attr_chain methods are available to all classes that are extended with the module
|
253
|
+
# @see AttrChain
|
254
|
+
# @return [void]
|
255
|
+
def attr_chain(variable_name, *attr_configs)
|
256
|
+
AttrChain.new(self, variable_name, attr_configs)
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'thread'
|
18
|
+
|
19
|
+
# ExceptionCatcher makes it easy to execute multiple operations even though any might fail with exception
|
20
|
+
# * executed tasks can be named to make it easier to identify which tasks failed
|
21
|
+
# * collects result and exceptions for each executed task
|
22
|
+
# * raises a {MultipleExceptions} exception if there were more than one exception
|
23
|
+
#
|
24
|
+
# catcher = ExceptionCatcher.new
|
25
|
+
# catcher.catch(1){raise "1"}
|
26
|
+
# catcher.catch(2){raise "2"}
|
27
|
+
# catcher.catch(3){"ok!"}
|
28
|
+
# catcher.exception(1) # -> exception object raised by the task 1
|
29
|
+
# catcher.exception(2) # -> exception object raise by the task 2
|
30
|
+
# catcher.result(3) # -> "ok!"
|
31
|
+
# catcher.check # raises a MultipleExceptions exception
|
32
|
+
#
|
33
|
+
class ExceptionCatcher
|
34
|
+
attr_reader :tasks, :results, :exceptions, :mutex
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@mutex = Mutex.new
|
38
|
+
@results = {}
|
39
|
+
@exceptions = {}
|
40
|
+
@tasks = []
|
41
|
+
end
|
42
|
+
|
43
|
+
# Catches exceptions thrown by block
|
44
|
+
# @param [Object, nil] task if task is defined, results can be checked per task
|
45
|
+
# @param [Proc] block, block which is executed
|
46
|
+
# @return [Object, nil] returns block's result or nil if block raised an exception
|
47
|
+
def catch(task=nil, &block)
|
48
|
+
if task.nil?
|
49
|
+
task = block
|
50
|
+
end
|
51
|
+
@mutex.synchronize do
|
52
|
+
@tasks << task
|
53
|
+
end
|
54
|
+
begin
|
55
|
+
result = block.call
|
56
|
+
@mutex.synchronize do
|
57
|
+
@results[task]=result
|
58
|
+
end
|
59
|
+
result
|
60
|
+
rescue Exception => e
|
61
|
+
@mutex.synchronize do
|
62
|
+
@exceptions[task]=e
|
63
|
+
end
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param [Object] task identifies the executed task
|
69
|
+
# @return [Object,nil] result for the named block or nil if the block ended in exception
|
70
|
+
def result(task)
|
71
|
+
@mutex.synchronize do
|
72
|
+
@results[task]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param [Object] task identifies the executed task
|
77
|
+
# @return [Object,nil] block's exception or nil if the block did not raise an exception
|
78
|
+
def exception(task)
|
79
|
+
@mutex.synchronize do
|
80
|
+
@exceptions[task]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [bool] exceptions? returns true if there has been exceptions
|
85
|
+
def exceptions?
|
86
|
+
@exceptions.size > 0
|
87
|
+
end
|
88
|
+
|
89
|
+
# Checks if there has been exceptions and raises the original exception if there has been one exception and {MultipleExceptions} if there has been many exceptions
|
90
|
+
def check
|
91
|
+
@mutex.synchronize do
|
92
|
+
if @exceptions.size == 1
|
93
|
+
e = @exceptions.values.first
|
94
|
+
raise e.class, e.message, e.backtrace
|
95
|
+
elsif @exceptions.size > 1
|
96
|
+
raise MultipleExceptions.new("Caught #{@exceptions.size} exceptions!").catcher(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# {ExceptionCatcher} raises MultipleExceptions if it has caught multiple exceptions.
|
102
|
+
# MultipleExceptions makes the {ExceptionCatcher} and its results available
|
103
|
+
class MultipleExceptions < RuntimeError
|
104
|
+
include Enumerable
|
105
|
+
# Reference to original {#ExceptionCatcher}
|
106
|
+
attr_chain :catcher, :require
|
107
|
+
# Map of exceptions
|
108
|
+
attr_chain :exceptions, -> { catcher.exceptions }
|
109
|
+
# Map of tasks that have been scheduled
|
110
|
+
attr_chain :tasks, -> { catcher.tasks }
|
111
|
+
|
112
|
+
def each(&block)
|
113
|
+
exceptions.values.each(&block)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
|
data/lib/util/hash.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'digest/sha1'
|
18
|
+
require 'digest/sha2'
|
19
|
+
require 'digest/md5'
|
20
|
+
|
21
|
+
module Ki
|
22
|
+
|
23
|
+
# SHA1, uses standard Ruby library
|
24
|
+
class SHA1
|
25
|
+
# SHA1, uses standard Ruby library
|
26
|
+
def SHA1.digest
|
27
|
+
Digest::SHA1.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# SHA2, uses standard Ruby library
|
32
|
+
class SHA2
|
33
|
+
# SHA2, uses standard Ruby library
|
34
|
+
def SHA2.digest
|
35
|
+
Digest::SHA2.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# MD5, uses standard Ruby library
|
40
|
+
class MD5
|
41
|
+
# MD5, uses standard Ruby library
|
42
|
+
def MD5.digest
|
43
|
+
Digest::MD5.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Ki
|
18
|
+
# Caching Hash, resolves values at request time
|
19
|
+
class HashCache < Hash
|
20
|
+
# If key has not been defined, uses block to resolve the value. Value is stored and returned
|
21
|
+
# @param key Key
|
22
|
+
# @param [Proc] block Block which is evaluated if the key does not have value yet. Block's value is stored to hash
|
23
|
+
# @return Existing value or one resolved with the block
|
24
|
+
def cache(key, &block)
|
25
|
+
if !include?(key)
|
26
|
+
store(key, block.call)
|
27
|
+
end
|
28
|
+
self[key]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module Ki
|
18
|
+
module KiEnumerable
|
19
|
+
def size!(*args)
|
20
|
+
args.each do |valid_size|
|
21
|
+
if valid_size.kind_of?(Range)
|
22
|
+
if valid_size.include?(size)
|
23
|
+
return self
|
24
|
+
end
|
25
|
+
elsif valid_size.respond_to?(:to_i)
|
26
|
+
if Integer(valid_size) == size
|
27
|
+
return self
|
28
|
+
end
|
29
|
+
else
|
30
|
+
raise "'#{valid_size.inspect}' not supported, needs to be either Range or have .to_i method"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
raise "size #{size} does not match '#{args.map { |s| s.to_s }.join("', '")}'"
|
34
|
+
end
|
35
|
+
|
36
|
+
def any_matches?(value)
|
37
|
+
each do |pattern|
|
38
|
+
if value.match(pattern)
|
39
|
+
return pattern
|
40
|
+
end
|
41
|
+
end
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_first(count=1, &block)
|
46
|
+
ret = []
|
47
|
+
each do |item|
|
48
|
+
if block.nil? || block.call(item)
|
49
|
+
ret << item
|
50
|
+
if ret.size == count
|
51
|
+
break
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
if count==1
|
56
|
+
ret.at(0)
|
57
|
+
else
|
58
|
+
ret
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_h(separator=nil, &block)
|
63
|
+
ret = {}
|
64
|
+
each do |item|
|
65
|
+
if separator
|
66
|
+
key, *values = item.split(separator)
|
67
|
+
if values.size > 0 || item.include?(separator)
|
68
|
+
ret[key]=values.join(separator)
|
69
|
+
else
|
70
|
+
ret[key]=true
|
71
|
+
end
|
72
|
+
elsif block
|
73
|
+
key, value = block.call(item)
|
74
|
+
ret[key]=value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
ret
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class Array
|
84
|
+
include Ki::KiEnumerable
|
85
|
+
|
86
|
+
def Array.wrap(maybe_arr)
|
87
|
+
if maybe_arr.kind_of?(Array)
|
88
|
+
maybe_arr
|
89
|
+
else
|
90
|
+
[maybe_arr]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
module Enumerable
|
96
|
+
include Ki::KiEnumerable
|
97
|
+
end
|
98
|
+
|
99
|
+
require 'fileutils'
|
100
|
+
class File
|
101
|
+
def File.safe_write(dest, txt=nil, &block)
|
102
|
+
tmp = dest + "-" + rand(9999).to_s
|
103
|
+
begin
|
104
|
+
File.open(tmp, "w") do |file|
|
105
|
+
if block
|
106
|
+
block.call(file)
|
107
|
+
elsif txt
|
108
|
+
file.write(txt)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
FileUtils.mv(tmp, dest)
|
112
|
+
rescue Exception => e
|
113
|
+
FileUtils.remove_entry_secure(tmp)
|
114
|
+
raise
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class Hash
|
120
|
+
original_get = self.instance_method(:[])
|
121
|
+
|
122
|
+
define_method(:[]) do |key, default=nil|
|
123
|
+
value = original_get.bind(self).call(key)
|
124
|
+
if value || include?(key)
|
125
|
+
value
|
126
|
+
else
|
127
|
+
default
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def require(key)
|
132
|
+
if !include?(key)
|
133
|
+
raise "'#{key}' is not defined!"
|
134
|
+
end
|
135
|
+
self[key]
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# Copyright 2012 Mikko Apo
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'monitor'
|
18
|
+
|
19
|
+
module Ki
|
20
|
+
class ServiceRegistry < Hash
|
21
|
+
attr_reader :by_parent
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@monitor = Monitor.new
|
25
|
+
@by_parent = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def register(*args)
|
29
|
+
@monitor.synchronize do
|
30
|
+
case args.size
|
31
|
+
when 1
|
32
|
+
args.first.each_pair do |url, clazz|
|
33
|
+
register(url, clazz)
|
34
|
+
end
|
35
|
+
when 2
|
36
|
+
url, clazz = args
|
37
|
+
self[url]=clazz
|
38
|
+
(@by_parent[File.dirname(url)]||=Array.new) << args
|
39
|
+
else
|
40
|
+
raise "Not supported '#{args.inspect}'"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def find(url, value=nil)
|
47
|
+
@monitor.synchronize do
|
48
|
+
if include?(url)
|
49
|
+
self[url]
|
50
|
+
elsif @by_parent.include?(url)
|
51
|
+
services = @by_parent[url]
|
52
|
+
if services
|
53
|
+
if value
|
54
|
+
services = services.select { |id, service| service.supports?(value) }
|
55
|
+
end
|
56
|
+
services = ServiceList.new.concat(services)
|
57
|
+
end
|
58
|
+
services
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def find!(url, value=nil)
|
64
|
+
found = find(url, value)
|
65
|
+
if found.nil?
|
66
|
+
raise "Could not resolve '#{url}'"
|
67
|
+
end
|
68
|
+
found
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear
|
72
|
+
@monitor.synchronize do
|
73
|
+
@by_parent.clear
|
74
|
+
super
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class ServiceList < Array
|
79
|
+
def services
|
80
|
+
map { |url, service| service }
|
81
|
+
end
|
82
|
+
|
83
|
+
def service_names
|
84
|
+
map { |url, service| File.basename(url) }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|