libis-tools 0.9.12 → 0.9.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +63 -2
- data/lib/libis/tools/parameter.rb +80 -57
- data/lib/libis/tools/version.rb +1 -1
- data/libis-tools.gemspec +0 -1
- data/spec/parameter_container_spec.rb +53 -13
- data/spec/parameter_spec.rb +1 -1
- metadata +3 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22d6fc69dcf878e87ea28eb0fbfd5965b7d2f15b
|
4
|
+
data.tar.gz: 48b5bf0cfdbe91755bb535132ad0efe76b29a116
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d90037281c4f2972f18bd44126efac4a1de466a4ea248f5f4e43aed724773f4badee3b66192620171f664ca65cf16fd91d3b6541974b8a152bf02192aef1236
|
7
|
+
data.tar.gz: fc29123e0c225e5b0755e32948c44f3a080d1697718b9a6ba2cf7e648fac341f134e5905280eb3d1d73fe8413c138cf09d17bc21000a9fe7d575adae9539b4a3
|
data/README.md
CHANGED
@@ -316,9 +316,70 @@ into the internal structure. A MarcRecord is created by supplying it an XML node
|
|
316
316
|
Libis::Tools::XmlDocument) that contains child nodes with the MARC data of a single record. The code will strip
|
317
317
|
namespaces from the input in order to greatly simplify working with the XML.
|
318
318
|
|
319
|
+
## Parameter
|
319
320
|
|
320
|
-
|
321
|
-
|
321
|
+
The class ::Libis::Tools::Parameter and the ::Libis::Tools::ParameterContainer module provide a simple framework for
|
322
|
+
instance variables that are type-safe and can easily be documented and provide defaults.
|
323
|
+
|
324
|
+
To use these parameters a class should include the ::Libis::Tools::ParameterContainer module and add 'parameter'
|
325
|
+
statements to the body of the class definition. It takes only one mandatory argument which is a Hash. The first entry is
|
326
|
+
interpreted as '<name> => <default>'. The name for the parameter should be unique and the default value can be any value
|
327
|
+
of type TrueClass, FalseClass, String, Integer, Float, Date, Time, DateTime, Array, Hash or NilClass.
|
328
|
+
|
329
|
+
The second up to last Hash entries are optional properties for the parameter. These are:
|
330
|
+
|
331
|
+
* datatype: the type of values the parameter will accept. Valid values are:
|
332
|
+
|
333
|
+
* 'bool' or 'boolean'
|
334
|
+
* 'string'
|
335
|
+
* 'int'
|
336
|
+
* 'float'
|
337
|
+
* 'datetime'
|
338
|
+
* 'array'
|
339
|
+
* 'hash'
|
340
|
+
|
341
|
+
Any other value will raise a RuntimeError when the parameter is used. The value is case-insensitive and if not present,
|
342
|
+
the datatype will be derived from the default value with 'string' being the default for NilClass. In any case the
|
343
|
+
parameter will try its best to convert supplied values to the proper data type. For instance, an Integer parameter will
|
344
|
+
accept 3, 3.1415, '3' and Rational(10/3) as valid values and store them as the integer value 3. Likewise DateTime
|
345
|
+
parameters will try to interprete date and time strings.
|
346
|
+
|
347
|
+
* description: any descriptive text you want to add to clarify what this parameter is used for. Any tool can ask the
|
348
|
+
class for its parameters and - for instance - can use this property to provide help in a GUI when asking the user for
|
349
|
+
input.
|
350
|
+
|
351
|
+
* constraint: adds a validation condition to the parameter. The condition value can be:
|
352
|
+
|
353
|
+
* an array: only values that convert to a value in the list are considered valid.
|
354
|
+
* a range: only values that convert to a value in the given range are considered valid.
|
355
|
+
* a regular expression: only values that match the regular expression are considered valid.
|
356
|
+
* a string: only values that are '==' to the constraint are considered valid.
|
357
|
+
|
358
|
+
* frozen: if set to true, prevents the class instance to set the parameter to any value other than the default. Mostly
|
359
|
+
useful when a derived class needs a parameter in the parent class to be set to a specific value. Setting a value on
|
360
|
+
a frozen parameter with the 'parameter(name,value)' method throws a ::Libis::Tools::ParameterFrozenError. The '[]='
|
361
|
+
method silently ignores the exception. In any case the default value will not be changed.
|
362
|
+
|
363
|
+
* options: a hash with any additional properties that you want to associate to the parameter. Any key-value pair in this
|
364
|
+
hash is added to the retrievable properties of the parameter. Likewise any property defined, that is not in the list of
|
365
|
+
known properties is added to the options hash. In this aspect the ::Libis::Tools::Parameter class behaves much like an
|
366
|
+
OpenStruct even though it is implemented as a Struct.
|
367
|
+
|
368
|
+
Besides enabling the 'parameter' class method to define parameters, the ::Libis::Tools::ParameterContainer add the class
|
369
|
+
method 'parameters' that will return a Hash with parameter names as keys and their respective parameter definitions as
|
370
|
+
values. On each class instance the 'parameter' method is added and serves as both getter and setter for parameter values:
|
371
|
+
With only one argument (the parameter name) it returns the current value for the parameter, but the optional second
|
372
|
+
argument will cause the method to set the parameter value. If the parameter is not available or the given value is not
|
373
|
+
a valid value for the parameter, the method will return the special constant ::Libis::ParameterContainer::NO_VALUE. The
|
374
|
+
methods '[]' and '[]=' serve as aliases for the getter and setter calls.
|
375
|
+
|
376
|
+
Additionally two protected methods are available on the instance:
|
377
|
+
* 'parameters': returns the Hash that keeps track of the current parameter values for the instance.
|
378
|
+
* 'get_parameter_defintion': retrieves the parameter definition from the instance's class for the given parameter name.
|
379
|
+
|
380
|
+
Any class that derives from a class that included the ::Libis::Tools::ParameterContainer module will automatically
|
381
|
+
inherit all parameter definitions from all of it's base classes and can override any of these parameter definitions e.g.
|
382
|
+
to change the default values for the parameter.
|
322
383
|
|
323
384
|
## Contributing
|
324
385
|
|
@@ -5,13 +5,26 @@ require 'libis/tools/extend/struct'
|
|
5
5
|
module Libis
|
6
6
|
module Tools
|
7
7
|
|
8
|
+
class ParameterValidationError < RuntimeError;
|
9
|
+
end
|
10
|
+
class ParameterFrozenError < RuntimeError;
|
11
|
+
end
|
12
|
+
|
8
13
|
# noinspection RubyConstantNamingConvention
|
9
|
-
Parameter = ::Struct.new(:name, :default, :datatype, :description, :constraint, :options) do
|
14
|
+
Parameter = ::Struct.new(:name, :default, :datatype, :description, :constraint, :frozen, :options) do
|
10
15
|
|
11
16
|
def initialize(*args)
|
12
17
|
# noinspection RubySuperCallWithoutSuperclassInspection
|
13
18
|
super(*args)
|
14
|
-
self
|
19
|
+
self[:options] ||= {}
|
20
|
+
self[:datatype] ||= guess_datatype if args[1]
|
21
|
+
end
|
22
|
+
|
23
|
+
def dup
|
24
|
+
new_obj = super
|
25
|
+
# noinspection RubyResolve
|
26
|
+
new_obj[:options] = Marshal.load(Marshal.dump(self[:options]))
|
27
|
+
new_obj
|
15
28
|
end
|
16
29
|
|
17
30
|
def [](key)
|
@@ -20,19 +33,19 @@ module Libis
|
|
20
33
|
self[:options][key]
|
21
34
|
end
|
22
35
|
|
23
|
-
def []=(key,value)
|
36
|
+
def []=(key, value)
|
24
37
|
# noinspection RubySuperCallWithoutSuperclassInspection
|
25
|
-
return super(key,value) if members.include?(key)
|
38
|
+
return super(key, value) if members.include?(key)
|
26
39
|
self[:options][key] = value
|
27
40
|
end
|
28
41
|
|
29
42
|
def self.from_hash(h)
|
30
|
-
h.each { |k,v| self[k.to_s.to_sym] = v }
|
43
|
+
h.each { |k, v| self[k.to_s.to_sym] = v }
|
31
44
|
end
|
32
45
|
|
33
46
|
def to_h
|
34
47
|
super.inject({}) do |hash, key, value|
|
35
|
-
key == :options ? value.each {|k,v| hash[k] = v} : hash[key] = value
|
48
|
+
key == :options ? value.each { |k, v| hash[k] = v } : hash[key] = value
|
36
49
|
hash
|
37
50
|
end
|
38
51
|
end
|
@@ -41,12 +54,7 @@ module Libis
|
|
41
54
|
FALSE_BOOL = %w'false no f n 0'
|
42
55
|
|
43
56
|
def parse(value = nil)
|
44
|
-
result =
|
45
|
-
send(:default)
|
46
|
-
else
|
47
|
-
dtype = guess_datatype.to_s.downcase
|
48
|
-
convert(dtype, value)
|
49
|
-
end
|
57
|
+
result = value.nil? ? self[:default] : convert(value)
|
50
58
|
check_constraint(result)
|
51
59
|
result
|
52
60
|
end
|
@@ -60,36 +68,37 @@ module Libis
|
|
60
68
|
true
|
61
69
|
end
|
62
70
|
|
63
|
-
def guess_datatype
|
64
|
-
return send(:datatype) if send(:datatype)
|
65
|
-
case send(:default)
|
66
|
-
when TrueClass, FalseClass
|
67
|
-
'bool'
|
68
|
-
when NilClass
|
69
|
-
'string'
|
70
|
-
when Integer
|
71
|
-
'int'
|
72
|
-
when Float
|
73
|
-
'float'
|
74
|
-
when DateTime, Date, Time
|
75
|
-
'datetime'
|
76
|
-
when Array
|
77
|
-
'array'
|
78
|
-
when Hash
|
79
|
-
'hash'
|
80
|
-
else
|
81
|
-
send(:default).class.name.downcase
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
71
|
private
|
86
72
|
|
87
|
-
def
|
88
|
-
case
|
73
|
+
def guess_datatype
|
74
|
+
self[:datatype] || case self[:default]
|
75
|
+
when TrueClass, FalseClass
|
76
|
+
'bool'
|
77
|
+
when NilClass
|
78
|
+
'string'
|
79
|
+
when Integer
|
80
|
+
'int'
|
81
|
+
when Float
|
82
|
+
'float'
|
83
|
+
when DateTime, Date, Time
|
84
|
+
'datetime'
|
85
|
+
when Array
|
86
|
+
'array'
|
87
|
+
when Hash
|
88
|
+
'hash'
|
89
|
+
else
|
90
|
+
self[:default].class.name.downcase
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def convert(v)
|
95
|
+
case self[:datatype]
|
89
96
|
when 'boolean', 'bool'
|
90
97
|
return true if TRUE_BOOL.include?(v.to_s.downcase)
|
91
98
|
return false if FALSE_BOOL.include?(v.to_s.downcase)
|
92
|
-
raise
|
99
|
+
raise ParameterValidationError, "No boolean information in '#{v.to_s}'. " +
|
100
|
+
"Valid values are: '#{TRUE_BOOL.join('\', \'')}" +
|
101
|
+
"' and '#{FALSE_BOOL.join('\', \'')}'."
|
93
102
|
when 'string'
|
94
103
|
return v.to_s.gsub('%s', Time.now.strftime('%Y%m%d%H%M%S'))
|
95
104
|
when 'int'
|
@@ -105,17 +114,19 @@ module Libis
|
|
105
114
|
return v.to_a if v.respond_to?(:to_a)
|
106
115
|
when 'hash'
|
107
116
|
return v when v.is_a?(Hash)
|
108
|
-
|
117
|
+
return Hash[(0...v.size).zip(v)] when v.is_a?(Array)
|
109
118
|
else
|
110
|
-
raise
|
119
|
+
raise ParameterValidationError, "Datatype not supported: '#{self[:datatype]}'"
|
111
120
|
end
|
112
121
|
nil
|
113
122
|
end
|
114
123
|
|
115
124
|
def check_constraint(v, constraint = nil)
|
116
|
-
constraint ||=
|
125
|
+
constraint ||= self[:constraint]
|
117
126
|
return if constraint.nil?
|
118
|
-
|
127
|
+
unless constraint_checker(v, constraint)
|
128
|
+
raise ParameterValidationError, "Value '#{v}' is not allowed (constraint: #{constraint})."
|
129
|
+
end
|
119
130
|
end
|
120
131
|
|
121
132
|
def constraint_checker(v, constraint)
|
@@ -142,23 +153,30 @@ module Libis
|
|
142
153
|
|
143
154
|
module ClassMethods
|
144
155
|
|
145
|
-
def
|
146
|
-
if
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
return param_def unless param_def.nil?
|
156
|
-
self.superclass.parameter(options) rescue nil
|
156
|
+
def parameter_defs
|
157
|
+
return @parameters if @parameters
|
158
|
+
@parameters = Hash.new
|
159
|
+
begin
|
160
|
+
self.superclass.parameter_defs.
|
161
|
+
each_with_object(@parameters) do |(name, param), hash|
|
162
|
+
hash[name] = param.dup
|
163
|
+
end
|
164
|
+
rescue NoMethodError
|
165
|
+
# ignored
|
157
166
|
end
|
167
|
+
@parameters
|
158
168
|
end
|
159
169
|
|
160
|
-
def
|
161
|
-
|
170
|
+
def parameter(options = {})
|
171
|
+
return self.parameter_defs[options] unless options.is_a? Hash
|
172
|
+
return nil if options.keys.empty?
|
173
|
+
param_def = options.shift
|
174
|
+
name = param_def.first.to_s.to_sym
|
175
|
+
default = param_def.last
|
176
|
+
param = (self.parameter_defs[name] ||= Parameter.new(name, default))
|
177
|
+
options[:default] = default
|
178
|
+
options.each { |key, value| param[key] = value if value }
|
179
|
+
param
|
162
180
|
end
|
163
181
|
|
164
182
|
end
|
@@ -177,6 +195,9 @@ module Libis
|
|
177
195
|
param_def.parse(param_value)
|
178
196
|
else
|
179
197
|
return NO_VALUE unless param_def.valid_value?(value)
|
198
|
+
if param_def[:frozen]
|
199
|
+
raise ParameterFrozenError, "Parameter '#{param_def[:name]}' is frozen in '#{self.class.name}'"
|
200
|
+
end
|
180
201
|
parameters[name] = value
|
181
202
|
end
|
182
203
|
end
|
@@ -187,16 +208,18 @@ module Libis
|
|
187
208
|
|
188
209
|
def []=(name, value)
|
189
210
|
parameter name, value
|
211
|
+
rescue ParameterFrozenError
|
212
|
+
# ignored
|
190
213
|
end
|
191
214
|
|
192
215
|
protected
|
193
216
|
|
194
217
|
def parameters
|
195
|
-
@
|
218
|
+
@parameter_values ||= Hash.new
|
196
219
|
end
|
197
220
|
|
198
221
|
def get_parameter_definition(name)
|
199
|
-
self.class.
|
222
|
+
self.class.parameter_defs[name]
|
200
223
|
end
|
201
224
|
|
202
225
|
end # ParameterContainer
|
data/lib/libis/tools/version.rb
CHANGED
data/libis-tools.gemspec
CHANGED
@@ -32,7 +32,6 @@ Gem::Specification.new do |spec|
|
|
32
32
|
spec.add_development_dependency 'awesome_print'
|
33
33
|
|
34
34
|
spec.add_runtime_dependency 'backports', '~> 3.6'
|
35
|
-
spec.add_runtime_dependency 'savon', '~> 2.0'
|
36
35
|
spec.add_runtime_dependency 'nokogiri', '~> 1.6'
|
37
36
|
spec.add_runtime_dependency 'gyoku', '~> 1.2'
|
38
37
|
spec.add_runtime_dependency 'nori', '~> 2.4'
|
@@ -7,10 +7,10 @@ describe 'ParameterContainer' do
|
|
7
7
|
class TestContainer
|
8
8
|
include ::Libis::Tools::ParameterContainer
|
9
9
|
|
10
|
-
parameter check: true
|
11
|
-
parameter count: 0
|
12
|
-
parameter price: 1.0
|
13
|
-
parameter name: 'nobody'
|
10
|
+
parameter check: true, description: 'check parameter'
|
11
|
+
parameter count: 0, description: 'count parameter'
|
12
|
+
parameter price: 1.0, description: 'price parameter'
|
13
|
+
parameter name: 'nobody', description: 'name parameter'
|
14
14
|
parameter calendar: Date.new(2014, 01, 01)
|
15
15
|
parameter clock: Time.parse('10:10')
|
16
16
|
parameter timestamp: DateTime.new(2014, 01, 01, 10, 10)
|
@@ -19,12 +19,18 @@ describe 'ParameterContainer' do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
class DerivedContainer < TestContainer
|
22
|
-
parameter name: 'somebody'
|
23
|
-
parameter check: '
|
22
|
+
parameter name: 'somebody', description: 'derived name parameter', frozen: true
|
23
|
+
parameter check: 'no'
|
24
|
+
parameter new_derived_param: false
|
24
25
|
end
|
25
26
|
|
26
|
-
class DerivedDerivedContainer < DerivedContainer
|
27
|
-
|
27
|
+
class DerivedDerivedContainer < DerivedContainer
|
28
|
+
parameter price: 2.0, description: 'derived derived price parameter'
|
29
|
+
end
|
30
|
+
|
31
|
+
class DerivedDerivedDerivedContainer < DerivedDerivedContainer
|
32
|
+
parameter price: 3.0, description: 'derived derived derived price parameter'
|
33
|
+
end
|
28
34
|
|
29
35
|
let(:test_container) { TestContainer.new }
|
30
36
|
let(:derived_container) { DerivedContainer.new }
|
@@ -96,17 +102,51 @@ describe 'ParameterContainer' do
|
|
96
102
|
expect(test_container.class.parameter(:with_options)[:c]).to be 3
|
97
103
|
end
|
98
104
|
|
99
|
-
it 'derived class should
|
105
|
+
it 'derived class should inherit parameters of the parent class' do
|
106
|
+
expect(derived_container.parameter(:price)).to eq 1.0
|
107
|
+
expect(derived_container.class.parameter(:price)[:description]).to eq 'price parameter'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'derived class should override parameter values and propertiesfrom parent class' do
|
100
111
|
expect(derived_container.parameter(:name)).to eq 'somebody'
|
101
|
-
expect(derived_container.parameter(:check)).to eq '
|
112
|
+
expect(derived_container.parameter(:check)).to eq 'no'
|
102
113
|
end
|
103
114
|
|
104
|
-
it 'derived
|
105
|
-
expect(
|
115
|
+
it 'overrides in the derived classes should not change values in the parent classes' do
|
116
|
+
expect(test_container.parameter(:check)).to eq true
|
117
|
+
expect(test_container.parameter(:price)).to eq 1.0
|
118
|
+
expect(test_container.class.parameter(:price)[:description]).to eq 'price parameter'
|
106
119
|
end
|
107
120
|
|
108
121
|
it 'derivation should be supported over multiple levels' do
|
109
|
-
expect(
|
122
|
+
expect(derived_container.parameter(:price)).to eq 1.0
|
123
|
+
expect(derived_derived_container.parameter(:price)).to eq 2.0
|
124
|
+
expect(derived_derived_derived_container.parameter(:price)).to eq 3.0
|
125
|
+
expect(derived_container.class.parameter(:price)[:description]).to eq 'price parameter'
|
126
|
+
expect(derived_derived_container.class.parameter(:price)[:description]).to eq 'derived derived price parameter'
|
127
|
+
expect(derived_derived_derived_container.class.parameter(:price)[:description]).to eq 'derived derived derived price parameter'
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'frozen parameters should be read-only' do
|
131
|
+
expect {
|
132
|
+
derived_container.parameter(:name, 'anybody')
|
133
|
+
}.to raise_error(::Libis::Tools::ParameterFrozenError)
|
134
|
+
expect(derived_container.parameter(:name)).to eq 'somebody'
|
135
|
+
expect {
|
136
|
+
derived_container[:name] = 'anybody'
|
137
|
+
}.not_to raise_error
|
138
|
+
expect(derived_container[:name]).to eq 'somebody'
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'frozen state should not affect parameter in parent class' do
|
142
|
+
expect {
|
143
|
+
test_container.parameter(:name, 'anybody')
|
144
|
+
}.not_to raise_error
|
145
|
+
expect(test_container.parameter(:name)).to eq 'anybody'
|
146
|
+
expect {
|
147
|
+
test_container[:name] = 'everybody'
|
148
|
+
}.not_to raise_error
|
149
|
+
expect(test_container[:name]).to eq 'everybody'
|
110
150
|
end
|
111
151
|
|
112
152
|
end
|
data/spec/parameter_spec.rb
CHANGED
@@ -21,7 +21,7 @@ describe 'Parameter' do
|
|
21
21
|
|
22
22
|
it 'should detect datatype from default value' do
|
23
23
|
@parameter_types.each do |dtype|
|
24
|
-
expect(eval("@#{dtype}_parameter").guess_datatype).to eq dtype
|
24
|
+
expect(eval("@#{dtype}_parameter").send(:guess_datatype)).to eq dtype
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: libis-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kris Dekeyser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,20 +108,6 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '3.6'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: savon
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '2.0'
|
118
|
-
type: :runtime
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '2.0'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: nokogiri
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -310,7 +296,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
310
296
|
version: '0'
|
311
297
|
requirements: []
|
312
298
|
rubyforge_project:
|
313
|
-
rubygems_version: 2.
|
299
|
+
rubygems_version: 2.2.2
|
314
300
|
signing_key:
|
315
301
|
specification_version: 4
|
316
302
|
summary: LIBIS toolbox.
|