tap 0.9.1 → 0.10.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.
Files changed (244) hide show
  1. data/History +37 -30
  2. data/MIT-LICENSE +1 -1
  3. data/README +92 -44
  4. data/bin/tap +62 -75
  5. data/cmd/console.rb +42 -0
  6. data/cmd/destroy.rb +16 -0
  7. data/cmd/generate.rb +16 -0
  8. data/cmd/run.rb +126 -0
  9. data/doc/Class Reference +362 -0
  10. data/doc/Command Reference +153 -0
  11. data/doc/Tutorial +237 -0
  12. data/lib/tap.rb +6 -45
  13. data/lib/tap/app.rb +126 -500
  14. data/lib/tap/constants.rb +2 -29
  15. data/lib/tap/env.rb +555 -250
  16. data/lib/tap/file_task.rb +60 -103
  17. data/lib/tap/generator/base.rb +109 -0
  18. data/lib/tap/generator/destroy.rb +37 -0
  19. data/lib/tap/generator/generate.rb +61 -0
  20. data/lib/tap/generator/generators/command/command_generator.rb +16 -12
  21. data/lib/tap/generator/generators/command/templates/command.erb +13 -19
  22. data/lib/tap/generator/generators/config/config_generator.rb +18 -27
  23. data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
  24. data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
  25. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -11
  26. data/lib/tap/generator/generators/file_task/templates/file.txt +11 -2
  27. data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
  28. data/lib/tap/generator/generators/file_task/templates/task.erb +24 -31
  29. data/lib/tap/generator/generators/file_task/templates/test.erb +18 -22
  30. data/lib/tap/generator/generators/root/root_generator.rb +45 -31
  31. data/lib/tap/generator/generators/root/templates/Rakefile +64 -41
  32. data/lib/tap/generator/generators/root/templates/gemspec +27 -0
  33. data/lib/tap/generator/generators/root/templates/tapfile +8 -0
  34. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -0
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  36. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +21 -28
  38. data/lib/tap/generator/generators/task/templates/task.erb +13 -23
  39. data/lib/tap/generator/generators/task/templates/test.erb +15 -18
  40. data/lib/tap/generator/manifest.rb +14 -0
  41. data/lib/tap/patches/rake/rake_test_loader.rb +0 -0
  42. data/lib/tap/patches/rake/testtask.rb +0 -0
  43. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -0
  44. data/lib/tap/patches/ruby19/parsedate.rb +0 -0
  45. data/lib/tap/root.rb +260 -21
  46. data/lib/tap/support/aggregator.rb +11 -11
  47. data/lib/tap/support/assignments.rb +172 -0
  48. data/lib/tap/support/audit.rb +20 -18
  49. data/lib/tap/support/batchable.rb +21 -10
  50. data/lib/tap/support/batchable_class.rb +107 -0
  51. data/lib/tap/support/class_configuration.rb +154 -239
  52. data/lib/tap/support/command_line.rb +97 -102
  53. data/lib/tap/support/comment.rb +270 -0
  54. data/lib/tap/support/configurable.rb +86 -65
  55. data/lib/tap/support/configurable_class.rb +296 -0
  56. data/lib/tap/support/configuration.rb +122 -0
  57. data/lib/tap/support/constant.rb +70 -0
  58. data/lib/tap/support/constant_utils.rb +127 -0
  59. data/lib/tap/support/declarations.rb +111 -0
  60. data/lib/tap/support/executable.rb +30 -17
  61. data/lib/tap/support/executable_queue.rb +0 -0
  62. data/lib/tap/support/framework.rb +71 -0
  63. data/lib/tap/support/framework_class.rb +199 -0
  64. data/lib/tap/support/instance_configuration.rb +147 -0
  65. data/lib/tap/support/lazydoc.rb +428 -0
  66. data/lib/tap/support/manifest.rb +89 -0
  67. data/lib/tap/support/run_error.rb +0 -0
  68. data/lib/tap/support/shell_utils.rb +33 -9
  69. data/lib/tap/support/summary.rb +30 -0
  70. data/lib/tap/support/tdoc.rb +339 -134
  71. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -0
  72. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -0
  73. data/lib/tap/support/templater.rb +180 -0
  74. data/lib/tap/support/validation.rb +409 -76
  75. data/lib/tap/support/versions.rb +5 -3
  76. data/lib/tap/task.rb +78 -174
  77. data/lib/tap/tasks/dump.rb +56 -0
  78. data/lib/tap/tasks/rake.rb +93 -0
  79. data/lib/tap/test.rb +3 -3
  80. data/lib/tap/test/env_vars.rb +2 -2
  81. data/lib/tap/test/file_methods.rb +19 -20
  82. data/lib/tap/test/script_methods.rb +144 -0
  83. data/lib/tap/test/subset_methods.rb +1 -1
  84. data/lib/tap/test/tap_methods.rb +28 -62
  85. data/lib/tap/workflow.rb +22 -39
  86. metadata +48 -179
  87. data/Basic Overview +0 -151
  88. data/Command Reference +0 -99
  89. data/Rakefile +0 -127
  90. data/Tutorial +0 -287
  91. data/lib/tap/cmd/console.rb +0 -31
  92. data/lib/tap/cmd/destroy.rb +0 -20
  93. data/lib/tap/cmd/generate.rb +0 -20
  94. data/lib/tap/cmd/run.rb +0 -151
  95. data/lib/tap/dump.rb +0 -57
  96. data/lib/tap/generator.rb +0 -91
  97. data/lib/tap/generator/generators/command/USAGE +0 -6
  98. data/lib/tap/generator/generators/config/USAGE +0 -21
  99. data/lib/tap/generator/generators/config/templates/config.erb +0 -1
  100. data/lib/tap/generator/generators/file_task/USAGE +0 -3
  101. data/lib/tap/generator/generators/file_task/templates/file.yml +0 -3
  102. data/lib/tap/generator/generators/generator/USAGE +0 -0
  103. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -21
  104. data/lib/tap/generator/generators/generator/templates/generator.erb +0 -32
  105. data/lib/tap/generator/generators/generator/templates/usage.erb +0 -1
  106. data/lib/tap/generator/generators/root/USAGE +0 -0
  107. data/lib/tap/generator/generators/root/templates/ReadMe.txt +0 -0
  108. data/lib/tap/generator/generators/root/templates/tap.yml +0 -80
  109. data/lib/tap/generator/generators/task/USAGE +0 -3
  110. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  111. data/lib/tap/generator/generators/workflow/templates/task.erb +0 -16
  112. data/lib/tap/generator/generators/workflow/templates/test.erb +0 -7
  113. data/lib/tap/generator/generators/workflow/workflow_generator.rb +0 -6
  114. data/lib/tap/generator/options.rb +0 -26
  115. data/lib/tap/generator/usage.rb +0 -26
  116. data/lib/tap/support/batchable_methods.rb +0 -34
  117. data/lib/tap/support/command_line_methods.rb +0 -76
  118. data/lib/tap/support/configurable_methods.rb +0 -224
  119. data/lib/tap/support/logger.rb +0 -88
  120. data/lib/tap/support/rake.rb +0 -43
  121. data/lib/tap/support/tdoc/config_attr.rb +0 -362
  122. data/test/app/config/another/task.yml +0 -1
  123. data/test/app/config/batch.yml +0 -2
  124. data/test/app/config/empty.yml +0 -0
  125. data/test/app/config/erb.yml +0 -2
  126. data/test/app/config/some/task.yml +0 -1
  127. data/test/app/config/template.yml +0 -2
  128. data/test/app/config/version-0.1.yml +0 -1
  129. data/test/app/config/version.yml +0 -1
  130. data/test/app/lib/app_test_task.rb +0 -3
  131. data/test/app_test.rb +0 -1849
  132. data/test/env/test_configure/recurse_a.yml +0 -2
  133. data/test/env/test_configure/recurse_b.yml +0 -2
  134. data/test/env/test_configure/tap.yml +0 -23
  135. data/test/env/test_load_env_config/dir/tap.yml +0 -3
  136. data/test/env/test_load_env_config/recurse_a.yml +0 -2
  137. data/test/env/test_load_env_config/recurse_b.yml +0 -2
  138. data/test/env/test_load_env_config/tap.yml +0 -3
  139. data/test/env_test.rb +0 -198
  140. data/test/file_task/config/batch.yml +0 -2
  141. data/test/file_task/config/configured.yml +0 -1
  142. data/test/file_task/old_file_one.txt +0 -0
  143. data/test/file_task/old_file_two.txt +0 -0
  144. data/test/file_task_test.rb +0 -1291
  145. data/test/root/alt_lib/alt_module.rb +0 -4
  146. data/test/root/file.txt +0 -0
  147. data/test/root/glob/one.txt +0 -0
  148. data/test/root/glob/two.txt +0 -0
  149. data/test/root/lib/absolute_alt_filepath.rb +0 -2
  150. data/test/root/lib/alternative_filepath.rb +0 -2
  151. data/test/root/lib/another_module.rb +0 -2
  152. data/test/root/lib/nested/some_module.rb +0 -4
  153. data/test/root/lib/no_module_included.rb +0 -0
  154. data/test/root/lib/some/module.rb +0 -4
  155. data/test/root/lib/some_class.rb +0 -2
  156. data/test/root/lib/some_module.rb +0 -3
  157. data/test/root/load_path/load_path_module.rb +0 -2
  158. data/test/root/load_path/skip_module.rb +0 -2
  159. data/test/root/mtime/older.txt +0 -0
  160. data/test/root/unload/full_path.rb +0 -2
  161. data/test/root/unload/loaded_by_nested.rb +0 -2
  162. data/test/root/unload/nested/nested_load.rb +0 -6
  163. data/test/root/unload/nested/nested_with_ext.rb +0 -4
  164. data/test/root/unload/nested/relative_path.rb +0 -4
  165. data/test/root/unload/older.rb +0 -2
  166. data/test/root/unload/unload_base.rb +0 -9
  167. data/test/root/versions/another.yml +0 -0
  168. data/test/root/versions/file-0.1.2.yml +0 -0
  169. data/test/root/versions/file-0.1.yml +0 -0
  170. data/test/root/versions/file.yml +0 -0
  171. data/test/root_test.rb +0 -718
  172. data/test/support/aggregator_test.rb +0 -99
  173. data/test/support/audit_test.rb +0 -445
  174. data/test/support/batchable_test.rb +0 -74
  175. data/test/support/class_configuration_test.rb +0 -331
  176. data/test/support/command_line_test.rb +0 -58
  177. data/test/support/configurable/config/configured.yml +0 -2
  178. data/test/support/configurable_test.rb +0 -295
  179. data/test/support/executable_queue_test.rb +0 -103
  180. data/test/support/executable_test.rb +0 -38
  181. data/test/support/logger_test.rb +0 -31
  182. data/test/support/rake_test.rb +0 -37
  183. data/test/support/shell_utils_test.rb +0 -24
  184. data/test/support/tdoc_test.rb +0 -370
  185. data/test/support/validation_test.rb +0 -54
  186. data/test/support/versions_test.rb +0 -103
  187. data/test/tap_test_helper.rb +0 -57
  188. data/test/tap_test_suite.rb +0 -7
  189. data/test/task/config/batch.yml +0 -2
  190. data/test/task/config/batched.yml +0 -2
  191. data/test/task/config/configured.yml +0 -1
  192. data/test/task/config/example.yml +0 -1
  193. data/test/task_base_test.rb +0 -24
  194. data/test/task_syntax_test.rb +0 -300
  195. data/test/task_test.rb +0 -320
  196. data/test/test/env_vars_test.rb +0 -48
  197. data/test/test/file_methods/test_assert_files/expected/one.txt +0 -1
  198. data/test/test/file_methods/test_assert_files/expected/two.txt +0 -1
  199. data/test/test/file_methods/test_assert_files/input/one.txt +0 -1
  200. data/test/test/file_methods/test_assert_files/input/two.txt +0 -1
  201. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/one.txt +0 -1
  202. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/two.txt +0 -1
  203. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +0 -1
  204. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/two.txt +0 -1
  205. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +0 -1
  206. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +0 -1
  207. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/expected/one.txt +0 -1
  208. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +0 -1
  209. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +0 -1
  210. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +0 -1
  211. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +0 -1
  212. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +0 -1
  213. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +0 -1
  214. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +0 -1
  215. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +0 -1
  216. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  217. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  218. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  219. data/test/test/file_methods_doc/test_sub/expected/one.txt +0 -1
  220. data/test/test/file_methods_doc/test_sub/expected/two.txt +0 -1
  221. data/test/test/file_methods_doc/test_sub/input/one.txt +0 -1
  222. data/test/test/file_methods_doc/test_sub/input/two.txt +0 -1
  223. data/test/test/file_methods_doc_test.rb +0 -29
  224. data/test/test/file_methods_test.rb +0 -275
  225. data/test/test/subset_methods_test.rb +0 -171
  226. data/test/test/tap_methods/test_assert_files/expected/task/name/a.txt +0 -1
  227. data/test/test/tap_methods/test_assert_files/expected/task/name/b.txt +0 -1
  228. data/test/test/tap_methods/test_assert_files/input/a.txt +0 -1
  229. data/test/test/tap_methods/test_assert_files/input/b.txt +0 -1
  230. data/test/test/tap_methods_test.rb +0 -399
  231. data/test/workflow_test.rb +0 -120
  232. data/vendor/rails_generator.rb +0 -56
  233. data/vendor/rails_generator/base.rb +0 -263
  234. data/vendor/rails_generator/commands.rb +0 -581
  235. data/vendor/rails_generator/generated_attribute.rb +0 -42
  236. data/vendor/rails_generator/lookup.rb +0 -209
  237. data/vendor/rails_generator/manifest.rb +0 -53
  238. data/vendor/rails_generator/options.rb +0 -143
  239. data/vendor/rails_generator/scripts.rb +0 -83
  240. data/vendor/rails_generator/scripts/destroy.rb +0 -7
  241. data/vendor/rails_generator/scripts/generate.rb +0 -7
  242. data/vendor/rails_generator/scripts/update.rb +0 -12
  243. data/vendor/rails_generator/simple_logger.rb +0 -46
  244. data/vendor/rails_generator/spec.rb +0 -44
