necromancer 0.1.0 → 0.2.0

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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +229 -19
  4. data/lib/necromancer.rb +13 -2
  5. data/lib/necromancer/context.rb +4 -0
  6. data/lib/necromancer/conversions.rb +2 -1
  7. data/lib/necromancer/converter.rb +11 -0
  8. data/lib/necromancer/converters/array.rb +29 -21
  9. data/lib/necromancer/converters/boolean.rb +40 -10
  10. data/lib/necromancer/converters/date_time.rb +33 -0
  11. data/lib/necromancer/converters/numeric.rb +88 -0
  12. data/lib/necromancer/converters/range.rb +13 -6
  13. data/lib/necromancer/version.rb +1 -1
  14. data/spec/spec_helper.rb +8 -0
  15. data/spec/unit/convert_spec.rb +31 -1
  16. data/spec/unit/converters/array/string_to_array_spec.rb +19 -1
  17. data/spec/unit/converters/boolean/boolean_to_integer_spec.rb +10 -0
  18. data/spec/unit/converters/boolean/integer_to_boolean_spec.rb +6 -0
  19. data/spec/unit/converters/boolean/string_to_boolean_spec.rb +19 -79
  20. data/spec/unit/converters/date_time/string_to_date_spec.rb +22 -0
  21. data/spec/unit/converters/date_time/string_to_datetime_spec.rb +32 -0
  22. data/spec/unit/converters/numeric/string_to_float_spec.rb +48 -0
  23. data/spec/unit/converters/{integer → numeric}/string_to_integer_spec.rb +28 -4
  24. data/spec/unit/converters/numeric/string_to_numeric_spec.rb +32 -0
  25. data/spec/unit/converters/range/string_to_range_spec.rb +25 -42
  26. data/spec/unit/register_spec.rb +17 -0
  27. metadata +17 -8
  28. data/lib/necromancer/converters/float.rb +0 -28
  29. data/lib/necromancer/converters/integer.rb +0 -48
  30. data/spec/unit/converters/float/string_to_float_spec.rb +0 -28
@@ -1,10 +1,14 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Necromancer
4
+ # Container for Boolean converter classes
4
5
  module BooleanConverters
6
+ TRUE_MATCHER = /^(yes|y|on|t(rue)?|1)$/i.freeze
7
+
8
+ FALSE_MATCHER = /^(no|n|off|f(alse)?|0)$/i.freeze
9
+
5
10
  # An object that converts a String to a Boolean
6
11
  class StringToBooleanConverter < Converter
7
-
8
12
  # Convert value to boolean type including range of strings
9
13
  #
10
14
  # @param [Object] value
@@ -19,32 +23,58 @@ module Necromancer
19
23
  # converter.call("False") # => false
20
24
  #
21
25
  # other values coerced to false are:
22
- # 0, f, F, FALSE, false, False, n, N, NO, no, No, on, ON
26
+ # 0, f, F, FALSE, false, False, n, N, NO, no, No, off, OFF
23
27
  #
24
28
  # @api public
25
29
  def call(value, options = {})
30
+ strict = options.fetch(:strict, false)
26
31
  case value.to_s
27
- when /^(yes|y|on|t(rue)?|1)$/i
28
- return true
29
- when /^(no|n|off|f(alse)?|0)$/i
30
- return false
31
- else
32
- fail ArgumentError, "Expected boolean type, got #{value}"
32
+ when TRUE_MATCHER then true
33
+ when FALSE_MATCHER then false
34
+ else strict ? fail_conversion_type(value) : value
33
35
  end
34
36
  end
35
37
  end
36
38
 
37
39
  # An object that converts an Integer to a Boolean
38
40
  class IntegerToBooleanConverter < Converter
41
+ # Convert integer to boolean
42
+ #
43
+ # @example
44
+ # converter.call(1) # => true
45
+ #
46
+ # @example
47
+ # converter.call(0) # => false
48
+ #
49
+ # @api public
39
50
  def call(value, options = {})
