necromancer 0.3.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +70 -3
  3. data/README.md +204 -86
  4. data/lib/necromancer.rb +17 -18
  5. data/lib/necromancer/configuration.rb +1 -1
  6. data/lib/necromancer/context.rb +16 -3
  7. data/lib/necromancer/conversion_target.rb +31 -14
  8. data/lib/necromancer/conversions.rb +39 -16
  9. data/lib/necromancer/converter.rb +10 -8
  10. data/lib/necromancer/converters/array.rb +143 -45
  11. data/lib/necromancer/converters/boolean.rb +21 -19
  12. data/lib/necromancer/converters/date_time.rb +58 -13
  13. data/lib/necromancer/converters/hash.rb +119 -0
  14. data/lib/necromancer/converters/numeric.rb +32 -28
  15. data/lib/necromancer/converters/range.rb +44 -18
  16. data/lib/necromancer/null_converter.rb +4 -2
  17. data/lib/necromancer/version.rb +2 -2
  18. metadata +39 -72
  19. data/.gitignore +0 -14
  20. data/.rspec +0 -3
  21. data/.ruby-version +0 -1
  22. data/.travis.yml +0 -19
  23. data/Gemfile +0 -16
  24. data/Rakefile +0 -8
  25. data/necromancer.gemspec +0 -21
  26. data/spec/spec_helper.rb +0 -53
  27. data/spec/unit/can_spec.rb +0 -11
  28. data/spec/unit/config_spec.rb +0 -32
  29. data/spec/unit/configuration/new_spec.rb +0 -30
  30. data/spec/unit/conversions/register_spec.rb +0 -49
  31. data/spec/unit/convert_spec.rb +0 -104
  32. data/spec/unit/converters/array/array_to_boolean_spec.rb +0 -22
  33. data/spec/unit/converters/array/array_to_numeric_spec.rb +0 -22
  34. data/spec/unit/converters/array/array_to_set_spec.rb +0 -18
  35. data/spec/unit/converters/array/object_to_array_spec.rb +0 -21
  36. data/spec/unit/converters/array/string_to_array_spec.rb +0 -33
  37. data/spec/unit/converters/boolean/boolean_to_integer_spec.rb +0 -26
  38. data/spec/unit/converters/boolean/integer_to_boolean_spec.rb +0 -22
  39. data/spec/unit/converters/boolean/string_to_boolean_spec.rb +0 -36
  40. data/spec/unit/converters/date_time/string_to_date_spec.rb +0 -22
  41. data/spec/unit/converters/date_time/string_to_datetime_spec.rb +0 -32
  42. data/spec/unit/converters/numeric/string_to_float_spec.rb +0 -48
  43. data/spec/unit/converters/numeric/string_to_integer_spec.rb +0 -62
  44. data/spec/unit/converters/numeric/string_to_numeric_spec.rb +0 -32
  45. data/spec/unit/converters/range/string_to_range_spec.rb +0 -35
  46. data/spec/unit/new_spec.rb +0 -12
  47. data/spec/unit/register_spec.rb +0 -17
  48. data/tasks/console.rake +0 -10
  49. data/tasks/coverage.rake +0 -11
  50. data/tasks/spec.rake +0 -29
@@ -1,21 +1,7 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
- require 'date'
5
- require 'set'
6
-
7
- require 'necromancer/conversions'
8
- require 'necromancer/configuration'
9
- require 'necromancer/context'
10
- require 'necromancer/converter'
11
- require 'necromancer/null_converter'
12
- require 'necromancer/converters/array'
13
- require 'necromancer/converters/boolean'
14
- require 'necromancer/converters/date_time'
15
- require 'necromancer/converters/numeric'
16
- require 'necromancer/converters/range'
17
- require 'necromancer/conversion_target'
18
- require 'necromancer/version'
3
+ require_relative "necromancer/context"
4
+ require_relative "necromancer/version"
19
5
 
20
6
  module Necromancer
21
7
  # Raised when cannot conver to a given type
@@ -35,6 +21,19 @@ module Necromancer
35
21
  def new(&block)
36
22
  Context.new(&block)
