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
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ class AbstractMapper
4
+
5
+ # Describes the command of the mapper
6
+ #
7
+ # Every command has a name, a node that is added to the AST and the function,
8
+ # that converts command attributes into the node's.
9
+ #
10
+ # Method `#call` builds a correspodning node for the AST.
11
+ #
12
+ # @api private
13
+ #
14
+ class Command
15
+
16
+ # @!attribute [r] name
17
+ #
18
+ # @return [Symbol] The name of the DSL command
19
+ #
20
+ attr_reader :name
21
+
22
+ # @!attribute [r] klass
23
+ #
24
+ # @return [Class] The class of the node to be created
25
+ #
26
+ attr_reader :klass
27
+
28
+ # @!attribute [r] converter
29
+ #
30
+ # @return [#call] The converter of the command's arguments into the node's
31
+ #
32
+ attr_reader :converter
33
+
34
+ # @!scope class
35
+ # @!method new(name, klass, converter)
36
+ # Creates the named command for node klass and arguments converter
37
+ #
38
+ # @param [#to_sym] name The name of the DSL command
39
+ # @param [Class] klass The klass of the node to be created
40
+ # @param [#call] converter The function that coerces attributes
41
+ #
42
+ # @return [AbstractMapper::Command]
43
+
44
+ # @private
45
+ def initialize(name, klass, converter = nil)
46
+ @name = name.to_sym
47
+ @klass = klass
48
+ @branch = Functions[:subclass?, Branch][klass]
49
+ @converter = converter || proc { |args = {}| args }
50
+ IceNine.deep_freeze(self)
51
+ end
52
+
53
+ # Builds the AST node
54
+ #
55
+ # @param [Object, Array] args
56
+ # The argument of the command that should be converted to node attributes
57
+ # @param [Proc] block
58
+ #
59
+ # @return [AbstractMapper::Node]
60
+ #
61
+ def call(*args, &block)
62
+ block = nil if @branch
63
+ klass.new(converter.call(*args), &block)
64
+ end
65
+
66
+ end # class Command
67
+
68
+ end # class AbstractMapper
@@ -2,20 +2,12 @@
2
2
 
3
3
  class AbstractMapper
4
4
 
5
- # Registry of DSL commands used by the builder
5
+ # Collection of DSL commands used by the builder
6
6
  #
7
7
  # @api private
8
8
  #
9
9
  class Commands
10
10
 
11
- # @!attribute [r] registry
12
- #
13
- # @return [Hash<Symbol => Class>]
14
- # The registry of command names, pointing to types of the nodes,
15
- # that should be built for adding to AST
16
- #
17
- attr_reader :registry
18
-
19
11
  # @!scope class
20
12
  # @!method new(registry = {})
21
13
  # Creates an immutable collection of commands
@@ -26,44 +18,32 @@ class AbstractMapper
26
18
 
27
19
  # @private
28
20
  def initialize(registry = {})
29
- @registry = registry.dup.freeze
30
- freeze
21
+ @registry = registry.dup
22
+ IceNine.deep_freeze(self)
31
23
  end
32
24
 
33
25
  # Returns a new immutable registry with added command name and type
34
26
  #
35
- # @param [Array<Symbol, Class>] other
27
+ # @param [[Symbol, Class, Proc]] other
36
28
  #
37
29
  # @return [undefined]
38
30
  #
39
31
  def <<(other)
40
- name = other[0]
41
- node = other[1]
42
- self.class.new registry.merge(name.to_sym => node)
32
+ command = Command.new(*other)
33
+ self.class.new @registry.merge(command.name => command)
43
34
  end
44
35
 
45
- # Builds a node by the name of DSL command
46
- #
47
- # Skips the block if a registered node is a branch
36
+ # Returns the registered command by name
48
37
  #
49
38
  # @param [#to_sym] name The name of the command
50
- # @param [Object, Array] args The command's arguments
51
- # @param [Proc] block
52
- # The block to be passed to the constructor of the simple node
39
+ #
40
+ # @return [AbstractMapper::Command]
53
41
  #
54
42
  # @raise [AbstractMapper::Errors::UnknownCommand]
55
43
  # When unknown command is called
56
44
  #
57
- def [](name, *args, &block)
58
- type = get(name)
59
- branch = Functions[:subclass?, Branch][type]
60
- branch ? type.new(*args) : type.new(*args, &block)
61
- end
62
-
63
- private
64
-
65
- def get(name)
66
- registry.fetch(name.to_sym) { fail(Errors::UnknownCommand.new name) }
45
+ def [](name)
46
+ @registry.fetch(name.to_sym) { fail(Errors::UnknownCommand.new name) }
67
47
  end
68
48
 
69
49
  end # class Commands
@@ -15,7 +15,7 @@ class AbstractMapper
15
15
  # @private
16
16
  def initialize(name)
17
17
  super "'#{name}' is not a registered DSL command"
18
- freeze
18
+ IceNine.deep_freeze(self)
19
19
  end
20
20
 
