kafo 0.9.8 → 1.0.0

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: e9aa7023bba923d94e836d8a08738bc10953938e
4
- data.tar.gz: 08b552989a2cbc3855b8d76d2bc06404372d89d9
3
+ metadata.gz: 37920206589ad542c671af010f5b8dd1bb02dff0
4
+ data.tar.gz: 3e001186cf0bd9f809a28d92e5223d6345e264e8
5
5
  SHA512:
6
- metadata.gz: c74d44e98884a8b71c2c89518f74b5a3607da2dace7ec9811337bdaaae595fb39d4d97a033b7eee0340ff1c919d0e48eb6325fd3b424a5c45e3151c11ad1a017
7
- data.tar.gz: 92a489f199664a79ef65b43972c47dfacc45471c5ef9d76aac8013bd7e25c7998ab29dbd38c51d87426f2b0e21dd2c1693d02592a9fefa5b05aba8ff1eb559e7
6
+ metadata.gz: c59eac78e4965f90f98b1bab03275aa2044e724f71e7ed3f90339e7dbc0201812d3cb04bf1ddd0b3b390a309a1c3455620bcd961cadba6fcca01c9b6e581a9f1
7
+ data.tar.gz: 797b544447e37f68faf90fe2a54afd033bda2254fa27d18b7c93d1c9ba453644db8b0c145ce9588e5c146507eebdaa60f59ac3321ad379d6f1b64fa3587f05a9
data/README.md CHANGED
@@ -400,10 +400,10 @@ Example:
400
400
  #
401
401
  # $enc:: Should foreman act as an external node classifier (manage puppet class
402
402
  # assignments)
403
- # type:boolean
403
+ #
404
404
  class foreman (
405
- $foreman_url = $foreman::params::foreman_url,
406
- $enc = $foreman::params::enc
405
+ String $foreman_url = $foreman::params::foreman_url,
406
+ Boolean $enc = $foreman::params::enc
407
407
  ) {
408
408
  class { 'foreman::install': }
409
409
  }
@@ -445,20 +445,45 @@ the particular parameter belongs.
445
445
 
446
446
  ## Argument types
447
447
 
