necromancer-0.5.1/0000755000175000017500000000000013620166405013654 5ustar gabstergabsternecromancer-0.5.1/tasks/0000755000175000017500000000000013620166405015001 5ustar gabstergabsternecromancer-0.5.1/tasks/spec.rake0000644000175000017500000000127113620166405016600 0ustar gabstergabster# frozen_string_literal: true begin require 'rspec/core/rake_task' desc 'Run all specs' RSpec::Core::RakeTask.new(:spec) do |task| task.pattern = 'spec/{unit,integration}{,/*/**}/*_spec.rb' end namespace :spec do desc 'Run unit specs' RSpec::Core::RakeTask.new(:unit) do |task| task.pattern = 'spec/unit{,/*/**}/*_spec.rb' end desc 'Run integration specs' RSpec::Core::RakeTask.new(:integration) do |task| 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`" end end end necromancer-0.5.1/tasks/coverage.rake0000644000175000017500000000033613620166405017442 0ustar gabstergabster# frozen_string_literal: true desc 'Measure code coverage' task :coverage do begin original, ENV['COVERAGE'] = ENV['COVERAGE'], 'true' Rake::Task['spec'].invoke ensure ENV['COVERAGE'] = original end end necromancer-0.5.1/tasks/console.rake0000644000175000017500000000032713620166405017311 0ustar gabstergabster# frozen_string_literal: true desc 'Load gem inside irb console' task :console do require 'irb' require 'irb/completion' require_relative '../lib/necromancer' ARGV.clear IRB.start end task :c => :console necromancer-0.5.1/spec/0000755000175000017500000000000013620166405014606 5ustar gabstergabsternecromancer-0.5.1/spec/unit/0000755000175000017500000000000013620166405015565 5ustar gabstergabsternecromancer-0.5.1/spec/unit/register_spec.rb0000644000175000017500000000073613620166405020756 0ustar gabstergabster# frozen_string_literal: true 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) value.to_s.upcase 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') end end necromancer-0.5.1/spec/unit/new_spec.rb0000644000175000017500000000031513620166405017714 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer, '#new' do subject(:converter) { described_class.new } it "creates context" do expect(converter).to be_a(Necromancer::Context) end end necromancer-0.5.1/spec/unit/inspect_spec.rb0000644000175000017500000000076113620166405020575 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer, '.inspect' do subject(:converter) { described_class.new } it "inspects converter instance" do expect(converter.inspect).to eq("#") end it "inspects conversion target" do conversion = converter.convert(11) expect(conversion.inspect).to eq("#") end end necromancer-0.5.1/spec/unit/converters/0000755000175000017500000000000013620166405017757 5ustar gabstergabsternecromancer-0.5.1/spec/unit/converters/range/0000755000175000017500000000000013620166405021053 5ustar gabstergabsternecromancer-0.5.1/spec/unit/converters/range/string_to_range_spec.rb0000644000175000017500000000146013620166405025577 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/numeric/0000755000175000017500000000000013620166405021421 5ustar gabstergabsternecromancer-0.5.1/spec/unit/converters/numeric/string_to_numeric_spec.rb0000644000175000017500000000131713620166405026514 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/numeric/string_to_integer_spec.rb0000644000175000017500000000276213620166405026514 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/numeric/string_to_float_spec.rb0000644000175000017500000000222113620166405026152 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/date_time/0000755000175000017500000000000013620166405021712 5ustar gabstergabsternecromancer-0.5.1/spec/unit/converters/date_time/string_to_time_spec.rb0000644000175000017500000000147113620166405026302 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/date_time/string_to_datetime_spec.rb0000644000175000017500000000167313620166405027144 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/date_time/string_to_date_spec.rb0000644000175000017500000000132413620166405026256 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/boolean/0000755000175000017500000000000013620166405021376 5ustar gabstergabsternecromancer-0.5.1/spec/unit/converters/boolean/string_to_boolean_spec.rb0000644000175000017500000000165513620166405026453 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/boolean/integer_to_boolean_spec.rb0000644000175000017500000000075713620166405026604 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/boolean/boolean_to_integer_spec.rb0000644000175000017500000000115413620166405026574 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/array/0000755000175000017500000000000013620166405021075 5ustar gabstergabsternecromancer-0.5.1/spec/unit/converters/array/string_to_array_spec.rb0000644000175000017500000000153113620166405025642 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/array/object_to_array_spec.rb0000644000175000017500000000071513620166405025605 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/array/array_to_set_spec.rb0000644000175000017500000000067413620166405025136 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/array/array_to_numeric_spec.rb0000644000175000017500000000126313620166405026000 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/converters/array/array_to_boolean_spec.rb0000644000175000017500000000127413620166405025757 0ustar gabstergabster# 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 necromancer-0.5.1/spec/unit/convert_spec.rb0000644000175000017500000000716713620166405020617 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer, '.convert' do subject(:converter) { described_class.new } it "indicates inability to perform the requested conversion" do expect { converter.convert(:foo).to(:float) }.to raise_error(Necromancer::NoTypeConversionAvailableError, /Conversion 'symbol->float' unavailable/) end it "allows for module level convert call" do expect(Necromancer.convert('1,2,3').to(:array)).to eq([1,2,3]) end it "allows replacing #to with #>> call" do expect(converter.convert('1,2,3') >> :array).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]) end it "allows to specify class as conversion target" do expect(converter.convert('1,2,3') >> Array).to eq([1,2,3]) end context 'when array' do it "converts string to array" do expect(converter.convert("1,2,3").to(:array)).to eq([1,2,3]) end it "converts array to numeric " do expect(converter.convert(['1','2.3','3.0']).to(:numeric)).to eq([1,2.3,3.0]) end it "converts array to boolean" do expect(converter.convert(['t', 'no']).to(:boolean)).to eq([true, false]) end it "converts object to array" do 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) }.to raise_error(Necromancer::ConversionTypeError) end end context 'when numeric' do it "converts string to integer" do 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) end it "convers integer to string" do expect(converter.convert(1).to(:string)).to eq('1') end it "allows for null type conversion" do expect(converter.convert(1).to(:integer)).to eq(1) end it "raises error when in strict mode" do expect { 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) end it "converts string to float" do 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) end end 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) end it "converts integer to boolean" do expect(converter.convert(0).to(:boolean)).to eq(false) end it "converts boolean to integer" do expect(converter.convert(true).to(:integer)).to eq(1) end end context 'when range' do it "converts string to range" do expect(converter.convert('1-10').to(:range)).to eq(1..10) end end 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')) 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')) end it "converts string to time" do expect(converter.convert('12:30').to(:time)). to eq(Time.parse('12:30')) end end end necromancer-0.5.1/spec/unit/conversions/0000755000175000017500000000000013620166405020135 5ustar gabstergabsternecromancer-0.5.1/spec/unit/conversions/to_hash_spec.rb0000644000175000017500000000155713620166405023131 0ustar gabstergabster# frozen_string_literal: true 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' ]) end end necromancer-0.5.1/spec/unit/conversions/register_spec.rb0000644000175000017500000000357213620166405023327 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer::Conversions, '.register' do it "allows to register converter" do context = described_class.new 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}) 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}) expect(context.register(converter)).to eq(true) expect(context[:string, :none]).to eq(converter) end it "allows to register anonymous converter" do conversions = described_class.new conversions.register do |c| c.source= :string c.target= :upcase c.convert = proc { |value| value.to_s.upcase } end expect(conversions[:string, :upcase].call('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) } end expect(conversions[String, Array].call('magic')).to eq(['magic']) end it "allows to register custom converter" do conversions = described_class.new UpcaseConverter = Struct.new(:source, :target) do def call(value) value.to_s.upcase 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') end end necromancer-0.5.1/spec/unit/conversions/fetch_spec.rb0000644000175000017500000000100013620166405022554 0ustar gabstergabster# frozen_string_literal: true 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) end it "fails to find conversion" do conversions = described_class.new expect { conversions['string', 'array'] }.to raise_error(Necromancer::NoTypeConversionAvailableError) end end necromancer-0.5.1/spec/unit/configuration/0000755000175000017500000000000013620166405020434 5ustar gabstergabsternecromancer-0.5.1/spec/unit/configuration/new_spec.rb0000644000175000017500000000115613620166405022567 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer::Configuration, '.new' do subject(:config) { described_class.new } it { is_expected.to respond_to(:strict=) } it { is_expected.to respond_to(:copy=) } it "is in non-strict mode by default" do expect(config.strict).to eq(false) end it "is in copy mode by default" do expect(config.copy).to eq(true) end it "allows to set strict through method" do config.strict true expect(config.strict).to eq(true) end it "allows to set copy mode through method" do config.copy false expect(config.strict).to eq(false) end end necromancer-0.5.1/spec/unit/config_spec.rb0000644000175000017500000000144313620166405020373 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer, 'config' do it "configures global settings per instance" do converter = described_class.new converter.configure do |config| config.strict false end expect(converter.convert("1.2.3").to(:array)).to eq(["1.2.3"]) converter.configure do |config| config.strict true end expect { converter.convert("1.2.3").to(:array) }.to raise_error(Necromancer::ConversionTypeError) end it "configures global settings through instance block" do converter = described_class.new do |config| config.strict true end expect(converter.configuration.strict).to eq(true) expect { converter.convert("1.2.3").to(:array) }.to raise_error(Necromancer::ConversionTypeError) end end necromancer-0.5.1/spec/unit/can_spec.rb0000644000175000017500000000042413620166405017665 0ustar gabstergabster# frozen_string_literal: true RSpec.describe Necromancer, 'can?' do it "checks if conversion is possible" do converter = described_class.new expect(converter.can?(:string, :integer)).to eq(true) expect(converter.can?(:unknown, :integer)).to eq(false) end end necromancer-0.5.1/spec/spec_helper.rb0000644000175000017500000000231613620166405017426 0ustar gabstergabster# frozen_string_literal: true if ENV['COVERAGE'] || ENV['TRAVIS'] require 'simplecov' require 'coveralls' SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ]) SimpleCov.start do command_name 'spec' add_filter 'spec' end end require 'necromancer' RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end # Limits the available syntax to the non-monkey patched syntax that is recommended. config.disable_monkey_patching! # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. config.warnings = true if config.files_to_run.one? config.default_formatter = 'doc' end config.profile_examples = 2 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 necromancer-0.5.1/necromancer.gemspec0000644000175000017500000000276513620166405017527 0ustar gabstergabsterlib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'necromancer/version' Gem::Specification.new do |spec| spec.name = 'necromancer' spec.version = Necromancer::VERSION spec.authors = ['Piotr Murach'] spec.email = ['me@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' 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" spec.metadata["documentation_uri"] = "https://www.rubydoc.info/gems/necromancer" 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.require_paths = ["lib"] 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' end necromancer-0.5.1/lib/0000755000175000017500000000000013620166405014422 5ustar gabstergabsternecromancer-0.5.1/lib/necromancer/0000755000175000017500000000000013620166405016716 5ustar gabstergabsternecromancer-0.5.1/lib/necromancer/version.rb0000644000175000017500000000013013620166405020722 0ustar gabstergabster# frozen_string_literal: true module Necromancer VERSION = "0.5.1" end # Necromancer necromancer-0.5.1/lib/necromancer/null_converter.rb0000644000175000017500000000035213620166405022304 0ustar gabstergabster# frozen_string_literal: true require_relative 'converter' module Necromancer # A pass through converter class NullConverter < Converter def call(value, **options) value end end # NullConverter end # Necromancer necromancer-0.5.1/lib/necromancer/converters/0000755000175000017500000000000013620166405021110 5ustar gabstergabsternecromancer-0.5.1/lib/necromancer/converters/range.rb0000644000175000017500000000253113620166405022532 0ustar gabstergabster# frozen_string_literal: true require_relative '../converter' require_relative '../null_converter' module Necromancer # Container for Range converter classes module RangeConverters SINGLE_DIGIT_MATCHER = /^(\-?\d+)$/.freeze DIGIT_MATCHER = /^(-?\d+?)(\.{2}\.?|-|,)(-?\d+)$/.freeze LETTER_MATCHER = /^(\w)(\.{2}\.?|-|,)(\w)$/.freeze # An object that converts a String to a Range class StringToRangeConverter < Converter # Convert value to Range type with possible ranges # # @param [Object] value # # @example # converter.call('0,9') # => (0..9) # # @example # 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 == '...') else strict ? raise_conversion_type(value) : value end end end def self.load(conversions) conversions.register StringToRangeConverter.new(:string, :range) conversions.register NullConverter.new(:range, :range) end end # RangeConverters end # Necromancer necromancer-0.5.1/lib/necromancer/converters/numeric.rb0000644000175000017500000000516713620166405023110 0ustar gabstergabster# frozen_string_literal: true require_relative '../converter' require_relative '../null_converter' module Necromancer # Container for Numeric converter classes module NumericConverters INTEGER_MATCHER = /^[-+]?(\d+)$/.freeze FLOAT_MATCHER = /^[-+]?(\d*)(\.\d+)?([eE]?[-+]?\d+)?$/.freeze # An object that converts a String to an Integer class StringToIntegerConverter < Converter # Convert string value to integer # # @example # converter.call('1abc') # => 1 # # @api public def call(value, **options) strict = options.fetch(:strict, config.strict) Integer(value) rescue strict ? raise_conversion_type(value) : value.to_i end end # An object that converts an Integer to a String class IntegerToStringConverter < Converter # Convert integer value to string # # @example # converter.call(1) # => '1' # # @api public def call(value, **_) value.to_s end end # An object that converts a String to a Float class StringToFloatConverter < Converter # Convert string to float value # # @example # converter.call('1.2') # => 1.2 # # @api public def call(value, **options) strict = options.fetch(:strict, config.strict) Float(value) rescue strict ? raise_conversion_type(value) : value.to_f end end # An object that converts a String to a Numeric class StringToNumericConverter < Converter # Convert string to numeric value # # @example # converter.call('1.0') # => 1.0 # # @example # converter.call('1') # => 1 # # @api public def call(value, **options) strict = options.fetch(:strict, config.strict) case value when INTEGER_MATCHER StringToIntegerConverter.new(:string, :integer).call(value, **options) when FLOAT_MATCHER StringToFloatConverter.new(:string, :float).call(value, **options) else strict ? raise_conversion_type(value) : value end end 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) end end # Conversion end # Necromancer necromancer-0.5.1/lib/necromancer/converters/date_time.rb0000644000175000017500000000474413620166405023401 0ustar gabstergabster# frozen_string_literal: true require 'date' require 'time' require_relative '../converter' require_relative '../null_converter' module Necromancer # Container for Date converter classes module DateTimeConverters # An object that converts a String to a Date class StringToDateConverter < Converter # Convert a string value to a Date # # @example # converter.call("1-1-2015") # => "2015-01-01" # converter.call("01/01/2015") # => "2015-01-01" # converter.call("2015-11-12") # => "2015-11-12" # converter.call("12/11/2015") # => "2015-11-12" # # @api public def call(value, options = {}) strict = options.fetch(:strict, config.strict) Date.parse(value) rescue strict ? raise_conversion_type(value) : value end end # An object that converts a String to a DateTime class StringToDateTimeConverter < Converter # Convert a string value to a DateTime # # @example # converer.call("1-1-2015") # => "2015-01-01T00:00:00+00:00" # 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) DateTime.parse(value) rescue strict ? raise_conversion_type(value) : value end end class StringToTimeConverter < Converter # Convert a String value to a Time value # # @param [String] value # the value to convert # # @example # converter.call("01-01-2015") # => 2015-01-01 00:00:00 +0100 # converter.call("01-01-2015 08:35") # => 2015-01-01 08:35:00 +0100 # converter.call("12:35") # => 2015-01-04 12:35:00 +0100 # # @api public def call(value, options = {}) strict = options.fetch(:strict, config.strict) Time.parse(value) rescue 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) end end # DateTimeConverters end # Necromancer necromancer-0.5.1/lib/necromancer/converters/boolean.rb0000644000175000017500000000515213620166405023057 0ustar gabstergabster# frozen_string_literal: true require_relative '../converter' require_relative '../null_converter' module Necromancer # Container for Boolean converter classes module BooleanConverters TRUE_MATCHER = /^(yes|y|on|t(rue)?|1)$/i.freeze FALSE_MATCHER = /^(no|n|off|f(alse)?|0)$/i.freeze # An object that converts a String to a Boolean class StringToBooleanConverter < Converter # Convert value to boolean type including range of strings # # @param [Object] value # # @example # converter.call("True") # => true # # other values converted to true are: # 1, t, T, TRUE, true, True, y, Y, YES, yes, Yes, on, ON # # @example # converter.call("False") # => false # # other values coerced to false are: # 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) case value.to_s when TRUE_MATCHER then true when FALSE_MATCHER then false else strict ? raise_conversion_type(value) : value end end end # An object that converts an Integer to a Boolean class IntegerToBooleanConverter < Converter # Convert integer to boolean # # @example # converter.call(1) # => true # # @example # 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 end end # An object that converts a Boolean to an Integer class BooleanToIntegerConverter < Converter # Convert boolean to integer # # @example # converter.call(true) # => 1 # # @example # converter.call(false) # => 0 # # @api public def call(value, options = {}) strict = options.fetch(:strict, config.strict) if %w[TrueClass FalseClass].include?(value.class.name) value ? 1 : 0 else strict ? raise_conversion_type(value) : value end end 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) end end # BooleanConverters end # Necromancer necromancer-0.5.1/lib/necromancer/converters/array.rb0000644000175000017500000000676613620166405022572 0ustar gabstergabster# frozen_string_literal: true require 'set' require_relative '../converter' module Necromancer # Container for Array converter classes module ArrayConverters # 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'] # # @example # 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) else strict ? raise_conversion_type(value) : Array(value) end end end # An object that converts an array to an array with numeric values class ArrayToNumericConverter < Converter # Convert an array to an array of numeric values # # @example # 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 end end # An object that converts an array to an array with boolean values class ArrayToBooleanConverter < 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, options = {}) boolean_converter = BooleanConverters::StringToBooleanConverter.new(:string, :boolean) value.reduce([]) do |acc, el| acc << boolean_converter.call(el, options) end end end # An object that converts an object to an array class ObjectToArrayConverter < Converter # Convert an object to an array # # @example # 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 end end # An object that converts an array to a set class ArrayToSetConverter < Converter # Convert an array to a set # # @example # converter.call([:x, :y, :x, 1, 2, 1]) # => # # @param [Array] value # 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 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) end end # ArrayConverters end # Necromancer necromancer-0.5.1/lib/necromancer/converter.rb0000644000175000017500000000255613620166405021262 0ustar gabstergabster# frozen_string_literal: true require_relative 'configuration' module Necromancer # Abstract converter used internally as a base for other converters # # @api private class Converter # Create an abstract converter # # @param [Object] source # the source object type # # @param [Object] target # the target object type # # @api public def initialize(source = nil, target = nil) @source = source if source @target = target if target @config ||= Configuration.new end # Run converter # # @api private def call(*) raise NotImplementedError end # Creates anonymous converter # # @api private def self.create(&block) Class.new(self) do define_method(:initialize) { |*a| block.call(self, *a) } define_method(:call) { |value| convert.call(value) } end.new end # Fail with conversion type error # # @param [Object] value # the value that cannot be converted # # @api private def raise_conversion_type(value) raise ConversionTypeError, "'#{value}' could not be converted " \ "from `#{source}` into `#{target}` " end attr_accessor :source attr_accessor :target attr_accessor :convert protected attr_reader :config end # Converter end # Necromancer necromancer-0.5.1/lib/necromancer/conversions.rb0000644000175000017500000000604213620166405021615 0ustar gabstergabster# 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' module Necromancer # Represents the context used to configure various converters # and facilitate type conversion # # @api public class Conversions DELIMITER = '->'.freeze # Creates a new conversions map # # @example # conversion = Necromancer::Conversions.new # # @api public def initialize(configuration = Configuration.new, map = {}) @configuration = configuration @converter_map = map.dup end # Load converters # # @api private def load ArrayConverters.load(self) BooleanConverters.load(self) DateTimeConverters.load(self) NumericConverters.load(self) RangeConverters.load(self) end # Retrieve converter for source and target # # @param [Object] source # the source of conversion # # @param [Object] target # the target of conversion # # @return [Converter] # the converter for the source and target # # @api public def [](source, target) key = "#{source}#{DELIMITER}#{target}" converter_map[key] || converter_map["object#{DELIMITER}#{target}"] || raise_no_type_conversion_available(key) end alias fetch [] # Register a converter # # @example with simple object # conversions.register NullConverter.new(:array, :array) # # @example with block # conversions.register do |c| # c.source = :array # c.target = :array # c.convert = -> { |val, options| val } # end # # @api public def register(converter = nil, &block) converter ||= Converter.create(&block) key = generate_key(converter) converter = add_config(converter, @configuration) return false if converter_map.key?(key) converter_map[key] = converter true end # Export all the conversions as hash # # @return [Hash[String, String]] # # @api public def to_hash converter_map.dup end protected # Fail with conversion error # # @api private def raise_no_type_conversion_available(key) raise NoTypeConversionAvailableError, "Conversion '#{key}' unavailable." end # @api private def generate_key(converter) parts = [] parts << (converter.source ? converter.source.to_s : 'none') parts << (converter.target ? converter.target.to_s : 'none') parts.join(DELIMITER) end # Inject config into converter # # @api private def add_config(converter, config) converter.instance_exec(:"@config") do |var| instance_variable_set(var, config) end converter end # Map of type and conversion # # @return [Hash] # # @api private attr_reader :converter_map end # Conversions end # Necromancer necromancer-0.5.1/lib/necromancer/conversion_target.rb0000644000175000017500000000435613620166405023006 0ustar gabstergabster# frozen_string_literal: true module Necromancer # A class responsible for wrapping conversion target # # @api private class ConversionTarget # Used as a stand in for lack of value # @api private UndefinedValue = Module.new # @api private def initialize(conversions, object) @object = object @conversions = conversions end # @api private def self.for(context, value, block) if UndefinedValue.equal?(value) unless block raise ArgumentError, '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`.' else new(context, value) end end # Allows to specify conversion source type # # @example # converter.convert('1').from(:string).to(:numeric) # => 1 # # @return [ConversionType] # # @api public def from(source) @source = source self end # Runs a given conversion # # @example # converter.convert('1').to(:numeric) # => 1 # # @example # converter.convert('1') >> Integer # => 1 # # @return [Object] # the converted target type # # @api public def to(target, options = {}) conversion = conversions[source || detect(object, false), detect(target)] conversion.call(object, **options) end alias >> to # Inspect this conversion # # @api public def inspect %(#<#{self.class}@#{object_id} @object=#{object}, @source=#{detect(object)}>) end protected # Detect object type and coerce into known key type # # @param [Object] object # # @api private def detect(object, symbol_as_object = true) case object when TrueClass, FalseClass then :boolean when Integer then :integer when Class then object.name.downcase else if object.is_a?(Symbol) && symbol_as_object object else object.class.name.downcase end end end attr_reader :object attr_reader :conversions attr_reader :source end # ConversionTarget end # Necromancer necromancer-0.5.1/lib/necromancer/context.rb0000644000175000017500000000367113620166405020736 0ustar gabstergabster# frozen_string_literal: true require 'forwardable' require_relative 'configuration' require_relative 'conversions' require_relative 'conversion_target' module Necromancer # A class used by Necromancer to provide user interace # # @api public class Context extend Forwardable def_delegators :"@conversions", :register # Create a context. # # @api private def initialize(&block) block.call(configuration) if block_given? @conversions = Conversions.new(configuration) @conversions.load end # The configuration object. # # @example # converter = Necromancer.new # converter.configuration.strict = true # # @return [Necromancer::Configuration] # # @api public def configuration @configuration ||= Configuration.new end # Yields global configuration to a block. # # @yield [Necromancer::Configuration] # # @example # converter = Necromancer.new # converter.configure do |config| # config.strict true # end # # @api public def configure yield configuration if block_given? end # Converts the object # @param [Object] object # any object to be converted # # @api public def convert(object = ConversionTarget::UndefinedValue, &block) ConversionTarget.for(conversions, object, block) end # Check if this converter can convert source to target # # @param [Object] source # the source class # @param [Object] target # the target class # # @return [Boolean] # # @api public def can?(source, target) !conversions[source, target].nil? rescue NoTypeConversionAvailableError false end # Inspect this context # # @api public def inspect %(#<#{self.class}@#{object_id} @config=#{configuration}>) end protected attr_reader :conversions end # Context end # Necromancer necromancer-0.5.1/lib/necromancer/configuration.rb0000644000175000017500000000154513620166405022117 0ustar gabstergabster# frozen_string_literal: true module Necromancer # A global configuration for converters. # # Used internally by {Necromancer::Context}. # # @api private class Configuration # Configure global strict mode # # @api public attr_writer :strict # Configure global copy mode # # @api public attr_writer :copy # Create a configuration # # @api private def initialize @strict = false @copy = true end # Set or get strict mode # # @return [Boolean] # # @api public def strict(value = (not_set = true)) not_set ? @strict : (self.strict = value) end # Set or get copy mode # # @return [Boolean] # # @api public def copy(value = (not_set = true)) not_set ? @copy : (self.copy = value) end end # Configuration end # Necromancer necromancer-0.5.1/lib/necromancer.rb0000644000175000017500000000146113620166405017245 0ustar gabstergabster# frozen_string_literal: true require_relative 'necromancer/context' require_relative 'necromancer/version' module Necromancer # Raised when cannot conver to a given type ConversionTypeError = Class.new(StandardError) # Raised when conversion type is not available NoTypeConversionAvailableError = Class.new(StandardError) # Create a conversion instance # # @example # converter = Necromancer.new # # @return [Context] # # @api private def new(&block) Context.new(&block) end module_function :new # Convenience to directly call conversion # # @example # Necromancer.convert('1').to(:integer) # # @return [ConversionTarget] # # @api public def convert(*args, &block) Context.new.convert(*args, &block) end module_function :convert end # Necromancer necromancer-0.5.1/Rakefile0000644000175000017500000000021713620166405015321 0ustar gabstergabsterrequire 'bundler/gem_tasks' FileList['tasks/**/*.rake'].each(&method(:import)) desc 'Run all specs' task ci: %w[ spec ] task default: :spec necromancer-0.5.1/README.md0000644000175000017500000003231513620166405015137 0ustar gabstergabster# Necromancer [![Gem Version](https://badge.fury.io/rb/necromancer.svg)][gem] [![Build Status](https://secure.travis-ci.org/piotrmurach/necromancer.svg?branch=master)][travis] [![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 [appveyor]: https://ci.appveyor.com/project/piotrmurach/necromancer [codeclimate]: https://codeclimate.com/github/piotrmurach/necromancer [coverage]: https://coveralls.io/github/piotrmurach/necromancer [inchpages]: http://inch-ci.org/github/piotrmurach/necromancer > Conversion from one object type to another with a bit of black magic. **Necromancer** provides independent type conversion component for [TTY](https://github.com/piotrmurach/tty) toolkit. ## Motivation Conversion between Ruby core types frequently comes up in projects but is solved by half-baked solutions. This library aims to provide an independent and extensible API to support a robust and generic way to convert between core Ruby types. ## Features * Simple and expressive API * Ability to specify own converters * Ability to compose conversions out of simpler ones * Support conversion of custom defined types * Ability to specify strict conversion mode ## Installation Add this line to your application's Gemfile: ```ruby gem 'necromancer' ``` And then execute: $ bundle Or install it yourself as: $ gem install necromancer ## Contents * [1. Usage](#1-usage) * [2. Interface](#2-interface) * [2.1 convert](#21-convert) * [2.2 from](#22-from) * [2.3 to](#23-to) * [2.4 can?](#24-can) * [2.5 configure](#25-configure) * [3. Converters](#3-converters) * [3.1 Array](#31-array) * [3.2 Boolean](#32-boolean) * [3.3 DateTime](#33-datetime) * [3.4 Hash](#34-hash) * [3.5 Numeric](#35-numeric) * [3.6 Range](#36-range) * [3.7 Custom](#37-custom) * [3.7.1 Using an Object](#371-using-an-object) * [3.7.2 Using a Proc](#372-using-a-proc) ## 1. Usage **Necromancer** knows how to handle conversions between various types using the `convert` method. The `convert` method takes as an argument the value to convert from. Then to perform actual coercion use the `to` or more functional style `>>` method that accepts the type for the returned value which can be `:symbol`, `object` or `ClassName`. 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 ``` In order to handle [boolean](#32-boolean) conversions: ```ruby 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 ``` or convert [array](#31-array) of string values into numeric type: ```ruby Necromancer.convert(['1', '2.3', '3.0']).to(:numeric) # => [1, 2.3, 3.0] ``` To provide extra information about the conversion value type you can use `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: ```ruby Necromancer.convert(:foo).to(:float) # => Necromancer::NoTypeConversionAvailableError: Conversion 'foo->float' unavailable. ``` ## 2. Interface **Necromancer** will perform conversions on the supplied object through use of `convert`, `from` and `to` methods. ### 2.1 convert 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 ``` Alternatively, you can use block: ```ruby 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: ```ruby Necromancer.convert(:foo).to(:float) # => Necromancer::NoTypeConversionAvailableError: Conversion 'foo->float' unavailable. ``` ### 2.2 from To specify conversion source type use `from` method: ```ruby 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 ### 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 ``` 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: ```ruby Necromancer.convert('1a').to(:integer, strict: true) # => raises Necromancer::ConversionTypeError ``` The target parameters are: * :array * :boolean * :date * :datetime * :float * :integer * :numeric * :range * :string * :time ### 2.4 can? To verify that a given conversion can be handled by **Necromancer** call `can?` with the `source` and `target` of the desired conversion. ```ruby converter = Necromancer.new converter.can?(:string, :integer) # => true converter.can?(:unknown, :integer) # => false ``` ### 2.5 configure You may set global configuration options on **Necromancer** instance by passing a block like so: ```ruby Necromancer.new do |config| config.strict true end ``` or calling `configure` method: ```ruby converter = Necromancer.new converter.configure do |config| config.copy false end ``` Available configuration options are: * 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`. ### 3.1 Array The **Necromancer** allows you to transform arbitrary object into array: ```ruby Necromancer.convert(nil).to(:array) # => [] 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'] ``` 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] ``` You can also convert array containing string objects to array containing numeric values: ```ruby Necromancer.convert(['1', '2.3', '3.0']).to(:numeric) ``` 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` ``` However, in `non-strict` mode the value will be simply returned unchanged: ```ruby Necromancer.convert(['1', '2.3', false]).to(:numeric, 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`. ```ruby 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`. ```ruby Necromancer.convert('no').to(:boolean) # => false ``` You can also convert an integer object to boolean: ```ruby Necromancer.convert(1).to(:boolean) # => true Necromancer.convert(0).to(:boolean) # => false ``` ### 3.3 DateTime **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" ``` You can also convert string to `datetime`: ```ruby Necromancer.convert("1-1-2015").to(:datetime) # => "2015-01-01T00:00:00+00:00" Necromancer.convert("1-1-2015 15:12:44").to(:datetime) # => "2015-01-01T15:12:44+00:00" ``` To convert a string to a time instance do: ```ruby Necromancer.convert("01-01-2015").to(:time) # => 2015-01-01 00:00:00 +0100 Necromancer.convert("01-01-2015 08:35").to(:time) # => 2015-01-01 08:35:00 +0100 Necromancer.convert("12:35").to(:time) # => 2015-01-04 12:35:00 +0100 ``` ### 3.4 Hash ```ruby Necromancer.convert({ x: '27.5', y: '4', z: '11'}).to(:numeric) # => { x: 27.5, y: 4, z: 11} ``` ### 3.5 Numeric **Necromancer** comes ready to convert all the primitive numeric values. To convert a string to a float do: ```ruby 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 ``` To convert a string to an integer do: ```ruby 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 ``` ### 3.6 Range **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 ``` or to create a range of letters: ```ruby Necromancer.convert('a-z').to(: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`. #### 3.7.1 Using an Object Firstly, you need to create a converter that at minimum requires to specify `call` method that will be invoked during conversion: ```ruby UpcaseConverter = Struct.new(:source, :target) do def call(value, options = {}) value.upcase end end ``` Inside the `UpcaseConverter` you have access to global configuration options by directly calling `config` method. Then you need to specify what type conversions this converter will support. For example, `UpcaseConverter` will allow a string object to be converted to a new string object with content upper cased. This can be done: ```ruby upcase_converter = UpcaseConverter.new(:string, :upcase) ``` **Necromancer** provides the `register` method to add converter: ```ruby converter = Necromancer.new 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' ``` #### 3.7.2 Using a Proc Using a Proc object you can create and immediately register a converter. You need to pass `source` and `target` of the conversion that will be used later on to match the conversion. The `convert` allows you to specify the actual conversion in block form. For example: ```ruby converter = Necromancer.new converter.register do |c| c.source= :string c.target= :upcase c.convert = proc { |value, options| value.upcase } 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' ``` ## Contributing Bug reports and pull requests are welcome on GitHub at https://github.com/piotrmurach/necromancer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. 1. Fork it ( https://github.com/piotrmurach/necromancer/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request ## License The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). ## Copyright Copyright (c) 2014 Piotr Murach. See LICENSE for further details. necromancer-0.5.1/LICENSE.txt0000644000175000017500000000205513620166405015501 0ustar gabstergabsterCopyright (c) 2014 Piotr Murach MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. necromancer-0.5.1/CHANGELOG.md0000644000175000017500000000334513620166405015472 0ustar gabstergabster# Change log ## [v0.5.1] - 2019-11-24 ### Changed * Change gemspec to include metadata ### Fixed * Fix Ruby 2.7 warnings by Ryan Davis(@zenspider) ## [v0.5.0] - 2019-03-23 ### Changed * Change to use Ruby >= 2.0.0 * Change to load files directly without git * Change to raise on error in place of fail ## [v0.4.0] - 2017-02-18 ### Added * Add :string -> :time conversion * Add inspection methods to Context and ConversionTarget * Add module level Necromancer#convert for convenience and more functional style * Add ConversionTarget#>> call for functional style converions ### Changed * Change fail to raise in ConversionTarget#for * Change fail to raise in Conversions * Change ConversionTarget#detect to handle Class type coercion ### Fixed * Fix bug with type detection * Fix Ruby 2.4.0 warning about Fixnum & Bignum type ## [v0.3.0] - 2014-12-14 ### Added * Add array converters for :hash, :set and :object conversions * Add ability to configure global conversion settings per instance ## [v0.2.0] - 2014-12-07 ### Added * Add #fail_conversion_type to Converter and use in converters * Add DateTimeConverters * Add string to numeric type conversion ### Changed * Change IntegerConverters & FloatConverters into Numeric Converters ## [v0.1.0] - 2014-11-30 * Initial implementation and release [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 [v0.3.0]: https://github.com/piotrmurach/necromancer/compare/v0.2.0...v0.3.0 [v0.2.0]: https://github.com/piotrmurach/necromancer/compare/v0.1.0...v0.2.0 [v0.1.0]: https://github.com/piotrmurach/necromancer/compare/v0.1.0