21
21
  end # class UnknownCommand
@@ -13,7 +13,7 @@ class AbstractMapper
13
13
  # @private
14
14
  def initialize(node)
15
15
  super "#{node} is not a subclass of AbstractMapper::Node"
16
- freeze
16
+ IceNine.deep_freeze(self)
17
17
  end
18
18
 
19
19
  end # class WrongNode
@@ -12,8 +12,8 @@ class AbstractMapper
12
12
 
13
13
  # @private
14
14
  def initialize(node)
15
- super "#{node} is not a subclass of AbstractMapper::SoleRule"
16
- freeze
15
+ super "#{node} is not a subclass of AbstractMapper::Rule"
16
+ IceNine.deep_freeze(self)
17
17
  end
18
18
 
19
19
  end # class WrongRule
@@ -10,7 +10,21 @@ class AbstractMapper
10
10
 
11
11
  extend Transproc::Registry
12
12
 
13
- uses :map_array, from: Transproc::ArrayTransformations
13
+ import :map_array, from: Transproc::ArrayTransformations
14
+
15
+ # Returns the unchanged value whatever parameters are given
16
+ #
17
+ # @example
18
+ # fn = Functions[:identity, :foo]
19
+ # fn[1] # => 1
20
+ #
21
+ # @param [Object] value
22
+ #
23
+ # @return [Object]
24
+ #
25
+ def self.identity(value, *)
26
+ value
27
+ end
14
28
 
15
29
  # Applies the function to every element of array and removes empty values
16
30
  #
@@ -24,8 +38,8 @@ class AbstractMapper
24
38
  #
25
39
  # @return [Array]
26
40
  #
27
- def filter(array, fn)
28
- map_array(array, fn).compact.flatten
41
+ def self.filter(array, fn)
42
+ t(:map_array, fn)[array].compact.flatten
29
43
  end
30
44
 
31
45
  # Applies the function to every consecutive pair of array elements,
@@ -44,7 +58,7 @@ class AbstractMapper
44
58
  #
45
59
  # @return [Array]
46
60
  #
47
- def compact(array, fn)
61
+ def self.compact(array, fn)
48
62
  array.each_with_object([]) do |i, a|
49
63
  if a.empty?
50
64
  a << i
@@ -67,10 +81,23 @@ class AbstractMapper
67
81
  #
68
82
  # @return [Boolean]
69
83
  #
70
- def subclass?(subling, ancestor)
84
+ def self.subclass?(subling, ancestor)
71
85
  subling.ancestors.include?(ancestor)
72
86
  end
73
87
 
88
+ # Restricts the hash by keys and values of the default one
89
+ #
90
+ # @param [Hash] hash
91
+ # @param [Hash] default_hash
92
+ #
93
+ # @return [Hash] <description>
94
+ #
95
+ def self.restrict(hash, default_hash)
96
+ keys = default_hash.keys
97
+ values = default_hash.merge(hash).values
98
+ Hash[keys.zip(values)]
99
+ end
100
+
74
101
  end # module Functions
75
102
 
76
103
  end # class AbstractMapper
@@ -17,11 +17,8 @@ class AbstractMapper
17
17
  #
18
18
  class Node
19
19
 
20
- # @!attribute [r] attributes
21
- #
22
- # @return [Array] The list of node-specific attributes
23
- #
24
- attr_reader :attributes
20
+ include Attributes # adds attributes and their DSL
21
+ include Comparable
25
22
 
26
23
  # @!attribute [r] block
27
24
  #
@@ -30,19 +27,21 @@ class AbstractMapper
30
27
  attr_reader :block
31
28
 
32
29
  # @private
33
- def initialize(*attributes, &block)
34
- @attributes = attributes.freeze
30
+ def initialize(_ = {}, &block)
31
+ super
35
32
  @block = block
36
- freeze
33
+ IceNine.deep_freeze(self)
37
34
  end
38
35
 
39
36
  # @!method transproc
40
37
  # The transformation function for the branch
41
38
  #
39
+ # @return [Transproc::Function]
40
+ #
42
41
  # @abstract
43
42
  #
44
43
  def transproc
45
- Transproc::Function.new(-> v { v }, {})
44
+ Functions[:identity]
46
45
  end
47
46
 
48
47
  # Returns a human-readable string representating the node
@@ -61,6 +60,17 @@ class AbstractMapper
61
60
  "#{__name__}#{__attributes__}"
62
61
  end
63
62
 
63
+ # Compares the node to another one by type and attributes
64
+ #
65
+ # @param [Object] other
66
+ #
67
+ # @return [Boolean]
68
+ #
69
+ def ==(other)
70
+ other.instance_of?(self.class) && attributes.eql?(other.attributes)
71
+ end
72
+ alias_method :eql?, :==
73
+
64
74
  private
65
75
 
66
76
  def __name__
@@ -68,7 +78,8 @@ class AbstractMapper
68
78
  end
69
79
 
70
80
  def __attributes__
