necromancer 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +229 -19
- data/lib/necromancer.rb +13 -2
- data/lib/necromancer/context.rb +4 -0
- data/lib/necromancer/conversions.rb +2 -1
- data/lib/necromancer/converter.rb +11 -0
- data/lib/necromancer/converters/array.rb +29 -21
- data/lib/necromancer/converters/boolean.rb +40 -10
- data/lib/necromancer/converters/date_time.rb +33 -0
- data/lib/necromancer/converters/numeric.rb +88 -0
- data/lib/necromancer/converters/range.rb +13 -6
- data/lib/necromancer/version.rb +1 -1
- data/spec/spec_helper.rb +8 -0
- data/spec/unit/convert_spec.rb +31 -1
- data/spec/unit/converters/array/string_to_array_spec.rb +19 -1
- data/spec/unit/converters/boolean/boolean_to_integer_spec.rb +10 -0
- data/spec/unit/converters/boolean/integer_to_boolean_spec.rb +6 -0
- data/spec/unit/converters/boolean/string_to_boolean_spec.rb +19 -79
- data/spec/unit/converters/date_time/string_to_date_spec.rb +22 -0
- data/spec/unit/converters/date_time/string_to_datetime_spec.rb +32 -0
- data/spec/unit/converters/numeric/string_to_float_spec.rb +48 -0
- data/spec/unit/converters/{integer → numeric}/string_to_integer_spec.rb +28 -4
- data/spec/unit/converters/numeric/string_to_numeric_spec.rb +32 -0
- data/spec/unit/converters/range/string_to_range_spec.rb +25 -42
- data/spec/unit/register_spec.rb +17 -0
- metadata +17 -8
- data/lib/necromancer/converters/float.rb +0 -28
- data/lib/necromancer/converters/integer.rb +0 -48
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1c2f7a7dfa509891b8a6e9b54b0dc1c06220bd6
|
4
|
+
data.tar.gz: 0e4645b6d70af9782ded19b153937cb35ae29ca1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3e8624ae93d5dea444a1948fecee7be8dfdcdc24b04e4caca39dd93d22214b2035f124de5fdd360ee1af62bfce87dbadd20aa0a13496b7003b104cc3c879de6
|
7
|
+
data.tar.gz: c61f41045c0709a96f7e1d81dce673260f4410ffd91f6d69febf8de0f334d3c8634bb74b20efa6fd8ad88172ad3748bcb5e9e5c6d988c684344759f7159a7a89
|
data/CHANGELOG.md
ADDED
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.
|
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
|
63
|
+
**Necromancer** requires you to instatiate it:
|
49
64
|
|
50
65
|
```ruby
|
51
66
|
converter = Necromancer.new
|
52
67
|
```
|
53
68
|
|
54
|
-
Once
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
180
|
+
The **Necromancer** allows you to transform string into an array object:
|
99
181
|
|
100
182
|
```ruby
|
101
|
-
converter.
|
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
|
-
|
198
|
+
or simply:
|
109
199
|
|
110
200
|
```ruby
|
111
|
-
converter.convert(['1', '2.3',
|
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
|
-
|
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]).
|
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.
|
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.
|
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 )
|
data/lib/necromancer.rb
CHANGED
@@ -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/
|
10
|
-
require 'necromancer/converters/
|
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
|
data/lib/necromancer/context.rb
CHANGED
@@ -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
|
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
|
20
|
+
when /^\s*?((\d+)(\s*(,|-)\s*)?)+\s*?$/
|
14
21
|
value.to_s.split($4).map(&:to_i)
|
15
|
-
when /^((\w)(\s
|
16
|
-
value.to_s.split(
|
22
|
+
when /^((\w)(\s*(,|-)\s*)?)+$/
|
23
|
+
value.to_s.split($4)
|
17
24
|
else
|
18
|
-
|
19
|
-
|
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
|
-
|
46
|
+
numeric_converter = NumericConverters::StringToNumericConverter.new(:string, :numeric)
|
27
47
|
value.reduce([]) do |acc, el|
|
28
|
-
acc <<
|
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
|