40
- !value.zero?
51
+ strict = options.fetch(:strict, false)
52
+ begin
53
+ !value.zero?
54
+ rescue
55
+ strict ? fail_conversion_type(value) : value
56
+ end
41
57
  end
42
58
  end
43
59
 
44
60
  # An object that converts a Boolean to an Integer
45
61
  class BooleanToIntegerConverter < Converter
62
+ # Convert boolean to integer
63
+ #
64
+ # @example
65
+ # converter.call(true) # => 1
66
+ #
67
+ # @example
68
+ # converter.call(false) # => 0
69
+ #
70
+ # @api public
46
71
  def call(value, options = {})
47
- value ? 1 : 0
72
+ strict = options.fetch(:strict, false)
73
+ if ['TrueClass', 'FalseClass'].include?(value.class.name)
74
+ value ? 1 : 0
75
+ else
76
+ strict ? fail_conversion_type(value) : value
77
+ end
48
78
  end
49
79
  end
50
80
 
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ # Container for Date converter classes
5
+ module DateTimeConverters
6
+ # An object that converts a String to a Date
7
+ class StringToDateConverter < Converter
8
+ def call(value, options = {})
9
+ strict = options.fetch(:strict, false)
10
+ Date.parse(value)
11
+ rescue
12
+ strict ? fail_conversion_type(value) : value
13
+ end
14
+ end
15
+
16
+ # An object that converts a String to a DateTime
17
+ class StringToDateTimeConverter < Converter
18
+ def call(value, options = {})
19
+ strict = options.fetch(:strict, false)
20
+ DateTime.parse(value)
21
+ rescue
22
+ strict ? fail_conversion_type(value) : value
23
+ end
24
+ end
25
+
26
+ def self.load(conversions)
27
+ conversions.register StringToDateConverter.new(:string, :date)
28
+ conversions.register NullConverter.new(:date, :date)
29
+ conversions.register StringToDateTimeConverter.new(:string, :datetime)
30
+ conversions.register NullConverter.new(:datetime, :datetime)
31
+ end
32
+ end # DateTimeConverters
33
+ end # Necromancer
@@ -0,0 +1,88 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ # Container for Numeric converter classes
5
+ module NumericConverters
6
+ INTEGER_MATCHER = /^[-+]?(\d+)$/.freeze
7
+
8
+ FLOAT_MATCHER = /^[-+]?(\d*)(\.\d+)?([eE]?[-+]?\d+)?$/.freeze
9
+
10
+ # An object that converts a String to an Integer
11
+ class StringToIntegerConverter < Converter
12
+ # Convert string value to integer
13
+ #
14
+ # @example
15
+ # converter.call('1abc') # => 1
16
+ #
17
+ # @api public
18
+ def call(value, options = {})
19
+ strict = options.fetch(:strict, false)
20
+ Integer(value)
21
+ rescue
22
+ strict ? fail_conversion_type(value) : value.to_i
23
+ end
24
+ end
25
+
26
+ # An object that converts an Integer to a String
27
+ class IntegerToStringConverter < Converter
28
+ # Convert integer value to string
29
+ #
30
+ # @example
31
+ # converter.call(1) # => '1'
32
+ #
33
+ # @api public
34
+ def call(value, _)
35
+ value.to_s
36
+ end
37
+ end
38
+
39
+ # An object that converts a String to a Float
40
+ class StringToFloatConverter < Converter
41
+ # Convert string to float value
42
+ #
43
+ # @example
44
+ # converter.call('1.2') # => 1.2
45
+ #
46
+ # @api public
47
+ def call(value, options = {})
48
+ strict = options.fetch(:strict, false)
49
+ Float(value)
50
+ rescue
51
+ strict ? fail_conversion_type(value) : value.to_f
52
+ end
53
+ end
54
+
55
+ # An object that converts a String to a Numeric
56
+ class StringToNumericConverter < Converter
57
+ # Convert string to numeric value
58
+ #
59
+ # @example
60
+ # converter.call('1.0') # => 1.0
61
+ #
62
+ # @example
63
+ # converter.call('1') # => 1
64
+ #
65
+ # @api public
66
+ def call(value, options = {})
67
+ strict = options.fetch(:strict, false)
68
+ case value
69
+ when INTEGER_MATCHER
70
+ StringToIntegerConverter.new(:string, :integer).call(value, options)
71
+ when FLOAT_MATCHER
72
+ StringToFloatConverter.new(:string, :float).call(value, options)
73
+ else
74
+ strict ? fail_conversion_type(value) : value
75
+ end
76
+ end
77
+ end
78
+
79
+ def self.load(conversions)
80
+ conversions.register StringToIntegerConverter.new(:string, :integer)
81
+ conversions.register IntegerToStringConverter.new(:integer, :string)
82
+ conversions.register NullConverter.new(:integer, :integer)
83
+ conversions.register StringToFloatConverter.new(:string, :float)
84
+ conversions.register NullConverter.new(:float, :float)
85
+ conversions.register StringToNumericConverter.new(:string, :numeric)
86
+ end
87
+ end # Conversion
88
+ end # Necromancer
@@ -3,6 +3,12 @@
3
3
  module Necromancer
