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.
Files changed (185) hide show
  1. data/Basic Overview +151 -0
  2. data/Command Reference +99 -0
  3. data/History +24 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README +29 -57
  6. data/Rakefile +30 -37
  7. data/Tutorial +243 -191
  8. data/bin/tap +66 -35
  9. data/lib/tap.rb +47 -29
  10. data/lib/tap/app.rb +700 -342
  11. data/lib/tap/{script → cmd}/console.rb +0 -0
  12. data/lib/tap/{script → cmd}/destroy.rb +0 -0
  13. data/lib/tap/{script → cmd}/generate.rb +0 -0
  14. data/lib/tap/cmd/run.rb +156 -0
  15. data/lib/tap/constants.rb +4 -0
  16. data/lib/tap/dump.rb +57 -0
  17. data/lib/tap/env.rb +316 -0
  18. data/lib/tap/file_task.rb +106 -109
  19. data/lib/tap/generator.rb +4 -1
  20. data/lib/tap/generator/generators/command/USAGE +6 -0
  21. data/lib/tap/generator/generators/command/command_generator.rb +17 -0
  22. data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
  23. data/lib/tap/generator/generators/config/USAGE +21 -0
  24. data/lib/tap/generator/generators/config/config_generator.rb +17 -7
  25. data/lib/tap/generator/generators/file_task/USAGE +3 -0
  26. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
  27. data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
  28. data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
  29. data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
  30. data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
  31. data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
  32. data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
  33. data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
  34. data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
  36. data/lib/tap/generator/generators/task/USAGE +3 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +18 -5
  38. data/lib/tap/generator/generators/task/templates/task.erb +7 -12
  39. data/lib/tap/generator/generators/task/templates/test.erb +10 -11
  40. data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
  41. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  42. data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
  43. data/lib/tap/patches/rake/testtask.rb +55 -0
  44. data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
  45. data/lib/tap/patches/ruby19/parsedate.rb +16 -0
  46. data/lib/tap/root.rb +172 -67
  47. data/lib/tap/script.rb +70 -336
  48. data/lib/tap/support/aggregator.rb +55 -0
  49. data/lib/tap/support/audit.rb +281 -280
  50. data/lib/tap/support/batchable.rb +59 -0
  51. data/lib/tap/support/class_configuration.rb +279 -0
  52. data/lib/tap/support/configurable.rb +92 -0
  53. data/lib/tap/support/configurable_methods.rb +296 -0
  54. data/lib/tap/support/executable.rb +98 -0
  55. data/lib/tap/support/executable_queue.rb +82 -0
  56. data/lib/tap/support/logger.rb +9 -15
  57. data/lib/tap/support/rake.rb +43 -54
  58. data/lib/tap/support/run_error.rb +32 -13
  59. data/lib/tap/support/shell_utils.rb +47 -0
  60. data/lib/tap/support/tdoc.rb +9 -8
  61. data/lib/tap/support/tdoc/config_attr.rb +40 -16
  62. data/lib/tap/support/validation.rb +77 -0
  63. data/lib/tap/support/versions.rb +36 -36
  64. data/lib/tap/task.rb +276 -482
  65. data/lib/tap/test.rb +20 -261
  66. data/lib/tap/test/env_vars.rb +7 -5
  67. data/lib/tap/test/file_methods.rb +126 -121
  68. data/lib/tap/test/subset_methods.rb +86 -45
  69. data/lib/tap/test/tap_methods.rb +271 -0
  70. data/lib/tap/workflow.rb +174 -46
  71. data/test/app/config/another/task.yml +1 -0
  72. data/test/app/config/erb.yml +2 -1
  73. data/test/app/config/some/task.yml +1 -0
  74. data/test/app/config/template.yml +2 -6
  75. data/test/app_test.rb +1241 -1008
  76. data/test/env/test_configure/recurse_a.yml +2 -0
  77. data/test/env/test_configure/recurse_b.yml +2 -0
  78. data/test/env/test_configure/tap.yml +23 -0
  79. data/test/env/test_load_env_config/dir/tap.yml +3 -0
  80. data/test/env/test_load_env_config/recurse_a.yml +2 -0
  81. data/test/env/test_load_env_config/recurse_b.yml +2 -0
  82. data/test/env/test_load_env_config/tap.yml +3 -0
  83. data/test/env_test.rb +198 -0
  84. data/test/file_task_test.rb +70 -53
  85. data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
  86. data/test/root_test.rb +621 -454
  87. data/test/script_test.rb +38 -174
  88. data/test/support/aggregator_test.rb +99 -0
  89. data/test/support/audit_test.rb +409 -416
  90. data/test/support/batchable_test.rb +74 -0
  91. data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
  92. data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
  93. data/test/support/configurable_test.rb +295 -0
  94. data/test/support/executable_queue_test.rb +103 -0
  95. data/test/support/executable_test.rb +38 -0
  96. data/test/support/logger_test.rb +17 -17
  97. data/test/support/rake_test.rb +4 -2
  98. data/test/support/shell_utils_test.rb +24 -0
  99. data/test/support/tdoc_test.rb +265 -258
  100. data/test/support/validation_test.rb +54 -0
  101. data/test/support/versions_test.rb +38 -38
  102. data/test/tap_test_helper.rb +19 -5
  103. data/test/tap_test_suite.rb +5 -2
  104. data/test/task_base_test.rb +13 -104
  105. data/test/task_syntax_test.rb +300 -0
  106. data/test/task_test.rb +258 -381
  107. data/test/test/env_vars_test.rb +40 -40
  108. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
  109. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
  110. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
  111. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
  112. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
  113. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
  114. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
  115. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
  116. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
  117. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
  118. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
  119. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
  120. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
  121. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
  122. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
  123. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
  124. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
  125. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
  126. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
  127. data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
  128. data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
  129. data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
  130. data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
  131. data/test/test/file_methods_doc_test.rb +29 -0
  132. data/test/test/file_methods_test.rb +214 -143
  133. data/test/test/subset_methods_test.rb +111 -115
  134. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
  135. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
  136. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
  137. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
  138. data/test/test/tap_methods_test.rb +399 -0
  139. data/test/workflow_test.rb +101 -91
  140. metadata +86 -70
  141. data/lib/tap/generator/generators/package/package_generator.rb +0 -38
  142. data/lib/tap/generator/generators/package/templates/package.erb +0 -186
  143. data/lib/tap/generator/generators/script/USAGE +0 -0
  144. data/lib/tap/generator/generators/script/script_generator.rb +0 -17
  145. data/lib/tap/script/run.rb +0 -154
  146. data/lib/tap/support/batch_queue.rb +0 -162
  147. data/lib/tap/support/combinator.rb +0 -114
  148. data/lib/tap/support/task_configuration.rb +0 -169
  149. data/lib/tap/support/template.rb +0 -81
  150. data/lib/tap/support/templater.rb +0 -155
  151. data/lib/tap/version.rb +0 -4
  152. data/test/app/config/addition_template.yml +0 -6
  153. data/test/app_class_test.rb +0 -33
  154. data/test/check/binding_eval.rb +0 -23
  155. data/test/check/define_method_check.rb +0 -22
  156. data/test/check/dependencies_check.rb +0 -175
  157. data/test/check/inheritance_check.rb +0 -22
  158. data/test/support/batch_queue_test.rb +0 -320
  159. data/test/support/combinator_test.rb +0 -249
  160. data/test/support/template_test.rb +0 -122
  161. data/test/support/templater/erb.txt +0 -2
  162. data/test/support/templater/erb.yml +0 -2
  163. data/test/support/templater/somefile.txt +0 -2
  164. data/test/support/templater_test.rb +0 -192
  165. data/test/task/config/template.yml +0 -4
  166. data/test/task_class_test.rb +0 -170
  167. data/test/task_execute_test.rb +0 -262
  168. data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
  169. data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
  170. data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
  171. data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
  172. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  173. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  174. data/test/test/file_methods/test_file_compare/expected/output_1.txt +0 -3
  175. data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
  176. data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
  177. data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
  178. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  179. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  180. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  181. data/test/test/file_methods/test_yml_compare/expected/output_1.yml +0 -6
  182. data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
  183. data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
  184. data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
  185. data/test/test_test.rb +0 -373
