tap 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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