necromancer 0.3.0 → 0.7.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 (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