abstract_mapper 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +34 -0
  4. data/Gemfile +1 -3
  5. data/README.md +29 -13
  6. data/Rakefile +4 -7
  7. data/abstract_mapper.gemspec +3 -2
  8. data/config/metrics/STYLEGUIDE +14 -15
  9. data/lib/abstract_mapper.rb +4 -1
  10. data/lib/abstract_mapper/attributes.rb +65 -0
  11. data/lib/abstract_mapper/branch.rb +17 -7
  12. data/lib/abstract_mapper/builder.rb +7 -3
  13. data/lib/abstract_mapper/command.rb +68 -0
  14. data/lib/abstract_mapper/commands.rb +11 -31
  15. data/lib/abstract_mapper/errors/unknown_command.rb +1 -1
  16. data/lib/abstract_mapper/errors/wrong_node.rb +1 -1
  17. data/lib/abstract_mapper/errors/wrong_rule.rb +2 -2
  18. data/lib/abstract_mapper/functions.rb +32 -5
  19. data/lib/abstract_mapper/node.rb +21 -10
  20. data/lib/abstract_mapper/optimizer.rb +3 -3
  21. data/lib/abstract_mapper/pair_rule.rb +2 -4
  22. data/lib/abstract_mapper/rspec.rb +1 -2
  23. data/lib/abstract_mapper/rule.rb +23 -2
  24. data/lib/abstract_mapper/rules.rb +3 -10
  25. data/lib/abstract_mapper/settings.rb +3 -3
  26. data/lib/abstract_mapper/sole_rule.rb +2 -4
  27. data/lib/abstract_mapper/version.rb +1 -1
  28. data/lib/rspec/doubles.rb +16 -0
  29. data/lib/rspec/nodes.rb +29 -16
  30. data/lib/rspec/rules.rb +3 -3
  31. data/spec/integration/faceter.rb +16 -7
  32. data/spec/integration/mapper_definition_spec.rb +1 -1
  33. data/spec/integration/rspec_examples_spec.rb +4 -30
  34. data/spec/spec_helper.rb +1 -1
  35. data/spec/unit/abstract_mapper/branch_spec.rb +91 -14
  36. data/spec/unit/abstract_mapper/builder_spec.rb +66 -48
  37. data/spec/unit/abstract_mapper/command_spec.rb +92 -0
  38. data/spec/unit/abstract_mapper/commands_spec.rb +38 -79
  39. data/spec/unit/abstract_mapper/dsl_spec.rb +60 -55
  40. data/spec/unit/abstract_mapper/errors/wrong_rule_spec.rb +1 -1
  41. data/spec/unit/abstract_mapper/functions/compact_spec.rb +3 -3
  42. data/spec/unit/abstract_mapper/functions/filter_spec.rb +1 -1
  43. data/spec/unit/abstract_mapper/functions/identity_spec.rb +21 -0
  44. data/spec/unit/abstract_mapper/functions/restrict_spec.rb +16 -0
  45. data/spec/unit/abstract_mapper/functions/subclass_spec.rb +1 -1
  46. data/spec/unit/abstract_mapper/node_spec.rb +119 -48
  47. data/spec/unit/abstract_mapper/optimizer_spec.rb +38 -32
  48. data/spec/unit/abstract_mapper/pair_rule_spec.rb +4 -11
  49. data/spec/unit/abstract_mapper/rule_spec.rb +31 -15
  50. data/spec/unit/abstract_mapper/rules_spec.rb +2 -2
  51. data/spec/unit/abstract_mapper/settings_spec.rb +80 -73
  52. data/spec/unit/abstract_mapper/sole_rule_spec.rb +3 -10
  53. data/spec/unit/abstract_mapper_spec.rb +13 -5
  54. metadata +33 -12
  55. data/lib/rspec/functions.rb +0 -25
  56. data/lib/rspec/mapper.rb +0 -40
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 805663151f746aef76f5fec0a546289ec9d94f22
4
- data.tar.gz: f6f64a22fe401d9a53d09bc10d0555445f41b66d
3
+ metadata.gz: 0547716c6b580abf30fd1b16d2e42cb2656971da
4
+ data.tar.gz: 7c793b44bebc3157b32cb754e76fd8e7c550a242
5
5
  SHA512:
