File: README.md

package info (click to toggle)
ruby-necromancer 0.7.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 308 kB
  • sloc: ruby: 1,578; sh: 4; makefile: 4
file content (500 lines) | stat: -rw-r--r-- 15,888 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
# Necromancer

[![Gem Version](https://badge.fury.io/rb/necromancer.svg)][gem]
[![Actions CI](https://github.com/piotrmurach/tty-runner/workflows/CI/badge.svg?branch=master)][gh_actions_ci]
[![Build status](https://ci.appveyor.com/api/projects/status/qj3xn5gbbfi4puet?svg=true)][appveyor]
[![Code Climate](https://codeclimate.com/github/piotrmurach/necromancer/badges/gpa.svg)][codeclimate]
[![Coverage Status](https://coveralls.io/repos/github/piotrmurach/necromancer/badge.svg?branch=master)][coverage]
[![Inline docs](http://inch-ci.org/github/piotrmurach/necromancer.svg?branch=master)][inchpages]

[gem]: http://badge.fury.io/rb/necromancer
[gh_actions_ci]: https://github.com/piotrmurach/tty-runner/actions?query=workflow%3ACI
[appveyor]: https://ci.appveyor.com/project/piotrmurach/necromancer
[codeclimate]: https://codeclimate.com/github/piotrmurach/necromancer
[coverage]: https://coveralls.io/github/piotrmurach/necromancer
[inchpages]: http://inch-ci.org/github/piotrmurach/necromancer

> Conversion from one object type to another with a bit of black magic.

**Necromancer** provides independent type conversion component for [TTY](https://github.com/piotrmurach/tty) toolkit.

## Motivation

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.

## Features

* Simple and expressive API
* Ability to specify own converters
* Ability to compose conversions out of simpler ones
* Support conversion of custom defined types
* Ability to specify strict conversion mode

## Installation

Add this line to your application's Gemfile:

```ruby
gem "necromancer"
```

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install necromancer

## Contents

* [1. Usage](#1-usage)
* [2. Interface](#2-interface)
  * [2.1 convert](#21-convert)
  * [2.2 from](#22-from)
  * [2.3 to](#23-to)
  * [2.4 can?](#24-can)
  * [2.5 configure](#25-configure)
* [3. Converters](#3-converters)
  * [3.1 Array](#31-array)
  * [3.2 Boolean](#32-boolean)
  * [3.3 DateTime](#33-datetime)
  * [3.4 Hash](#34-hash)
  * [3.5 Numeric](#35-numeric)
  * [3.6 Range](#36-range)
  * [3.7 Custom](#37-custom)
    * [3.7.1 Using an Object](#371-using-an-object)
    * [3.7.2 Using a Proc](#372-using-a-proc)

## 1. Usage

**Necromancer** knows how to handle conversions between various types using the `convert` method. The `convert` method takes as an argument the value to convert from.  Then to perform actual coercion use the `to` or more functional style `>>` method that accepts the type for the returned value which can be `:symbol`, `object` or `ClassName`.

For example, to convert a string to a [range](#36-range) type:

```ruby
Necromancer.convert("1-10").to(:range)  # => 1..10
Necromancer.convert("1-10") >> :range   # => 1..10
Necromancer.convert("1-10") >> Range    # => 1..10
```

In order to handle [boolean](#32-boolean) conversions:

```ruby
Necromancer.convert("t").to(:boolean)   # => true
Necromancer.convert("t") >> true        # => true
```

To convert string to [numeric](#35-numeric) value:

```ruby
Necromancer.convert("10e1").to(:numeric)  # => 100
```

You can convert string to [array](#31-array) of values like `boolean`, `integer` or `float`:

```ruby
Necromancer.convert("t,f,t"]).to(:booleans)      # => [true, false, true]
Necromancer.convert("1,2.3,3.0"]).to(:integers)  # => [1, 2, 3]
Necromancer.convert("1,2.3,3.0"]).to(:floats)    # => [1.0, 2.3, 3.0]
```

To convert string to [hash](#34-hash) value:

```ruby
Necromancer.convert("a:1 b:2 c:3").to(:hash) # => {a: "1", b: "2", c: "3"}
Necromancer.convert("a=1 b=2 c=3").to(:hash) # => {a: "1", b: "2", c: "3"}
````

To provide extra information about the conversion value type use the `from`:

```ruby
Necromancer.convert(["1", "2.3", "3.0"]).from(:array).to(:numeric) # => [1, 2.3, 3.0]
```

**Necromancer** also allows you to add [custom](#37-custom) conversions.

When conversion isn't possible, a `Necromancer::NoTypeConversionAvailableError` is thrown indicating that `convert` doesn't know how to perform the requested conversion:

```ruby
Necromancer.convert(:foo).to(:float)
# => Necromancer::NoTypeConversionAvailableError: Conversion 'foo->float' unavailable.
```

## 2. Interface

**Necromancer** will perform conversions on the supplied object through use of `convert`, `from` and `to` methods.

### 2.1 convert

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:

```ruby
Necromancer.convert("1,10").to(:range)  #  => 1..10
```

Alternatively, you can use block:

```ruby
Necromancer.convert { "1,10" }.to(:range) # => 1..10
```

Conversion isn't always possible, in which case a `Necromancer::NoTypeConversionAvailableError` is thrown indicating that `convert` doesn't know how to perform the requested conversion:

```ruby
Necromancer.convert(:foo).to(:float)
# => Necromancer::NoTypeConversionAvailableError: Conversion 'foo->float' unavailable.
```

### 2.2 from

To specify conversion source type use `from` method:

```ruby
Necromancer.convert("1.0").from(:string).to(:numeric)
```

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

The source parameters are:

* `:array`
* `:boolean`
* `:date`
* `:datetime`
* `:float`
* `:integer`
* `:numeric`
* `:range`
* `:string`
* `:time`

### 2.3 to

To convert objects between types, **Necromancer** provides several target types. The `to` or functional style `>>` method allows you to pass target as an argument to perform actual conversion. The target can be one of `:symbol`, `object` or `ClassName`:

```ruby
Necromancer.convert("yes").to(:boolean)   # => true
Necromancer.convert("yes") >> :boolean    # => true
Necromancer.convert("yes") >> true        # => true
Necromancer.convert("yes") >> TrueClass   # => true
```

By default, when target conversion fails the original value is returned. However, you can pass `strict` as an additional argument to ensure failure when conversion cannot be performed:

```ruby
Necromancer.convert("1a").to(:integer, strict: true)
# => raises Necromancer::ConversionTypeError
```

The target parameters are:

* `:array`
* `:boolean`, `:booleans`, `:bools`, `:boolean_hash`, `:bool_hash`
* `:date`
* `:datetime`,
* `:float`, `:floats`, `:float_hash`
* `:integer`, `:integers`, `:ints`, `:integer_hash`, `:int_hash`
* `:numeric`, `:numerics`, `:nums`, `:numeric_hash`, `:num_hash`
* `:range`
* `:string`
* `:time`

### 2.4 can?

To verify that a given conversion can be handled by **Necromancer** call `can?` with the `source` and `target` of the desired conversion.

```ruby
converter = Necromancer.new
converter.can?(:string, :integer)   # => true
converter.can?(:unknown, :integer)  # => false
```

### 2.5 configure

You may set global configuration options on **Necromancer** instance by passing a block like so:

```ruby
Necromancer.new do |config|
  config.strict true
end
```

Or calling `configure` method:

```ruby
converter = Necromancer.new
converter.configure do |config|
  config.copy false
end
```

Available configuration options are:

* `strict` - ensures correct types for conversion, by default `false`
* `copy` - ensures only copy is modified, by default `true`

## 3. Converters

**Necromancer** flexibility means you can register your own converters or use the already defined converters for such types as `Array`, `Boolean`, `Date`, `DateTime`, `Hash`, `Numeric`, `Range` and `Time`.

### 3.1 Array

The **Necromancer** allows you to transform arbitrary object into array:

```ruby
Necromancer.convert(nil).to(:array)     # => []
Necromancer.convert({x: 1}).to(:array)  # => [[:x, 1]]
```

In addition, **Necromancer** excels at converting `,` or `-` delimited string into an array object:

```ruby
Necromancer.convert("a, b, c").to(:array)  # => ["a", "b", "c"]
```

If the string is a list of `-` or `,` separated numbers, they will be converted to their respective numeric types:

```ruby
Necromancer.convert("1 - 2 - 3").to(:array)  # => [1, 2, 3]
```

It handles conversion of string into an array of boolean values as well:

```ruby
Necromancer.convert("yes,no,t").to(:booleans)    # => [true, false, true]
Necromancer.convert("1 - f - FALSE").to(:bools)  # => [true, false, false]
```

You can also convert array containing string objects to array containing numeric values:

```ruby
Necromancer.convert(["1", "2.3", "3.0"]).to(:numerics) # => [1, 2.3, 3.0]
Necromancer.convert(["1", "2.3", "3.0"]).to(:nums)     # => [1, 2.3, 3.0]
```

Or you can be more specific by using `:integers` and `:floats` as the resulting type:

```ruby
Necromancer.convert(["1", "2.3", "3.0"]).to(:integers) # => [1, 2, 3]
```

When in `strict` mode the conversion will raise a `Necromancer::ConversionTypeError` error like so:

```ruby
Necromancer.convert(["1", "2.3", false]).to(:numerics, strict: true)
# => Necromancer::ConversionTypeError: false cannot be converted from `array` to `numerics`
```

However, in `non-strict` mode the value will be simply returned unchanged:

```ruby
Necromancer.convert(["1", "2.3", false]).to(:numerics, strict: false)
# => [1, 2.3, false]
```

### 3.2 Boolean

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

```ruby
Necromancer.convert("yes").to(:boolean)  # => true
```

Similarly, the `0`, `"0"`, `"f"`, `"F"`, `"false"`, `"FALSE"`, `"n"`, `"N"`, `"no"`, `"No"`, `"off"`, `"OFF"` values are converted to `FalseClass`.

```ruby
Necromancer.convert("no").to(:boolean) # => false
```

You can also convert an integer object to boolean:

```ruby
Necromancer.convert(1).to(:boolean)  # => true
Necromancer.convert(0).to(:boolean)  # => false
```

### 3.3 DateTime

**Necromancer** knows how to convert string to `date` object:

```ruby
Necromancer.convert("1-1-2015").to(:date)    # => "2015-01-01"
Necromancer.convert("01/01/2015").to(:date)  # => "2015-01-01"
```

You can also convert string to `datetime`:

```ruby
Necromancer.convert("1-1-2015").to(:datetime)          # => "2015-01-01T00:00:00+00:00"
Necromancer.convert("1-1-2015 15:12:44").to(:datetime) # => "2015-01-01T15:12:44+00:00"
```

To convert a string to a time instance do:

```ruby
Necromancer.convert("01-01-2015").to(:time)       # => 2015-01-01 00:00:00 +0100
Necromancer.convert("01-01-2015 08:35").to(:time) # => 2015-01-01 08:35:00 +0100
Necromancer.convert("12:35").to(:time)            # => 2015-01-04 12:35:00 +0100
```

### 3.4 Hash

With **Necromancer** you can convert a string with pairs delimited by `=` or `:` characters into a hash:

```ruby
Necromancer.convert("a:1 b:2 c:3").to(:hash)
Necromancer.convert("a=1 b=2 c=3").to(:hash)
# => {a: "1", b: "2", c: "3"}
```

The pairs can be separated by `&` symbols and mix `=` and `:` pair delimiters:

```ruby
Necromancer.convert("a:1 & b=2 & c:3").to(:hash)
# => {a: "1", b: "2", c: "3"}
```

You can also convert string to hash with integer values using `:int_hash` type:

```ruby
Necromancer.convert("a:1 b:2 c:3").to(:int_hash)     # => {a: 1, b: 2, c: 3}
Necromancer.convert("a:1 b:2 c:3").to(:integer_hash) # => {a: 1, b: 2, c: 3}
```

Similarly you can convert string to hash with `float` or `numeric` values using `:float_hash` and `numeric_hash` types:

```ruby
Necromancer.convert("a:1 b:2 c:3").to(:float_hash)    # => {a: 1.0, b: 2.0, c: 3.0}
Necromancer.convert("a:1 b:2.0 c:3").to(:num_hash)    # => {a: 1, b:2.0, c: 3}
```

String can also be converted to hash with boolean values using `:boolean_hash` or `:bool_hash`:

```ruby
Necromancer.convert("a:yes b:no c:t").to(:bool_hash)  # => {a: true, b: false, c: true}
```

### 3.5 Numeric

**Necromancer** comes ready to convert all the primitive numeric values.

To convert a string to a float do:

```ruby
Necromancer.convert("1.2a").to(:float)  #  => 1.2
```

Conversion to numeric in strict mode raises `Necromancer::ConversionTypeError`:

```ruby
Necromancer.convert("1.2a").to(:float, strict: true) # => raises error
```

To convert a string to an integer do:

```ruby
Necromancer.convert("1a").to(:integer)  #  => 1
```

However, if you want to convert string to an appropriate matching numeric type do:

```ruby
Necromancer.convert("1e1").to(:numeric)   # => 10
```

### 3.6 Range

**Necromancer** is no stranger to figuring out ranges from strings. You can pass `,`, `-`, `..`, `...` characters to denote ranges:

```ruby
Necromancer.convert("1,10").to(:range)  # => 1..10
```

Or to create a range of letters:

```ruby
Necromancer.convert("a-z").to(:range)   # => "a".."z"
```

It will handle space characters:

```ruby
Necromancer.convert("1 . . 10") >> :range   # => 1..10
Necromancer.convert("a . . . z") >> :range  # =>  "a"..."z"
````

### 3.7 Custom

In case where provided conversions do not match your needs you can create your own and `register` with **Necromancer** by using an `Object` or a `Proc`.

#### 3.7.1 Using an Object

Firstly, you need to create a converter that at minimum requires to specify `call` method that will be invoked during conversion:

```ruby
UpcaseConverter = Struct.new(:source, :target) do
  def call(value, options = {})
    value.upcase
  end
end
```

Inside the `UpcaseConverter` you have access to global configuration options by directly calling `config` method.

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:

```ruby
upcase_converter = UpcaseConverter.new(:string, :upcase)
```

**Necromancer** provides the `register` method to add converter:

```ruby
converter = Necromancer.new
converter.register(upcase_converter)   # => true if successfully registered
```

Finally, by invoking `convert` method and specifying `:upcase` as the target for the conversion we achieve the result:

```ruby
converter.convert("magic").to(:upcase)   # => "MAGIC"
```

#### 3.7.2 Using a Proc

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:

```ruby
converter = Necromancer.new

converter.register do |c|
  c.source= :string
  c.target= :upcase
  c.convert = proc { |value, options| value.upcase }
end
```

Then by invoking the `convert` method and passing the `:upcase` conversion type you can transform the string like so:

```ruby
converter.convert("magic").to(:upcase)   # => "MAGIC"
```

## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/necromancer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.

1. Fork it ( https://github.com/piotrmurach/necromancer/fork )
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create a new Pull Request

## License

The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

## Copyright

Copyright (c) 2014 Piotr Murach. See LICENSE for further details.