data/lib/tap/app.rb CHANGED
@@ -1,61 +1,33 @@
1
+ require 'logger'
2
+ require 'tap/support/run_error'
3
+ require 'tap/support/aggregator'
4
+ require 'tap/support/executable_queue'
5
+
1
6
  module Tap
2
7
 
3
- # = Overview
4
- #
5
8
  # App coordinates the setup and running of tasks, and provides an interface
6
9
  # to the application directory structure. App is convenient for use within
7
- # scripts, and provides the basis for the 'tap' command line application.
8
- #
9
- # === Task Setup
10
- #
11
- # All tasks have an App (by default App.instance) which helps initialize the
12
- # task by loading configuration templates from the config directory. Say
13
- # we had the following configuration files:
14
- #
15
- # [/path/to/app/config/some/task.yml]
16
- # key: one
17
- #
18
- # [/path/to/app/config/another/task.yml]
19
- # key: two
20
- #
21
- # Tasks initialized with the names 'some/task' and 'another/task' will
22
- # be cofigured by App like this:
23
- #
24
- # app = App.instance
25
- # app.root # => '/path/to/app'
26
- # app[:config] # => '/path/to/app/config'
27
- #
28
- # some_task = Task.new 'some/task'
29
- # some_task.app # => App.instance
30
- # some_task.config_file # => '/path/to/app/config/some/task.yml'
31
- # some_task.config # => {:key => 'one'}
32
- #
33
- # another_task = Task.new 'another/task'
34
- # another_task.app # => App.instance
35
- # another_task.config_file # => '/path/to/app/config/another/task.yml'
36
- # another_task.config # => {:key => 'two'}
37
- #
38
- # If app[:config] referenced a different directory then the tasks would be
39
- # initialized from files relative to that location.
40
- #
41
- # (see Tap::Root for more details)
10
+ # scripts and, with Env, provides the basis for the 'tap' command line
11
+ # application.
42
12
  #