@@ -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
- # === Overview
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 is designed so you can ask a result 'hey where did you come from?' rather than
12
- # being able to ask an input 'what are all the results you ultimately produce?'. Say your
13
- # workflowconsists of 3 sequential tasks [:a, :b, :c]. Tasks :a and :b add one to their input
14
- # value, while :c adds two. Behind the scences, this is what happens when we run the workflow
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
- # # when task :a finishes, it records the new value and
23
- # # the source of the value (ie task ':a')
24
- # ... task :a adds one ...
25
- # audit._record(:a, 4)
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
- # # next the audit is passed to task :b, then task :c
28
- # # each of which records the next source and value
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
- # # at the end, if you want to know how your final
35
- # # value got to be 7, you can look at the source_trail
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
- # Audit supports forks by duplicating an audit trail (ie the recorded sources and values) and
40
- # merges by storing the various sources and values in an array. For example:
49
+ # # record some values
50
+ # a._record(:A, 2)
51
+ # a._record(:B, 3)
41
52
  #
42
- # # now let :a fork its results to both :b and :c
43
- # audit = Audit.new(3)
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
- # ... tasks :b adds one and :c adds two ...
49
- # fork_b._record(:b, 5)
50
- # fork_c._record(:c, 6)
56
+ # a._source_trail # => [nil, :A, :B]
57
+ # a._value_trail # => [1, 2, 3]
51
58
  #
