caruby-core 1.5.5 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +9 -0
- data/History.md +5 -1
- data/lib/caruby.rb +3 -5
- data/lib/caruby/caruby-src.tar.gz +0 -0
- data/lib/caruby/database.rb +53 -69
- data/lib/caruby/database/application_service.rb +25 -0
- data/lib/caruby/database/cache.rb +60 -0
- data/lib/caruby/database/fetched_matcher.rb +52 -38
- data/lib/caruby/database/lazy_loader.rb +4 -4
- data/lib/caruby/database/operation.rb +34 -0
- data/lib/caruby/database/persistable.rb +171 -86
- data/lib/caruby/database/persistence_service.rb +32 -34
- data/lib/caruby/database/persistifier.rb +100 -43
- data/lib/caruby/database/reader.rb +107 -85
- data/lib/caruby/database/reader_template_builder.rb +60 -0
- data/lib/caruby/database/saved_matcher.rb +3 -3
- data/lib/caruby/database/sql_executor.rb +88 -17
- data/lib/caruby/database/writer.rb +213 -177
- data/lib/caruby/database/writer_template_builder.rb +334 -0
- data/lib/caruby/{util → helpers}/controlled_value.rb +0 -0
- data/lib/caruby/{util → helpers}/coordinate.rb +4 -4
- data/lib/caruby/{util → helpers}/person.rb +3 -3
- data/lib/caruby/{util → helpers}/properties.rb +7 -9
- data/lib/caruby/{util → helpers}/roman.rb +2 -2
- data/lib/caruby/{util → helpers}/version.rb +1 -1
- data/lib/caruby/json/deserializer.rb +2 -2
- data/lib/caruby/json/serializer.rb +49 -7
- data/lib/caruby/metadata.rb +30 -0
- data/lib/caruby/metadata/java_property.rb +21 -0
- data/lib/caruby/metadata/propertied.rb +191 -0
- data/lib/caruby/metadata/property.rb +22 -0
- data/lib/caruby/metadata/property_characteristics.rb +201 -0
- data/lib/caruby/migration/migratable.rb +11 -182
- data/lib/caruby/rdbi/driver/jdbc.rb +446 -0
- data/lib/caruby/resource.rb +20 -823
- data/lib/caruby/version.rb +1 -1
- data/test/lib/caruby/database/cache_test.rb +54 -0
- data/test/lib/caruby/{util → helpers}/controlled_value_test.rb +3 -5
- data/test/lib/caruby/{util → helpers}/person_test.rb +4 -6
- data/test/lib/caruby/helpers/properties_test.rb +34 -0
- data/test/lib/caruby/{util → helpers}/roman_test.rb +2 -3
- data/test/lib/caruby/{util → helpers}/version_test.rb +2 -3
- data/test/lib/helper.rb +7 -0
- metadata +161 -214
- data/lib/caruby/cli/application.rb +0 -36
- data/lib/caruby/cli/command.rb +0 -202
- data/lib/caruby/csv/csv_mapper.rb +0 -159
- data/lib/caruby/csv/csvio.rb +0 -203
- data/lib/caruby/database/search_template_builder.rb +0 -56
- data/lib/caruby/database/store_template_builder.rb +0 -278
- data/lib/caruby/domain.rb +0 -193
- data/lib/caruby/domain/attribute.rb +0 -584
- data/lib/caruby/domain/attributes.rb +0 -628
- data/lib/caruby/domain/dependency.rb +0 -225
- data/lib/caruby/domain/id_alias.rb +0 -22
- data/lib/caruby/domain/importer.rb +0 -183
- data/lib/caruby/domain/introspection.rb +0 -176
- data/lib/caruby/domain/inverse.rb +0 -172
- data/lib/caruby/domain/inversible.rb +0 -90
- data/lib/caruby/domain/java_attribute.rb +0 -173
- data/lib/caruby/domain/merge.rb +0 -185
- data/lib/caruby/domain/metadata.rb +0 -142
- data/lib/caruby/domain/mixin.rb +0 -35
- data/lib/caruby/domain/properties.rb +0 -95
- data/lib/caruby/domain/reference_visitor.rb +0 -428
- data/lib/caruby/domain/uniquify.rb +0 -50
- data/lib/caruby/import/java.rb +0 -387
- data/lib/caruby/migration/migrator.rb +0 -918
- data/lib/caruby/migration/resource_module.rb +0 -9
- data/lib/caruby/migration/uniquify.rb +0 -17
- data/lib/caruby/util/attribute_path.rb +0 -44
- data/lib/caruby/util/cache.rb +0 -56
- data/lib/caruby/util/class.rb +0 -149
- data/lib/caruby/util/collection.rb +0 -1152
- data/lib/caruby/util/domain_extent.rb +0 -46
- data/lib/caruby/util/file_separator.rb +0 -65
- data/lib/caruby/util/inflector.rb +0 -27
- data/lib/caruby/util/log.rb +0 -95
- data/lib/caruby/util/math.rb +0 -12
- data/lib/caruby/util/merge.rb +0 -59
- data/lib/caruby/util/module.rb +0 -18
- data/lib/caruby/util/options.rb +0 -97
- data/lib/caruby/util/partial_order.rb +0 -35
- data/lib/caruby/util/pretty_print.rb +0 -204
- data/lib/caruby/util/stopwatch.rb +0 -74
- data/lib/caruby/util/topological_sync_enumerator.rb +0 -62
- data/lib/caruby/util/transitive_closure.rb +0 -55
- data/lib/caruby/util/tree.rb +0 -48
- data/lib/caruby/util/trie.rb +0 -37
- data/lib/caruby/util/uniquifier.rb +0 -30
- data/lib/caruby/util/validation.rb +0 -20
- data/lib/caruby/util/visitor.rb +0 -365
- data/lib/caruby/util/weak_hash.rb +0 -36
- data/test/lib/caruby/csv/csv_mapper_test.rb +0 -40
- data/test/lib/caruby/csv/csvio_test.rb +0 -69
- data/test/lib/caruby/database/persistable_test.rb +0 -92
- data/test/lib/caruby/domain/domain_test.rb +0 -112
- data/test/lib/caruby/domain/inversible_test.rb +0 -99
- data/test/lib/caruby/domain/reference_visitor_test.rb +0 -130
- data/test/lib/caruby/import/java_test.rb +0 -80
- data/test/lib/caruby/import/mixed_case_test.rb +0 -14
- data/test/lib/caruby/migration/test_case.rb +0 -102
- data/test/lib/caruby/test_case.rb +0 -230
- data/test/lib/caruby/util/cache_test.rb +0 -23
- data/test/lib/caruby/util/class_test.rb +0 -61
- data/test/lib/caruby/util/collection_test.rb +0 -398
- data/test/lib/caruby/util/command_test.rb +0 -55
- data/test/lib/caruby/util/domain_extent_test.rb +0 -60
- data/test/lib/caruby/util/file_separator_test.rb +0 -30
- data/test/lib/caruby/util/inflector_test.rb +0 -12
- data/test/lib/caruby/util/lazy_hash_test.rb +0 -34
- data/test/lib/caruby/util/merge_test.rb +0 -83
- data/test/lib/caruby/util/module_test.rb +0 -25
- data/test/lib/caruby/util/options_test.rb +0 -59
- data/test/lib/caruby/util/partial_order_test.rb +0 -42
- data/test/lib/caruby/util/pretty_print_test.rb +0 -85
- data/test/lib/caruby/util/properties_test.rb +0 -50
- data/test/lib/caruby/util/stopwatch_test.rb +0 -18
- data/test/lib/caruby/util/topological_sync_enumerator_test.rb +0 -69
- data/test/lib/caruby/util/transitive_closure_test.rb +0 -67
- data/test/lib/caruby/util/tree_test.rb +0 -23
- data/test/lib/caruby/util/trie_test.rb +0 -14
- data/test/lib/caruby/util/visitor_test.rb +0 -278
- data/test/lib/caruby/util/weak_hash_test.rb +0 -45
- data/test/lib/examples/clinical_trials/migration/migration_test.rb +0 -58
- data/test/lib/examples/clinical_trials/migration/test_case.rb +0 -38
data/lib/caruby/util/trie.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), 'tree')
|
2
|
-
|
3
|
-
# A Trie[http://en.wikipedia.org/wiki/Trie] is an associative access tree structure.
|
4
|
-
#
|
5
|
-
# @example
|
6
|
-
# trie = Trie.new
|
7
|
-
# trie[:a, :b] = 1
|
8
|
-
# trie[:a, :b] #=> 1
|
9
|
-
# trie[:a, :c] #=> nil
|
10
|
-
class Trie
|
11
|
-
# Creates a new empty Trie.
|
12
|
-
def initialize
|
13
|
-
@tree = Tree.new
|
14
|
-
end
|
15
|
-
|
16
|
-
# @return the value at the given trie path
|
17
|
-
def [](*path)
|
18
|
-
tree = @tree[nil, *path]
|
19
|
-
tree.value if tree
|
20
|
-
end
|
21
|
-
|
22
|
-
# @return the top_level Tree for this trie
|
23
|
-
def to_tree
|
24
|
-
@tree
|
25
|
-
end
|
26
|
-
|
27
|
-
# Sets the value for a node path.
|
28
|
-
#
|
29
|
-
# @example
|
30
|
-
# trie = Trie.new
|
31
|
-
# trie[:a, :b] = 1
|
32
|
-
# trie[:a, :b] #=> 1
|
33
|
-
def []=(*path_and_value)
|
34
|
-
value = path_and_value.pop
|
35
|
-
@tree.fill(nil, *path_and_value).value = value
|
36
|
-
end
|
37
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
# A test utility class to generate id qualifiers.
|
2
|
-
class Uniquifier
|
3
|
-
# Returns a relatively unique integral qualifier. Successive calls to this method
|
4
|
-
# within the same time zone spaced more than a millisecond apart return different
|
5
|
-
# integers. Each generated qualifier is greater than the previous by an unspecified
|
6
|
-
# amount.
|
7
|
-
def self.qualifier
|
8
|
-
# the first date that this method could be called
|
9
|
-
@first ||= Date.new(2000, 01, 01)
|
10
|
-
# days as integer + milliseconds as fraction since the first date
|
11
|
-
diff = DateTime.now - @first
|
12
|
-
# shift a tenth of a milli up into the integer portion
|
13
|
-
decimillis = diff * 24 * 60 * 60 * 10000
|
14
|
-
# truncate the fraction
|
15
|
-
decimillis.truncate
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
class String
|
20
|
-
# Returns a relatively unique value obtained from the specified base value.
|
21
|
-
# Spaces are removed, e.g.:
|
22
|
-
# Uniquifier.uniquify('Test Name')
|
23
|
-
# might produce:
|
24
|
-
# Test_Name_3309388006
|
25
|
-
#
|
26
|
-
# The suffix is generated by {Uniquifier.qualifier}.
|
27
|
-
def uniquify
|
28
|
-
gsub(' ', '_') + "_#{Uniquifier.qualifier}"
|
29
|
-
end
|
30
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
# Raised when an object fails a validation test.
|
2
|
-
class ValidationError < RuntimeError; end
|
3
|
-
|
4
|
-
class Object
|
5
|
-
# Returns whether this object is nil, false, empty, or a whitespace string.
|
6
|
-
# For example, "", " ", +nil+, [], and {} are blank.
|
7
|
-
#
|
8
|
-
# This method is borrowed from Rails ActiveSupport.
|
9
|
-
def blank?
|
10
|
-
respond_to?(:empty?) ? empty? : !self
|
11
|
-
end
|
12
|
-
|
13
|
-
# Returns whether this object is nil, empty, or a whitespace string.
|
14
|
-
# For example, "", " ", +nil+, [], and {} return +true+.
|
15
|
-
#
|
16
|
-
# This method differs from blank? in that +false+ is an allowed value.
|
17
|
-
def nil_or_empty?
|
18
|
-
nil? or (respond_to?(:empty?) and empty?)
|
19
|
-
end
|
20
|
-
end
|
data/lib/caruby/util/visitor.rb
DELETED
@@ -1,365 +0,0 @@
|
|
1
|
-
require 'caruby/util/collection'
|
2
|
-
require 'caruby/util/options'
|
3
|
-
|
4
|
-
# Enumerator overwrites to_enum, so include it first
|
5
|
-
require 'enumerator'
|
6
|
-
require 'generator'
|
7
|
-
|
8
|
-
module CaRuby
|
9
|
-
# Error raised on a visit failure.
|
10
|
-
class VisitError < RuntimeError; end
|
11
|
-
|
12
|
-
# Visitor traverses items and applies an operation, e.g.:
|
13
|
-
# class Node
|
14
|
-
# attr_accessor :children, :value
|
15
|
-
# def initialize(value, parent=nil)
|
16
|
-
# @value = value
|
17
|
-
# @children = []
|
18
|
-
# @parent = parent
|
19
|
-
# @parent.children << self if @parent
|
20
|
-
# end
|
21
|
-
# end
|
22
|
-
# parent = Node.new(1)
|
23
|
-
# child = Node.new(2, parent)
|
24
|
-
# multiplier = 2
|
25
|
-
# CaRuby::Visitor.new { |node| node.children }.visit(parent) { |node| node.value *= multiplier } #=> 2
|
26
|
-
# parent.value #=> 2
|
27
|
-
# child.value #=> 4
|
28
|
-
#
|
29
|
-
# The visit result is the result of evaluating the operation block on the initial visited node.
|
30
|
-
# Visiting a collection returns an array of the result of visiting each member of the collection,
|
31
|
-
# e.g. augmenting the preceding example:
|
32
|
-
# parent2 = Node.new(3)
|
33
|
-
# child2 = Node.new(4, parent2)
|
34
|
-
# CaRuby::Visitor.new { |node| node.children }.visit([parent, parent2]) { |node| node.value *= multiplier } #=> [2, 6]
|
35
|
-
# Each visit captures the visit result in the +visited+ hash, e.g.:
|
36
|
-
# parent = Node.new(1)
|
37
|
-
# child = Node.new(2, parent)
|
38
|
-
# visitor = CaRuby::Visitor.new { |node| node.children }
|
39
|
-
# visitor.visit([parent]) { |node| node.value += 1 }
|
40
|
-
# parent.value #=> 2
|
41
|
-
# visitor.visited[parent] #=> 2
|
42
|
-
# child.value #=> 3
|
43
|
-
# visitor.visited[child] #=> 3
|
44
|
-
#
|
45
|
-
# A +return+ from the operation block terminates the visit and exits from the defining scope with the block return value,
|
46
|
-
# e.g. given the preceding example:
|
47
|
-
# def increment(parent, limit)
|
48
|
-
# CaRuby::Visitor.new { |node| node.children }.visit(parent) { |node| node.value < limit ? node.value += 1 : return }
|
49
|
-
# end
|
50
|
-
# increment(parent, 2) #=> nil
|
51
|
-
# parent.value #=> 2
|
52
|
-
# child.value #=> 2
|
53
|
-
#
|
54
|
-
# The to_enum method allows navigator iteration, e.g.:
|
55
|
-
# CaRuby::Visitor.new { |node| node.children }.to_enum(parent).detect { |node| node.value == 2 }
|
56
|
-
class Visitor
|
57
|
-
|
58
|
-
attr_reader :options, :visited, :lineage, :cycles
|
59
|
-
|
60
|
-
# Creates a new Visitor which traverses the child objects returned by the navigator block.
|
61
|
-
# The navigator block takes a parent argument and returns the children to visit. If the block
|
62
|
-
# return value is not nil and not a collection, then the returned object is visited. A nil or
|
63
|
-
# empty child is not visited.
|
64
|
-
#
|
65
|
-
# options is a symbol => value hash. A Symbol argument _symbol_ is the same as +{+_symbol_+=>true}+.
|
66
|
-
# Supported options include the follwing:
|
67
|
-
#
|
68
|
-
# The value of :depth_first can be +true+, +false+ or a Proc. If the value is a Proc, then
|
69
|
-
# value determines whether a child is visited depth-first. See the {#visit} method for more information.
|
70
|
-
#
|
71
|
-
# If the the :visited option is set, then the visited nodes are recorded in the :visited option hash.
|
72
|
-
# In that case, the {#visit} call does not clear the visited hash.
|
73
|
-
#
|
74
|
-
# If the :operator option is set, then the visit operator block is called when a node is visited.
|
75
|
-
# The operator block argument is the visited node.
|
76
|
-
#
|
77
|
-
# @param [Symbol, {Symbol => Object}] opts the visit options. A symbol argument is the same
|
78
|
-
# as symbol => true
|
79
|
-
# @option opts [String] :depth_first depth-first traversal
|
80
|
-
# @option opts [Hash] :visited the hash to use when recording visited node => value associations
|
81
|
-
# @option opts [Proc] :operator the visit operator block
|
82
|
-
# @option opts [String] :prune_cycle flag indicating whether to exclude cycles to the root in a visit
|
83
|
-
# @yield [parent] the parent being visited
|
84
|
-
def initialize(opts=nil, &navigator)
|
85
|
-
@navigator = navigator
|
86
|
-
@options = Options.to_hash(opts)
|
87
|
-
@depth_first_flag = @options[:depth_first]
|
88
|
-
@visited = @options[:visited] || {}
|
89
|
-
@prune_cycle_flag = @options[:prune_cycle]
|
90
|
-
@lineage = []
|
91
|
-
@cycles = []
|
92
|
-
@exclude = Set.new
|
93
|
-
end
|
94
|
-
|
95
|
-
# Navigates to node and the children returned by this Visitor's navigator block.
|
96
|
-
# Applies the optional operator block to each child node if the block is given to this method.
|
97
|
-
# Returns the result of the operator block if given, or the node itself otherwise.
|
98
|
-
#
|
99
|
-
# The nodes to visit from a parent node are determined in the following sequence:
|
100
|
-
# * Return if the parent node has already been visited.
|
101
|
-
# * If depth_first, then call the navigator block defined in the initializer on
|
102
|
-
# the parent node and visit each child node.
|
103
|
-
# * Visit the parent node.
|
104
|
-
# * If not depth-first, then call the navigator block defined in the initializer
|
105
|
-
# on the parent node and visit each child node.
|
106
|
-
# The :depth option value constrains child traversal to that number of levels.
|
107
|
-
#
|
108
|
-
# This method first clears the _visited_ hash, unless the :visited option was set in the initializer.
|
109
|
-
#
|
110
|
-
# @param node the root object to visit
|
111
|
-
# @yield [visited] an operator applied to each visited object
|
112
|
-
# @yieldparam visited the object currently being visited
|
113
|
-
# @return the result of the yield block on node, or node itself if no block is given
|
114
|
-
def visit(node, &operator)
|
115
|
-
visit_root(node, &operator)
|
116
|
-
end
|
117
|
-
|
118
|
-
# @param node the node to check
|
119
|
-
# @return whether the node was visited
|
120
|
-
def visited?(node)
|
121
|
-
@visited.has_key?(node)
|
122
|
-
end
|
123
|
-
|
124
|
-
# @return the top node visited
|
125
|
-
def root
|
126
|
-
@lineage.first
|
127
|
-
end
|
128
|
-
|
129
|
-
# @return the current node being visited
|
130
|
-
def current
|
131
|
-
@lineage.last
|
132
|
-
end
|
133
|
-
|
134
|
-
# @return the node most recently passed as an argument to this visitor's navigator block,
|
135
|
-
# or nil if visiting the first node
|
136
|
-
def parent
|
137
|
-
@lineage[-2]
|
138
|
-
end
|
139
|
-
|
140
|
-
# @return [Enumerable] iterator over each visited node
|
141
|
-
def to_enum(node)
|
142
|
-
# JRuby could use Generator instead, but that results in dire behavior on any error
|
143
|
-
# by crashing with an elided Java lineage trace.
|
144
|
-
VisitorEnumerator.new(self, node)
|
145
|
-
end
|
146
|
-
|
147
|
-
# Returns a new visitor that traverses a collection of parent nodes in lock-step fashion using
|
148
|
-
# this visitor. The synced {#visit} method applies the visit operator block to an array of child
|
149
|
-
# nodes taken from each parent node, e.g.:
|
150
|
-
# parent1 = Node.new(1)
|
151
|
-
# child11 = Node.new(2, parent1)
|
152
|
-
# child12 = Node.new(3, parent1)
|
153
|
-
# parent2 = Node.new(1)
|
154
|
-
# child21 = Node.new(3, parent2)
|
155
|
-
# CaRuby::Visitor.new { |node| node.children }.sync.to_enum.to_a #=> [
|
156
|
-
# [parent1, parent2],
|
157
|
-
# [child11, child21],
|
158
|
-
# [child12, nil]
|
159
|
-
# ]
|
160
|
-
#
|
161
|
-
# By default, the children are grouped in enumeration order. If a block is given to this method,
|
162
|
-
# then the block is called to match child nodes, e.g. using the above example:
|
163
|
-
# visitor = CaRuby::Visitor.new { |node| node.children }
|
164
|
-
# synced = visitor.sync { |nodes, others| nodes.to_compact_hash { others.detect { |other| node.value == other.value } } }
|
165
|
-
# synced.to_enum.to_a #=> [
|
166
|
-
# [parent1, parent2],
|
167
|
-
# [child11, nil],
|
168
|
-
# [child12, child21]
|
169
|
-
# ]
|
170
|
-
#
|
171
|
-
# @yield [nodes, others] matches node in others (optional)
|
172
|
-
# @yieldparam [<Resource>] nodes the visited nodes to match
|
173
|
-
# @yieldparam [<Resource>] others the candidates for matching the node
|
174
|
-
def sync(&matcher)
|
175
|
-
SyncVisitor.new(self, &matcher)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Returns a new Visitor which determines which nodes to visit by applying the given block
|
179
|
-
# to this visitor, e.g.:
|
180
|
-
# CaRuby::Visitor.new { |node| node.children }.filter { |parent, children| children.first if parent.age >= 18 }
|
181
|
-
# navigates to the first child of parents 18 or older.
|
182
|
-
#
|
183
|
-
# The filter block arguments consist of a parent node and an array of children nodes for the parent.
|
184
|
-
# The block can return nil, a single node to visit or a collection of nodes to visit.
|
185
|
-
#
|
186
|
-
# @return [Visitor] the filter visitor
|
187
|
-
# @yield [parent, children] the filter to select which of the children to visit next
|
188
|
-
# @yieldparam parent the currently visited node
|
189
|
-
# @yieldparam children the nodes slated by this Visitor to visit next
|
190
|
-
# @raise [ArgumentError] if a block is not given to this method
|
191
|
-
def filter
|
192
|
-
raise ArgumentError.new("Filter block not given to visitor filter method") unless block_given?
|
193
|
-
Visitor.new(@options) { |node| yield(node, node_children(node)) }
|
194
|
-
end
|
195
|
-
|
196
|
-
protected
|
197
|
-
|
198
|
-
# Resets this visitor's state in preparation for a new visit.
|
199
|
-
def clear
|
200
|
-
# clear the lineage
|
201
|
-
@lineage.clear
|
202
|
-
# if the visited hash is not shared, then clear it
|
203
|
-
@visited.clear unless @options.has_key?(:visited)
|
204
|
-
# clear the cycles
|
205
|
-
@cycles.clear
|
206
|
-
end
|
207
|
-
|
208
|
-
# Sets the visited hash.
|
209
|
-
def visited=(hash)
|
210
|
-
@visited = hash ||= {}
|
211
|
-
end
|
212
|
-
|
213
|
-
# Visits the given node using the block given to this method.
|
214
|
-
# The default block returns node.
|
215
|
-
def visit_node(node)
|
216
|
-
@visited[node] = block_given? ? yield(node) : node
|
217
|
-
end
|
218
|
-
|
219
|
-
# Returns the children to visit for the given node.
|
220
|
-
def node_children(node)
|
221
|
-
children = @navigator.call(node)
|
222
|
-
return Array::EMPTY_ARRAY if children.nil?
|
223
|
-
Enumerable === children ? children.to_a.compact : [children]
|
224
|
-
end
|
225
|
-
|
226
|
-
private
|
227
|
-
|
228
|
-
def depth_first?
|
229
|
-
@depth_first_flag
|
230
|
-
end
|
231
|
-
|
232
|
-
# Visits the root node and all descendants.
|
233
|
-
def visit_root(node, &operator)
|
234
|
-
clear
|
235
|
-
prune_cycle_nodes(node) if @prune_cycle_flag
|
236
|
-
# visit the root node
|
237
|
-
visit_recursive(node, &operator)
|
238
|
-
end
|
239
|
-
|
240
|
-
# Excludes the internal nodes in cycles starting and ending at the given root.
|
241
|
-
def prune_cycle_nodes(root)
|
242
|
-
@exclude.clear
|
243
|
-
# visit the root, which will detect cycles, and remove the visited nodes afterwords
|
244
|
-
@prune_cycle_flag = false
|
245
|
-
to_enum(root).collect.each { |node| @visited.delete(node) }
|
246
|
-
@prune_cycle_flag = true
|
247
|
-
# add each cyclic internal node to the exclude list
|
248
|
-
@cycles.each { |cycle| cycle[1...-1].each { |node| @exclude << node } if cycle.first == root }
|
249
|
-
end
|
250
|
-
|
251
|
-
def visit_recursive(node, &operator)
|
252
|
-
# bail if no node or the node is specifically excluded
|
253
|
-
return if node.nil? or @exclude.include?(node)
|
254
|
-
# return the visited value if the node has already been visited
|
255
|
-
if @visited.has_key?(node) then
|
256
|
-
#capture a cycle
|
257
|
-
index = @lineage.index(node)
|
258
|
-
if index then
|
259
|
-
cycle = @lineage[index..-1] << node
|
260
|
-
@cycles << cycle
|
261
|
-
end
|
262
|
-
return @visited[node]
|
263
|
-
end
|
264
|
-
# return nil if the node has not been visited but has been navigated in a depth-first visit
|
265
|
-
return if @lineage.include?(node)
|
266
|
-
# all systems go: visit the node graph
|
267
|
-
visit_node_and_children(node, &operator)
|
268
|
-
end
|
269
|
-
|
270
|
-
def visit_node_and_children(node, &operator)
|
271
|
-
# set the current node
|
272
|
-
@lineage.push(node)
|
273
|
-
# if depth-first, then visit the children before the current node
|
274
|
-
visit_children(node, &operator) if depth_first?
|
275
|
-
# visit the current node
|
276
|
-
result = visit_node(node, &operator)
|
277
|
-
# if not depth-first, then visit the children after the current node
|
278
|
-
visit_children(node, &operator) unless depth_first?
|
279
|
-
@lineage.pop
|
280
|
-
# return the visit result
|
281
|
-
result
|
282
|
-
end
|
283
|
-
|
284
|
-
def visit_children(node, &operator)
|
285
|
-
children = node_children(node)
|
286
|
-
children.each { |child| visit_recursive(child, &operator) }
|
287
|
-
end
|
288
|
-
|
289
|
-
class VisitorEnumerator
|
290
|
-
include Enumerable
|
291
|
-
|
292
|
-
def initialize(visitor, node)
|
293
|
-
@visitor = visitor
|
294
|
-
@root = node
|
295
|
-
end
|
296
|
-
|
297
|
-
def each
|
298
|
-
@visitor.visit(@root) { |node| yield(node) }
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
class SyncVisitor < Visitor
|
303
|
-
# @param [Visitor] visitor the Visitor which will visit synchronized input
|
304
|
-
# @yield (see Visitor#sync)
|
305
|
-
def initialize(visitor, &matcher)
|
306
|
-
# the next node to visit is an array of child node pairs matched by the given matcher block
|
307
|
-
super() { |nodes| match_children(visitor, nodes, &matcher) }
|
308
|
-
end
|
309
|
-
|
310
|
-
# Visits the given pair of nodes.
|
311
|
-
#
|
312
|
-
# Raises ArgumentError if nodes does not consist of either two node arguments or one two-item Array
|
313
|
-
# argument.
|
314
|
-
def visit(*nodes)
|
315
|
-
if nodes.size == 1 then
|
316
|
-
nodes = nodes.first
|
317
|
-
raise ArgumentError.new("Sync visitor requires a pair of entry nodes") unless nodes.size == 2
|
318
|
-
end
|
319
|
-
super(nodes)
|
320
|
-
end
|
321
|
-
|
322
|
-
# Returns an Enumerable which applies the given block to each matched node starting at the given nodes.
|
323
|
-
#
|
324
|
-
# Raises ArgumentError if nodes does not consist of either two node arguments or one two-item Array
|
325
|
-
# argument.
|
326
|
-
def to_enum(*nodes)
|
327
|
-
if nodes.size == 1 then
|
328
|
-
nodes = nodes.first
|
329
|
-
raise ArgumentError.new("Sync visitor requires a pair of entry nodes") unless nodes.size == 2
|
330
|
-
end
|
331
|
-
super(nodes)
|
332
|
-
end
|
333
|
-
|
334
|
-
private
|
335
|
-
|
336
|
-
# Returns an array of arrays of matched children from the given parent nodes. The children are matched
|
337
|
-
# using the block given to this method, if supplied, or by index otherwise.
|
338
|
-
#
|
339
|
-
# @see #sync a usage example
|
340
|
-
# @yield (see Visitor#sync)
|
341
|
-
def match_children(visitor, nodes)
|
342
|
-
# the parent nodes
|
343
|
-
p1, p2 = nodes
|
344
|
-
# this visitor's children
|
345
|
-
c1 = visitor.node_children(p1)
|
346
|
-
c2 = p2 ? visitor.node_children(p2) : []
|
347
|
-
|
348
|
-
# Apply the matcher block on each of this visitor's children and the other children.
|
349
|
-
# If no block is given, then group the children by index, which is the transpose of the array of
|
350
|
-
# children arrays.
|
351
|
-
if block_given? then
|
352
|
-
# Match each item in the first children array to an item from the second children array using
|
353
|
-
# then given block.
|
354
|
-
matches = yield(c1, c2)
|
355
|
-
c1.map { |c| [c, matches[c]] }
|
356
|
-
else
|
357
|
-
# Ensure that both children arrays are the same size.
|
358
|
-
others = c2.size <= c1.size ? c2.fill(nil, c2.size...c1.size) : c2[0, c1.size]
|
359
|
-
# The children grouped by index is the transpose of the array of children arrays.
|
360
|
-
[c1, others].transpose
|
361
|
-
end
|
362
|
-
end
|
363
|
-
end
|
364
|
-
end
|
365
|
-
end
|