43
13
  # === Running Tasks
44
14
  #
45
- # Task enque commands are passed to app, and tasks access application-wide
46
- # resources like the logger and options through App.
15
+ # All tasks have an App (by default App.instance) through which tasks access
16
+ # access application-wide resources like the logger. Additionally, task
17
+ # enque command are forwarded to App#enq:
47
18
  #
48
19
  # t1 = Task.new {|task, input| input += 1 }
49
- # t1.enq 0
50
- # t1.enq 10
20
+ # t1.enq(0)
21
+ # app.enq(t1, 1)
51
22
  #
52
23
  # app.run
53
- # app.results(t1) # => [1, 11]
24
+ # app.results(t1) # => [1, 2]
54
25
  #
55
- # When a task completes, app collects its results into a data structure that
56
- # allows access to them as shown above. This behavior can be modified by
57
- # setting an on_complete block for the task; on_complete blocks can be used
58
- # to pass results among tasks, allowing the construction of workflows.
26
+ # When a task completes, the results will either be passed to the task
27
+ # <tt>on_complete</tt> block (if set) or be collected into an Aggregator;
28
+ # aggregated results may be accessed per-task, as shown above. Task
29
+ # <tt>on_complete</tt> blocks typically enque other tasks, allowing the
30
+ # construction of workflows:
59
31
  #
60
32
  # # clear the previous results
