necromancer 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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