cli_miami 0.0.9 → 1.0.1.pre

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.
@@ -0,0 +1,189 @@
1
+ #
2
+ # validates user input values against their expected type
3
+ #
4
+ class CliMiami::Validation
5
+ attr_reader :error, :valid, :value
6
+
7
+ # validates string provided by the user
8
+ #
9
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
+ def initialize initial_value, options
11
+ @options = CliMiami.get_options options
12
+ @value = initial_value
13
+ @valid = true
14
+ @error = nil
15
+
16
+ # convert
17
+ # only attempt to convert strings
18
+ # if not a string, skip conversion and go straight to validation
19
+ if initial_value.is_a? String
20
+ begin
21
+ @value = send("convert_string_to_#{options[:type]}", initial_value, @options)
22
+ rescue
23
+ @error = CliMiami::Error.new(initial_value, @options, :convert).message
24
+ @valid = false
25
+ end
26
+ end
27
+
28
+ return if @value.nil? || !@valid
29
+
30
+ # validate
31
+ begin
32
+ # keep all values the same unless validation fails
33
+ send "validate_#{options[:type]}", @value, @options
34
+ rescue => error
35
+ # if validation fails, set values, including error to the specific validation error
36
+ @error = error.to_s
37
+ @valid = false
38
+ end
39
+ end
40
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
41
+
42
+ def valid?
43
+ @valid
44
+ end
45
+
46
+ private
47
+
48
+ #
49
+ # TYPE CONVERSIONS
50
+ # all type conversions must raise an exception if they fail
51
+ #
52
+ def convert_string_to_boolean string, _options
53
+ return true if CliMiami::BOOLEAN_TRUE_VALUES.include? string.downcase
54
+ return false if CliMiami::BOOLEAN_FALSE_VALUES.include? string.downcase
55
+ raise
56
+ end
57
+
58
+ def convert_string_to_file string, _options
59
+ return string if string.empty?
60
+ File.absolute_path(File.expand_path(string))
61
+ end
62
+
63
+ # the `Integer` class fails when passed a float, so we convert to a float and back to an integer
64
+ # this allows users to enter a float when an integer is requested without it failing
65
+ #
66
+ def convert_string_to_fixnum string, _options
67
+ Integer Float(string).to_i
68
+ end
69
+
70
+ def convert_string_to_float string, _options
71
+ Float string
72
+ end
73
+
74
+ def convert_string_to_range string, _options
75
+ range_array = string.split('..').map { |i| Float(i) }.sort
76
+
77
+ # check that array has 2 supported range values
78
+ return Range.new(range_array[0], range_array[1]) if range_array.length == 2
79
+ raise
80
+ end
81
+
82
+ # pass-through method for converting a string to a string
83
+ def convert_string_to_string string, _options
84
+ string
85
+ end
86
+
87
+ def convert_string_to_symbol string, _options
88
+ raw_symbol = string.underscore.to_sym
89
+
90
+ # if the symbol is quoted, remove some characters and try converting again
91
+ if raw_symbol.inspect =~ /\:\"/
92
+ # convert non a-z to underscores, then remove duplicate or leading/trailing underscores
93
+ converted_symbol = raw_symbol.to_s.gsub(/[^a-z]/, '_').gsub(/\_+/, '_').gsub(/^_/, '').gsub(/_$/, '').to_sym
94
+
95
+ # if symbol was converted, return it, otherwise raise an error
96
+ return converted_symbol unless converted_symbol.empty?
97
+ raise
98
+ end
99
+
100
+ raw_symbol
101
+ end
102
+
103
+ #
104
+ # TYPE VALIDATORS
105
+ # all type validators must raise an exception if they fail
106
+ #
107
+
108
+ # validate the value is within the min and max values
109
+ #
110
+ def validate_length value, length, options
111
+ unless (options[:min]..options[:max]).cover? length
112
+ options[:value_length] = length
113
+
114
+ # replace empty values with i18n `empty` string
115
+ value = I18n.t('cli_miami.core.empty') if value.respond_to?(:empty?) && value.empty?
116
+
117
+ raise CliMiami::Error.new(value, options, :length).message
118
+ end
119
+ end
120
+
121
+ # validate that the value passes a regular expression
122
+ #
123
+ def validate_regexp value, options
124
+ unless value =~ options[:regexp]
125
+ raise CliMiami::Error.new(value, options, :regexp).message
126
+ end
127
+ end
128
+
129
+ def validate_array array, options
130
+ validate_length array, array.length, options
131
+ end
132
+
133
+ # no validation needed for boolean types
134
+ # type conversion is all the validation required
135
+ def validate_boolean boolean, options
136
+ end
137
+
138
+ def validate_file file, options
139
+ raise if file.empty?
140
+ File.new File.expand_path file
141
+ rescue
142
+ raise CliMiami::Error.new(file, options, :validate).message
143
+ end
144
+
145
+ def validate_fixnum fixnum, options
146
+ validate_length fixnum, fixnum, options
147
+ end
148
+
149
+ def validate_float float, options
150
+ validate_length float, float, options
151
+ end
152
+
153
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
154
+ def validate_hash hash, options
155
+ hash.deep_symbolize_keys!
156
+
157
+ validate_length hash, hash.keys.length, options
158
+
159
+ # return if no value options are set
160
+ value_options = options[:value_options]
161
+ return unless value_options
162
+
163
+ # validate required keys are set
164
+ if value_options[:keys]
165
+ missing_keys = Set.new(value_options[:keys].map(&:to_sym)) - Set.new(hash.keys)
166
+ value_options[:missing_values] = missing_keys.to_a.to_sentence
167
+
168
+ unless missing_keys.empty?
169
+ raise CliMiami::Error.new(hash, options, :keys).message
170
+ end
171
+ end
172
+ end
173
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
174
+
175
+ def validate_range range, options
176
+ range_diff = (range.max - range.min) + 1
177
+ validate_length range, range_diff, options
178
+ end
179
+
180
+ def validate_string string, options
181
+ validate_length string, string.length, options
182
+ validate_regexp string, options
183
+ end
184
+
185
+ def validate_symbol symbol, options
186
+ validate_length symbol, symbol.to_s.length, options
187
+ validate_regexp symbol, options
188
+ end
189
+ end
data/lib/cli_miami.rb CHANGED
@@ -7,6 +7,6 @@
7
7
  # the core initilization runs from `namespaced.rb`
