abstract_mapper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +9 -0
- data/.metrics +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +19 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +9 -0
- data/Guardfile +14 -0
- data/LICENSE +21 -0
- data/README.md +239 -0
- data/Rakefile +34 -0
- data/abstract_mapper.gemspec +26 -0
- data/config/metrics/STYLEGUIDE +230 -0
- data/config/metrics/cane.yml +5 -0
- data/config/metrics/churn.yml +6 -0
- data/config/metrics/flay.yml +2 -0
- data/config/metrics/metric_fu.yml +15 -0
- data/config/metrics/reek.yml +1 -0
- data/config/metrics/roodi.yml +24 -0
- data/config/metrics/rubocop.yml +76 -0
- data/config/metrics/saikuro.yml +3 -0
- data/config/metrics/simplecov.yml +6 -0
- data/config/metrics/yardstick.yml +37 -0
- data/lib/abstract_mapper/branch.rb +110 -0
- data/lib/abstract_mapper/builder.rb +84 -0
- data/lib/abstract_mapper/commands.rb +71 -0
- data/lib/abstract_mapper/dsl.rb +62 -0
- data/lib/abstract_mapper/errors/unknown_command.rb +25 -0
- data/lib/abstract_mapper/errors/wrong_node.rb +23 -0
- data/lib/abstract_mapper/errors/wrong_rule.rb +23 -0
- data/lib/abstract_mapper/functions.rb +76 -0
- data/lib/abstract_mapper/node.rb +76 -0
- data/lib/abstract_mapper/optimizer.rb +48 -0
- data/lib/abstract_mapper/pair_rule.rb +66 -0
- data/lib/abstract_mapper/rspec.rb +7 -0
- data/lib/abstract_mapper/rule.rb +52 -0
- data/lib/abstract_mapper/rules.rb +61 -0
- data/lib/abstract_mapper/settings.rb +95 -0
- data/lib/abstract_mapper/sole_rule.rb +58 -0
- data/lib/abstract_mapper/version.rb +9 -0
- data/lib/abstract_mapper.rb +82 -0
- data/lib/rspec/functions.rb +25 -0
- data/lib/rspec/mapper.rb +40 -0
- data/lib/rspec/nodes.rb +58 -0
- data/lib/rspec/rules.rb +59 -0
- data/spec/integration/faceter.rb +62 -0
- data/spec/integration/mapper_definition_spec.rb +33 -0
- data/spec/integration/rspec_examples_spec.rb +77 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/unit/abstract_mapper/branch_spec.rb +126 -0
- data/spec/unit/abstract_mapper/builder_spec.rb +78 -0
- data/spec/unit/abstract_mapper/commands_spec.rb +105 -0
- data/spec/unit/abstract_mapper/dsl_spec.rb +82 -0
- data/spec/unit/abstract_mapper/errors/unknown_command_spec.rb +21 -0
- data/spec/unit/abstract_mapper/errors/wrong_node_spec.rb +21 -0
- data/spec/unit/abstract_mapper/errors/wrong_rule_spec.rb +23 -0
- data/spec/unit/abstract_mapper/functions/compact_spec.rb +25 -0
- data/spec/unit/abstract_mapper/functions/filter_spec.rb +26 -0
- data/spec/unit/abstract_mapper/functions/subclass_spec.rb +66 -0
- data/spec/unit/abstract_mapper/node_spec.rb +84 -0
- data/spec/unit/abstract_mapper/optimizer_spec.rb +48 -0
- data/spec/unit/abstract_mapper/pair_rule_spec.rb +59 -0
- data/spec/unit/abstract_mapper/rule_spec.rb +83 -0
- data/spec/unit/abstract_mapper/rules_spec.rb +88 -0
- data/spec/unit/abstract_mapper/settings_spec.rb +113 -0
- data/spec/unit/abstract_mapper/sole_rule_spec.rb +51 -0
- data/spec/unit/abstract_mapper_spec.rb +36 -0
- metadata +171 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
---
|
2
|
+
AssignmentInConditionalCheck:
|
3
|
+
CaseMissingElseCheck:
|
4
|
+
ClassLineCountCheck:
|
5
|
+
line_count: 100
|
6
|
+
ClassNameCheck:
|
7
|
+
pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
|
8
|
+
ClassVariableCheck:
|
9
|
+
CyclomaticComplexityBlockCheck:
|
10
|
+
complexity: 3
|
11
|
+
CyclomaticComplexityMethodCheck:
|
12
|
+
complexity: 3
|
13
|
+
EmptyRescueBodyCheck:
|
14
|
+
ForLoopCheck:
|
15
|
+
MethodLineCountCheck:
|
16
|
+
line_count: 7
|
17
|
+
MethodNameCheck:
|
18
|
+
pattern: !ruby/regexp /^[\||\^|\&|\!]$|^[_a-z<>=\[|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
|
19
|
+
ModuleLineCountCheck:
|
20
|
+
line_count: 100
|
21
|
+
ModuleNameCheck:
|
22
|
+
pattern: !ruby/regexp /^[A-Z][a-zA-Z0-9]*$/
|
23
|
+
ParameterNumberCheck:
|
24
|
+
parameter_count: 3
|
@@ -0,0 +1,76 @@
|
|
1
|
+
---
|
2
|
+
# settings added by the 'hexx-suit' module
|
3
|
+
# output: "tmp/rubocop"
|
4
|
+
# format: "html"
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
Exclude:
|
8
|
+
- '**/db/schema.rb'
|
9
|
+
|
10
|
+
Lint/HandleExceptions:
|
11
|
+
Exclude:
|
12
|
+
- '**/*_spec.rb'
|
13
|
+
|
14
|
+
Lint/RescueException:
|
15
|
+
Exclude:
|
16
|
+
- '**/*_spec.rb'
|
17
|
+
|
18
|
+
Metrics/LineLength:
|
19
|
+
Exclude:
|
20
|
+
- '**/integration/*_spec.rb'
|
21
|
+
|
22
|
+
Style/AccessorMethodName:
|
23
|
+
Exclude:
|
24
|
+
- '**/*_spec.rb'
|
25
|
+
|
26
|
+
Style/AsciiComments:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/ClassAndModuleChildren:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Style/Documentation:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Style/EmptyLinesAroundBlockBody:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
Style/EmptyLinesAroundClassBody:
|
39
|
+
Enabled: false
|
40
|
+
|
41
|
+
Style/EmptyLinesAroundMethodBody:
|
42
|
+
Enabled: false
|
43
|
+
|
44
|
+
Style/EmptyLinesAroundModuleBody:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
Style/EmptyLineBetweenDefs:
|
48
|
+
Enabled: false
|
49
|
+
|
50
|
+
Style/FileName:
|
51
|
+
Enabled: false
|
52
|
+
|
53
|
+
Style/RaiseArgs:
|
54
|
+
EnforcedStyle: compact
|
55
|
+
|
56
|
+
Style/SingleLineMethods:
|
57
|
+
Exclude:
|
58
|
+
- '**/*_spec.rb'
|
59
|
+
|
60
|
+
Style/SingleSpaceBeforeFirstArg:
|
61
|
+
Enabled: false
|
62
|
+
|
63
|
+
Style/SpecialGlobalVars:
|
64
|
+
Exclude:
|
65
|
+
- '**/Gemfile'
|
66
|
+
- '**/*.gemspec'
|
67
|
+
|
68
|
+
Style/StringLiterals:
|
69
|
+
EnforcedStyle: double_quotes
|
70
|
+
|
71
|
+
Style/StringLiteralsInInterpolation:
|
72
|
+
EnforcedStyle: double_quotes
|
73
|
+
|
74
|
+
Style/TrivialAccessors:
|
75
|
+
Exclude:
|
76
|
+
- '**/*_spec.rb'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
---
|
2
|
+
# Settings added by the 'hexx-suit' gem
|
3
|
+
output: "tmp/yardstick/output.log"
|
4
|
+
path: "lib/**/*.rb"
|
5
|
+
rules:
|
6
|
+
ApiTag::Presence:
|
7
|
+
enabled: true
|
8
|
+
exclude: []
|
9
|
+
ApiTag::Inclusion:
|
10
|
+
enabled: true
|
11
|
+
exclude: []
|
12
|
+
ApiTag::ProtectedMethod:
|
13
|
+
enabled: true
|
14
|
+
exclude: []
|
15
|
+
ApiTag::PrivateMethod:
|
16
|
+
enabled: false
|
17
|
+
exclude: []
|
18
|
+
ExampleTag:
|
19
|
+
enabled: true
|
20
|
+
exclude: []
|
21
|
+
ReturnTag:
|
22
|
+
enabled: true
|
23
|
+
exclude: []
|
24
|
+
Summary::Presence:
|
25
|
+
enabled: true
|
26
|
+
exclude: []
|
27
|
+
Summary::Length:
|
28
|
+
enabled: true
|
29
|
+
exclude: []
|
30
|
+
Summary::Delimiter:
|
31
|
+
enabled: true
|
32
|
+
exclude: []
|
33
|
+
Summary::SingleLine:
|
34
|
+
enabled: true
|
35
|
+
exclude: []
|
36
|
+
threshold: 100
|
37
|
+
verbose: false
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# A special type of the composed node, that describes transformation,
|
6
|
+
# applied to some level of nested input.
|
7
|
+
#
|
8
|
+
# Unlike the simple node, describing a transformation of data, the
|
9
|
+
# branch carries a collection of subnodes along with methods to [#rebuild]
|
10
|
+
# itself with the same attributes and different subnodes.
|
11
|
+
#
|
12
|
+
# Tne branch only stores subnodes and composes transformations.
|
13
|
+
# Its has no access to DSL and knows neither how to build a tree
|
14
|
+
# (see [AbstractMapper::Builder]), nor how to optimize it later
|
15
|
+
# (see [AbstractMapper::Optimizer]).
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
#
|
19
|
+
class Branch < Node
|
20
|
+
|
21
|
+
include Enumerable
|
22
|
+
|
23
|
+
# @!scope class
|
24
|
+
# @!method new(*attributes, &block)
|
25
|
+
# Creates a new branch
|
26
|
+
#
|
27
|
+
# @param [Object, Array<Object>] attributes
|
28
|
+
# The list of type-specific attributes of the branch
|
29
|
+
# @param [Proc] block
|
30
|
+
# The block that returns an array of subnodes for the branch
|
31
|
+
#
|
32
|
+
# @return [Branch::Node]
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def initialize(*attributes)
|
36
|
+
@subnodes = block_given? ? yield : []
|
37
|
+
super(*attributes, &nil)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns a new branch of the same type, with the same attributes,
|
41
|
+
# but with a different collection of subnodes, transmitted by the block.
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# branch = Branch.new(:foo)
|
45
|
+
# # => <Branch(:foo) []>
|
46
|
+
# branch.rebuild { Node.new(:bar) }
|
47
|
+
# # => <Branch(:foo) [<Node(:bar)>]>
|
48
|
+
#
|
49
|
+
# @param [Proc] block
|
50
|
+
# The block that should return an array of subnodes for the branch
|
51
|
+
#
|
52
|
+
# @return [AbstractMapper::Branch]
|
53
|
+
#
|
54
|
+
# @yield block
|
55
|
+
#
|
56
|
+
def rebuild(&block)
|
57
|
+
self.class.new(*attributes, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @!method each
|
61
|
+
# Returns the enumerator for subnodes
|
62
|
+
#
|
63
|
+
# @return [Enumerator]
|
64
|
+
#
|
65
|
+
def each(&block)
|
66
|
+
@subnodes.each(&block)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a new branch with the other node added to its subnodes
|
70
|
+
#
|
71
|
+
# @param [AbstractMapper::Node] other
|
72
|
+
#
|
73
|
+
# @return [AbstractMapper::Branch]
|
74
|
+
#
|
75
|
+
def <<(other)
|
76
|
+
rebuild { entries << other }
|
77
|
+
end
|
78
|
+
|
79
|
+
# The composition of transformations from all subnodes of the branch
|
80
|
+
#
|
81
|
+
# To be reloaded by the subclasses to apply the composition to
|
82
|
+
# a corresponding level of nested data.
|
83
|
+
#
|
84
|
+
# @return [Transproc::Function]
|
85
|
+
#
|
86
|
+
# @abstract
|
87
|
+
#
|
88
|
+
def transproc
|
89
|
+
map(&:transproc).inject(:>>)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Adds subnodes to the default description of the branch
|
93
|
+
#
|
94
|
+
# @return [String]
|
95
|
+
#
|
96
|
+
def to_s
|
97
|
+
"#{super} [#{map(&:inspect).join(", ")}]"
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
# Substitutes the name of the class by the special name "Root"
|
103
|
+
# to describe the root node of AST.
|
104
|
+
def __name__
|
105
|
+
instance_of?(Branch) ? "Root" : super
|
106
|
+
end
|
107
|
+
|
108
|
+
end # class Branch
|
109
|
+
|
110
|
+
end # class AbstractMapper
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# Builds the immutable abstract syntax tree (AST) using DSL commands.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# Builder.update do
|
9
|
+
# field :user do # DSL method
|
10
|
+
# add_prefix :user # DSL method for subtree
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
# # => <Root() [<Field(:user) [<AddPrefix(:user)>]>]>
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
#
|
17
|
+
class Builder
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
# @!attribute [rw] commands
|
22
|
+
#
|
23
|
+
# @return [AbstractMapper::Commands] The registry of DSL commands
|
24
|
+
#
|
25
|
+
attr_accessor :commands
|
26
|
+
|
27
|
+
end # eigenclass
|
28
|
+
|
29
|
+
# Updates given node by adding its subnode from the block
|
30
|
+
#
|
31
|
+
# @param [AbstractMapper::Branch] node
|
32
|
+
# The root of the tree to be updated
|
33
|
+
# @param [Proc] block
|
34
|
+
# The block with DSL commands for adding subnodes
|
35
|
+
#
|
36
|
+
# @return (see #tree)
|
37
|
+
#
|
38
|
+
def self.update(node = Branch.new, &block)
|
39
|
+
new(node, &block).tree
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!attribute [r] tree
|
43
|
+
#
|
44
|
+
# @return [AbstractMapper::Branch] The updated tree
|
45
|
+
#
|
46
|
+
attr_reader :tree
|
47
|
+
|
48
|
+
# @!scope class
|
49
|
+
# @!method new(node, &block)
|
50
|
+
# Builds the AST by recursively adding new nodes, using the commands
|
51
|
+
#
|
52
|
+
# @param [AbstractMapper::Branch] node
|
53
|
+
# The root of the tree to be updated
|
54
|
+
# @param [Proc] block
|
55
|
+
# The block with DSL commands for adding subnodes
|
56
|
+
#
|
57
|
+
# @return [AbstractMapper::Builder]
|
58
|
+
|
59
|
+
# @private
|
60
|
+
def initialize(node, &block)
|
61
|
+
@tree = node
|
62
|
+
@commands = self.class.commands
|
63
|
+
instance_eval(&block) if block_given?
|
64
|
+
freeze
|
65
|
+
end
|
66
|
+
|
67
|
+
private # DSL commands
|
68
|
+
|
69
|
+
def method_missing(name, *args, &block)
|
70
|
+
node = @commands[name, *args, &block]
|
71
|
+
@tree = tree << (node.is_a?(Branch) ? update(node, &block) : node)
|
72
|
+
end
|
73
|
+
|
74
|
+
def respond_to_missing?(*)
|
75
|
+
true
|
76
|
+
end
|
77
|
+
|
78
|
+
def update(node, &block)
|
79
|
+
self.class.update(node, &block)
|
80
|
+
end
|
81
|
+
|
82
|
+
end # class Builder
|
83
|
+
|
84
|
+
end # class AbstractMapper
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# Registry of DSL commands used by the builder
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
class Commands
|
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
|
+
# @!scope class
|
20
|
+
# @!method new(registry = {})
|
21
|
+
# Creates an immutable collection of commands
|
22
|
+
#
|
23
|
+
# @param [Hash] registry
|
24
|
+
#
|
25
|
+
# @return [Faceter::Commands]
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def initialize(registry = {})
|
29
|
+
@registry = registry.dup.freeze
|
30
|
+
freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a new immutable registry with added command name and type
|
34
|
+
#
|
35
|
+
# @param [Array<Symbol, Class>] other
|
36
|
+
#
|
37
|
+
# @return [undefined]
|
38
|
+
#
|
39
|
+
def <<(other)
|
40
|
+
name = other[0]
|
41
|
+
node = other[1]
|
42
|
+
self.class.new registry.merge(name.to_sym => node)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Builds a node by the name of DSL command
|
46
|
+
#
|
47
|
+
# Skips the block if a registered node is a branch
|
48
|
+
#
|
49
|
+
# @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
|
53
|
+
#
|
54
|
+
# @raise [AbstractMapper::Errors::UnknownCommand]
|
55
|
+
# When unknown command is called
|
56
|
+
#
|
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) }
|
67
|
+
end
|
68
|
+
|
69
|
+
end # class Commands
|
70
|
+
|
71
|
+
end # class AbstractMapper
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# Provides features to configure mappers
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
module DSL
|
10
|
+
|
11
|
+
# @private
|
12
|
+
def inherited(klass)
|
13
|
+
klass.instance_variable_set :@settings, settings
|
14
|
+
end
|
15
|
+
|
16
|
+
# @!attribute [r] settings
|
17
|
+
#
|
18
|
+
# @return [AbstractMapper::Settings]
|
19
|
+
# The configurable container of domain-specific settings
|
20
|
+
# (DSL commands and optimization rules along with corresponding
|
21
|
+
# AST builder and optimizer).
|
22
|
+
#
|
23
|
+
attr_reader :settings
|
24
|
+
|
25
|
+
# Configures domain-specific settings
|
26
|
+
#
|
27
|
+
# @param [Proc] block The block where rules and commands to be registered
|
28
|
+
#
|
29
|
+
# @return [self] itself
|
30
|
+
#
|
31
|
+
# @yield a block
|
32
|
+
#
|
33
|
+
def configure(&block)
|
34
|
+
@settings = Settings.new(&block)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the optimized AST
|
39
|
+
#
|
40
|
+
# @return [AbstractMapper::Branch]
|
41
|
+
#
|
42
|
+
def finalize
|
43
|
+
settings.optimizer.update(tree)
|
44
|
+
end
|
45
|
+
|
46
|
+
private # DSL commands
|
47
|
+
|
48
|
+
def tree
|
49
|
+
@tree ||= Branch.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def respond_to_missing?(*)
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def method_missing(name, *args, &block)
|
57
|
+
@tree = settings.builder.update(tree) { public_send(name, *args, &block) }
|
58
|
+
end
|
59
|
+
|
60
|
+
end # module DSL
|
61
|
+
|
62
|
+
end # class AbstractMapper
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# The collection of gem-specific errors
|
6
|
+
#
|
7
|
+
module Errors
|
8
|
+
|
9
|
+
# An exception to be raised when unknown DSL methods is used
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
#
|
13
|
+
class UnknownCommand < NameError
|
14
|
+
|
15
|
+
# @private
|
16
|
+
def initialize(name)
|
17
|
+
super "'#{name}' is not a registered DSL command"
|
18
|
+
freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
end # class UnknownCommand
|
22
|
+
|
23
|
+
end # module Errors
|
24
|
+
|
25
|
+
end # class AbstractMapper
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
module Errors
|
6
|
+
|
7
|
+
# An exception to be raised when wrong node is registered as a DSL command
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
class WrongNode < TypeError
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def initialize(node)
|
15
|
+
super "#{node} is not a subclass of AbstractMapper::Node"
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
end # class WrongNode
|
20
|
+
|
21
|
+
end # module Errors
|
22
|
+
|
23
|
+
end # class AbstractMapper
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
module Errors
|
6
|
+
|
7
|
+
# An exception to be raised when wrong node is registered as a DSL command
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
#
|
11
|
+
class WrongRule < TypeError
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def initialize(node)
|
15
|
+
super "#{node} is not a subclass of AbstractMapper::SoleRule"
|
16
|
+
freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
end # class WrongRule
|
20
|
+
|
21
|
+
end # module Errors
|
22
|
+
|
23
|
+
end # class AbstractMapper
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# The collection of gem-specific pure functions (transproc)
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
module Functions
|
10
|
+
|
11
|
+
extend Transproc::Registry
|
12
|
+
|
13
|
+
uses :map_array, from: Transproc::ArrayTransformations
|
14
|
+
|
15
|
+
# Applies the function to every element of array and removes empty values
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# fn = Functions[:filter, -> v { v - 1 if v > 3 }]
|
19
|
+
# fn[[1, 4, 5, 3, 2, 5, 9]]
|
20
|
+
# # => [3, 4, 4, 8]
|
21
|
+
#
|
22
|
+
# @param [Array] array
|
23
|
+
# @param [Proc] fn
|
24
|
+
#
|
25
|
+
# @return [Array]
|
26
|
+
#
|
27
|
+
def filter(array, fn)
|
28
|
+
map_array(array, fn).compact.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
# Applies the function to every consecutive pair of array elements,
|
32
|
+
# and removes empty values
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# function = -> a, b { (a == b) ? [a + b] : [a, b] }
|
36
|
+
# fn = Functions[:compact, function]
|
37
|
+
# fn[[1, 1, 2]] # => [4]
|
38
|
+
# fn[[1, 2, 2]] # => [1, 4]
|
39
|
+
#
|
40
|
+
# @param [Array] array
|
41
|
+
# @param [Proc] fn
|
42
|
+
# Anonymous function (proc, lambda), that takes two arguments
|
43
|
+
# and returns an array
|
44
|
+
#
|
45
|
+
# @return [Array]
|
46
|
+
#
|
47
|
+
def compact(array, fn)
|
48
|
+
array.each_with_object([]) do |i, a|
|
49
|
+
if a.empty?
|
50
|
+
a << i
|
51
|
+
else
|
52
|
+
a[-1] = fn.call(a.last, i)
|
53
|
+
a.flatten!
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Checks whether the class or module has given ancestor
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# fn = Functions[:subclass?, Module]
|
62
|
+
# fn[Class] # => true
|
63
|
+
# fn[Object] # => false
|
64
|
+
#
|
65
|
+
# @param [Module] subling
|
66
|
+
# @param [Module] ancestor
|
67
|
+
#
|
68
|
+
# @return [Boolean]
|
69
|
+
#
|
70
|
+
def subclass?(subling, ancestor)
|
71
|
+
subling.ancestors.include?(ancestor)
|
72
|
+
end
|
73
|
+
|
74
|
+
end # module Functions
|
75
|
+
|
76
|
+
end # class AbstractMapper
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class AbstractMapper
|
4
|
+
|
5
|
+
# An immutable node of the abstract syntax tree (AST), that describes
|
6
|
+
# either some "end-up" transformation, or a level of nested input data.
|
7
|
+
#
|
8
|
+
# Every node is expected to accept attributes and (possibly) block, and
|
9
|
+
# implement `#transproc` that implements the `#call` method to
|
10
|
+
# transform input data to the output.
|
11
|
+
#
|
12
|
+
# Nodes describe only the structure of AST, they know
|
13
|
+
# neither how to build the tree with DSL (see [AbstractMapper::Builder]),
|
14
|
+
# nor how to optimize it later (see [AbstractMapper::Optimizer]).
|
15
|
+
#
|
16
|
+
# @api public
|
17
|
+
#
|
18
|
+
class Node
|
19
|
+
|
20
|
+
# @!attribute [r] attributes
|
21
|
+
#
|
22
|
+
# @return [Array] The list of node-specific attributes
|
23
|
+
#
|
24
|
+
attr_reader :attributes
|
25
|
+
|
26
|
+
# @!attribute [r] block
|
27
|
+
#
|
28
|
+
# @return [Proc] The block given to initializer
|
29
|
+
#
|
30
|
+
attr_reader :block
|
31
|
+
|
32
|
+
# @private
|
33
|
+
def initialize(*attributes, &block)
|
34
|
+
@attributes = attributes.freeze
|
35
|
+
@block = block
|
36
|
+
freeze
|
37
|
+
end
|
38
|
+
|
39
|
+
# @!method transproc
|
40
|
+
# The transformation function for the branch
|
41
|
+
#
|
42
|
+
# @abstract
|
43
|
+
#
|
44
|
+
def transproc
|
45
|
+
Transproc::Function.new(-> v { v }, {})
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a human-readable string representating the node
|
49
|
+
#
|
50
|
+
# @return [String]
|
51
|
+
#
|
52
|
+
def inspect
|
53
|
+
"<#{self}>"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Converts the node into string with its name and attributes
|
57
|
+
#
|
58
|
+
# @return [String]
|
59
|
+
#
|
60
|
+
def to_s
|
61
|
+
"#{__name__}#{__attributes__}"
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def __name__
|
67
|
+
self.class.name.split("::").last
|
68
|
+
end
|
69
|
+
|
70
|
+
def __attributes__
|
71
|
+
"(#{attributes.map(&:inspect).join(", ")})" if attributes.any?
|
72
|
+
end
|
73
|
+
|
74
|
+
end # class Node
|
75
|
+
|
76
|
+
end # class AbstractMapper
|