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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2acfb7ca3db4644a7c4001df1ee44999ccc8632f
4
- data.tar.gz: 32a297ad8bdc665211b6d4ff51efd67f9e026589
3
+ metadata.gz: 22d6fc69dcf878e87ea28eb0fbfd5965b7d2f15b
4
+ data.tar.gz: 48b5bf0cfdbe91755bb535132ad0efe76b29a116
5
5
  SHA512:
6
- metadata.gz: 609edc8e2ec6db1ced8bb0db45b1b55c9f00ebc7b8c6c45cdb12fe7d8dc44194871d5902310b7e173d54c61b4d23cf233919879c3f3feb2d2dc249caf82ceaf0
7
- data.tar.gz: e967facb80617a24e4bd3cd4ae2e80995b13e8672fb0dc65d79b2d663883aa3d464d8ed5d61d0faf45cf118363aa60457b32b5eb896c01589ba4f03d77f91ede
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.options = {} unless self.options
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 = if value.nil?
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 convert(dtype, v)
88
- case dtype.to_s.downcase
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 ArgumentError, "No boolean information in '#{v.to_s}'. Valid values are: '#{TRUE_BOOL.join('\', \'')}' and '#{FALSE_BOOL.join('\', \'')}'."
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
- return Hash[(0...v.size).zip(v)] when v.is_a?(Array)
117
+ return Hash[(0...v.size).zip(v)] when v.is_a?(Array)
109
118
  else
110
- raise RuntimeError, "Datatype not supported: '#{dtype}'"
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 ||= send(:constraint)
125
+ constraint ||= self[:constraint]
117
126
  return if constraint.nil?
118
- raise ArgumentError, "Value '#{v}' is not allowed (constraint: #{constraint})." unless constraint_checker(v, constraint)
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 parameter(options = {})
146
- if options.is_a? Hash
147
- return nil if options.keys.empty?
148
- param_def = options.shift
149
- name = param_def.first.to_s.to_sym
150
- default = param_def.last
151
- parameters[name] = Parameter.new(name, default) if parameters[name].nil?
152
- options.each { |key, value| parameters[name][key] = value if value }
153
- else
154
- param_def = parameters[options]
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 parameters
161
- @parameters ||= Hash.new
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
- @parameters ||= Hash.new
218
+ @parameter_values ||= Hash.new
196
219
  end
197
220
 
198
221
  def get_parameter_definition(name)
199
- self.class.parameter(name)
222
+ self.class.parameter_defs[name]
200
223
  end
201
224
 
202
225
  end # ParameterContainer
@@ -1,5 +1,5 @@
1
1
  module Libis
2
2
  module Tools
3
- VERSION = '0.9.12'
3
+ VERSION = '0.9.13'
4
4
  end
5
5
  end
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: 'yes'
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; end
27
- class DerivedDerivedDerivedContainer < DerivedDerivedContainer; end
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 override parameter values from parent class' do
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 'yes'
112
+ expect(derived_container.parameter(:check)).to eq 'no'
102
113
  end
103
114
 
104
- it 'derived class should be able to access parameters of parent class' do
105
- expect(derived_container.parameter(:price)).to eq 1.0
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(derived_derived_derived_container.parameter(:price)).to eq 1.0
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
@@ -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.12
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-07 00:00:00.000000000 Z
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.5.0
299
+ rubygems_version: 2.2.2
314
300
  signing_key:
315
301
  specification_version: 4
316
302
  summary: LIBIS toolbox.