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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f9c37da594bf2020bd875ad6a46514c1d20b7d23
4
- data.tar.gz: f351c2bbbd62d9f4780306897ea5f09983607af7
3
+ metadata.gz: a1c2f7a7dfa509891b8a6e9b54b0dc1c06220bd6
4
+ data.tar.gz: 0e4645b6d70af9782ded19b153937cb35ae29ca1
5
5
  SHA512:
6
- metadata.gz: 71b242fd171277c210324744d09d80702af4f718c675f747b997bfb8ec1bb9525c2f9a4bb4de5c6a5de6a2605248e02b576956a31cfd3cc7fb51ea2bb6bf1988
7
- data.tar.gz: 2a0cfcb08fb44936ad433903a2d12024d31f615b8fafcbc3100c2a0ab56c253e6d96c9432373ce1c9cd74a47bab2ab948bcae886874824936885641fbeeb54f8
6
+ metadata.gz: b3e8624ae93d5dea444a1948fecee7be8dfdcdc24b04e4caca39dd93d22214b2035f124de5fdd360ee1af62bfce87dbadd20aa0a13496b7003b104cc3c879de6
7
+ data.tar.gz: c61f41045c0709a96f7e1d81dce673260f4410ffd91f6d69febf8de0f334d3c8634bb74b20efa6fd8ad88172ad3748bcb5e9e5c6d988c684344759f7159a7a89
@@ -0,0 +1,6 @@
1
+ 0.2.0 (December 7, 2014)
2
+
3
+ * Add #fail_conversion_type to Converter and use in converters
4
+ * Add DateTimeConverters
5
+ * Change IntegerConverters & FloatConverters into Numeric Converters
6
+ * Add string to numeric type conversion
data/README.md CHANGED
@@ -13,10 +13,15 @@
13
13
 
