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.
- 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
|