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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9c37da594bf2020bd875ad6a46514c1d20b7d23
4
+ data.tar.gz: f351c2bbbd62d9f4780306897ea5f09983607af7
5
+ SHA512:
6
+ metadata.gz: 71b242fd171277c210324744d09d80702af4f718c675f747b997bfb8ec1bb9525c2f9a4bb4de5c6a5de6a2605248e02b576956a31cfd3cc7fb51ea2bb6bf1988
7
+ data.tar.gz: 2a0cfcb08fb44936ad433903a2d12024d31f615b8fafcbc3100c2a0ab56c253e6d96c9432373ce1c9cd74a47bab2ab948bcae886874824936885641fbeeb54f8
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --warnings
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ bundler_args: --without yard benchmarks
3
+ script: "bundle exec rake ci"
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - ruby-head
9
+ - jruby-19mode
10
+ - rbx-2
11
+ matrix:
12
+ include:
13
+ - rvm: jruby-head
14
+ allow_failures:
15
+ - rvm: ruby-head
16
+ - rvm: jruby-head
17
+ fast_finish: true
18
+ branches:
19
+ only: master
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake', '~> 10.3.2'
7
+ gem 'rspec', '~> 3.1.0'
8
+ gem 'yard', '~> 0.8.7'
9
+ gem 'benchmark-ips', '~> 2.0.0'
10
+ end
11
+
12
+ group :metrics do
13
+ gem 'coveralls', '~> 0.7.0'
14
+ gem 'simplecov', '~> 0.8.2'
15
+ gem 'yardstick', '~> 0.9.9'
16
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Piotr Murach
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # Necromancer
2
+ [![Gem Version](https://badge.fury.io/rb/necromancer.png)][gem]
3
+ [![Build Status](https://secure.travis-ci.org/peter-murach/necromancer.png?branch=master)][travis]
4
+ [![Code Climate](https://codeclimate.com/github/peter-murach/necromancer.png)][codeclimate]
5
+ [![Coverage Status](https://coveralls.io/repos/peter-murach/necromancer/badge.png)][coverage]
6
+
7
+ [gem]: http://badge.fury.io/rb/necromancer
8
+ [travis]: http://travis-ci.org/peter-murach/necromancer
9
+ [codeclimate]: https://codeclimate.com/github/peter-murach/necromancer
10
+ [coverage]: https://coveralls.io/r/peter-murach/necromancer
11
+
12
+ > Conversion from one object type to another with a bit of black magic.
13
+
14
+ **Necromancer** provides independent type conversion component for [TTY](https://github.com/peter-murach/tty) toolkit.
15
+
16
+ ## Features
17
+
18
+ * Simple and expressive API
19
+ * Ability to specify own converters
20
+ * Support conversion of custom defined types
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem 'necromancer'
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ $ bundle
33
+
34
+ Or install it yourself as:
35
+
36
+ $ gem install necromancer
37
+
38
+ ## Contents
39
+
40
+ * [1. Usage](#1-usage)
41
+ * [2. Interface](#2-interface)
42
+ * [2.1 convert](#21-convert)
43
+ * [2.1 can?](#22-can)
44
+ * [3. Converters](#3-converters)
45
+
46
+ ## 1. Usage
47
+
48
+ **Necromancer** requires you to instatiate it like so:
49
+
50
+ ```ruby
51
+ converter = Necromancer.new
52
+ ```
53
+
54
+ Once initialize **Necromancer** knows how to handle numerous conversions.
55
+
56
+ For example, to convert a string to a range type:
57
+
58
+ ```ruby
59
+ converter.convert('1-10').to(:range) # => 1..10
60
+ ```
61
+
62
+ In order to handle boolean conversions:
63
+
64
+ ```ruby
65
+ converter.convert('t').to(:boolean) # => true
66
+ ```
67
+
68
+ or get array elements into numeric type:
69
+
70
+ ```ruby
71
+ converter.convert(['1', '2.3', '3.0']).to(:numeric) # => [1, 2.3, 3.0]
72
+ ```
73
+
74
+ However, if you want to tell **Necromancer** about source type use `from`:
75
+
76
+ ```ruby
77
+ converter.convert(['1', '2.3', '3.0']).from(:array).to(:numeric) # => [1, 2.3, 3.0]
78
+ ```
79
+
80
+ ## 2. Interaface
81
+
82
+ ### 2.1 convert
83
+
84
+ ### 2.2 can?
85
+
86
+ To verify that a a given conversion can be handled by **Necormancer** call `can?` with the `source` and `target` of the desired conversion.
87
+
88
+ ```ruby
89
+ converter = Necromancer.new
90
+ converter.can?(:string, :integer) # => true
91
+ converter.can?(:unknown, :integer) # => false
92
+ ```
93
+
94
+ ## 3. Converters
95
+
96
+ ### 3.1 Custom
97
+
98
+ ### 3.2 Array
99
+
100
+ ```ruby
101
+ converter.convert(['1', '2.3', '3.0']).to(:numeric)
102
+ ```
103
+
104
+ ```ruby
105
+ converter.convert(['1', '2.3', '3.0']).from(:array).to(:numeric)
106
+ ```
107
+
108
+ Raises error when in strict mode
109
+
110
+ ```ruby
111
+ converter.convert(['1', '2.3', false]).from(:array).to(:numeric)
112
+ # => Necromancer::ConversionError: false cannot be converter to numeric value
113
+ ```
114
+
115
+ Returns value when in non strict mode
116
+
117
+ ```ruby
118
+ converter.convert(['1', '2.3', false]).from(:array).to(:numeric)
119
+ # => [1, 2.3, false]
120
+ ```
121
+
122
+ ### 3.3 Range
123
+
124
+ ### 3.4 Hash
125
+
126
+ ```ruby
127
+ converter.convert({ x: '27.5', y: '4', z: '11'}).to(:numeric)
128
+ # => { x: 27.5, y: 4, z: 11}
129
+ ```
130
+
131
+ ## Contributing
132
+
133
+ 1. Fork it ( https://github.com/peter-murach/necromancer/fork )
134
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
135
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
136
+ 4. Push to the branch (`git push origin my-new-feature`)
137
+ 5. Create a new Pull Request
138
+
139
+ ## Copyright
140
+
141
+ Copyright (c) 2014 Piotr Murach. See LICENSE for further details.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # coding: utf-8
2
+
3
+ require 'bundler/gem_tasks'
4
+
5
+ FileList['tasks/**/*.rake'].each(&method(:import))
6
+
7
+ desc 'Run all specs'
8
+ task ci: %w[ spec ]
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+
3
+ require 'necromancer/conversions'
4
+ require 'necromancer/context'
5
+ require 'necromancer/converter'
6
+ require 'necromancer/null_converter'
7
+ require 'necromancer/converters/array'
8
+ require 'necromancer/converters/boolean'
9
+ require 'necromancer/converters/float'
10
+ require 'necromancer/converters/integer'
11
+ require 'necromancer/converters/range'
12
+ require 'necromancer/conversion_target'
13
+ require 'necromancer/version'
14
+
15
+ module Necromancer
16
+ # Raised when cannot conver to a given type
17
+ ConversionTypeError = Class.new(StandardError)
18
+
19
+ # Raised when conversion type is not available
20
+ NoTypeConversionAvailableError = Class.new(StandardError)
21
+
22
+ def new
23
+ Context.new
24
+ end
25
+
26
+ module_function :new
27
+ end # Necromancer
@@ -0,0 +1,45 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ # A class used by Necromancer to provide user interace
5
+ #
6
+ # @api public
7
+ class Context
8
+ # Create a context
9
+ #
10
+ # @api private
11
+ def initialize
12
+ @conversions = Conversions.new
13
+ @conversions.load
14
+ end
15
+
16
+ # Converts the object
17
+ # @param [Object] value
18
+ # any object to be converted
19
+ #
20
+ # @api public
21
+ def convert(object = ConversionTarget::UndefinedValue, &block)
22
+ ConversionTarget.for(conversions, object, block)
23
+ end
24
+
25
+ # Check if this converter can convert source to target
26
+ #
27
+ # @param [Object] source
28
+ # the source class
29
+ # @param [Object] target
30
+ # the target class
31
+ #
32
+ # @return [Boolean]
33
+ #
34
+ # @api public
35
+ def can?(source, target)
36
+ !conversions[source, target].nil?
37
+ rescue NoTypeConversionAvailableError
38
+ false
39
+ end
40
+
41
+ protected
42
+
43
+ attr_reader :conversions
44
+ end # Context
45
+ end # Necromancer
@@ -0,0 +1,82 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ # A class responsible for wrapping conversion target
5
+ #
6
+ # @api private
7
+ class ConversionTarget
8
+ # Used as a stand in for lack of value
9
+ # @api private
10
+ UndefinedValue = Module.new
11
+
12
+ # @api private
13
+ def initialize(conversions, object)
14
+ @object = object
15
+ @conversions = conversions
16
+ end
17
+
18
+ # @api private
19
+ def self.for(context, value, block)
20
+ if UndefinedValue.equal?(value)
21
+ unless block
22
+ fail ArgumentError,
23
+ 'You need to pass either argument or a block to `convert`.'
24
+ end
25
+ new(context, block.call)
26
+ elsif block
27
+ fail ArgumentError,
28
+ 'You cannot pass both an argument and a block to `convert`.'
29
+ else
30
+ new(context, value)
31
+ end
32
+ end
33
+
34
+ # Allows to specify conversion source type
35
+ #
36
+ # @example
37
+ # converter.convert('1').from(:string).to(:numeric) # => 1
38
+ #
39
+ # @return [ConversionType]
40
+ #
41
+ # @api public
42
+ def from(source)
43
+ @source = source
44
+ self
45
+ end
46
+
47
+ # Runs a given conversion
48
+ #
49
+ # @example
50
+ # converter.convert('1').to(:numeric) # => 1
51
+ #
52
+ # @return [Object]
53
+ # the converted target type
54
+ #
55
+ # @api public
56
+ def to(target, options = {})
57
+ conversion = conversions[source || detect(object), detect(target)]
58
+ conversion.call(object, options)
59
+ end
60
+
61
+ protected
62
+
63
+ # Detect object type
64
+ #
65
+ # @api private
66
+ def detect(object)
67
+ case object
68
+ when TrueClass, FalseClass then :boolean
69
+ when Fixnum, Bignum then :integer
70
+ when Symbol then object
71
+ else
72
+ object.class.name.downcase
73
+ end
74
+ end
75
+
76
+ attr_reader :object
77
+
78
+ attr_reader :conversions
79
+
80
+ attr_reader :source
81
+ end # ConversionTarget
82
+ end # Necromancer
@@ -0,0 +1,75 @@
1
+ # coding: utf-8
2
+
3
+ module Necromancer
4
+ # Represents the context used to configure various converters
5
+ # and facilitate type conversion
6
+ #
7
+ # @api public
8
+ class Conversions
9
+ DELIMITER = '->'.freeze
10
+
11
+ # TODO: allow to register converters as just simple proc objects
12
+ #
13
+ # register "integer->string", { |value| value.to_s }
14
+ # if block present then take it as converter class
15
+ #
16
+
17
+ # Creates a new conversions map
18
+ #
19
+ # @example
20
+ # conversion = Necromancer::Conversions.new
21
+ #
22
+ # @api public
23
+ def initialize
24
+ @converter_map = {}
25
+ end
26
+
27
+ # Load converters
28
+ #
29
+ # @api private
30
+ def load
31
+ ArrayConverters.load(self)
32
+ BooleanConverters.load(self)
33
+ IntegerConverters.load(self)
34
+ RangeConverters.load(self)
35
+ end
36
+
37
+ def [](source, target)
38
+ key = "#{source}#{DELIMITER}#{target}"
39
+ converter_map[key] || fail(NoTypeConversionAvailableError)
40
+ end
41
+
42
+ # @example with simple object
43
+ #
44
+ #
45
+ # @example with block
46
+ #
47
+ #
48
+ # @api public
49
+ def register(converter = nil, &block)
50
+ converter ||= Converter.create(&block)
51
+ key = generate_key(converter)
52
+ return false if converter_map.key?(key)
53
+ converter_map[key] = converter
54
+ true
55
+ end
56
+
57
+ def to_hash
58
+ converter_map.dup
59
+ end
60
+
61
+ protected
62
+
63
+ def generate_key(converter)
64
+ parts = []
65
+ parts << (converter.source ? converter.source.to_s : 'none')
66
+ parts << (converter.target ? converter.target.to_s : 'none')
67
+ parts.join(DELIMITER)
68
+ end
69
+
70
+ # Map of type and conversion
71
+ #
72
+ # @api private
73
+ attr_reader :converter_map
74
+ end # Conversions
75
+ end # Necromancer