tap 0.7.9 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History +28 -0
- data/MIT-LICENSE +1 -1
- data/README +71 -43
- data/Rakefile +81 -64
- data/Tutorial +235 -0
- data/bin/tap +80 -44
- data/lib/tap.rb +41 -12
- data/lib/tap/app.rb +243 -246
- data/lib/tap/file_task.rb +357 -118
- data/lib/tap/generator.rb +88 -29
- data/lib/tap/generator/generators/config/config_generator.rb +4 -2
- data/lib/tap/generator/generators/config/templates/config.erb +1 -2
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
- data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
- data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
- data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
- data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
- data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
- data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
- data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
- data/lib/tap/generator/generators/package/package_generator.rb +38 -0
- data/lib/tap/generator/generators/package/templates/package.erb +186 -0
- data/lib/tap/generator/generators/root/root_generator.rb +14 -9
- data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
- data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
- data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
- data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +17 -0
- data/lib/tap/generator/generators/script/templates/script.erb +42 -0
- data/lib/tap/generator/generators/task/task_generator.rb +1 -1
- data/lib/tap/generator/generators/task/templates/task.erb +24 -16
- data/lib/tap/generator/generators/task/templates/test.erb +13 -17
- data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
- data/lib/tap/root.rb +108 -146
- data/lib/tap/script.rb +362 -0
- data/lib/tap/script/console.rb +28 -0
- data/lib/tap/script/destroy.rb +13 -1
- data/lib/tap/script/generate.rb +13 -1
- data/lib/tap/script/run.rb +100 -57
- data/lib/tap/support/batch_queue.rb +0 -3
- data/lib/tap/support/logger.rb +6 -3
- data/lib/tap/support/rake.rb +54 -0
- data/lib/tap/support/task_configuration.rb +169 -0
- data/lib/tap/support/tdoc.rb +198 -0
- data/lib/tap/support/tdoc/config_attr.rb +338 -0
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
- data/lib/tap/support/versions.rb +33 -1
- data/lib/tap/task.rb +339 -227
- data/lib/tap/test.rb +86 -128
- data/lib/tap/test/env_vars.rb +16 -5
- data/lib/tap/test/file_methods.rb +373 -0
- data/lib/tap/test/subset_methods.rb +299 -180
- data/lib/tap/version.rb +2 -1
- data/lib/tap/workflow.rb +2 -0
- data/test/app/lib/app_test_task.rb +1 -0
- data/test/app_test.rb +327 -83
- data/test/check/binding_eval.rb +23 -0
- data/test/check/define_method_check.rb +22 -0
- data/test/check/dependencies_check.rb +175 -0
- data/test/check/inheritance_check.rb +22 -0
- data/test/file_task_test.rb +524 -291
- data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
- data/test/root/glob/two.txt +0 -0
- data/test/root_test.rb +330 -262
- data/test/script_test.rb +194 -0
- data/test/support/audit_test.rb +5 -2
- data/test/support/combinator_test.rb +10 -10
- data/test/support/rake_test.rb +35 -0
- data/test/support/task_configuration_test.rb +272 -0
- data/test/support/tdoc_test.rb +363 -0
- data/test/support/templater_test.rb +2 -2
- data/test/support/versions_test.rb +32 -0
- data/test/tap_test_helper.rb +39 -0
- data/test/task_base_test.rb +115 -0
- data/test/task_class_test.rb +56 -4
- data/test/task_execute_test.rb +29 -0
- data/test/task_test.rb +89 -70
- data/test/test/env_vars_test.rb +48 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
- data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
- data/test/test/file_methods_test.rb +204 -0
- data/test/test/subset_methods_test.rb +93 -33
- data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
- data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
- data/test/test/test_file_task_test/expected/one.txt +1 -0
- data/test/test/test_file_task_test/expected/two.txt +1 -0
- data/test/test/test_file_task_test/input/one.txt +1 -0
- data/test/test/test_file_task_test/input/two.txt +1 -0
- data/test/test_test.rb +143 -3
- data/test/workflow_test.rb +2 -0
- data/vendor/rails_generator.rb +56 -0
- data/vendor/rails_generator/base.rb +263 -0
- data/vendor/rails_generator/commands.rb +581 -0
- data/vendor/rails_generator/generated_attribute.rb +42 -0
- data/vendor/rails_generator/lookup.rb +209 -0
- data/vendor/rails_generator/manifest.rb +53 -0
- data/vendor/rails_generator/options.rb +143 -0
- data/vendor/rails_generator/scripts.rb +83 -0
- data/vendor/rails_generator/scripts/destroy.rb +7 -0
- data/vendor/rails_generator/scripts/generate.rb +7 -0
- data/vendor/rails_generator/scripts/update.rb +12 -0
- data/vendor/rails_generator/simple_logger.rb +46 -0
- data/vendor/rails_generator/spec.rb +44 -0
- metadata +180 -196
- data/lib/tap/generator/generators/root/templates/app.yml +0 -19
- data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
- data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
- data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
- data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
- data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
- data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
- data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
- data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
- data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
- data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
- data/lib/tap/script/server.rb +0 -12
- data/lib/tap/support/rap.rb +0 -38
- data/lib/tap/test/inference_methods.rb +0 -298
- data/test/task/config/task_with_config.yml +0 -1
- data/test/test/inference_methods_test.rb +0 -311
data/bin/tap
CHANGED
|
@@ -1,63 +1,99 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Tap::
|
|
1
|
+
# usage: tap <command> {options} [args]
|
|
2
|
+
#
|
|
3
|
+
# examples:
|
|
4
|
+
# tap generate root /path/to/root # generates a root dir
|
|
5
|
+
# tap run taskname --option input # runs the 'taskname' task
|
|
6
|
+
#
|
|
7
|
+
# help:
|
|
8
|
+
# tap help # prints this help
|
|
9
|
+
# tap command --help # prints help for 'command'
|
|
10
|
+
|
|
11
|
+
require File.join( File.dirname(__FILE__), "../lib/tap.rb")
|
|
12
|
+
require "tap/script"
|
|
13
|
+
|
|
14
|
+
app = Tap::App.instance
|
|
15
|
+
script = Tap::Script.instance
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
# configure the app to tap.yml if it exists
|
|
19
|
+
config_file = Tap::Script.config_filepath(Dir.pwd)
|
|
20
|
+
config = Tap::Script.read_config(config_file)
|
|
21
|
+
script.configure_app(config)
|
|
22
|
+
rescue(Exception)
|
|
23
|
+
# catch errors and exit gracefully
|
|
24
|
+
# (errors usu from gem loading errors)
|
|
25
|
+
puts "Configuration error: #{$!.message}"
|
|
26
|
+
puts "Check #{config_file} configurations"
|
|
27
|
+
exit(1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# alert the user to the root directory if it's not Dir.pwd
|
|
31
|
+
unless app.options.quiet || app.root == File.expand_path(Dir.pwd)
|
|
32
|
+
puts "root: #{app.root}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#
|
|
36
|
+
# run before script
|
|
37
|
+
#
|
|
38
|
+
begin
|
|
39
|
+
eval(script.config.before.to_s)
|
|
40
|
+
rescue
|
|
41
|
+
puts "Error in before script."
|
|
42
|
+
if app.options.debug
|
|
43
|
+
raise
|
|
44
|
+
else
|
|
45
|
+
puts $!.message
|
|
46
|
+
exit(1)
|
|
47
|
+
end
|
|
21
48
|
end
|
|
22
49
|
|
|
23
50
|
begin
|
|
51
|
+
available_commands = script.config.scripts
|
|
24
52
|
command = ARGV.shift
|
|
53
|
+
|
|
25
54
|
case command
|
|
26
|
-
when "help", "?", nil
|
|
27
|
-
|
|
55
|
+
when "--help", "-h", "help", "?", nil
|
|
56
|
+
# give some help
|
|
57
|
+
puts Tap::Script.usage(__FILE__)
|
|
58
|
+
puts
|
|
28
59
|
puts "available commands:"
|
|
29
|
-
|
|
30
|
-
commands =
|
|
31
|
-
|
|
32
|
-
Tap::App.instance.relative_filepath(:script, script).chomp(".rb")
|
|
33
|
-
end
|
|
34
|
-
Tap::App.glob(lib_dir + "tap/script/**/*").each do |script|
|
|
35
|
-
next unless File.extname(script) == ".rb"
|
|
36
|
-
command = Tap::App.relative_filepath(lib_dir + "tap/script", script).chomp(".rb")
|
|
37
|
-
commands << command unless commands.include?(command)
|
|
38
|
-
end
|
|
60
|
+
|
|
61
|
+
commands = available_commands.keys
|
|
62
|
+
commands.unshift("help")
|
|
39
63
|
|
|
40
64
|
print " "
|
|
41
65
|
puts commands.sort.join("\n ")
|
|
42
66
|
puts
|
|
43
|
-
|
|
44
|
-
else
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if File.exists?(app_script_filepath)
|
|
49
|
-
load app_script_filepath
|
|
50
|
-
elsif File.exists?(tap_script_filepath)
|
|
51
|
-
load tap_script_filepath
|
|
67
|
+
puts "version #{Tap::VERSION} -- #{Tap::HOMEPAGE}"
|
|
68
|
+
else
|
|
69
|
+
if available_commands.has_key?(command)
|
|
70
|
+
# run the script, if it exists
|
|
71
|
+
load available_commands[command]
|
|
52
72
|
else
|
|
53
|
-
|
|
73
|
+
puts "Unknown command: '#{command}'"
|
|
74
|
+
puts "Type 'tap help' for usage information."
|
|
54
75
|
end
|
|
55
76
|
end
|
|
56
77
|
rescue
|
|
57
|
-
if
|
|
78
|
+
if app.options.debug
|
|
79
|
+
raise
|
|
80
|
+
else
|
|
81
|
+
puts $!.message
|
|
82
|
+
puts "Type 'tap #{command} --help' for usage information."
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
#
|
|
87
|
+
# run after script
|
|
88
|
+
#
|
|
89
|
+
begin
|
|
90
|
+
eval(script.config.after.to_s)
|
|
91
|
+
rescue
|
|
92
|
+
puts "Error in after script."
|
|
93
|
+
if app.options.debug
|
|
58
94
|
raise
|
|
59
95
|
else
|
|
60
96
|
puts $!.message
|
|
61
|
-
|
|
97
|
+
exit(1)
|
|
62
98
|
end
|
|
63
99
|
end
|
data/lib/tap.rb
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
|
-
|
|
1
|
+
# gem_original_require (set in rubygems) is used here to speed up
|
|
2
|
+
# requires when possible... ok since I'm manually activating the
|
|
3
|
+
# neeeded gems.
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
|
|
6
|
+
gem_original_require 'yaml' # expensive to load
|
|
7
|
+
gem_original_require 'logger'
|
|
8
|
+
gem_original_require 'ostruct'
|
|
9
|
+
gem_original_require 'thread'
|
|
10
|
+
gem_original_require 'monitor'
|
|
11
|
+
gem_original_require 'erb'
|
|
12
|
+
|
|
13
|
+
# Loading activesupport piecemeal like this cuts the tap load time in half.
|
|
14
|
+
gem 'activesupport'
|
|
15
|
+
|
|
16
|
+
gem_original_require 'active_support/core_ext/array/extract_options.rb'
|
|
17
|
+
class Array #:nodoc:
|
|
18
|
+
include ActiveSupport::CoreExtensions::Array::ExtractOptions
|
|
19
|
+
end
|
|
20
|
+
gem_original_require 'active_support/core_ext/class.rb'
|
|
21
|
+
gem_original_require 'active_support/core_ext/module.rb'
|
|
22
|
+
gem_original_require 'active_support/core_ext/symbol.rb'
|
|
23
|
+
gem_original_require 'active_support/core_ext/string.rb'
|
|
24
|
+
gem_original_require 'active_support/core_ext/blank.rb'
|
|
25
|
+
gem_original_require 'active_support/core_ext/hash/keys.rb'
|
|
26
|
+
gem_original_require 'active_support/dependencies'
|
|
27
|
+
class Hash #:nodoc:
|
|
28
|
+
include ActiveSupport::CoreExtensions::Hash::Keys
|
|
29
|
+
end
|
|
2
30
|
|
|
3
31
|
$:.unshift File.dirname(__FILE__)
|
|
4
32
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
gem_original_require 'tap/support/audit'
|
|
34
|
+
gem_original_require 'tap/support/task_configuration'
|
|
35
|
+
gem_original_require 'tap/support/logger'
|
|
36
|
+
gem_original_require 'tap/support/templater'
|
|
37
|
+
gem_original_require 'tap/support/batch_queue'
|
|
38
|
+
gem_original_require 'tap/support/run_error'
|
|
39
|
+
gem_original_require 'tap/version'
|
|
40
|
+
gem_original_require 'tap/root'
|
|
41
|
+
gem_original_require 'tap/app'
|
|
42
|
+
gem_original_require 'tap/task'
|
|
43
|
+
gem_original_require 'tap/workflow'
|
|
44
|
+
gem_original_require 'tap/file_task'
|
data/lib/tap/app.rb
CHANGED
|
@@ -1,33 +1,23 @@
|
|
|
1
|
-
require 'yaml'
|
|
2
|
-
require 'logger'
|
|
3
|
-
require 'ostruct'
|
|
4
|
-
require 'getoptlong'
|
|
5
|
-
require 'monitor'
|
|
6
|
-
require 'thread'
|
|
7
|
-
require 'thwait'
|
|
8
|
-
require 'erb'
|
|
9
|
-
|
|
10
1
|
module Tap
|
|
11
2
|
|
|
12
3
|
# == Overview
|
|
13
4
|
#
|
|
14
5
|
# App coordinates the setup and running of tasks, and provides an interface
|
|
15
6
|
# to the application directory structure. App is convenient for use within
|
|
16
|
-
# scripts, and
|
|
17
|
-
# utilites or to be integrated within a tap server.
|
|
7
|
+
# scripts, and provides the basis for the 'tap' command line application.
|
|
18
8
|
#
|
|
19
|
-
# All tasks have an App (by default App.instance) which helps
|
|
20
|
-
#
|
|
21
|
-
#
|
|
9
|
+
# All tasks have an App (by default App.instance) which helps initialize the
|
|
10
|
+
# task by loading configuration templates from the application directory.
|
|
11
|
+
# Task queue commands are passed to app, and tasks access application-
|
|
22
12
|
# wide resources like the logger and various options through App.
|
|
23
13
|
#
|
|
24
14
|
# task = Task.new {|task, input| input += 1 }
|
|
25
15
|
# task.app # => App.instance
|
|
26
|
-
# task.
|
|
16
|
+
# task.enq 1,2,3
|
|
27
17
|
#
|
|
28
|
-
# app.run
|
|
18
|
+
# task.app.run
|
|
29
19
|
#
|
|
30
|
-
# task.
|
|
20
|
+
# task.results.collect do |audit|
|
|
31
21
|
# audit._current
|
|
32
22
|
# end # => [2,3,4]
|
|
33
23
|
#
|
|
@@ -38,29 +28,32 @@ module Tap
|
|
|
38
28
|
# Multithreaded tasks execute cosynchronously, each on their own thread.
|
|
39
29
|
#
|
|
40
30
|
# Workflows can be achieved by setting the condition and on_complete blocks
|
|
41
|
-
# for a task. Tasks are skipped
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
# tasks, and these tasks queued for execution.
|
|
31
|
+
# for a task. Tasks are skipped until their condition block is met by the
|
|
32
|
+
# queued inputs. When a task finishes, it executes its on_complete block,
|
|
33
|
+
# thereby allowing results to be dispatched to other tasks.
|
|
45
34
|
#
|
|
46
35
|
# In this system, the workflow logic exists among the tasks. App keeps on
|
|
47
|
-
# running as long as it finds executable tasks in the queue
|
|
36
|
+
# running as long as it finds executable tasks in the queue, or until it
|
|
37
|
+
# is stopped or terminated.
|
|
48
38
|
#
|
|
49
39
|
# === Error Handling
|
|
50
40
|
#
|
|
51
41
|
# When unhandled errors arise during a run, App enters a termination (rescue)
|
|
52
|
-
# routine. During termination a TerminationError is raised
|
|
53
|
-
# task so that the task
|
|
54
|
-
#
|
|
42
|
+
# routine. During termination a TerminationError is raised in each executing
|
|
43
|
+
# task so that the task exits, or begins executing its internal error handling
|
|
44
|
+
# code (perhaps performing rollbacks).
|
|
55
45
|
#
|
|
56
46
|
# Additional errors that arise during termination are collected and packaged
|
|
57
47
|
# with the orignal error into a RunError. By default all errors are logged
|
|
58
|
-
# and the run exits. If options.debug == true, then the RunError will be
|
|
59
|
-
# for further handling.
|
|
48
|
+
# and the run exits. If options.debug == true, then the RunError will be
|
|
49
|
+
# raised for further handling.
|
|
60
50
|
#
|
|
61
51
|
# Note: the task that caused the original unhandled error is no longer executing
|
|
62
52
|
# when termination begins and thus will not recieve a TerminationError.
|
|
63
53
|
#
|
|
54
|
+
#--
|
|
55
|
+
# === Auditing
|
|
56
|
+
#++
|
|
64
57
|
class App < Root
|
|
65
58
|
include MonitorMixin
|
|
66
59
|
|
|
@@ -68,8 +61,7 @@ module Tap
|
|
|
68
61
|
attr_writer :instance
|
|
69
62
|
|
|
70
63
|
# Returns the current instance of App. If no instance has been set,
|
|
71
|
-
# then a new App with the default configuration will be initialized
|
|
72
|
-
# to instance.
|
|
64
|
+
# then a new App with the default configuration will be initialized.
|
|
73
65
|
def instance
|
|
74
66
|
@instance ||= App.new
|
|
75
67
|
end
|
|
@@ -79,29 +71,31 @@ module Tap
|
|
|
79
71
|
instance.run(task, *inputs)
|
|
80
72
|
end
|
|
81
73
|
|
|
82
|
-
# Parses the input
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
|
|
74
|
+
# Parses the input string as YAML, if the string matches the YAML document
|
|
75
|
+
# specifier (ie it begins with "---\s*\n"). Otherwise returns the string.
|
|
76
|
+
#
|
|
77
|
+
# str = {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
|
|
78
|
+
# Tap::App.parse_yaml(str) # => {'key' => 'value'}
|
|
79
|
+
# Tap::App.parse_yaml("str") # => "str"
|
|
80
|
+
def parse_yaml(str)
|
|
81
|
+
str =~ /^---\s*\n/ ? YAML.load(str) : str
|
|
87
82
|
end
|
|
88
83
|
|
|
84
|
+
# Read the contents of filepath, templates these using ERB, and loads
|
|
85
|
+
# the result as YAML. Returns nil if filepath does not exist.
|
|
89
86
|
def read_erb_yaml(filepath)
|
|
90
|
-
return nil
|
|
87
|
+
return nil if !File.exists?(filepath) || File.directory?(filepath)
|
|
91
88
|
|
|
92
89
|
input = File.read(filepath)
|
|
93
90
|
input = ERB.new(input).result
|
|
94
91
|
YAML.load(input)
|
|
95
92
|
end
|
|
96
|
-
|
|
97
|
-
def make_config(task, config, version=nil)
|
|
98
|
-
|
|
99
|
-
end
|
|
100
93
|
end
|
|
101
94
|
|
|
102
95
|
attr_reader :options, :logger, :queue, :state
|
|
103
96
|
attr_accessor :map
|
|
104
97
|
|
|
98
|
+
# The constants defining the possible App states.
|
|
105
99
|
module State
|
|
106
100
|
READY = 0
|
|
107
101
|
RUN = 1
|
|
@@ -111,26 +105,26 @@ module Tap
|
|
|
111
105
|
|
|
112
106
|
DEFAULT_MAX_THREADS = 10
|
|
113
107
|
|
|
114
|
-
# Creates a new App with the given configuration.
|
|
115
|
-
# configuration options.
|
|
108
|
+
# Creates a new App with the given configuration.
|
|
109
|
+
# See reconfigure for configuration options.
|
|
116
110
|
def initialize(config={})
|
|
117
111
|
super()
|
|
118
112
|
|
|
119
113
|
@queue = Support::BatchQueue.new
|
|
120
|
-
@threads = []
|
|
114
|
+
@threads = [].extend(MonitorMixin)
|
|
121
115
|
@thread_queue = nil
|
|
122
116
|
@main_thread = nil
|
|
123
117
|
@state = State::READY
|
|
124
|
-
|
|
118
|
+
|
|
125
119
|
# defaults must be provided for options and logging to ensure
|
|
126
120
|
# that they will be initialized by reconfigure
|
|
127
121
|
self.reconfigure( {
|
|
128
|
-
:options => {}, :logger => {}, :map => {}
|
|
122
|
+
:options => {}, :logger => {}, :map => {}
|
|
129
123
|
}.merge(config) )
|
|
130
124
|
end
|
|
131
125
|
|
|
132
|
-
# Returns the
|
|
133
|
-
def config
|
|
126
|
+
# Returns the configuration of self.
|
|
127
|
+
def config
|
|
134
128
|
{:root => self.root,
|
|
135
129
|
:directories => self.directories,
|
|
136
130
|
:absolute_paths => self.absolute_paths,
|
|
@@ -138,18 +132,26 @@ module Tap
|
|
|
138
132
|
:logger => {
|
|
139
133
|
:device => self.logger.logdev.dev,
|
|
140
134
|
:level => self.logger.level,
|
|
141
|
-
:datetime_format => self.logger.datetime_format}
|
|
142
|
-
:load_paths => self.load_paths}
|
|
135
|
+
:datetime_format => self.logger.datetime_format}}
|
|
143
136
|
end
|
|
144
137
|
|
|
145
|
-
# Reconfigures
|
|
138
|
+
# Reconfigures self with the input configurations; other configurations are not affected.
|
|
139
|
+
#
|
|
140
|
+
# app = Tap::App.new :root => "/root", :directories => {:dir => 'path/to/dir'}
|
|
141
|
+
# app.reconfigure(
|
|
142
|
+
# :root => "./new/root",
|
|
143
|
+
# :logger => {:level => Logger::DEBUG})
|
|
144
|
+
#
|
|
145
|
+
# app.root # => File.expand_path("./new/root")
|
|
146
|
+
# app[:dir] # => File.expand_path("./new/root/path/to/dir")
|
|
147
|
+
# app.logger.level # => Logger::DEBUG
|
|
146
148
|
#
|
|
147
149
|
# Available configurations:
|
|
148
|
-
# root:: resets the root directory of
|
|
149
|
-
# directories:: resets
|
|
150
|
-
# are reset. use app[
|
|
151
|
-
# options:: resets the application options (note ALL options are reset.
|
|
152
|
-
# app.options.opt= to set a single option)
|
|
150
|
+
# root:: resets the root directory of self using root=
|
|
151
|
+
# directories:: resets directory aliases using directories= (note ALL
|
|
152
|
+
# aliases are reset. use app[da]= to set a single alias)
|
|
153
|
+
# options:: resets the application options (note ALL options are reset.
|
|
154
|
+
# use app.options.opt= to set a single option)
|
|
153
155
|
# logger:: creates and sets a new logger from the configuration
|
|
154
156
|
#
|
|
155
157
|
# Available logger configurations and defaults:
|
|
@@ -162,9 +164,9 @@ module Tap
|
|
|
162
164
|
# - Additional configurations may be added in subclasses using handle_configuration
|
|
163
165
|
def reconfigure(config={})
|
|
164
166
|
config = config.symbolize_keys
|
|
165
|
-
|
|
167
|
+
|
|
166
168
|
# ensure critical keys are evaluated in the proper order
|
|
167
|
-
keys = [:root, :directories, :absolute_paths, :options
|
|
169
|
+
keys = [:root, :directories, :absolute_paths, :options]
|
|
168
170
|
config.keys.each do |key|
|
|
169
171
|
keys << key unless keys.include?(key)
|
|
170
172
|
end
|
|
@@ -196,33 +198,18 @@ module Tap
|
|
|
196
198
|
self.logger = logger
|
|
197
199
|
when :map
|
|
198
200
|
self.map = value
|
|
199
|
-
when :load_paths
|
|
200
|
-
self.load_paths = value
|
|
201
|
-
when :loadable
|
|
202
|
-
value.each {|dir| self.load_paths << self[dir]}
|
|
203
201
|
else
|
|
204
|
-
|
|
202
|
+
unless handle_configuation(key, value)
|
|
203
|
+
if block_given?
|
|
204
|
+
yield(key, value)
|
|
205
|
+
else
|
|
206
|
+
raise ArgumentError.new("Unknown configuration: #{key}")
|
|
207
|
+
end
|
|
208
|
+
end
|
|
205
209
|
end
|
|
206
210
|
end
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
#
|
|
210
|
-
# Dependencies and reloading
|
|
211
|
-
#
|
|
212
|
-
|
|
213
|
-
def load_paths
|
|
214
|
-
Dependencies.load_paths
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def load_paths=(paths)
|
|
218
|
-
Dependencies.load_paths = paths
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
def reload
|
|
222
|
-
# TODO - logging, maybe through Dependencies as
|
|
223
|
-
# RAILS_DEFAULT_LOGGER = logger (problem with this during server?)
|
|
224
|
-
# Dependencies.log_activity = true
|
|
225
|
-
Dependencies.clear
|
|
211
|
+
|
|
212
|
+
self
|
|
226
213
|
end
|
|
227
214
|
|
|
228
215
|
#
|
|
@@ -243,81 +230,95 @@ module Tap
|
|
|
243
230
|
logger.add(level, msg, action.to_s) unless options.quiet
|
|
244
231
|
end
|
|
245
232
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
unless options.quiet || !@monitoring
|
|
260
|
-
action.nil? ?
|
|
261
|
-
logger.tick :
|
|
262
|
-
logger.format_add(level, msg, action.to_s) {|format| "\n" + format}
|
|
233
|
+
# Formatted log. Works like log, but passes the current log format to the
|
|
234
|
+
# block and uses whatever format the block returns. The format recieves
|
|
235
|
+
# the following arguments like so:
|
|
236
|
+
#
|
|
237
|
+
# format % [severity, timestamp, (action || '--'), msg]
|
|
238
|
+
#
|
|
239
|
+
# By default, if you don't specify a block, flog just chomps a newline off
|
|
240
|
+
# the format, so your log will be inline.
|
|
241
|
+
def flog(action="", msg="", level=Logger::INFO) # :yields: format
|
|
242
|
+
unless options.quiet
|
|
243
|
+
logger.format_add(level, msg, action) do |format|
|
|
244
|
+
block_given? ? yield(format) : format.chomp("\n")
|
|
245
|
+
end
|
|
263
246
|
end
|
|
264
247
|
end
|
|
265
248
|
|
|
266
|
-
def
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
249
|
+
# def monitor(action="beginning", msg="", &block)
|
|
250
|
+
# if options.quiet
|
|
251
|
+
# yield
|
|
252
|
+
# nil
|
|
253
|
+
# else
|
|
254
|
+
# @monitoring = true
|
|
255
|
+
# result = logger.monitor(action, msg, &block)
|
|
256
|
+
# @monitoring = false
|
|
257
|
+
# result
|
|
258
|
+
# end
|
|
259
|
+
# end
|
|
260
|
+
#
|
|
261
|
+
# def tick_monitor(action=nil, msg="", level=Logger::INFO)
|
|
262
|
+
# unless options.quiet || !@monitoring
|
|
263
|
+
# action.nil? ?
|
|
264
|
+
# logger.tick :
|
|
265
|
+
# logger.format_add(level, msg, action.to_s) {|format| "\n" + format}
|
|
266
|
+
# end
|
|
267
|
+
# end
|
|
268
|
+
#
|
|
269
|
+
# def monitor_stats(hash)
|
|
270
|
+
# unless options.quiet || !@monitoring
|
|
271
|
+
# logger << "\n"
|
|
272
|
+
# logger << hash.stringify_keys.to_yaml
|
|
273
|
+
# end
|
|
274
|
+
# end
|
|
272
275
|
|
|
273
276
|
#
|
|
274
277
|
# Task methods
|
|
275
278
|
#
|
|
276
279
|
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# t = Task.new :key => 'value'
|
|
280
|
-
# t = app.task(t, :key => 'another')
|
|
281
|
-
# t.config[:key] # => 'another'
|
|
282
|
-
#
|
|
283
|
-
# If a task name is given, then the appropriate class will be loaded and
|
|
284
|
-
# instantiated into a task with given name. The class is the de-versioned
|
|
285
|
-
# and camelized name, or the class as specified in map.
|
|
280
|
+
# Instantiates the specifed task with config (if provided).
|
|
286
281
|
#
|
|
287
|
-
# app.map
|
|
282
|
+
# app.map = {"mapped-task" => "AnotherTask"}
|
|
288
283
|
#
|
|
289
284
|
# t = app.task('mapped-task-1.0', :key => 'value')
|
|
290
|
-
# t.class # =>
|
|
285
|
+
# t.class # => AnotherTask
|
|
291
286
|
# t.name # => "mapped-task-1.0"
|
|
292
287
|
# t.config[:key] # => 'value'
|
|
293
288
|
#
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
289
|
+
# A new task is instantiated for each call to task; tasks may share the
|
|
290
|
+
# same name. The task class is the de-versioned and camelized task name,
|
|
291
|
+
# or the class specified in map. The task class will be auto-loaded using
|
|
292
|
+
# Dependencies, if needed.
|
|
293
|
+
#
|
|
294
|
+
# A LookupError is raised if the task class cannot be found.
|
|
295
|
+
def task(task_name, config=nil)
|
|
296
|
+
begin
|
|
298
297
|
# lookup the corresponding class and instantiate
|
|
299
|
-
constants = task_class_name(
|
|
298
|
+
constants = task_class_name(task_name).split('::')
|
|
300
299
|
task_class = constants.inject(Object) do |klass, const|
|
|
301
|
-
|
|
302
|
-
klass.const_get(const)
|
|
303
|
-
rescue(NameError)
|
|
304
|
-
raise "unknown task '#{td}'"
|
|
305
|
-
end
|
|
300
|
+
klass.const_get(const)
|
|
306
301
|
end
|
|
307
|
-
task_class.new(
|
|
302
|
+
task_class.new(task_name, config)
|
|
303
|
+
rescue(NameError)
|
|
304
|
+
raise LookupError.new("unknown task '#{task_name}'")
|
|
308
305
|
end
|
|
309
|
-
|
|
310
|
-
# reconfigure if a config is provided
|
|
311
|
-
t.config = config unless config.nil?
|
|
312
|
-
t
|
|
313
306
|
end
|
|
314
307
|
|
|
308
|
+
# Returns the class name of the specified task. If a task descriptor
|
|
309
|
+
# is given, task_class_name returns the de-versioned, camelized
|
|
310
|
+
# descriptor, or the class name as specified in map.
|
|
311
|
+
#
|
|
312
|
+
# t = Task.new
|
|
313
|
+
# app.map = {"mapped-task" => "AnotherTask"}
|
|
314
|
+
# app.task_class_name(t) # => "Tap::Task"
|
|
315
|
+
# app.task_class_name('mapped-task-1.0') # => "AnotherTask"
|
|
315
316
|
def task_class_name(td)
|
|
316
317
|
case td
|
|
317
|
-
when Tap::Task then td.class.to_s
|
|
318
|
+
when Tap::Task::Base then td.class.to_s
|
|
318
319
|
else
|
|
319
320
|
# de-version and resolve using map
|
|
320
|
-
name, version = deversion(td)
|
|
321
|
+
name, version = deversion(td.to_s)
|
|
321
322
|
map.has_key?(name) ? map[name] : name.camelize
|
|
322
323
|
end
|
|
323
324
|
end
|
|
@@ -352,55 +353,55 @@ module Tap
|
|
|
352
353
|
#
|
|
353
354
|
# Execution methods
|
|
354
355
|
#
|
|
355
|
-
|
|
356
|
+
|
|
356
357
|
# Dequeues the task and inputs and executes the task with the current task inputs.
|
|
357
|
-
#
|
|
358
|
-
#
|
|
358
|
+
#
|
|
359
|
+
# Execute differs from run in several significant ways:
|
|
360
|
+
# - Only the provided task will be executed (ie the task batch will not be executed)
|
|
361
|
+
# - Execution is on the current thread even if the task is multithreaded.
|
|
362
|
+
# - No error handling is performed
|
|
359
363
|
#
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
#log(:execute, task.to_dir, Logger::DEBUG) if options.debug
|
|
365
|
-
|
|
366
|
-
unless state == State::READY || state == State::RUN
|
|
367
|
-
raise "cannot execute unless application state is READY or RUN"
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
if deq = queue.deq(task)
|
|
371
|
-
task, inputs = deq
|
|
372
|
-
task.execute(*inputs)
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
end
|
|
376
|
-
|
|
377
|
-
# Enqueues the task and inputs (if provided), then iterates through the queue
|
|
378
|
-
# executing tasks using this execution cycle:
|
|
379
|
-
#
|
|
380
|
-
# for each enqueued task:
|
|
381
|
-
# next unless executable?(task)
|
|
364
|
+
# Execute can only run if the application state is READY or RUN.
|
|
365
|
+
# def execute(task)
|
|
366
|
+
# synchronize do
|
|
367
|
+
# #log(:execute, task.to_dir, Logger::DEBUG) if options.debug
|
|
382
368
|
#
|
|
383
|
-
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
386
|
-
#
|
|
387
|
-
#
|
|
388
|
-
#
|
|
389
|
-
#
|
|
390
|
-
#
|
|
391
|
-
#
|
|
392
|
-
#
|
|
369
|
+
# unless state == State::READY || state == State::RUN
|
|
370
|
+
# raise "cannot execute unless application state is READY or RUN"
|
|
371
|
+
# end
|
|
372
|
+
#
|
|
373
|
+
# if deq = queue.deq(task)
|
|
374
|
+
# task, inputs = deq
|
|
375
|
+
# task.execute(*inputs)
|
|
376
|
+
# end
|
|
377
|
+
# end
|
|
378
|
+
# end
|
|
379
|
+
|
|
380
|
+
# Enqueues the task and inputs (if provided), then begins the run cycle.
|
|
381
|
+
#
|
|
382
|
+
# During the run cycle, the app will iterate through the queue and execute
|
|
383
|
+
# tasks. Tasks will be executed ONLY if task.executable? returns true with
|
|
384
|
+
# the currently enqueued inputs. Tasks are executed on their own thread if
|
|
385
|
+
# task.multithread? is true.
|
|
386
|
+
#
|
|
387
|
+
# As a result of these rules, the queue may still contain unexecuted tasks
|
|
388
|
+
# a the end of run. These tasks require additional inputs or conditions
|
|
389
|
+
# in order to execute. Execution errors are handled as described above.
|
|
390
|
+
#
|
|
391
|
+
# Tasks may be specified as a task descriptor (ex: "sample/task") or provided
|
|
392
|
+
# directly. Task descriptors are resolved using task.
|
|
393
|
+
#
|
|
394
|
+
# run returns the specified task on completion.
|
|
393
395
|
def run(td=nil, *inputs)
|
|
394
396
|
synchronize do
|
|
395
397
|
# TODO: log starting run
|
|
396
398
|
|
|
397
399
|
# lookup and enqueue the task, if provided
|
|
398
|
-
specified_task =
|
|
400
|
+
specified_task = (td == nil || td.kind_of?(Tap::Task::Base) ? td : task(td))
|
|
399
401
|
queue.enq(specified_task, *inputs) if specified_task
|
|
400
402
|
|
|
401
403
|
# generate threading variables
|
|
402
404
|
self.state = State::RUN
|
|
403
|
-
self.main_thread = Thread.current
|
|
404
405
|
max_threads = options.max_threads || DEFAULT_MAX_THREADS
|
|
405
406
|
self.thread_queue = max_threads > 0 ? Queue.new : nil
|
|
406
407
|
|
|
@@ -461,8 +462,8 @@ module Tap
|
|
|
461
462
|
# executed. collect any errors that arise during
|
|
462
463
|
# termination.
|
|
463
464
|
clear_thread_queue
|
|
464
|
-
errors =
|
|
465
|
-
|
|
465
|
+
errors = clear_threads(false)
|
|
466
|
+
|
|
466
467
|
# handle the errors accordingly
|
|
467
468
|
if options.debug
|
|
468
469
|
raise Tap::Support::RunError.new($!, errors)
|
|
@@ -477,7 +478,6 @@ module Tap
|
|
|
477
478
|
|
|
478
479
|
# reset run variables
|
|
479
480
|
self.thread_queue = nil
|
|
480
|
-
self.main_thread = nil
|
|
481
481
|
self.state = State::READY
|
|
482
482
|
|
|
483
483
|
# TODO: log run complete
|
|
@@ -495,26 +495,20 @@ module Tap
|
|
|
495
495
|
self.state = State::STOP if state == State::RUN
|
|
496
496
|
end
|
|
497
497
|
|
|
498
|
-
#
|
|
499
|
-
#
|
|
500
|
-
#
|
|
501
|
-
#
|
|
502
|
-
#
|
|
503
|
-
#
|
|
504
|
-
#
|
|
505
|
-
#
|
|
506
|
-
#
|
|
507
|
-
# rollbacks. (ie terminate should be used as a graceful
|
|
508
|
-
# exit)
|
|
498
|
+
# Signals a running application to terminate executing tasks
|
|
499
|
+
# by setting state = State::TERMINATE. When running tasks
|
|
500
|
+
# reach a termination check, the task will raise a Termination
|
|
501
|
+
# error, thus allowing executing tasks to invoke their specific
|
|
502
|
+
# error handling code, perhaps performing rollbacks.
|
|
503
|
+
#
|
|
504
|
+
# Termination checks can be manually specified in a task
|
|
505
|
+
# using the check_terminate method. Termination checks
|
|
506
|
+
# automatically occur before each task execution.
|
|
509
507
|
#
|
|
510
508
|
# Does nothing unless state is State::RUN or State::STOP.
|
|
511
|
-
def terminate(error=TerminateError.new)
|
|
509
|
+
def terminate#(error=TerminateError.new)
|
|
512
510
|
if state == State::RUN || state == State::STOP
|
|
513
511
|
self.state = State::TERMINATE
|
|
514
|
-
|
|
515
|
-
# the terminate error must be raised on the main thread
|
|
516
|
-
# so that the App will enter the termination (rescue) block
|
|
517
|
-
main_thread.raise(error)
|
|
518
512
|
end
|
|
519
513
|
end
|
|
520
514
|
|
|
@@ -542,7 +536,7 @@ module Tap
|
|
|
542
536
|
|
|
543
537
|
# Sets the condition block for the task. If the task is batched,
|
|
544
538
|
# then the condition will be set for each task in the batch.
|
|
545
|
-
def condition(task, &block) # :yields: task,
|
|
539
|
+
def condition(task, &block) # :yields: task, audited_inputs
|
|
546
540
|
task.batch.each {|t| t.condition(&block)}
|
|
547
541
|
end
|
|
548
542
|
|
|
@@ -565,8 +559,7 @@ module Tap
|
|
|
565
559
|
# The condition block between these tasks will be set to the
|
|
566
560
|
# input block, if provided. Batched tasks will have the pattern
|
|
567
561
|
# set for each task in the batch.
|
|
568
|
-
def sequence(*tasks, &block) # :yields: task,
|
|
569
|
-
tasks.collect! {|td| task(td)}
|
|
562
|
+
def sequence(*tasks, &block) # :yields: task, audited_inputs
|
|
570
563
|
current_task = tasks.shift
|
|
571
564
|
|
|
572
565
|
tasks.each do |next_task|
|
|
@@ -582,7 +575,7 @@ module Tap
|
|
|
582
575
|
# source completes. The condition block for the targets will
|
|
583
576
|
# be set to the input block, if provided. Batched tasks will have
|
|
584
577
|
# the pattern set for each task in the batch.
|
|
585
|
-
def fork(source, *targets, &block) # :yields: task,
|
|
578
|
+
def fork(source, *targets, &block) # :yields: task, audited_inputs
|
|
586
579
|
on_complete(source) do |results|
|
|
587
580
|
targets.each do |target|
|
|
588
581
|
# forking requires new audit trails because the same
|
|
@@ -601,7 +594,7 @@ module Tap
|
|
|
601
594
|
# completes. The condition block for the target will be set to the
|
|
602
595
|
# input block, if provided. Batched tasks will have the pattern set
|
|
603
596
|
# for each task in the batch.
|
|
604
|
-
def merge(target, *sources, &block) # :yields: task,
|
|
597
|
+
def merge(target, *sources, &block) # :yields: task, audited_inputs
|
|
605
598
|
sources.each do |source|
|
|
606
599
|
# merging can use the existing audit trails... each distinct
|
|
607
600
|
# input is getting sent to one place (the target)
|
|
@@ -620,7 +613,7 @@ module Tap
|
|
|
620
613
|
end
|
|
621
614
|
|
|
622
615
|
attr_writer :state
|
|
623
|
-
attr_accessor :thread_queue, :
|
|
616
|
+
attr_accessor :thread_queue, :threads
|
|
624
617
|
|
|
625
618
|
private
|
|
626
619
|
|
|
@@ -635,6 +628,11 @@ module Tap
|
|
|
635
628
|
# terminated but still running. Raise another
|
|
636
629
|
# termination error to enter the termination
|
|
637
630
|
# (rescue) code.
|
|
631
|
+
|
|
632
|
+
# BUG - if things just work out that way, terminate can
|
|
633
|
+
# be set from a thread that had an error, then control
|
|
634
|
+
# switches such that this is raised on the main thread.
|
|
635
|
+
# in this case it looks like THIS is the original error.
|
|
638
636
|
raise TerminateError.new
|
|
639
637
|
end
|
|
640
638
|
|
|
@@ -662,75 +660,74 @@ module Tap
|
|
|
662
660
|
queue.priority_enq(task, *inputs) unless task.nil?
|
|
663
661
|
end
|
|
664
662
|
end
|
|
665
|
-
|
|
666
|
-
def clear_threads
|
|
667
|
-
|
|
663
|
+
|
|
664
|
+
def clear_threads(raise_errors=true)
|
|
665
|
+
threads.synchronize do
|
|
666
|
+
errors = []
|
|
667
|
+
return errors if threads.empty?
|
|
668
668
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
669
|
+
# clears threads gracefully by enqueuing nils, to break
|
|
670
|
+
# the threads out of their loops, then waiting for the
|
|
671
|
+
# threads to work through the queue to the nils
|
|
672
|
+
#
|
|
673
|
+
threads.size.times { thread_queue.enq nil }
|
|
674
|
+
while true
|
|
675
|
+
# TODO -- add a time out?
|
|
676
|
+
|
|
677
|
+
threads.dup.each do |thread|
|
|
678
|
+
next if thread.alive?
|
|
679
|
+
threads.delete(thread)
|
|
680
|
+
error = thread["error"]
|
|
681
|
+
|
|
682
|
+
next if error.nil?
|
|
683
|
+
raise error if raise_errors
|
|
684
|
+
|
|
685
|
+
errors << error
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
break if threads.empty?
|
|
689
|
+
Thread.pass
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
errors
|
|
675
693
|
end
|
|
676
|
-
threads.clear
|
|
677
694
|
end
|
|
678
695
|
|
|
679
696
|
def start_thread
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
#
|
|
697
|
+
threads.synchronize do
|
|
698
|
+
# start a new thread and add it to threads.
|
|
699
|
+
# threads simply loop and wait for a task to
|
|
700
|
+
# be queued. the thread will block until a
|
|
701
|
+
# task is available (due to thread_queue.deq)
|
|
702
|
+
#
|
|
703
|
+
# TODO -- track thread index like?
|
|
704
|
+
# thread["index"] = threads.length
|
|
705
|
+
threads << Thread.new do
|
|
706
|
+
# TODO - log thread start
|
|
686
707
|
|
|
687
|
-
execution_loop do
|
|
688
|
-
task, inputs = thread_queue.deq
|
|
689
|
-
break if task.nil?
|
|
690
|
-
|
|
691
|
-
# TODO: log execute task on thread #
|
|
692
708
|
begin
|
|
693
|
-
|
|
709
|
+
execution_loop do
|
|
710
|
+
task, inputs = thread_queue.deq
|
|
711
|
+
break if task.nil?
|
|
712
|
+
|
|
713
|
+
# TODO: log execute task on thread #
|
|
714
|
+
task.execute(*inputs)
|
|
715
|
+
end
|
|
694
716
|
rescue
|
|
695
717
|
# unless you're already terminating,
|
|
696
718
|
# an unhandled error should immediately
|
|
697
719
|
# terminate all threads
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
else
|
|
701
|
-
terminate($!)
|
|
702
|
-
end
|
|
720
|
+
terminate unless state == State::TERMINATE
|
|
721
|
+
Thread.current["error"] = $! unless $!.kind_of?(TerminateError)
|
|
703
722
|
end
|
|
704
|
-
end
|
|
705
|
-
end
|
|
706
|
-
end
|
|
707
|
-
|
|
708
|
-
def terminate_threads
|
|
709
|
-
# terminate each thread by raising an exception
|
|
710
|
-
# wait for the thread to exit and gather any
|
|
711
|
-
# errors that arise that are not from the
|
|
712
|
-
# termination exception.
|
|
713
|
-
errors = []
|
|
714
|
-
threads.each_with_index do |thread, index|
|
|
715
|
-
next unless thread.alive?
|
|
716
|
-
|
|
717
|
-
begin
|
|
718
|
-
thread.raise TerminateError.new
|
|
719
|
-
thread.join
|
|
720
|
-
rescue TerminateError
|
|
721
|
-
# do not track these as errors
|
|
722
|
-
# arising from termination
|
|
723
|
-
rescue
|
|
724
|
-
# track the thread of the error
|
|
725
|
-
# with the error itself
|
|
726
|
-
errors[index] = $!
|
|
727
723
|
end
|
|
728
724
|
end
|
|
729
|
-
threads.clear
|
|
730
|
-
|
|
731
|
-
errors
|
|
732
725
|
end
|
|
733
726
|
|
|
727
|
+
# LookupErrors are raised for errors during dependency lookup
|
|
728
|
+
class LookupError < RuntimeError # :nodoc:
|
|
729
|
+
end
|
|
730
|
+
|
|
734
731
|
# TerminateErrors are raised to kill executing tasks when terminate
|
|
735
732
|
# is called on an running App.
|
|
736
733
|
class TerminateError < RuntimeError
|