necromancer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +19 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +141 -0
- data/Rakefile +8 -0
- data/lib/necromancer.rb +27 -0
- data/lib/necromancer/context.rb +45 -0
- data/lib/necromancer/conversion_target.rb +82 -0
- data/lib/necromancer/conversions.rb +75 -0
- data/lib/necromancer/converter.rb +37 -0
- data/lib/necromancer/converters/array.rb +50 -0
- data/lib/necromancer/converters/boolean.rb +58 -0
- data/lib/necromancer/converters/float.rb +28 -0
- data/lib/necromancer/converters/integer.rb +48 -0
- data/lib/necromancer/converters/range.rb +38 -0
- data/lib/necromancer/null_converter.rb +10 -0
- data/lib/necromancer/version.rb +5 -0
- data/necromancer.gemspec +21 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/unit/can_spec.rb +11 -0
- data/spec/unit/conversions/register_spec.rb +50 -0
- data/spec/unit/convert_spec.rb +66 -0
- data/spec/unit/converters/array/array_to_numeric_spec.rb +22 -0
- data/spec/unit/converters/array/string_to_array_spec.rb +15 -0
- data/spec/unit/converters/boolean/boolean_to_integer_spec.rb +16 -0
- data/spec/unit/converters/boolean/integer_to_boolean_spec.rb +16 -0
- data/spec/unit/converters/boolean/string_to_boolean_spec.rb +96 -0
- data/spec/unit/converters/float/string_to_float_spec.rb +28 -0
- data/spec/unit/converters/integer/string_to_integer_spec.rb +38 -0
- data/spec/unit/converters/range/string_to_range_spec.rb +52 -0
- data/spec/unit/new_spec.rb +12 -0
- data/tasks/console.rake +10 -0
- data/tasks/coverage.rake +11 -0
- data/tasks/spec.rake +29 -0
- metadata +109 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Necromancer
|
4
|
+
# Abstract converter used internally as a base for other converters
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class Converter
|
8
|
+
def initialize(source = nil, target = nil)
|
9
|
+
@source = source if source
|
10
|
+
@target = target if target
|
11
|
+
end
|
12
|
+
|
13
|
+
# Run converter
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
def call(*)
|
17
|
+
fail NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates anonymous converter
|
21
|
+
#
|
22
|
+
# @api private
|
23
|
+
def self.create(&block)
|
24
|
+
Class.new(self) do
|
25
|
+
define_method(:initialize) { |*a| block.call(self, *a) }
|
26
|
+
|
27
|
+
define_method(:call) { |value| convert.call(value) }
|
28
|
+
end.new
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_accessor :source
|
32
|
+
|
33
|
+
attr_accessor :target
|
34
|
+
|
35
|
+
attr_accessor :convert
|
36
|
+
end # Converter
|
37
|
+
end # Necromancer
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Necromancer
|
4
|
+
module ArrayConverters
|
5
|
+
class StringToArrayConverter < Converter
|
6
|
+
|
7
|
+
# @example
|
8
|
+
# converter.call('1,2,3') # => ['1', '2', '3']
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
def call(value, options = {})
|
12
|
+
case value.to_s
|
13
|
+
when /^((\d+)(\s*(,)\s*)?)+$/
|
14
|
+
value.to_s.split($4).map(&:to_i)
|
15
|
+
when /^((\w)(\s*,\s*)?)+$/
|
16
|
+
value.to_s.split(',')
|
17
|
+
else
|
18
|
+
fail ConversionTypeError, "#{value} could not be converted " \
|
19
|
+
"from #{source} into `#{target}`"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ArrayToNumericConverter < Converter
|
25
|
+
def call(value, options = {})
|
26
|
+
strict = options.fetch(:strict, false)
|
27
|
+
value.reduce([]) do |acc, el|
|
28
|
+
acc << case el.to_s
|
29
|
+
when /^\d+\.\d+$/
|
30
|
+
el.to_f
|
31
|
+
when /^\d+$/
|
32
|
+
el.to_i
|
33
|
+
else
|
34
|
+
if strict
|
35
|
+
raise ConversionTypeError, "#{value} could not be converted " \
|
36
|
+
" from `#{source}` into `#{target}`"
|
37
|
+
else
|
38
|
+
el
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.load(conversions)
|
46
|
+
conversions.register StringToArrayConverter.new(:string, :array)
|
47
|
+
conversions.register ArrayToNumericConverter.new(:array, :numeric)
|
48
|
+
end
|
49
|
+
end # ArrayConverters
|
50
|
+
end # Necromancer
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Necromancer
|
4
|
+
module BooleanConverters
|
5
|
+
# An object that converts a String to a Boolean
|
6
|
+
class StringToBooleanConverter < Converter
|
7
|
+
|
8
|
+
# Convert value to boolean type including range of strings
|
9
|
+
#
|
10
|
+
# @param [Object] value
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# converter.call("True") # => true
|
14
|
+
#
|
15
|
+
# other values converted to true are:
|
16
|
+
# 1, t, T, TRUE, true, True, y, Y, YES, yes, Yes, on, ON
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# converter.call("False") # => false
|
20
|
+
#
|
21
|
+
# other values coerced to false are:
|
22
|
+
# 0, f, F, FALSE, false, False, n, N, NO, no, No, on, ON
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def call(value, options = {})
|
26
|
+
case value.to_s
|
27
|
+
when /^(yes|y|on|t(rue)?|1)$/i
|
28
|
+
return true
|
29
|
+
when /^(no|n|off|f(alse)?|0)$/i
|
30
|
+
return false
|
31
|
+
else
|
32
|
+
fail ArgumentError, "Expected boolean type, got #{value}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# An object that converts an Integer to a Boolean
|
38
|
+
class IntegerToBooleanConverter < Converter
|
39
|
+
def call(value, options = {})
|
40
|
+
!value.zero?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# An object that converts a Boolean to an Integer
|
45
|
+
class BooleanToIntegerConverter < Converter
|
46
|
+
def call(value, options = {})
|
47
|
+
value ? 1 : 0
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.load(conversions)
|
52
|
+
conversions.register StringToBooleanConverter.new(:string, :boolean)
|
53
|
+
conversions.register IntegerToBooleanConverter.new(:integer, :boolean)
|
54
|
+
conversions.register BooleanToIntegerConverter.new(:boolean, :integer)
|
55
|
+
conversions.register NullConverter.new(:boolean, :boolean)
|
56
|
+
end
|
57
|
+
end # BooleanConverters
|
58
|
+
end # Necromancer
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Necromancer
|
4
|
+
module FloatConverters
|
5
|
+
class StringToFloatConverter < Converter
|
6
|
+
# Convert string to float value
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# converter.call('1.2') # => 1.2
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def call(value, options = {})
|
13
|
+
strict = options.fetch(:strict, false)
|
14
|
+
Float(value.to_s)
|
15
|
+
rescue
|
16
|
+
if strict
|
17
|
+
raise ConversionTypeError, "#{value} could not be converted from " \
|
18
|
+
"`#{source}` into `#{target}`"
|
19
|
+
else
|
20
|
+
value.to_f
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.load(conversions)
|
26
|
+
end
|
27
|
+
end # Conversion
|
28
|
+
end # Necromancer
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Necromancer
|
4
|
+
# Container for Integer converter classes
|
5
|
+
module IntegerConverters
|
6
|
+
# An object that converts a String to an Integer
|
7
|
+
class StringToIntegerConverter < Converter
|
8
|
+
# Convert string value to integer
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# converter.call('1abc') # => 1
|
12
|
+
#
|
13
|
+
# @api public
|
14
|
+
def call(value, options = {})
|
15
|
+
strict = options.fetch(:strict, false)
|
16
|
+
Integer(value.to_s)
|
17
|
+
rescue
|
18
|
+
if strict
|
19
|
+
raise ConversionTypeError, "#{value} could not be converted from " \
|
20
|
+
"`#{source}` into `#{target}`"
|
21
|
+
else
|
22
|
+
value.to_i
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# An object that converts an Integer to a String
|
28
|
+
class IntegerToStringConverter < Converter
|
29
|
+
# Convert integer value to string
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# converter.call(1) # => '1'
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def call(value, _)
|
36
|
+
value.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.load(conversions)
|
41
|
+
conversions.register StringToIntegerConverter.new(:string, :integer)
|
42
|
+
conversions.register IntegerToStringConverter.new(:integer, :string)
|
43
|
+
conversions.register IntegerToStringConverter.new(:fixnum, :string)
|
44
|
+
conversions.register NullConverter.new(:integer, :integer)
|
45
|
+
conversions.register NullConverter.new(:fixnum, :integer)
|
46
|
+
end
|
47
|
+
end # IntegerConverters
|
48
|
+
end # Necromancer
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Necromancer
|
4
|
+
# Container for Range converter classes
|
5
|
+
module RangeConverters
|
6
|
+
# An object that converts a String to a Range
|
7
|
+
class StringToRangeConverter < Converter
|
8
|
+
# Convert value to Range type with possible ranges
|
9
|
+
#
|
10
|
+
# @param [Object] value
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# converter.call('0,9') # => (0..9)
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# converter.call('0-9') # => (0..9)
|
17
|
+
#
|
18
|
+
# @api public
|
19
|
+
def call(value, options = {})
|
20
|
+
case value.to_s
|
21
|
+
when /\A(\-?\d+)\Z/
|
22
|
+
::Range.new($1.to_i, $1.to_i)
|
23
|
+
when /\A(-?\d+?)(\.{2}\.?|-|,)(-?\d+)\Z/
|
24
|
+
::Range.new($1.to_i, $3.to_i, $2 == '...')
|
25
|
+
when /\A(\w)(\.{2}\.?|-|,)(\w)\Z/
|
26
|
+
::Range.new($1.to_s, $3.to_s, $2 == '...')
|
27
|
+
else
|
28
|
+
fail ConversionTypeError, "#{value} could not be converted " \
|
29
|
+
"from #{source} into Range type"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.load(conversions)
|
35
|
+
conversions.register StringToRangeConverter.new(:string, :range)
|
36
|
+
end
|
37
|
+
end # RangeConverters
|
38
|
+
end # Necromancer
|
data/necromancer.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'necromancer/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'necromancer'
|
8
|
+
spec.version = Necromancer::VERSION
|
9
|
+
spec.authors = ['Piotr Murach']
|
10
|
+
spec.email = ['']
|
11
|
+
spec.summary = %q{Conversion from one object type to another with a bit of black magic.}
|
12
|
+
spec.description = %q{Conversion from one object type to another with a bit of black magic.}
|
13
|
+
spec.homepage = 'https://github.com/peter-murach/necromancer'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.test_files = spec.files.grep(%r{^(spec)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
if RUBY_VERSION > '1.9' and (ENV['COVERAGE'] || ENV['TRAVIS'])
|
4
|
+
require 'simplecov'
|
5
|
+
require 'coveralls'
|
6
|
+
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
Coveralls::SimpleCov::Formatter
|
10
|
+
]
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
command_name 'spec'
|
14
|
+
add_filter 'spec'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'necromancer'
|
19
|
+
|
20
|
+
RSpec.configure do |config|
|
21
|
+
config.expect_with :rspec do |expectations|
|
22
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
23
|
+
end
|
24
|
+
|
25
|
+
config.mock_with :rspec do |mocks|
|
26
|
+
mocks.verify_partial_doubles = true
|
27
|
+
end
|
28
|
+
|
29
|
+
# Limits the available syntax to the non-monkey patched syntax that is recommended.
|
30
|
+
config.disable_monkey_patching!
|
31
|
+
|
32
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
33
|
+
# be too noisy due to issues in dependencies.
|
34
|
+
config.warnings = true
|
35
|
+
|
36
|
+
if config.files_to_run.one?
|
37
|
+
config.default_formatter = 'doc'
|
38
|
+
end
|
39
|
+
|
40
|
+
config.profile_examples = 2
|
41
|
+
|
42
|
+
config.order = :random
|
43
|
+
|
44
|
+
Kernel.srand config.seed
|
45
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Necromancer, 'can?' do
|
6
|
+
it "checks if conversion is possible" do
|
7
|
+
converter = described_class.new
|
8
|
+
expect(converter.can?(:string, :integer)).to eq(true)
|
9
|
+
expect(converter.can?(:unknown, :integer)).to eq(false)
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Necromancer::Conversions, '.register' do
|
6
|
+
|
7
|
+
it "allows to register converter" do
|
8
|
+
context = described_class.new
|
9
|
+
converter = double(:converter, {source: :string, target: :numeric})
|
10
|
+
expect(context.register(converter)).to eq(true)
|
11
|
+
expect(context[:string, :numeric]).to eq(converter)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "allows to register converter with no source" do
|
15
|
+
context = described_class.new
|
16
|
+
converter = double(:converter, {source: nil, target: :numeric})
|
17
|
+
expect(context.register(converter)).to eq(true)
|
18
|
+
expect(context[:none, :numeric]).to eq(converter)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "allows to register converter with no target" do
|
22
|
+
context = described_class.new
|
23
|
+
converter = double(:converter, {source: :string, target: nil})
|
24
|
+
expect(context.register(converter)).to eq(true)
|
25
|
+
expect(context[:string, :none]).to eq(converter)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "allows to register anonymous converter" do
|
29
|
+
conversions = described_class.new
|
30
|
+
|
31
|
+
conversions.register do |c|
|
32
|
+
c.source= :string
|
33
|
+
c.target= :upcase
|
34
|
+
c.convert = proc { |value| value.to_s.upcase }
|
35
|
+
end
|
36
|
+
expect(conversions[:string, :upcase].call('magic')).to eq('MAGIC')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "allows to register custom converter" do
|
40
|
+
conversions = described_class.new
|
41
|
+
UpcaseConverter = Struct.new(:source, :target) do
|
42
|
+
def call(value)
|
43
|
+
value.to_s.upcase
|
44
|
+
end
|
45
|
+
end
|
46
|
+
upcase_converter = UpcaseConverter.new(:string, :upcase)
|
47
|
+
expect(conversions.register(upcase_converter)).to be(true)
|
48
|
+
expect(conversions[:string, :upcase].call('magic')).to eq('MAGIC')
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe Necromancer, '.convert' do
|
6
|
+
|
7
|
+
subject(:converter) { described_class.new }
|
8
|
+
|
9
|
+
context 'when integer' do
|
10
|
+
it "converts string to integer" do
|
11
|
+
expect(converter.convert('1').to(:integer)).to eq(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "allows for block for conversion method" do
|
15
|
+
expect(converter.convert { '1' }.to(:integer)).to eq(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "convers integer to string" do
|
19
|
+
expect(converter.convert(1).to(:string)).to eq('1')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "allows for null type conversion" do
|
23
|
+
expect(converter.convert(1).to(:integer)).to eq(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "raises error when in strict mode" do
|
27
|
+
expect {
|
28
|
+
converter.convert('1a').to(:integer, strict: true)
|
29
|
+
}.to raise_error(Necromancer::ConversionTypeError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'when boolean' do
|
34
|
+
it "converts boolean to boolean" do
|
35
|
+
expect(converter.convert(true).to(:boolean)).to eq(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "converts string to boolean" do
|
39
|
+
expect(converter.convert('yes').to(:boolean)).to eq(true)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "converts integer to boolean" do
|
43
|
+
expect(converter.convert(0).to(:boolean)).to eq(false)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "converts boolean to integer" do
|
47
|
+
expect(converter.convert(true).to(:integer)).to eq(1)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when range' do
|
52
|
+
it "converts string to range" do
|
53
|
+
expect(converter.convert('1-10').to(:range)).to eq(1..10)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when array' do
|
58
|
+
it "converts string to array" do
|
59
|
+
expect(converter.convert("1,2,3").to(:array)).to eq([1,2,3])
|
60
|
+
end
|
61
|
+
|
62
|
+
it "converts array to numeric " do
|
63
|
+
expect(converter.convert(['1','2.3','3.0']).to(:numeric)).to eq([1,2.3,3.0])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|