8
8
  require 'namespaced'
9
9
 
10
- # create alias classes to support `A.sk` and `S.ay`
10
+ # create alias classes to support `A.sk` and `S.ay` syntax
11
11
  A = CliMiami::A
12
12
  S = CliMiami::S
data/lib/namespaced.rb CHANGED
@@ -1,30 +1,80 @@
1
- # This is the primary initializer
2
-
1
+ # root namespace class
2
+ #
3
3
  class CliMiami
4
+ # require & include core first
5
+ require 'cli_miami/core'
6
+ include CliMiami::Core
7
+
8
+ # CliMiami library
9
+ require 'cli_miami/error'
10
+ require 'cli_miami/validation'
4
11
  require 'cli_miami/ask'
5
12
  require 'cli_miami/say'
6
13
 
7
- # default presets
8
- @@presets = {
9
- :fail => {
10
- :color => :red
11
- },
12
- :warn => {
13
- :color => :yellow
14
- },
15
- :success => {
16
- :color => :green
17
- }
18
- }
19
-
20
- # Returns all presets
21
- def self.presets
22
- @@presets
23
- end
24
-
25
14
  # Create a new custom preset
26
15
  def self.set_preset type, options
27
- raise 'Preset must be a hash of options' unless options.is_a? Hash
16
+ raise ArgumentError, 'Preset must be a hash of options' unless options.is_a? Hash
28
17
  @@presets[type] = options
29
18
  end