14
14
  **Necromancer** provides independent type conversion component for [TTY](https://github.com/peter-murach/tty) toolkit.
15
15
 
16
+ ## Motivation
17
+
18
+ Conversion between Ruby core types frequently comes up in projects but is solved by half-baked solutions. This library aims to provide an independent and extensible API to support a robust and generic way to convert between core Ruby types.
19
+
16
20
  ## Features
17
21
 
18
22
  * Simple and expressive API
19
23
  * Ability to specify own converters
24
+ * Ability to compose conversions out of simpler ones
20
25
  * Support conversion of custom defined types
21
26
 
22
27
  ## Installation
@@ -40,32 +45,48 @@ Or install it yourself as:
40
45
  * [1. Usage](#1-usage)
41
46
  * [2. Interface](#2-interface)
42
47
  * [2.1 convert](#21-convert)
43
- * [2.1 can?](#22-can)
48
+ * [2.2 from](#22-from)
49
+ * [2.3 to](#23-to)
50
+ * [2.4 can?](#24-can)
44
51
  * [3. Converters](#3-converters)
52
+ * [3.1 Array](#31-array)
53
+ * [3.2 Boolean](#32-boolean)
54
+ * [3.3 Hash](#33-hash)
55
+ * [3.4 Numeric](#34-numeric)
56
+ * [3.5 Range](#35-range)
57
+ * [3.6 Custom](#36-custom)
58
+ * [3.6.1 Using an Object](#361-using-an-object)
59
+ * [3.6.2 Using a Proc](#362-using-a-proc)
45
60
 
46
61
  ## 1. Usage
47
62
 
48
- **Necromancer** requires you to instatiate it like so:
63
+ **Necromancer** requires you to instatiate it:
49
64
 
50
65
  ```ruby
51
66
  converter = Necromancer.new
52
67
  ```
53
68
 
54
- Once initialize **Necromancer** knows how to handle numerous conversions.
69
+ Once initialized **Necromancer** knows how to handle numerous conversions and also allows you to add your custom type [converters](#35-custom).
55
70
 
56
- For example, to convert a string to a range type:
71
+ For example, to convert a string to a [range](#34-range) type:
57
72
 
58
73
  ```ruby
59
74
  converter.convert('1-10').to(:range) # => 1..10
60
75
  ```
61
76
 
62
- In order to handle boolean conversions:
77
+ In order to handle [boolean](#32-boolean) conversions:
63
78
 
64
79
  ```ruby
65
80
  converter.convert('t').to(:boolean) # => true
66
81
  ```
67
82
 
68
- or get array elements into numeric type:
83
+ To convert string to [numeric](#34-numeric) value:
84
+
85
+ ```ruby
86
+ converter.convert('10e1').to(:numeric) # => 100
87
+ ```
88
+
89
+ or get [array](#31-array) elements into numeric type:
69
90
 
70
91
  ```ruby
71
92
  converter.convert(['1', '2.3', '3.0']).to(:numeric) # => [1, 2.3, 3.0]
@@ -77,13 +98,72 @@ However, if you want to tell **Necromancer** about source type use `from`:
77
98
  converter.convert(['1', '2.3', '3.0']).from(:array).to(:numeric) # => [1, 2.3, 3.0]
78
99
  ```
79
100
 
80
- ## 2. Interaface
101
+ ## 2. Interface
102
+
103
+ **Necromancer** will perform conversions on the supplied object through use of `convert`, `from` and `to` methods.
81
104
 
82
105
  ### 2.1 convert
83
106
 
84
- ### 2.2 can?
107
+ For the purpose of divination, **Necromancer** uses `convert` method to turn source type into target type. For example, in order to convert a string into a range type do:
108
+
109
+ ```ruby
110
+ converter.convert('1,10').to(:range) # => 1..10
111
+ ```
112
+
113
+ Alternatively, you can use block:
114
+
115
+ ```ruby
116
+ converter.convert { '1,10' }.to(:range) # => 1..10
117
+ ```
118
+
119
+ ### 2.2 from
120
+
121
+ To specify conversion source type use `from` method:
122
+
123
+ ```ruby
124
+ converter.convert('1.0').from(:string).to(:numeric)
125
+ ```
126
+
127
+ In majority of cases you do not need to specify `from` as the type will be inferred from the `convert` method argument and then appropriate conversion will be applied to result in `target` type such as `:numeric`. However, if you do not control the input to `convert` and want to ensure consistent behaviour please use `from`.
128
+
129
+ The source parameters are:
130
+
131
+ * :array
132
+ * :boolean
133
+ * :float
134
+ * :integer
135
+ * :numeric
136
+ * :range
137
+ * :string
138
+
139
+ ### 2.3 to
140
+
141
+ To convert objects between types, **Necromancer** provides several target types. The `to` method allows you to pass target as an argument to perform actual conversion:
142
+
143
+ ```ruby
144
+ converter.convert('yes').to(:boolean) # => true
145
+ ```
146
+
147
+ By default, when target conversion fails the orignal value is returned. However, you can pass `strict` as an additional argument to ensure failure when conversion cannot be performed:
148
+
149
+ ```ruby
150
+ converter.convert('1a').to(:integer, strict: true)
151
+ # => raises Necromancer::ConversionTypeError
152
+ ```
153
+
154
+ The target parameters are:
155
+
156
+ * :array
157
+ * :boolean
158
+ * :float
159
+ * :integer
160
+ * :numeric
161
+ * :range
162
+ * :string
85
163
 
86
- To verify that a a given conversion can be handled by **Necormancer** call `can?` with the `source` and `target` of the desired conversion.
164
+ ### 2.4 can?
165
+
166
+ To verify that a given conversion can be handled by **Necormancer** call `can?` with the `source` and `target` of the desired conversion.
87
167
 
88
168
  ```ruby
89
169
  converter = Necromancer.new
@@ -93,41 +173,171 @@ converter.can?(:unknown, :integer) # => false
93
173
 
94
174
  ## 3. Converters
95
175
 
96
- ### 3.1 Custom
176
+ **Necromancer** flexibility means you can register your own converters or use the already defined converters for such types as 'Array', 'Boolean', 'Hash', 'Numeric', 'Range'.
177
+
178
+ ### 3.1 Array
97
179
 
98
- ### 3.2 Array
180
+ The **Necromancer** allows you to transform string into an array object:
99
181
 
100
182
  ```ruby
101
- converter.convert(['1', '2.3', '3.0']).to(:numeric)
183
+ converter.call('a, b, c') # => ['a', 'b', 'c']
102
184
  ```
103
185
 
186
+ If the string is a list of separated numbers, they will be converted to their respective numeric types:
187
+
188
+ ```ruby
189
+ converter.call('1 - 2 - 3') # => [1, 2, 3]
190
+ ```
191
+
192
+ You can also convert array containing string objects to array containing numeric values:
193
+
104
194
  ```ruby
105
195
  converter.convert(['1', '2.3', '3.0']).from(:array).to(:numeric)
106
196
  ```
107
197
 
108
- Raises error when in strict mode
198
+ or simply:
109
199
 
110
200
  ```ruby
111
- converter.convert(['1', '2.3', false]).from(:array).to(:numeric)
112
- # => Necromancer::ConversionError: false cannot be converter to numeric value
201
+ converter.convert(['1', '2.3', '3.0']).to(:numeric)
113
202
  ```
114
203
 
115
- Returns value when in non strict mode
204
+ When in `strict` mode the conversion will raise a `Necromancer::ConversionTypeError` error like so:
116
205
 
117
206
  ```ruby
118
- converter.convert(['1', '2.3', false]).from(:array).to(:numeric)
207
+ converter.convert(['1', '2.3', false]).to(:numeric, strict: true)
208
+ # => Necromancer::ConversionTypeError: false cannot be converted from `array` to `numeric`
209
+ ```
210
+
211
+ However, in `non-strict` mode the value will be simply returned unchanged:
212
+
213
+ ```ruby
214
+ converter.convert(['1', '2.3', false]).to(:numeric, strict: false)
119
215
  # => [1, 2.3, false]
120
216
  ```
121
217
 
122
- ### 3.3 Range
218
+ ### 3.2 Boolean
219
+
220
+ The **Necromancer** allows you to convert a string object to boolean object. The `1`, `'1'`, `'t'`, `'T'`, `'true'`, `'TRUE'`, `'y'`, `'Y'`, `'yes'`, `'Yes'`, `'on'`, `'ON'` values are converted to `TrueClass`.
221
+
222
+ ```ruby
223
+ converter.convert('yes').to(:boolean) # => true
224
+ ```
225
+
226
+ Similarly, the `0`, `'0'`, `'f'`, `'F'`, `'false'`, `'FALSE'`, `'n'`, `'N'`, `'no'`, `'No'`, `'off'`, `'OFF'` values are converted to `FalseClass`.
227
+
228
+ ```ruby
229
+ converter.convert('no').to(:boolean) # => false
230
+ ```
231
+
232
+ You can also convert an integer object to boolean:
233
+
234
+ ```ruby
235
+ converter.convert(1).to(:boolean) # => true
236
+ converter.convert(0).to(:boolean) # => false
237
+ ```
123
238
 
124
- ### 3.4 Hash
239
+ ### 3.3 Hash
125
240
 
126
241
  ```ruby
127
242
  converter.convert({ x: '27.5', y: '4', z: '11'}).to(:numeric)
128
243
  # => { x: 27.5, y: 4, z: 11}
129
244
  ```
130
245
 
246
+ ### 3.4 Numeric
247
+
248
+ **Necromancer** comes ready to convert all the primitive numeric values.
249
+
250
+ To convert a string to a float do:
251
+
252
+ ```ruby
253
+ converter.convert('1.2a').to(:float) # => 1.2
254
+ ```
255
+
256
+ Conversion to numeric in strict mode raises `Necromancer::ConversionTypeError`:
257
+
258
+ ```ruby
259
+ converter.convert('1.2a').to(:float, strict: true) # => raises error
260
+ ```
261
+
262
+ To convert a string to an integer do:
263
+
264
+ ```ruby
265
+ converter.convert('1a').to(:integer) # => 1
266
+ ```
267
+
268
+ However, if you want to convert string to an appropriate matching numeric type do:
269
+
270
+ ```ruby
271
+ converter.convert('1e1').to(:numeric) # => 10
272
+ ```
273
+
274
+ ### 3.5 Range
275
+
276
+ **Necromancer** is no stranger to figuring out ranges from strings. You can pass `,`, `-`, `..`, `...` characters to denote ranges:
277
+
278
+ ```ruby
279
+ converter.convert('1,10').to(:range) # => 1..10
280
+ ```
281
+
282
+ or to create a range of letters:
283
+
284
+ ```ruby
285
+ converter.convert('a-z').to(:range) # => 'a'..'z'
286
+ ```
287
+
288
+ ### 3.6 Custom
289
+
290
+ #### 3.6.1 Using an Object
291
+
292
+ Firstly, you need to create a converter that at minimum requires to specify `call` method that will be invoked during conversion:
293
+
294
+ ```ruby
295
+ UpcaseConverter = Struct.new(:source, :target) do
296
+ def call(value, options)
297
+ value.upcase
298
+ end
299
+ end
300
+ ```
301
+
302
+ Then you need to specify what type conversions this converter will support. For example, `UpcaseConverter` will allow a string object to be converted to a new string object with content upper cased. This can be done:
303
+
304
+ ```ruby
305
+ upcase_converter = UpcaseConverter.new(:string, :upcase)
306
+ ```
307
+
308
+ **Necromancer** provides the `register` method to add converter:
309
+
310
+ ```ruby
311
+ converter = Necromancer.new
312
+ converter.register(upcase_converter) # => true if successfully registered
313
+ ```
314
+
315
+ Finally, by invoking `convert` method and specifying `:upcase` as the target for the conversion we achieve the result:
316
+
317
+ ```ruby
318
+ converter.convert('magic').to(:upcase) # => 'MAGIC'
319
+ ```
320
+
321
+ #### 3.6.2 Using a Proc
322
+
323
+ Using a Proc object you can create and immediately register a converter. You need to pass `source` and `target` of the conversion that will be used later on to match the conversion. The `convert` allows you to specify the actual conversion in block form. For example:
324
+
325
+ ```ruby
326
+ converter = Necromancer.new
327
+
328
+ converter.register do |c|
329
+ c.source= :string
330
+ c.target= :upcase
331
+ c.convert = proc { |value, options| value.upcase }
332
+ end
333
+ ```
334
+
335
+ Then by invoking the `convert` method and passing the `:upcase` conversion type you can transform the string like so:
336
+
337
+ ```ruby
338
+ converter.convert('magic').to(:upcase) # => 'MAGIC'
339
+ ```
340
+
131
341
  ## Contributing
132
342
 
133
343
  1. Fork it ( https://github.com/peter-murach/necromancer/fork )
@@ -1,13 +1,16 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'forwardable'
4
+ require 'date'
5
+
3
6
  require 'necromancer/conversions'
4
7
  require 'necromancer/context'
5
8
  require 'necromancer/converter'
6
9
  require 'necromancer/null_converter'
7
10
  require 'necromancer/converters/array'
8
11
  require 'necromancer/converters/boolean'
9
- require 'necromancer/converters/float'
10
- require 'necromancer/converters/integer'
12
+ require 'necromancer/converters/date_time'
13
+ require 'necromancer/converters/numeric'
11
14
  require 'necromancer/converters/range'
12
15
  require 'necromancer/conversion_target'
13
16
  require 'necromancer/version'
@@ -19,6 +22,14 @@ module Necromancer
19
22
  # Raised when conversion type is not available
20
23
  NoTypeConversionAvailableError = Class.new(StandardError)
21
24
 
25
+ # Create a conversion instance
26
+ #
27
+ # @example
28
+ # converter = Necromancer.new
29
+ #
30
+ # @return [Context]
31
+ #
32
+ # @api private
22
33
  def new
23
34
  Context.new
24
35
  end
@@ -5,6 +5,10 @@ module Necromancer
5
5
  #
6
6
  # @api public
7
7
  class Context
8
+ extend Forwardable
9
+
10
+ def_delegators :"@conversions", :register
11
+
8
12
  # Create a context
9
13
  #
10
14
  # @api private
@@ -30,7 +30,8 @@ module Necromancer
30
30
  def load
31
31
  ArrayConverters.load(self)
32
32
  BooleanConverters.load(self)
33
- IntegerConverters.load(self)
33
+ DateTimeConverters.load(self)
34
+ NumericConverters.load(self)
34
35
  RangeConverters.load(self)
35
36
  end
36
37
 
@@ -28,6 +28,17 @@ module Necromancer
28
28
  end.new
29
29
  end
30
30
 
31
+ # Fail with conversion type error
32
+ #
33
+ # @param [Object] value
34
+ # the value that cannot be converted
35
+ #
36
+ # @api private
37
+ def fail_conversion_type(value)
38
+ fail ConversionTypeError, "'#{value}' could not be converted " \
39
+ "from `#{source}` into `#{target}` "
40
+ end
41
+
31
42
  attr_accessor :source
32
43
 
33
44
  attr_accessor :target
@@ -1,43 +1,51 @@
1
1
  # coding: utf-8
2
2
 
3
3
  module Necromancer
4
+ # Container for Array converter classes
4
5
  module ArrayConverters
6
+ # An object that converts a String to an Array
5
7
  class StringToArrayConverter < Converter
6
-
8
+ # Convert string value to array
9
+ #
10
+ # @example
11
+ # converter.call('a, b, c') # => ['a', 'b', 'c']
12
+ #
7
13
  # @example
8
- # converter.call('1,2,3') # => ['1', '2', '3']
14
+ # converter.call('1 - 2 - 3') # => [1, 2, 3]
9
15
  #
10
16
  # @api public
11
17
  def call(value, options = {})
18
+ strict = options.fetch(:strict, false)
12
19
  case value.to_s
13
- when /^((\d+)(\s*(,)\s*)?)+$/
20
+ when /^\s*?((\d+)(\s*(,|-)\s*)?)+\s*?$/
14
21
  value.to_s.split($4).map(&:to_i)
15
- when /^((\w)(\s*,\s*)?)+$/
16
- value.to_s.split(',')
22
+ when /^((\w)(\s*(,|-)\s*)?)+$/
23
+ value.to_s.split($4)
17
24
  else
18
- fail ConversionTypeError, "#{value} could not be converted " \
19
- "from #{source} into `#{target}`"
25
+ if strict
26
+ fail_conversion_type(value)
27
+ else
28
+ Array(value)
29
+ end
20
30
  end
21
31
  end
22
32
  end
23
33
 
34
+ # An object that converts an Array to a numeric
24
35
  class ArrayToNumericConverter < Converter
36
+ # Convert an array to a numeric value
37
+ #
38
+ # @example
39
+ # converter.call(['1', '2.3', '3.0]) # => [1, 2.3, 3.0]
40
+ #
41
+ # @param [Object] value
42
+ # the value to convert
43
+ #
44
+ # @api public
25
45
  def call(value, options = {})
26
- strict = options.fetch(:strict, false)
46
+ numeric_converter = NumericConverters::StringToNumericConverter.new(:string, :numeric)
27
47
  value.reduce([]) do |acc, el|
28
- acc << case el.to_s
29
- when /^\d+\.\d+$/
30
- el.to_f
31
- when /^\d+$/
32
- el.to_i
33
- else
34
- if strict
35
- raise ConversionTypeError, "#{value} could not be converted " \
36
- " from `#{source}` into `#{target}`"
37
- else
38
- el
39
- end
40
- end
48
+ acc << numeric_converter.call(el, options)
41
49
  end
42
50
  end
43
51
  end