6
- metadata.gz: 84df16b02fe5e3d555ba1829f2876d97383bee2e1ce6c0ac382b2894339bc429aa0ff5bc39c0b048567e44c7dc1b392a457334dc2e94694692ebbfe967bfc453
7
- data.tar.gz: 525f8da10ebddb8f46c6448d2b2c679d5621b5e72632c8a7b3b468ed7c5983513bbb6871e747ac2290a8a22a24667e8a42578230d0953b5cbc814071fda3ec9c
6
+ metadata.gz: 89772b4b3618585644e6224b93abd13276d1ed057be038ce38c677b752aefacc424fa3d05076810555afe976a45d4e059c82eb96cb7acc2b9f29b96fa66c7f15
7
+ data.tar.gz: 688f0f055d98a12c7558948b5191c9f1e7fe0d43ac740929339fb3a1e7a700252273a535430c21d3676ecf229ba61257661890711a343f333c66bc9fdce2da5a
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  language: ruby
3
3
  cache: bundler
4
+ script: "bundle exec rake test:coverage:run"
4
5
  bundler_args: --without metrics
5
6
  rvm:
6
7
  - '1.9.3'
@@ -1,3 +1,37 @@
1
+ ## v0.0.2 2015-08-06
2
+
3
+ ### Added
4
+
5
+ * Coersion of command arguments into the the node's attributes (nepalez)
6
+
7
+ ### Changed
8
+
9
+ * Removed force ordering of rules (nepalez)
10
+ * Nodes takes hash of attributes instead of array (nepalez)
11
+
12
+ ### Deleted
13
+
14
+ * Removed shared examples for mapper. The mapper should be specified by integration tests (nepalez)
15
+
16
+ ### Bugs fixed
17
+
18
+ * Fixed the `:transforming_immutable_data` shared examples so that it can deal with singleton inputs (nepalez)
19
+ * Fixed the `:mapping_immutable_input` shared examples so that it can deal with singleton inputs (nepalez)
20
+ * Fixed typo in `WrongRule` exception message (nepalez)
21
+
22
+ ### Internal
23
+
24
+ * Switched to `transproc` gem v0.3.0 (nepalez)
25
+ * Added the `Functions#identity` pure function (nepalez)
26
+ * Made the `Rule` to fully implement the interface, including method `.transproc`, `#optimize?`, `#optimize` that by default change nothing (nepalez)
27
+ * Made the `.composer` setting private for all rules (`Rule`, `SoleRule`, `PairRule`) (nepalez)
28
+ * Switched to 'ice_nine' gem for freezing instances deeply (nepalez)
29
+ * Added `ice_double` feature to specs (nepalez)
30
+ * Added `AbstractMapper::Command` that can convert arguments of the command to the node's (nepalez)
31
+ * Switched to `virtus` attributes (nepalez)
32
+
33
+ [Compare v0.0.1...v0.0.2](https://github.com/nepalez/abstract_mapper/compare/v0.0.1...v0.0.2)
34
+
1
35
  ## v0.0.1 2015-07-04
2
36
 
3
37
  This is the first published version of the gem.
data/Gemfile CHANGED
@@ -3,7 +3,5 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :metrics do
6
- gem "hexx-suit", "~> 2.2" if RUBY_ENGINE == "ruby"
6
+ gem "hexx-suit", "~> 2.3" if RUBY_ENGINE == "ruby" && RUBY_VERSION >= "2.1"
7
7
  end
8
-
9
- gem "thread_safe" # Why on earth I need this here?!
data/README.md CHANGED
@@ -62,7 +62,7 @@ The following example represents an oversimplified version of the [faceter] gem.
62
62
 
63
63
  Every node should implement the `#transproc` method that transforms some input data to the output.
64
64
 
65
- When you need attributes, assign them via initializer:
65
+ When you need attributes, assign them using [virtus] method `attribute`:
66
66
 
67
67
  ```ruby
68
68
  require "abstract_mapper"
@@ -79,21 +79,19 @@ module Faceter
79
79
  end
80
80
  end
81
81
 
82
- # The node to define a renaming of key in a tuple
82
+ # The node to define a renaming of keys in a tuple
83
83
  class Rename < AbstractMapper::Node
84
- def initialize(key, **options)
85
- @key = key
86
- @new_key = options.fetch(:to)
87
- super
88
- end
84
+ attribute :keys
89
85
 
90
86
  def transproc
91
- Transproc::HashTransformations[:rename_keys, @key => @new_key]
87
+ Transproc::HashTransformations[:rename_keys, keys]
92
88
  end
93
89
  end
94
90
  end
95
91
  ```
96
92
 
93
+ [virtus]: https://github.com/solnic/virtus
94
+
97
95
  ### Define optimization rules
98
96
 
99
97
  AbstractMapper defines 2 rules `AbstractMapper::SoleRule` and `AbstractMapper::PairRule`. The first one is applicable to every single node to check if it can be optimized by itself, the second one takes two consecutive nodes and either return them unchanged, or merges them into more time-efficient node.
@@ -125,29 +123,47 @@ module Faceter
125
123
  # into the one list, containing subnodes (entries) from both sources.
126
124
  class CompactLists < AbstractMapper::PairRule
127
125
  def optimize?
128
- nodes.join(:|) { |n| n.is_a? List }
126
+ nodes.map { |n| n.is_a? List }.reduce(:&)
129
127
  end
130
128
 
131
129
  def optimize
132
130
  List.new { nodes.map(:entries).flatten }
133
131
  end
134
132
  end
133
+
134
+ # Two consecutive renames can be merged
135
+ class CompactRenames < AbstractMapper::PairRule
136
+ def optimize?
137
+ nodes.map { |n| n.is_a? Rename }.reduce(:&)
138
+ end
139
+
140
+ def optimize
141
+ Rename.new nodes.map(&:attributes).reduce(:merge)
142
+ end
143
+ end
135
144
  end
136
145
  ```
137
146
 
138
147
  ### Register commands and rules
139
148
 
140
- Now that both the nodes (transformers) and optimization rules are defined, its time to register them for the mapper:
149
+ Now that both the nodes (transformers) and optimization rules are defined, its time to register them for the mapper.
150
+
151
+ You can coerce command argumets into node attributes. The coercer is expected to return a hash:
141
152
 
142
153
  ```ruby
143
154
  module Faceter
144
155
  class Mapper < AbstractMapper
145
156
  configure do
146
- command :list, List
147
- command :rename, Rename
157
+ command :list, List
158
+
159
+ # `:foo, to: :bar` becomes `{ keys: { foo: :bar } }`
160
+ command :rename, Rename do |name, opts|
161
+ { keys: { name => opts.fetch(:to) } }
162
+ end
148
163
 
149
164
  rule RemoveEmptyLists
150
165
  rule CompactLists
166
+ rule CompactRenames
151
167
  end
152
168
  end
153
169
  end
@@ -184,7 +200,7 @@ All the rules are applied before initializing `my_mapper`, so the AST will be th
184
200
 
185
201
  ```ruby
186
202
  my_mapper.tree
187
- # => <Root <List [<Rename(foo:, to: :bar)>, <Rename(:baz, to: :qux)>]>
203
+ # => <Root [<List [<Rename(foo: :bar, baz: :qux)>]>]>
188
204
  ```
189
205
 
190
206
  Testing
data/Rakefile CHANGED
@@ -1,10 +1,7 @@
1
1
  # encoding: utf-8
2
- begin
3
- require "bundler/setup"
4
- rescue LoadError
5
- puts "You must `gem install bundler` and `bundle install` to run rake tasks"
6
- exit
7
- end
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
8
5
 
9
6
  # Loads bundler tasks
10
7
  Bundler::GemHelper.install_tasks
@@ -20,7 +17,7 @@ end
20
17
 
21
18
  # Sets the Hexx::RSpec :test task to default
22
19
  task :default do
23
- system "bundle exec rspec spec"
20
+ system "bundle exec rake test:coverage:run"
24
21
  end
25
22
 
26
23
  desc "Runs mutation metric before the first evil being kept"
@@ -19,8 +19,9 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.required_ruby_version = ">= 2.1"
21
21
 
22
- gem.add_runtime_dependency "transproc", "~> 0.2", "> 0.2.3"
22
+ gem.add_runtime_dependency "ice_nine", "~> 0.11"
23
+ gem.add_runtime_dependency "transproc", "~> 0.3", ">= 0.3.1"
23
24
 
24
- gem.add_development_dependency "hexx-rspec", "~> 0.4"
25
+ gem.add_development_dependency "hexx-rspec", "~> 0.5"
25
26
 
26
27
  end # Gem::Specification
@@ -16,18 +16,17 @@ https://github.com/dkubb/styleguide/blob/master/RUBY-STYLE
16
16
  - FEATURE - for adding new features, or backward-compatible changes;
17
17
  - CHANGE - for backward-incompatible changes;
18
18
  - BUG FIX - for fixing bugs;
19
- - REFACTORING - for other changes of the code not affecting the API;
20
- - OTHER - for changes in documentaton, metrics etc, not touching the code;
19
+ - INTERNAL - for other changes of the code, documentaton, metrics etc.;
21
20
  - VERSION - for version changes.
22
21
 
23
- * Always separate commits of different types (such as FEATURE and CHANGE).
22
+ * Always separate commits of different types (such as FEATURE and INTERNAL).
24
23
 
25
- * Try to separate various features from each other.
24
+ * Separate various features from each other.
26
25
 
27
26
  * Include specification to the same commit as the code.
28
27
 
29
28
  * Run all tests before making a commit.
30
- Never commit the code that break unit tests.
29
+ Never commit the code that break a specification.
31
30
 
32
31
  * Use metric (run `rake check`) before making a commit.
33
32
 
@@ -37,9 +36,9 @@ https://github.com/dkubb/styleguide/blob/master/RUBY-STYLE
37
36
 
38
37
  http://semver.org/
39
38
 
40
- * For versions name the commit after a version number, following the pattern:
39
+ * For versions name the commit after a version number:
41
40
 
42
- VERSION 1.0.0-rc2
41
+ [VERSION] 1.0.0-rc2
43
42
 
44
43
 
45
44
  == Formatting:
@@ -71,7 +70,7 @@ https://github.com/dkubb/styleguide/blob/master/RUBY-STYLE
71
70
 
72
71
  == Syntax:
73
72
 
74
- * Write for 2.0.
73
+ * Write for 1.9.3+
75
74
 
76
75
  * Use double quotes
77
76
 
@@ -89,14 +88,13 @@ https://github.com/dkubb/styleguide/blob/master/RUBY-STYLE
89
88
  of thumb: If you have to use outer parentheses, you are using the
90
89
  wrong operators.)
