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 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.