37
23
  end
38
-
39
24
  module_function :new
25
+
26
+ # Convenience to directly call conversion
27
+ #
28
+ # @example
29
+ # Necromancer.convert("1").to(:integer)
30
+ # # => 1
31
+ #
32
+ # @return [ConversionTarget]
33
+ #
34
+ # @api public
35
+ def convert(*args, &block)
36
+ Context.new.convert(*args, &block)
37
+ end
38
+ module_function :convert
40
39
  end # Necromancer
@@ -1,4 +1,4 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Necromancer
4
4
  # A global configuration for converters.
@@ -1,4 +1,10 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require_relative "configuration"
6
+ require_relative "conversions"
7
+ require_relative "conversion_target"
2
8
 
3
9
  module Necromancer
4
10
  # A class used by Necromancer to provide user interace
@@ -13,7 +19,7 @@ module Necromancer
13
19
  #
14
20
  # @api private
15
21
  def initialize(&block)
16
- block.call(configuration) if block_given?
22
+ block.(configuration) if block_given?
17
23
  @conversions = Conversions.new(configuration)
18
24
  @conversions.load
19
25
  end
@@ -47,7 +53,7 @@ module Necromancer
47
53
  end
48
54
 
49
55
  # Converts the object
50
- # @param [Object] value
56
+ # @param [Object] object
51
57
  # any object to be converted
52
58
  #
53
59
  # @api public
@@ -71,6 +77,13 @@ module Necromancer
71
77
  false
72
78
  end
73
79
 
