necromancer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +19 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +141 -0
  9. data/Rakefile +8 -0
  10. data/lib/necromancer.rb +27 -0
  11. data/lib/necromancer/context.rb +45 -0
  12. data/lib/necromancer/conversion_target.rb +82 -0
  13. data/lib/necromancer/conversions.rb +75 -0
  14. data/lib/necromancer/converter.rb +37 -0
  15. data/lib/necromancer/converters/array.rb +50 -0
  16. data/lib/necromancer/converters/boolean.rb +58 -0
  17. data/lib/necromancer/converters/float.rb +28 -0
  18. data/lib/necromancer/converters/integer.rb +48 -0
  19. data/lib/necromancer/converters/range.rb +38 -0
  20. data/lib/necromancer/null_converter.rb +10 -0
  21. data/lib/necromancer/version.rb +5 -0
  22. data/necromancer.gemspec +21 -0
  23. data/spec/spec_helper.rb +45 -0
  24. data/spec/unit/can_spec.rb +11 -0
  25. data/spec/unit/conversions/register_spec.rb +50 -0
  26. data/spec/unit/convert_spec.rb +66 -0
  27. data/spec/unit/converters/array/array_to_numeric_spec.rb +22 -0
  28. data/spec/unit/converters/array/string_to_array_spec.rb +15 -0
  29. data/spec/unit/converters/boolean/boolean_to_integer_spec.rb +16 -0
  30. data/spec/unit/converters/boolean/integer_to_boolean_spec.rb +16 -0
  31. data/spec/unit/converters/boolean/string_to_boolean_spec.rb +96 -0
  32. data/spec/unit/converters/float/string_to_float_spec.rb +28 -0
  33. data/spec/unit/converters/integer/string_to_integer_spec.rb +38 -0
  34. data/spec/unit/converters/range/string_to_range_spec.rb +52 -0
  35. data/spec/unit/new_spec.rb +12 -0
  36. data/tasks/console.rake +10 -0
  37. data/tasks/coverage.rake +11 -0
  38. data/tasks/spec.rake +29 -0
  39. 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
@@ -0,0 +1,10 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ # A pass through converter
5
+ class NullConverter < Converter
6
+ def call(value, options = {})
7
+ value
8
+ end
9
+ end # NullConverter
10
+ end # Necromancer
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ VERSION = "0.1.0"
5
+ end # Necromancer
@@ -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
@@ -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