bahuvrihi-tap 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History +69 -0
- data/MIT-LICENSE +21 -0
- data/README +119 -0
- data/bin/tap +114 -0
- data/cmd/console.rb +42 -0
- data/cmd/destroy.rb +16 -0
- data/cmd/generate.rb +16 -0
- data/cmd/run.rb +126 -0
- data/doc/Class Reference +362 -0
- data/doc/Command Reference +153 -0
- data/doc/Tutorial +237 -0
- data/lib/tap.rb +32 -0
- data/lib/tap/app.rb +720 -0
- data/lib/tap/constants.rb +8 -0
- data/lib/tap/env.rb +640 -0
- data/lib/tap/file_task.rb +547 -0
- data/lib/tap/generator/base.rb +109 -0
- data/lib/tap/generator/destroy.rb +37 -0
- data/lib/tap/generator/generate.rb +61 -0
- data/lib/tap/generator/generators/command/command_generator.rb +21 -0
- data/lib/tap/generator/generators/command/templates/command.erb +32 -0
- data/lib/tap/generator/generators/config/config_generator.rb +26 -0
- data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
- data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +27 -0
- data/lib/tap/generator/generators/file_task/templates/file.txt +11 -0
- data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +33 -0
- data/lib/tap/generator/generators/file_task/templates/test.erb +29 -0
- data/lib/tap/generator/generators/root/root_generator.rb +55 -0
- data/lib/tap/generator/generators/root/templates/Rakefile +86 -0
- data/lib/tap/generator/generators/root/templates/gemspec +27 -0
- data/lib/tap/generator/generators/root/templates/tapfile +8 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +5 -0
- data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
- data/lib/tap/generator/generators/task/task_generator.rb +27 -0
- data/lib/tap/generator/generators/task/templates/task.erb +14 -0
- data/lib/tap/generator/generators/task/templates/test.erb +21 -0
- data/lib/tap/generator/manifest.rb +14 -0
- data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
- data/lib/tap/patches/rake/testtask.rb +55 -0
- data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
- data/lib/tap/patches/ruby19/parsedate.rb +16 -0
- data/lib/tap/root.rb +581 -0
- data/lib/tap/support/aggregator.rb +55 -0
- data/lib/tap/support/assignments.rb +172 -0
- data/lib/tap/support/audit.rb +418 -0
- data/lib/tap/support/batchable.rb +47 -0
- data/lib/tap/support/batchable_class.rb +107 -0
- data/lib/tap/support/class_configuration.rb +194 -0
- data/lib/tap/support/command_line.rb +98 -0
- data/lib/tap/support/comment.rb +270 -0
- data/lib/tap/support/configurable.rb +114 -0
- data/lib/tap/support/configurable_class.rb +296 -0
- data/lib/tap/support/configuration.rb +122 -0
- data/lib/tap/support/constant.rb +70 -0
- data/lib/tap/support/constant_utils.rb +127 -0
- data/lib/tap/support/declarations.rb +111 -0
- data/lib/tap/support/executable.rb +111 -0
- data/lib/tap/support/executable_queue.rb +82 -0
- data/lib/tap/support/framework.rb +71 -0
- data/lib/tap/support/framework_class.rb +199 -0
- data/lib/tap/support/instance_configuration.rb +147 -0
- data/lib/tap/support/lazydoc.rb +428 -0
- data/lib/tap/support/manifest.rb +89 -0
- data/lib/tap/support/run_error.rb +39 -0
- data/lib/tap/support/shell_utils.rb +71 -0
- data/lib/tap/support/summary.rb +30 -0
- data/lib/tap/support/tdoc.rb +404 -0
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
- data/lib/tap/support/templater.rb +180 -0
- data/lib/tap/support/validation.rb +410 -0
- data/lib/tap/support/versions.rb +97 -0
- data/lib/tap/task.rb +259 -0
- data/lib/tap/tasks/dump.rb +56 -0
- data/lib/tap/tasks/rake.rb +93 -0
- data/lib/tap/test.rb +37 -0
- data/lib/tap/test/env_vars.rb +29 -0
- data/lib/tap/test/file_methods.rb +377 -0
- data/lib/tap/test/script_methods.rb +144 -0
- data/lib/tap/test/subset_methods.rb +420 -0
- data/lib/tap/test/tap_methods.rb +237 -0
- data/lib/tap/workflow.rb +187 -0
- metadata +145 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# Aggregator allows thread-safe collection of Audits, organized
|
5
|
+
# by Audit#_current_source.
|
6
|
+
class Aggregator < Monitor
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super
|
10
|
+
clear
|
11
|
+
end
|
12
|
+
|
13
|
+
# Clears self of all audits.
|
14
|
+
def clear
|
15
|
+
synchronize { self.hash = Hash.new }
|
16
|
+
end
|
17
|
+
|
18
|
+
# The total number of audits recorded in self.
|
19
|
+
def size
|
20
|
+
synchronize { hash.values.inject(0) {|sum, array| sum + array.length} }
|
21
|
+
end
|
22
|
+
|
23
|
+
# True if size == 0
|
24
|
+
def empty?
|
25
|
+
synchronize { hash.empty? }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Stores the Audit according to _result._current_source
|
29
|
+
def store(_result)
|
30
|
+
synchronize { (hash[_result._current_source] ||= []) << _result }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retreives all aggregated audits for the specified source.
|
34
|
+
def retrieve(source)
|
35
|
+
synchronize { hash[source] }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Retreives all audits for the input sources, joined into an array.
|
39
|
+
def retrieve_all(*sources)
|
40
|
+
synchronize do
|
41
|
+
sources.collect {|src| hash[src] }.flatten.compact
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Converts self to a hash of (source, audits) pairs.
|
46
|
+
def to_hash
|
47
|
+
hash.dup
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
attr_accessor :hash # :nodoc:
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module Tap
|
2
|
+
module Support
|
3
|
+
|
4
|
+
# Assignments defines an array of [key, values] pairs that tracks
|
5
|
+
# which values are assigned to a particular key. A value may only
|
6
|
+
# be assigned to one key at a time.
|
7
|
+
#
|
8
|
+
# Assignments tracks the order in which keys are declared, and the
|
9
|
+
# order in which values are assigned to a key. This behavior is
|
10
|
+
# used by ClassConfiguration to track the order in which configurations
|
11
|
+
# are assigned to a class; the order, in turn, is used in the formation
|
12
|
+
# of config files, command line documentation, etc.
|
13
|
+
#
|
14
|
+
# === Example
|
15
|
+
#
|
16
|
+
# a = Assignments.new
|
17
|
+
# a.assign(:one, 'one')
|
18
|
+
# a.assign(:two, 'two')
|
19
|
+
# a.assign(:one, 'ONE')
|
20
|
+
# a.to_a # => [[:one, ['one', 'ONE']], [:two, ['two']]]
|
21
|
+
#
|
22
|
+
# b = Assignments.new(a)
|
23
|
+
# b.to_a # => [[:one, ['one', 'ONE']], [:two, ['two']]]
|
24
|
+
#
|
25
|
+
# b.unassign('one')
|
26
|
+
# b.assign(:one, 1)
|
27
|
+
# b.to_a # => [[:one, ['ONE', 1]], [:two, ['two']]]
|
28
|
+
# a.to_a # => [[:one, ['one', 'ONE']], [:two, ['two']]]
|
29
|
+
#
|
30
|
+
#--
|
31
|
+
# TODO:
|
32
|
+
# Assignments may be optimizable... check if an alternate internal
|
33
|
+
# storage can be made faster or to take up less memory. Not that
|
34
|
+
# that much can be gained period...
|
35
|
+
class Assignments
|
36
|
+
include Enumerable
|
37
|
+
|
38
|
+
def initialize(parent=nil)
|
39
|
+
existing_array = case parent
|
40
|
+
when Assignments then parent.array
|
41
|
+
when Array then parent
|
42
|
+
when nil then []
|
43
|
+
else
|
44
|
+
raise ArgumentError.new("cannot convert #{parent.class} to Assignments, Array, or nil")
|
45
|
+
end
|
46
|
+
|
47
|
+
@array = []
|
48
|
+
existing_array.each do |key, values|
|
49
|
+
assign(key, *values)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Adds the key to the declarations.
|
54
|
+
def declare(key)
|
55
|
+
array << [key, []] unless declared?(key)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Removes all values for the specified key and
|
59
|
+
# removes the key from declarations.
|
60
|
+
def undeclare(key)
|
61
|
+
array.delete_if {|k, values| k == key}
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns true if the key is declared.
|
65
|
+
def declared?(key)
|
66
|
+
array.each do |k, values|
|
67
|
+
return true if k == key
|
68
|
+
end
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns an array of all the declared keys
|
73
|
+
def declarations
|
74
|
+
array.collect {|key, values| key }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Assigns the specified values to the key. The key will
|
78
|
+
# be declared, if necessary. Raises an error if the key
|
79
|
+
# is nil.
|
80
|
+
def assign(key, *values)
|
81
|
+
raise ArgumentError.new("nil keys are not allowed") if key == nil
|
82
|
+
|
83
|
+
# partition the input values into existing and new
|
84
|
+
# values, then check for conflicts.
|
85
|
+
current_values = self.values
|
86
|
+
existing_values, new_values = values.partition {|value| current_values.include?(value) }
|
87
|
+
|
88
|
+
conflicts = []
|
89
|
+
existing_values.collect do |value|
|
90
|
+
current_key = key_for(value)
|
91
|
+
if current_key != key
|
92
|
+
conflicts << "#{value} (#{key}) already assigned to #{current_key}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
unless conflicts.empty?
|
97
|
+
raise ArgumentError.new(conflicts.join("\n"))
|
98
|
+
end
|
99
|
+
|
100
|
+
declare(key)
|
101
|
+
values_for(key).concat new_values
|
102
|
+
end
|
103
|
+
|
104
|
+
# Removes the specified value.
|
105
|
+
def unassign(value)
|
106
|
+
array.each do |key, values|
|
107
|
+
values.delete(value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns true if the value has been assigned to a key.
|
112
|
+
def assigned?(value)
|
113
|
+
array.each do |key, values|
|
114
|
+
return true if values.include?(value)
|
115
|
+
end
|
116
|
+
false
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns the ordered values as an array
|
120
|
+
def values
|
121
|
+
array.collect {|key, values| values}.flatten
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns the key for the specified value, or nil
|
125
|
+
# if the value is unassigned.
|
126
|
+
def key_for(value)
|
127
|
+
array.each do |key, values|
|
128
|
+
return key if values.include?(value)
|
129
|
+
end
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the values for the specified key, or nil if
|
134
|
+
# the key cannot be found.
|
135
|
+
def values_for(key)
|
136
|
+
array.each do |k, values|
|
137
|
+
return values if k == key
|
138
|
+
end
|
139
|
+
nil
|
140
|
+
end
|
141
|
+
|
142
|
+
# Yields each key, value pair in the order in which
|
143
|
+
# the keys were declared. Keys with no values are
|
144
|
+
# skipped.
|
145
|
+
def each
|
146
|
+
array.each do |key, values|
|
147
|
+
values.each {|value| yield(key, value) }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Yields each key, values pair in the order in which
|
152
|
+
# the keys were declared.
|
153
|
+
def each_pair
|
154
|
+
array.each do |key, values|
|
155
|
+
yield(key, values)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns the [key, values] as an array
|
160
|
+
def to_a
|
161
|
+
array.collect {|key, values| [key, values.dup] }
|
162
|
+
end
|
163
|
+
|
164
|
+
protected
|
165
|
+
|
166
|
+
# An array of [key, values] arrays tracking the
|
167
|
+
# key and order in which values were assigned.
|
168
|
+
attr_reader :array
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,418 @@
|
|
1
|
+
autoload(:PP, 'pp')
|
2
|
+
|
3
|
+
module Tap
|
4
|
+
module Support
|
5
|
+
|
6
|
+
# Marks the merge of multiple Audit trails
|
7
|
+
class AuditMerge < Array
|
8
|
+
def ==(another)
|
9
|
+
another.kind_of?(AuditMerge) && super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Marks a split in an Audit trail
|
14
|
+
class AuditSplit
|
15
|
+
attr_reader :block
|
16
|
+
def initialize(block) @block = block end
|
17
|
+
|
18
|
+
def ==(another)
|
19
|
+
another.kind_of?(AuditSplit) && another.block == block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Marks the expansion of an Audit trail
|
24
|
+
class AuditExpand
|
25
|
+
attr_reader :index
|
26
|
+
def initialize(index) @index = index end
|
27
|
+
|
28
|
+
def ==(another)
|
29
|
+
another.kind_of?(AuditExpand) && another.index == index
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Audit provides a way to track the values (inputs and results) passed
|
34
|
+
# among tasks or, more generally, any Executable method. Audits allow
|
35
|
+
# you to track inputs as they make their way through a workflow, and
|
36
|
+
# have great utility in debugging and record keeping.
|
37
|
+
#
|
38
|
+
# During execution, the group of inputs for a task are used to initialize
|
39
|
+
# an Audit. These inputs mark the begining of an audit trail; every
|
40
|
+
# task that processes them (including the first) adds to the trail by
|
41
|
+
# recording it's result using itself as the 'source' of the result.
|
42
|
+
#
|
43
|
+
# Since Audits are meant to be fairly general structures, they can take
|
44
|
+
# any object as a source, so for illustration lets use some symbols:
|
45
|
+
#
|
46
|
+
# # initialize a new audit
|
47
|
+
# a = Audit.new(1, nil)
|
48
|
+
#
|
49
|
+
# # record some values
|
50
|
+
# a._record(:A, 2)
|
51
|
+
# a._record(:B, 3)
|
52
|
+
#
|
53
|
+
# Now you can pull up the source and value trails, as well as
|
54
|
+
# information like the current and original values:
|
55
|
+
#
|
56
|
+
# a._source_trail # => [nil, :A, :B]
|
57
|
+
# a._value_trail # => [1, 2, 3]
|
58
|
+
#
|
59
|
+
# a._original # => 1
|
60
|
+
# a._original_source # => nil
|
61
|
+
#
|
62
|
+
# a._current # => 3
|
63
|
+
# a._current_source # => :B
|
64
|
+
#
|
65
|
+
# Merges are supported by using an array of the merging trails (internally
|
66
|
+
# an AuditMerge) as the source, and an array of the merging values as the
|
67
|
+
# initial value.
|
68
|
+
#
|
69
|
+
# b = Audit.new(10, nil)
|
70
|
+
# b._record(:C, 11)
|
71
|
+
# b._record(:D, 12)
|
72
|
+
#
|
73
|
+
# c = Audit.merge(a, b)
|
74
|
+
# c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]] ]
|
75
|
+
# c._value_trail # => [ [[1,2,3], [10, 11, 12]] ]
|
76
|
+
# c._current # => [3, 12]
|
77
|
+
#
|
78
|
+
# c._record(:E, "a string value")
|
79
|
+
# c._record(:F, {'a' => 'hash value'})
|
80
|
+
# c._record(:G, ['an', 'array', 'value'])
|
81
|
+
#
|
82
|
+
# c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]], :E, :F, :G]
|
83
|
+
# c._value_trail # => [ [[1,2,3], [10, 11, 12]], "a string value", {'a' => 'hash value'}, ['an', 'array', 'value']]
|
84
|
+
#
|
85
|
+
# Audit supports forks by duplicating the source and value trails. Forks
|
86
|
+
# can be developed independently. Importantly, Audits are forked during
|
87
|
+
# a merge; notice the additional record in +a+ doesn't change the source
|
88
|
+
# trail for +c+:
|
89
|
+
#
|
90
|
+
# a1 = a._fork
|
91
|
+
#
|
92
|
+
# a._record(:X, -1)
|
93
|
+
# a1._record(:Y, -2)
|
94
|
+
#
|
95
|
+
# a._source_trail # => [nil, :A, :B, :X]
|
96
|
+
# a1._source_trail # => [nil, :A, :B, :Y]
|
97
|
+
# c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]], :E, :F, :G]
|
98
|
+
#
|
99
|
+
# The data structure for an audit gets nasty after a few merges because
|
100
|
+
# the lead array gets more and more nested. Audit provides iterators
|
101
|
+
# to help gain access, as well as a printing method to visualize the
|
102
|
+
# audit trail:
|
103
|
+
#
|
104
|
+
# [c._to_s]
|
105
|
+
# o-[] 1
|
106
|
+
# o-[A] 2
|
107
|
+
# o-[B] 3
|
108
|
+
# |
|
109
|
+
# | o-[] 10
|
110
|
+
# | o-[C] 11
|
111
|
+
# | o-[D] 12
|
112
|
+
# | |
|
113
|
+
# `-`-o-[E] "a string value"
|
114
|
+
# o-[F] {"a"=>"hash value"}
|
115
|
+
# o-[G] ["an", "array", "value"]
|
116
|
+
#
|
117
|
+
# In practice, tasks are recored as sources. Thus source trails can be used
|
118
|
+
# to access task configurations and other information that may be useful
|
119
|
+
# when creating reports or making workflow decisions (ex: raise an
|
120
|
+
# error after looping to a given task too many times).
|
121
|
+
#
|
122
|
+
#--
|
123
|
+
# TODO:
|
124
|
+
# Track nesting level of ams; see if you can hook this into the _to_s process to make
|
125
|
+
# extraction/presentation of audits more managable.
|
126
|
+
#
|
127
|
+
# Create a FirstLastArray to minimize the audit data collected. Allow different audit
|
128
|
+
# modes:
|
129
|
+
# - full ([] both)
|
130
|
+
# - source_only (fl value)
|
131
|
+
# - minimal (fl source and value)
|
132
|
+
#
|
133
|
+
# Try to work a _to_s that doesn't repeat the same audit twice. Think about a format
|
134
|
+
# like:
|
135
|
+
# |
|
136
|
+
# ------|-----+
|
137
|
+
# | |
|
138
|
+
# ------|-----|-----+
|
139
|
+
# | | |
|
140
|
+
# `-----`-----`-o-[j] j5
|
141
|
+
#
|
142
|
+
class Audit
|
143
|
+
class << self
|
144
|
+
|
145
|
+
# Creates a new Audit by merging the input audits. The value of the new
|
146
|
+
# Audit will be an array of the _current values of the audits. The source
|
147
|
+
# will be an AuditMerge whose values are forks of the audits. Non-Audit
|
148
|
+
# sources can be provided; they are initialized to Audits before merging.
|
149
|
+
#
|
150
|
+
# a = Audit.new
|
151
|
+
# a._record(:a, 'a')
|
152
|
+
#
|
153
|
+
# b = Audit.new
|
154
|
+
# b._record(:b, 'b')
|
155
|
+
#
|
156
|
+
# c = Audit.merge(a, b, 1)
|
157
|
+
# c._record(:c, 'c')
|
158
|
+
#
|
159
|
+
# c._values # => [['a','b', 1], 'c']
|
160
|
+
# c._sources # => [AuditMerge[a, b, Audit.new(1)], :c]
|
161
|
+
#
|
162
|
+
# If no audits are provided, merge returns a new Audit. If only one
|
163
|
+
# audit is provided, merge returns a fork of that audit.
|
164
|
+
def merge(*audits)
|
165
|
+
case audits.length
|
166
|
+
when 0 then Audit.new
|
167
|
+
when 1 then audits[0]._fork
|
168
|
+
else
|
169
|
+
sources = AuditMerge.new
|
170
|
+
audits.each {|a| sources << (a.kind_of?(Audit) ? a._fork : Audit.new(a)) }
|
171
|
+
values = audits.collect {|a| a.kind_of?(Audit) ? a._current : a}
|
172
|
+
|
173
|
+
Audit.new(values, sources)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# An array of the sources in self
|
179
|
+
attr_reader :_sources
|
180
|
+
|
181
|
+
# An array of the values in self
|
182
|
+
attr_reader :_values
|
183
|
+
|
184
|
+
# An arbitrary constant used to identify when no inputs have been
|
185
|
+
# provided to Audit.new. (nil itself cannot be used as nil is a
|
186
|
+
# valid initial value for an audit trail)
|
187
|
+
AUDIT_NIL = Object.new
|
188
|
+
|
189
|
+
# A new audit takes a value and/or source. A nil source is typically given
|
190
|
+
# for the original value.
|
191
|
+
def initialize(value=AUDIT_NIL, source=nil)
|
192
|
+
@_sources = []
|
193
|
+
@_values = []
|
194
|
+
|
195
|
+
_record(source, value) unless value == AUDIT_NIL
|
196
|
+
end
|
197
|
+
|
198
|
+
# Records the next value produced by the source. When an audit is
|
199
|
+
# passed as a value, record will record the current value of the audit.
|
200
|
+
# Record will similarly resolve every audit in an array containing audits.
|
201
|
+
#
|
202
|
+
# Example:
|
203
|
+
#
|
204
|
+
# a = Audit.new(1)
|
205
|
+
# b = Audit.new(2)
|
206
|
+
# c = Audit.new(3)
|
207
|
+
#
|
208
|
+
# c.record(:a, a)
|
209
|
+
# c.sources # => [:a]
|
210
|
+
# c.values # => [1]
|
211
|
+
#
|
212
|
+
# c.record(:ab, [a,b])
|
213
|
+
# c.sources # => [:a, :ab]
|
214
|
+
# c.values # => [1, [1, 2]]
|
215
|
+
def _record(source, value)
|
216
|
+
_sources << source
|
217
|
+
_values << value
|
218
|
+
self
|
219
|
+
end
|
220
|
+
|
221
|
+
# The original value used to initialize the Audit
|
222
|
+
def _original
|
223
|
+
_values.first
|
224
|
+
end
|
225
|
+
|
226
|
+
# The current (ie last) value recorded in the Audit
|
227
|
+
def _current
|
228
|
+
_values.last
|
229
|
+
end
|
230
|
+
|
231
|
+
# The original source used to initialize the Audit
|
232
|
+
def _original_source
|
233
|
+
_sources.first
|
234
|
+
end
|
235
|
+
|
236
|
+
# The current (ie last) source recorded in the Audit
|
237
|
+
def _current_source
|
238
|
+
_sources.last
|
239
|
+
end
|
240
|
+
|
241
|
+
# Searches back and recursively (if the source is an audit) collects all sources
|
242
|
+
# for the current value.
|
243
|
+
def _source_trail
|
244
|
+
_collect_records {|source, value| source}
|
245
|
+
end
|
246
|
+
|
247
|
+
# Searches back and recursively (if the source is an audit) collects all values
|
248
|
+
# leading to the current value.
|
249
|
+
def _value_trail
|
250
|
+
_collect_records {|source, value| value}
|
251
|
+
end
|
252
|
+
|
253
|
+
def _collect_records(&block) # :yields: source, value
|
254
|
+
collection = []
|
255
|
+
0.upto(_sources.length-1) do |i|
|
256
|
+
collection << collect_records(_sources[i], _values[i], &block)
|
257
|
+
end
|
258
|
+
collection
|
259
|
+
end
|
260
|
+
|
261
|
+
def _each_record(merge_level=0, merge_index=0, &block) # :yields: source, value, merge_level, merge_index, index
|
262
|
+
0.upto(_sources.length-1) do |i|
|
263
|
+
each_record(_sources[i], _values[i], merge_level, merge_index, i, &block)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# Creates a new Audit by merging self and the input audits, using Audit#merge.
|
268
|
+
def _merge(*audits)
|
269
|
+
Audit.merge(self, *audits)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Produces a new Audit with duplicate sources and values, suitable for
|
273
|
+
# separate development along a separate path.
|
274
|
+
def _fork
|
275
|
+
a = Audit.new
|
276
|
+
a._sources = _sources.dup
|
277
|
+
a._values = _values.dup
|
278
|
+
a
|
279
|
+
end
|
280
|
+
|
281
|
+
# _forks self and records the next value as [<return from block>, AuditSplit.new(block)]
|
282
|
+
def _split(&block) # :yields: _current
|
283
|
+
_fork._record(AuditSplit.new(block), yield(_current))
|
284
|
+
end
|
285
|
+
|
286
|
+
# _forks self for each member in _current. Records the next value as
|
287
|
+
# [item, AuditExpand.new(<index of item>)]. Raises an error if _current
|
288
|
+
# does not respond to each.
|
289
|
+
def _expand
|
290
|
+
expanded = []
|
291
|
+
_current.each do |value|
|
292
|
+
expanded << _fork._record(AuditExpand.new(expanded.length), value)
|
293
|
+
end
|
294
|
+
expanded
|
295
|
+
end
|
296
|
+
|
297
|
+
# Returns true if the _sources and _values for self are equal
|
298
|
+
# to those of another.
|
299
|
+
def ==(another)
|
300
|
+
another.kind_of?(Audit) && self._sources == another._sources && self._values == another._values
|
301
|
+
end
|
302
|
+
|
303
|
+
# A kind of pretty-print for Audits. See the example in the overview.
|
304
|
+
def _to_s
|
305
|
+
# TODO -- find a way to avoid repeating groups
|
306
|
+
|
307
|
+
group = []
|
308
|
+
groups = [group]
|
309
|
+
extended_groups = [groups]
|
310
|
+
group_merges = []
|
311
|
+
extended_group_merges = []
|
312
|
+
current_level = nil
|
313
|
+
current_index = nil
|
314
|
+
|
315
|
+
_each_record do |source, value, merge_level, merge_index, index|
|
316
|
+
source_str, value_str = if block_given?
|
317
|
+
yield(source, value)
|
318
|
+
else
|
319
|
+
[source, value == nil ? '' : PP.singleline_pp(value, '')]
|
320
|
+
end
|
321
|
+
|
322
|
+
if !group.empty? && (merge_level != current_level || index == 0)
|
323
|
+
unless merge_level <= current_level
|
324
|
+
groups = []
|
325
|
+
extended_groups << groups
|
326
|
+
end
|
327
|
+
|
328
|
+
group = []
|
329
|
+
groups << group
|
330
|
+
|
331
|
+
if merge_level < current_level
|
332
|
+
if merge_index == 0
|
333
|
+
extended_group_merges << group.object_id
|
334
|
+
end
|
335
|
+
|
336
|
+
unless index == 0
|
337
|
+
group_merges << group.object_id
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
group << "o-[#{source_str}] #{value_str}"
|
343
|
+
current_level = merge_level
|
344
|
+
current_index = merge_index
|
345
|
+
end
|
346
|
+
|
347
|
+
lines = []
|
348
|
+
group_prefix = ""
|
349
|
+
extended_groups.each do |ext_groups|
|
350
|
+
indentation = 0
|
351
|
+
|
352
|
+
ext_groups.each_with_index do |ext_group, group_num|
|
353
|
+
ext_group.each_with_index do |line, line_num|
|
354
|
+
if line_num == 0
|
355
|
+
unless lines.empty?
|
356
|
+
lines << group_prefix + " " * indentation + "| " * (group_num-indentation)
|
357
|
+
end
|
358
|
+
|
359
|
+
if group_merges.include?(ext_group.object_id)
|
360
|
+
lines << group_prefix + " " * indentation + "`-" * (group_num-indentation) + line
|
361
|
+
indentation = group_num
|
362
|
+
|
363
|
+
if extended_group_merges.include?(ext_group.object_id)
|
364
|
+
lines.last.gsub!(/\| \s*/) {|match| "`-" + "-" * (match.length - 2)}
|
365
|
+
group_prefix.gsub!(/\| /, " ")
|
366
|
+
end
|
367
|
+
next
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
lines << group_prefix + " " * indentation + "| " * (group_num-indentation) + line
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
group_prefix += " " * (ext_groups.length-1) + "| "
|
376
|
+
end
|
377
|
+
|
378
|
+
lines.join("\n") + "\n"
|
379
|
+
end
|
380
|
+
|
381
|
+
protected
|
382
|
+
|
383
|
+
attr_writer :_sources, :_values # :nodoc:
|
384
|
+
|
385
|
+
private
|
386
|
+
|
387
|
+
# helper method to recursively collect the value trail for a given source
|
388
|
+
def collect_records(source, value, &block)
|
389
|
+
case source
|
390
|
+
when AuditMerge
|
391
|
+
collection = []
|
392
|
+
0.upto(source.length-1) do |i|
|
393
|
+
collection << collect_records(source[i], value[i], &block)
|
394
|
+
end
|
395
|
+
collection
|
396
|
+
when Audit
|
397
|
+
source._collect_records(&block)
|
398
|
+
else
|
399
|
+
yield(source, value)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def each_record(source, value, merge_level, merge_index, index, &block)
|
404
|
+
case source
|
405
|
+
when AuditMerge
|
406
|
+
merge_level += 1
|
407
|
+
0.upto(source.length-1) do |i|
|
408
|
+
each_record(source[i], value[i], merge_level, i, index, &block)
|
409
|
+
end
|
410
|
+
when Audit
|
411
|
+
source._each_record(merge_level, merge_index, &block)
|
412
|
+
else
|
413
|
+
yield(source, value, merge_level, merge_index, index)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|