tap 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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/cmd/destroy.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'tap/generator/base'
2
+ require 'tap/generator/destroy'
3
+
4
+ env = Tap::Env.instance
5
+
6
+ if ARGV.empty? || ARGV == ['-T']
7
+ puts env.summarize(:generators) {|const| const.document[const.name]['generator'] }
8
+ exit
9
+ end
10
+
11
+ name = ARGV.shift
12
+ const = env.search(:generators, name) or raise "unknown generator: #{name}"
13
+
14
+ generator_class = const.constantize
15
+ generator, argv = generator_class.instantiate(ARGV)
16
+ generator.extend(Tap::Generator::Destroy).process(*argv)
data/cmd/generate.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'tap/generator/base'
2
+ require 'tap/generator/generate'
3
+
4
+ env = Tap::Env.instance
5
+
6
+ if ARGV.empty? || ARGV == ['-T']
7
+ puts env.summarize(:generators) {|const| const.document[const.name]['generator'] }
8
+ exit
9
+ end
10
+
11
+ name = ARGV.shift
12
+ const = env.search(:generators, name) or raise "unknown generator: #{name}"
13
+
14
+ generator_class = const.constantize
15
+ generator, argv = generator_class.instantiate(ARGV)
16
+ generator.extend(Tap::Generator::Generate).process(*argv)
data/cmd/run.rb ADDED
@@ -0,0 +1,126 @@
1
+ # tap run {options} -- {task options} task INPUTS...
2
+ #
3
+ # examples:
4
+ # tap run --help Prints this help
5
+ # tap run -- task --help Prints help for task
6
+ #
7
+
8
+ env = Tap::Env.instance
9
+ app = Tap::App.instance
10
+ cmdline = Tap::Support::CommandLine
11
+
12
+ #
13
+ # handle options
14
+ #
15
+
16
+ dump = false
17
+ OptionParser.new do |opts|
18
+
19
+ opts.separator ""
20
+ opts.separator "configurations:"
21
+
22
+ Tap::App.configurations.each do |receiver, key, config|
23
+ next if receiver == Tap::Root
24
+
25
+ opts.on(*cmdline.configv(config)) do |value|
26
+ app.send(configuration.writer, value)
27
+ end
28
+ end
29
+
30
+ opts.separator ""
31
+ opts.separator "options:"
32
+
33
+ opts.on("-h", "--help", "Show this message") do
34
+ opts.banner = cmdline.usage(__FILE__)
35
+ Tap::App.lazydoc.resolve
36
+ puts opts
37
+ exit
38
+ end
39
+
40
+ opts.on('-T', '--manifest', 'Print a list of available tasks') do |v|
41
+ puts env.summarize(:tasks) {|const| const.document[const.name]['manifest'] }
42
+ exit
43
+ end
44
+
45
+ end.parse!(ARGV)
46
+
47
+ #
48
+ # handle options for each specified task
49
+ #
50
+
51
+ rounds = cmdline.split(ARGV).collect do |argvs|
52
+ argvs.each do |argv|
53
+ ARGV.clear
54
+ ARGV.concat(argv)
55
+
56
+ unless td = cmdline.shift(ARGV)
57
+ # warn nil?
58
+ next
59
+ end
60
+
61
+ # attempt lookup the task class
62
+ const = env.search(:tasks, td) or raise "unknown task: #{td}"
63
+ task_class = const.constantize or raise "unknown task: #{td}"
64
+
65
+ # now let the class handle the argv
66
+ task, argv = task_class.instantiate(ARGV, app)
67
+ task.enq *argv.collect! {|str| cmdline.parse_yaml(str) }
68
+ end
69
+
70
+ app.queue.clear
71
+ end
72
+ ARGV.clear
73
+
74
+ rounds.delete_if {|round| round.empty? }
75
+ if rounds.empty?
76
+ puts "no task specified"
77
+ exit
78
+ end
79
+
80
+ #
81
+ # set signals
82
+ #
83
+
84
+ # info signal -- Note: some systems do
85
+ # not support the INFO signal
86
+ # (windows, fedora, at least)
87
+ signals = Signal.list.keys
88
+ if signals.include?("INFO")
89
+ Signal.trap("INFO") do
90
+ puts app.info
91
+ end
92
+ end
93
+
94
+ # interuption signal
95
+ if signals.include?("INT")
96
+ Signal.trap("INT") do
97
+ puts " interrupted!"
98
+ # prompt for decision
99
+ while true
100
+ print "stop, terminate, exit, or resume? (s/t/e/r):"
101
+ case gets.strip
102
+ when /s(top)?/i
103
+ app.stop
104
+ break
105
+ when /t(erminate)?/i
106
+ app.terminate
107
+ break
108
+ when /e(xit)?/i
109
+ exit
110
+ when /r(esume)?/i
111
+ break
112
+ else
113
+ puts "unexpected response..."
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ #
120
+ # enque tasks and run!
121
+ #
122
+
123
+ rounds.each_with_index do |queue, i|
124
+ app.queue.concat(queue)
125
+ app.run
126
+ end
@@ -0,0 +1,362 @@
1
+ = Class Reference
2
+
3
+ Working up from the ground is useful to get a sense for how Tap does what it does. This reference goes through the modules and classes that build up a task application: Tasks, Apps, and Envs.
4
+
5
+ == Tasks (Tap::Task, Tap::Workflow)
6
+
7
+ ==== Methods
8
+
9
+ http://tap.rubyforge.org/images/Method.png
10
+
11
+ Tasks begin with methods, simply a block of code.
12
+
13
+ ==== Tap::Support::Executable
14
+
15
+ http://tap.rubyforge.org/images/Executable.png
16
+
17
+ Executable extends objects allowing them to be enqued and run by an App. Executable objects specify a method that gets called upon execution; in essence Executable wraps this method and adds workflow behaviors like auditing and an <tt>on_complete</tt> block.
18
+
19
+ Tasks are constructed such that <tt>execute</tt> is their executable method. Task#execute simply provides hooks before and after forwarding control to the <tt>process</tt> method. Hence <tt>process</tt> is the standard method overridden in subclasses of Task.
20
+
21
+ ==== Tap::Support::Batchable
22
+
23
+ http://tap.rubyforge.org/images/Batchable.png
24
+
25
+ Tasks can be assembled into batches that all enque at the same time and share the same <tt>on_complete</tt> block. This behavior is very powerful, allowing workflow logic to be defined once but executed under multiple conditions. Task includes the Batchable module to facilitate batching.
26
+
27
+ ==== Tap::Support::Configurable
28
+
29
+ http://tap.rubyforge.org/images/Configurable.png
30
+
31
+ Configurable allows declaration of class configurations. Configurable classes have a <tt>configurations</tt> method accessing a {ClassConfiguration}[link:classes/Tap/Support/ClassConfiguration.html] which holds all declared configs, their default values, and metadata for transforming configurations into command line options (for example).
32
+
33
+ Instances of a Configurable class have a <tt>config</tt> method accessing a {InstanceConfiguration}[link:classes/Tap/Support/InstanceConfiguration.html] object. The instance configuration acts like a forwarding hash; read and write operations for declared configs get forwarded to class methods while undeclared configs are stored directly. The writer for a config may be defined through a block provided during config declaration. For instance:
34
+
35
+ class ConfigClass
36
+ include Tap::Support::Configurable
37
+
38
+ config :key, 'value' do |input|
39
+ input.upcase
40
+ end
41
+ end
42
+
43
+ Is basically the same as:
44
+
45
+ class RegularClass
46
+ attr_reader :key
47
+
48
+ def key=(input)
49
+ @key = input.upcase
50
+ end
51
+
52
+ def initialize
53
+ self.key = 'value'
54
+ end
55
+ end
56
+
57
+ As you can see here:
58
+
59
+ c = ConfigClass.new
60
+ c.key # => 'VALUE'
61
+
62
+ c.config[:key] = 'new value'
63
+ c.key # => 'NEW VALUE'
64
+
65
+ c.key = 'another value'
66
+ c.config[:key] # => 'ANOTHER VALUE'
67
+
68
+ This setup is both fast and convenient.
69
+
70
+ ==== Tap::Support::Validation
71
+
72
+ When configurations are set from the command line, the writer method will inevitably receive a string, whereas configurations set within code can receive any type of object. The {Validation}[link:classes/Tap/Support/Validation.html] module provides standard blocks for validating and transforming inputs, accessible through the <tt>c</tt> method (ex: <tt>c.integer</tt> or <tt>c.regexp</tt>). These blocks (generally) load string inputs as YAML and validate that the result is the correct class; non-string inputs are simply validated.
73
+
74
+ class ValidatingClass
75
+ include Tap::Support::Configurable
76
+
77
+ config :int, 1, &c.integer # assures the input is an integer
78
+ config :int_or_nil, 1, &c.integer_or_nil # integer or nil only
79
+ config :array, [], &c.array # you get the idea
80
+ end
81
+
82
+ vc = ValidatingClass.new
83
+
84
+ vc.array = [:a, :b, :c]
85
+ vc.array # => [:a, :b, :c]
86
+
87
+ vc.array = "[1, 2, 3]"
88
+ vc.array # => [1, 2, 3]
89
+
90
+ vc.array = "string" # !> ValidationError
91
+
92
+ Validation blocks sometimes imply metadata. For instance <tt>c.flag</tt> makes a config into a flag on the command line.
93
+
94
+ ==== Tap::Support::Lazydoc
95
+
96
+ http://tap.rubyforge.org/images/Lazydoc.png
97
+
98
+ Ah lazydoc. Lazydoc fits into the space between live code and code documentation. Lazydoc can scan a file (code or not) and pull out documentation into the object space where it can be utilized. Lazydoc uses a key-value syntax that is both invalid Ruby and easily hidden in RDoc. Looks like this:
99
+
100
+ # ::key value
101
+
102
+ Try the former line without the comment and a syntax error results. In RDoc, the syntax doesn't interfere with any of the standard documentation conventions and can be hidden with the use of a <tt>:startdoc:</tt> attribute (an extra space is added to <tt>:startdoc:</tt> so the line isn't actually hidden in this document):
103
+
104
+ # :start doc::key value
105
+
106
+ Lazydoc parses a constant name, the key, the value, and any comment following the value until a non-comment line or an end key. For example:
107
+
108
+ [lazydoc_file.rb]
109
+ # Name::Space::key value
110
+ #
111
+ # This documentation
112
+ # gets parsed.
113
+ #
114
+
115
+ # Name::Space::another another value
116
+ # This gets parsed.
117
+ # Name::Space::another-
118
+ #
119
+ # This does not.
120
+
121
+ require 'tap'
122
+
123
+ lazydoc = Tap::Support::Lazydoc[__FILE__]
124
+ lazydoc.resolve
125
+
126
+ lazydoc['Name::Space']['key'].to_s # => "This documentation gets parsed."
127
+ lazydoc['Name::Space']['another'].subject # => "another value"
128
+
129
+ Furthermore, Lazydoc allows living code to register lines that should get documented. These lines are parsed to echo what happens in RDoc.
130
+
131
+ [another_lazydoc_file.rb]
132
+ # documentation
133
+ # for the method
134
+ def method
135
+ end
136
+
137
+ require 'tap'
138
+
139
+ lazydoc = Tap::Support::Lazydoc[__FILE__]
140
+ code_comment = lazydoc.register(2)
141
+ lazydoc.resolve
142
+
143
+ code_comment.subject # => "def method"
144
+ code_comment.to_s # => "documentation for the method"
145
+
146
+ Tap uses Lazydoc to indicate when a file contains a Task (<tt>::manifest</tt>) or a generator (<tt>::generator</tt>), and for config documentation. Tap::Env uses this information to facilitate lookup and instantiation of task classes.
147
+
148
+ One note: when no constant name is specified for a Lazydoc key, it gets treated as a default for the whole file.
149
+
150
+ [lib/sample/task.rb]
151
+ # ::manifest sample task description
152
+ #
153
+ # This manifest is expected to apply to the Sample::Task class.
154
+ # If more than one task is defined in this file, or if Sample::Task
155
+ # is not defined by loading this file, Tap will run into trouble.
156
+
157
+ However, the best practice is to include the namespace explicitly.
158
+
159
+ === Tap::Task
160
+
161
+ http://tap.rubyforge.org/images/Task.png
162
+
163
+ Running a task through the tap executable instantiates a task class, configures it, enques it, and runs a Tap::App to get the inputs to the <tt>process</tt> method. Tasks do not have to be used this way; they are perfectly capable as objects in free-standing scripts.
164
+
165
+ Task instances can take a block that acts as a stand-in for <tt>process</tt>:
166
+
167
+ t = Tap::Task.new {|task| 1 + 2 }
168
+ t.process # => 3
169
+
170
+ t = Tap::Task.new {|task, x, y| x + y }
171
+ t.process(1, 2) # => 3
172
+
173
+ Tasks can be configured:
174
+
175
+ t1 = Tap::Task.new(:key => 'one') {|task, input| "#{input}:#{task.config[:key]}"}
176
+ t1.process('str') # => "str:one"
177
+
178
+ And batched:
179
+
180
+ t2 = t1.initialize_batch_obj(:key => 'two')
181
+ t3 = t1.initialize_batch_obj(:key => 'three')
182
+
183
+ t1.batch # => [t1, t2, t3]
184
+
185
+ Batched tasks enque together, and therefore run sequentially with the same inputs:
186
+
187
+ app = Tap::App.instance
188
+ app.enq(t1, 'str')
189
+ app.queue.to_a # => [[t1, ['str']], [t2, ['str']], [t3, ['str']]]
190
+ app.run
191
+
192
+ app.results(t1) # => ["str:one"]
193
+ app.results(t2) # => ["str:two"]
194
+ app.results(t3) # => ["str:three"]
195
+
196
+ Also, as a consequence of Task being Executable, the results have audit trails. In the audit trail, the tasks are identified by name (by default the name the underscored class name):
197
+
198
+ t1.name = 'task one'
199
+ t2.name = 'task two'
200
+ t3.name = 'task three'
201
+
202
+ app._results(t1,t2,t3).collect do |_result|
203
+ _result.to_s
204
+ end.join("---\n")
205
+ # =>
206
+ # o-[] 1
207
+ # o-[task one] "str:one"
208
+ # ---
209
+ # o-[] 1
210
+ # o-[task two] "str:two"
211
+ # ---
212
+ # o-[] 1
213
+ # o-[task three] "str:three"
214
+
215
+ Task instances can be joined into workflows. The workflow model used by Tap is very simple; when a task completes it calls its <tt>on_complete</tt> block to enque the next task (or tasks) in the workflow. Arbitrary workflow logic is allowed since there are no restrictions on what the <tt>on_complete</tt> block does. If a task has no <tt>on_complete</tt> block, App collects the unhandled results (as shown above) so they can be handled somewhere else. See below for more details.
216
+
217
+ === Tap::Workflow
218
+
219
+ http://tap.rubyforge.org/images/Workflow.png
220
+
221
+ Workflows are not Tasks but are constructed with the same modules as Task and work very similarly Tasks. Workflows have a <tt>workflow</tt> method which defines the entry and exit points for the workflow; there can be 1+ entry points and 0+ exit points. The enque method for a workflow enques all it's entry points, and when the <tt>on_complete</tt> block is set for a workflow, it is set for all exit points.
222
+
223
+ Like Tasks, Workflows can be configured, enqued, used in workflows, and subclassed.
224
+
225
+ == Apps
226
+
227
+ ==== Tap::Root
228
+
229
+ http://tap.rubyforge.org/images/Root.png
230
+
231
+ A Root represents the base of a directory structure. Roots allow you to alias directories and ease working with filepaths, basically allowing you to develop code for a conceptual directory structure that can be defined later.
232
+
233
+ root = Tap::Root.new '/path/to/root'
234
+ root.root # => '/path/to/root'
235
+ root['config'] # => '/path/to/root/config'
236
+ root.filepath('config', 'sample.yml') # => '/path/to/root/config/sampl.yml'
237
+
238
+ While simple, this ability to reference files using aliases is useful, powerful, and forms the basis of the Tap execution environment.
239
+
240
+ ==== Tap::Support::ExecutableQueue
241
+
242
+ http://tap.rubyforge.org/images/ExecutableQueue.png
243
+
244
+ Apps coordinate the execution of tasks through a queue. The queue is just a stack of Executable objects, basically methods, and the inputs to those methods; during a run the enqued methods are sequentially executed with the inputs.
245
+
246
+ Normally the methods execute one after the other on the main thread, however the methods can be set to multithread. In this case all sequential, multithreadable methods are executed on their own thread (up to a configurable maximum number of threads). A non-multithreadable method blocks until all these threads finish, and then the sequential, single thread execution resumes.
247
+
248
+ For instance, assuming non-multithreadable executables [a,b,c] and multithread executables [m1, m2, m3], a queue of [a, m1, m2, m3, b, c] executes like:
249
+
250
+ a... done
251
+ (m1, m2, m3)... done
252
+ b... done
253
+ c... done
254
+
255
+ ==== Tap::Support::Audit
256
+
257
+ Tap tracks inputs as they are modified by various tasks, again through Executable. At the end of a run, any individual result can be tracked back to it's original value with references to the source of each change in the value (ie the task or Executable). This auditing can be very useful when workflows diverge, as they often do.
258
+
259
+ Auditing is largely invisible except in <tt>on_complete</tt> blocks. <tt>on_complete</tt> blocks receive the audited results so that this information can be used, as needed, to make decisions.
260
+
261
+ t = Task.new
262
+ t.on_complete do |_result| # _result is an Audit instance
263
+ _result._current # the current value
264
+ _result._original # the original value
265
+ end
266
+
267
+ To help indicate when a result is actually a result and when it is an audit, Tap uses a convention whereby a leading underscore signals auditing is involved.
268
+
269
+ ==== Tap::Support::Aggregator
270
+
271
+ When a task completes, it executes it's <tt>on_complete</tt> block to handle the results, perhaps passing them on to other tasks. Aggregators collect results when no <tt>on_complete</tt> block is specified. Results are collected per-task into an array; a single task executed many times will have it's results aggregated into this single array.
272
+
273
+ === Tap::App
274
+
275
+ http://tap.rubyforge.org/images/App.png
276
+
277
+ Instances of Tap::App coordinate the execution of tasks. Apps are basically a subclass of Root with an ExecutableQueue and Aggregator. Task initialization requires an App, which is by default Tap::App.instance. Tasks use their app for logging, checks, and to enque themselves. Normally a script will only need and use a single instance (often Tap::App.instance), but there is no reason why multiple instances could not be used.
278
+
279
+ log = StringIO.new
280
+ app = Tap::App.instance
281
+ app.logger = Logger.new(log)
282
+
283
+ t = Tap::Task.new
284
+ t.log 'action', 'to app'
285
+ log.string # => " I[15:21:23] action to app\n"
286
+
287
+ t.enq(1)
288
+ t.enq(2,3)
289
+ app.queue.to_a # => [[t, [1]], [t, [2,3]]
290
+
291
+ Apps also coordinate the creation of standard workflow patterns like sequence, fork, and merge. These methods set <tt>on_complete</tt> blocks for the input tasks.
292
+
293
+ t1 = Tap.task('t1') {|t| 'hellO'}
294
+ t2 = Tap.task('t2') {|t, input| input + ' woRld' }
295
+ t3 = Tap.task('t3') {|t, input| input.downcase }
296
+ t4 = Tap.task('t4') {|t, input| input.upcase }
297
+ t5 = Tap.task('t5') {|t, input| input + "!" }
298
+
299
+ # sequence t1, t2
300
+ app.sequence(t1, t2)
301
+
302
+ # fork t2 results to t3 and t4
303
+ app.fork(t2, t3, t4)
304
+
305
+ # unsynchronized merge of t3 and t4 into t5
306
+ app.merge(t5, t3, t4)
307
+
308
+ app.enq(t1)
309
+ app.run
310
+
311
+ app.results(t5) # => ["hello world!", "HELLO WORLD!"]
312
+
313
+ As shown above, aggregated results may be accessed by task through the <tt>results</tt> method. To access the audited results, use <tt>_results</tt>:
314
+
315
+ app._results(t5).collect do |_result|
316
+ _result.to_s
317
+ end.join("---\n")
318
+ # =>
319
+ # o-[t1] "hellO"
320
+ # o-[t2] "hellO woRld"
321
+ # o-[t3] "hello world"
322
+ # o-[t5] "hello world!"
323
+ # ----
324
+ # o-[t1] "hellO"
325
+ # o-[t2] "hellO woRld"
326
+ # o-[t4] "HELLO WORLD"
327
+ # o-[t5] "HELLO WORLD!"
328
+
329
+ == Envs
330
+
331
+ ==== Tap::Env
332
+
333
+ http://tap.rubyforge.org/images/Env.png
334
+
335
+ Environments are still under construction. Basically a wrapper for a Root, Envs define methods to generate manifests for a type of file-based resource (tasks, generators, etc). Furthermore they provide methods to uniquely identify the resource by path or, more specifically, minimized base paths. In this directory structure:
336
+
337
+ path
338
+ `- to
339
+ |- another
340
+ | `- file.rb
341
+ |- file-0.1.0.rb
342
+ |- file-0.2.0.rb
343
+ `- file.rb
344
+
345
+ The minimal paths that uniquely identify these files are (respectively):
346
+
347
+ 'another/file'
348
+ 'file-0.1.0'
349
+ 'file-0.2.0'
350
+ 'file.rb'
351
+
352
+ Envs facilitate mapping the minimal path, which might be provided by the command line, to the actual path, and hence to the resource. Envs can be nested so that manifests span multiple directories. Indeed, this is how tap accesses tasks and generators within gems; the gem directories are initialized as Envs and nested within the Env for the working directory.
353
+
354
+ http://tap.rubyforge.org/images/Nested-Env.png
355
+
356
+ To prevent conflicts between similarly-named resources under two Envs, Env allows selection of Envs, also by minimized paths. At present this is difficult to illustrate in a code snippit.
357
+
358
+ --
359
+ ==== Run Env
360
+ http://tap.rubyforge.org/images/Run-Env.png
361
+ ++
362
+