tap 0.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|