4
4
  # Container for Range converter classes
5
5
  module RangeConverters
6
+ SINGLE_DIGIT_MATCHER = /^(\-?\d+)$/.freeze
7
+
8
+ DIGIT_MATCHER = /^(-?\d+?)(\.{2}\.?|-|,)(-?\d+)$/.freeze
9
+
10
+ LETTER_MATCHER = /^(\w)(\.{2}\.?|-|,)(\w)$/.freeze
11
+
6
12
  # An object that converts a String to a Range
7
13
  class StringToRangeConverter < Converter
8
14
  # Convert value to Range type with possible ranges
@@ -17,22 +23,23 @@ module Necromancer
17
23
  #
18
24
  # @api public
19
25
  def call(value, options = {})
20
- case value.to_s
21
- when /\A(\-?\d+)\Z/
26
+ strict = options.fetch(:strict, false)
27
+ case value
28
+ when SINGLE_DIGIT_MATCHER
22
29
  ::Range.new($1.to_i, $1.to_i)
23
- when /\A(-?\d+?)(\.{2}\.?|-|,)(-?\d+)\Z/
30
+ when DIGIT_MATCHER
24
31
  ::Range.new($1.to_i, $3.to_i, $2 == '...')
25
- when /\A(\w)(\.{2}\.?|-|,)(\w)\Z/
32
+ when LETTER_MATCHER
26
33
  ::Range.new($1.to_s, $3.to_s, $2 == '...')
27
34
  else
28
- fail ConversionTypeError, "#{value} could not be converted " \
29
- "from #{source} into Range type"
35
+ strict ? fail_conversion_type(value) : value
30
36
  end
31
37
  end
32
38
  end
33
39
 
34
40
  def self.load(conversions)
35
41
  conversions.register StringToRangeConverter.new(:string, :range)
42
+ conversions.register NullConverter.new(:range, :range)
36
43
  end
37
44
  end # RangeConverters
38
45
  end # Necromancer
@@ -1,5 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Necromancer
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end # Necromancer
@@ -42,4 +42,12 @@ RSpec.configure do |config|
42
42
  config.order = :random
43
43
 
44
44
  Kernel.srand config.seed
45
+
46
+ config.before :each do
47
+ [:UpcaseConverter].each do |class_name|
48
+ if Object.const_defined?(class_name)
49
+ Object.send(:remove_const, class_name)
50
+ end
51
+ end
52
+ end
45
53
  end
@@ -6,7 +6,7 @@ RSpec.describe Necromancer, '.convert' do
6
6
 
7
7
  subject(:converter) { described_class.new }
8
8
 
9
- context 'when integer' do
9
+ context 'when numeric' do
10
10
  it "converts string to integer" do
11
11
  expect(converter.convert('1').to(:integer)).to eq(1)
12
12
  end
@@ -28,6 +28,18 @@ RSpec.describe Necromancer, '.convert' do
28
28
  converter.convert('1a').to(:integer, strict: true)
