tap 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (146) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README +71 -0
  3. data/Rakefile +117 -0
  4. data/bin/tap +63 -0
  5. data/lib/tap.rb +15 -0
  6. data/lib/tap/app.rb +739 -0
  7. data/lib/tap/file_task.rb +354 -0
  8. data/lib/tap/generator.rb +29 -0
  9. data/lib/tap/generator/generators/config/USAGE +0 -0
  10. data/lib/tap/generator/generators/config/config_generator.rb +23 -0
  11. data/lib/tap/generator/generators/config/templates/config.erb +2 -0
  12. data/lib/tap/generator/generators/file_task/USAGE +0 -0
  13. data/lib/tap/generator/generators/file_task/file_task_generator.rb +21 -0
  14. data/lib/tap/generator/generators/file_task/templates/task.erb +27 -0
  15. data/lib/tap/generator/generators/file_task/templates/test.erb +12 -0
  16. data/lib/tap/generator/generators/root/USAGE +0 -0
  17. data/lib/tap/generator/generators/root/root_generator.rb +36 -0
  18. data/lib/tap/generator/generators/root/templates/Rakefile +48 -0
  19. data/lib/tap/generator/generators/root/templates/app.yml +19 -0
  20. data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +4 -0
  21. data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +26 -0
  22. data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
  23. data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +57 -0
  24. data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +108 -0
  25. data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +40 -0
  26. data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +21 -0
  27. data/lib/tap/generator/generators/root/templates/server/config/environment.rb +60 -0
  28. data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +5 -0
  29. data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +53 -0
  30. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
  31. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +4 -0
  32. data/lib/tap/generator/generators/task/USAGE +0 -0
  33. data/lib/tap/generator/generators/task/task_generator.rb +21 -0
  34. data/lib/tap/generator/generators/task/templates/task.erb +21 -0
  35. data/lib/tap/generator/generators/task/templates/test.erb +29 -0
  36. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  37. data/lib/tap/generator/generators/workflow/templates/task.erb +16 -0
  38. data/lib/tap/generator/generators/workflow/templates/test.erb +7 -0
  39. data/lib/tap/generator/generators/workflow/workflow_generator.rb +21 -0
  40. data/lib/tap/generator/options.rb +26 -0
  41. data/lib/tap/generator/usage.rb +26 -0
  42. data/lib/tap/root.rb +275 -0
  43. data/lib/tap/script/console.rb +7 -0
  44. data/lib/tap/script/destroy.rb +8 -0
  45. data/lib/tap/script/generate.rb +8 -0
  46. data/lib/tap/script/run.rb +111 -0
  47. data/lib/tap/script/server.rb +12 -0
  48. data/lib/tap/support/audit.rb +415 -0
  49. data/lib/tap/support/batch_queue.rb +165 -0
  50. data/lib/tap/support/combinator.rb +114 -0
  51. data/lib/tap/support/logger.rb +91 -0
  52. data/lib/tap/support/rap.rb +38 -0
  53. data/lib/tap/support/run_error.rb +20 -0
  54. data/lib/tap/support/template.rb +81 -0
  55. data/lib/tap/support/templater.rb +155 -0
  56. data/lib/tap/support/versions.rb +63 -0
  57. data/lib/tap/task.rb +448 -0
  58. data/lib/tap/test.rb +320 -0
  59. data/lib/tap/test/env_vars.rb +16 -0
  60. data/lib/tap/test/inference_methods.rb +298 -0
  61. data/lib/tap/test/subset_methods.rb +260 -0
  62. data/lib/tap/version.rb +3 -0
  63. data/lib/tap/workflow.rb +73 -0
  64. data/test/app/config/addition_template.yml +6 -0
  65. data/test/app/config/batch.yml +2 -0
  66. data/test/app/config/empty.yml +0 -0
  67. data/test/app/config/erb.yml +1 -0
  68. data/test/app/config/template.yml +6 -0
  69. data/test/app/config/version-0.1.yml +1 -0
  70. data/test/app/config/version.yml +1 -0
  71. data/test/app/lib/app_test_task.rb +2 -0
  72. data/test/app_class_test.rb +33 -0
  73. data/test/app_test.rb +1372 -0
  74. data/test/file_task/config/batch.yml +2 -0
  75. data/test/file_task/config/configured.yml +1 -0
  76. data/test/file_task/old_file_one.txt +0 -0
  77. data/test/file_task/old_file_two.txt +0 -0
  78. data/test/file_task_test.rb +1041 -0
  79. data/test/root/alt_lib/alt_module.rb +4 -0
  80. data/test/root/lib/absolute_alt_filepath.rb +2 -0
  81. data/test/root/lib/alternative_filepath.rb +2 -0
  82. data/test/root/lib/another_module.rb +2 -0
  83. data/test/root/lib/nested/some_module.rb +4 -0
  84. data/test/root/lib/no_module_included.rb +0 -0
  85. data/test/root/lib/some/module.rb +4 -0
  86. data/test/root/lib/some_class.rb +2 -0
  87. data/test/root/lib/some_module.rb +3 -0
  88. data/test/root/load_path/load_path_module.rb +2 -0
  89. data/test/root/load_path/skip_module.rb +2 -0
  90. data/test/root/mtime/older.txt +0 -0
  91. data/test/root/unload/full_path.rb +2 -0
  92. data/test/root/unload/loaded_by_nested.rb +2 -0
  93. data/test/root/unload/nested/nested_load.rb +6 -0
  94. data/test/root/unload/nested/nested_with_ext.rb +4 -0
  95. data/test/root/unload/nested/relative_path.rb +4 -0
  96. data/test/root/unload/older.rb +2 -0
  97. data/test/root/unload/unload_base.rb +9 -0
  98. data/test/root/versions/another.yml +0 -0
  99. data/test/root/versions/file-0.1.2.yml +0 -0
  100. data/test/root/versions/file-0.1.yml +0 -0
  101. data/test/root/versions/file.yml +0 -0
  102. data/test/root_test.rb +483 -0
  103. data/test/support/audit_test.rb +449 -0
  104. data/test/support/batch_queue_test.rb +320 -0
  105. data/test/support/combinator_test.rb +249 -0
  106. data/test/support/logger_test.rb +31 -0
  107. data/test/support/template_test.rb +122 -0
  108. data/test/support/templater/erb.txt +2 -0
  109. data/test/support/templater/erb.yml +2 -0
  110. data/test/support/templater/somefile.txt +2 -0
  111. data/test/support/templater_test.rb +192 -0
  112. data/test/support/versions_test.rb +71 -0
  113. data/test/tap_test_helper.rb +4 -0
  114. data/test/tap_test_suite.rb +4 -0
  115. data/test/task/config/batch.yml +2 -0
  116. data/test/task/config/batched.yml +2 -0
  117. data/test/task/config/configured.yml +1 -0
  118. data/test/task/config/example.yml +1 -0
  119. data/test/task/config/overriding.yml +2 -0
  120. data/test/task/config/task_with_config.yml +1 -0
  121. data/test/task/config/template.yml +4 -0
  122. data/test/task_class_test.rb +118 -0
  123. data/test/task_execute_test.rb +233 -0
  124. data/test/task_test.rb +424 -0
  125. data/test/test/inference_methods/test_assert_expected/expected/file.txt +1 -0
  126. data/test/test/inference_methods/test_assert_expected/expected/folder/file.txt +1 -0
  127. data/test/test/inference_methods/test_assert_expected/input/file.txt +1 -0
  128. data/test/test/inference_methods/test_assert_expected/input/folder/file.txt +1 -0
  129. data/test/test/inference_methods/test_assert_files_exist/input/input_1.txt +0 -0
  130. data/test/test/inference_methods/test_assert_files_exist/input/input_2.txt +0 -0
  131. data/test/test/inference_methods/test_file_compare/expected/output_1.txt +3 -0
  132. data/test/test/inference_methods/test_file_compare/expected/output_2.txt +1 -0
  133. data/test/test/inference_methods/test_file_compare/input/input_1.txt +3 -0
  134. data/test/test/inference_methods/test_file_compare/input/input_2.txt +3 -0
  135. data/test/test/inference_methods/test_infer_glob/expected/file.yml +0 -0
  136. data/test/test/inference_methods/test_infer_glob/expected/file_1.txt +0 -0
  137. data/test/test/inference_methods/test_infer_glob/expected/file_2.txt +0 -0
  138. data/test/test/inference_methods/test_yml_compare/expected/output_1.yml +6 -0
  139. data/test/test/inference_methods/test_yml_compare/expected/output_2.yml +6 -0
  140. data/test/test/inference_methods/test_yml_compare/input/input_1.yml +4 -0
  141. data/test/test/inference_methods/test_yml_compare/input/input_2.yml +4 -0
  142. data/test/test/inference_methods_test.rb +311 -0
  143. data/test/test/subset_methods_test.rb +115 -0
  144. data/test/test_test.rb +233 -0
  145. data/test/workflow_test.rb +108 -0
  146. metadata +274 -0