448
- By default all arguments that are parsed from puppet are treated as strings.
449
- If you want to indicate that a parameter has a particular type you can do it
450
- in the puppet manifest documentation like this
448
+ When using Puppet 4 or newer, the data type will be read from the parameter
449
+ list and defaults to Puppet's [Any](https://docs.puppet.com/puppet/latest/reference/lang_data_abstract.html#any)
450
+ data type, which Kafo handles as a basic string with no validation.
451
+
452
+ If more specific data types, such as `Optional[Array[2]]` or similar are
453
+ given in the [parameter list](https://docs.puppet.com/puppet/4.5/reference/lang_data_type.html#usage)
454
+ then Kafo will parse and validate parameters values according to the
455
+ specification.
456
+
457
+ ```puppet
458
+ class example (
459
+ Boolean $param = false
460
+ ) {
461
+ ```
462
+
463
+ When using Puppet 3, data types can be specified in the manifest documentation
464
+ rather than the parameter list, like this:
451
465
 
452
466
  ```puppet
453
467
  # $param:: Some documentation for param
454
- type:boolean
468
+ type:Array[String]
455
469
  ```
456
470
 
457
- Supported types are: string, boolean, integer, array, password, hash
471
+ For compatibility with older Kafo releases, additional types are supported:
472
+ string, boolean, integer, array, password, hash. These are equivalent to their
473
+ Puppet 4 namesakes, plus wrapped in `Optional[..]` to permit `undef`.
474
+
475
+ If the data type is given in both the manifest documentation and the parameter
476
+ list, then the manifest documentation will be preferred.
458
477
 
459
478
  Note that all arguments that are nil (have no value in answers.yaml or you
460
479
  set them UNDEF (see below)) are translated to ```undef``` in puppet.
461
480
 
481
+ If your module declares its own types, you can add new corresponding subclasses
482
+ of DataType which implement validation and typecasting. This can be added to a
483
+ `boot` hook by calling:
484
+
485
+ Kafo::DataType.register_type('YourType', Kafo::DataType::YourType)
486
+
462
487
  ## Password arguments
463
488
 
464
489
  Kafo supports password arguments. It's adding some level of protection for your
@@ -704,6 +729,10 @@ you must follow a few rules however:
704
729
  These functions are re-implemented in Kafo from common stdlib functions, so please
705
730
  contribute any missing ones.
706
731
 
732
+ If class parameters are declared with Puppet 4 data types then Kafo will
733
+ validate user inputs against Puppet's type validation rules, which should
734
+ replace the use of separate validation functions.
735
+
707
736
  ## Enabling or disabling module
708
737
 
709
738
  You can enable or disable a module specified in the answers.yaml file. Every module
@@ -0,0 +1,100 @@
1
+ module Kafo
2
+ class DataType
3
+ def self.new_from_string(str)
4
+ keyword_re = /\A([\w:]+)(?:\[(.*)\])?\z/m.match(str)
5
+ if keyword_re
6
+ if (type = @keywords[keyword_re[1]])
7
+ args = if keyword_re[2]
8
+ hash_re = keyword_re[2].match(/\A\s*{(.*)}\s*\z/m)
9
+ if hash_re
10
+ [parse_hash(hash_re[1])]
11
+ else
12
+ split_arguments(keyword_re[2])
13
+ end
14
+ else
15
+ []
16
+ end
17
+ type.new(*args)
18
+ elsif ['Data'].include?(keyword_re[1])
19
+ DataTypes::Any.new
20
+ elsif keyword_re[1] == 'Default'
21
+ DataTypes::Enum.new('default')
22
+ elsif @keywords[keyword_re[1].capitalize] # pre-Puppet 4 data types
23
+ DataTypes::Optional.new(keyword_re[1].capitalize)
24
+ else
25
+ raise ConfigurationException, "unknown data type #{keyword_re[1]}"
26
+ end
27
+ else
28
+ raise ConfigurationException, "data type not recognized #{str}"
29
+ end
30
+ end
31
+
32
+ def self.register_type(keyword, type)
33
+ @keywords ||= {}
34
+ raise ArgumentError, "Data type #{keyword} is already registered, cannot be re-registered" if @keywords.has_key?(keyword)
35
+ @keywords[keyword] = type
36
+ end
37
+
38
+ def self.types
39
+ @keywords ? @keywords.keys : []
40
+ end
41
+
42
+ def self.unregister_type(keyword)
43
+ @keywords.delete(keyword) if @keywords
44
+ end
45
+
46
+ def self.split_arguments(input)
47
+ input.scan(%r{\s*["'/]?([\w:]+(?:\[.+\])?|.+?)["'/]?\s*(?:,|$)}m).flatten
48
+ end
49
+
50
+ def self.parse_hash(input)
51
+ Hash[input.scan(%r{\s*["'/]?([\w:]+(?:\[[^\]]+\])?|.+?)["'/]?\s*=>\s*["'/]?([\w:]+(?:\[[^\]]+\])?|.+?)["'/]?\s*(?:,|$)}m)]
52
+ end
53
+
54
+ # public interface
55
+
56
+ def condition_value(value)
57
+ value.inspect
58
+ end
59
+
60
+ def dump_default(value)
61
+ %{"#{value}"}
62
+ end
63
+
64
+ def multivalued?
65
+ false
66
+ end
67
+
68
+ def to_s
69
+ self.class.name.split('::').last.downcase
70
+ end
71
+
72
+ def typecast(value)
73
+ value == 'UNDEF' ? nil : value
74
+ end
75
+
76
+ def valid?(value, errors = [])
77
+ true
78
+ end
79
+ end
80
+ end
81
+
82
+ require 'kafo/data_types/any'
83
+ require 'kafo/data_types/array'
84
+ require 'kafo/data_types/boolean'
85
+ require 'kafo/data_types/enum'
86
+ require 'kafo/data_types/float'
87
+ require 'kafo/data_types/hash'
88
+ require 'kafo/data_types/integer'
89
+ require 'kafo/data_types/not_undef'
90
+ require 'kafo/data_types/numeric'
91
+ require 'kafo/data_types/optional'
92
+ require 'kafo/data_types/pattern'
93
+ require 'kafo/data_types/regexp'
94
+ require 'kafo/data_types/scalar'
95
+ require 'kafo/data_types/string'
96
+ require 'kafo/data_types/struct'
97
+ require 'kafo/data_types/tuple'
98
+ require 'kafo/data_types/type_reference'
99
+ require 'kafo/data_types/undef'
100
+ require 'kafo/data_types/variant'
@@ -0,0 +1,8 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Any < DataType
4
+ end
5
+
6
+ DataType.register_type('Any', Any)
7
+ end
8
+ end
@@ -0,0 +1,62 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Array < DataType
4
+ def initialize(inner_type = 'Data', min = :default, max = :default)
5
+ @inner_type = DataType.new_from_string(inner_type)
6
+ @min = (min.to_s == 'default') ? 0 : min.to_i
7
+ @max = (max.to_s == 'default') ? :infinite : max.to_i
8
+ end
9
+
10
+ def condition_value(value)
11
+ "[ #{value.map { |v| @inner_type.condition_value(v) }.join(', ')} ]"
12
+ end
13
+
14
+ def multivalued?
15
+ true
16
+ end
17
+
18
+ def to_s
19
+ type = "array of #{@inner_type}"
20
+ if @min > 0 && @max == :infinite
21
+ "#{type} (at least #{@min} items)"
22
+ elsif @min == 0 && @max != :infinite
23
+ "#{type} (up to #{@max} items)"
24
+ elsif @min > 0 && @max != :infinite
25
+ "#{type} (between #{@min} and #{@max} items)"
26
+ else
27
+ type
28
+ end
29
+ end
30
+
31
+ def typecast(value)
32
+ if value.nil?
33
+ nil
34
+ elsif value == ['EMPTY_ARRAY']
35
+ []
36
+ else
37
+ [value].flatten.map { |v| @inner_type.typecast(v) }
38
+ end
39
+ end
40
+
41
+ def valid?(input, errors = [])
42
+ unless input.is_a?(::Array)
43
+ errors << "#{input.inspect} is not a valid array"
44
+ return false
45
+ end
46
+
47
+ inner_errors = []
48
+ input.each { |v| @inner_type.valid?(v, inner_errors) }
49
+ unless inner_errors.empty?
50
+ errors << "Elements of the array are invalid: #{inner_errors.join(', ')}"
51
+ end
52
+
53
+ errors << "The array must have at least #{@min} items" if input.size < @min
54
+ errors << "The array must have at maximum #{@max} items" if @max != :infinite && input.size > @max
55
+
56
+ return errors.empty?
57
+ end
58
+ end
59
+
60
+ DataType.register_type('Array', Array)
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Boolean < DataType
4
+ def typecast(value)
5
+ case value
6
+ when '0', 'false', 'f', 'n', false
7
+ false
8
+ when '1', 'true', 't', 'y', true
9
+ true
10
+ else
11
+ value
12
+ end
13
+ end
14
+
15
+ def valid?(input, errors = [])
16
+ (input.is_a?(::TrueClass) || input.is_a?(::FalseClass)).tap do |valid|
17
+ errors << "#{input.inspect} is not a valid boolean" unless valid
18
+ end
19
+ end
20
+ end
21
+
22
+ DataType.register_type('Boolean', Boolean)
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Enum < DataType
4
+ def initialize(*permitted)
5
+ @permitted = permitted
6
+ end
7
+
8
+ def to_s
9
+ @permitted.map(&:inspect).join(' or ')
10
+ end
11
+
12
+ def valid?(input, errors = [])
13
+ unless input.is_a?(::String)
14
+ errors << "#{input.inspect} is not a valid string"
15
+ return false
16
+ end
17
+
18
+ errors << "#{input} must be one of #{@permitted.join(', ')}" unless @permitted.include?(input)
19
+ return errors.empty?
20
+ end
21
+ end
22
+
23
+ DataType.register_type('Enum', Enum)
24
+ end
25
+ end
@@ -0,0 +1,40 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Float < DataType
4
+ def initialize(min = :default, max = :default)
5
+ @min = (min.to_s == 'default') ? :infinite : min.to_i
6
+ @max = (max.to_s == 'default') ? :infinite : max.to_i
7
+ end
8
+
9
+ def to_s
10
+ if @min != :infinite && @max == :infinite
11
+ "float (at least #{@min})"
12
+ elsif @min == :infinite && @max != :infinite
13
+ "float (up to #{@max})"
14
+ elsif @min != :infinite && @max != :infinite
15
+ "float (between #{@min} and #{@max})"
16
+ else
17
+ "float"
18
+ end
19
+ end
20
+
21
+ def typecast(value)
22
+ value.to_s =~ /\d+/ ? value.to_f : value
23
+ end
24
+
25
+ def valid?(input, errors = [])
26
+ unless input.is_a?(::Float)
27
+ errors << "#{input.inspect} is not a valid float"
28
+ return false
29
+ end
30
+
31
+ errors << "#{input} must be at least #{@min}" if @min != :infinite && input < @min
32
+ errors << "#{input} must be up to #{@max}" if @max != :infinite && input > @max
33
+
34
+ return errors.empty?
35
+ end
36
+ end
37
+
38
+ DataType.register_type('Float', Float)
39
+ end
40
+ end
@@ -0,0 +1,70 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Hash < DataType
4
+ def initialize(inner_key_type = 'Scalar', inner_value_type = 'Data', min = :default, max = :default)
5
+ @inner_key_type = DataType.new_from_string(inner_key_type)
6
+ @inner_value_type = DataType.new_from_string(inner_value_type)
7
+ @min = (min.to_s == 'default') ? 0 : min.to_i
8
+ @max = (max.to_s == 'default') ? :infinite : max.to_i
9
+ end
10
+
11
+ def multivalued?
12
+ true
13
+ end
14
+
15
+ def to_s
16
+ type = "hash of #{@inner_key_type}/#{@inner_value_type}"
17
+ if @min > 0 && @max == :infinite
18
+ "#{type} (at least #{@min} items)"
19
+ elsif @min == 0 && @max != :infinite
20
+ "#{type} (up to #{@max} items)"
21
+ elsif @min > 0 && @max != :infinite
22
+ "#{type} (between #{@min} and #{@max} items)"
23
+ else
24
+ type
25
+ end
26
+ end
27
+
28
+ def typecast(value)
29
+ if value.nil?
30
+ nil
31
+ elsif value.is_a?(::Hash)
32
+ value
33
+ elsif value == ['EMPTY_HASH']
34
+ {}
35
+ else
36
+ ::Hash[[value].flatten.map do |kv|
37
+ k, v = kv.split(':', 2)
38
+ [@inner_key_type.typecast(k), @inner_value_type.typecast(v)]
39
+ end]
40
+ end
41
+ end
42
+
43
+ def valid?(input, errors = [])
44
+ unless input.is_a?(::Hash)
45
+ errors << "#{input.inspect} is not a valid hash"
46
+ return false
47
+ end
48
+
49
+ inner_key_errors = []
50
+ input.keys.each { |v| @inner_key_type.valid?(v, inner_key_errors) }
51
+ unless inner_key_errors.empty?
52
+ errors << "Hash key elements are invalid: #{inner_key_errors.join(', ')}"
53
+ end
54
+
55
+ inner_value_errors = []
56
+ input.values.each { |v| @inner_value_type.valid?(v, inner_value_errors) }
57
+ unless inner_value_errors.empty?
58
+ errors << "Hash value elements are invalid: #{inner_value_errors.join(', ')}"
59
+ end
60
+
61
+ errors << "The hash must have at least #{@min} items" if input.size < @min
62
+ errors << "The hash must have at maximum #{@max} items" if @max != :infinite && input.size > @max
63
+
64
+ return errors.empty?
65
+ end
66
+ end
67
+
68
+ DataType.register_type('Hash', Hash)
69
+ end
70
+ end
@@ -0,0 +1,40 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Integer < DataType
4
+ def initialize(min = :default, max = :default)
5
+ @min = (min.to_s == 'default') ? :infinite : min.to_i
6
+ @max = (max.to_s == 'default') ? :infinite : max.to_i
7
+ end
8
+
9
+ def to_s
10
+ if @min != :infinite && @max == :infinite
11
+ "integer (at least #{@min})"
12
+ elsif @min == :infinite && @max != :infinite
13
+ "integer (up to #{@max})"
14
+ elsif @min != :infinite && @max != :infinite
15
+ "integer (between #{@min} and #{@max})"
16
+ else
17
+ "integer"
18
+ end
19
+ end
20
+
21
+ def typecast(value)
22
+ value =~ /\d+/ ? value.to_i : value
23
+ end
24
+
25
+ def valid?(input, errors = [])
26
+ unless input.is_a?(::Integer)
27
+ errors << "#{input.inspect} is not a valid integer"
28
+ return false
29
+ end
30
+
31
+ errors << "#{input} must be at least #{@min}" if @min != :infinite && input < @min
32
+ errors << "#{input} must be up to #{@max}" if @max != :infinite && input > @max
33
+
34
+ return errors.empty?
35
+ end
36
+ end
37
+
38
+ DataType.register_type('Integer', Integer)
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class NotUndef < DataType
4
+ extend Forwardable
5
+ def_delegators :@inner_type, :condition_value, :dump_default, :multivalued?, :typecast
6
+ attr_reader :inner_type, :inner_value
7
+
8
+ def initialize(inner_type_or_value)
9
+ begin
10
+ @inner_type = DataType.new_from_string(inner_type_or_value)
11
+ rescue ConfigurationException
12
+ @inner_value = inner_type_or_value
13
+ end
14
+ end
15
+
16
+ def to_s
17
+ if @inner_type
18
+ "#{@inner_type} but not undef"
19
+ else
20
+ "#{@inner_value.inspect} but not undef"
21
+ end
22
+ end
23
+
24
+ def valid?(input, errors = [])
25
+ return false if input.nil?
26
+ return true if @inner_type && @inner_type.valid?(input, errors)
27
+ return true if @inner_value && @inner_value == input
28
+ return false
29
+ end
30
+ end
31
+
32
+ DataType.register_type('NotUndef', NotUndef)
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Numeric < DataType
4
+ def typecast(value)
5
+ value =~ /\d+/ ? value.to_f : value
6
+ end
7
+
8
+ def valid?(input, errors = [])
9
+ errors << "#{input.inspect} is not a valid number" unless input.is_a?(::Integer) || input.is_a?(::Float)
10
+ return errors.empty?
11
+ end
12
+ end
13
+
14
+ DataType.register_type('Numeric', Numeric)
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Optional < DataType
4
+ extend Forwardable
5
+ def_delegators :@inner_type, :condition_value, :dump_default, :multivalued?, :typecast
6
+ attr_reader :inner_type, :inner_value
7
+
8
+ def initialize(inner_type_or_value)
9
+ begin
10
+ @inner_type = DataType.new_from_string(inner_type_or_value)
11
+ rescue ConfigurationException
12
+ @inner_value = inner_type_or_value
13
+ end
14
+ end
15
+
16
+ def to_s
17
+ if @inner_type
18
+ "#{@inner_type} or undef"
19
+ else
20
+ "#{@inner_value.inspect} or undef"
21
+ end
22
+ end
23
+
24
+ def valid?(input, errors = [])
25
+ return true if input.nil?
26
+ return true if @inner_type && @inner_type.valid?(input, errors)
27
+ return true if @inner_value && @inner_value == input
28
+ return false
29
+ end
30
+ end
31
+
32
+ DataType.register_type('Optional', Optional)
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Pattern < DataType
4
+ def initialize(*regexes)
5
+ @regex_strings = regexes
6
+ @regexes = regexes.map { |r| ::Regexp.new(r) }
7
+ end
8
+
9
+ def to_s
10
+ "regexes matching #{@regex_strings.map { |r| "/#{r}/" }.join(' or ')}"
11
+ end
12
+
13
+ def valid?(input, errors = [])
14
+ unless input.is_a?(::String)
15
+ errors << "#{input.inspect} is not a valid string"
16
+ return false
17
+ end
18
+
19
+ unless @regexes.any? { |r| r.match(input) }
20
+ errors << "#{input} must match one of #{@regexes.join(', ')}"
21
+ end
22
+
23
+ return errors.empty?
24
+ end
25
+ end
26
+
27
+ DataType.register_type('Pattern', Pattern)
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Regexp < DataType
4
+ extend Forwardable
5
+ def_delegators :@inner_type, :condition_value, :dump_default, :multivalued?, :typecast, :valid?
6
+
7
+ def initialize
8
+ @inner_type = DataTypes::String.new
9
+ end
10
+ end
11
+
12
+ DataType.register_type('Regexp', Regexp)
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Scalar < DataType
4
+ extend Forwardable
5
+ def_delegators :@inner_type, :condition_value, :dump_default, :multivalued?, :typecast, :valid?
6
+
7
+ def initialize
8
+ @inner_type = DataTypes::Variant.new('Integer', 'Float', 'String', 'Boolean', 'Regexp')
9
+ end
10
+ end
11
+
12
+ DataType.register_type('Scalar', Scalar)
13
+ end
14
+ end
@@ -0,0 +1,36 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class String < DataType
4
+ def initialize(min = :default, max = :default)
5
+ @min = (min.to_s == 'default') ? 0 : min.to_i
6
+ @max = (max.to_s == 'default') ? :infinite : max.to_i
7
+ end
8
+
9
+ def to_s
10
+ if @min > 0 && @max == :infinite
11
+ "string (at least #{@min} characters)"
12
+ elsif @min == 0 && @max != :infinite
13
+ "string (up to #{@max} characters)"
14
+ elsif @min > 0 && @max != :infinite
15
+ "string (between #{@min} and #{@max} characters)"
16
+ else
17
+ "string"
18
+ end
19
+ end
20
+
21
+ def valid?(input, errors = [])
22
+ unless input.is_a?(::String)
23
+ errors << "#{input.inspect} is not a valid string"
24
+ return false
25
+ end
26
+
27
+ errors << "#{input} must be at least #{@min}" if input.size < @min
28
+ errors << "#{input} must be up to #{@max}" if @max != :infinite && input.size > @max
29
+
30
+ return errors.empty?
31
+ end
32
+ end
33
+
34
+ DataType.register_type('String', String)
35
+ end
36
+ end
@@ -0,0 +1,100 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Struct < DataType
4
+ def initialize(spec)
5
+ @spec = ::Hash[spec.map do |k,v|
6
+ begin
7
+ k = DataType.new_from_string(k)
8
+ rescue ConfigurationException; end
9
+ begin
10
+ v = DataType.new_from_string(v)
11
+ rescue ConfigurationException; end
12
+ [k, v]
13
+ end]
14
+ end
15
+
16
+ def multivalued?
17
+ true
18
+ end
19
+
20
+ def to_s
21
+ "struct containing " + @spec.keys.map do |k|
22
+ if k.is_a?(Optional)
23
+ [k.inner_value, %{"#{k.inner_value}" (optional #{@spec[k]})}]
24
+ elsif k.is_a?(NotUndef)
25
+ [k.inner_value, %{"#{k.inner_value}" (required #{@spec[k]})}]
26
+ else
27
+ [k, %{"#{k}" (#{@spec[k]})}]
28
+ end
29
+ end.sort_by(&:first).map(&:last).join(', ')
30
+ end
31
+
32
+ def typecast(value)
33
+ if value.nil?
34
+ nil
35
+ elsif value.is_a?(::Hash)
36
+ value
37
+ elsif value == ['EMPTY_HASH']
38
+ {}
39
+ else
40
+ ::Hash[[value].flatten.map do |kv|
41
+ k, v = kv.split(':', 2)
42
+ if (value_type = spec_value(k))
43
+ [k, value_type.typecast(v)]
44
+ else
45
+ [k, v]
46
+ end
47
+ end]
48
+ end
49
+ end
50
+
51
+ def valid?(input, errors = [])
52
+ unless input.is_a?(::Hash)
53
+ errors << "#{input.inspect} is not a valid struct"
54
+ return false
55
+ end
56
+
57
+ required_keys = @spec.keys.select { |k| k.is_a?(NotUndef) }.map { |k| k.inner_value }
58
+ missing_keys = required_keys - input.keys
59
+ errors << "Struct elements are missing: #{missing_keys.join(', ')}" unless missing_keys.empty?
60
+
61
+ known_keys = @spec.keys.map { |k| spec_key_name(k) }
62
+ extra_keys = input.keys - known_keys
63
+ errors << "Struct elements are not permitted: #{extra_keys.join(', ')}" unless extra_keys.empty?
64
+
65
+ value_errors = []
66
+
67
+ # Only check values for optional keys if present
68
+ optional_keys = @spec.keys.select { |k| k.is_a?(Optional) }.map { |k| k.inner_value }
69
+ (optional_keys & input.keys).each { |k| spec_value(k).valid?(input[k], value_errors) }
70
+
71
+ # For non-optional and non-required keys, assume nil/undef values if absent
72
+ regular_keys = @spec.keys.select { |k| !k.is_a?(Optional) }.map { |k| spec_key_name(k) }
73
+ regular_keys.each { |k| spec_value(k).valid?(input[k], value_errors) }
74
+
75
+ errors << "Struct values are invalid: #{value_errors.join(', ')}" unless value_errors.empty?
76
+
77
+ return errors.empty?
78
+ end
79
+
80
+ private
81
+
82
+ def spec_value(key)
83
+ spec_entry = @spec.find do |k,v|
84
+ spec_key_name(k) == key
85
+ end
86
+ spec_entry ? spec_entry.last : nil
87
+ end
88
+
89
+ def spec_key_name(key)
90
+ if key.is_a?(Optional) || key.is_a?(NotUndef)
91
+ key.inner_value
92
+ else
93
+ key
94
+ end
95
+ end
96
+ end
97
+
98
+ DataType.register_type('Struct', Struct)
99
+ end
100
+ end
@@ -0,0 +1,76 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Tuple < DataType
4
+ def initialize(*args)
5
+ if args.last.to_s =~ /\d+/ || args.last.to_s == 'default'
6
+ max = args.pop
7
+ min = args.pop
8
+ else
9
+ max = :default
10
+ min = :default
11
+ end
12
+ @max = (max.to_s == 'default') ? :infinite : max.to_i
13
+ @min = (min.to_s == 'default') ? 0 : min.to_i
14
+ @inner_types = args.map { |type| DataType.new_from_string(type) }
15
+ end
16
+
17
+ def condition_value(value)
18
+ "[ #{value.each_with_index.map { |v,i| inner_types(value)[i].condition_value(v) }.join(', ')} ]"
19
+ end
20
+
21
+ def multivalued?
22
+ true
23
+ end
24
+
25
+ def to_s
26
+ type = "tuple of #{@inner_types.join(', ')}"
27
+ if @min > 0 && @max == :infinite
28
+ "#{type} (at least #{@min} items)"
29
+ elsif @min == 0 && @max != :infinite
30
+ "#{type} (up to #{@max} items)"
31
+ elsif @min > 0 && @max != :infinite
32
+ "#{type} (between #{@min} and #{@max} items)"
33
+ else
34
+ type
35
+ end
36
+ end
37
+
38
+ def typecast(value)
39
+ if value.nil?
40
+ nil
41
+ elsif value == ['EMPTY_ARRAY']
42
+ []
43
+ else
44
+ values = [value].flatten
45
+ values.each_with_index.map { |v,i| inner_types(values)[i].typecast(v) }
46
+ end
47
+ end
48
+
49
+ def valid?(input, errors = [])
50
+ unless input.is_a?(::Array)
51
+ errors << "#{input.inspect} is not a valid tuple"
52
+ return false
53
+ end
54
+
55
+ inner_errors = []
56
+ input.each_with_index { |v,i| inner_types(input)[i].valid?(v, inner_errors) }
57
+ unless inner_errors.empty?
58
+ errors << "Elements of the tuple are invalid: #{inner_errors.join(', ')}"
59
+ end
60
+
61
+ errors << "The tuple must have at least #{@min} items" if input.size < @min
62
+ errors << "The tuple must have at maximum #{@max} items" if @max != :infinite && input.size > @max
63
+
64
+ return errors.empty?
65
+ end
66
+
67
+ private
68
+
69
+ def inner_types(value)
70
+ @inner_types + ([@inner_types.last] * (value.size - @inner_types.size))
71
+ end
72
+ end
73
+
74
+ DataType.register_type('Tuple', Tuple)
75
+ end
76
+ end
@@ -0,0 +1,14 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class TypeReference < DataType
4
+ extend Forwardable
5
+ def_delegators :@inner_type, :condition_value, :dump_default, :multivalued?, :to_s, :typecast, :valid?
6
+
7
+ def initialize(inner_type)
8
+ @inner_type = DataType.new_from_string(inner_type)
9
+ end
10
+ end
11
+
12
+ DataType.register_type('TypeReference', TypeReference)
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Undef < DataType
4
+ def valid?(input, errors = [])
5
+ errors << "#{input} must be undef" unless input.nil?
6
+ return errors.empty?
7
+ end
8
+ end
9
+
10
+ DataType.register_type('Undef', Undef)
11
+ end
12
+ end
@@ -0,0 +1,50 @@
1
+ module Kafo
2
+ module DataTypes
3
+ class Variant < DataType
4
+ def initialize(*inner_types)
5
+ @inner_types = inner_types.map { |t| DataType.new_from_string(t) }
6
+ end
7
+
8
+ def condition_value(value)
9
+ type = find_type(value)
10
+ type ? type.condition_value(value) : super(value)
11
+ end
12
+
13
+ def dump_default(value)
14
+ type = find_type(value)
15
+ type ? type.dump_default(value) : super(value)
16
+ end
17
+
18
+ def multivalued?
19
+ @inner_types.any? { |t| t.multivalued? }
20
+ end
21
+
22
+ def to_s
23
+ @inner_types.join(' or ')
24
+ end
25
+
26
+ def typecast(value)
27
+ type = find_type(value)
28
+ type ? type.typecast(value) : value
29
+ end
30
+
31
+ def valid?(value, errors = [])
32
+ type = find_type(value)
33
+ if type
34
+ type.valid?(value, errors)
35
+ else
36
+ errors << "#{value} is not one of #{to_s}"
37
+ false
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def find_type(value)
44
+ @inner_types.find { |t| t.valid?(t.typecast(value)) }
45
+ end
46
+ end
47
+
48
+ DataType.register_type('Variant', Variant)
49
+ end
50
+ end
@@ -1,3 +1,5 @@
1
+ require 'kafo/data_type'
2
+
1
3
  module Kafo
2
4
  class HookContext
3
5
  attr_reader :kafo
@@ -197,10 +197,6 @@ module Kafo
197
197
  self.class.exit(:unknown_module)
198
198
  end
199
199
 
200
- def enabled_params
201
- params.select { |p| p.module.enabled? }
202
- end
203
-
204
200
  def reset_params_cache
205
201
  @params = nil
206
202
  params
@@ -395,7 +391,7 @@ module Kafo
395
391
 
396
392
  def validate_all(logging = true)
397
393
  logger.info 'Running validation checks'
398
- results = enabled_params.map do |param|
394
+ results = params.map do |param|
399
395
  result = param.valid?
400
396
  errors = param.validation_errors.join(', ')
401
397
  progress_log(:error, "Parameter #{with_prefix(param)} invalid: #{errors}") if logging && !result
@@ -1,15 +1,17 @@
1
1
  # encoding: UTF-8
2
2
  require 'kafo/condition'
3
+ require 'kafo/validator'
3
4
 
4
5
  module Kafo
5
6
  class Param
6
- attr_reader :name, :module
7
+ attr_reader :name, :module, :type
7
8
  attr_accessor :default, :doc, :value_set, :condition
8
9
  attr_writer :groups
9
10
 
10
- def initialize(builder, name)
11
+ def initialize(builder, name, type)
11
12
  @name = name
12
13
  @module = builder
14
+ @type = DataType.new_from_string(type)
13
15
  end
14
16
 
15
17
  def groups
@@ -18,12 +20,13 @@ module Kafo
18
20
 
19
21
  # we use @value_set flag because even nil can be valid value
20
22
  def value
21
- @value_set ? @value : default
23
+ @value_set ? @type.typecast(@value) : default
22
24
  end
23
25
 
24
26
  def value=(value)
25
27
  @value_set = true
26
- @value = value == 'UNDEF' ? nil : normalize_value(value)
28
+ value = value.to_s if value.is_a?(::HighLine::String) # don't persist highline extensions
29
+ @value = value
27
30
  end
28
31
 
29
32
  def unset_value
@@ -32,7 +35,7 @@ module Kafo
32
35
  end
33
36
 
34
37
  def dump_default
35
- default
38
+ @type.dump_default(default)
36
39
  end
37
40
 
38
41
  def module_name
@@ -40,7 +43,7 @@ module Kafo
40
43
  end
41
44
 
42
45
  def to_s
43
- "#<#{self.class}:#{self.object_id} @name=#{name.inspect} @default=#{default.inspect} @value=#{value.inspect}>"
46
+ "#<#{self.class}:#{self.object_id} @name=#{name.inspect} @default=#{default.inspect} @value=#{value.inspect} @type=#{@type}>"
44
47
  end
45
48
 
46
49
  def set_default(defaults)
@@ -86,22 +89,23 @@ module Kafo
86
89
  {:name => v.name, :arguments => interpret_validation_args(args)}
87
90
  end
88
91
 
92
+ # run old style validation functions
89
93
  @validator = Validator.new
90
94
  validations.each { |v| @validator.send(v[:name], v[:arguments]) }
91
- @validator.errors.empty?
95
+ @validation_errors = @validator.errors.dup
96
+
97
+ # run data type based validations, append errors
98
+ @type.valid?(value, @validation_errors)
99
+
100
+ @validation_errors.empty?
92
101
  end
93
102
 
94
103
  def validation_errors
95
- if @validator
96
- @validator.errors
97
- else
98
- []
99
- end
104
+ @validation_errors || []
100
105
  end
101
106
 
102
- # To be overwritten in children
103
107
  def multivalued?
104
- false
108
+ @type.multivalued?
105
109
  end
106
110
 
107
111
  def <=> o
@@ -117,7 +121,7 @@ module Kafo
117
121
  end
118
122
 
119
123
  def condition_value
120
- value.to_s
124
+ @type.condition_value(value)
121
125
  end
122
126
 
123
127
  private
@@ -139,25 +143,7 @@ module Kafo
139
143
  arg == :undef ? nil : arg
140
144
  end
141
145
  end
142
-
143
- def normalize_value(value)
144
- case value
145
- when ::HighLine::String # don't persist highline extensions
146
- value.to_s
147
- when Array
148
- value.map { |v| normalize_value(v) }
149
- when Hash
150
- Hash[value.map { |k,v| [normalize_value(k), normalize_value(v)] }]
151
- else
152
- value
153
- end
154
- end
155
146
  end
156
147
  end
157
148
 
158
- require 'kafo/params/boolean'
159
- require 'kafo/params/string'
160
149
  require 'kafo/params/password'
161
- require 'kafo/params/array'
162
- require 'kafo/params/hash'
163
- require 'kafo/params/integer'
@@ -52,7 +52,14 @@ module Kafo
52
52
  end
53
53
 
54
54
  def build(name, data)
55
- param = get_type(data[:types][name]).new(@module, name)
55
+ data_type = data[:types][name] || 'Data'
56
+ if data_type == 'password'
57
+ type = Params::Password
58
+ data_type = 'String'
59
+ else
60
+ type = Param
61
+ end
62
+ param = type.new(@module, name, data_type)
56
63
  param.default = data[:values][name]
57
64
  param.doc = data[:docs][name]
58
65
  param.groups = data[:groups][name]
@@ -77,10 +84,5 @@ module Kafo
77
84
  end
78
85
  param_group
79
86
  end
80
-
81
- def get_type(type)
82
- type = (type || 'string').capitalize
83
- Params.const_defined?(type) ? Params.const_get(type) : raise(TypeError, "undefined parameter type '#{type}'")
84
- end
85
87
  end
86
88
  end
@@ -19,14 +19,6 @@ module Kafo
19
19
  @encrypted
20
20
  end
21
21
 
22
- def condition_value
23
- %{"#{value}"}
24
- end
25
-
26
- def dump_default
27
- %{"#{super}"}
28
- end
29
-
30
22
  private
31
23
 
32
24
  def setup_password
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
  module Kafo
3
3
  PARSER_CACHE_VERSION = 1
4
- VERSION = "0.9.8"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -124,6 +124,7 @@ END
124
124
  def configure(param)
125
125
  say "\n" + HighLine.color("Parameter #{param.name} (of module #{param.module.name})", :headline)
126
126
  say HighLine.color(param.doc.join("\n").gsub('"', '\"'), :important)
127
+ say HighLine.color("Data type: #{param.type}", :important)
127
128
  value = param.multivalued? ? configure_multi(param) : configure_single(param)
128
129
  value_was = param.value
129
130
  param.value = value unless value.empty?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kafo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.8
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marek Hulan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-12 00:00:00.000000000 Z
11
+ date: 2016-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -220,6 +220,26 @@ files:
220
220
  - lib/kafo/color_scheme.rb
221
221
  - lib/kafo/condition.rb
222
222
  - lib/kafo/configuration.rb
223
+ - lib/kafo/data_type.rb
224
+ - lib/kafo/data_types/any.rb
225
+ - lib/kafo/data_types/array.rb
226
+ - lib/kafo/data_types/boolean.rb
227
+ - lib/kafo/data_types/enum.rb
228
+ - lib/kafo/data_types/float.rb
229
+ - lib/kafo/data_types/hash.rb
230
+ - lib/kafo/data_types/integer.rb
231
+ - lib/kafo/data_types/not_undef.rb
232
+ - lib/kafo/data_types/numeric.rb
233
+ - lib/kafo/data_types/optional.rb
234
+ - lib/kafo/data_types/pattern.rb
235
+ - lib/kafo/data_types/regexp.rb
236
+ - lib/kafo/data_types/scalar.rb
237
+ - lib/kafo/data_types/string.rb
238
+ - lib/kafo/data_types/struct.rb
239
+ - lib/kafo/data_types/tuple.rb
240
+ - lib/kafo/data_types/type_reference.rb
241
+ - lib/kafo/data_types/undef.rb
242
+ - lib/kafo/data_types/variant.rb
223
243
  - lib/kafo/exceptions.rb
224
244
  - lib/kafo/exit_handler.rb
225
245
  - lib/kafo/help_builder.rb
@@ -236,12 +256,7 @@ files:
236
256
  - lib/kafo/param.rb
237
257
  - lib/kafo/param_builder.rb
238
258
  - lib/kafo/param_group.rb
239
- - lib/kafo/params/array.rb
240
- - lib/kafo/params/boolean.rb
241
- - lib/kafo/params/hash.rb
242
- - lib/kafo/params/integer.rb
243
259
  - lib/kafo/params/password.rb
244
- - lib/kafo/params/string.rb
245
260
  - lib/kafo/parser_cache_reader.rb
246
261
  - lib/kafo/parser_cache_writer.rb
247
262
  - lib/kafo/password_manager.rb
@@ -1,28 +0,0 @@
1
- module Kafo
2
- module Params
3
- class Array < Param
4
- def value=(value)
5
- super
6
- if @value == ['EMPTY_ARRAY']
7
- @value = []
8
- else
9
- @value = typecast(@value)
10
- end
11
- end
12
-
13
- def multivalued?
14
- true
15
- end
16
-
17
- def condition_value
18
- "[ #{value.map(&:inspect).join(', ')} ]"
19
- end
20
-
21
- private
22
-
23
- def typecast(value)
24
- value.nil? ? nil : [value].flatten
25
- end
26
- end
27
- end
28
- end
@@ -1,27 +0,0 @@
1
- module Kafo
2
- module Params
3
- class Boolean < Param
4
- def value=(value)
5
- super
6
- @value = typecast(@value)
7
- end
8
-
9
- def dump_default
10
- %{"#{super}"}
11
- end
12
-
13
- private
14
-
15
- def typecast(value)
16
- case value
17
- when '0', 'false', 'f', 'n', false
18
- false
19
- when '1', 'true', 't', 'y', true
20
- true
21
- else
22
- value
23
- end
24
- end
25
- end
26
- end
27
- end
@@ -1,38 +0,0 @@
1
- module Kafo
2
- module Params
3
- class Hash < Param
4
- def value=(value)
5
- super
6
- if @value == ['EMPTY_HASH']
7
- @value = {}
8
- else
9
- @value = typecast(@value)
10
- end
11
- end
12
-
13
- def multivalued?
14
- true
15
- end
16
-
17
- def condition_value
18
- value.inspect
19
- end
20
-
21
- private
22
-
23
- def typecast(value)
24
- if value.nil?
25
- nil
26
- elsif value.is_a?(::Hash)
27
- value
28
- else
29
- value = [value].flatten
30
- ::Hash[value.map { |v| v.split(':', 2) }]
31
- end
32
- rescue NoMethodError => e
33
- KafoConfigure.logger.warn "Could not typecast #{value} for parameter #{name}, defaulting to {}"
34
- return {}
35
- end
36
- end
37
- end
38
- end
@@ -1,19 +0,0 @@
1
- module Kafo
2
- module Params
3
- class Integer < Param
4
- def value=(value)
5
- super
6
- @value = typecast(@value)
7
- end
8
-
9
- private
10
-
11
- def typecast(value)
12
- value.nil? ? nil : value.to_i
13
- rescue NoMethodError => e
14
- KafoConfigure.logger.warn "Could not typecast #{value} for parameter #{name}, defaulting to 0"
15
- return 0
16
- end
17
- end
18
- end
19
- end
@@ -1,13 +0,0 @@
1
- module Kafo
2
- module Params
3
- class String < Param
4
- def condition_value
5
- %{"#{value}"}
6
- end
7
-
8
- def dump_default
9
- %{"#{super}"}
10
- end
11
- end
12
- end
13
- end