29
29
  }.to raise_error(Necromancer::ConversionTypeError)
30
30
  end
31
+
32
+ it "doesn't raise error when in non-strict mode" do
33
+ expect(converter.convert('1').to(:integer, strict: false)).to eql(1)
34
+ end
35
+
36
+ it "converts string to float" do
37
+ expect(converter.convert('1.0').to(:float)).to eql(1.0)
38
+ end
39
+
40
+ it "converts string to numeric" do
41
+ expect(converter.convert('1.0').to(:numeric)).to eql(1.0)
42
+ end
31
43
  end
32
44
 
33
45
  context 'when boolean' do
@@ -62,5 +74,23 @@ RSpec.describe Necromancer, '.convert' do
62
74
  it "converts array to numeric " do
63
75
  expect(converter.convert(['1','2.3','3.0']).to(:numeric)).to eq([1,2.3,3.0])
64
76
  end
77
+
78
+ it "fails to convert in strict mode" do
79
+ expect {
80
+ converter.convert(['1', '2.3', false]).to(:numeric, strict: true)
81
+ }.to raise_error(Necromancer::ConversionTypeError)
82
+ end
83
+ end
84
+
85
+ context 'when datetime' do
86
+ it "converts string to date" do
87
+ expect(converter.convert('2014-12-07').to(:date)).
88
+ to eq(Date.parse('2014-12-07'))
89
+ end
90
+
91
+ it "converts string to datetime" do
92
+ expect(converter.convert('2014-12-07 17:35:44').to(:datetime)).
93
+ to eq(DateTime.parse('2014-12-07 17:35:44'))
94
+ end
65
95
  end
66
96
  end
@@ -3,7 +3,17 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  RSpec.describe Necromancer::ArrayConverters::StringToArrayConverter, '.call' do
6
- subject(:converter) { described_class.new }
6
+ subject(:converter) { described_class.new(:string, :array) }
7
+
8
+ it "converts empty string to array" do
9
+ expect(converter.call('', strict: false)).to eq([''])
10
+ end
11
+
12
+ it "fails to convert empty string to array in strict mode" do
13
+ expect {
14
+ converter.call('', strict: true)
15
+ }.to raise_error(Necromancer::ConversionTypeError)
16
+ end
7
17
 
8
18
  it "converts `1,2,3` to array" do
9
19
  expect(converter.call('1,2,3')).to eq([1,2,3])
@@ -12,4 +22,12 @@ RSpec.describe Necromancer::ArrayConverters::StringToArrayConverter, '.call' do
12
22
  it "converts `a,b,c` to array" do
13
23
  expect(converter.call('a,b,c')).to eq(['a','b','c'])
14
24
  end
25
+
26
+ it "converts '1-2-3' to array" do
27
+ expect(converter.call('1-2-3')).to eq([1,2,3])
28
+ end
29
+
30
+ it "converts ' 1 - 2 - 3 ' to array" do
31
+ expect(converter.call(' 1 - 2 - 3 ')).to eq([1,2,3])
32
+ end
15
33
  end
@@ -13,4 +13,14 @@ RSpec.describe Necromancer::BooleanConverters::BooleanToIntegerConverter, '.call
13
13
  it "converts false to 0 value" do
14
14
  expect(converter.call(false)).to eq(0)
15
15
  end
16
+
17
+ it "fails to convert in strict mode" do
18
+ expect {
19
+ converter.call('unknown', strict: true)
20
+ }.to raise_error(Necromancer::ConversionTypeError)
21
+ end
22
+
23
+ it "returns value in non-strict mode" do
24
+ expect(converter.call('unknown', strict: false)).to eq('unknown')
25
+ end
16
26
  end
@@ -13,4 +13,10 @@ RSpec.describe Necromancer::BooleanConverters::IntegerToBooleanConverter, '.call
13
13
  it "converts 0 to false value" do
14
14
  expect(converter.call(0)).to eq(false)
15
15
  end
16
+
17
+ it "fails to convert in strict mode" do
18
+ expect {
19
+ converter.call('1', strict: true)
20
+ }.to raise_error(Necromancer::ConversionTypeError)
21
+ end
16
22
  end
