bahuvrihi-tap 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History +69 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +119 -0
  4. data/bin/tap +114 -0
  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 +32 -0
  13. data/lib/tap/app.rb +720 -0
  14. data/lib/tap/constants.rb +8 -0
  15. data/lib/tap/env.rb +640 -0
  16. data/lib/tap/file_task.rb +547 -0
  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 +21 -0
  21. data/lib/tap/generator/generators/command/templates/command.erb +32 -0
  22. data/lib/tap/generator/generators/config/config_generator.rb +26 -0
  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 +27 -0
  26. data/lib/tap/generator/generators/file_task/templates/file.txt +11 -0
  27. data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
  28. data/lib/tap/generator/generators/file_task/templates/task.erb +33 -0
  29. data/lib/tap/generator/generators/file_task/templates/test.erb +29 -0
  30. data/lib/tap/generator/generators/root/root_generator.rb +55 -0
  31. data/lib/tap/generator/generators/root/templates/Rakefile +86 -0
  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 +3 -0
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +5 -0
  36. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +27 -0
  38. data/lib/tap/generator/generators/task/templates/task.erb +14 -0
  39. data/lib/tap/generator/generators/task/templates/test.erb +21 -0
  40. data/lib/tap/generator/manifest.rb +14 -0
  41. data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
  42. data/lib/tap/patches/rake/testtask.rb +55 -0
  43. data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
  44. data/lib/tap/patches/ruby19/parsedate.rb +16 -0
  45. data/lib/tap/root.rb +581 -0
  46. data/lib/tap/support/aggregator.rb +55 -0
  47. data/lib/tap/support/assignments.rb +172 -0
  48. data/lib/tap/support/audit.rb +418 -0
  49. data/lib/tap/support/batchable.rb +47 -0
  50. data/lib/tap/support/batchable_class.rb +107 -0
  51. data/lib/tap/support/class_configuration.rb +194 -0
  52. data/lib/tap/support/command_line.rb +98 -0
  53. data/lib/tap/support/comment.rb +270 -0
  54. data/lib/tap/support/configurable.rb +114 -0
  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 +111 -0
  61. data/lib/tap/support/executable_queue.rb +82 -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 +39 -0
  68. data/lib/tap/support/shell_utils.rb +71 -0
  69. data/lib/tap/support/summary.rb +30 -0
  70. data/lib/tap/support/tdoc.rb +404 -0
  71. data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
  72. data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
  73. data/lib/tap/support/templater.rb +180 -0
  74. data/lib/tap/support/validation.rb +410 -0
  75. data/lib/tap/support/versions.rb +97 -0
  76. data/lib/tap/task.rb +259 -0
  77. data/lib/tap/tasks/dump.rb +56 -0
  78. data/lib/tap/tasks/rake.rb +93 -0
  79. data/lib/tap/test.rb +37 -0
  80. data/lib/tap/test/env_vars.rb +29 -0
  81. data/lib/tap/test/file_methods.rb +377 -0
  82. data/lib/tap/test/script_methods.rb +144 -0
  83. data/lib/tap/test/subset_methods.rb +420 -0
  84. data/lib/tap/test/tap_methods.rb +237 -0
  85. data/lib/tap/workflow.rb +187 -0
  86. metadata +145 -0