61
33
  # app.aggregator.clear
@@ -73,41 +45,25 @@ module Tap
73
45
  # Here t1 has no results because the on_complete block passed them to t2 in
74
46
  # a simple sequence.
75
47
  #
76
- # === Running Methods
77
- #
78
- # Running a task really consists of calling a method. For tasks, the method is
79
- # basically the block you provide to Task.new, although execution is mediated by
80
- # Tap::Task#execute and Tap::Task#process so that the block receives the task
81
- # as a standard input. In subclasses, the method corresponds to the subclass
82
- # 'process' method.
83
- #
84
- # # the block is called to add one to the input
85
- # Task.new {|task, input| input += 1 }
86
- #
87
- # # same thing, but now in a subclass
88
- # class AddOne < Tap::Task
89
- # def process(input) input += 1 end
90
- # end
48
+ # ==== Batching
91
49
  #
92
- # When tasks are enqued, their executable method is pushed onto the queue along
93
- # with the inputs for the method. Tasks can be batched such that the executable
94
- # methods of several tasks are enqued at the same time, allowing you to feed the
95
- # same inputs to multiple methods at once.
50
+ # Tasks can be batched, allowing the same input to be enqued to multiple
51
+ # tasks at once.
96
52
  #
97
53
  # t1 = Task.new {|task, input| input += 1 }
98
54
  # t2 = Task.new {|task, input| input += 10 }
99
55
  # Task.batch(t1, t2) # => [t1, t2]
100
56
  #
101
57
  # t1.enq 0
102
- # t2.enq 10
103
58
  #
104
59
  # app.run
105
- # app.results(t1) # => [1, 11]
106
- # app.results(t2) # => [10, 20]
60
+ # app.results(t1) # => [1]
61
+ # app.results(t2) # => [10]
107
62
  #
108
- # App also supports multithreading; multithreaded methods execute cosynchronously,
109
- # each on their own thread (of course, you need to take care to make each method
110
- # thread safe).
63
+ # ==== Multithreading
64
+ #
65
+ # App supports multithreading; multithreaded tasks execute cosynchronously,
66
+ # each on their own thread.
111
67
  #
112
68
  # lock = Mutex.new
113
69
  # array = []
@@ -123,25 +79,20 @@ module Tap
123
79
  # array.length # => 2
124
80
  # array[0] == array[1] # => false
125
81
  #
126
- # Since App is geared towards methods, methods from non-task objects can get
127
- # hooked into a workflow as needed. The preferred way to do so is to make the
128
- # non-task objects behave like tasks using Task::Base#initialize. The objects
129
- # can now be enqued, incorporated into workflows, and batched.
82
+ # Naturally, it is up to you to make sure each task is thread safe. Note
83
+ # that for the most part Tap::App is NOT thread safe; only run and
84
+ # run-related methods (ready, stop, terminate, info) are synchronized.
85
+ # Methods enq and results act on thread-safe objects ExecutableQueue and
86
+ # Aggregator, and should be ok to use from multiple threads.
130
87
  #
131
- # array = []
132
- # Task::Base.initialize(array, :push)
88
+ # ==== Executables
133
89
  #
134
- # array.enq(1)
135
- # array.enq(2)
90
+ # App can use any Executable object in place of a task. One way to initialize
91
+ # an Executable for a method is to use the Object#_method defined by Tap. The
92
+ # result can be enqued and incorporated into workflows, but they cannot be
93
+ # batched.
136
94
  #
137
- # array.empty? # => true
138
- # app.run
139
- # array # => [1, 2]
140
- #
141
- # Lastly, if you can't or don't want to turn your object into a task, Tap defines
142
- # Object#_method to generate executable objects that can be enqued and
143
- # incorporated into workflows, although they cannot be batched. The mq
144
- # (method enq) method generates and enques the method in one step.
95
+ # The mq (method enq) method generates and enques the method in one step.
145
96
  #
146
97
  # array = []
147
98
  # m = array._method(:push)
@@ -153,22 +104,20 @@ module Tap
153
104
  # app.run
154
105
  # array # => [1, 2]
155
106
  #
156
- # App keeps running as long as it finds methods in the queue, or until it is stopped
157
- # or terminated.
158
- #
159
- # (see Tap::Support::Executable, Tap::Task, and Tap::Task::Base for more details)
160
- #
161
107
  # === Auditing
162
108
  #
163
- # All results generated by methods are audited to track how a given input
164
- # evolves during a workflow.
109
+ # All results generated by executable methods are audited to track how a given
110
+ # input evolves during a workflow.
165
111
  #
166
112
  # To illustrate auditing, consider a workflow that uses the 'add_one' method
167
113
  # to add one to an input until the result is 3, then adds five more with the
168
114
  # 'add_five' method. The final result should always be 8.
169
115
  #
170
- # t1 = Tap::Task.new('add_one') {|task, input| input += 1 }
171
- # t2 = Tap::Task.new('add_five') {|task, input| input += 5 }
116
+ # t1 = Tap::Task.new {|task, input| input += 1 }
117
+ # t1.name = "add_one"
118
+ #
119
+ # t2 = Tap::Task.new {|task, input| input += 5 }
120
+ # t2.name = "add_five"
172
121
  #
173
122
  # t1.on_complete do |_result|
174
123
  # # _result is the audit; use the _current method
@@ -219,7 +168,7 @@ module Tap
219
168
  # See Tap::Support::Audit for more details.
220
169
  class App < Root
221
170
  include MonitorMixin
222
-
171
+
223
172
  class << self
224
173
  # Sets the current app instance
225
174
  attr_writer :instance
@@ -230,28 +179,25 @@ module Tap
230
179
  @instance ||= App.new
231
180
  end
232
181
  end
233
-
234
- # An OpenStruct containing the application options.
235
- attr_reader :options
236
-
237
- # The shared logger.
182
+
183
+ # The shared logger
238
184
  attr_reader :logger
239
185
 