80
+ # Inspect this context
81
+ #
82
+ # @api public
83
+ def inspect
84
+ %(#<#{self.class}@#{object_id} @config=#{configuration}>)
85
+ end
86
+
74
87
  protected
75
88
 
76
89
  attr_reader :conversions
@@ -1,4 +1,4 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Necromancer
4
4
  # A class responsible for wrapping conversion target
@@ -19,13 +19,13 @@ module Necromancer
19
19
  def self.for(context, value, block)
20
20
  if UndefinedValue.equal?(value)
21
21
  unless block
22
- fail ArgumentError,
23
- 'You need to pass either argument or a block to `convert`.'
22
+ raise ArgumentError,
23
+ "You need to pass either argument or a block to `convert`."
24
24
  end
25
25
  new(context, block.call)
26
26
  elsif block
27
- fail ArgumentError,
28
- 'You cannot pass both an argument and a block to `convert`.'
27
+ raise ArgumentError,
28
+ "You cannot pass both an argument and a block to `convert`."
29
29
  else
30
30
  new(context, value)
31
31
  end
@@ -34,7 +34,7 @@ module Necromancer
34
34
  # Allows to specify conversion source type
35
35
  #
36
36
  # @example
37
- # converter.convert('1').from(:string).to(:numeric) # => 1
37
+ # converter.convert("1").from(:string).to(:numeric) # => 1
38
38
  #
39
39
  # @return [ConversionType]
40
40
  #
@@ -47,29 +47,46 @@ module Necromancer
47
47
  # Runs a given conversion
48
48
  #
49
49
  # @example
50
- # converter.convert('1').to(:numeric) # => 1
50
+ # converter.convert("1").to(:numeric) # => 1
51
+ #
52
+ # @example
53
+ # converter.convert("1") >> Integer # => 1
51
54
  #
52
55
  # @return [Object]
53
56
  # the converted target type
54
57
  #
55
58
  # @api public
56
59
  def to(target, options = {})
57
- conversion = conversions[source || detect(object), detect(target)]
58
- conversion.call(object, options)
60
+ conversion = conversions[source || detect(object, false), detect(target)]
61
+ conversion.call(object, **options)
62
+ end
63
+ alias >> to
64
+
65
+ # Inspect this conversion
66
+ #
67
+ # @api public
68
+ def inspect
69
+ %(#<#{self.class}@#{object_id} @object=#{object}, @source=#{detect(object)}>)
59
70
  end
60
71
 
61
72
  protected
62
73
 
63
- # Detect object type
74
+ # Detect object type and coerce into known key type
75
+ #
76
+ # @param [Object] object
64
77
  #
65
78
  # @api private
66
- def detect(object)
79
+ def detect(object, symbol_as_object = true)
67
80
  case object
68
81
  when TrueClass, FalseClass then :boolean
69
- when Fixnum, Bignum then :integer
70
- when Symbol then object
82
+ when Integer then :integer
83
+ when Class then object.name.downcase
71
84
  else
72
- object.class.name.downcase
85
+ if object.is_a?(Symbol) && symbol_as_object
86
+ object
87
+ else
88
+ object.class.name.downcase
89
+ end
73
90
  end
74
91
  end
75
92
 
@@ -1,4 +1,13 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration"
4
+ require_relative "converter"
5
+ require_relative "converters/array"
6
+ require_relative "converters/boolean"
7
+ require_relative "converters/date_time"
8
+ require_relative "converters/hash"
9
+ require_relative "converters/numeric"
10
+ require_relative "converters/range"
2
11
 
3
12
  module Necromancer
4
13
  # Represents the context used to configure various converters
@@ -6,7 +15,7 @@ module Necromancer
6
15
  #
7
16
  # @api public
8
17
  class Conversions
9
- DELIMITER = '->'.freeze
18
+ DELIMITER = "->"
10
19
 
11
20
  # Creates a new conversions map
12
21
  #
@@ -14,9 +23,9 @@ module Necromancer
14
23
  # conversion = Necromancer::Conversions.new
15
24
  #
16
25
  # @api public
17
- def initialize(configuration = Configuration.new)
26
+ def initialize(configuration = Configuration.new, map = {})
18
27
  @configuration = configuration
19
- @converter_map = {}
28
+ @converter_map = map.dup
20
29
  end
21
30
 
22
31
  # Load converters
@@ -26,6 +35,7 @@ module Necromancer
26
35
  ArrayConverters.load(self)
27
36
  BooleanConverters.load(self)
28
37
  DateTimeConverters.load(self)
38
+ HashConverters.load(self)
29
39
  NumericConverters.load(self)
30
40
  RangeConverters.load(self)
31
41
  end
@@ -45,22 +55,22 @@ module Necromancer
45
55
  def [](source, target)
46
56
  key = "#{source}#{DELIMITER}#{target}"
47
57
  converter_map[key] ||
48
- converter_map["object->#{target}"] ||
49
- fail_no_type_conversion_available(key)
58
+ converter_map["object#{DELIMITER}#{target}"] ||
59
+ raise_no_type_conversion_available(key)
50
60
  end
61
+ alias fetch []
51
62
 
52
- # Fail with conversion error
63
+ # Register a converter
53
64
  #
54
- # @api private
55
- def fail_no_type_conversion_available(key)
56
- fail NoTypeConversionAvailableError, "Conversion '#{key}' unavailable."
57
- end
58
-
59
65
  # @example with simple object
60
- #
66
+ # conversions.register NullConverter.new(:array, :array)
61
67
  #
62
68
  # @example with block
63
- #
69
+ # conversions.register do |c|
70
+ # c.source = :array
71
+ # c.target = :array
72
+ # c.convert = -> { |val, options| val }
73
+ # end
64
74
  #
65
75
  # @api public
66
76
  def register(converter = nil, &block)
@@ -68,21 +78,34 @@ module Necromancer
68
78
  key = generate_key(converter)
69
79
  converter = add_config(converter, @configuration)
70
80
  return false if converter_map.key?(key)
81
+
71
82
  converter_map[key] = converter
72
83
  true
73
84
  end
74
85
 
86
+ # Export all the conversions as hash
87
+ #
88
+ # @return [Hash[String, String]]
89
+ #
90
+ # @api public
75
91
  def to_hash
76
92
  converter_map.dup
77
93
  end
78
94
 
79
95
  protected
80
96
 
97
+ # Fail with conversion error
98
+ #
99
+ # @api private
100
+ def raise_no_type_conversion_available(key)
101
+ raise NoTypeConversionAvailableError, "Conversion '#{key}' unavailable."
102
+ end
103
+
81
104
  # @api private
82
105
  def generate_key(converter)
83
106
  parts = []
84
- parts << (converter.source ? converter.source.to_s : 'none')
85
- parts << (converter.target ? converter.target.to_s : 'none')
107
+ parts << (converter.source ? converter.source.to_s : "none")
108
+ parts << (converter.target ? converter.target.to_s : "none")
86
109
  parts.join(DELIMITER)
87
110
  end
88
111
 
@@ -1,4 +1,6 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "configuration"
2
4
 
3
5
  module Necromancer
4
6
  # Abstract converter used internally as a base for other converters
@@ -24,7 +26,7 @@ module Necromancer
24
26
  #
25
27
  # @api private
26
28
  def call(*)
27
- fail NotImplementedError
29
+ raise NotImplementedError
28
30
  end
29
31
 
30
32
  # Creates anonymous converter
@@ -32,9 +34,9 @@ module Necromancer
32
34
  # @api private
33
35
  def self.create(&block)
34
36
  Class.new(self) do
35
- define_method(:initialize) { |*a| block.call(self, *a) }
37
+ define_method(:initialize) { |*a| block.(self, *a) }
36
38
 
37
- define_method(:call) { |value| convert.call(value) }
39
+ define_method(:call) { |value| convert.(value) }
38
40
  end.new
39
41
  end
40
42
 
@@ -44,9 +46,9 @@ module Necromancer
44
46
  # the value that cannot be converted
45
47
  #
46
48
  # @api private
47
- def fail_conversion_type(value)
48
- fail ConversionTypeError, "'#{value}' could not be converted " \
49
- "from `#{source}` into `#{target}` "
49
+ def raise_conversion_type(value)
50
+ raise ConversionTypeError, "'#{value}' could not be converted " \
51
+ "from `#{source}` into `#{target}`"
50
52
  end
51
53
 
52
54
  attr_accessor :source
@@ -55,7 +57,7 @@ module Necromancer
55
57
 
56
58
  attr_accessor :convert
57
59
 
58
- protected
60
+ # protected
59
61
 
60
62
  attr_reader :config
61
63
  end # Converter
@@ -1,65 +1,150 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require_relative "../converter"
6
+ require_relative "boolean"
7
+ require_relative "numeric"
2
8
 
3
9
  module Necromancer
4
10
  # Container for Array converter classes
5
11
  module ArrayConverters
12
+ ARRAY_MATCHER = /^(.+?(\s*(?<sep>(,|-))\s*))+/x.freeze
13
+
6
14
  # An object that converts a String to an Array
7
15
  class StringToArrayConverter < Converter
8
16
  # Convert string value to array
9
17
  #
10
18
  # @example
11
- # converter.call('a, b, c') # => ['a', 'b', 'c']
19
+ # converter.call("a, b, c") # => ["a", "b", "c"]
12
20
  #
13
21
  # @example
14
- # converter.call('1 - 2 - 3') # => [1, 2, 3]
22
+ # converter.call("1 - 2 - 3") # => ["1", "2", "3"]
15
23
  #
16
24
  # @api public
17
- def call(value, options = {})
18
- strict = options.fetch(:strict, config.strict)
19
- case value.to_s
20
- when /^\s*?((\d+)(\s*(,|-)\s*)?)+\s*?$/
21
- value.to_s.split($4).map(&:to_i)
22
- when /^((\w)(\s*(,|-)\s*)?)+$/
23
- value.to_s.split($4)
25
+ def call(value, strict: config.strict)
26
+ return [] if value.to_s.empty?
27
+
28
+ if match = value.to_s.match(ARRAY_MATCHER)
29
+ value.to_s.split(match[:sep])
24
30
  else
25
- strict ? fail_conversion_type(value) : Array(value)
31
+ strict ? raise_conversion_type(value) : Array(value)
26
32
  end
27
33
  end
28
34
  end
29
35
 
36
+ class StringToBooleanArrayConverter < Converter
37
+ # @example
38
+ # converter.call("t,f,yes,no") # => [true, false, true, false]
39
+ #
40
+ # @param [Array] value
41
+ # the array value to boolean
42
+ #
43
+ # @api public
44
+ def call(value, strict: config.strict)
45
+ array_converter = StringToArrayConverter.new(:string, :array)
46
+ array = array_converter.(value, strict: strict)
47
+ bool_converter = ArrayToBooleanArrayConverter.new(:array, :boolean)
48
+ bool_converter.(array, strict: strict)
49
+ end
50
+ end
51
+
52
+ class StringToIntegerArrayConverter < Converter
53
+ # @example
54
+ # converter.call("1,2,3") # => [1, 2, 3]
55
+ #
56
+ # @api public
57
+ def call(string, strict: config.strict)
58
+ array_converter = StringToArrayConverter.new(:string, :array)
59
+ array = array_converter.(string, strict: strict)
60
+ int_converter = ArrayToIntegerArrayConverter.new(:array, :integers)
61
+ int_converter.(array, strict: strict)
62
+ end
63
+ end
64
+
65
+ class StringToFloatArrayConverter < Converter
66
+ # @example
67
+ # converter.call("1,2,3") # => [1.0, 2.0, 3.0]
68
+ #
69
+ # @api public
70
+ def call(string, strict: config.strict)
71
+ array_converter = StringToArrayConverter.new(:string, :array)
72
+ array = array_converter.(string, strict: strict)
73
+ float_converter = ArrayToFloatArrayConverter.new(:array, :floats)
74
+ float_converter.(array, strict: strict)
75
+ end
76
+ end
77
+
78
+ class StringToNumericArrayConverter < Converter
79
+ # Convert string value to array with numeric values
80
+ #
81
+ # @example
82
+ # converter.call("1,2.0,3") # => [1, 2.0, 3]
83
+ #
84
+ # @api public
85
+ def call(string, strict: config.strict)
86
+ array_converter = StringToArrayConverter.new(:string, :array)
87
+ array = array_converter.(string, strict: strict)
88
+ num_converter = ArrayToNumericArrayConverter.new(:array, :numeric)
89
+ num_converter.(array, strict: strict)
90
+ end
91
+ end
92
+
93
+ class ArrayToIntegerArrayConverter < Converter
94
+ # @example
95
+ # converter.call(["1", "2", "3"]) # => [1, 2, 3]
96
+ #
97
+ # @api public
98
+ def call(array, strict: config.strict)
99
+ int_converter = NumericConverters::StringToIntegerConverter.new(:string,
100
+ :integer)
101
+ array.map { |val| int_converter.(val, strict: strict) }
102
+ end
103
+ end
104
+
105
+ class ArrayToFloatArrayConverter < Converter
106
+ # @example
107
+ # converter.call(["1", "2", "3"]) # => [1.0, 2.0, 3.0]
108
+ #
109
+ # @api public
110
+ def call(array, strict: config.strict)
111
+ float_converter = NumericConverters::StringToFloatConverter.new(:string,
112
+ :float)
113
+ array.map { |val| float_converter.(val, strict: strict) }
114
+ end
115
+ end
116
+
30
117
  # An object that converts an array to an array with numeric values
31
- class ArrayToNumericConverter < Converter
118
+ class ArrayToNumericArrayConverter < Converter
32
119
  # Convert an array to an array of numeric values
33
120
  #
34
121
  # @example
35
- # converter.call(['1', '2.3', '3.0]) # => [1, 2.3, 3.0]
122
+ # converter.call(["1", "2.3", "3.0]) # => [1, 2.3, 3.0]
36
123
  #
37
124
  # @param [Array] value
38
125
  # the value to convert
39
126
  #
40
127
  # @api public
41
- def call(value, options = {})
42
- numeric_converter = NumericConverters::StringToNumericConverter.new(:string, :numeric)
43
- value.reduce([]) do |acc, el|
44
- acc << numeric_converter.call(el, options)
45
- end
128
+ def call(value, strict: config.strict)
129
+ num_converter = NumericConverters::StringToNumericConverter.new(:string,
130
+ :numeric)
131
+ value.map { |val| num_converter.(val, strict: strict) }
46
132
  end
47
133
  end
48
134
 
49
135
  # An object that converts an array to an array with boolean values
50
- class ArrayToBooleanConverter < Converter
136
+ class ArrayToBooleanArrayConverter < Converter
51
137
  # @example
52
- # converter.call(['t', 'f', 'yes', 'no']) # => [true, false, true, false]
138
+ # converter.call(["t", "f", "yes", "no"]) # => [true, false, true, false]
53
139
  #
54
140
  # @param [Array] value
55
141
  # the array value to boolean
56
142
  #
57
143
  # @api public
58
- def call(value, options = {})
59
- boolean_converter = BooleanConverters::StringToBooleanConverter.new(:string, :boolean)
60
- value.reduce([]) do |acc, el|
61
- acc << boolean_converter.call(el, options)
62
- end
144
+ def call(value, strict: config.strict)
145
+ bool_converter = BooleanConverters::StringToBooleanConverter.new(:string,
146
+ :boolean)
147
+ value.map { |val| bool_converter.(val, strict: strict) }
63
148
  end
64
149
  end
65
150
 
@@ -71,13 +156,10 @@ module Necromancer
71
156
  # converter.call({x: 1}) # => [[:x, 1]]
72
157
  #
73
158
  # @api public
74
- def call(value, options = {})
75
- strict = options.fetch(:strict, config.strict)
76
- begin
77
- Array(value)
78
- rescue
79
- strict ? fail_conversion_type(value) : value
80
- end
159
+ def call(value, strict: config.strict)
160
+ Array(value)
161
+ rescue StandardError
162
+ strict ? raise_conversion_type(value) : value
81
163
  end
82
164
  end
83
165
 
@@ -92,23 +174,39 @@ module Necromancer
92
174
  # the array to convert
93
175
  #
94
176
  # @api public
95
- def call(value, options = {})
96
- strict = options.fetch(:strict, config.strict)
97
- begin
98
- value.to_set
99
- rescue
100
- strict ? fail_conversion_type(value) : value
101
- end
177
+ def call(value, strict: config.strict)
178
+ value.to_set
179
+ rescue StandardError
180
+ strict ? raise_conversion_type(value) : value
102
181
  end
103
182
  end
104
183
 
105
184
  def self.load(conversions)
106
- conversions.register NullConverter.new(:array, :array)
107
- conversions.register StringToArrayConverter.new(:string, :array)
108
- conversions.register ArrayToNumericConverter.new(:array, :numeric)
109
- conversions.register ArrayToBooleanConverter.new(:array, :boolean)
110
- conversions.register ObjectToArrayConverter.new(:object, :array)
111
- conversions.register ObjectToArrayConverter.new(:hash, :array)
185
+ [
186
+ NullConverter.new(:array, :array),
187
+
188
+ StringToArrayConverter.new(:string, :array),
189
+ StringToBooleanArrayConverter.new(:string, :bools),
190
+ StringToBooleanArrayConverter.new(:string, :booleans),
191
+ StringToIntegerArrayConverter.new(:string, :integers),
192
+ StringToIntegerArrayConverter.new(:string, :ints),
193
+ StringToFloatArrayConverter.new(:string, :floats),
194
+ StringToNumericArrayConverter.new(:string, :numerics),
195
+ StringToNumericArrayConverter.new(:string, :nums),
196
+
197
+ ArrayToNumericArrayConverter.new(:array, :numerics),
198
+ ArrayToNumericArrayConverter.new(:array, :nums),
199
+ ArrayToIntegerArrayConverter.new(:array, :integers),
200
+ ArrayToIntegerArrayConverter.new(:array, :ints),
201
+ ArrayToFloatArrayConverter.new(:array, :floats),
202
+ ArrayToBooleanArrayConverter.new(:array, :booleans),
203
+ ArrayToBooleanArrayConverter.new(:array, :bools),
204
+
205
+ ObjectToArrayConverter.new(:object, :array),
206
+ ObjectToArrayConverter.new(:hash, :array)
207
+ ].each do |converter|
208
+ conversions.register converter
209
+ end
112
210
  end
113
211
  end # ArrayConverters
114
212
  end # Necromancer