ki-repo 0.1.0 → 0.1.1
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/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
|