52
- # # at the end you have a separate source trail for
53
- # # each result.
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
- # # now lets say you decided to merge both of
58
- # # these trails into a new task :d which adds
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
- # # now you can look back at the full source trail
65
- # # where an array of sources indicates two trails
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
- # An important thing to note is that while in these examples symbols have been used
70
- # to represent the tasks, the actual tasks themselves are recorded as sources in practice.
71
- # Thus the source trails can be used to access task configurations and other information
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
- # === Working with Audits
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
- # Once an input enters the execution stream, it will be used to initialize an Audit.
79
- # From this point on, the Audit and not the value will be passed among tasks and ultimately
80
- # passed out in the results array.
77
+ # c._record(:E, "a string value")
78
+ # c._record(:F, {'a' => 'hash value'})
79
+ # c._record(:G, ['an', 'array', 'value'])
81
80
  #
82
- # This must be kept in mind when building tasks into a workflow. For convenience, Audits are
83
- # constructed to pass unknown methods and most comparision methods to the current value, such
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
- # t = Tap::Task.new
88
- # t.on_complete do |results|
89
- # results.each do |result|
90
- # # you might expect result to be a value like 10 or "str"
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
- # result.class # => Audit
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
- # t = Tap::Task.new do |task, input|
119
- # # here in the process block, the input is the current value
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
- # results = t.execute(3)
126
- # results.class # => Array
127
- # results.length # => 1
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
- # === Summing it up:
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
- # - Task inputs may be values or Audits
134
- # - Task results are always an array of Audits
135
- # - Workflow blocks (ex: on_complete and condition) recieve Audits and not values
136
- # - The process method recieves the values themselves
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
- # Convenience method to create a new Audit for each of the inputs, if the
142
- # input is not already an Audit. Returns an array of Audits.
143
- def register(*inputs)
144
- inputs.collect {|input| input.kind_of?(Audit) ? input : Audit.new(input) }
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(1)
152
- # b = Audit.merge(a, 2)
153
- # b._values # => [[1, 2]]
154
- # b._sources # => [[a, nil]]
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 inputs are provided, then merge a new Audit with an initial value of nil.
157
- # If only one input is provided, then merge returns a new Audit initialized to
158
- # the input, or a _fork of the input if it is already an Audit.
159
- def merge(*inputs)
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
- values = inputs.collect {|input| input.kind_of?(Audit) ? input._current : input}
167
- sources = inputs.collect {|input| input.kind_of?(Audit) ? input : nil}
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=nil, source=nil)
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 # => [:a]
197
- # c.values # => [1]
207
+ # c.sources # => [:a]
208
+ # c.values # => [1]
198
209
  #
199
210
  # c.record(:ab, [a,b])
200
- # c.sources # => [:a, :ab]
201
- # c.values # => [1, [1, 2]]
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
- _sources.collect do |source|
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
- trail = []
291
- 0.upto(_sources.length-1) do |index|
292
- trail << value_trail(_sources[index], _values[index])
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
- # Produces a new Audit suitable for development along a separate path, by merging
298
- # the input sources with self. The value of the new audit will be an array of the
299
- # current values of the input sources and self.
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
- # Produces a new Audit from self and records the next value as
317
- # the return value of the block. The block itself will be recored
318
- # as the value's source.
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
- # Compares _current with another using ==
328
- def ==(another)
329
- _current == another
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
- # The method =~ does NOT work properly with an audit. As with other
335
- # methods, =~ is aliased to work on _current. However, variables like
336
- # $1, $2, etc cannot be passed back. As a result:
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
- # Compares _current with another using <=> if <=> is defined
357
- # for _current. Otherwise returns 0.
358
- #def <=>(another)
359
- # _current.respond_to?(:<=>) ? _current <=> another : 0
360
- #end
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
- # CONSIDER FORWARDING ALL OF THESE!
363
- #
364
- #[:eql?, :equal?, :is_a?, :kind_of?, :nil?, :respond_to?, :tainted?, :to_str].each do |method|
365
- # alias_name = "_#{method}".to_sym
366
- # alias alias_name method
367
- # define_method(method) do |*args|
368
- # _current.send(method, *args)
369
- # end
370
- #end
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
- #alias _cmp ===
373
- #def ===(another) _current.send('===', another) end
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 source trail for a given source
387
- def source_trail(source)
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 Array
390
- source.collect {|s| source_trail(s)}
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._source_trail
395
+ source._collect_records(&block)
393
396
  else
394
- source
397
+ yield(source, value)
395
398
  end
396
399
  end
397
400
 
398
- # helper method to recursively collect the value trail for a given source
399
- def value_trail(source, value)
401
+ def each_record(source, value, merge_level, merge_index, index, &block)
400
402
  case source
401
- when Array
402
- trail = []
403
- 0.upto(source.length-1) do |index|
404
- trail << value_trail(source[index], value[index])
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._value_trail
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