Conversion from one object type to another with a bit of black magic.
Conversion from one object type to another with a bit of black magic.
Necromancer provides independent type conversion component for TTY toolkit.
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.
Add this line to your application's Gemfile:
gem "necromancer"
And then execute:
$ bundle
Or install it yourself as:
$ gem install necromancer
Necromancer knows how to handle conversions between various types using the
convertmethod. The
convertmethod takes as an argument the value to convert from. Then to perform actual coercion use the
toor more functional style
>>method that accepts the type for the returned value which can be
:symbol,
objector
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,
integeror
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:
Necromancer.convert(["1", "2.3", "3.0"]).from(:array).to(:numeric) # => [1, 2.3, 3.0]
Necromancer also allows you to add custom conversions.
When conversion isn't possible, a
Necromancer::NoTypeConversionAvailableErroris thrown indicating that
convertdoesn't know how to perform the requested conversion:
Necromancer.convert(:foo).to(:float) # => Necromancer::NoTypeConversionAvailableError: Conversion 'foo->float' unavailable.
Necromancer will perform conversions on the supplied object through use of
convert,
fromand
tomethods.
For the purpose of divination, Necromancer uses
convertmethod to turn source type into target type. For example, in order to convert a string into a range type do:
Necromancer.convert("1,10").to(:range) # => 1..10
Alternatively, you can use block:
Necromancer.convert { "1,10" }.to(:range) # => 1..10
Conversion isn't always possible, in which case a
Necromancer::NoTypeConversionAvailableErroris thrown indicating that
convertdoesn't know how to perform the requested conversion:
Necromancer.convert(:foo).to(:float) # => Necromancer::NoTypeConversionAvailableError: Conversion 'foo->float' unavailable.
To specify conversion source type use
frommethod:
Necromancer.convert("1.0").from(:string).to(:numeric)
In majority of cases you do not need to specify
fromas the type will be inferred from the
convertmethod argument and then appropriate conversion will be applied to result in
targettype such as
:numeric. However, if you do not control the input to
convertand want to ensure consistent behaviour please use
from.
The source parameters are:
:array
:boolean
:date
:datetime
:float
:integer
:numeric
:range
:string
:time
To convert objects between types, Necromancer provides several target types. The
toor functional style
>>method allows you to pass target as an argument to perform actual conversion. The target can be one of
:symbol,
objector
ClassName:
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
strictas an additional argument to ensure failure when conversion cannot be performed:
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
To verify that a given conversion can be handled by Necromancer call
can?with the
sourceand
targetof the desired conversion.
converter = Necromancer.new converter.can?(:string, :integer) # => true converter.can?(:unknown, :integer) # => false
You may set global configuration options on Necromancer instance by passing a block like so:
Necromancer.new do |config| config.strict true end
Or calling
configuremethod:
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
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,
Rangeand
Time.
The Necromancer allows you to transform arbitrary object into array:
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:
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:
Necromancer.convert("1 - 2 - 3").to(:array) # => [1, 2, 3]
It handles conversion of string into an array of boolean values as well:
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:
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
:integersand
:floatsas the resulting type:
Necromancer.convert(["1", "2.3", "3.0"]).to(:integers) # => [1, 2, 3]
When in
strictmode the conversion will raise a
Necromancer::ConversionTypeErrorerror like so:
Necromancer.convert(["1", "2.3", false]).to(:numerics, strict: true) # => Necromancer::ConversionTypeError: false cannot be converted from `array` to `numerics`
However, in
non-strictmode the value will be simply returned unchanged:
Necromancer.convert(["1", "2.3", false]).to(:numerics, strict: false) # => [1, 2.3, false]
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.
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.
Necromancer.convert("no").to(:boolean) # => false
You can also convert an integer object to boolean:
Necromancer.convert(1).to(:boolean) # => true Necromancer.convert(0).to(:boolean) # => false
Necromancer knows how to convert string to
dateobject:
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:
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:
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
With Necromancer you can convert a string with pairs delimited by
=or
:characters into a hash:
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:
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_hashtype:
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
floator
numericvalues using
:float_hashand
numeric_hashtypes:
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_hashor
:bool_hash:
Necromancer.convert("a:yes b:no c:t").to(:bool_hash) # => {a: true, b: false, c: true}
Necromancer comes ready to convert all the primitive numeric values.
To convert a string to a float do:
Necromancer.convert("1.2a").to(:float) # => 1.2
Conversion to numeric in strict mode raises
Necromancer::ConversionTypeError:
Necromancer.convert("1.2a").to(:float, strict: true) # => raises error
To convert a string to an integer do:
Necromancer.convert("1a").to(:integer) # => 1
However, if you want to convert string to an appropriate matching numeric type do:
Necromancer.convert("1e1").to(:numeric) # => 10
Necromancer is no stranger to figuring out ranges from strings. You can pass
,,
-,
..,
...characters to denote ranges:
Necromancer.convert("1,10").to(:range) # => 1..10
Or to create a range of letters:
Necromancer.convert("a-z").to(:range) # => "a".."z"
It will handle space characters:
Necromancer.convert("1 . . 10") >> :range # => 1..10 Necromancer.convert("a . . . z") >> :range # => "a"..."z"
In case where provided conversions do not match your needs you can create your own and
registerwith Necromancer by using an
Objector a
Proc.
Firstly, you need to create a converter that at minimum requires to specify
callmethod that will be invoked during conversion:
UpcaseConverter = Struct.new(:source, :target) do def call(value, options = {}) value.upcase end end
Inside the
UpcaseConverteryou have access to global configuration options by directly calling
configmethod.
Then you need to specify what type conversions this converter will support. For example,
UpcaseConverterwill 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
registermethod to add converter:
converter = Necromancer.new converter.register(upcase_converter) # => true if successfully registered
Finally, by invoking
convertmethod and specifying
:upcaseas the target for the conversion we achieve the result:
converter.convert("magic").to(:upcase) # => "MAGIC"
Using a Proc object you can create and immediately register a converter. You need to pass
sourceand
targetof the conversion that will be used later on to match the conversion. The
convertallows you to specify the actual conversion in block form. For example:
converter = Necromancer.newconverter.register do |c| c.source= :string c.target= :upcase c.convert = proc { |value, options| value.upcase } end
Then by invoking the
convertmethod and passing the
:upcaseconversion type you can transform the string like so:
converter.convert("magic").to(:upcase) # => "MAGIC"
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 code of conduct.
git checkout -b my-new-feature)
git commit -am 'Add some feature')
git push origin my-new-feature)
The gem is available as open source under the terms of the MIT License.
Copyright (c) 2014 Piotr Murach. See LICENSE for further details.