abstract_mapper 0.0.1
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.
- 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
|