19
+
20
+ # build an options hash from preset options and/or additional options
21
+ #
22
+ # build shared validation hash object
23
+ #
24
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
25
+ def self.get_options user_options = {}
26
+ # lookup preset if passed in as a symbol, or as a :preset key in an options hash
27
+ if user_options.is_a? Hash
28
+ user_options.deep_symbolize_keys!
29
+ options = @@presets[user_options.delete(:preset)] || {}
30
+ options.deep_merge! user_options
31
+ else
32
+ options = @@presets[user_options.to_sym] || {}
33
+ end
34
+
35
+ # make sure all keys are symbols
36
+ options.deep_symbolize_keys!
37
+
38
+ # set defaults
39
+ options.reverse_merge!(
40
+ description: I18n.t('cli_miami.core.no_description'),
41
+ max: Float::INFINITY,
42
+ min: options.delete(:required) ? 1 : 0,
43
+ newline: true,
44
+ justify: :left,
45
+ padding: 0,
46
+ regexp: //,
47
+ style: [],
48
+ type: :string
49
+ )
50
+
51
+ # prepare style array
52
+ options[:style] = [options[:style]].flatten
53
+
54
+ # lookup type in type map
55
+ options[:type] = TYPE_MAP[options[:type].to_sym] || :string
56
+
57
+ # convert range to min/max values
58
+ range = options.delete(:range)
59
+ if range
60
+ options[:min] = range.min
61
+ options[:max] = range.max
62
+ end
63
+
64
+ # convert length to min/max values
65
+ length = options.delete(:length)
66
+ if length
67
+ options[:min] = options[:max] = length
68
+ end
69
+
70
+ # if value options are set, apply the same treatment to them
71
+ value_options = options[:value_options]
72
+ if value_options
73
+ options[:value_options] = get_options value_options
74
+ options[:value_options][:description] = options[:description]
75
+ end
76
+
77
+ options
78
+ end
79
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
30
80
  end
