libis-tools 0.9.12 → 0.9.13
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.
- 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.
|