@@ -0,0 +1,7 @@
1
+ require "irb"
2
+
3
+ def app
4
+ Tap::App.instance
5
+ end
6
+
7
+ IRB.start
@@ -0,0 +1,8 @@
1
+ require 'tap/generator'
2
+ Rails::Generator::Base.use_tap_sources!
3
+
4
+ require 'rails_generator/scripts/destroy'
5
+ generator = ARGV.shift
6
+ script = Rails::Generator::Scripts::Destroy.new
7
+ script.extend Tap::Generator::Usage
8
+ script.run(ARGV, :generator => generator)
@@ -0,0 +1,8 @@
1
+ require 'tap/generator'
2
+ Rails::Generator::Base.use_tap_sources!
3
+
4
+ require 'rails_generator/scripts/generate'
5
+ generator = ARGV.shift
6
+ script = Rails::Generator::Scripts::Generate.new
7
+ script.extend Tap::Generator::Usage
8
+ script.run(ARGV, :generator => generator)
@@ -0,0 +1,111 @@
1
+ app = Tap::App.instance
2
+ task_config = {}
3
+
4
+ #
5
+ # handle options
6
+ #
7
+ require 'getoptlong'
8
+
9
+ opts = GetoptLong.new(
10
+ ['--app-config', '-a', GetoptLong::REQUIRED_ARGUMENT],# "Specifies an application config file"],
11
+ ['--config', '-c', GetoptLong::REQUIRED_ARGUMENT],# "Specifies configurations for the task."],
12
+ ['--quiet', '-q', GetoptLong::NO_ARGUMENT],# "Suppresses logging"],
13
+ ['--force', '-f', GetoptLong::NO_ARGUMENT],# "Force execution at checkpoints."],
14
+ ['--debug', '-d', GetoptLong::NO_ARGUMENT],# "Trace execution and debug."],
15
+ ['--help', '-h', GetoptLong::OPTIONAL_ARGUMENT])#, "Display help for app, or specified task."])
16
+
17
+ opts.each do |opt, value|
18
+ case opt
19
+ when '--help'
20
+ puts "help!"
21
+ exit
22
+
23
+ if value.empty?
24
+ help
25
+ else
26
+ task = task(value)
27
+ puts task.class.help
28
+ puts "Default Config:"
29
+ puts task.class.default_config.stringify_keys.to_yaml
30
+ end
31
+
32
+ when '--usage'
33
+ puts "usage!"
34
+ exit
35
+
36
+ when '--app-config'
37
+ config = Tap::App.parse_yaml(value)
38
+ if config.kind_of?(String)
39
+ raise "application config file does not exist: #{config}" unless File.exists?(config)
40
+ config = Tap::App.read_erb_yaml(config)
41
+ end
42
+ app.reconfigure(config)
43
+
44
+ when '--config'
45
+ task_config = Tap::App.parse_yaml(value)
46
+ if task_config.kind_of?(String)
47
+ raise "task config file does not exist: #{config}" unless File.exists?(config)
48
+ task_config = Tap::App.read_erb_yaml(config)
49
+ end
50
+
51
+ when '--quiet', '--force', '--debug'
52
+ # simply track these have been set
53
+ opt =~ /^-+(\w+)/
54
+ app.options.send("#{$1}=", true)
55
+
56
+ else
57
+ puts "unknown option: #{opt}"
58
+ exit
59
+ end
60
+ end
61
+
62
+ #
63
+ # gather arguments
64
+ # (be sure to clear for gets during interruption)
65
+
66
+ if ARGV.empty?
67
+ puts "no task specified"
68
+ exit
69
+ end
70
+
71
+ td = ARGV.shift
72
+ args = ARGV.collect {|arg| Tap::App.parse_yaml(arg) }
73
+ ARGV.clear
74
+
75
+ task = app.task(td, task_config)
76
+
77
+ #
78
+ # set signals and run!
79
+ #
80
+
81
+ # info signal
82
+ Signal.trap("INFO") do
83
+ puts app.info
84
+ end
85
+
86
+ # interuption signal
87
+ Signal.trap("INT") do
88
+ puts " interrupted!"
89
+ # prompt for decision
90
+ while true
91
+ print "stop, terminate, or resume? (s/t/r):"
92
+ case gets.strip
93
+ when /s(top)?/i
94
+ app.stop
95
+ break
96
+ when /t(erminate)?/i
97
+ app.terminate
98
+ break
99
+ when /r(esume)?/i
100
+ break
101
+ else
102
+ puts "unexpected response..."
103
+ end
104
+ end
105
+ end
106
+
107
+ puts "ctl-i prints information"
108
+ puts "ctl-c interupts execution"
109
+ puts "beginning run..."
110
+
111
+ app.run(task, *args)
@@ -0,0 +1,12 @@
1
+ # change to the server dir so that script/server launches as normal
2
+ # (otherwise Mongrel can raise errors because it can't find a log file)
3
+ Dir.chdir Tap::App.instance[:server]
4
+
5
+ server_script = "script/server"
6
+ unless File.exists?(server_script)
7
+ puts "server script does not exist: #{Tap::App.instance.filepath(:server, server_script)}"
8
+ puts "no tap server available?"
9
+ exit
10
+ end
11
+
12
+ load server_script
@@ -0,0 +1,415 @@
1
+ module Tap
2
+ module Support
3
+
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.
10
+ #
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)
21
+ #
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)
26
+ #
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)
33
+ #
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]
38
+ #
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:
41
+ #
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
47
+ #
48
+ # ... tasks :b adds one and :c adds two ...
49
+ # fork_b._record(:b, 5)
50
+ # fork_c._record(:c, 6)
51
+ #
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]
56
+ #
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)
63
+ #
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]
68
+ #
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.
75
+ #
76
+ # === Working with Audits
77
+ #
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.
81
+ #
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>.
86
+ #
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
93
+ #
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>
117
+ #
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
124
+ #
125
+ # results = t.execute(3)
126
+ # results.class # => Array
127
+ # results.length # => 1
128
+ # results.first.class # => Audit
129
+ # results.first._current # => 4
130
+ #
131
+ # === Summing it up:
132
+ #
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
137
+ #
138
+ class Audit
139
+ class << self
140
+
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.
150
+ #
151
+ # a = Audit.new(1)
152
+ # b = Audit.merge(a, 2)
153
+ # b._values # => [[1, 2]]
154
+ # b._sources # => [[a, nil]]
155
+ #
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
161
+ when 0 then Audit.new
162
+ when 1
163
+ input = inputs.first
164
+ input.kind_of?(Audit) ? input._fork : Audit.new(input)
165
+ else
166
+ values = inputs.collect {|input| input.kind_of?(Audit) ? input._current : input}
167
+ sources = inputs.collect {|input| input.kind_of?(Audit) ? input : nil}
168
+ Audit.new(values, sources)
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ attr_reader :_sources, :_values
175
+
176
+ # A new audit takes a value and/or source. A nil source is typically given
177
+ # for the original value.
178
+ def initialize(value=nil, source=nil)
179
+ @_sources = []
180
+ @_values = []
181
+
182
+ _record(source, value)
183
+ end
184
+
185
+ # Records the next value produced by the source. When an audit is
186
+ # passed as a value, record will record the current value of the audit.
187
+ # Record will similarly resolve every audit in an array containing audits.
188
+ #
189
+ # Example:
190
+ #
191
+ # a = Audit.new(1)
192
+ # b = Audit.new(2)
193
+ # c = Audit.new(3)
194
+ #
195
+ # c.record(:a, a)
196
+ # c.sources # => [:a]
197
+ # c.values # => [1]
198
+ #
199
+ # c.record(:ab, [a,b])
200
+ # c.sources # => [:a, :ab]
201
+ # c.values # => [1, [1, 2]]
202
+ def _record(source, value)
203
+ _sources << source
204
+ _values << value
205
+ self
206
+ end
207
+
208
+ # The original value used to initialize the Audit
209
+ def _original
210
+ _values.first
211
+ end
212
+
213
+ # The current (ie last) value recorded in the Audit
214
+ def _current
215
+ _values.last
216
+ end
217
+
218
+ # The original source used to initialize the Audit
219
+ def _original_source
220
+ _sources.first
221
+ end
222
+
223
+ # The current (ie last) source recorded in the Audit
224
+ def _current_source
225
+ _sources.last
226
+ end
227
+
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
+ # Searches back and recursively (if the source is an audit) collects all sources
280
+ # for the current value.
281
+ def _source_trail
282
+ _sources.collect do |source|
283
+ source_trail(source)
284
+ end
285
+ end
286
+
287
+ # Searches back and recursively (if the source is an audit) collects all values
288
+ # leading to the current value.
289
+ def _value_trail
290
+ trail = []
291
+ 0.upto(_sources.length-1) do |index|
292
+ trail << value_trail(_sources[index], _values[index])
293
+ end
294
+ trail
295
+ end
296
+
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)
305
+ end
306
+
307
+ # Produces a new Audit with duplicate sources and values, suitable for
308
+ # separate development along a separate path.
309
+ def _fork
310
+ a = Audit.new
311
+ a._sources = _sources.dup
312
+ a._values = _values.dup
313
+ a
314
+ end
315
+
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
323
+ end
324
+
325
+ alias _eql ==
326
+
327
+ # Compares _current with another using ==
328
+ def ==(another)
329
+ _current == another
330
+ end
331
+
332
+ alias _match =~
333
+
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
351
+ end
352
+
353
+ # this shouldn't be necessary as Comparable feeds all it's methods
354
+ #include Comparable
355
+
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
361
+
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
371
+
372
+ #alias _cmp ===
373
+ #def ===(another) _current.send('===', another) end
374
+
375
+ protected
376
+
377
+ 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
+
384
+ private
385
+
386
+ # helper method to recursively collect the source trail for a given source
387
+ def source_trail(source)
388
+ case source
389
+ when Array
390
+ source.collect {|s| source_trail(s)}
391
+ when Audit
392
+ source._source_trail
393
+ else
394
+ source
395
+ end
396
+ end
397
+
398
+ # helper method to recursively collect the value trail for a given source
399
+ def value_trail(source, value)
400
+ case source
401
+ when Array
402
+ trail = []
403
+ 0.upto(source.length-1) do |index|
404
+ trail << value_trail(source[index], value[index])
405
+ end
406
+ trail
407
+ when Audit
408
+ source._value_trail
409
+ else
410
+ value
411
+ end
412
+ end
413
+ end
414
+ end
415
+ end