@@ -4,93 +4,33 @@ require 'spec_helper'
4
4
 
5
5
  RSpec.describe Necromancer::BooleanConverters::StringToBooleanConverter, '.call' do
6
6
 
7
- subject(:converter) { described_class.new }
7
+ subject(:converter) { described_class.new(:string, :boolean) }
8
8
 
9
- it "raises error for empty string" do
10
- expect { converter.call('') }.to raise_error(ArgumentError)
9
+ it "raises error for empty string strict mode" do
10
+ expect {
11
+ converter.call('', strict: true)
12
+ }.to raise_error(Necromancer::ConversionTypeError)
11
13
  end
12
14
 
13
- it "passes through boolean value" do
14
- expect(converter.call(true)).to eq(true)
15
- end
16
-
17
- it "converts 'true' to true value" do
18
- expect(converter.call('true')).to eq(true)
19
- end
20
-
21
- it "converts 'TRUE' to true value" do
22
- expect(converter.call('TRUE')).to eq(true)
23
- end
24
-
25
- it "converts TRUE to true value" do
26
- expect(converter.call(TRUE)).to eq(true)
27
- end
28
-
29
- it "converts 't' to true value" do
30
- expect(converter.call('t')).to eq(true)
31
- end
32
-
33
- it "converts 'T' to true value" do
34
- expect(converter.call('T')).to eq(true)
35
- end
36
-
37
- it "converts 1 to true value" do
38
- expect(converter.call(1)).to eq(true)
39
- end
40
-
41
- it "converts '1' to true value" do
42
- expect(converter.call('1')).to eq(true)
43
- end
44
-
45
- it "converts 'y' to true value" do
46
- expect(converter.call('y')).to eq(true)
47
- end
48
-
49
- it "converts 'yes' to true value" do
50
- expect(converter.call('yes')).to eq(true)
51
- end
52
-
53
- it "converts 'on' to true value" do
54
- expect(converter.call('on')).to eq(true)
55
- end
56
-
57
- it "converts 'false' to false value" do
58
- expect(converter.call('false')).to eq(false)
59
- end
60
-
61
- it "converts FALSE to false value" do
62
- expect(converter.call(FALSE)).to eq(false)
63
- end
64
-
65
- it "converts 'FALSE' to false value" do
66
- expect(converter.call('FALSE')).to eq(false)
67
- end
68
-
69
- it "converts 'f' to false value" do
70
- expect(converter.call('f')).to eq(false)
71
- end
72
-
73
- it "converts 'F' to false value" do
74
- expect(converter.call('F')).to eq(false)
75
- end
76
-
77
- it "converts '0' to false value" do
78
- expect(converter.call('0')).to eq(false)
79
- end
80
-
81
- it "converts 'n' to false value" do
82
- expect(converter.call('n')).to eq(false)
15
+ it "fails to convert unkonwn value FOO" do
16
+ expect {
17
+ converter.call('FOO', strict: true)
18
+ }.to raise_error(Necromancer::ConversionTypeError)
83
19
  end
84
20
 
85
- it "converts 'no' to false value" do
86
- expect(converter.call('no')).to eq(false)
21
+ it "passes through boolean value" do
22
+ expect(converter.call(true)).to eq(true)
87
23
  end
88
24
 
89
- it "converts 'off' to false value" do
90
- expect(converter.call('off')).to eq(false)
25
+ %w[true TRUE t T 1 y Y YES yes on ON].each do |value|
26
+ it "converts '#{value}' to true value" do
27
+ expect(converter.call(value)).to eq(true)
28
+ end
91
29
  end
92
30
 
93
- it "fails to convert unkonwn value FOO" do
94
- expect { converter.call('FOO') }.to raise_error(ArgumentError)
31
+ %w[false FALSE f F 0 n N NO No no off OFF].each do |value|
32
+ it "converts '#{value}' to false value" do
33
+ expect(converter.call(value)).to eq(false)
34
+ end
95
35
  end
96
36
  end