91
90
 
92
- * Avoid double negation (!!), unless Null Objects are expected.
93
-
91
+ * Avoid double negation (!!), unless Null Object is expected.
92
+
94
93
  http://devblog.avdi.org/2011/05/30/null-objects-and-falsiness
95
94
 
96
95
  * Avoid multiline ?:, use if.
97
96
 
98
- * Use {...} when defining blocks on one line. Use do...end for multiline
99
- blocks.
97
+ * Use {...} when defining blocks on one line. Use do...end for multiline blocks.
100
98
 
101
99
  * Avoid return where not required.
102
100
 
@@ -133,7 +131,8 @@ https://github.com/dkubb/styleguide/blob/master/RUBY-STYLE
133
131
 
134
132
  * Prefer map over collect, detect over find, select over find_all.
135
133
 
136
- * Use def self.method to define singleton methods.
134
+ * Use def self.method to define singleton methods. Extend modules
135
+ when you need many singleton methods.
137
136
 
138
137
  * Avoid alias when alias_method will do.
139
138
 
@@ -141,9 +140,9 @@ https://github.com/dkubb/styleguide/blob/master/RUBY-STYLE
141
140
  == Comments:
142
141
 
143
142
  * Use YARD and its conventions for API documentation. Don't put an
144
- empty line between the comment block and the def.
143
+ empty line between the comment block and the def. Use empty comment line.
145
144
 