240
- # The application queue.
186
+ # The application queue
241
187
  attr_reader :queue
242
188
 
243
- # The state of the application (see App::State).
189
+ # The state of the application (see App::State)
244
190
  attr_reader :state
245
191
 
246
- # A hash of (task_name, task_class_name) pairs mapping names to
247
- # classes for instantiating tasks that have a non-default name.
248
- # See task_class_name for more details.
249
- attr_accessor :map
250
-
251
192
  # A Tap::Support::Aggregator to collect the results of
252
- # methods that have no on_complete block.
193
+ # methods that have no <tt>on_complete</tt> block
253
194
  attr_reader :aggregator
254
195
 
196
+ config :max_threads, 10, &c.integer # For multithread execution
197
+ config :debug, false, &c.flag # Flag debugging
198
+ config :force, false, &c.flag # Force execution at checkpoints
199
+ config :quiet, false, &c.flag # Suppress logging
200
+
255
201
  # The constants defining the possible App states.
256
202
  module State
257
203
  READY = 0
@@ -271,11 +217,8 @@ module Tap
271
217
  end
272
218
  end
273
219
 
274
- DEFAULT_MAX_THREADS = 10
275
-
276
220
  # Creates a new App with the given configuration.
277
- # See reconfigure for configuration options.
278
- def initialize(config={})
221
+ def initialize(config={}, logger=DEFAULT_LOGGER)
279
222
  super()
280
223
 
281
224
  @state = State::READY
@@ -285,356 +228,43 @@ module Tap
285
228
 
286
229
  @queue = Support::ExecutableQueue.new
287
230
  @aggregator = Support::Aggregator.new
288
-
289
- # defaults must be provided for options and logging to ensure
290
- # that they will be initialized by reconfigure
291
- self.reconfigure( {
292
- :options => {}, :logger => {}, :map => {}
293
- }.merge(config) )
294
- end
295
-
296
- # Clears the queue and aggregator.
297
- #def clear(options={})
298
- # # syncrhonize?
299
- # ready
300
- # raise "cannot clear unless state == READY" unless state == State::READY
301
- #
302
- # queue.clear
303
- # aggregator.clear
304
- #end
305
-
306
- # True if options.debug or the global variable $DEBUG is true.
307
- def debug?
308
- options.debug || $DEBUG ? true : false
309
- end
310
-
311
- # Returns the configuration of self.
312
- def config
313
- {:root => self.root,
314
- :directories => self.directories,
315
- :absolute_paths => self.absolute_paths,
316
- :options => self.options.marshal_dump,
317
- :logger => {
318
- :device => self.logger.logdev.dev,
319
- :level => self.logger.level,
320
- :datetime_format => self.logger.datetime_format}}
321
- end
322
-
323
- # Reconfigures self with the input configurations; other configurations are not affected.
324
- #
325
- # app = Tap::App.new :root => "/root", :directories => {:dir => 'path/to/dir'}
326
- # app.reconfigure(
327
- # :root => "./new/root",
328
- # :logger => {:level => Logger::DEBUG})
329
- #
330
- # app.root # => File.expand_path("./new/root")
331
- # app[:dir] # => File.expand_path("./new/root/path/to/dir")
332
- # app.logger.level # => Logger::DEBUG
333
- #
334
- # Available configurations:
335
- # root:: resets the root directory of self using root=
336
- # directories:: resets directory aliases using directories= (note ALL
337
- # aliases are reset. use app[dir]= to set a single alias)
338
- # absolute_paths:: resets absolute path aliases using absolute_paths= (note ALL
339
- # aliases are reset. use app[dir]= to set a single alias)
340
- # options:: resets the application options (note ALL options are reset.
341
- # use app.options.opt= to set a single option)
342
- # logger:: creates and sets a new logger from the configuration
343
- #
344
- # Available logger configurations and defaults:
345
- # device:: STDOUT
346
- # level:: INFO (1)
347
- # datetime_format:: %H:%M:%S
348
- #
349
- # Unknown configurations raise an error.
350
- def reconfigure(config={})
351
- config = config.symbolize_keys
352
-
353
- # ensure critical keys are evaluated in the proper order
354
- keys = [:root, :directories, :absolute_paths, :options]
355
- config.keys.each do |key|
356
- keys << key unless keys.include?(key)
357
- end
358
-
359
- keys.each do |key|
360
- next unless config.has_key?(key)
361
- value = config[key]
362
-
363
- case key
364
- when :root
365
- self.root = value
366
- when :directories
367
- self.directories = value
368
- when :absolute_paths
369
- self.absolute_paths = value
370
- when :options
371
- @options = OpenStruct.new
372
- value.each_pair {|k,v| options.send("#{k}=", v) }
373
- when :logger
374
- log_config = {
375
- :device => STDOUT,
376
- :level => 'INFO',
377
- :datetime_format => '%H:%M:%S'
378
- }.merge(value.symbolize_keys)
379
-
380
- logger = Logger.new(log_config[:device])
381
- logger.level = log_config[:level].kind_of?(String) ? Logger.const_get(log_config[:level]) : log_config[:level]
382
- logger.datetime_format = log_config[:datetime_format]
383
- self.logger = logger
384
- when :map
385
- self.map = value
386
- else
387
- unless handle_configuation(key, value)
388
- if block_given?
389
- yield(key, value)
390
- else
391
- raise ArgumentError.new("Unknown configuration: #{key}")
392
- end
393
- end
394
- end
395
- end
396
231
 
397
- self
232
+ initialize_config(config)
233
+ self.logger = logger
398
234
  end
399
235
 