@@ -0,0 +1,97 @@
1
+ module Tap
2
+ module Support
3
+
4
+ # Version provides methods for adding, removing, and incrementing versions
5
+ # at the end of filepaths. Versions are all formatted like:
6
+ # 'filepath-version.extension'.
7
+ #
8
+ module Versions
9
+
10
+ # Adds a version to the filepath. Versioned filepaths follow the format:
11
+ # 'path-version.extension'. If no version is specified, then the filepath
12
+ # is returned.
13
+ #
14
+ # version("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
15
+ #
16
+ def version(path, version)
17
+ version = version.to_s.strip
18
+ if version.empty?
19
+ path
20
+ else
21
+ extname = File.extname(path)
22
+ path.chomp(extname) + '-' + version + extname
23
+ end
24
+ end
25
+
26
+ # Increments the version of the filepath by the specified increment.
27
+ #
28
+ # increment("path/to/file-1.0.txt", "0.0.1") # => "path/to/file-1.0.1.txt"
29
+ # increment("path/to/file.txt", 1.0) # => "path/to/file-1.0.txt"
30
+ #
31
+ def increment(path, increment)
32
+ path, version = deversion(path)
33
+
34
+ # split the version and increment into integer arrays of equal length
35
+ increment, version = [increment, version].collect do |vstr|
36
+ begin
37
+ vstr.to_s.split(/\./).collect {|v| v.to_i}
38
+ rescue
39
+ raise "Bad version or increment: #{vstr}"
40
+ end
41
+ end
42
+ version.concat Array.new(increment.length - version.length, 0) if increment.length > version.length
43
+
44
+ # add the increment to version
45
+ 0.upto(version.length-1) do |i|
46
+ version[i] += (increment[i] || 0)
47
+ end
48
+
49
+ self.version(path, version.join("."))
50
+ end
51
+
52
+ # Splits the version from the input path, then returns the path and version.
53
+ # If no version is specified, then the returned version will be nil.
54
+ #
55
+ # deversion("path/to/file-1.0.txt") # => ["path/to/file.txt", "1.0"]
56
+ # deversion("path/to/file.txt") # => ["path/to/file.txt", nil]
57
+ #
58
+ def deversion(path)
59
+ extname = File.extname(path)
60
+ extname = '' if extname =~ /^\.\d+$/
61
+ path =~ /^(.*)-(\d(\.?\d)*)#{extname}$/ ? [$1 + extname, $2] : [path, nil]
62
+ end
63
+
64
+ # A <=> comparison for versions. compare_versions can take strings,
65
+ # integers, or even arrays representing the parts of a version.
66
+ #
67
+ # compare_versions("1.0.0", "0.9.9") # => 1
68
+ # compare_versions(1.1, 1.1) # => 0
69
+ # compare_versions([0,9], [0,9,1]) # => -1
70
+ def compare_versions(a,b)
71
+ a, b = [a,b].collect {|item| to_integer_array(item) }
72
+
73
+ # equalize the lengths of the integer arrays
74
+ d = b.length - a.length
75
+ case
76
+ when d < 0 then b.concat Array.new(-d, 0)
77
+ when d > 0 then a.concat Array.new(d, 0)
78
+ end
79
+
80
+ a <=> b
81
+ end
82
+
83
+ private
84
+
85
+ # Converts an input argument (typically a string or an array)
86
+ # to an array of integers. Splits version string on "."
87
+ def to_integer_array(arg)
88
+ arr = case arg
89
+ when Array then arg
90
+ else arg.to_s.split('.')
91
+ end
92
+ arr.collect {|i| i.to_i}
93
+ end
94
+
95
+ end
96
+ end
97
+ end
data/lib/tap/task.rb ADDED
@@ -0,0 +1,259 @@
1
+ require 'tap/support/framework'
2
+
3
+ module Tap
4
+
5
+ # Tasks are the basic organizational unit of Tap. Tasks provide
6
+ # a standard backbone for creating the working parts of an application
7
+ # by facilitating configuration, batched execution of methods, and
8
+ # documentation.
9
+ #
10
+ # The functionality of Task is built from several base modules:
11
+ # - Tap::Support::Batchable
12
+ # - Tap::Support::Configurable
13
+ # - Tap::Support::Executable
14
+ #
15
+ # Tap::Workflow is built on the same foundations; the sectons on
16
+ # configuration and batching apply equally to Workflows as Tasks.
17
+ #
18
+ # === Task Definition
19
+ #
20
+ # Tasks are instantiated with a task block; when the task is run
21
+ # the block gets called with the enqued inputs. As such, the block
22
+ # should specify the same number of inputs as you enque (plus the
23
+ # task itself, which is a standard input).
24
+ #
25
+ # no_inputs = Task.new {|task| }
26
+ # one_input = Task.new {|task, input| }
27
+ # mixed_inputs = Task.new {|task, a, b, *args| }
28
+ #
29
+ # no_inputs.enq
30
+ # one_input.enq(:a)
31
+ # mixed_inputs.enq(:a, :b)
32
+ # mixed_inputs.enq(:a, :b, 1, 2, 3)
33
+ #
34
+ # Subclasses of Task specify executable code by overridding the process
35
+ # method. In this case the number of enqued inputs should correspond to
36
+ # process (passing the task would be redundant).
37
+ #
38
+ # class NoInput < Tap::Task
39
+ # def process() end
40
+ # end
41
+ #
42
+ # class OneInput < Tap::Task
43
+ # def process(input) end
44
+ # end
45
+ #
46
+ # class MixedInputs < Tap::Task
47
+ # def process(a, b, *args) end
48
+ # end
49
+ #
50
+ # NoInput.new.enq
51
+ # OneInput.new.enq(:a)
52
+ # MixedInputs.new.enq(:a, :b)
53
+ # MixedInputs.new.enq(:a, :b, 1, 2, 3)
54
+ #
55
+ # === Configuration
56
+ #
57
+ # Tasks are configurable. By default each task will be configured
58
+ # with the default class configurations, which can be set when the
59
+ # class is defined.
60
+ #
61
+ # class ConfiguredTask < Tap::Task
62
+ # config :one, 'one'
63
+ # config :two, 'two'
64
+ # end
65
+ #
66
+ # t = ConfiguredTask.new
67
+ # t.name # => "configured_task"
68
+ # t.config # => {:one => 'one', :two => 'two'}
69
+ #
70
+ # Configurations can be validated or processed using an optional
71
+ # block. Tap::Support::Validation pre-packages several common
72
+ # validation/processing blocks, and can be accessed through the
73
+ # class method 'c':
74
+ #
75
+ # class ValidatingTask < Tap::Task
76
+ # # string config validated to be a string
77
+ # config :string, 'str', &c.check(String)
78
+ #
79
+ # # integer config; string inputs are converted using YAML
80
+ # config :integer, 1, &c.yaml(Integer)
81
+ # end
82
+ #
83
+ # t = ValidatingTask.new
84
+ # t.string = 1 # !> ValidationError
85
+ # t.integer = 1.1 # !> ValidationError
86
+ #
87
+ # t.integer = "1"
88
+ # t.integer == 1 # => true
89
+ #
90
+ # Tasks have a name that gets used in auditing, and as a relative
91
+ # filepath to find associated files (for instance config files).
92
+ # By default the task name is based on the task class, such that
93
+ # Tap::Task has the default name 'tap/task'. Configurations
94
+ # and custom names can be provided when a task is initialized.
95
+ #
96
+ # t = ConfiguredTask.new({:one => 'ONE', :three => 'three'}, "example")
97
+ # t.name # => "example"
98
+ # t.config # => {:one => 'ONE', :two => 'two', :three => 'three'}
99
+ #
100
+ # === Batches
101
+ #
102
+ # Tasks can be assembled into batches that enque and execute collectively.
103
+ # Batched tasks are often alternatively-configured derivatives of one
104
+ # parent task, although they can be manually assembled using Task.batch.
105
+ #
106
+ # app = Tap::App.instance
107
+ # t1 = Tap::Task.new(:key => 'one') do |task, input|
108
+ # input + task.config[:key]
109
+ # end
110
+ # t1.batch # => [t1]
111
+ #
112
+ # t2 = t1.initialize_batch_obj(:key => 'two')
113
+ # t1.batch # => [t1, t2]
114
+ # t2.batch # => [t1, t2]
115
+ #
116
+ # t1.enq 't1_by_'
117
+ # t2.enq 't2_by_'
118
+ # app.run
119
+ #
120
+ # app.results(t1) # => ["t1_by_one", "t2_by_one"]
121
+ # app.results(t2) # => ["t1_by_two", "t2_by_two"]
122
+ #
123
+ # Here the results reflects that t1 and t2 were run in succession with the
124
+ # input to t1, and then the input to t2.
125
+ #
126
+ # === Subclassing
127
+ # Tasks can be subclassed normally, with one reminder related to batching.
128
+ #
129
+ # Batched tasks are generated by duplicating an existing instance, hence
130
+ # all instance variables will point to the same object in the batched
131
+ # and original task. At times (as with configurations), this is
132
+ # undesirable; the batched task should have it's own copy of an
133
+ # instance variable.
134
+ #
135
+ # In these cases, the <tt>initialize_copy</tt> should be overridden
136
+ # and should re-initialize the appropriate variables. Be sure to call
137
+ # super to invoke the default <tt>initialize_copy</tt>:
138
+ #
139
+ # class SubclassTask < Tap::Task
140
+ # attr_accessor :array
141
+ # def initialize(*args)
142
+ # @array = []
143
+ # super
144
+ # end
145
+ #
146
+ # def initialize_copy(orig)
147
+ # @array = orig.array.dup
148
+ # super
149
+ # end
150
+ # end
151
+ #
152
+ # t1 = SubclassTask.new
153
+ # t2 = t1.initialize_batch_obj
154
+ # t1.array == t2.array # => true
155
+ # t1.array.object_id == t2.array.object_id # => false
156
+ #
157
+ class Task
158
+ include Support::Executable
159
+ include Support::Framework
160
+
161
+ attr_reader :task_block
162
+
163
+ def initialize(config={}, name=nil, app=App.instance, &task_block)
164
+ super(config, name, app)
165
+
166
+ @task_block = (task_block == nil ? default_task_block : task_block)
167
+ @multithread = false
168
+ @on_complete_block = nil
169
+ @_method_name = :execute
170
+ end
171
+
172
+ # Enqueues self and self.batch to app with the inputs.
173
+ # The number of inputs provided should match the number
174
+ # of inputs specified by the arity of the _method_name method.
175
+ def enq(*inputs)
176
+ app.queue.enq(self, inputs)
177
+ end
178
+
179
+ batch_function :enq, :multithread=
180
+ batch_function(:on_complete) {}
181
+
182
+ # Executes self with the given inputs. Execute provides hooks for subclasses
183
+ # to insert standard execution code: before_execute, on_execute_error,
184
+ # and after_execute. Override any/all of these methods as needed.
185
+ #
186
+ # Execute passes the inputs to process and returns the result.
187
+ def execute(*inputs)
188
+ before_execute
189
+ begin
190
+ result = process(*inputs)
191
+ rescue
192
+ on_execute_error($!)
193
+ end
194
+ after_execute
195
+
196
+ result
197
+ end
198
+
199
+ # The method for processing inputs into outputs. Override this method in
200
+ # subclasses to provide class-specific process logic. The number of
201
+ # arguments specified by process corresponds to the number of arguments
202
+ # the task should have when enqued.
203
+ #
204
+ # class TaskWithTwoInputs < Tap::Task
205
+ # def process(a, b)
206
+ # [b,a]
207
+ # end
208
+ # end
209
+ #
210
+ # t = TaskWithTwoInputs.new
211
+ # t.enq(1,2).enq(3,4)
212
+ # t.app.run
213
+ # t.app.results(t) # => [[2,1], [4,3]]
214
+ #
215
+ # By default process passes self and the input(s) to the task_block
216
+ # provided during initialization. In this case the task block dictates
217
+ # the number of arguments enq should receive. Simply returns the inputs
218
+ # if no task_block is set.
219
+ #
220
+ # # two arguments in addition to task are specified
221
+ # # so this Task must be enqued with two inputs...
222
+ # t = Task.new {|task, a, b| [b,a] }
223
+ # t.enq(1,2).enq(3,4)
224
+ # t.app.run
225
+ # t.app.results(t) # => [[2,1], [4,3]]
226
+ #
227
+ def process(*inputs)
228
+ return inputs if task_block == nil
229
+ inputs.unshift(self)
230
+
231
+ arity = task_block.arity
232
+ n = inputs.length
233
+ unless n == arity || (arity < 0 && (-1-n) <= arity)
234
+ raise ArgumentError.new("wrong number of arguments (#{n} for #{arity})")
235
+ end
236
+
237
+ task_block.call(*inputs)
238
+ end
239
+
240
+ protected
241
+
242
+ # Hook to set a default task block. By default, nil.
243
+ def default_task_block
244
+ nil
245
+ end
246
+
247
+ # Hook to execute code before inputs are processed.
248
+ def before_execute() end
249
+
250
+ # Hook to execute code after inputs are processed.
251
+ def after_execute() end
252
+
253
+ # Hook to handle unhandled errors from processing inputs on a task level.
254
+ # By default on_execute_error simply re-raises the unhandled error.
255
+ def on_execute_error(err)
256
+ raise err
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,56 @@
1
+ module Tap
2
+ module Tasks
3
+ # :startdoc::manifest the default dump task
4
+ #
5
+ # A dump task to print application results to a file or IO. The results are
6
+ # printed in a format allowing dumped results to be reloaded and used as
7
+ # inputs to other tasks. See Tap::Load for more details.
8
+ #
9
+ # Often dump is used as the final task in a round of tasks; if no filepath is
10
+ # specified, the results are printed to stdout.
11
+ #
12
+ # % tap run -- [your tasks] --+ dump FILEPATH
13
+ #
14
+ class Dump < Tap::FileTask
15
+
16
+ config :date_format, '%Y-%m-%d %H:%M:%S' # the date format
17
+ config :audit, true, &c.switch # include the audit trails
18
+ config :date, true, &c.switch # include a date
19
+
20
+ def process(target=$stdout)
21
+ case target
22
+ when IO then dump_to(target)
23
+ else
24
+ log_basename(:dump, target)
25
+ prepare(target)
26
+ File.open(target, "wb") {|file| dump_to(file) }
27
+ end
28
+ end
29
+
30
+ # Dumps the current results in app.aggregator to the io.
31
+ # The dump will include the result audits and a date,
32
+ # as specified in config.
33
+ def dump_to(io)
34
+ trails = []
35
+ results = {}
36
+ app.aggregator.to_hash.each_pair do |src, _results|
37
+ name = src.respond_to?(:name) ? src.name : ''
38
+
39
+ results["#{name} (#{src.object_id})"] = _results.collect {|_audit| _audit._current }
40
+ _results.each {|_audit| trails << _audit._to_s }
41
+ end
42
+
43
+ if audit
44
+ io.puts "# audit:"
45
+ trails.each {|trail| io.puts "# #{trail.gsub("\n", "\n# ")}"}
46
+ end
47
+
48
+ if date
49
+ io.puts "# date: #{Time.now.strftime(date_format)}"
50
+ end
51
+
52
+ YAML::dump(results, io)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,93 @@
1
+ require 'rake'
2
+
3
+ module Tap
4
+ module Tasks
5
+ # :startdoc::manifest run rake tasks
6
+ #
7
+ # Simply enques the specified rake task(s) for execution. Useful when a
8
+ # rake task needs to be executed within a workflow. For example these
9
+ # are equivalent:
10
+ #
11
+ # % tap run -- rake test
12
+ # % rake test
13
+ #
14
+ # The only exeception is in the use of the --help option. Use --rake-help
15
+ # to access the rake help, and --help to access this help.
16
+ #
17
+ class Rake < Tap::Task
18
+
19
+ # Modifies Rake::Application by adding a hook to the standard_exception_handling
20
+ # method. This allows more fine-grained use of Rake::Applications by Tap.
21
+ module Application
22
+ def enq_top_level(app)
23
+ # takes the place of rake.top_level
24
+ if options.show_tasks
25
+ display_tasks_and_comments
26
+ exit
27
+ elsif options.show_prereqs
28
+ display_prerequisites
29
+ exit
30
+ else
31
+ top_level_tasks.each do |task_string|
32
+ name, args = parse_task_string(task_string)
33
+ task = self[name]
34
+
35
+ unless task.kind_of?(Tap::Support::Executable)
36
+ Tap::Support::Executable.initialize(task, :invoke)
37
+ end
38
+
39
+ app.enq(task, *args)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ class << self
46
+
47
+ # Overrides Tap::Support::FrameworkClass#instantiate to do
48
+ # nothing so that all args get passed forward to rake.
49
+ def instantiate(argv, app=Tap::App.instance) # => instance, argv
50
+ if argv.include?('--help')
51
+ puts help
52
+ exit
53
+ end
54
+ [new({}, default_name, app), argv.collect {|arg| arg == '--rake-help' ? '--help' : arg}]
55
+ end
56
+ end
57
+
58
+ #--
59
+ # def on_complete(override=false, &block)
60
+ # @rake_tasks.each do |task|
61
+ # task.on_complete(override, &block)
62
+ # end
63
+ # end
64
+ #++
65
+
66
+ def enq(*argv)
67
+ rake = ::Rake.application
68
+ unless rake.kind_of?(Application)
69
+ rake.extend Application
70
+ end
71
+
72
+ # run as if from command line using argv
73
+ current_argv = ARGV.dup
74
+ begin
75
+ ARGV.clear
76
+ ARGV.concat(argv)
77
+
78
+ # now follow the same protocol as
79
+ # in run, handling options
80
+ rake.init
81
+ rake.load_rakefile
82
+ ensure
83
+ ARGV.clear
84
+ ARGV.concat(current_argv)
85
+ end
86
+
87
+ rake.enq_top_level(app)
88
+
89
+ nil
90
+ end
91
+ end
92
+ end
93
+ end