146
- * Comments longer than a word are capitalized and use punctuation.
145
+ * Capitalize comments longer than a word, and use punctuation.
147
146
  Use one space after periods.
148
147
 
149
148
  * Avoid superfluous comments.
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require "ice_nine"
3
4
  require "transproc"
4
5
 
5
6
  require_relative "abstract_mapper/functions"
@@ -8,8 +9,10 @@ require_relative "abstract_mapper/errors/unknown_command"
8
9
  require_relative "abstract_mapper/errors/wrong_node"
9
10
  require_relative "abstract_mapper/errors/wrong_rule"
10
11
 
12
+ require_relative "abstract_mapper/attributes"
11
13
  require_relative "abstract_mapper/node"
12
14
  require_relative "abstract_mapper/branch"
15
+ require_relative "abstract_mapper/command"
13
16
  require_relative "abstract_mapper/commands"
14
17
  require_relative "abstract_mapper/builder"
15
18
 
@@ -65,7 +68,7 @@ class AbstractMapper
65
68
  def initialize
66
69
  @tree = self.class.finalize
67
70
  @transproc = @tree.transproc
68
- freeze
71
+ IceNine.deep_freeze(self)
69
72
  end
70
73
 
71
74
  # Maps the input data to some output using the transformation,
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # Defines attributes along with the DSL for setting them
6
+ #
7
+ module Attributes
8
+
9
+ # @private
10
+ def self.included(klass)
11
+ klass.__send__ :extend, DSL
12
+ end
13
+
14
+ # @!attribute [r] attributes
15
+ #
16
+ # @return [Hash] The initialized attributes
17
+ #
18
+ attr_reader :attributes
19
+
20
+ # Initializes the instance with the hash of attributes
21
+ #
22
+ # @param [Hash] attributes
23
+ #
24
+ def initialize(attributes)
25
+ @attributes = Functions[:restrict, self.class.attributes][attributes]
26
+ end
27
+
28
+ # Attributes DSL
29
+ #
30
+ module DSL
31
+
32
+ # Makes a attributes inheritable
33
+ #
34
+ # @private
35
+ #
36
+ def inherited(klass)
37
+ attributes.each { |key, value| klass.attribute key, default: value }
38
+ end
39
+
40
+ # Default attributes for the node
41
+ #
42
+ # @return [Hash]
43
+ #
44
+ def attributes
45
+ @attributes ||= {}
46
+ end
47
+
48
+ # Declares the attribute
49
+ #
50
+ # @param [#to_sym] name
51
+ # @param [Hash] options
52
+ # @option options [Object] :default
53
+ #
54
+ # @return [undefined]
55
+ #
56
+ def attribute(name, options = {})
57
+ attributes[name.to_sym] = options[:default]
58
+ define_method(name) { attributes[name.to_sym] }
59
+ end
60
+
61
+ end # module DSL
62
+
63
+ end # module Attributes
64
+
65
+ end # class AbstractMapper
@@ -6,7 +6,7 @@ class AbstractMapper
6
6
  # applied to some level of nested input.