71
- "(#{attributes.map(&:inspect).join(", ")})" if attributes.any?
81
+ return if attributes.empty?
82
+ "(#{attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")})"
72
83
  end
73
84
 
74
85
  end # class Node
@@ -28,7 +28,7 @@ class AbstractMapper
28
28
  # @private
29
29
  def initialize(rules)
30
30
  @rules = rules
31
- freeze
31
+ IceNine.deep_freeze(self)
32
32
  end
33
33
 
34
34
  # Recursively optimizes the AST from root to leafs
@@ -39,8 +39,8 @@ class AbstractMapper
39
39
  #
40
40
  def update(tree)
41
41
  return tree unless tree.is_a? Branch
42
- new_tree = tree.rebuild { rules[tree] }
43
- new_tree.rebuild { new_tree.map(&method(:update)) }
42
+ new_tree = tree.update { rules[tree] }
43
+ new_tree.update { new_tree.map(&method(:update)) }
44
44
  end
45
45
 
46
46
  end # class Optimizer
@@ -26,13 +26,11 @@ class AbstractMapper
26
26
  #
27
27
  class PairRule < Rule
28
28
 
29
- # Returns the name of transproc used to compose rules
30
- #
31
- # @return [Symbol]
32
- #
29
+ # @private
33
30
  def self.composer
34
31
  :compact
35
32
  end
33
+ private_class_method :composer
36
34
 
37
35
  # @!attribute [r] node
38
36
  #
@@ -1,7 +1,6 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  # Collection of shared examples for testing parts of DSL
4
- require_relative "../rspec/functions"
5
4
  require_relative "../rspec/nodes"
6
5
  require_relative "../rspec/rules"
7
- require_relative "../rspec/mapper"
6
+ require_relative "../rspec/doubles"
@@ -10,6 +10,12 @@ class AbstractMapper
10
10
  #
11
11
  class Rule
12
12
 
13
+ # @private
14
+ def self.composer
15
+ :identity
16
+ end
17
+ private_class_method :composer
18
+
13
19
  # The transformation function that applies the rule to the array of nodes
14
20
  #
15
21
  # @return [Transproc::Function]
@@ -35,8 +41,23 @@ class AbstractMapper
35
41
 
36
42
  # @private
37
43
  def initialize(*nodes)
38
- @nodes = nodes.freeze
39
- freeze
44
+ @nodes = nodes
45
+ IceNine.deep_freeze(self)
46
+ end
47
+
48
+ # Checks if optimization is needed for the node(s)
49
+ #
50
+ # @return [Boolean]
51
+ #
52
+ def optimize?
53
+ end
54
+
55
+ # Returns the optimized node(s)
56
+ #
57
+ # @return [Object]
58
+ #
59
+ def optimize
60
+ nodes
40
61
  end
41
62
 
42
63
  # Returns the result of the rule applied to the initialized [#nodes]
@@ -24,9 +24,9 @@ class AbstractMapper
24
24
 
25
25
  # @private
26
26
  def initialize(registry = [])
27
- @registry = registry.dup.freeze
28
- @transproc = ordered.map(&:transproc).inject(:>>)
29
- freeze
27
+ @registry = registry.dup
28
+ @transproc = registry.map(&:transproc).inject(:>>)
29
+ IceNine.deep_freeze(self)
30
30
  end
31
31
 
32
32
  # Returns the copy of current registry with the new rule added
@@ -49,13 +49,6 @@ class AbstractMapper
49
49
  @transproc ? @transproc[nodes] : nodes
50
50
  end
51
51
 
52
- private
53
-
54
- # Calls all sole rules first
55
- def ordered
56
- registry.sort_by { |rule| Functions[:subclass?, PairRule][rule].to_s }
57
- end
58
-
59
52
  end # class Rules
60
53
 
61
54
  end # class AbstractMapper
@@ -52,7 +52,7 @@ class AbstractMapper
52
52
  __configure__(&block)
53
53
  __set_builder__
54
54
  __set_optimizer__
55
- freeze
55
+ IceNine.deep_freeze(self)
56
56
  end
57
57
 
58
58
  private
@@ -63,10 +63,10 @@ class AbstractMapper
63
63
  @rules = rules << value
64
64
  end
65
65
 
66
- def command(name, node)
66
+ def command(name, node, &block)
67
67
  fn = Functions[:subclass?, Node]
68
68
  fail Errors::WrongNode.new(node) unless fn[node]
69
- @commands = commands << [name, node]
69
+ @commands = commands << [name, node, block]
70
70
  end
71
71
 
72
72
  def __set_rules__
@@ -25,13 +25,11 @@ class AbstractMapper
25
25
  #
26
26
  class SoleRule < Rule
27
27
 
28
- # Returns the name of transproc used to compose rules
29
- #
30
- # @return [Symbol]
31
- #
28
+ # @private
32
29
  def self.composer
33
30
  :filter
34
31
  end
32
+ private_class_method :composer
35
33
 
36
34
  # @!attribute [r] node
37
35
  #