tap 0.8.0 → 0.9.0
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.
- data/Basic Overview +151 -0
- data/Command Reference +99 -0
- data/History +24 -0
- data/MIT-LICENSE +1 -1
- data/README +29 -57
- data/Rakefile +30 -37
- data/Tutorial +243 -191
- data/bin/tap +66 -35
- data/lib/tap.rb +47 -29
- data/lib/tap/app.rb +700 -342
- data/lib/tap/{script → cmd}/console.rb +0 -0
- data/lib/tap/{script → cmd}/destroy.rb +0 -0
- data/lib/tap/{script → cmd}/generate.rb +0 -0
- data/lib/tap/cmd/run.rb +156 -0
- data/lib/tap/constants.rb +4 -0
- data/lib/tap/dump.rb +57 -0
- data/lib/tap/env.rb +316 -0
- data/lib/tap/file_task.rb +106 -109
- data/lib/tap/generator.rb +4 -1
- data/lib/tap/generator/generators/command/USAGE +6 -0
- data/lib/tap/generator/generators/command/command_generator.rb +17 -0
- data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
- data/lib/tap/generator/generators/config/USAGE +21 -0
- data/lib/tap/generator/generators/config/config_generator.rb +17 -7
- data/lib/tap/generator/generators/file_task/USAGE +3 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
- data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
- data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
- data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
- data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
- data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
- data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
- data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
- data/lib/tap/generator/generators/task/USAGE +3 -0
- data/lib/tap/generator/generators/task/task_generator.rb +18 -5
- data/lib/tap/generator/generators/task/templates/task.erb +7 -12
- data/lib/tap/generator/generators/task/templates/test.erb +10 -11
- data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- 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 +172 -67
- data/lib/tap/script.rb +70 -336
- data/lib/tap/support/aggregator.rb +55 -0
- data/lib/tap/support/audit.rb +281 -280
- data/lib/tap/support/batchable.rb +59 -0
- data/lib/tap/support/class_configuration.rb +279 -0
- data/lib/tap/support/configurable.rb +92 -0
- data/lib/tap/support/configurable_methods.rb +296 -0
- data/lib/tap/support/executable.rb +98 -0
- data/lib/tap/support/executable_queue.rb +82 -0
- data/lib/tap/support/logger.rb +9 -15
- data/lib/tap/support/rake.rb +43 -54
- data/lib/tap/support/run_error.rb +32 -13
- data/lib/tap/support/shell_utils.rb +47 -0
- data/lib/tap/support/tdoc.rb +9 -8
- data/lib/tap/support/tdoc/config_attr.rb +40 -16
- data/lib/tap/support/validation.rb +77 -0
- data/lib/tap/support/versions.rb +36 -36
- data/lib/tap/task.rb +276 -482
- data/lib/tap/test.rb +20 -261
- data/lib/tap/test/env_vars.rb +7 -5
- data/lib/tap/test/file_methods.rb +126 -121
- data/lib/tap/test/subset_methods.rb +86 -45
- data/lib/tap/test/tap_methods.rb +271 -0
- data/lib/tap/workflow.rb +174 -46
- data/test/app/config/another/task.yml +1 -0
- data/test/app/config/erb.yml +2 -1
- data/test/app/config/some/task.yml +1 -0
- data/test/app/config/template.yml +2 -6
- data/test/app_test.rb +1241 -1008
- data/test/env/test_configure/recurse_a.yml +2 -0
- data/test/env/test_configure/recurse_b.yml +2 -0
- data/test/env/test_configure/tap.yml +23 -0
- data/test/env/test_load_env_config/dir/tap.yml +3 -0
- data/test/env/test_load_env_config/recurse_a.yml +2 -0
- data/test/env/test_load_env_config/recurse_b.yml +2 -0
- data/test/env/test_load_env_config/tap.yml +3 -0
- data/test/env_test.rb +198 -0
- data/test/file_task_test.rb +70 -53
- data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
- data/test/root_test.rb +621 -454
- data/test/script_test.rb +38 -174
- data/test/support/aggregator_test.rb +99 -0
- data/test/support/audit_test.rb +409 -416
- data/test/support/batchable_test.rb +74 -0
- data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
- data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
- data/test/support/configurable_test.rb +295 -0
- data/test/support/executable_queue_test.rb +103 -0
- data/test/support/executable_test.rb +38 -0
- data/test/support/logger_test.rb +17 -17
- data/test/support/rake_test.rb +4 -2
- data/test/support/shell_utils_test.rb +24 -0
- data/test/support/tdoc_test.rb +265 -258
- data/test/support/validation_test.rb +54 -0
- data/test/support/versions_test.rb +38 -38
- data/test/tap_test_helper.rb +19 -5
- data/test/tap_test_suite.rb +5 -2
- data/test/task_base_test.rb +13 -104
- data/test/task_syntax_test.rb +300 -0
- data/test/task_test.rb +258 -381
- data/test/test/env_vars_test.rb +40 -40
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
- data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
- data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
- data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
- data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
- data/test/test/file_methods_doc_test.rb +29 -0
- data/test/test/file_methods_test.rb +214 -143
- data/test/test/subset_methods_test.rb +111 -115
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
- data/test/test/tap_methods_test.rb +399 -0
- data/test/workflow_test.rb +101 -91
- metadata +86 -70
- data/lib/tap/generator/generators/package/package_generator.rb +0 -38
- data/lib/tap/generator/generators/package/templates/package.erb +0 -186
- data/lib/tap/generator/generators/script/USAGE +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +0 -17
- data/lib/tap/script/run.rb +0 -154
- data/lib/tap/support/batch_queue.rb +0 -162
- data/lib/tap/support/combinator.rb +0 -114
- data/lib/tap/support/task_configuration.rb +0 -169
- data/lib/tap/support/template.rb +0 -81
- data/lib/tap/support/templater.rb +0 -155
- data/lib/tap/version.rb +0 -4
- data/test/app/config/addition_template.yml +0 -6
- data/test/app_class_test.rb +0 -33
- data/test/check/binding_eval.rb +0 -23
- data/test/check/define_method_check.rb +0 -22
- data/test/check/dependencies_check.rb +0 -175
- data/test/check/inheritance_check.rb +0 -22
- data/test/support/batch_queue_test.rb +0 -320
- data/test/support/combinator_test.rb +0 -249
- data/test/support/template_test.rb +0 -122
- data/test/support/templater/erb.txt +0 -2
- data/test/support/templater/erb.yml +0 -2
- data/test/support/templater/somefile.txt +0 -2
- data/test/support/templater_test.rb +0 -192
- data/test/task/config/template.yml +0 -4
- data/test/task_class_test.rb +0 -170
- data/test/task_execute_test.rb +0 -262
- data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_file_compare/expected/output_1.txt +0 -3
- data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
- data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
- data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_yml_compare/expected/output_1.yml +0 -6
- data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
- data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
- data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
- data/test/test_test.rb +0 -373
data/lib/tap/support/audit.rb
CHANGED
|
@@ -1,185 +1,196 @@
|
|
|
1
1
|
module Tap
|
|
2
2
|
module Support
|
|
3
|
+
|
|
4
|
+
# Marks the merge of multiple Audit trails
|
|
5
|
+
class AuditMerge < Array
|
|
6
|
+
def ==(another)
|
|
7
|
+
another.kind_of?(AuditMerge) && super
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Marks a split in an Audit trail
|
|
12
|
+
class AuditSplit
|
|
13
|
+
attr_reader :block
|
|
14
|
+
def initialize(block) @block = block end
|
|
15
|
+
|
|
16
|
+
def ==(another)
|
|
17
|
+
another.kind_of?(AuditSplit) && another.block == block
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Marks the expansion of an Audit trail
|
|
22
|
+
class AuditExpand
|
|
23
|
+
attr_reader :index
|
|
24
|
+
def initialize(index) @index = index end
|
|
25
|
+
|
|
26
|
+
def ==(another)
|
|
27
|
+
another.kind_of?(AuditExpand) && another.index == index
|
|
28
|
+
end
|
|
29
|
+
end
|
|
3
30
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# Audit tracks input and result values passed among tasks within a workflow. At the end
|
|
7
|
-
# of a run, each result will have an audit trail detailing the values it has obtained
|
|
8
|
-
# at various stages, and the source of that value. The ability to do track back all the
|
|
9
|
-
# places where a value was changed or modified is very important during workflow debugging.
|
|
31
|
+
# == Overview
|
|
10
32
|
#
|
|
11
|
-
# Audit
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# with an initial input value of 3:
|
|
16
|
-
#
|
|
17
|
-
# # task :a initializes a new audit with the original
|
|
18
|
-
# # value upon execution
|
|
19
|
-
# ... run :a with input 3 ...
|
|
20
|
-
# audit = Audit.new(3)
|
|
33
|
+
# Audit provides a way to track the values (inputs and results) passed
|
|
34
|
+
# among tasks. Audits allow you to track inputs as they make their
|
|
35
|
+
# way through a workflow, and have a great deal of importance for
|
|
36
|
+
# debugging and record keeping.
|
|
21
37
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
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) records it's result in
|
|
41
|
+
# the trail with the task as the 'source' of the result.
|
|
26
42
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
# ... task :b adds one ...
|
|
30
|
-
# audit._record(:b, 5)
|
|
31
|
-
# ... task :c adds two ...
|
|
32
|
-
# audit._record(:c, 7)
|
|
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:
|
|
33
45
|
#
|
|
34
|
-
# #
|
|
35
|
-
#
|
|
36
|
-
# # (note the very first source is nil)
|
|
37
|
-
# audit._source_trail # => [nil, :a, :b, :c]
|
|
46
|
+
# # initialize a new audit
|
|
47
|
+
# a = Audit.new(1, nil)
|
|
38
48
|
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
49
|
+
# # record some values
|
|
50
|
+
# a._record(:A, 2)
|
|
51
|
+
# a._record(:B, 3)
|
|
41
52
|
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# audit._record(:a, 4)
|
|
45
|
-
# fork_b = audit._fork
|
|
46
|
-
# fork_c = audit._fork
|
|
53
|
+
# Now you can pull up the trails of sources and values, as well as
|
|
54
|
+
# information like the current and original values:
|
|
47
55
|
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
# fork_c._record(:c, 6)
|
|
56
|
+
# a._source_trail # => [nil, :A, :B]
|
|
57
|
+
# a._value_trail # => [1, 2, 3]
|
|
51
58
|
#
|
|
52
|
-
# #
|
|
53
|
-
# #
|
|
54
|
-
# fork_b._source_trail # => [nil, :a, :b]
|
|
55
|
-
# fork_c._source_trail # => [nil, :a, :c]
|
|
59
|
+
# a._original # => 1
|
|
60
|
+
# a._original_source # => nil
|
|
56
61
|
#
|
|
57
|
-
# #
|
|
58
|
-
# #
|
|
59
|
-
# # all values that come to it.
|
|
60
|
-
# ... task :d recieves results from :b and :c and adds them ...
|
|
61
|
-
# merged_audit = Audit.merge(fork_b, fork_c)
|
|
62
|
-
# merged_audit._record(:d, 11)
|
|
62
|
+
# a._current # => 3
|
|
63
|
+
# a._current_source # => :B
|
|
63
64
|
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# # that merged
|
|
67
|
-
# merged_audit._source_trail # => [[[nil,:a,:b], [nil,:a,:c]], :d]
|
|
65
|
+
# Merges are supported by using an array of the merging trails as the
|
|
66
|
+
# source, and an array of the merging values as the initial value.
|
|
68
67
|
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# that may be useful when assessing an audit. Incidentally, this is one of the reasons why Tap
|
|
73
|
-
# is designed to be used with configurations that DO NOT change during execution; if they don't
|
|
74
|
-
# change then you're able to look back at your handiwork.
|
|
68
|
+
# b = Audit.new(10, nil)
|
|
69
|
+
# b._record(:C, 11)
|
|
70
|
+
# b._record(:D, 12)
|
|
75
71
|
#
|
|
76
|
-
#
|
|
72
|
+
# c = Audit.merge(a, b)
|
|
73
|
+
# c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]] ]
|
|
74
|
+
# c._value_trail # => [ [[1,2,3], [10, 11, 12]] ]
|
|
75
|
+
# c._current # => [3, 12]
|
|
77
76
|
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
77
|
+
# c._record(:E, "a string value")
|
|
78
|
+
# c._record(:F, {'a' => 'hash value'})
|
|
79
|
+
# c._record(:G, ['an', 'array', 'value'])
|
|
81
80
|
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# that they behave like the current value. It's important to realize that <em>workflow blocks
|
|
85
|
-
# (ex: on_complete and condition) recieve Audits and NOT values</em>.
|
|
81
|
+
# c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]], :E, :F, :G]
|
|
82
|
+
# c._value_trail # => [ [[1,2,3], [10, 11, 12]], "a string value", {'a' => 'hash value'}, ['an', 'array', 'value']]
|
|
86
83
|
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# # in fact it's an Audit, but it passes unknown methods
|
|
92
|
-
# # along to it's current value
|
|
84
|
+
# Audit supports forks by duplicating the source and value trails. Forks
|
|
85
|
+
# can be developed independently. Importantly, Audits are forked during
|
|
86
|
+
# a merge; notice the additional record in +a+ doesn't change the source
|
|
87
|
+
# trail for +c+
|
|
93
88
|
#
|
|
94
|
-
#
|
|
95
|
-
# result._current # => "str"
|
|
96
|
-
# result == "str" # => true
|
|
97
|
-
# result.upcase # => "STR"
|
|
98
|
-
#
|
|
99
|
-
# # the forwarding behavior is for convenience when
|
|
100
|
-
# # making decisions about what to do with a result
|
|
101
|
-
# # but be sure you don't get caught! The only object
|
|
102
|
-
# # methods forwarded are '==' and '=~'. Other methods
|
|
103
|
-
# # are NOT forwarded, and =~ cannot (due to context
|
|
104
|
-
# # issues) capture match strings
|
|
105
|
-
#
|
|
106
|
-
# result.kind_of?(String) # => false
|
|
107
|
-
# result =~ /s(\w+)/ # => true
|
|
108
|
-
# $1 # => nil (watch out! you may expect "tr")
|
|
109
|
-
#
|
|
110
|
-
# end
|
|
111
|
-
# end
|
|
112
|
-
#
|
|
113
|
-
# Audits and NOT values are passed into these workflow blocks because you may need to make
|
|
114
|
-
# a workflow decision based on where a value came from (ie you may need the source trail).
|
|
115
|
-
# The same does not hold true when processing inputs. <em>The process method recieves the
|
|
116
|
-
# values themselves.</em>
|
|
89
|
+
# a1 = a._fork
|
|
117
90
|
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
# # and NOT the Audit tracking the inputs and results
|
|
121
|
-
# input.class # => Fixnum (given that we execute t with 3)
|
|
122
|
-
# input += 1
|
|
123
|
-
# end
|
|
91
|
+
# a._record(:X, -1)
|
|
92
|
+
# a1._record(:Y, -2)
|
|
124
93
|
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
# results.first.class # => Audit
|
|
129
|
-
# results.first._current # => 4
|
|
94
|
+
# a._source_trail # => [nil, :A, :B, :X]
|
|
95
|
+
# a1._source_trail # => [nil, :A, :B, :Y]
|
|
96
|
+
# c._source_trail # => [ [[nil, :A, :B], [nil, :C, :D]], :E, :F, :G]
|
|
130
97
|
#
|
|
131
|
-
#
|
|
98
|
+
# The data structure for an audit gets nasty after a few merges because
|
|
99
|
+
# the lead array gets more and more nested. Audit provides iterators
|
|
100
|
+
# to help gain access, as well as a printing method to visualize the
|
|
101
|
+
# audit trail:
|
|
132
102
|
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
103
|
+
# [c._to_s]
|
|
104
|
+
# o-[] 1
|
|
105
|
+
# o-[A] 2
|
|
106
|
+
# o-[B] 3
|
|
107
|
+
# |
|
|
108
|
+
# | o-[] 10
|
|
109
|
+
# | o-[C] 11
|
|
110
|
+
# | o-[D] 12
|
|
111
|
+
# | |
|
|
112
|
+
# `-`-o-[E] "a string value"
|
|
113
|
+
# o-[F] {"a"=>"hash value"}
|
|
114
|
+
# o-[G] ["an", "array", "value"]
|
|
115
|
+
#
|
|
116
|
+
# In practice, tasks are recored as sources. Thus source trails can be used
|
|
117
|
+
# to access task configurations and other information that may be useful
|
|
118
|
+
# when creating reports or making workflow decisions (ex: raise an
|
|
119
|
+
# error after looping to a given task too many times).
|
|
120
|
+
#
|
|
121
|
+
#--
|
|
122
|
+
# TODO:
|
|
123
|
+
# Create an AuditMerge class to mark merges (don't use arrays). Track nesting level
|
|
124
|
+
# of ams; see if you can hook this into _to_s process to make extraction/presentation
|
|
125
|
+
# 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
|
|
137
141
|
#
|
|
138
142
|
class Audit
|
|
143
|
+
autoload(:PP, 'pp')
|
|
144
|
+
|
|
139
145
|
class << self
|
|
140
146
|
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
# Creates a new Audit from the inputs. The value of the new Audit will be the inputs
|
|
148
|
-
# array where any Audits are replaced by their _current value. The source of the
|
|
149
|
-
# new Audit will be a corresponding array of nils, or Audits if provided.
|
|
147
|
+
# Creates a new Audit by merging the input audits. The value of the new
|
|
148
|
+
# Audit will be an array of the _current values of the audits. The source
|
|
149
|
+
# will be an AuditMerge whose values are forks of the audits. Non-Audit
|
|
150
|
+
# sources can be provided; they are initialized to Audits before merging.
|
|
150
151
|
#
|
|
151
|
-
# a = Audit.new
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
# b
|
|
152
|
+
# a = Audit.new
|
|
153
|
+
# a._record(:a, 'a')
|
|
154
|
+
#
|
|
155
|
+
# b = Audit.new
|
|
156
|
+
# b._record(:b, 'b')
|
|
157
|
+
#
|
|
158
|
+
# c = Audit.merge(a, b, 1)
|
|
159
|
+
# c._record(:c, 'c')
|
|
160
|
+
#
|
|
161
|
+
# c._values # => [['a','b', 1], 'c']
|
|
162
|
+
# c._sources # => [AuditMerge[a, b, Audit.new(1)], :c]
|
|
155
163
|
#
|
|
156
|
-
# If no
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
case inputs.length
|
|
164
|
+
# If no audits are provided, merge returns a new Audit. If only one
|
|
165
|
+
# audit is provided, merge returns a fork of that audit.
|
|
166
|
+
def merge(*audits)
|
|
167
|
+
case audits.length
|
|
161
168
|
when 0 then Audit.new
|
|
162
|
-
when 1
|
|
163
|
-
input = inputs.first
|
|
164
|
-
input.kind_of?(Audit) ? input._fork : Audit.new(input)
|
|
169
|
+
when 1 then audits[0]._fork
|
|
165
170
|
else
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
sources = AuditMerge.new
|
|
172
|
+
audits.each {|a| sources << (a.kind_of?(Audit) ? a._fork : Audit.new(a)) }
|
|
173
|
+
values = audits.collect {|a| a.kind_of?(Audit) ? a._current : a}
|
|
174
|
+
|
|
168
175
|
Audit.new(values, sources)
|
|
169
176
|
end
|
|
170
177
|
end
|
|
171
|
-
|
|
172
178
|
end
|
|
173
179
|
|
|
174
180
|
attr_reader :_sources, :_values
|
|
175
|
-
|
|
181
|
+
|
|
182
|
+
# An arbitrary constant used to identify when no inputs have been
|
|
183
|
+
# provided to Audit.new. (nil itself cannot be used as nil is a
|
|
184
|
+
# valid initial value for an audit trail)
|
|
185
|
+
AUDIT_NIL = Object.new
|
|
186
|
+
|
|
176
187
|
# A new audit takes a value and/or source. A nil source is typically given
|
|
177
188
|
# for the original value.
|
|
178
|
-
def initialize(value=
|
|
189
|
+
def initialize(value=AUDIT_NIL, source=nil)
|
|
179
190
|
@_sources = []
|
|
180
191
|
@_values = []
|
|
181
192
|
|
|
182
|
-
_record(source, value)
|
|
193
|
+
_record(source, value) unless value == AUDIT_NIL
|
|
183
194
|
end
|
|
184
195
|
|
|
185
196
|
# Records the next value produced by the source. When an audit is
|
|
@@ -193,12 +204,12 @@ module Tap
|
|
|
193
204
|
# c = Audit.new(3)
|
|
194
205
|
#
|
|
195
206
|
# c.record(:a, a)
|
|
196
|
-
# c.sources
|
|
197
|
-
# c.values
|
|
207
|
+
# c.sources # => [:a]
|
|
208
|
+
# c.values # => [1]
|
|
198
209
|
#
|
|
199
210
|
# c.record(:ab, [a,b])
|
|
200
|
-
# c.sources
|
|
201
|
-
# c.values
|
|
211
|
+
# c.sources # => [:a, :ab]
|
|
212
|
+
# c.values # => [1, [1, 2]]
|
|
202
213
|
def _record(source, value)
|
|
203
214
|
_sources << source
|
|
204
215
|
_values << value
|
|
@@ -225,83 +236,35 @@ module Tap
|
|
|
225
236
|
_sources.last
|
|
226
237
|
end
|
|
227
238
|
|
|
228
|
-
# The index of the last occurence of source in the audit (Note
|
|
229
|
-
# equality is based on the object id of the specified source)
|
|
230
|
-
def _index_last(source)
|
|
231
|
-
_sources.rindex(source)
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
# The value recorded with the last occurence of source in the audit. (Note
|
|
235
|
-
# equality is based on the object id of the specified source)
|
|
236
|
-
def _last(source)
|
|
237
|
-
index = _index_last(source)
|
|
238
|
-
index.nil? ? nil : _values[index]
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Returns the value at the specified index.
|
|
242
|
-
def _input(index)
|
|
243
|
-
_values[index]
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Returns the input to the last occurence of source in the audit (ie
|
|
247
|
-
# the value prior to this source). Example:
|
|
248
|
-
#
|
|
249
|
-
# a = Audit.new
|
|
250
|
-
# a.record(:a, 'a')
|
|
251
|
-
# a.record(:b, 'b')
|
|
252
|
-
#
|
|
253
|
-
# a.input_last(:a) # => nil
|
|
254
|
-
# a.input_last(:b) # => 'a'
|
|
255
|
-
def _input_last(source)
|
|
256
|
-
index = _index_last(source)
|
|
257
|
-
_input(index-1)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
# Returns the value after the specfied index
|
|
261
|
-
def _output(index)
|
|
262
|
-
_values[index+1]
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# Returns the output of the last occurence of source in the audit (ie
|
|
266
|
-
# the value at this source). Example:
|
|
267
|
-
#
|
|
268
|
-
# a = Audit.new
|
|
269
|
-
# a.record(:a, 'a')
|
|
270
|
-
# a.record(:b, 'b')
|
|
271
|
-
#
|
|
272
|
-
# a.output_last(:a) # => 'a'
|
|
273
|
-
# a.output_last(:b) # => 'b'
|
|
274
|
-
def _output_last(source)
|
|
275
|
-
index = _index_last(source)
|
|
276
|
-
_output(index-1)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
239
|
# Searches back and recursively (if the source is an audit) collects all sources
|
|
280
240
|
# for the current value.
|
|
281
241
|
def _source_trail
|
|
282
|
-
|
|
283
|
-
source_trail(source)
|
|
284
|
-
end
|
|
242
|
+
_collect_records {|source, value| source}
|
|
285
243
|
end
|
|
286
244
|
|
|
287
245
|
# Searches back and recursively (if the source is an audit) collects all values
|
|
288
246
|
# leading to the current value.
|
|
289
247
|
def _value_trail
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
248
|
+
_collect_records {|source, value| value}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def _collect_records(&block) # :yields: source, value
|
|
252
|
+
collection = []
|
|
253
|
+
0.upto(_sources.length-1) do |i|
|
|
254
|
+
collection << collect_records(_sources[i], _values[i], &block)
|
|
255
|
+
end
|
|
256
|
+
collection
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def _each_record(merge_level=0, merge_index=0, &block) # :yields: source, value, merge_level, merge_index, index
|
|
260
|
+
0.upto(_sources.length-1) do |i|
|
|
261
|
+
each_record(_sources[i], _values[i], merge_level, merge_index, i, &block)
|
|
293
262
|
end
|
|
294
|
-
trail
|
|
295
263
|
end
|
|
296
264
|
|
|
297
|
-
#
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
#
|
|
301
|
-
# If no sources are provided, then _merge returns _fork.
|
|
302
|
-
def _merge(*sources)
|
|
303
|
-
sources.unshift(self)
|
|
304
|
-
Audit.merge(*sources)
|
|
265
|
+
# Creates a new Audit by merging self and the input audits, using Audit#merge.
|
|
266
|
+
def _merge(*audits)
|
|
267
|
+
Audit.merge(self, *audits)
|
|
305
268
|
end
|
|
306
269
|
|
|
307
270
|
# Produces a new Audit with duplicate sources and values, suitable for
|
|
@@ -313,101 +276,139 @@ module Tap
|
|
|
313
276
|
a
|
|
314
277
|
end
|
|
315
278
|
|
|
316
|
-
#
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
def _split(&block)
|
|
320
|
-
sp = Audit.new(nil, self)
|
|
321
|
-
sp._record(block, yield(_current))
|
|
322
|
-
sp
|
|
279
|
+
# _forks self and records the next value as [<return from block>, AuditSplit.new(block)]
|
|
280
|
+
def _split(&block) # :yields: _current
|
|
281
|
+
_fork._record(AuditSplit.new(block), yield(_current))
|
|
323
282
|
end
|
|
324
|
-
|
|
325
|
-
alias _eql ==
|
|
326
283
|
|
|
327
|
-
#
|
|
328
|
-
|
|
329
|
-
|
|
284
|
+
# _forks self for each member in _current. Records the next value as
|
|
285
|
+
# [item, AuditExpand.new(<index of item>)]. Raises an error if _current
|
|
286
|
+
# does not respond to each.
|
|
287
|
+
def _expand
|
|
288
|
+
expanded = []
|
|
289
|
+
_current.each do |value|
|
|
290
|
+
expanded << _fork._record(AuditExpand.new(expanded.length), value)
|
|
291
|
+
end
|
|
292
|
+
expanded
|
|
330
293
|
end
|
|
331
|
-
|
|
332
|
-
alias _match =~
|
|
333
294
|
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
# a = Audit.new "abcd"
|
|
339
|
-
# a =~ /ab(\w)/ # => true
|
|
340
|
-
# $1 # => nil (should be 'c')
|
|
341
|
-
#
|
|
342
|
-
# # instead use _current directly...
|
|
343
|
-
# a._current =~ /ab(\w)/ # => true
|
|
344
|
-
# $1 # => 'c'
|
|
345
|
-
#
|
|
346
|
-
# Note the same applies to !~, as it executes through =~
|
|
347
|
-
def =~(regexp)
|
|
348
|
-
# note: this is not ideal... the variables $1, $2,
|
|
349
|
-
# etc are not sent back to the executing context (binding)
|
|
350
|
-
_current =~ regexp
|
|
295
|
+
# Returns true if the _sources and _values for self are equal
|
|
296
|
+
# to those of another.
|
|
297
|
+
def ==(another)
|
|
298
|
+
another.kind_of?(Audit) && self._sources == another._sources && self._values == another._values
|
|
351
299
|
end
|
|
352
|
-
|
|
353
|
-
# this shouldn't be necessary as Comparable feeds all it's methods
|
|
354
|
-
#include Comparable
|
|
355
300
|
|
|
356
|
-
#
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
301
|
+
# A kind of pretty-print for Audits. See the example in the overview.
|
|
302
|
+
def _to_s
|
|
303
|
+
# TODO -- find a way to avoid repeating groups
|
|
304
|
+
|
|
305
|
+
group = []
|
|
306
|
+
groups = [group]
|
|
307
|
+
extended_groups = [groups]
|
|
308
|
+
group_merges = []
|
|
309
|
+
extended_group_merges = []
|
|
310
|
+
current_level = nil
|
|
311
|
+
current_index = nil
|
|
312
|
+
|
|
313
|
+
_each_record do |source, value, merge_level, merge_index, index|
|
|
314
|
+
source_str, value_str = if block_given?
|
|
315
|
+
yield(source, value)
|
|
316
|
+
else
|
|
317
|
+
[source, value == nil ? '' : PP.singleline_pp(value, '')]
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
if !group.empty? && (merge_level != current_level || index == 0)
|
|
321
|
+
unless merge_level <= current_level
|
|
322
|
+
groups = []
|
|
323
|
+
extended_groups << groups
|
|
324
|
+
end
|
|
361
325
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
326
|
+
group = []
|
|
327
|
+
groups << group
|
|
328
|
+
|
|
329
|
+
if merge_level < current_level
|
|
330
|
+
if merge_index == 0
|
|
331
|
+
extended_group_merges << group.object_id
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
unless index == 0
|
|
335
|
+
group_merges << group.object_id
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
group << "o-[#{source_str}] #{value_str}"
|
|
341
|
+
current_level = merge_level
|
|
342
|
+
current_index = merge_index
|
|
343
|
+
end
|
|
371
344
|
|
|
372
|
-
|
|
373
|
-
|
|
345
|
+
lines = []
|
|
346
|
+
group_prefix = ""
|
|
347
|
+
extended_groups.each do |ext_groups|
|
|
348
|
+
indentation = 0
|
|
374
349
|
|
|
350
|
+
ext_groups.each_with_index do |ext_group, group_num|
|
|
351
|
+
ext_group.each_with_index do |line, line_num|
|
|
352
|
+
if line_num == 0
|
|
353
|
+
unless lines.empty?
|
|
354
|
+
lines << group_prefix + " " * indentation + "| " * (group_num-indentation)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
if group_merges.include?(ext_group.object_id)
|
|
358
|
+
lines << group_prefix + " " * indentation + "`-" * (group_num-indentation) + line
|
|
359
|
+
indentation = group_num
|
|
360
|
+
|
|
361
|
+
if extended_group_merges.include?(ext_group.object_id)
|
|
362
|
+
lines.last.gsub!(/\| \s*/) {|match| "`-" + "-" * (match.length - 2)}
|
|
363
|
+
group_prefix.gsub!(/\| /, " ")
|
|
364
|
+
end
|
|
365
|
+
next
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
lines << group_prefix + " " * indentation + "| " * (group_num-indentation) + line
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
group_prefix += " " * (ext_groups.length-1) + "| "
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
lines.join("\n") + "\n"
|
|
377
|
+
end
|
|
378
|
+
|
|
375
379
|
protected
|
|
376
380
|
|
|
377
381
|
attr_writer :_sources, :_values
|
|
378
|
-
|
|
379
|
-
# Forwards all missing methods to _current
|
|
380
|
-
def method_missing(sym, *args, &block)
|
|
381
|
-
_current.send(sym, *args, &block)
|
|
382
|
-
end
|
|
383
382
|
|
|
384
383
|
private
|
|
385
384
|
|
|
386
|
-
# helper method to recursively collect the
|
|
387
|
-
def
|
|
385
|
+
# helper method to recursively collect the value trail for a given source
|
|
386
|
+
def collect_records(source, value, &block)
|
|
388
387
|
case source
|
|
389
|
-
when
|
|
390
|
-
|
|
388
|
+
when AuditMerge
|
|
389
|
+
collection = []
|
|
390
|
+
0.upto(source.length-1) do |i|
|
|
391
|
+
collection << collect_records(source[i], value[i], &block)
|
|
392
|
+
end
|
|
393
|
+
collection
|
|
391
394
|
when Audit
|
|
392
|
-
source.
|
|
395
|
+
source._collect_records(&block)
|
|
393
396
|
else
|
|
394
|
-
source
|
|
397
|
+
yield(source, value)
|
|
395
398
|
end
|
|
396
399
|
end
|
|
397
400
|
|
|
398
|
-
|
|
399
|
-
def value_trail(source, value)
|
|
401
|
+
def each_record(source, value, merge_level, merge_index, index, &block)
|
|
400
402
|
case source
|
|
401
|
-
when
|
|
402
|
-
|
|
403
|
-
0.upto(source.length-1) do |
|
|
404
|
-
|
|
403
|
+
when AuditMerge
|
|
404
|
+
merge_level += 1
|
|
405
|
+
0.upto(source.length-1) do |i|
|
|
406
|
+
each_record(source[i], value[i], merge_level, i, index, &block)
|
|
405
407
|
end
|
|
406
|
-
trail
|
|
407
408
|
when Audit
|
|
408
|
-
source.
|
|
409
|
+
source._each_record(merge_level, merge_index, &block)
|
|
409
410
|
else
|
|
410
|
-
value
|
|
411
|
+
yield(source, value, merge_level, merge_index, index)
|
|
411
412
|
end
|
|
412
413
|
end
|
|
413
414
|
end
|