7
7
  #
8
8
  # Unlike the simple node, describing a transformation of data, the
9
- # branch carries a collection of subnodes along with methods to [#rebuild]
9
+ # branch carries a collection of subnodes along with methods to [#update]
10
10
  # itself with the same attributes and different subnodes.
11
11
  #
12
12
  # Tne branch only stores subnodes and composes transformations.
@@ -32,9 +32,9 @@ class AbstractMapper
32
32
  # @return [Branch::Node]
33
33
 
34
34
  # @private
35
- def initialize(*attributes)
35
+ def initialize(attributes = {})
36
36
  @subnodes = block_given? ? yield : []
37
- super(*attributes, &nil)
37
+ super(attributes, &nil)
38
38
  end
39
39
 
40
40
  # Returns a new branch of the same type, with the same attributes,
@@ -43,7 +43,7 @@ class AbstractMapper
43
43
  # @example
44
44
  # branch = Branch.new(:foo)
45
45
  # # => <Branch(:foo) []>
46
- # branch.rebuild { Node.new(:bar) }
46
+ # branch.update { Node.new(:bar) }
47
47
  # # => <Branch(:foo) [<Node(:bar)>]>
48
48
  #
49
49
  # @param [Proc] block
@@ -53,8 +53,8 @@ class AbstractMapper
53
53
  #
54
54
  # @yield block
55
55
  #
56
- def rebuild(&block)
57
- self.class.new(*attributes, &block)
56
+ def update
57
+ self.class.new(attributes) { yield }
58
58
  end
59
59
 
60
60
  # @!method each
@@ -73,7 +73,7 @@ class AbstractMapper
73
73
  # @return [AbstractMapper::Branch]
74
74
  #
75
75
  def <<(other)
76
- rebuild { entries << other }
76
+ update { entries << other }
77
77
  end
78
78
 
79
79
  # The composition of transformations from all subnodes of the branch
@@ -97,6 +97,16 @@ class AbstractMapper
97
97
  "#{super} [#{map(&:inspect).join(", ")}]"
98
98
  end
99
99
 
100
+ # Checks equality of branches by type, attributes and subnodes
101
+ #
102
+ # @param [Other] other
103
+ #
104
+ # @return [Boolean]
105
+ #
106
+ def eql?(other)
107
+ super && entries.eql?(other.entries)
108
+ end
109
+
100
110
  private
101
111
 
102
112
  # Substitutes the name of the class by the special name "Root"
@@ -22,7 +22,11 @@ class AbstractMapper
22
22
  #
23
23
  # @return [AbstractMapper::Commands] The registry of DSL commands
24
24
  #
25
- attr_accessor :commands
25
+ attr_writer :commands
26
+
27
+ def commands
28
+ @commands ||= AbstractMapper::Commands
29
+ end
26
30
 
27
31
  end # eigenclass
28
32
 
@@ -61,13 +65,13 @@ class AbstractMapper
61
65
  @tree = node
62
66
  @commands = self.class.commands
63
67
  instance_eval(&block) if block_given?
64
- freeze
68
+ IceNine.deep_freeze(self)
65
69
  end
66
70
 
67
71
  private # DSL commands
68
72
 
69
73
  def method_missing(name, *args, &block)
70
- node = @commands[name, *args, &block]
74
+ node = @commands[name].call(*args, &block)
71
75
  @tree = tree << (node.is_a?(Branch) ? update(node, &block) : node)
72
76
  end
73
77