@@ -0,0 +1,36 @@
1
+ en:
2
+ cli_miami:
3
+ errors:
4
+ spec: generic error
5
+
6
+ type:
7
+ spec: "type: %{value} - %{allowed_values}"
8
+ nested:
9
+ spec: nested spec
10
+
11
+ array:
12
+ spec: "array: %{value} - %{allowed_values}"
13
+
14
+ boolean:
15
+ spec: "boolean: %{value} - %{allowed_values}"
16
+
17
+ file:
18
+ spec: "file: %{value} - %{allowed_values}"
19
+
20
+ fixnum:
21
+ spec: "fixnum: %{value} - %{allowed_values}"
22
+
23
+ float:
24
+ spec: "float: %{value} - %{allowed_values}"
25
+
26
+ hash:
27
+ spec: "hash: %{value} - %{allowed_values}"
28
+
29
+ range:
30
+ spec: "range: %{value} - %{allowed_values}"
31
+
32
+ string:
33
+ spec: "string: %{value} - %{allowed_values}"
34
+
35
+ symbol:
36
+ spec: "symbol: %{value} - %{allowed_values}"
@@ -0,0 +1,134 @@
1
+ describe CliMiami::A do
2
+ before do
3
+ @q = 'Who am i?'
4
+ allow(CliMiami::S).to receive(:ay).and_call_original
5
+ allow_any_instance_of(CliMiami::A).to receive(:request_string).and_return 'Jane Doe'
6
+ end
7
+
8
+ after do
9
+ allow_any_instance_of(CliMiami::A).to receive(:request_string).and_call_original
10
+ end
11
+
12
+ describe '.sk' do
13
+ context 'when passed a block' do
14
+ it 'should yield a response' do
15
+ CliMiami::A.sk @q do |response|
16
+ expect(response).to eq 'Jane Doe'
17
+ end
18
+ end
19
+ end
20
+
21
+ context 'when not passed a block' do
22
+ it 'should return a response' do
23
+ expect(CliMiami::A.sk(@q).value).to eq 'Jane Doe'
24
+ end
25
+ end
26
+ end
27
+
28
+ #
29
+ # COMPLEX REQUEST TYPES
30
+ #
31
+ describe 'ARRAY' do
32
+ it 'should allow user to enter values until they enter an empty string' do
33
+ allow($stdin).to receive(:gets).and_return 'foo', 'bar', ''
34
+ ask = CliMiami::A.sk @q, type: :array
35
+ expect(ask.value).to eq %w(foo bar)
36
+ end
37
+
38
+ it 'should prevent user from entering less than the minimum' do
39
+ allow($stdin).to receive(:gets).and_return 'foo', '', 'bar', ''
40
+ ask = CliMiami::A.sk @q, type: :array, min: 2
41
+ expect(ask.value).to eq %w(foo bar)
42
+ end
43
+
44
+ it 'should stop prompting user when the maximum is reached' do
45
+ allow($stdin).to receive(:gets).and_return 'foo', 'bar'
46
+ ask = CliMiami::A.sk @q, type: :array, max: 2
47
+ expect(ask.value).to eq %w(foo bar)
48
+ end
49
+ end
50
+
51
+ describe 'HASH' do
52
+ it 'should allow user to enter keys & values until they enter an empty key string' do
53
+ allow($stdin).to receive(:gets).and_return 'foo1', '1', 'bar1', '2', ''
54
+ ask = CliMiami::A.sk @q, type: :hash
55
+ expect(ask.value).to eq(foo1: '1', bar1: '2')
56
+ end
57
+
58
+ it 'should prevent user from entering less than the minimum' do
59
+ allow($stdin).to receive(:gets).and_return 'foo2', '1', '', 'bar2', '2', ''
60
+ ask = CliMiami::A.sk @q, type: :hash, min: 2
61
+ expect(ask.value).to eq(foo2: '1', bar2: '2')
62
+ end
63
+
64
+ it 'should stop prompting user when the maximum is reached' do
65
+ allow($stdin).to receive(:gets).and_return 'foo3', '1'
66
+ ask = CliMiami::A.sk @q, type: :hash, max: 1
67
+ expect(ask.value).to eq(foo3: '1')
68
+ end
69
+
70
+ context 'when keys options is set' do
71
+ it 'should prompt for keys first, then user-defined keys' do
72
+ allow($stdin).to receive(:gets).and_return '1', 'bar4', '2', ''
73
+ ask = CliMiami::A.sk @q, type: :hash, keys: [:foo4]
74
+ expect(ask.value).to eq(foo4: '1', bar4: '2')
75
+ end
76
+ end
77
+ end
78
+
79
+ describe 'RANGE' do
80
+ it 'should allow user to enter floats or integers for start and end values' do
81
+ allow($stdin).to receive(:gets).and_return '1.1', '3'
82
+ ask = CliMiami::A.sk @q, type: :range
83
+ expect(ask.value).to eq((1.1..3.0))
84
+ end
85
+
86
+ it 'should prevent user from entering non-numeric values' do
87
+ allow($stdin).to receive(:gets).and_return 'foo', '1', 'bar', '3'
88
+ ask = CliMiami::A.sk @q, type: :range
89
+ expect(ask.value).to eq((1.0..3.0))
90
+ end
91
+
92
+ it 'should re-prompt user if range is invalid' do
93
+ allow($stdin).to receive(:gets).and_return '1', '3', '1', '4'
94
+ ask = CliMiami::A.sk @q, type: :range, min: 4
95
+ expect(ask.value).to eq((1.0..4.0))
96
+ end
97
+ end
98
+
99
+ #
100
+ # SIMPLE REQUEST TYPES
101
+ #
102
+ context 'with simple types' do
103
+ shared_examples 'when prompting the user' do |type, invalid_string, valid_string, value|
104
+ it 'should prompt until a valid value is entered' do
105
+ allow($stdin).to receive(:gets).and_return(invalid_string, valid_string)
106
+ allow(Readline).to receive(:readline).and_return(invalid_string, valid_string)
107
+ ask = CliMiami::A.sk type.to_s, type: type
108
+ expect(ask.value).to eq value
109
+ end
110
+ end
111
+
112
+ describe 'BOOLEAN' do
113
+ it_behaves_like 'when prompting the user', :boolean, 'foo', 'TrUe', true
114
+ end
115
+
116
+ describe 'FILE' do
117
+ it_behaves_like 'when prompting the user', :file, 'foo', '.', Dir.pwd
118
+ end
119
+
120
+ describe 'FLOAT' do
121
+ it_behaves_like 'when prompting the user', :float, 'foo', '2.0', 2.0
122
+ it_behaves_like 'when prompting the user', :float, 'foo', '3', 3
123
+ end
124
+
125
+ describe 'FIXNUM' do
126
+ it_behaves_like 'when prompting the user', :fixnum, 'foo', '2', 2
127
+ it_behaves_like 'when prompting the user', :fixnum, 'foo', '3.8', 3
128
+ end
129
+
130
+ describe 'SYMBOL' do
131
+ it_behaves_like 'when prompting the user', :symbol, '$', '$f_ 0o8<0o', :f_o_o
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,75 @@
1
+ describe CliMiami::Error do
2
+ describe '.initialize' do
3
+ context 'with valid type error' do
4
+ CliMiami::TYPE_MAP.values.uniq.each do |type|
5
+ case type
6
+
7
+ when :array
8
+ value_type = [1, nil, :foo, '', (1..2)]
9
+ value_string = '1, [EMPTY], foo, [EMPTY], and 1-2'
10
+ allowed_values = '1-38'
11
+
12
+ when :boolean
13
+ value_type = true
14
+ value_string = 'true'
15
+ allowed_values = (CliMiami::BOOLEAN_TRUE_VALUES + CliMiami::BOOLEAN_FALSE_VALUES).to_sentence
16
+
17
+ when :file
18
+ value_type = File.new('.')
19
+ value_string = File.expand_path('.')
20
+ allowed_values = '?'
21
+
22
+ when :fixnum
23
+ value_type = 6
24
+ value_string = '6'
25
+ allowed_values = '1-38'
26
+
27
+ when :float
28
+ value_type = 6.66
29
+ value_string = '6.66'
30
+ allowed_values = '1-38'
31
+
32
+ when :hash
33
+ value_type = { foo: 1, bar: 2 }
34
+ value_string = 'foo: 1 and bar: 2'
35
+ allowed_values = '1-38'
36
+
37
+ when :range
38
+ value_type = (4..20)
39
+ value_string = '4-20'
40
+ allowed_values = '1-38'
41
+
42
+ when :string
43
+ value_type = 'foo'
44
+ value_string = 'foo'
45
+ allowed_values = '1-38'
46
+
47
+ when :symbol
48
+ value_type = :foo
49
+ value_string = 'foo'
50
+ allowed_values = '1-38'
51
+ end
52
+
53
+ it "should convert #{type} to string" do
54
+ expect(CliMiami::Error.new(value_type, { type: type, min: 1, max: 38, regexp: /6{3}/ }, :spec).message).to eq "#{type}: #{value_string} - #{allowed_values}"
55
+ end
56
+ end
57
+
58
+ it 'should support multiple/nested keys' do
59
+ expect(CliMiami::Error.new('foo', { type: :type }, :nested, :spec).message).to eq 'nested spec'
60
+ end
61
+ end
62
+
63
+ context 'with invalid type error' do
64
+ it 'should have a generic error' do
65
+ expect(CliMiami::Error.new('foo', { type: :foo }, :spec).message).to eq 'generic error'
66
+ end
67
+ end
68
+
69
+ context 'when no error is found' do
70
+ it 'should have an unknown error' do
71
+ expect(CliMiami::Error.new('foo', { type: :foo }, :bar).message).to eq 'Unknown Error'
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,87 @@
1
+ describe CliMiami::S do
2
+ subject { CliMiami::S }
3
+
4
+ describe '.ay' do
5
+ it 'should not accept no arguments' do
6
+ expect { subject.ay 'no arguments' }.to_not raise_error
7
+ end
8
+
9
+ context 'with options' do
10
+ it 'should accept color' do
11
+ expect($stdout).to receive(:puts).with "\e[31mcolor\e[0m"
12
+ subject.ay 'color', color: :red
13
+ end
14
+
15
+ it 'should accept bgcolor' do
16
+ expect($stdout).to receive(:puts).with "\e[41mbgcolor\e[0m"
17
+ subject.ay 'bgcolor', bgcolor: :red
18
+ end
19
+
20
+ it 'should accept justify / padding' do
21
+ expect($stdout).to receive(:puts).with(/^\s{1}justify\s{2}$/)
22
+ subject.ay 'justify', justify: :center, padding: 10
23
+ end
24
+
25
+ it 'should accept indent' do
26
+ expect($stdout).to receive(:puts).with(/^\s{10}indent$/)
27
+ subject.ay 'indent', indent: 10
28
+ end
29
+
30
+ it 'should accept newline' do
31
+ # when true, use puts
32
+ expect($stdout).to receive(:puts).with 'newline'
33
+ subject.ay 'newline', newline: true
34
+
35
+ # when false, use print
36
+ expect($stdout).to receive(:print).with 'newline '
37
+ subject.ay 'newline', newline: false
38
+ end
39
+
40
+ it 'should accept overwrite' do
41
+ expect($stdout).to receive(:print).with "overwrite\r "
42
+ subject.ay 'overwrite', overwrite: true
43
+ end
44
+
45
+ context 'with style' do
46
+ it 'should accept style' do
47
+ expect($stdout).to receive(:puts).with "\e[4mstyle\e[0m"
48
+ subject.ay 'style', style: :underline
49
+ end
50
+
51
+ it 'should apply bright style to color' do
52
+ expect($stdout).to receive(:puts).with "\e[91mbright\e[0m"
53
+ subject.ay 'bright', color: :red, style: :bright
54
+ end
55
+
56
+ it 'should accept multiple styles' do
57
+ expect($stdout).to receive(:puts).with "\e[4m\e[1m\e[91mmultiple\e[0m\e[0m\e[0m"
58
+ subject.ay 'multiple', color: :red, style: [:bright, :bold, :underline]
59
+ end
60
+ end
61
+ end
62
+
63
+ context 'with preset' do
64
+ before do
65
+ CliMiami.set_preset :preset_symbol,
66
+ color: :blue,
67
+ bgcolor: :white,
68
+ style: :underline
69
+ end
70
+
71
+ it 'should apply preset as symbol' do
72
+ expect($stdout).to receive(:puts).with "\e[4m\e[47m\e[34mpreset\e[0m\e[0m\e[0m"
73
+ subject.ay 'preset', :preset_symbol
74
+ end
75
+
76
+ it 'should apply preset as option' do
77
+ expect($stdout).to receive(:puts).with "\e[4m\e[47m\e[34mpreset\e[0m\e[0m\e[0m"
78
+ subject.ay 'preset', preset: :preset_symbol
79
+ end
80
+
81
+ it 'should override presets with additional options' do
82
+ expect($stdout).to receive(:puts).with "\e[4m\e[47m\e[31mpreset\e[0m\e[0m\e[0m"
83
+ subject.ay 'preset', preset: :preset_symbol, color: :red
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,52 @@
1
+ describe CliMiami::Validation do
2
+ describe '.initialize' do
3
+ shared_examples 'a valid type' do |type, valid_string, value|
4
+ it "and should return a #{type}" do
5
+ validation = CliMiami::Validation.new valid_string, type: type
6
+ expect(validation.value).to eq value
7
+ expect(validation.valid?).to eq true
8
+ end
9
+ end
10
+
11
+ shared_examples 'an invalid type' do |type, invalid_string|
12
+ it 'and should set the error field' do
13
+ validation = CliMiami::Validation.new invalid_string, type: type
14
+ expect(validation.value).to eq invalid_string
15
+ expect(validation.valid?).to eq false
16
+ expect(validation.error).to_not be_empty
17
+ end
18
+ end
19
+
20
+ describe 'BOOLEAN' do
21
+ it_behaves_like 'a valid type', :boolean, 'TrUe', true
22
+ it_behaves_like 'an invalid type', :boolean, 'foo'
23
+ end
24
+
25
+ describe 'FILE' do
26
+ it_behaves_like 'a valid type', :file, '.', Dir.pwd
27
+ it_behaves_like 'an invalid type', :file, File.join(Dir.pwd, 'foo')
28
+ end
29
+
30
+ describe 'FLOAT' do
31
+ it_behaves_like 'a valid type', :float, '2.0', 2.0
32
+ it_behaves_like 'a valid type', :float, '3', 3.0
33
+ it_behaves_like 'an invalid type', :float, 'foo'
34
+ end
35
+
36
+ describe 'FIXNUM' do
37
+ it_behaves_like 'a valid type', :fixnum, '3', 3
38
+ it_behaves_like 'a valid type', :fixnum, '3.8', 3
39
+ it_behaves_like 'an invalid type', :fixnum, 'foo'
40
+ end
41
+
42
+ describe 'RANGE' do
43
+ it_behaves_like 'a valid type', :range, '2..4', (2..4)
44
+ it_behaves_like 'an invalid type', :range, 'foo'
45
+ end
46
+
47
+ describe 'SYMBOL' do
48
+ it_behaves_like 'a valid type', :symbol, '$F 8o*: .o.!', :f_o_o
49
+ it_behaves_like 'an invalid type', :symbol, '$'
50
+ end
51
+ end
52
+ end