400
- # Unloads constants loaded by Dependencies, so that they will be reloaded
401
- # (with any changes made) next time they are called. Returns the unloaded
402
- # constants.
403
- def reload
404
- unloaded = []
405
-
406
- # echos the behavior of Dependencies.clear,
407
- # but collects unloaded constants
408
- Dependencies.loaded.clear
409
- Dependencies.autoloaded_constants.each do |const|
410
- Dependencies.remove_constant const
411
- unloaded << const
412
- end
413
- Dependencies.autoloaded_constants.clear
414
- Dependencies.explicitly_unloadable_constants.each do |const|
415
- Dependencies.remove_constant const
416
- unloaded << const
417
- end
418
-
419
- unloaded
236
+ DEFAULT_LOGGER = Logger.new(STDOUT)
237
+ DEFAULT_LOGGER.level = Logger::INFO
238
+ DEFAULT_LOGGER.formatter = lambda do |severity, time, progname, msg|
239
+ " %s[%s] %18s %s\n" % [severity[0,1], time.strftime('%H:%M:%S') , progname || '--' , msg]
420
240
  end
421
241
 
422
- # Looks up the specified constant, dynamically loading via Dependencies
423
- # if necessary. Returns the const_name if const_name is a Module.
424
- # Yields to the optional block if the constant cannot be found; otherwise
425
- # raises a LookupError.
426
- def lookup_const(const_name)
427
- return const_name if const_name.kind_of?(Module)
428
-
429
- begin
430
- const_name = const_name.camelize
431
-
432
- case RUBY_VERSION
433
- when /^1.9/
434
-
435
- # a check is necessary to maintain the 1.8 behavior
436
- # of lookup_const in 1.9, where ancestor constants
437
- # may be returned by a direct evaluation
438
- const_name.split("::").inject(Object) do |current, const|
439
- const = const.to_sym
440
-
441
- current.const_get(const).tap do |c|
442
- unless current.const_defined?(const, false)
443
- raise NameError.new("uninitialized constant #{const_name}")
444
- end
445
- end
446
- end
447
-
448
- else
449
- const_name.constantize
450
- end
451
-
452
- rescue(NameError)
453
- if block_given?
454
- yield
455
- else
456
- raise LookupError.new("unknown constant: #{const_name}")
457
- end
458
- end
242
+ # True if debug or the global variable $DEBUG is true.
243
+ def debug?
244
+ debug || $DEBUG
459
245
  end
460
246
 
461
- #
462
- # Logging methods
463
- #
464
-
465
- # Sets the current logger. The logger is extended with Support::Logger to provide
466
- # additional logging capabilities. The logger level is set to Logger::DEBUG if
467
- # the global variable $DEBUG is true.
247
+ # Sets the current logger. The logger level is set to Logger::DEBUG if
248
+ # debug? is true.
468
249
  def logger=(logger)
250
+ unless logger.nil?
251
+ logger.level = Logger::DEBUG if debug?
252
+ end
253
+
469
254
  @logger = logger
470
- @logger.extend Support::Logger unless @logger.nil?
471
- @logger.level = Logger::DEBUG if $DEBUG
472
- @logger
473
255
  end
474
256
 
475
257
  # Logs the action and message at the input level (default INFO).
476
- # Logging is suppressed if options.quiet
258
+ # Logging is suppressed if quiet is true.
477
259
  def log(action, msg="", level=Logger::INFO)
478
- logger.add(level, msg, action.to_s) unless options.quiet
479
- end
480
-
481
- # EXPERIMENTAL
482
- #
483
- # Formatted log. Works like log, but passes the current log format to the
484
- # block and uses whatever format the block returns. The format recieves
485
- # the following arguments like so:
486
- #
487
- # format % [severity, timestamp, (action || '--'), msg]
488
- #
489
- # By default, if you don't specify a block, flog just chomps a newline off
490
- # the format, so your log will be inline.
491
- #
492
- # BUG: Not thread safe at the moment.
493
- def flog(action="", msg="", level=Logger::INFO) # :yields: format
494
- unless options.quiet
495
- logger.format_add(level, msg, action) do |format|
496
- block_given? ? yield(format) : format.chomp("\n")
497
- end
498
- end
499
- end
500
-
501
- #
502
- # Task methods
503
- #
504
-
505
- # Instantiates the specifed task with config (if provided). The task
506
- # class is determined by task_class.
507
- #
508
- # t = app.task('tap/file_task')
509
- # t.class # => Tap::FileTask
510
- # t.name # => 'tap/file_task'
511
- #
512
- # app.map = {"mapped-task" => "Tap::FileTask"}
513
- # t = app.task('mapped-task-1.0', :key => 'value')
514
- # t.class # => Tap::FileTask
515
- # t.name # => "mapped-task-1.0"
516
- # t.config[:key] # => 'value'
517
- #
518
- # A new task is instantiated for each call to task; tasks may share the
519
- # same name.
520
- def task(task_name, config={}, &block)
521
- task_class(task_name).new(task_name, config, &block)
522
- end
523
-
524
- # Looks up the specifed task class. Names are mapped to task classes
525
- # using task_class_name.
526
- #
527
- # t_class = app.task_class('tap/file_task')
528
- # t_class # => Tap::FileTask
529
- #
530
- # app.map = {"mapped-task" => "Tap::FileTask"}
531
- # t_class = app.task_class('mapped-task-1.0')
532
- # t_class # => Tap::FileTask
533
- #
534
- # Notes:
535
- # - The task class will be auto-loaded using Dependencies, if needed.
536
- # - A LookupError is raised if the task class cannot be found.
537
- def task_class(task_name)
538
- lookup_const task_class_name(task_name) do
539
- raise LookupError.new("unknown task '#{task_name}'")
540
- end
541
- end
542
-
543
- # Returns the class name of the specified task. If the task
544
- # descriptor is a string, the class name is the de-versioned,
545
- # descriptor, or the class name as specified in map by the
546
- # de-versioned descriptor.
547
- #
548
- # app.map = {"mapped-task" => "Tap::FileTask"}
549
- # app.task_class_name('some/task_class') # => "some/task_class"
550
- # app.task_class_name('mapped-task-1.0') # => "Tap::FileTask"
551
- #
552
- # If td is a type of Tap::Task::Base, then task_class_name
553
- # returns td.class.to_s
554
- #
555
- # t1 = Task.new
556
- # app.task_class_name(t1) # => "Tap::Task"
557
- #
558
- # t2 = Object.new.extend Tap::Task::Base
559
- # app.task_class_name(t2) # => "Object"
560
- #
561
- def task_class_name(td)
562
- case td
563
- when Tap::Task::Base then td.class.to_s
564
- else
565
- # de-version and resolve using map
566
- name, version = deversion(td.to_s)
567
- map.has_key?(name) ? map[name].to_s : name
568
- end
569
- end
570
-
571
- # Iteratively passes the block the configuration templates for the specified file.
572
- # Ultimately these templates specify configurations for tasks, as well batched tasks,
573
- # linked to to self. If no block is specified, each_config_template collects the
574
- # templates and returns them as an array.
575
- #
576
- # To make templates, the contents of the file are processed using ERB, then loaded
577
- # as YAML. ERB for the config files is evaluated in a binding that contains
578
- # references to self (app) and the input filepath.
579
- #
580
- # # [simple.yml]
581
- # # key: value
582
- #
583
- # app.each_config_template("simple.yml") # => [{"key" => "value"}]
584
- #
585
- # # [erb.yml]
586
- # # app: <%= app.object_id %>
587
- # # filepath: <%= filepath %>
588
- #
589
- # app.each_config_template("erb.yml") # => [{"app" => app.object_id, "filepath" => "erb.yml"}]
590
- #
591
- # Batched tasks can be specified by providing an array of hashes.
592
- #
593
- # # [batched_with_erb.yml]
594
- # # - key: <%= 1 %>
595
- # # - key: <%= 1 + 1 %>
596
- #
597
- # app.each_config_template("batched_with_erb.yml") # => [{"key" => 1}, {"key" => 2}]
598
- #
599
- # If no config templates can be loaded (as when the filepath does not exist, or
600
- # the file is empty), each_config_template passes the block a single empty template.
601
- def each_config_template(filepath) # :yields: template
602
- unless block_given?
603
- templates = []
604
- each_config_template(filepath) {|template| templates << template}
605
- return templates
606
- end
607
-
608
- if filepath == nil
609
- yield({})
610
- else
611
- templates = if !File.exists?(filepath) || File.directory?(filepath)
612
- nil
613
- else
614
- # create the reference to app for templating
615
- app = self
616
- input = ERB.new(File.read(filepath)).result(binding)
617
- YAML.load(input)
618
- end
619
-
620
- case templates
621
- when Array
622
- templates.each do |template|
623
- yield(template)
624
- end
625
- when Hash
626
- yield(templates)
627
- else
628
- yield({})
629
- end
630
- end
260
+ logger.add(level, msg, action.to_s) unless quiet
631
261
  end
