necromancer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|