New Upstream Release - ruby-necromancer

Ready changes

Summary

Merged new upstream version: 0.7.0 (was: 0.5.1).

Resulting package

Built on 2022-12-29T18:13 (took 4m14s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-necromancer

Lintian Result

Diff

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..2bb2923
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,9 @@
+root = true
+
+[*.rb]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..06ec573
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: piotrmurach
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..78ece6f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,23 @@
+### Are you in the right place?
+* For issues or feature requests file a GitHub issue in this repository
+* For general questions or discussion post on StackOverflow
+
+### Describe the problem
+A brief description of the issue/feature.
+
+### Steps to reproduce the problem
+```
+Your code here to reproduce the issue
+```
+
+### Actual behaviour
+What happened? This could be a description, log output, error raised etc...
+
+### Expected behaviour
+What did you expect to happen?
+
+### Describe your environment
+
+* OS version:
+* Ruby version:
+* Necromancer version:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..e07f4b7
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,19 @@
+### Describe the change
+What does this Pull Request do?
+
+### Why are we doing this?
+Any related context as to why is this is a desirable change.
+
+### Benefits
+How will the library improve?
+
+### Drawbacks
+Possible drawbacks applying this change.
+
+### Requirements
+Put an X between brackets on each line if you have done the item:
+
+- [ ] Tests written & passing locally?
+- [ ] Code style checked?
+- [ ] Rebased with `master` branch?
+- [ ] Documentation updated?
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d2e3d64
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,56 @@
+---
+name: CI
+on:
+  push:
+    branches:
+      - master
+    paths-ignore:
+      - "bin/**"
+      - "*.md"
+  pull_request:
+    branches:
+      - master
+    paths-ignore:
+      - "bin/**"
+      - "*.md"
+jobs:
+  tests:
+    name: Ruby ${{ matrix.ruby }}
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os:
+          - ubuntu-latest
+        ruby:
+          - 2.1
+          - 2.2
+          - 2.3
+          - 2.4
+          - 2.5
+          - 2.6
+          - 3.0
+          - ruby-head
+          - jruby-9.2.13.0
+          - jruby-head
+          - truffleruby-head
+        include:
+          - ruby: 2.7
+            os: ubuntu-latest
+            coverage: true
+    env:
+      COVERAGE: ${{ matrix.coverage }}
+      COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
+    continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Ruby
+        uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+      - name: Install bundler
+        run: gem install bundler -v '< 2.0'
+      - name: Install dependencies
+        run: bundle install --jobs 4 --retry 3
+      - name: Run tests
+        run: bundle exec rake ci
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ae3fdc2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+/.bundle/
+/.yardoc
+/Gemfile.lock
+/_yardoc/
+/coverage/
+/doc/
+/pkg/
+/spec/reports/
+/tmp/
+*.bundle
+*.so
+*.o
+*.a
+mkmf.log
diff --git a/.rspec b/.rspec
new file mode 100644
index 0000000..65122f0
--- /dev/null
+++ b/.rspec
@@ -0,0 +1,3 @@
+--color
+--require spec_helper
+--warnings
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 0000000..714bba5
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,58 @@
+AllCops:
+  NewCops: enable
+
+Layout/FirstArrayElementIndentation:
+  EnforcedStyle: consistent
+
+Layout/HashAlignment:
+  EnforcedHashRocketStyle: table
+
+Layout/LineLength:
+  Max: 82
+
+Lint/AssignmentInCondition:
+  Enabled: false
+
+Metrics/AbcSize:
+  Max: 30
+
+Metrics/BlockLength:
+  CountComments: true
+  Max: 25
+  IgnoredMethods: []
+  Exclude:
+    - "spec/**/*"
+
+Metrics/ClassLength:
+  Max: 1500
+
+Metrics/CyclomaticComplexity:
+  Enabled: false
+
+Metrics/MethodLength:
+  Max: 20
+
+Naming/BinaryOperatorParameterName:
+  Enabled: false
+
+Style/AccessorGrouping:
+  Enabled: false
+
+Style/AsciiComments:
+  Enabled: false
+
+Style/LambdaCall:
+  EnforcedStyle: braces
+
+Style/StringLiterals:
+  EnforcedStyle: double_quotes
+
+Style/TrivialAccessors:
+  Enabled: false
+
+# { ... } for multi-line blocks is okay
+Style/BlockDelimiters:
+  Enabled: false
+
+Style/CommentedKeyword:
+  Enabled: false
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f8f2ca..b6561b0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
 # Change log
 
+## [v0.7.0] - 2020-12-29
+
+### Added
+* Add HashConverters for transforming string into hash of string, integer, float or boolean values
+* Add converters for transforming string to array of booleans, integers, floats and numeric
+
+### Changed
+* Change StringToRange converter to work with decimal numbers and spaces
+* Change :strict to be a keyword argument
+* Change StringToNumeric converter to allow numbers with space characters
+
+## [v0.6.0] - 2020-03-08
+
+### Changed
+* Change gemspec to remove test artifacts
+
 ## [v0.5.1] - 2019-11-24
 
 ### Changed
@@ -52,6 +68,8 @@
 
 * Initial implementation and release
 
+[v0.7.0]: https://github.com/piotrmurach/necromancer/compare/v0.6.0...v0.7.0
+[v0.6.0]: https://github.com/piotrmurach/necromancer/compare/v0.5.1...v0.6.0
 [v0.5.1]: https://github.com/piotrmurach/necromancer/compare/v0.5.0...v0.5.1
 [v0.5.0]: https://github.com/piotrmurach/necromancer/compare/v0.4.0...v0.5.0
 [v0.4.0]: https://github.com/piotrmurach/necromancer/compare/v0.3.0...v0.4.0
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..c22634c
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at piotr@piotrmurach.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [https://contributor-covenant.org/version/1/4][version]
+
+[homepage]: https://contributor-covenant.org
+[version]: https://contributor-covenant.org/version/1/4/
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..6112c23
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,12 @@
+source "https://rubygems.org"
+
+gemspec
+
+gem "simplecov", "~> 0.16.1"
+gem "coveralls", "~> 0.8.22"
+gem "yardstick", "~> 0.9.9"
+gem "json", "2.4.1" if RUBY_VERSION == "2.0.0"
+
+group :development do
+  gem "benchmark-ips", "~> 2.7.2"
+end
diff --git a/README.md b/README.md
index 88075f3..f727907 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,14 @@
 # Necromancer
 
 [![Gem Version](https://badge.fury.io/rb/necromancer.svg)][gem]
-[![Build Status](https://secure.travis-ci.org/piotrmurach/necromancer.svg?branch=master)][travis]
+[![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
-[travis]: http://travis-ci.org/piotrmurach/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
@@ -35,7 +35,7 @@ Conversion between Ruby core types frequently comes up in projects but is solved
 Add this line to your application's Gemfile:
 
 ```ruby
-gem 'necromancer'
+gem "necromancer"
 ```
 
 And then execute:
@@ -73,39 +73,48 @@ Or install it yourself as:
 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
+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
+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
+Necromancer.convert("10e1").to(:numeric)  # => 100
 ```
 
-or convert [array](#31-array) of string values into numeric type:
+You can convert string to [array](#31-array) of values like `boolean`, `integer` or `float`:
 
 ```ruby
-Necromancer.convert(['1', '2.3', '3.0']).to(:numeric)  # => [1, 2.3, 3.0]
+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 provide extra information about the conversion value type you can use `from`.
+To convert string to [hash](#34-hash) value:
 
 ```ruby
-Necromancer.convert(['1', '2.3', '3.0']).from(:array).to(:numeric) # => [1, 2.3, 3.0]
+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.
 
-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:
+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)
@@ -121,13 +130,13 @@ Necromancer.convert(:foo).to(:float)
 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
+Necromancer.convert("1,10").to(:range)  #  => 1..10
 ```
 
 Alternatively, you can use block:
 
 ```ruby
-Necromancer.convert { '1,10' }.to(:range) # => 1..10
+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:
@@ -142,54 +151,54 @@ Necromancer.convert(:foo).to(:float)
 To specify conversion source type use `from` method:
 
 ```ruby
-Necromancer.convert('1.0').from(:string).to(:numeric)
+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
+* `: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
+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 orignal value is returned. However, you can pass `strict` as an additional argument to ensure failure when conversion cannot be performed:
+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)
+Necromancer.convert("1a").to(:integer, strict: true)
 # => raises Necromancer::ConversionTypeError
 ```
 
 The target parameters are:
 
-* :array
-* :boolean
-* :date
-* :datetime
-* :float
-* :integer
-* :numeric
-* :range
-* :string
-* :time
+* `: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?
 
@@ -211,7 +220,7 @@ Necromancer.new do |config|
 end
 ```
 
-or calling `configure` method:
+Or calling `configure` method:
 
 ```ruby
 converter = Necromancer.new
@@ -222,12 +231,12 @@ end
 
 Available configuration options are:
 
-* strict - ensures correct types for conversion, by default `false`
-* copy - ensures only copy is modified, by default `true`
+* `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`, `Hash`, `Numeric`, `Range`.
+**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
 
@@ -241,47 +250,61 @@ 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']
+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]
+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(:numeric)
+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(:numeric, strict: true)
-# => Necromancer::ConversionTypeError: false cannot be converted from `array` to `numeric`
+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(:numeric, strict: false)
+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`.
+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
+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`.
+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
+Necromancer.convert("no").to(:boolean) # => false
 ```
 
 You can also convert an integer object to boolean:
@@ -296,8 +319,8 @@ Necromancer.convert(0).to(:boolean)  # => false
 **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"
+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`:
@@ -317,9 +340,39 @@ 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({ x: '27.5', y: '4', z: '11'}).to(:numeric)
-# => { x: 27.5, y: 4, z: 11}
+Necromancer.convert("a:yes b:no c:t").to(:bool_hash)  # => {a: true, b: false, c: true}
 ```
 
 ### 3.5 Numeric
@@ -329,25 +382,25 @@ Necromancer.convert({ x: '27.5', y: '4', z: '11'}).to(:numeric)
 To convert a string to a float do:
 
 ```ruby
-Necromancer.convert('1.2a').to(:float)  #  => 1.2
+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
+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
+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
+Necromancer.convert("1e1").to(:numeric)   # => 10
 ```
 
 ### 3.6 Range
@@ -355,15 +408,22 @@ Necromancer.convert('1e1').to(:numeric)   # => 10
 **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
+Necromancer.convert("1,10").to(:range)  # => 1..10
 ```
 
-or to create a range of letters:
+Or to create a range of letters:
 
 ```ruby
-Necromancer.convert('a-z').to(:range)   # => 'a'..'z'
+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`.
@@ -398,7 +458,7 @@ 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'
+converter.convert("magic").to(:upcase)   # => "MAGIC"
 ```
 
 #### 3.7.2 Using a Proc
@@ -418,7 +478,7 @@ 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'
+converter.convert("magic").to(:upcase)   # => "MAGIC"
 ```
 
 ## Contributing
diff --git a/Rakefile b/Rakefile
index d5eb532..af3337a 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,8 +1,10 @@
-require 'bundler/gem_tasks'
+# frozen_string_literal: true
 
-FileList['tasks/**/*.rake'].each(&method(:import))
+require "bundler/gem_tasks"
 
-desc 'Run all specs'
-task ci: %w[ spec ]
+FileList["tasks/**/*.rake"].each(&method(:import))
+
+desc "Run all specs"
+task ci: %w[spec]
 
 task default: :spec
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..8319665
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,32 @@
+---
+skip_commits:
+  files:
+    - "bin/**"
+    - "*.md"
+install:
+  - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
+  - gem install bundler -v '< 2.0'
+  - bundle install --jobs 4 --retry 3
+before_test:
+  - ruby -v
+  - gem -v
+  - bundle -v
+build: off
+test_script:
+  - bundle exec rake ci
+environment:
+  matrix:
+    - ruby_version: "200"
+    - ruby_version: "200-x64"
+    - ruby_version: "21"
+    - ruby_version: "21-x64"
+    - ruby_version: "22"
+    - ruby_version: "22-x64"
+    - ruby_version: "23"
+    - ruby_version: "23-x64"
+    - ruby_version: "24"
+    - ruby_version: "24-x64"
+    - ruby_version: "25"
+    - ruby_version: "25-x64"
+    - ruby_version: "26"
+    - ruby_version: "26-x64"
diff --git a/bin/console b/bin/console
new file mode 100755
index 0000000..1c9c29f
--- /dev/null
+++ b/bin/console
@@ -0,0 +1,14 @@
+#!/usr/bin/env ruby
+
+require "bundler/setup"
+require "necromancer"
+
+# You can add fixtures and/or initialization code here to make experimenting
+# with your gem easier. You can also use a different console, if you like.
+
+# (If you use this, don't forget to add pry to your Gemfile!)
+# require "pry"
+# Pry.start
+
+require "irb"
+IRB.start(__FILE__)
diff --git a/bin/setup b/bin/setup
new file mode 100755
index 0000000..dce67d8
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+set -euo pipefail
+IFS=$'\n\t'
+set -vx
+
+bundle install
+
+# Do any other automated setup that you need to do here
diff --git a/debian/changelog b/debian/changelog
index 196f617..7e4c3df 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,10 +1,12 @@
-ruby-necromancer (0.5.1-3) UNRELEASED; urgency=low
+ruby-necromancer (0.7.0-1) UNRELEASED; urgency=low
 
   * Set field Upstream-Contact in debian/copyright.
   * Remove obsolete fields Contact, Name from debian/upstream/metadata (already
     present in machine-readable debian/copyright).
+  * New upstream release.
+  * Drop patch bring_back_dot_rspec, present upstream.
 
- -- Debian Janitor <janitor@jelmer.uk>  Sat, 29 Aug 2020 23:59:39 -0000
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 29 Dec 2022 18:09:35 -0000
 
 ruby-necromancer (0.5.1-2) unstable; urgency=medium
 
diff --git a/debian/patches/bring_back_dot_rspec b/debian/patches/bring_back_dot_rspec
deleted file mode 100644
index 00e0a92..0000000
--- a/debian/patches/bring_back_dot_rspec
+++ /dev/null
@@ -1,21 +0,0 @@
-Description: The .rspec file is missing from the released gem
- The github repository for this library contains a .rspec file that makes it
- possible to run tests on the source code. However, it is currently not
- released within the gem file published on rubygems.org. This makes it
- impossible for the debian package build process to run tests without failing.
- .
- The file content is what can currently be found in the git repository for tag
- 0.5.1.
- .
- ruby-necromancer (0.5.1-1) UNRELEASED; urgency=medium
- .
-   * Initial release (Closes: #939307)
-Author: Gabriel Filion <gabriel@koumbit.org>
-Bug-Debian: https://bugs.debian.org/939307
-
---- /dev/null
-+++ ruby-necromancer-0.5.1/.rspec
-@@ -0,0 +1,3 @@
-+--color
-+--require spec_helper
-+--warnings
diff --git a/debian/patches/series b/debian/patches/series
index 9c7f614..e69de29 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +0,0 @@
-bring_back_dot_rspec
diff --git a/lib/necromancer.rb b/lib/necromancer.rb
index 4007130..6d5f07c 100644
--- a/lib/necromancer.rb
+++ b/lib/necromancer.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
-require_relative 'necromancer/context'
-require_relative 'necromancer/version'
+require_relative "necromancer/context"
+require_relative "necromancer/version"
 
 module Necromancer
   # Raised when cannot conver to a given type
@@ -26,7 +26,8 @@ module Necromancer
   # Convenience to directly call conversion
   #
   # @example
-  #   Necromancer.convert('1').to(:integer)
+  #   Necromancer.convert("1").to(:integer)
+  #   # => 1
   #
   # @return [ConversionTarget]
   #
diff --git a/lib/necromancer/context.rb b/lib/necromancer/context.rb
index 1a5124f..dfe093c 100644
--- a/lib/necromancer/context.rb
+++ b/lib/necromancer/context.rb
@@ -1,10 +1,10 @@
 # frozen_string_literal: true
 
-require 'forwardable'
+require "forwardable"
 
-require_relative 'configuration'
-require_relative 'conversions'
-require_relative 'conversion_target'
+require_relative "configuration"
+require_relative "conversions"
+require_relative "conversion_target"
 
 module Necromancer
   # A class used by Necromancer to provide user interace
@@ -19,7 +19,7 @@ module Necromancer
     #
     # @api private
     def initialize(&block)
-      block.call(configuration) if block_given?
+      block.(configuration) if block_given?
       @conversions = Conversions.new(configuration)
       @conversions.load
     end
diff --git a/lib/necromancer/conversion_target.rb b/lib/necromancer/conversion_target.rb
index 0cd4180..d55d21d 100644
--- a/lib/necromancer/conversion_target.rb
+++ b/lib/necromancer/conversion_target.rb
@@ -20,12 +20,12 @@ module Necromancer
       if UndefinedValue.equal?(value)
         unless block
           raise ArgumentError,
-                'You need to pass either argument or a block to `convert`.'
+                "You need to pass either argument or a block to `convert`."
         end
         new(context, block.call)
       elsif block
         raise ArgumentError,
-              'You cannot pass both an argument and a block to `convert`.'
+              "You cannot pass both an argument and a block to `convert`."
       else
         new(context, value)
       end
@@ -34,7 +34,7 @@ module Necromancer
     # Allows to specify conversion source type
     #
     # @example
-    #   converter.convert('1').from(:string).to(:numeric)  # => 1
+    #   converter.convert("1").from(:string).to(:numeric)  # => 1
     #
     # @return [ConversionType]
     #
@@ -47,10 +47,10 @@ module Necromancer
     # Runs a given conversion
     #
     # @example
-    #   converter.convert('1').to(:numeric)  # => 1
+    #   converter.convert("1").to(:numeric)  # => 1
     #
     # @example
-    #   converter.convert('1') >> Integer # => 1
+    #   converter.convert("1") >> Integer # => 1
     #
     # @return [Object]
     #   the converted target type
diff --git a/lib/necromancer/conversions.rb b/lib/necromancer/conversions.rb
index f579317..b0dea9d 100644
--- a/lib/necromancer/conversions.rb
+++ b/lib/necromancer/conversions.rb
@@ -1,12 +1,13 @@
 # frozen_string_literal: true
 
-require_relative 'configuration'
-require_relative 'converter'
-require_relative 'converters/array'
-require_relative 'converters/boolean'
-require_relative 'converters/date_time'
-require_relative 'converters/numeric'
-require_relative 'converters/range'
+require_relative "configuration"
+require_relative "converter"
+require_relative "converters/array"
+require_relative "converters/boolean"
+require_relative "converters/date_time"
+require_relative "converters/hash"
+require_relative "converters/numeric"
+require_relative "converters/range"
 
 module Necromancer
   # Represents the context used to configure various converters
@@ -14,7 +15,7 @@ module Necromancer
   #
   # @api public
   class Conversions
-    DELIMITER = '->'.freeze
+    DELIMITER = "->"
 
     # Creates a new conversions map
     #
@@ -34,6 +35,7 @@ module Necromancer
       ArrayConverters.load(self)
       BooleanConverters.load(self)
       DateTimeConverters.load(self)
+      HashConverters.load(self)
       NumericConverters.load(self)
       RangeConverters.load(self)
     end
@@ -76,6 +78,7 @@ module Necromancer
       key = generate_key(converter)
       converter = add_config(converter, @configuration)
       return false if converter_map.key?(key)
+
       converter_map[key] = converter
       true
     end
@@ -101,8 +104,8 @@ module Necromancer
     # @api private
     def generate_key(converter)
       parts = []
-      parts << (converter.source ? converter.source.to_s : 'none')
-      parts << (converter.target ? converter.target.to_s : 'none')
+      parts << (converter.source ? converter.source.to_s : "none")
+      parts << (converter.target ? converter.target.to_s : "none")
       parts.join(DELIMITER)
     end
 
diff --git a/lib/necromancer/converter.rb b/lib/necromancer/converter.rb
index 238b5f7..ff771ea 100644
--- a/lib/necromancer/converter.rb
+++ b/lib/necromancer/converter.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_relative 'configuration'
+require_relative "configuration"
 
 module Necromancer
   # Abstract converter used internally as a base for other converters
@@ -34,9 +34,9 @@ module Necromancer
     # @api private
     def self.create(&block)
       Class.new(self) do
-        define_method(:initialize) { |*a| block.call(self, *a) }
+        define_method(:initialize) { |*a| block.(self, *a) }
 
-        define_method(:call) { |value| convert.call(value) }
+        define_method(:call) { |value| convert.(value) }
       end.new
     end
 
@@ -48,7 +48,7 @@ module Necromancer
     # @api private
     def raise_conversion_type(value)
       raise ConversionTypeError, "'#{value}' could not be converted " \
-                                "from `#{source}` into `#{target}` "
+                                 "from `#{source}` into `#{target}`"
     end
 
     attr_accessor :source
@@ -57,7 +57,7 @@ module Necromancer
 
     attr_accessor :convert
 
-    protected
+   # protected
 
     attr_reader :config
   end # Converter
diff --git a/lib/necromancer/converters/array.rb b/lib/necromancer/converters/array.rb
index 79a7795..c84cd9a 100644
--- a/lib/necromancer/converters/array.rb
+++ b/lib/necromancer/converters/array.rb
@@ -1,69 +1,150 @@
 # frozen_string_literal: true
 
-require 'set'
+require "set"
 
-require_relative '../converter'
+require_relative "../converter"
+require_relative "boolean"
+require_relative "numeric"
 
 module Necromancer
   # Container for Array converter classes
   module ArrayConverters
+    ARRAY_MATCHER = /^(.+?(\s*(?<sep>(,|-))\s*))+/x.freeze
+
     # An object that converts a String to an Array
     class StringToArrayConverter < Converter
       # Convert string value to array
       #
       # @example
-      #   converter.call('a, b, c')  # => ['a', 'b', 'c']
+      #   converter.call("a, b, c")  # => ["a", "b", "c"]
       #
       # @example
-      #   converter.call('1 - 2 - 3')  # => [1, 2, 3]
+      #   converter.call("1 - 2 - 3")  # => ["1", "2", "3"]
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
-        case value.to_s
-        when /^\s*?((\d+)(\s*(,|-)\s*)?)+\s*?$/
-          value.to_s.split($4).map(&:to_i)
-        when /^((\w)(\s*(,|-)\s*)?)+$/
-          value.to_s.split($4)
+      def call(value, strict: config.strict)
+        return [] if value.to_s.empty?
+
+        if match = value.to_s.match(ARRAY_MATCHER)
+          value.to_s.split(match[:sep])
         else
           strict ? raise_conversion_type(value) : Array(value)
         end
       end
     end
 
+    class StringToBooleanArrayConverter < Converter
+      # @example
+      #   converter.call("t,f,yes,no") # => [true, false, true, false]
+      #
+      # @param [Array] value
+      #   the array value to boolean
+      #
+      # @api public
+      def call(value, strict: config.strict)
+        array_converter = StringToArrayConverter.new(:string, :array)
+        array = array_converter.(value, strict: strict)
+        bool_converter = ArrayToBooleanArrayConverter.new(:array, :boolean)
+        bool_converter.(array, strict: strict)
+      end
+    end
+
+    class StringToIntegerArrayConverter < Converter
+      # @example
+      #   converter.call("1,2,3") # => [1, 2, 3]
+      #
+      # @api public
+      def call(string, strict: config.strict)
+        array_converter = StringToArrayConverter.new(:string, :array)
+        array = array_converter.(string, strict: strict)
+        int_converter = ArrayToIntegerArrayConverter.new(:array, :integers)
+        int_converter.(array, strict: strict)
+      end
+    end
+
+    class StringToFloatArrayConverter < Converter
+      # @example
+      #   converter.call("1,2,3") # => [1.0, 2.0, 3.0]
+      #
+      # @api public
+      def call(string, strict: config.strict)
+        array_converter = StringToArrayConverter.new(:string, :array)
+        array = array_converter.(string, strict: strict)
+        float_converter = ArrayToFloatArrayConverter.new(:array, :floats)
+        float_converter.(array, strict: strict)
+      end
+    end
+
+    class StringToNumericArrayConverter < Converter
+      # Convert string value to array with numeric values
+      #
+      # @example
+      #   converter.call("1,2.0,3") # => [1, 2.0, 3]
+      #
+      # @api public
+      def call(string, strict: config.strict)
+        array_converter = StringToArrayConverter.new(:string, :array)
+        array = array_converter.(string, strict: strict)
+        num_converter = ArrayToNumericArrayConverter.new(:array, :numeric)
+        num_converter.(array, strict: strict)
+      end
+    end
+
+    class ArrayToIntegerArrayConverter < Converter
+      # @example
+      #   converter.call(["1", "2", "3"]) # => [1, 2, 3]
+      #
+      # @api public
+      def call(array, strict: config.strict)
+        int_converter = NumericConverters::StringToIntegerConverter.new(:string,
+                                                                        :integer)
+        array.map { |val| int_converter.(val, strict: strict) }
+      end
+    end
+
+    class ArrayToFloatArrayConverter < Converter
+      # @example
+      #   converter.call(["1", "2", "3"]) # => [1.0, 2.0, 3.0]
+      #
+      # @api public
+      def call(array, strict: config.strict)
+        float_converter = NumericConverters::StringToFloatConverter.new(:string,
+                                                                        :float)
+        array.map { |val| float_converter.(val, strict: strict) }
+      end
+    end
+
     # An object that converts an array to an array with numeric values
-    class ArrayToNumericConverter < Converter
+    class ArrayToNumericArrayConverter < Converter
       # Convert an array to an array of numeric values
       #
       # @example
-      #   converter.call(['1', '2.3', '3.0])  # => [1, 2.3, 3.0]
+      #   converter.call(["1", "2.3", "3.0])  # => [1, 2.3, 3.0]
       #
       # @param [Array] value
       #   the value to convert
       #
       # @api public
-      def call(value, options = {})
-        numeric_converter = NumericConverters::StringToNumericConverter.new(:string, :numeric)
-        value.reduce([]) do |acc, el|
-          acc << numeric_converter.call(el, **options)
-        end
+      def call(value, strict: config.strict)
+        num_converter = NumericConverters::StringToNumericConverter.new(:string,
+                                                                        :numeric)
+        value.map { |val| num_converter.(val, strict: strict) }
       end
     end
 
     # An object that converts an array to an array with boolean values
-    class ArrayToBooleanConverter < Converter
+    class ArrayToBooleanArrayConverter < Converter
       # @example
-      #   converter.call(['t', 'f', 'yes', 'no']) # => [true, false, true, false]
+      #   converter.call(["t", "f", "yes", "no"]) # => [true, false, true, false]
       #
       # @param [Array] value
       #   the array value to boolean
       #
       # @api public
-      def call(value, options = {})
-        boolean_converter = BooleanConverters::StringToBooleanConverter.new(:string, :boolean)
-        value.reduce([]) do |acc, el|
-          acc << boolean_converter.call(el, options)
-        end
+      def call(value, strict: config.strict)
+        bool_converter = BooleanConverters::StringToBooleanConverter.new(:string,
+                                                                         :boolean)
+        value.map { |val| bool_converter.(val, strict: strict) }
       end
     end
 
@@ -75,13 +156,10 @@ module Necromancer
       #   converter.call({x: 1})   # => [[:x, 1]]
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
-        begin
-          Array(value)
-        rescue
-          strict ? raise_conversion_type(value) : value
-        end
+      def call(value, strict: config.strict)
+        Array(value)
+      rescue StandardError
+        strict ? raise_conversion_type(value) : value
       end
     end
 
@@ -96,23 +174,39 @@ module Necromancer
       #   the array to convert
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
-        begin
-          value.to_set
-        rescue
-          strict ? raise_conversion_type(value) : value
-        end
+      def call(value, strict: config.strict)
+        value.to_set
+      rescue StandardError
+        strict ? raise_conversion_type(value) : value
       end
     end
 
     def self.load(conversions)
-      conversions.register NullConverter.new(:array, :array)
-      conversions.register StringToArrayConverter.new(:string, :array)
-      conversions.register ArrayToNumericConverter.new(:array, :numeric)
-      conversions.register ArrayToBooleanConverter.new(:array, :boolean)
-      conversions.register ObjectToArrayConverter.new(:object, :array)
-      conversions.register ObjectToArrayConverter.new(:hash, :array)
+      [
+        NullConverter.new(:array, :array),
+
+        StringToArrayConverter.new(:string, :array),
+        StringToBooleanArrayConverter.new(:string, :bools),
+        StringToBooleanArrayConverter.new(:string, :booleans),
+        StringToIntegerArrayConverter.new(:string, :integers),
+        StringToIntegerArrayConverter.new(:string, :ints),
+        StringToFloatArrayConverter.new(:string, :floats),
+        StringToNumericArrayConverter.new(:string, :numerics),
+        StringToNumericArrayConverter.new(:string, :nums),
+
+        ArrayToNumericArrayConverter.new(:array, :numerics),
+        ArrayToNumericArrayConverter.new(:array, :nums),
+        ArrayToIntegerArrayConverter.new(:array, :integers),
+        ArrayToIntegerArrayConverter.new(:array, :ints),
+        ArrayToFloatArrayConverter.new(:array, :floats),
+        ArrayToBooleanArrayConverter.new(:array, :booleans),
+        ArrayToBooleanArrayConverter.new(:array, :bools),
+
+        ObjectToArrayConverter.new(:object, :array),
+        ObjectToArrayConverter.new(:hash, :array)
+      ].each do |converter|
+        conversions.register converter
+      end
     end
   end # ArrayConverters
 end # Necromancer
diff --git a/lib/necromancer/converters/boolean.rb b/lib/necromancer/converters/boolean.rb
index 8385221..cbf053b 100644
--- a/lib/necromancer/converters/boolean.rb
+++ b/lib/necromancer/converters/boolean.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
-require_relative '../converter'
-require_relative '../null_converter'
+require_relative "../converter"
+require_relative "../null_converter"
 
 module Necromancer
   # Container for Boolean converter classes
@@ -29,8 +29,7 @@ module Necromancer
       #    0, f, F, FALSE, false, False, n, N, NO,  no,  No, off, OFF
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         case value.to_s
         when TRUE_MATCHER then true
         when FALSE_MATCHER then false
@@ -50,13 +49,10 @@ module Necromancer
       #   converter.call(0)  # => false
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
-        begin
-          !value.zero?
-        rescue
-          strict ? raise_conversion_type(value) : value
-        end
+      def call(value, strict: config.strict)
+        !value.zero?
+      rescue StandardError
+        strict ? raise_conversion_type(value) : value
       end
     end
 
@@ -71,8 +67,7 @@ module Necromancer
       #   converter.call(false)  # => 0
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         if %w[TrueClass FalseClass].include?(value.class.name)
           value ? 1 : 0
         else
@@ -82,10 +77,14 @@ module Necromancer
     end
 
     def self.load(conversions)
-      conversions.register StringToBooleanConverter.new(:string, :boolean)
-      conversions.register IntegerToBooleanConverter.new(:integer, :boolean)
-      conversions.register BooleanToIntegerConverter.new(:boolean, :integer)
-      conversions.register NullConverter.new(:boolean, :boolean)
+      [
+        StringToBooleanConverter.new(:string, :boolean),
+        IntegerToBooleanConverter.new(:integer, :boolean),
+        BooleanToIntegerConverter.new(:boolean, :integer),
+        NullConverter.new(:boolean, :boolean)
+      ].each do |converter|
+        conversions.register converter
+      end
     end
   end # BooleanConverters
 end # Necromancer
diff --git a/lib/necromancer/converters/date_time.rb b/lib/necromancer/converters/date_time.rb
index cb3e7c1..0d9c497 100644
--- a/lib/necromancer/converters/date_time.rb
+++ b/lib/necromancer/converters/date_time.rb
@@ -1,10 +1,10 @@
 # frozen_string_literal: true
 
-require 'date'
-require 'time'
+require "date"
+require "time"
 
-require_relative '../converter'
-require_relative '../null_converter'
+require_relative "../converter"
+require_relative "../null_converter"
 
 module Necromancer
   # Container for Date converter classes
@@ -20,10 +20,9 @@ module Necromancer
       #   converter.call("12/11/2015")  # => "2015-11-12"
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         Date.parse(value)
-      rescue
+      rescue StandardError
         strict ? raise_conversion_type(value) : value
       end
     end
@@ -37,10 +36,9 @@ module Necromancer
       #  converer.call("1-1-2015 15:12:44")  # => "2015-01-01T15:12:44+00:00"
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         DateTime.parse(value)
-      rescue
+      rescue StandardError
         strict ? raise_conversion_type(value) : value
       end
     end
@@ -57,21 +55,24 @@ module Necromancer
       #   converter.call("12:35")            # => 2015-01-04 12:35:00 +0100
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         Time.parse(value)
-      rescue
+      rescue StandardError
         strict ? raise_conversion_type(value) : value
       end
     end
 
     def self.load(conversions)
-      conversions.register StringToDateConverter.new(:string, :date)
-      conversions.register NullConverter.new(:date, :date)
-      conversions.register StringToDateTimeConverter.new(:string, :datetime)
-      conversions.register NullConverter.new(:datetime, :datetime)
-      conversions.register StringToTimeConverter.new(:string, :time)
-      conversions.register NullConverter.new(:time, :time)
+      [
+        StringToDateConverter.new(:string, :date),
+        NullConverter.new(:date, :date),
+        StringToDateTimeConverter.new(:string, :datetime),
+        NullConverter.new(:datetime, :datetime),
+        StringToTimeConverter.new(:string, :time),
+        NullConverter.new(:time, :time)
+      ].each do |converter|
+        conversions.register converter
+      end
     end
   end # DateTimeConverters
 end # Necromancer
diff --git a/lib/necromancer/converters/hash.rb b/lib/necromancer/converters/hash.rb
new file mode 100644
index 0000000..91f580f
--- /dev/null
+++ b/lib/necromancer/converters/hash.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require_relative "../converter"
+require_relative "boolean"
+require_relative "numeric"
+
+module Necromancer
+  module HashConverters
+    # An object that converts a String to a Hash
+    class StringToHashConverter < Converter
+      DEFAULT_CONVERSION = ->(val, *_rest) { val }
+
+      # Convert string value to hash
+      #
+      # @example
+      #   converter.call("a:1 b:2 c:3")
+      #   # => {a: "1", b: "2", c: "3"}
+      #
+      # @example
+      #   converter.call("a=1 & b=3 & c=3")
+      #   # => {a: "1", b: "2", c: "3"}
+      #
+      # @api public
+      def call(value, strict: config.strict, value_converter: DEFAULT_CONVERSION)
+        values = value.split(/\s*[& ]\s*/)
+        values.each_with_object({}) do |pair, pairs|
+          key, value = pair.split(/[=:]/, 2)
+          value_converted = value_converter.(value, strict: strict)
+          if current = pairs[key.to_sym]
+            pairs[key.to_sym] = Array(current) << value_converted
+          else
+            pairs[key.to_sym] = value_converted
+          end
+          pairs
+        end
+      end
+    end
+
+    class StringToIntegerHashConverter < Converter
+      # Convert string value to hash with integer values
+      #
+      # @example
+      #   converter.call("a:1 b:2 c:3")
+      #   # => {a: 1, b: 2, c: 3}
+      #
+      # @api public
+      def call(value, strict: config.strict)
+        int_converter = NumericConverters::StringToIntegerConverter.new(:string,
+                                                                        :integer)
+        hash_converter = StringToHashConverter.new(:string, :hash)
+        hash_converter.(value, strict: strict, value_converter: int_converter)
+      end
+    end
+
+    class StringToFloatHashConverter < Converter
+      # Convert string value to hash with float values
+      #
+      # @example
+      #   converter.call("a:1 b:2 c:3")
+      #   # => {a: 1.0, b: 2.0, c: 3.0}
+      #
+      # @api public
+      def call(value, strict: config.strict)
+        float_converter = NumericConverters::StringToFloatConverter.new(:string,
+                                                                        :float)
+        hash_converter = StringToHashConverter.new(:string, :hash)
+        hash_converter.(value, strict: strict, value_converter: float_converter)
+      end
+    end
+
+    class StringToNumericHashConverter < Converter
+      # Convert string value to hash with numeric values
+      #
+      # @example
+      #   converter.call("a:1 b:2.0 c:3")
+      #   # => {a: 1, b: 2.0, c: 3}
+      #
+      # @api public
+      def call(value, strict: config.strict)
+        num_converter = NumericConverters::StringToNumericConverter.new(:string,
+                                                                        :numeric)
+        hash_converter = StringToHashConverter.new(:string, :hash)
+        hash_converter.(value, strict: strict, value_converter: num_converter)
+      end
+    end
+
+    class StringToBooleanHashConverter < Converter
+      # Convert string value to hash with boolean values
+      #
+      # @example
+      #   converter.call("a:yes b:no c:t")
+      #   # => {a: true, b: false, c: true}
+      #
+      # @api public
+      def call(value, strict: config.strict)
+        bool_converter = BooleanConverters::StringToBooleanConverter.new(:string,
+                                                                         :boolean)
+        hash_converter = StringToHashConverter.new(:string, :hash)
+        hash_converter.(value, strict: strict, value_converter: bool_converter)
+      end
+    end
+
+    def self.load(conversions)
+      [
+        NullConverter.new(:hash, :hash),
+        StringToHashConverter.new(:string, :hash),
+        StringToIntegerHashConverter.new(:string, :int_hash),
+        StringToIntegerHashConverter.new(:string, :integer_hash),
+        StringToFloatHashConverter.new(:string, :float_hash),
+        StringToNumericHashConverter.new(:string, :num_hash),
+        StringToNumericHashConverter.new(:string, :numeric_hash),
+        StringToBooleanHashConverter.new(:string, :boolean_hash),
+        StringToBooleanHashConverter.new(:string, :bool_hash)
+      ].each do |converter|
+        conversions.register(converter)
+      end
+    end
+  end # HashConverters
+end # Necromancer
diff --git a/lib/necromancer/converters/numeric.rb b/lib/necromancer/converters/numeric.rb
index d45f8c0..a8b3343 100644
--- a/lib/necromancer/converters/numeric.rb
+++ b/lib/necromancer/converters/numeric.rb
@@ -1,27 +1,26 @@
 # frozen_string_literal: true
 
-require_relative '../converter'
-require_relative '../null_converter'
+require_relative "../converter"
+require_relative "../null_converter"
 
 module Necromancer
   # Container for Numeric converter classes
   module NumericConverters
-    INTEGER_MATCHER = /^[-+]?(\d+)$/.freeze
+    INTEGER_MATCHER = /^\s*[-+]?\s*(\d[\d\s]*)?$/.freeze
 
-    FLOAT_MATCHER = /^[-+]?(\d*)(\.\d+)?([eE]?[-+]?\d+)?$/.freeze
+    FLOAT_MATCHER = /^\s*[-+]?([\d\s]*)(\.[\d\s]+)?([eE]?[-+]?[\d\s]+)?$/.freeze
 
     # An object that converts a String to an Integer
     class StringToIntegerConverter < Converter
       # Convert string value to integer
       #
       # @example
-      #   converter.call('1abc')  # => 1
+      #   converter.call("1abc")  # => 1
       #
       # @api public
-      def call(value, **options)
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         Integer(value)
-      rescue
+      rescue StandardError
         strict ? raise_conversion_type(value) : value.to_i
       end
     end
@@ -31,7 +30,7 @@ module Necromancer
       # Convert integer value to string
       #
       # @example
-      #   converter.call(1)  # => '1'
+      #   converter.call(1)  # => "1"
       #
       # @api public
       def call(value, **_)
@@ -44,13 +43,12 @@ module Necromancer
       # Convert string to float value
       #
       # @example
-      #   converter.call('1.2') # => 1.2
+      #   converter.call("1.2") # => 1.2
       #
       # @api public
-      def call(value, **options)
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         Float(value)
-      rescue
+      rescue StandardError
         strict ? raise_conversion_type(value) : value.to_f
       end
     end
@@ -60,19 +58,18 @@ module Necromancer
       # Convert string to numeric value
       #
       # @example
-      #   converter.call('1.0')  # => 1.0
+      #   converter.call("1.0")  # => 1.0
       #
       # @example
-      #   converter.call('1')   # => 1
+      #   converter.call("1")   # => 1
       #
       # @api public
-      def call(value, **options)
-        strict = options.fetch(:strict, config.strict)
+      def call(value, strict: config.strict)
         case value
         when INTEGER_MATCHER
-          StringToIntegerConverter.new(:string, :integer).call(value, **options)
+          StringToIntegerConverter.new(:string, :integer).(value, strict: strict)
         when FLOAT_MATCHER
-          StringToFloatConverter.new(:string, :float).call(value, **options)
+          StringToFloatConverter.new(:string, :float).(value, strict: strict)
         else
           strict ? raise_conversion_type(value) : value
         end
@@ -80,12 +77,16 @@ module Necromancer
     end
 
     def self.load(conversions)
-      conversions.register StringToIntegerConverter.new(:string, :integer)
-      conversions.register IntegerToStringConverter.new(:integer, :string)
-      conversions.register NullConverter.new(:integer, :integer)
-      conversions.register StringToFloatConverter.new(:string, :float)
-      conversions.register NullConverter.new(:float, :float)
-      conversions.register StringToNumericConverter.new(:string, :numeric)
+      [
+        StringToIntegerConverter.new(:string, :integer),
+        IntegerToStringConverter.new(:integer, :string),
+        NullConverter.new(:integer, :integer),
+        StringToFloatConverter.new(:string, :float),
+        NullConverter.new(:float, :float),
+        StringToNumericConverter.new(:string, :numeric)
+      ].each do |converter|
+        conversions.register converter
+      end
     end
   end # Conversion
 end # Necromancer
diff --git a/lib/necromancer/converters/range.rb b/lib/necromancer/converters/range.rb
index 4018c05..00e412c 100644
--- a/lib/necromancer/converters/range.rb
+++ b/lib/necromancer/converters/range.rb
@@ -1,16 +1,22 @@
 # frozen_string_literal: true
 
-require_relative '../converter'
-require_relative '../null_converter'
+require_relative "../converter"
+require_relative "../null_converter"
 
 module Necromancer
   # Container for Range converter classes
   module RangeConverters
-    SINGLE_DIGIT_MATCHER = /^(\-?\d+)$/.freeze
+    SINGLE_DIGIT_MATCHER = /^(?<digit>-?\d+(\.\d+)?)$/.freeze
 
-    DIGIT_MATCHER = /^(-?\d+?)(\.{2}\.?|-|,)(-?\d+)$/.freeze
+    DIGIT_MATCHER = /^(?<open>-?\d+(\.\d+)?)
+                      \s*(?<sep>(\.\s*){2,3}|-|,)\s*
+                      (?<close>-?\d+(\.\d+)?)$
+                    /x.freeze
 
-    LETTER_MATCHER = /^(\w)(\.{2}\.?|-|,)(\w)$/.freeze
+    LETTER_MATCHER = /^(?<open>\w)
+                      \s*(?<sep>(\.\s*){2,3}|-|,)\s*
+                      (?<close>\w)$
+                      /x.freeze
 
     # An object that converts a String to a Range
     class StringToRangeConverter < Converter
@@ -19,30 +25,47 @@ module Necromancer
       # @param [Object] value
       #
       # @example
-      #   converter.call('0,9')  # => (0..9)
+      #   converter.call("0,9")  # => (0..9)
       #
       # @example
-      #   converter.call('0-9')  # => (0..9)
+      #   converter.call("0-9")  # => (0..9)
       #
       # @api public
-      def call(value, options = {})
-        strict = options.fetch(:strict, config.strict)
-        case value
-        when SINGLE_DIGIT_MATCHER
-          ::Range.new($1.to_i, $1.to_i)
-        when DIGIT_MATCHER
-          ::Range.new($1.to_i, $3.to_i, $2 == '...')
-        when LETTER_MATCHER
-          ::Range.new($1.to_s, $3.to_s, $2 == '...')
+      def call(value, strict: config.strict)
+        if match = value.match(SINGLE_DIGIT_MATCHER)
+          digit = cast_to_num(match[:digit])
+          ::Range.new(digit, digit)
+        elsif match = value.match(DIGIT_MATCHER)
+          open = cast_to_num(match[:open])
+          close = cast_to_num(match[:close])
+          ::Range.new(open, close, match[:sep].gsub(/\s*/, "") == "...")
+        elsif match = value.match(LETTER_MATCHER)
+          ::Range.new(match[:open], match[:close],
+                      match[:sep].gsub(/\s*/, "") == "...")
         else
           strict ? raise_conversion_type(value) : value
         end
       end
+
+      # Convert range end to numeric value
+      #
+      # @api private
+      def cast_to_num(str)
+        Integer(str)
+      rescue ArgumentError
+        Float(str)
+      rescue ArgumentError
+        nil
+      end
     end
 
     def self.load(conversions)
-      conversions.register StringToRangeConverter.new(:string, :range)
-      conversions.register NullConverter.new(:range, :range)
+      [
+        StringToRangeConverter.new(:string, :range),
+        NullConverter.new(:range, :range)
+      ].each do |converter|
+        conversions.register converter
+      end
     end
   end # RangeConverters
 end # Necromancer
diff --git a/lib/necromancer/null_converter.rb b/lib/necromancer/null_converter.rb
index f605025..4a2856a 100644
--- a/lib/necromancer/null_converter.rb
+++ b/lib/necromancer/null_converter.rb
@@ -1,11 +1,11 @@
 # frozen_string_literal: true
 
-require_relative 'converter'
+require_relative "converter"
 
 module Necromancer
   # A pass through converter
   class NullConverter < Converter
-    def call(value, **options)
+    def call(value, strict: config.strict)
       value
     end
   end # NullConverter
diff --git a/lib/necromancer/version.rb b/lib/necromancer/version.rb
index d0edae5..9081a44 100644
--- a/lib/necromancer/version.rb
+++ b/lib/necromancer/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module Necromancer
-  VERSION = "0.5.1"
+  VERSION = "0.7.0"
 end # Necromancer
diff --git a/necromancer.gemspec b/necromancer.gemspec
index b954049..7ac136f 100644
--- a/necromancer.gemspec
+++ b/necromancer.gemspec
@@ -1,17 +1,16 @@
-lib = File.expand_path('../lib', __FILE__)
-$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
-require 'necromancer/version'
+# frozen_string_literal: true
+
+require_relative "lib/necromancer/version"
 
 Gem::Specification.new do |spec|
-  spec.name          = 'necromancer'
+  spec.name          = "necromancer"
   spec.version       = Necromancer::VERSION
-  spec.authors       = ['Piotr Murach']
-  spec.email         = ['me@piotrmurach.com']
+  spec.authors       = ["Piotr Murach"]
+  spec.email         = ["piotr@piotrmurach.com"]
   spec.summary       = %q{Conversion from one object type to another with a bit of black magic.}
   spec.description   = %q{Conversion from one object type to another with a bit of black magic.}
-  spec.homepage      = 'https://github.com/piotrmurach/necromancer'
-  spec.license       = 'MIT'
-
+  spec.homepage      = "https://github.com/piotrmurach/necromancer"
+  spec.license       = "MIT"
   if spec.respond_to?(:metadata=)
     spec.metadata["allowed_push_host"] = "https://rubygems.org"
     spec.metadata["changelog_uri"] = "https://github.com/piotrmurach/necromancer/blob/master/CHANGELOG.md"
@@ -19,16 +18,11 @@ Gem::Specification.new do |spec|
     spec.metadata["homepage_uri"] = spec.homepage
     spec.metadata["source_code_uri"] = "https://github.com/piotrmurach/necromancer"
   end
-
-  spec.files         = Dir['{lib,spec}/**/*.rb']
-  spec.files        += Dir['tasks/*', 'necromancer.gemspec']
-  spec.files        += Dir['README.md', 'CHANGELOG.md', 'LICENSE.txt', 'Rakefile']
-  spec.test_files    = spec.files.grep(%r{^(spec)/})
+  spec.files         = Dir["lib/**/*"]
+  spec.extra_rdoc_files = ["README.md", "CHANGELOG.md", "LICENSE.txt"]
   spec.require_paths = ["lib"]
+  spec.required_ruby_version = ">= 2.0.0"
 
-  spec.required_ruby_version = '>= 2.0.0'
-
-  spec.add_development_dependency 'bundler', '>= 1.6'
-  spec.add_development_dependency 'rake'
-  spec.add_development_dependency 'rspec', '~> 3.0'
+  spec.add_development_dependency "rake"
+  spec.add_development_dependency "rspec", ">= 3.0"
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9479672..1161d98 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,8 +1,8 @@
 # frozen_string_literal: true
 
-if ENV['COVERAGE'] || ENV['TRAVIS']
-  require 'simplecov'
-  require 'coveralls'
+if ENV["COVERAGE"] == "true"
+  require "simplecov"
+  require "coveralls"
 
   SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
     SimpleCov::Formatter::HTMLFormatter,
@@ -10,16 +10,17 @@ if ENV['COVERAGE'] || ENV['TRAVIS']
   ])
 
   SimpleCov.start do
-    command_name 'spec'
-    add_filter 'spec'
+    command_name "spec"
+    add_filter "spec"
   end
 end
 
-require 'necromancer'
+require "necromancer"
 
 RSpec.configure do |config|
   config.expect_with :rspec do |expectations|
     expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+    expectations.max_formatted_output_length = nil
   end
 
   config.mock_with :rspec do |mocks|
@@ -34,7 +35,7 @@ RSpec.configure do |config|
   config.warnings = true
 
   if config.files_to_run.one?
-    config.default_formatter = 'doc'
+    config.default_formatter = "doc"
   end
 
   config.profile_examples = 2
@@ -42,12 +43,4 @@ RSpec.configure do |config|
   config.order = :random
 
   Kernel.srand config.seed
-
-  config.before :each do
-    [:UpcaseConverter, :Custom].each do |class_name|
-      if Object.const_defined?(class_name)
-        Object.send(:remove_const, class_name)
-      end
-    end
-  end
 end
diff --git a/spec/unit/can_spec.rb b/spec/unit/can_spec.rb
index d141f9f..31f7116 100644
--- a/spec/unit/can_spec.rb
+++ b/spec/unit/can_spec.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer, 'can?' do
+RSpec.describe Necromancer, "can?" do
   it "checks if conversion is possible" do
     converter = described_class.new
     expect(converter.can?(:string, :integer)).to eq(true)
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
index 9973b47..ef33160 100644
--- a/spec/unit/config_spec.rb
+++ b/spec/unit/config_spec.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer, 'config' do
+RSpec.describe Necromancer, "config" do
   it "configures global settings per instance" do
     converter = described_class.new
 
diff --git a/spec/unit/configuration/new_spec.rb b/spec/unit/configuration/new_spec.rb
index c84c5a9..2706710 100644
--- a/spec/unit/configuration/new_spec.rb
+++ b/spec/unit/configuration/new_spec.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer::Configuration, '.new' do
-
+RSpec.describe Necromancer::Configuration, ".new" do
   subject(:config) { described_class.new }
 
   it { is_expected.to respond_to(:strict=) }
diff --git a/spec/unit/conversions/fetch_spec.rb b/spec/unit/conversions/fetch_spec.rb
index 4431d53..96307cf 100644
--- a/spec/unit/conversions/fetch_spec.rb
+++ b/spec/unit/conversions/fetch_spec.rb
@@ -1,16 +1,16 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer::Conversions, '#fetch' do
+RSpec.describe Necromancer::Conversions, "#fetch" do
   it "retrieves conversion given source & target" do
     converter = double(:converter)
-    conversions = described_class.new nil, {'string->array' => converter}
-    expect(conversions['string', 'array']).to eq(converter)
+    conversions = described_class.new nil, { "string->array" => converter }
+    expect(conversions["string", "array"]).to eq(converter)
   end
 
   it "fails to find conversion" do
     conversions = described_class.new
     expect {
-      conversions['string', 'array']
+      conversions["string", "array"]
     }.to raise_error(Necromancer::NoTypeConversionAvailableError)
   end
 end
diff --git a/spec/unit/conversions/register_spec.rb b/spec/unit/conversions/register_spec.rb
index a7a4466..5ccfb48 100644
--- a/spec/unit/conversions/register_spec.rb
+++ b/spec/unit/conversions/register_spec.rb
@@ -1,23 +1,23 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer::Conversions, '.register' do
+RSpec.describe Necromancer::Conversions, ".register" do
   it "allows to register converter" do
     context = described_class.new
-    converter = double(:converter, {source: :string, target: :numeric})
+    converter = double(:converter, { source: :string, target: :numeric })
     expect(context.register(converter)).to eq(true)
     expect(context[:string, :numeric]).to eq(converter)
   end
 
   it "allows to register converter with no source" do
     context = described_class.new
-    converter = double(:converter, {source: nil, target: :numeric})
+    converter = double(:converter, { source: nil, target: :numeric })
     expect(context.register(converter)).to eq(true)
     expect(context[:none, :numeric]).to eq(converter)
   end
 
   it "allows to register converter with no target" do
     context = described_class.new
-    converter = double(:converter, {source: :string, target: nil})
+    converter = double(:converter, { source: :string, target: nil })
     expect(context.register(converter)).to eq(true)
     expect(context[:string, :none]).to eq(converter)
   end
@@ -26,33 +26,33 @@ RSpec.describe Necromancer::Conversions, '.register' do
     conversions = described_class.new
 
     conversions.register do |c|
-      c.source= :string
-      c.target= :upcase
-      c.convert = proc { |value| value.to_s.upcase }
+      c.source = :string
+      c.target = :upcase
+      c.convert = ->(value) { value.to_s.upcase }
     end
-    expect(conversions[:string, :upcase].call('magic')).to eq('MAGIC')
+    expect(conversions[:string, :upcase].("magic")).to eq("MAGIC")
   end
 
   it "allows to register anonymous converter with class names" do
     conversions = described_class.new
 
     conversions.register do |c|
-      c.source= String
-      c.target= Array
-      c.convert = proc { |value| Array(value) }
+      c.source = String
+      c.target = Array
+      c.convert = ->(value) { Array(value) }
     end
-    expect(conversions[String, Array].call('magic')).to eq(['magic'])
+    expect(conversions[String, Array].("magic")).to eq(["magic"])
   end
 
   it "allows to register custom converter" do
     conversions = described_class.new
-    UpcaseConverter = Struct.new(:source, :target) do
+    stub_const("UpcaseConverter", Struct.new(:source, :target) do
       def call(value)
         value.to_s.upcase
       end
-    end
+    end)
     upcase_converter = UpcaseConverter.new(:string, :upcase)
     expect(conversions.register(upcase_converter)).to be(true)
-    expect(conversions[:string, :upcase].call('magic')).to eq('MAGIC')
+    expect(conversions[:string, :upcase].("magic")).to eq("MAGIC")
   end
 end
diff --git a/spec/unit/conversions/to_hash_spec.rb b/spec/unit/conversions/to_hash_spec.rb
index 89808e7..8e8ec4e 100644
--- a/spec/unit/conversions/to_hash_spec.rb
+++ b/spec/unit/conversions/to_hash_spec.rb
@@ -1,37 +1,58 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer::Conversions, '#to_hash' do
-  it 'exports default conversions to hash' do
+RSpec.describe Necromancer::Conversions, "#to_hash" do
+  it "exports default conversions to hash" do
     conversions = Necromancer::Conversions.new
     expect(conversions.to_hash).to eq({})
 
     conversions.load
 
     expect(conversions.to_hash.keys.sort).to eq([
-      'array->array',
-      'array->boolean',
-      'array->numeric',
-      'boolean->boolean',
-      'boolean->integer',
-      'date->date',
-      'datetime->datetime',
-      'float->float',
-      'hash->array',
-      'integer->boolean',
-      'integer->integer',
-      'integer->string',
-      'object->array',
-      'range->range',
-      'string->array',
-      'string->boolean',
-      'string->date',
-      'string->datetime',
-      'string->float',
-      'string->integer',
-      'string->numeric',
-      'string->range',
-      'string->time',
-      'time->time'
+      "array->array",
+      "array->booleans",
+      "array->bools",
+      "array->floats",
+      "array->integers",
+      "array->ints",
+      "array->numerics",
+      "array->nums",
+      "boolean->boolean",
+      "boolean->integer",
+      "date->date",
+      "datetime->datetime",
+      "float->float",
+      "hash->array",
+      "hash->hash",
+      "integer->boolean",
+      "integer->integer",
+      "integer->string",
+      "object->array",
+      "range->range",
+      "string->array",
+      "string->bool_hash",
+      "string->boolean",
+      "string->boolean_hash",
+      "string->booleans",
+      "string->bools",
+      "string->date",
+      "string->datetime",
+      "string->float",
+      "string->float_hash",
+      "string->floats",
+      "string->hash",
+      "string->int_hash",
+      "string->integer",
+      "string->integer_hash",
+      "string->integers",
+      "string->ints",
+      "string->num_hash",
+      "string->numeric",
+      "string->numeric_hash",
+      "string->numerics",
+      "string->nums",
+      "string->range",
+      "string->time",
+      "time->time"
     ])
   end
 end
diff --git a/spec/unit/convert_spec.rb b/spec/unit/convert_spec.rb
index f26bf6c..2a402f1 100644
--- a/spec/unit/convert_spec.rb
+++ b/spec/unit/convert_spec.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer, '.convert' do
-
+RSpec.describe Necromancer, ".convert" do
   subject(:converter) { described_class.new }
 
   it "indicates inability to perform the requested conversion" do
@@ -12,56 +11,112 @@ RSpec.describe Necromancer, '.convert' do
   end
 
   it "allows for module level convert call" do
-    expect(Necromancer.convert('1,2,3').to(:array)).to eq([1,2,3])
+    expect(Necromancer.convert("1,2,3").to(:array)).to eq(%w[1 2 3])
   end
 
   it "allows replacing #to with #>> call" do
-    expect(converter.convert('1,2,3') >> :array).to eq([1,2,3])
+    expect(converter.convert("1,2,3") >> :integers).to eq([1, 2, 3])
   end
 
   it "allows to specify object as conversion target" do
-    expect(converter.convert('1,2,3') >> []).to eq([1,2,3])
+    expect(converter.convert("1,2,3") >> []).to eq(%w[1 2 3])
   end
 
   it "allows to specify class as conversion target" do
-    expect(converter.convert('1,2,3') >> Array).to eq([1,2,3])
+    expect(converter.convert("1,2,3") >> Array).to eq(%w[1 2 3])
   end
 
-  context 'when array' do
+  context "when array" do
     it "converts string to array" do
-      expect(converter.convert("1,2,3").to(:array)).to eq([1,2,3])
+      expect(converter.convert("1,2,3").to(:array)).to eq(%w[1 2 3])
+    end
+
+    it "converts string to array of booleans" do
+      expect(converter.convert("t,f,t").to(:booleans)).to eq([true, false, true])
+      expect(converter.convert("t,f,t").to(:bools)).to eq([true, false, true])
     end
 
-    it "converts array to numeric " do
-      expect(converter.convert(['1','2.3','3.0']).to(:numeric)).to eq([1,2.3,3.0])
+    it "converts string to array of integers" do
+      expect(converter.convert("1,2,3").to(:integers)).to eq([1, 2, 3])
+      expect(converter.convert("1,2,3").to(:ints)).to eq([1, 2, 3])
     end
 
-    it "converts array to boolean" do
-      expect(converter.convert(['t', 'no']).to(:boolean)).to eq([true, false])
+    it "converts string to array of numerics" do
+      expect(converter.convert("1,2.0,3").to(:numerics)).to eq([1, 2.0, 3])
+      expect(converter.convert("1,2.0,3").to(:nums)).to eq([1, 2.0, 3])
+    end
+
+    it "converts array to numerics " do
+      conversion = converter.convert(["1", "2.3", "3.0"]).to(:numerics)
+      expect(conversion).to eq([1, 2.3, 3.0])
+      conversion = converter.convert(["1", "2.3", "3.0"]).to(:nums)
+      expect(conversion).to eq([1, 2.3, 3.0])
+    end
+
+    it "converts array to array of booleans" do
+      expect(converter.convert(%w[t no]).to(:booleans)).to eq([true, false])
+      expect(converter.convert(%w[t no]).to(:bools)).to eq([true, false])
     end
 
     it "converts object to array" do
-      expect(converter.convert({x: 1}).to(:array)).to eq([[:x, 1]])
+      expect(converter.convert({ x: 1 }).to(:array)).to eq([[:x, 1]])
     end
 
     it "fails to convert in strict mode" do
       expect {
-        converter.convert(['1', '2.3', false]).to(:numeric, strict: true)
+        converter.convert(["1", "2.3", false]).to(:numerics, strict: true)
       }.to raise_error(Necromancer::ConversionTypeError)
     end
   end
 
-  context 'when numeric' do
+  context "when hash" do
+    it "converts hash to hash" do
+      conversion = converter.convert({ a: 1, b: 2, c: 3 }).to(:hash)
+      expect(conversion).to eq({ a: 1, b: 2, c: 3 })
+    end
+
+    it "converts string to hash" do
+      conversion = converter.convert("a:1 b:2 c:3").to(:hash)
+      expect(conversion).to eq({ a: "1", b: "2", c: "3" })
+    end
+
+    it "converts string to integer hash" do
+      conversion = converter.convert("a:1 b:2 c:3").to(:int_hash)
+      expect(conversion).to eq({ a: 1, b: 2, c: 3 })
+    end
+
+    it "converts string to float hash" do
+      conversion = converter.convert("a:1 b:2 c:3").to(:float_hash)
+      expect(conversion).to eq({ a: 1.0, b: 2.0, c: 3.0 })
+    end
+
+    it "converts string to numeric hash" do
+      conversion = converter.convert("a:1 b:2.0 c:3").to(:numeric_hash)
+      expect(conversion).to eq({ a: 1, b: 2.0, c: 3 })
+    end
+
+    it "converts string to boolean hash" do
+      conversion = converter.convert("a:t b:f c:t").to(:boolean_hash)
+      expect(conversion).to eq({ a: true, b: false, c: true })
+    end
+
+    it "converts string to bool hash" do
+      conversion = converter.convert("a:t b:f c:t").to(:bool_hash)
+      expect(conversion).to eq({ a: true, b: false, c: true })
+    end
+  end
+
+  context "when numeric" do
     it "converts string to integer" do
-      expect(converter.convert('1').to(:integer)).to eq(1)
+      expect(converter.convert("1").to(:integer)).to eq(1)
     end
 
     it "allows for block for conversion method" do
-      expect(converter.convert { '1' }.to(:integer)).to eq(1)
+      expect(converter.convert { "1" }.to(:integer)).to eq(1)
     end
 
     it "convers integer to string" do
-      expect(converter.convert(1).to(:string)).to eq('1')
+      expect(converter.convert(1).to(:string)).to eq("1")
     end
 
     it "allows for null type conversion" do
@@ -70,30 +125,30 @@ RSpec.describe Necromancer, '.convert' do
 
     it "raises error when in strict mode" do
       expect {
-        converter.convert('1a').to(:integer, strict: true)
+        converter.convert("1a").to(:integer, strict: true)
       }.to raise_error(Necromancer::ConversionTypeError)
     end
 
     it "doesn't raise error when in non-strict mode" do
-      expect(converter.convert('1').to(:integer, strict: false)).to eql(1)
+      expect(converter.convert("1").to(:integer, strict: false)).to eql(1)
     end
 
     it "converts string to float" do
-      expect(converter.convert('1.0').to(:float)).to eql(1.0)
+      expect(converter.convert("1.0").to(:float)).to eql(1.0)
     end
 
     it "converts string to numeric" do
-      expect(converter.convert('1.0').to(:numeric)).to eql(1.0)
+      expect(converter.convert("1.0").to(:numeric)).to eql(1.0)
     end
   end
 
-  context 'when boolean' do
+  context "when boolean" do
     it "converts boolean to boolean" do
       expect(converter.convert(true).to(:boolean)).to eq(true)
     end
 
     it "converts string to boolean" do
-      expect(converter.convert('yes').to(:boolean)).to eq(true)
+      expect(converter.convert("yes").to(:boolean)).to eq(true)
     end
 
     it "converts integer to boolean" do
@@ -105,26 +160,26 @@ RSpec.describe Necromancer, '.convert' do
     end
   end
 
-  context 'when range' do
+  context "when range" do
     it "converts string to range" do
-      expect(converter.convert('1-10').to(:range)).to eq(1..10)
+      expect(converter.convert("1-10").to(:range)).to eq(1..10)
     end
   end
 
-  context 'when datetime' do
+  context "when datetime" do
     it "converts string to date" do
-      expect(converter.convert('2014-12-07').to(:date)).
-        to eq(Date.parse('2014-12-07'))
+      expect(converter.convert("2014-12-07").to(:date))
+        .to eq(Date.parse("2014-12-07"))
     end
 
     it "converts string to datetime" do
-      expect(converter.convert('2014-12-07 17:35:44').to(:datetime)).
-        to eq(DateTime.parse('2014-12-07 17:35:44'))
+      expect(converter.convert("2014-12-07 17:35:44").to(:datetime))
+        .to eq(DateTime.parse("2014-12-07 17:35:44"))
     end
 
     it "converts string to time" do
-      expect(converter.convert('12:30').to(:time)).
-        to eq(Time.parse('12:30'))
+      expect(converter.convert("12:30").to(:time))
+        .to eq(Time.parse("12:30"))
     end
   end
 end
diff --git a/spec/unit/converters/array/array_to_boolean_spec.rb b/spec/unit/converters/array/array_to_boolean_spec.rb
deleted file mode 100644
index 30d8f5f..0000000
--- a/spec/unit/converters/array/array_to_boolean_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::ArrayConverters::ArrayToBooleanConverter, '.call' do
-
-  subject(:converter) { described_class.new(:array, :boolean) }
-
-  it "converts `['t', 'f', 'yes', 'no']` to boolean array" do
-    expect(converter.call(['t', 'f', 'yes', 'no'])).to eq([true, false, true, false])
-  end
-
-  it "fails to convert `['t', 'no', 5]` to boolean array in strict mode" do
-    expect {
-      converter.call(['t', 'no', 5], strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "converts `['t', 'no', 5]` to boolean array in non-strict mode" do
-    expect(converter.call(['t', 'no', 5], strict: false)).to eql([true, false, 5])
-  end
-end
diff --git a/spec/unit/converters/array/array_to_numeric_spec.rb b/spec/unit/converters/array/array_to_numeric_spec.rb
deleted file mode 100644
index 3ba1fab..0000000
--- a/spec/unit/converters/array/array_to_numeric_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::ArrayConverters::ArrayToNumericConverter, '.call' do
-
-  subject(:converter) { described_class.new(:array, :numeric) }
-
-  it "converts `['1','2.3','3.0']` to numeric array" do
-    expect(converter.call(['1', '2.3', '3.0'])).to eq([1, 2.3, 3.0])
-  end
-
-  it "fails to convert `['1','2.3',false]` to numeric array in strict mode" do
-    expect {
-      converter.call(['1', '2.3', false], strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "converts `['1','2.3',false]` to numeric array in non-strict mode" do
-    expect(converter.call(['1', '2.3', false], strict: false)).to eq([1, 2.3, false])
-  end
-end
diff --git a/spec/unit/converters/array/array_to_set_spec.rb b/spec/unit/converters/array/array_to_set_spec.rb
deleted file mode 100644
index 7189e39..0000000
--- a/spec/unit/converters/array/array_to_set_spec.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::ArrayConverters::ArrayToSetConverter, '.call' do
-
-  subject(:converter) { described_class.new(:array, :set) }
-
-  it "converts `[:x,:y,:x,1,2,1]` to set" do
-    expect(converter.call([:x,:y,:x,1,2,1])).to eql(Set[:x,:y,1,2])
-  end
-
-  it "fails to convert `1` to set" do
-    expect {
-      converter.call(1, strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-end
diff --git a/spec/unit/converters/array/object_to_array_spec.rb b/spec/unit/converters/array/object_to_array_spec.rb
deleted file mode 100644
index 3cf3aea..0000000
--- a/spec/unit/converters/array/object_to_array_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::ArrayConverters::ObjectToArrayConverter, '.call' do
-  subject(:converter) { described_class.new(:object, :array) }
-
-  it "converts nil to array" do
-    expect(converter.call(nil)).to eq([])
-  end
-
-  it "converts custom object to array" do
-    Custom = Class.new do
-      def to_ary
-        [:x, :y]
-      end
-    end
-    custom = Custom.new
-    expect(converter.call(custom)).to eq([:x, :y])
-  end
-end
diff --git a/spec/unit/converters/array/string_to_array_spec.rb b/spec/unit/converters/array/string_to_array_spec.rb
deleted file mode 100644
index 9e2febb..0000000
--- a/spec/unit/converters/array/string_to_array_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::ArrayConverters::StringToArrayConverter, '.call' do
-  subject(:converter) { described_class.new(:string, :array) }
-
-  it "converts empty string to array" do
-    expect(converter.call('', strict: false)).to eq([''])
-  end
-
-  it "fails to convert empty string to array in strict mode" do
-    expect {
-      converter.call('', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "converts `1,2,3` to array" do
-    expect(converter.call('1,2,3')).to eq([1,2,3])
-  end
-
-  it "converts `a,b,c` to array" do
-    expect(converter.call('a,b,c')).to eq(['a','b','c'])
-  end
-
-  it "converts '1-2-3' to array" do
-    expect(converter.call('1-2-3')).to eq([1,2,3])
-  end
-
-  it "converts ' 1 - 2 - 3 ' to array" do
-    expect(converter.call('  1 - 2 - 3 ')).to eq([1,2,3])
-  end
-end
diff --git a/spec/unit/converters/array_spec.rb b/spec/unit/converters/array_spec.rb
new file mode 100644
index 0000000..f0f36ea
--- /dev/null
+++ b/spec/unit/converters/array_spec.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+RSpec.describe Necromancer::ArrayConverters, "#call" do
+  describe ":string -> :array" do
+    subject(:converter) {
+      described_class::StringToArrayConverter.new(:string, :array)
+    }
+
+    {
+      ""                => [],
+      "123"             => %w[123],
+      "1,2,3"           => %w[1 2 3],
+      "1-2-3"           => %w[1 2 3],
+      " 1  - 2 - 3 "    => [" 1  ", " 2 ", " 3 "],
+      "1  ,  2  , 3"    => ["1  ", "  2  ", " 3"],
+      "1.2,2.3,3.4"     => %w[1.2 2.3 3.4],
+      "a,b,c"           => %w[a b c],
+      "a-b-c"           => %w[a b c],
+      "aa a,b bb,c c c" => %w[aa\ a b\ bb c\ c\ c]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert empty string to array in strict mode" do
+      expect {
+        converter.("unknown", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'unknown' could not be converted from `string` into `array`"
+      )
+    end
+  end
+
+  describe ":string -> :booleans" do
+    subject(:converter) { described_class::StringToBooleanArrayConverter.new }
+
+    {
+      "t,f,t"     => [true, false, true],
+      "yes,no,Y"  => [true, false, true],
+      "1-0-FALSE" => [true, false, false]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("yes,unknown", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'unknown' could not be converted from `string` into `boolean`"
+      )
+    end
+  end
+
+  describe ":string -> :integers/:ints" do
+    subject(:converter) { described_class::StringToIntegerArrayConverter.new }
+
+    {
+      "1,2,3"         => [1, 2, 3],
+      "1.2, 2.3, 3.4" => [1, 2, 3]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("1,unknown", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'unknown' could not be converted from `string` into `integer`"
+      )
+    end
+  end
+
+  describe ":string -> :floats" do
+    subject(:converter) { described_class::StringToFloatArrayConverter.new }
+
+    {
+      "1,2,3"         => [1.0, 2.0, 3.0],
+      "1.2, 2.3, 3.4" => [1.2, 2.3, 3.4]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("1,unknown", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'unknown' could not be converted from `string` into `float`"
+      )
+    end
+  end
+
+  describe ":string -> :numerics/:nums" do
+    subject(:converter) { described_class::StringToNumericArrayConverter.new }
+
+    {
+      "1,2.0,3"       => [1, 2.0, 3],
+      "1.2, 2.3, 3.4" => [1.2, 2.3, 3.4]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("1,unknown", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'unknown' could not be converted from `string` into `numeric`"
+      )
+    end
+  end
+
+  describe ":array -> :booleans/:bools" do
+    subject(:converter) {
+      described_class::ArrayToBooleanArrayConverter.new(:array, :boolean)
+    }
+
+    {
+      %w[t f yes no] => [true, false, true, false],
+      %w[t no 5]     => [true, false, "5"]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert `['t', 'no', 5]` to boolean array in strict mode" do
+      expect {
+        converter.(["t", "no", 5], strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'5' could not be converted from `string` into `boolean`"
+      )
+    end
+  end
+
+  describe ":array -> :integers" do
+    subject(:converter) { described_class::ArrayToIntegerArrayConverter.new }
+
+    {
+      %w[1 2 3]       => [1, 2, 3],
+      %w[1.2 2.3 3.4] => [1, 2, 3]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert `['1','2.3']` to integer array in strict mode" do
+      expect {
+        converter.(["1", "2.3"], strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'2.3' could not be converted from `string` into `integer`"
+      )
+    end
+  end
+
+  describe ":array -> :floats" do
+    subject(:converter) { described_class::ArrayToFloatArrayConverter.new }
+
+    {
+      %w[1 2 3]       => [1.0, 2.0, 3.0],
+      %w[1.2 2.3 3.4] => [1.2, 2.3, 3.4]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert `['1','2.3',false]` to float array in strict mode" do
+      expect {
+        converter.(["1", "2.3", false], strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'false' could not be converted from `string` into `float`"
+      )
+    end
+  end
+
+  describe ":array -> :numerics/:nums" do
+    subject(:converter) {
+      described_class::ArrayToNumericArrayConverter.new(:array, :numerics)
+    }
+
+    {
+      %w[1 2.3 3.0]   => [1, 2.3, 3.0],
+      %w[1 2.3 false] => [1, 2.3, "false"]
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert `['1','2.3',false]` to numeric array in strict mode" do
+      expect {
+        converter.(["1", "2.3", false], strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'false' could not be converted from `string` into `numeric`"
+      )
+    end
+  end
+
+  describe ":array -> :set" do
+    subject(:converter) {
+      described_class::ArrayToSetConverter.new(:array, :set)
+    }
+
+    it "converts `[:x,:y,:x,1,2,1]` to set" do
+      expect(converter.([:x, :y, :x, 1, 2, 1])).to eql(Set[:x, :y, 1, 2])
+    end
+
+    it "fails to convert `1` to set" do
+      expect {
+        converter.(1, strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+
+  describe ":object -> :array" do
+    subject(:converter) {
+      described_class::ObjectToArrayConverter.new(:object, :array)
+    }
+
+    it "converts nil to array" do
+      expect(converter.(nil)).to eq([])
+    end
+
+    it "converts custom object to array" do
+      stub_const("Custom", Class.new do
+        def to_ary
+          %i[x y]
+        end
+      end)
+      custom = Custom.new
+      expect(converter.(custom)).to eq(%i[x y])
+    end
+  end
+end
diff --git a/spec/unit/converters/boolean/boolean_to_integer_spec.rb b/spec/unit/converters/boolean/boolean_to_integer_spec.rb
deleted file mode 100644
index d2d9dbc..0000000
--- a/spec/unit/converters/boolean/boolean_to_integer_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::BooleanConverters::BooleanToIntegerConverter, '.call' do
-
-  subject(:converter) { described_class.new }
-
-  it "converts true to 1 value" do
-    expect(converter.call(true)).to eq(1)
-  end
-
-  it "converts false to 0 value" do
-    expect(converter.call(false)).to eq(0)
-  end
-
-  it "fails to convert in strict mode" do
-    expect {
-      converter.call('unknown', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "returns value in non-strict mode" do
-    expect(converter.call('unknown', strict: false)).to eq('unknown')
-  end
-end
diff --git a/spec/unit/converters/boolean/integer_to_boolean_spec.rb b/spec/unit/converters/boolean/integer_to_boolean_spec.rb
deleted file mode 100644
index ac6cef2..0000000
--- a/spec/unit/converters/boolean/integer_to_boolean_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::BooleanConverters::IntegerToBooleanConverter, '.call' do
-
-  subject(:converter) { described_class.new }
-
-  it "converts 1 to true value" do
-    expect(converter.call(1)).to eq(true)
-  end
-
-  it "converts 0 to false value" do
-    expect(converter.call(0)).to eq(false)
-  end
-
-  it "fails to convert in strict mode" do
-    expect  {
-      converter.call('1', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-end
diff --git a/spec/unit/converters/boolean/string_to_boolean_spec.rb b/spec/unit/converters/boolean/string_to_boolean_spec.rb
deleted file mode 100644
index af4f46e..0000000
--- a/spec/unit/converters/boolean/string_to_boolean_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::BooleanConverters::StringToBooleanConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :boolean) }
-
-  it "raises error for empty string strict mode" do
-    expect {
-      converter.call('', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "fails to convert unkonwn value FOO" do
-    expect {
-      converter.call('FOO', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "passes through boolean value" do
-    expect(converter.call(true)).to eq(true)
-  end
-
-  %w[true TRUE t T 1 y Y YES yes on ON].each do |value|
-    it "converts '#{value}' to true value" do
-      expect(converter.call(value)).to eq(true)
-    end
-  end
-
-  %w[false FALSE f F 0 n N NO No no off OFF].each do |value|
-    it "converts '#{value}' to false value" do
-      expect(converter.call(value)).to eq(false)
-    end
-  end
-end
diff --git a/spec/unit/converters/boolean_spec.rb b/spec/unit/converters/boolean_spec.rb
new file mode 100644
index 0000000..df9e791
--- /dev/null
+++ b/spec/unit/converters/boolean_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.describe Necromancer::BooleanConverters, "#call" do
+  describe ":string -> :boolean" do
+    subject(:converter) {
+      described_class::StringToBooleanConverter.new(:string, :boolean)
+    }
+
+    it "passes through boolean value" do
+      expect(converter.(true)).to eq(true)
+    end
+
+    %w[true TRUE t T 1 y Y YES yes on ON].each do |value|
+      it "converts '#{value}' to true value" do
+        expect(converter.(value)).to eq(true)
+      end
+    end
+
+    %w[false FALSE f F 0 n N NO No no off OFF].each do |value|
+      it "converts '#{value}' to false value" do
+        expect(converter.(value)).to eq(false)
+      end
+    end
+
+    it "raises error for empty string strict mode" do
+      expect {
+        converter.("", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+
+    it "fails to convert unkonwn value FOO" do
+      expect {
+        converter.("FOO", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+
+  describe ":boolean -> :integer" do
+    subject(:converter) {
+      described_class::BooleanToIntegerConverter.new(:boolean, :integer)
+    }
+
+    {
+      true      => 1,
+      false     => 0,
+      "unknown" => "unknown"
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("unknown", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'unknown' could not be converted from `boolean` into `integer`"
+      )
+    end
+  end
+
+  describe ":integer -> :boolean" do
+    subject(:converter) { described_class::IntegerToBooleanConverter.new }
+
+    {
+      1         => true,
+      0         => false,
+      "unknown" => "unknown"
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect  {
+        converter.("1", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+end
diff --git a/spec/unit/converters/date_time/string_to_date_spec.rb b/spec/unit/converters/date_time/string_to_date_spec.rb
deleted file mode 100644
index 0f68ee7..0000000
--- a/spec/unit/converters/date_time/string_to_date_spec.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::DateTimeConverters::StringToDateConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :date) }
-
-  it "converts '1-1-2015' to date value" do
-    expect(converter.call('1-1-2015')).to eq(Date.parse('2015/01/01'))
-  end
-
-  it "converts '2014/12/07' to date value" do
-    expect(converter.call('2014/12/07')).to eq(Date.parse('2014/12/07'))
-  end
-
-  it "converts '2014-12-07' to date value" do
-    expect(converter.call('2014-12-07')).to eq(Date.parse('2014/12/07'))
-  end
-
-  it "fails to convert in strict mode" do
-    expect {
-      converter.call('2014 - 12 - 07', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-end
diff --git a/spec/unit/converters/date_time/string_to_datetime_spec.rb b/spec/unit/converters/date_time/string_to_datetime_spec.rb
deleted file mode 100644
index 76258b4..0000000
--- a/spec/unit/converters/date_time/string_to_datetime_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::DateTimeConverters::StringToDateTimeConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :datetime) }
-
-  it "converts '2014/12/07' to date value" do
-    expect(converter.call('2014/12/07')).to eq(DateTime.parse('2014/12/07'))
-  end
-
-  it "converts '2014-12-07' to date value" do
-    expect(converter.call('2014-12-07')).to eq(DateTime.parse('2014-12-07'))
-  end
-
-  it "converts '7th December 2014' to datetime value" do
-    expect(converter.call('7th December 2014')).
-      to eq(DateTime.parse('2014-12-07'))
-  end
-
-  it "converts '7th December 2014 17:19:44' to datetime value" do
-    expect(converter.call('7th December 2014 17:19:44')).
-      to eq(DateTime.parse('2014-12-07 17:19:44'))
-  end
-
-  it "fails to convert in strict mode" do
-    expect {
-      converter.call('2014 - 12 - 07', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-end
diff --git a/spec/unit/converters/date_time/string_to_time_spec.rb b/spec/unit/converters/date_time/string_to_time_spec.rb
deleted file mode 100644
index 959f015..0000000
--- a/spec/unit/converters/date_time/string_to_time_spec.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::DateTimeConverters::StringToTimeConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :time) }
-
-  it "converts to time instance" do
-    expect(converter.call('01/01/2015')).to be_a(Time)
-  end
-
-  it "converts '01/01/2015' to time value" do
-    expect(converter.call('01/01/2015')).to eq(Time.parse('01/01/2015'))
-  end
-
-  it "converts '01/01/2015 08:35' to time value" do
-    expect(converter.call('01/01/2015 08:35')).to eq(Time.parse('01/01/2015 08:35'))
-  end
-
-  it "converts '12:35' to time value" do
-    expect(converter.call('12:35')).to eq(Time.parse('12:35'))
-  end
-
-  it "fails to convert in strict mode" do
-    expect {
-      converter.call('11-13-2015', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-end
diff --git a/spec/unit/converters/date_time_spec.rb b/spec/unit/converters/date_time_spec.rb
new file mode 100644
index 0000000..37bd080
--- /dev/null
+++ b/spec/unit/converters/date_time_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+RSpec.describe Necromancer::DateTimeConverters, "#call" do
+  describe ":string -> :date" do
+    subject(:converter) {
+      described_class::StringToDateConverter.new(:string, :date)
+    }
+
+    {
+      ""           => "",
+      "1-1-2015"   => Date.parse("2015/01/01"),
+      "2014/12/07" => Date.parse("2014/12/07"),
+      "2014-12-07" => Date.parse("2014/12/07")
+    }.each do |actual, expected|
+      it "converts #{actual.inspect} to range type" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("2014 - 12 - 07", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'2014 - 12 - 07' could not be converted from `string` into `date`"
+      )
+    end
+  end
+
+  describe ":string -> :datetime" do
+    subject(:converter) {
+      described_class::StringToDateTimeConverter.new(:string, :datetime)
+    }
+
+    {
+      ""                           => "",
+      "2014/12/07"                 => DateTime.parse("2014/12/07"),
+      "2014-12-07"                 => DateTime.parse("2014-12-07"),
+      "7th December 2014"          => DateTime.parse("2014-12-07"),
+      "7th December 2014 17:19:44" => DateTime.parse("2014-12-07 17:19:44")
+    }.each do |actual, expected|
+      it "converts #{actual.inspect} to range type" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("2014 - 12 - 07", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+
+  describe ":string -> :time" do
+    subject(:converter) {
+      described_class::StringToTimeConverter.new(:string, :time)
+    }
+
+    {
+      ""                 => "",
+      "01/01/2015"       => Time.parse("01/01/2015"),
+      "01/01/2015 08:35" => Time.parse("01/01/2015 08:35"),
+      "12:35"            => Time.parse("12:35")
+    }.each do |actual, expected|
+      it "converts #{actual.inspect} to range type" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+
+    it "fails to convert in strict mode" do
+      expect {
+        converter.("11-13-2015", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+end
diff --git a/spec/unit/converters/hash_spec.rb b/spec/unit/converters/hash_spec.rb
new file mode 100644
index 0000000..8eaf96e
--- /dev/null
+++ b/spec/unit/converters/hash_spec.rb
@@ -0,0 +1,108 @@
+# frozen_string_literal: true
+
+RSpec.describe Necromancer::HashConverters, "#call" do
+  describe ":string -> :hash" do
+    subject(:converter) { described_class::StringToHashConverter.new }
+
+    {
+      "a=1"             => { a: "1" },
+      "a=1&b=2"         => { a: "1", b: "2" },
+      "a= & b=2"        => { a: "", b: "2" },
+      "a=1 & b=2 & a=3" => { a: %w[1 3], b: "2" },
+      "a:1 b:2"         => { a: "1", b: "2" },
+      "a:1 b:2 a:3"     => { a: %w[1 3], b: "2" }
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+  end
+
+  describe ":string -> :int_hash" do
+    subject(:converter) { described_class::StringToIntegerHashConverter.new }
+
+    {
+      "a=1 & b=2 & a=3" => { a: [1, 3], b: 2 },
+      "a:1 b:2 c:3"     => { a: 1, b: 2, c: 3 },
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eq(obj)
+      end
+    end
+
+    it "fails to convert '1.2o' value in strict mode" do
+      expect {
+        converter.("a=1.2o", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'1.2o' could not be converted from `string` into `integer`"
+      )
+    end
+  end
+
+  describe ":string -> :float_hash" do
+    subject(:converter) { described_class::StringToFloatHashConverter.new }
+
+    {
+      "a=1 & b=2 & a=3" => { a: [1.0, 3.0], b: 2.0 },
+      "a:1 b:2 c:3"     => { a: 1.0, b: 2.0, c: 3.0 }
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eql(obj)
+      end
+    end
+
+    it "fails to convert '1.2o' value in strict mode" do
+      expect {
+        converter.("a=1.2o", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'1.2o' could not be converted from `string` into `float`"
+      )
+    end
+  end
+
+  describe ":string -> :numeric_hash" do
+    subject(:converter) { described_class::StringToNumericHashConverter.new }
+
+    {
+      "a=1 & b=2.0 & a=3.0" => { a: [1, 3.0], b: 2.0 },
+      "a:1 b:2.0 c:3.0"     => { a: 1, b: 2.0, c: 3.0 }
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eql(obj)
+      end
+    end
+
+    it "fails to convert '1.2o' value in strict mode" do
+      expect {
+        converter.("a=1.2o", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'1.2o' could not be converted from `string` into `numeric`"
+      )
+    end
+  end
+
+  describe ":string -> :bool_hash" do
+    subject(:converter) { described_class::StringToBooleanHashConverter.new }
+
+    {
+      "a=t & b=t & a=f" => { a: [true, false], b: true },
+      "a:yes b:no c:t"  => { a: true, b: false, c: true }
+    }.each do |input, obj|
+      it "converts #{input.inspect} to #{obj.inspect}" do
+        expect(converter.(input)).to eql(obj)
+      end
+    end
+
+    it "fails to convert '1.2o' value in strict mode" do
+      expect {
+        converter.("a=1.2", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'1.2' could not be converted from `string` into `boolean`"
+      )
+    end
+  end
+end
diff --git a/spec/unit/converters/numeric/string_to_float_spec.rb b/spec/unit/converters/numeric/string_to_float_spec.rb
deleted file mode 100644
index 5bd4e81..0000000
--- a/spec/unit/converters/numeric/string_to_float_spec.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::NumericConverters::StringToFloatConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :float) }
-
-  it "raises error for empty string in strict mode" do
-    expect {
-      converter.call('', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  {
-    '1'       => 1.0,
-    '+1'      => 1.0,
-    '-1'      => -1.0,
-    '1e1'     => 10.0,
-    '1e-1'    => 0.1,
-    '-1e1'    => -10.0,
-    '-1e-1'   => -0.1,
-    '1.0'     => 1.0,
-    '1.0e+1'  => 10.0,
-    '1.0e-1'  => 0.1,
-    '-1.0e+1' => -10.0,
-    '-1.0e-1' => -0.1,
-    '.1'      => 0.1,
-    '.1e+1'   => 1.0,
-    '.1e-1'   => 0.01,
-    '-.1e+1'  => -1.0,
-    '-.1e-1'  => -0.01
-  }.each do |actual, expected|
-    it "converts '#{actual}' to float value" do
-      expect(converter.call(actual)).to eql(expected)
-    end
-  end
-
-  it "failse to convert '1.2a' in strict mode" do
-    expect {
-      converter.call('1.2a', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "converts '1.2a' in non-strict mode" do
-    expect(converter.call('1.2a', strict: false)).to eq(1.2)
-  end
-end
diff --git a/spec/unit/converters/numeric/string_to_integer_spec.rb b/spec/unit/converters/numeric/string_to_integer_spec.rb
deleted file mode 100644
index c344d12..0000000
--- a/spec/unit/converters/numeric/string_to_integer_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::NumericConverters::StringToIntegerConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :integer) }
-
-  {
-    '1'       => 1,
-    '+1'      => 1,
-    '-1'      => -1,
-    '1e+1'    => 1,
-    '+1e-1'   => 1,
-    '-1e1'    => -1,
-    '-1e-1'   => -1,
-    '1.0'     => 1,
-    '1.0e+1'  => 1,
-    '1.0e-1'  => 1,
-    '-1.0e+1' => -1,
-    '-1.0e-1' => -1,
-     '.1'     => 0,
-    '.1e+1'   => 0,
-    '.1e-1'   => 0,
-     '-.1e+1' => 0,
-    '-.1e-1'  => 0
-  }.each do |actual, expected|
-    it "converts '#{actual}' to float value" do
-      expect(converter.call(actual)).to eql(expected)
-    end
-  end
-
-  it "raises error for empty string in strict mode" do
-    expect {
-      converter.call('', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "converts empty string to 0 in non-strict mode" do
-    expect(converter.call('', strict: false)).to eq(0)
-  end
-
-  it "raises error for float in strict mode" do
-    expect {
-      converter.call('1.2', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "converts float to integer in non-strict mode" do
-    expect(converter.call(1.2)).to eq(1)
-  end
-
-  it "converts mixed string to integer in non-strict mode" do
-    expect(converter.call('1abc')).to eq(1)
-  end
-
-  it "raises error for mixed string in strict mode" do
-    expect {
-      converter.call('1abc', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-end
diff --git a/spec/unit/converters/numeric/string_to_numeric_spec.rb b/spec/unit/converters/numeric/string_to_numeric_spec.rb
deleted file mode 100644
index 0eb8511..0000000
--- a/spec/unit/converters/numeric/string_to_numeric_spec.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::NumericConverters::StringToNumericConverter, '.call' do
-
-  subject(:converter) { described_class.new(:string, :numeric) }
-
-  {
-    '1'       => 1,
-    '+1'      => 1,
-    '-1'      => -1,
-    '1e1'     => 10.0,
-    '1e-1'    => 0.1,
-    '-1e1'    => -10.0,
-    '-1e-1'   => -0.1,
-    '1.0'     => 1.0,
-    '1.0e+1'  => 10.0,
-    '1.0e-1'  => 0.1,
-    '-1.0e+1' => -10.0,
-    '-1.0e-1' => -0.1,
-    '.1'      => 0.1,
-    '.1e+1'   => 1.0,
-    '.1e-1'   => 0.01,
-    '-.1e+1'  => -1.0,
-    '-.1e-1'  => -0.01
-  }.each do |actual, expected|
-    it "converts '#{actual}' to '#{expected}'" do
-      expect(converter.call(actual)).to eql(expected)
-    end
-  end
-end
diff --git a/spec/unit/converters/numeric_spec.rb b/spec/unit/converters/numeric_spec.rb
new file mode 100644
index 0000000..a8bfec2
--- /dev/null
+++ b/spec/unit/converters/numeric_spec.rb
@@ -0,0 +1,147 @@
+# frozen_string_literal: true
+
+RSpec.describe Necromancer::NumericConverters, "#call" do
+  describe ":string -> :float" do
+    subject(:converter) {
+      described_class::StringToFloatConverter.new(:string, :float)
+    }
+
+    {
+      ""        => 0.0,
+      "1"       => 1.0,
+      "+1"      => 1.0,
+      "1.2a"    => 1.2,
+      "-1"      => -1.0,
+      "1e1"     => 10.0,
+      "1e-1"    => 0.1,
+      "-1e1"    => -10.0,
+      "-1e-1"   => -0.1,
+      "1.0"     => 1.0,
+      "1.0e+1"  => 10.0,
+      "1.0e-1"  => 0.1,
+      "-1.0e+1" => -10.0,
+      "-1.0e-1" => -0.1,
+      ".1"      => 0.1,
+      ".1e+1"   => 1.0,
+      ".1e-1"   => 0.01,
+      "-.1e+1"  => -1.0,
+      "-.1e-1"  => -0.01,
+      " 1. 10 " => 1.0,
+      "    1.0" => 1.0,
+      " .1    " => 0.1,
+      "  -1.1 " => -1.1,
+      " -1 . 1" => -1.0
+    }.each do |actual, expected|
+      it "converts '#{actual}' to float value" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+
+    it "raises error for empty string in strict mode" do
+      expect {
+        converter.("", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+
+    it "fails to convert '1.2a' in strict mode" do
+      expect {
+        converter.("1.2a", strict: true)
+      }.to raise_error(
+        Necromancer::ConversionTypeError,
+        "'1.2a' could not be converted from `string` into `float`"
+      )
+    end
+  end
+
+  describe ":string -> :integer" do
+    subject(:converter) {
+      described_class::StringToIntegerConverter.new(:string, :integer)
+    }
+
+    {
+      ""        => 0,
+      "1"       => 1,
+      "1ab"     => 1,
+      "+1"      => 1,
+      "-1"      => -1,
+      "1e+1"    => 1,
+      "+1e-1"   => 1,
+      "-1e1"    => -1,
+      "-1e-1"   => -1,
+      "1.0"     => 1,
+      "1.0e+1"  => 1,
+      "1.0e-1"  => 1,
+      "-1.0e+1" => -1,
+      "-1.0e-1" => -1,
+      ".1"      => 0,
+      ".1e+1"   => 0,
+      ".1e-1"   => 0,
+      "-.1e+1"  => 0,
+      "-.1e-1"  => 0,
+      " 1 00"   => 1,
+      "  1  "   => 1,
+      "  -1 "   => -1
+    }.each do |actual, expected|
+      it "converts '#{actual}' to float value" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+
+    it "raises error for empty string in strict mode" do
+      expect {
+        converter.("", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+
+    it "raises error for float in strict mode" do
+      expect {
+        converter.("1.2", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+
+    it "raises error for mixed string in strict mode" do
+      expect {
+        converter.("1abc", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+
+  describe ":string -> :numeric" do
+    subject(:converter) {
+      described_class::StringToNumericConverter.new(:string, :numeric)
+    }
+
+    {
+      ""        => 0,
+      "1"       => 1,
+      "+1"      => 1,
+      "-1"      => -1,
+      "1e1"     => 10.0,
+      "1e-1"    => 0.1,
+      "-1e1"    => -10.0,
+      "-1e-1"   => -0.1,
+      "1.0"     => 1.0,
+      "1.0e+1"  => 10.0,
+      "1.0e-1"  => 0.1,
+      "-1.0e+1" => -10.0,
+      "-1.0e-1" => -0.1,
+      ".1"      => 0.1,
+      ".1e+1"   => 1.0,
+      ".1e-1"   => 0.01,
+      "-.1e+1"  => -1.0,
+      "-.1e-1"  => -0.01,
+      " 1 00"   => 1,
+      "  1  "   => 1,
+      "  -1 "   => -1,
+      " 1. 10 " => 1.0,
+      "    1.0" => 1.0,
+      " .1    " => 0.1,
+      "  -1.1 " => -1.1,
+      " -1 . 1" => -1.0
+    }.each do |actual, expected|
+      it "converts '#{actual}' to '#{expected}'" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+  end
+end
diff --git a/spec/unit/converters/range/string_to_range_spec.rb b/spec/unit/converters/range/string_to_range_spec.rb
deleted file mode 100644
index b3c4f88..0000000
--- a/spec/unit/converters/range/string_to_range_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.describe Necromancer::RangeConverters::StringToRangeConverter, '.call' do
-
-  subject(:converter) { described_class.new }
-
-  it "raises error for empty string in strict mode" do
-    expect {
-      converter.call('', strict: true)
-    }.to raise_error(Necromancer::ConversionTypeError)
-  end
-
-  it "returns value in non-strict mode" do
-    expect(converter.call('', strict: false)).to eq('')
-  end
-
-  {
-    '1'     => 1..1,
-    '1..10' => 1..10,
-    '1-10'  => 1..10,
-    '1,10'  => 1..10,
-    '1...10' => 1...10,
-    '-1..10' => -1..10,
-    '1..-10' => 1..-10,
-    'a..z' => 'a'..'z',
-    'a-z' => 'a'..'z',
-    'A-Z' => 'A'..'Z'
-  }.each do |actual, expected|
-    it "converts '#{actual}' to range type" do
-      expect(converter.call(actual)).to eql(expected)
-    end
-  end
-end
diff --git a/spec/unit/converters/range_spec.rb b/spec/unit/converters/range_spec.rb
new file mode 100644
index 0000000..58df860
--- /dev/null
+++ b/spec/unit/converters/range_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+RSpec.describe Necromancer::RangeConverters, "#call" do
+  describe ":string -> :range" do
+    subject(:converter) { described_class::StringToRangeConverter.new }
+
+    {
+      ""          => "",
+      "a"         => "a",
+      "1"         => 1..1,
+      "1.0"       => 1.0..1.0,
+      "1..10"     => 1..10,
+      "1.0..10.0" => 1.0..10.0,
+      "1-10"      => 1..10,
+      "1 , 10"    => 1..10,
+      "1...10"    => 1...10,
+      "1 . . 10"  => 1..10,
+      "-1..10"    => -1..10,
+      "1..-10"    => 1..-10,
+      "a..z"      => "a".."z",
+      "a . . . z" => "a"..."z",
+      "a-z"       => "a".."z",
+      "A , Z"     => "A".."Z"
+    }.each do |actual, expected|
+      it "converts #{actual.inspect} to range type" do
+        expect(converter.(actual)).to eql(expected)
+      end
+    end
+
+    it "raises error for empty string in strict mode" do
+      expect {
+        converter.("", strict: true)
+      }.to raise_error(Necromancer::ConversionTypeError)
+    end
+  end
+end
diff --git a/spec/unit/inspect_spec.rb b/spec/unit/inspect_spec.rb
index 5659e79..1403435 100644
--- a/spec/unit/inspect_spec.rb
+++ b/spec/unit/inspect_spec.rb
@@ -1,14 +1,20 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer, '.inspect' do
+RSpec.describe Necromancer, ".inspect" do
   subject(:converter) { described_class.new }
 
   it "inspects converter instance" do
-    expect(converter.inspect).to eq("#<Necromancer::Context@#{converter.object_id} @config=#{converter.configuration}>")
+    expect(converter.inspect).to eq(
+      "#<Necromancer::Context@#{converter.object_id} " \
+      "@config=#{converter.configuration}>"
+    )
   end
 
   it "inspects conversion target" do
     conversion = converter.convert(11)
-    expect(conversion.inspect).to eq("#<Necromancer::ConversionTarget@#{conversion.object_id} @object=11, @source=integer>")
+    expect(conversion.inspect).to eq(
+      "#<Necromancer::ConversionTarget@#{conversion.object_id} " \
+      "@object=11, @source=integer>"
+    )
   end
 end
diff --git a/spec/unit/new_spec.rb b/spec/unit/new_spec.rb
index df7e9c3..83ac31a 100644
--- a/spec/unit/new_spec.rb
+++ b/spec/unit/new_spec.rb
@@ -1,7 +1,6 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer, '#new' do
-
+RSpec.describe Necromancer, "#new" do
   subject(:converter) { described_class.new }
 
   it "creates context" do
diff --git a/spec/unit/register_spec.rb b/spec/unit/register_spec.rb
index 002cadd..7995788 100644
--- a/spec/unit/register_spec.rb
+++ b/spec/unit/register_spec.rb
@@ -1,15 +1,17 @@
 # frozen_string_literal: true
 
-RSpec.describe Necromancer, '.register' do
+RSpec.describe Necromancer, ".register" do
   it "allows ro register converter" do
     converter = described_class.new
-    UpcaseConverter = Struct.new(:source, :target) do
-      def call(value, **options)
+    stub_const("UpcaseConverter", Struct.new(:source, :target) do
+      def call(value, **_options)
         value.to_s.upcase
       end
-    end
+    end)
+
     upcase_converter = UpcaseConverter.new(:string, :upcase)
+
     expect(converter.register(upcase_converter)).to eq(true)
-    expect(converter.convert('magic').to(:upcase)).to eq('MAGIC')
+    expect(converter.convert("magic").to(:upcase)).to eq("MAGIC")
   end
 end
diff --git a/tasks/console.rake b/tasks/console.rake
index 7dbb944..79b0079 100644
--- a/tasks/console.rake
+++ b/tasks/console.rake
@@ -1,11 +1,11 @@
 # frozen_string_literal: true
 
-desc 'Load gem inside irb console'
+desc "Load gem inside irb console"
 task :console do
-  require 'irb'
-  require 'irb/completion'
-  require_relative '../lib/necromancer'
+  require "irb"
+  require "irb/completion"
+  require_relative "../lib/necromancer"
   ARGV.clear
   IRB.start
 end
-task :c => :console
+task c: :console
diff --git a/tasks/coverage.rake b/tasks/coverage.rake
index e7c9050..247724a 100644
--- a/tasks/coverage.rake
+++ b/tasks/coverage.rake
@@ -1,11 +1,11 @@
 # frozen_string_literal: true
 
-desc 'Measure code coverage'
+desc "Measure code coverage"
 task :coverage do
   begin
-    original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true'
-    Rake::Task['spec'].invoke
+    original, ENV["COVERAGE"] = ENV["COVERAGE"], "true"
+    Rake::Task["spec"].invoke
   ensure
-    ENV['COVERAGE'] = original
+    ENV["COVERAGE"] = original
   end
 end
diff --git a/tasks/spec.rake b/tasks/spec.rake
index c4373c8..d986d67 100644
--- a/tasks/spec.rake
+++ b/tasks/spec.rake
@@ -1,29 +1,28 @@
 # frozen_string_literal: true
 
 begin
-  require 'rspec/core/rake_task'
+  require "rspec/core/rake_task"
 
-  desc 'Run all specs'
+  desc "Run all specs"
   RSpec::Core::RakeTask.new(:spec) do |task|
-    task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb'
+    task.pattern = "spec/{unit,integration}{,/*/**}/*_spec.rb"
   end
 
   namespace :spec do
-    desc 'Run unit specs'
+    desc "Run unit specs"
     RSpec::Core::RakeTask.new(:unit) do |task|
-      task.pattern = 'spec/unit{,/*/**}/*_spec.rb'
+      task.pattern = "spec/unit{,/*/**}/*_spec.rb"
     end
 
-    desc 'Run integration specs'
+    desc "Run integration specs"
     RSpec::Core::RakeTask.new(:integration) do |task|
-      task.pattern = 'spec/integration{,/*/**}/*_spec.rb'
+      task.pattern = "spec/integration{,/*/**}/*_spec.rb"
     end
   end
-
 rescue LoadError
   %w[spec spec:unit spec:integration].each do |name|
     task name do
-      $stderr.puts "In order to run #{name}, do `gem install rspec`"
+      warn "In order to run #{name}, do `gem install rspec`"
     end
   end
 end

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/configuration.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/context.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/conversion_target.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/conversions.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converter.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converters/array.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converters/boolean.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converters/date_time.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converters/hash.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converters/numeric.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/converters/range.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/null_converter.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.7.0/lib/necromancer/version.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/necromancer-0.7.0.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/configuration.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/context.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/conversion_target.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/conversions.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/converter.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/converters/array.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/converters/boolean.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/converters/date_time.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/converters/numeric.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/converters/range.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/null_converter.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/lib/necromancer/version.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/tasks/console.rake
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/tasks/coverage.rake
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/gems/necromancer-0.5.1/tasks/spec.rake
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/necromancer-0.5.1.gemspec

No differences were encountered in the control files

More details

Full run details