632
262
 
633
263
  # Returns the configuration filepath for the specified task name,
634
264
  # File.join(app['config'], task_name + ".yml"). Returns nil if
635
- # task_name==nil.
636
- def config_filepath(task_name)
637
- task_name == nil ? nil : filepath('config', task_name + ".yml")
265
+ # task_name is nil.
266
+ def config_filepath(name)
267
+ name == nil ? nil : filepath('config', "#{name}.yml")
638
268
  end
639
269
 
640
270
  #
@@ -659,41 +289,45 @@ module Tap
659
289
  end
660
290
  end
661
291
 
662
- # Runs the methods in the queue in which they were enqued. Run exists when there
663
- # are no more enqued methods. Run returns self. An app can only run on one thread
664
- # at a time. If run is called when self is already running, run returns immediately.
292
+ # Sequentially executes the methods (ie Executable objects) in queue; run
293
+ # continues until the queue is empty and then returns self. An app can
294
+ # only run on one thread at a time. If run is called when already running,
295
+ # run returns immediately.
665
296
  #
666
297
  # === The Run Cycle
667
- # During run, each method is executed sequentially on the current thread unless
668
- # m.multithread == true. In this case run switches into a multithreaded mode and
669
- # launches up to n execution threads (where n is options.max_threads or
670
- # DEFAULT_MAX_THREADS) each of which can run a multithreaded method.
671
- #
672
- # These threads will run methods until a non-multithreaded method reaches the top
673
- # of the queue. At that point, run waits for the multithreaded methods to complete,
674
- # and then switches back into the sequential mode. Run never executes multithreaded
675
- # and non-multithreaded methods at the same time.
676
- #
677
- # Run checks the state of self before executing a method. If the state is changed
678
- # to State::STOP, then no more methods will be executed (but currently running methods
679
- # will continute to completion). If the state is changed to State::TERMINATE then
680
- # no more methods will be executed and currently running methods will be discontinued
681
- # as described below.
682
- #
683
- # When a series of multithreaded methods are stopped or terminated mid-execution,
684
- # several methods may be waiting for a free execution thread. These are requeued.
685
- #
686
- # === Error Handling and Termination
687
- # When unhandled errors arise during run, run enters a termination (rescue)
688
- # routine. During termination a TerminationError is raised in each executing
689
- # method so that the method exits or begins executing its internal error handling
690
- # code (perhaps performing rollbacks).
691
- #
692
- # The TerminationError is ONLY raised when the method calls Task::Base#check_terminate
693
- # This method is available to all Task::Base objects, but obviously is NOT available
694
- # to Executable methods generated by _method. These methods need to check the state
695
- # of app themselves; otherwise they will continue on to completion even when app
696
- # is in State::TERMINATE.
298
+ # Run can execute methods in sequential or multithreaded mode. In sequential
299
+ # mode, run executes enqued methods in order and on the current thread. Run
300
+ # continues until it reaches a method marked with multithread = true, at which
301
+ # point run switches into multithreading mode.
302
+ #
303
+ # When multithreading, run shifts methods off of the queue and executes each
304
+ # on their own thread (launching up to max_threads threads at one time).
305
+ # Multithread execution continues until run reaches a non-multithread method,
306
+ # at which point run blocks, waits for the threads to complete, and switches
307
+ # back into sequential mode.
308
+ #
309
+ # Run never executes multithreaded and non-multithreaded methods at the same
310
+ # time.
311
+ #
312
+ # ==== Checks
313
+ # Run checks the state of self before executing a method. If the state is
314
+ # changed to State::STOP, then no more methods will be executed; currently
315
+ # running methods will continute to completion. If the state is changed to
316
+ # State::TERMINATE then no more methods will be executed and currently running
317
+ # methods will be discontinued as described below.
318
+ #
319
+ # ==== Error Handling and Termination
320
+ # When unhandled errors arise during run, run enters a termination routine.
321
+ # During termination a TerminationError is raised in each executing method so
322
+ # that the method exits or begins executing its internal error handling code
323
+ # (perhaps performing rollbacks).
324
+ #
325
+ # The TerminationError can ONLY be raised by the method itself, usually via a
326
+ # call to Tap::Support::Framework#check_terminate. <tt>check_terminate</tt>
327
+ # is available to all Framework objects (ex Task and Workflow), but not to
328
+ # Executable methods generated by _method. These methods need to check the
329
+ # state of app themselves; otherwise they will continue on to completion even
330
+ # when app is in State::TERMINATE.
697
331
  #
