tap 0.7.9
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +21 -0
- data/README +71 -0
- data/Rakefile +117 -0
- data/bin/tap +63 -0
- data/lib/tap.rb +15 -0
- data/lib/tap/app.rb +739 -0
- data/lib/tap/file_task.rb +354 -0
- data/lib/tap/generator.rb +29 -0
- data/lib/tap/generator/generators/config/USAGE +0 -0
- data/lib/tap/generator/generators/config/config_generator.rb +23 -0
- data/lib/tap/generator/generators/config/templates/config.erb +2 -0
- data/lib/tap/generator/generators/file_task/USAGE +0 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +21 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +27 -0
- data/lib/tap/generator/generators/file_task/templates/test.erb +12 -0
- data/lib/tap/generator/generators/root/USAGE +0 -0
- data/lib/tap/generator/generators/root/root_generator.rb +36 -0
- data/lib/tap/generator/generators/root/templates/Rakefile +48 -0
- data/lib/tap/generator/generators/root/templates/app.yml +19 -0
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +4 -0
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +26 -0
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +57 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +108 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +40 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +21 -0
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +60 -0
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +5 -0
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +53 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +4 -0
- data/lib/tap/generator/generators/task/USAGE +0 -0
- data/lib/tap/generator/generators/task/task_generator.rb +21 -0
- data/lib/tap/generator/generators/task/templates/task.erb +21 -0
- data/lib/tap/generator/generators/task/templates/test.erb +29 -0
- data/lib/tap/generator/generators/workflow/USAGE +0 -0
- data/lib/tap/generator/generators/workflow/templates/task.erb +16 -0
- data/lib/tap/generator/generators/workflow/templates/test.erb +7 -0
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +21 -0
- data/lib/tap/generator/options.rb +26 -0
- data/lib/tap/generator/usage.rb +26 -0
- data/lib/tap/root.rb +275 -0
- data/lib/tap/script/console.rb +7 -0
- data/lib/tap/script/destroy.rb +8 -0
- data/lib/tap/script/generate.rb +8 -0
- data/lib/tap/script/run.rb +111 -0
- data/lib/tap/script/server.rb +12 -0
- data/lib/tap/support/audit.rb +415 -0
- data/lib/tap/support/batch_queue.rb +165 -0
- data/lib/tap/support/combinator.rb +114 -0
- data/lib/tap/support/logger.rb +91 -0
- data/lib/tap/support/rap.rb +38 -0
- data/lib/tap/support/run_error.rb +20 -0
- data/lib/tap/support/template.rb +81 -0
- data/lib/tap/support/templater.rb +155 -0
- data/lib/tap/support/versions.rb +63 -0
- data/lib/tap/task.rb +448 -0
- data/lib/tap/test.rb +320 -0
- data/lib/tap/test/env_vars.rb +16 -0
- data/lib/tap/test/inference_methods.rb +298 -0
- data/lib/tap/test/subset_methods.rb +260 -0
- data/lib/tap/version.rb +3 -0
- data/lib/tap/workflow.rb +73 -0
- data/test/app/config/addition_template.yml +6 -0
- data/test/app/config/batch.yml +2 -0
- data/test/app/config/empty.yml +0 -0
- data/test/app/config/erb.yml +1 -0
- data/test/app/config/template.yml +6 -0
- data/test/app/config/version-0.1.yml +1 -0
- data/test/app/config/version.yml +1 -0
- data/test/app/lib/app_test_task.rb +2 -0
- data/test/app_class_test.rb +33 -0
- data/test/app_test.rb +1372 -0
- data/test/file_task/config/batch.yml +2 -0
- data/test/file_task/config/configured.yml +1 -0
- data/test/file_task/old_file_one.txt +0 -0
- data/test/file_task/old_file_two.txt +0 -0
- data/test/file_task_test.rb +1041 -0
- data/test/root/alt_lib/alt_module.rb +4 -0
- data/test/root/lib/absolute_alt_filepath.rb +2 -0
- data/test/root/lib/alternative_filepath.rb +2 -0
- data/test/root/lib/another_module.rb +2 -0
- data/test/root/lib/nested/some_module.rb +4 -0
- data/test/root/lib/no_module_included.rb +0 -0
- data/test/root/lib/some/module.rb +4 -0
- data/test/root/lib/some_class.rb +2 -0
- data/test/root/lib/some_module.rb +3 -0
- data/test/root/load_path/load_path_module.rb +2 -0
- data/test/root/load_path/skip_module.rb +2 -0
- data/test/root/mtime/older.txt +0 -0
- data/test/root/unload/full_path.rb +2 -0
- data/test/root/unload/loaded_by_nested.rb +2 -0
- data/test/root/unload/nested/nested_load.rb +6 -0
- data/test/root/unload/nested/nested_with_ext.rb +4 -0
- data/test/root/unload/nested/relative_path.rb +4 -0
- data/test/root/unload/older.rb +2 -0
- data/test/root/unload/unload_base.rb +9 -0
- data/test/root/versions/another.yml +0 -0
- data/test/root/versions/file-0.1.2.yml +0 -0
- data/test/root/versions/file-0.1.yml +0 -0
- data/test/root/versions/file.yml +0 -0
- data/test/root_test.rb +483 -0
- data/test/support/audit_test.rb +449 -0
- data/test/support/batch_queue_test.rb +320 -0
- data/test/support/combinator_test.rb +249 -0
- data/test/support/logger_test.rb +31 -0
- data/test/support/template_test.rb +122 -0
- data/test/support/templater/erb.txt +2 -0
- data/test/support/templater/erb.yml +2 -0
- data/test/support/templater/somefile.txt +2 -0
- data/test/support/templater_test.rb +192 -0
- data/test/support/versions_test.rb +71 -0
- data/test/tap_test_helper.rb +4 -0
- data/test/tap_test_suite.rb +4 -0
- data/test/task/config/batch.yml +2 -0
- data/test/task/config/batched.yml +2 -0
- data/test/task/config/configured.yml +1 -0
- data/test/task/config/example.yml +1 -0
- data/test/task/config/overriding.yml +2 -0
- data/test/task/config/task_with_config.yml +1 -0
- data/test/task/config/template.yml +4 -0
- data/test/task_class_test.rb +118 -0
- data/test/task_execute_test.rb +233 -0
- data/test/task_test.rb +424 -0
- data/test/test/inference_methods/test_assert_expected/expected/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/expected/folder/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/input/file.txt +1 -0
- data/test/test/inference_methods/test_assert_expected/input/folder/file.txt +1 -0
- data/test/test/inference_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/inference_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/inference_methods/test_file_compare/expected/output_1.txt +3 -0
- data/test/test/inference_methods/test_file_compare/expected/output_2.txt +1 -0
- data/test/test/inference_methods/test_file_compare/input/input_1.txt +3 -0
- data/test/test/inference_methods/test_file_compare/input/input_2.txt +3 -0
- data/test/test/inference_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/inference_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/inference_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/inference_methods/test_yml_compare/expected/output_1.yml +6 -0
- data/test/test/inference_methods/test_yml_compare/expected/output_2.yml +6 -0
- data/test/test/inference_methods/test_yml_compare/input/input_1.yml +4 -0
- data/test/test/inference_methods/test_yml_compare/input/input_2.yml +4 -0
- data/test/test/inference_methods_test.rb +311 -0
- data/test/test/subset_methods_test.rb +115 -0
- data/test/test_test.rb +233 -0
- data/test/workflow_test.rb +108 -0
- metadata +274 -0
@@ -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
|