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.
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