698
332
  # # this task will loop until app.terminate
699
333
  # Task.new {|task| while(true) task.check_terminate end }
@@ -703,11 +337,12 @@ module Tap
703
337
  #
704
338
  # Additional errors that arise during termination are collected and packaged
705
339
  # with the orignal error into a RunError. By default all errors are logged
706
- # and run exits. If debug? == true, then the RunError will be raised for further
707
- # handling.
340
+ # and run exits. If debug? is true, then the RunError will be raised for
341
+ # further handling.
708
342
  #
709
- # Note: the method that caused the original unhandled error is no longer executing
710
- # when termination begins and thus will not recieve a TerminationError.
343
+ # Note: the method that caused the original unhandled error is no longer
344
+ # executing when termination begins and thus will not recieve a
345
+ # TerminationError.
711
346
  def run
712
347
  synchronize do
713
348
  return self unless self.ready.state == State::READY
@@ -717,7 +352,6 @@ module Tap
717
352
  end
718
353
 
719
354
  # generate threading variables
720
- max_threads = options.max_threads || DEFAULT_MAX_THREADS
721
355
  self.thread_queue = max_threads > 0 ? Queue.new : nil
722
356
 
723
357
  # TODO: log starting run
@@ -777,7 +411,7 @@ module Tap
777
411
  # termination.
778
412
  clear_thread_queue
779
413
  errors = [$!] + clear_threads(false)
780
- errors.delete_if {|error| error.kind_of?(TerminateError) }
414
+ errors.delete_if {|error| error.kind_of?(TerminateError) }
781
415
 
782
416
  # handle the errors accordingly
783
417
  case
@@ -822,7 +456,7 @@ module Tap
822
456
  # error handling code, perhaps performing rollbacks.
823
457
  #
824
458
  # Termination checks can be manually specified in a task
825
- # using the check_terminate method (see Tap::Task::Base#check_terminate).
459
+ # using the check_terminate method (see Tap::Task#check_terminate).
826
460
  # Termination checks automatically occur before each task execution.
827
461
  #
828
462
  # Does nothing if state == State::READY.
@@ -852,17 +486,13 @@ module Tap
852
486
  end
853
487
  end
854
488
 
855
- #
856
- # workflow related
857
- #
858
-
859
489
  # Enques the task with the inputs. If the task is batched, then each
860
490
  # task in task.batch will be enqued with the inputs. Returns task.
861
491
  #
862
492
  # An Executable may provided instead of a task.
863
493
  def enq(task, *inputs)
864
494
  case task
865
- when Tap::Task::Base
495
+ when Tap::Task, Tap::Workflow
866
496
  raise "not assigned to enqueing app: #{task}" unless task.app == self
867
497
  task.enq(*inputs)
868
498
  when Support::Executable
@@ -898,7 +528,7 @@ module Tap
898
528
  current_task = next_task
899
529
  end
900
530
  end
901
-
531
+
902
532
  # Sets a fork workflow pattern for the tasks such that each of the
903
533
  # targets will be enqueued with the results of the source when the
904
534
  # source completes. Batched tasks will have the pattern set for each
@@ -914,7 +544,7 @@ module Tap
914
544
  end
915
545
  end
916
546
  end
917
-
547
+
918
548
  # Sets a merge workflow pattern for the tasks such that the results
919
549
  # of each source will be enqueued to the target when the source
920
550
  # completes. Batched tasks will have the pattern set for each
@@ -961,7 +591,7 @@ module Tap
961
591
  protected
962
592
 
963
593
  # A hook for handling unknown configurations in subclasses, called from
964
- # reconfigure. If handle_configuration evaluates to false, then reconfigure
594
+ # configure. If handle_configuration evaluates to false, then configure
965
595
  # raises an error.
966
596
  def handle_configuation(key, value)
967
597
  false
@@ -977,7 +607,7 @@ module Tap
977
607
  attr_accessor :threads
978
608
 
979
609
  # A Queue containing multithread tasks waiting to be run
980
- # on the execution threads. Nil if options.max_threads= 0
610
+ # on the execution threads. Nil if max_threads == 0
981
611
  attr_accessor :thread_queue
982
612
 
983
613
  private
@@ -1082,10 +712,6 @@ module Tap
1082
712
  end
1083
713
  end
1084
714
 
1085
- # LookupErrors are raised for errors during dependency lookup
1086
- class LookupError < RuntimeError
1087
- end
1088
-
1089
715
  # TerminateErrors are raised to kill executing tasks when terminate
1090
716
  # is called on an running App. They are handled by the run rescue code.
1091
717
  class TerminateError < RuntimeError