Necromancer
¶ ↑
Conversion from one object type to another with a bit of black magic.
Necromancer provides independent type conversion component for 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:
gem "necromancer"
And then execute:
$ bundle
Or install it yourself as:
$ gem install necromancer
Contents¶ ↑
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 type:
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 conversions:
Necromancer.convert("t").to(:boolean) # => true Necromancer.convert("t") >> true # => true
To convert string to numeric value:
Necromancer.convert("10e1").to(:numeric) # => 100
You can convert string to array of values like boolean
, integer
or float
:
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 value:
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:
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:
upcase_converter = UpcaseConverter.new(:string, :upcase)
Necromancer provides the register
method to add converter:
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:
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:
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:
converter.convert("magic").to(:upcase) # => "MAGIC"
Contributing¶ ↑
Bug reports and pull requests are welcome on GitHub at 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 code of conduct.
-
Fork it ( github.com/piotrmurach/necromancer/fork )
-
Create your feature branch (
git checkout -b my-new-feature
) -
Commit your changes (
git commit -am 'Add some feature'
) -
Push to the branch (
git push origin my-new-feature
) -
Create a new Pull Request
License¶ ↑
The gem is available as open source under the terms of the MIT License.
Copyright¶ ↑
Copyright © 2014 Piotr Murach. See LICENSE for further details.