tap 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Basic Overview +151 -0
- data/Command Reference +99 -0
- data/History +24 -0
- data/MIT-LICENSE +1 -1
- data/README +29 -57
- data/Rakefile +30 -37
- data/Tutorial +243 -191
- data/bin/tap +66 -35
- data/lib/tap.rb +47 -29
- data/lib/tap/app.rb +700 -342
- data/lib/tap/{script → cmd}/console.rb +0 -0
- data/lib/tap/{script → cmd}/destroy.rb +0 -0
- data/lib/tap/{script → cmd}/generate.rb +0 -0
- data/lib/tap/cmd/run.rb +156 -0
- data/lib/tap/constants.rb +4 -0
- data/lib/tap/dump.rb +57 -0
- data/lib/tap/env.rb +316 -0
- data/lib/tap/file_task.rb +106 -109
- data/lib/tap/generator.rb +4 -1
- data/lib/tap/generator/generators/command/USAGE +6 -0
- data/lib/tap/generator/generators/command/command_generator.rb +17 -0
- data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
- data/lib/tap/generator/generators/config/USAGE +21 -0
- data/lib/tap/generator/generators/config/config_generator.rb +17 -7
- data/lib/tap/generator/generators/file_task/USAGE +3 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
- data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
- data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
- data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
- data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
- data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
- data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
- data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
- data/lib/tap/generator/generators/task/USAGE +3 -0
- data/lib/tap/generator/generators/task/task_generator.rb +18 -5
- data/lib/tap/generator/generators/task/templates/task.erb +7 -12
- data/lib/tap/generator/generators/task/templates/test.erb +10 -11
- data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
- data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
- data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
- data/lib/tap/patches/rake/testtask.rb +55 -0
- data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
- data/lib/tap/patches/ruby19/parsedate.rb +16 -0
- data/lib/tap/root.rb +172 -67
- data/lib/tap/script.rb +70 -336
- data/lib/tap/support/aggregator.rb +55 -0
- data/lib/tap/support/audit.rb +281 -280
- data/lib/tap/support/batchable.rb +59 -0
- data/lib/tap/support/class_configuration.rb +279 -0
- data/lib/tap/support/configurable.rb +92 -0
- data/lib/tap/support/configurable_methods.rb +296 -0
- data/lib/tap/support/executable.rb +98 -0
- data/lib/tap/support/executable_queue.rb +82 -0
- data/lib/tap/support/logger.rb +9 -15
- data/lib/tap/support/rake.rb +43 -54
- data/lib/tap/support/run_error.rb +32 -13
- data/lib/tap/support/shell_utils.rb +47 -0
- data/lib/tap/support/tdoc.rb +9 -8
- data/lib/tap/support/tdoc/config_attr.rb +40 -16
- data/lib/tap/support/validation.rb +77 -0
- data/lib/tap/support/versions.rb +36 -36
- data/lib/tap/task.rb +276 -482
- data/lib/tap/test.rb +20 -261
- data/lib/tap/test/env_vars.rb +7 -5
- data/lib/tap/test/file_methods.rb +126 -121
- data/lib/tap/test/subset_methods.rb +86 -45
- data/lib/tap/test/tap_methods.rb +271 -0
- data/lib/tap/workflow.rb +174 -46
- data/test/app/config/another/task.yml +1 -0
- data/test/app/config/erb.yml +2 -1
- data/test/app/config/some/task.yml +1 -0
- data/test/app/config/template.yml +2 -6
- data/test/app_test.rb +1241 -1008
- data/test/env/test_configure/recurse_a.yml +2 -0
- data/test/env/test_configure/recurse_b.yml +2 -0
- data/test/env/test_configure/tap.yml +23 -0
- data/test/env/test_load_env_config/dir/tap.yml +3 -0
- data/test/env/test_load_env_config/recurse_a.yml +2 -0
- data/test/env/test_load_env_config/recurse_b.yml +2 -0
- data/test/env/test_load_env_config/tap.yml +3 -0
- data/test/env_test.rb +198 -0
- data/test/file_task_test.rb +70 -53
- data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
- data/test/root_test.rb +621 -454
- data/test/script_test.rb +38 -174
- data/test/support/aggregator_test.rb +99 -0
- data/test/support/audit_test.rb +409 -416
- data/test/support/batchable_test.rb +74 -0
- data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
- data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
- data/test/support/configurable_test.rb +295 -0
- data/test/support/executable_queue_test.rb +103 -0
- data/test/support/executable_test.rb +38 -0
- data/test/support/logger_test.rb +17 -17
- data/test/support/rake_test.rb +4 -2
- data/test/support/shell_utils_test.rb +24 -0
- data/test/support/tdoc_test.rb +265 -258
- data/test/support/validation_test.rb +54 -0
- data/test/support/versions_test.rb +38 -38
- data/test/tap_test_helper.rb +19 -5
- data/test/tap_test_suite.rb +5 -2
- data/test/task_base_test.rb +13 -104
- data/test/task_syntax_test.rb +300 -0
- data/test/task_test.rb +258 -381
- data/test/test/env_vars_test.rb +40 -40
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
- data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
- data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
- data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
- data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
- data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
- data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
- data/test/test/file_methods_doc_test.rb +29 -0
- data/test/test/file_methods_test.rb +214 -143
- data/test/test/subset_methods_test.rb +111 -115
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
- data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
- data/test/test/tap_methods_test.rb +399 -0
- data/test/workflow_test.rb +101 -91
- metadata +86 -70
- data/lib/tap/generator/generators/package/package_generator.rb +0 -38
- data/lib/tap/generator/generators/package/templates/package.erb +0 -186
- data/lib/tap/generator/generators/script/USAGE +0 -0
- data/lib/tap/generator/generators/script/script_generator.rb +0 -17
- data/lib/tap/script/run.rb +0 -154
- data/lib/tap/support/batch_queue.rb +0 -162
- data/lib/tap/support/combinator.rb +0 -114
- data/lib/tap/support/task_configuration.rb +0 -169
- data/lib/tap/support/template.rb +0 -81
- data/lib/tap/support/templater.rb +0 -155
- data/lib/tap/version.rb +0 -4
- data/test/app/config/addition_template.yml +0 -6
- data/test/app_class_test.rb +0 -33
- data/test/check/binding_eval.rb +0 -23
- data/test/check/define_method_check.rb +0 -22
- data/test/check/dependencies_check.rb +0 -175
- data/test/check/inheritance_check.rb +0 -22
- data/test/support/batch_queue_test.rb +0 -320
- data/test/support/combinator_test.rb +0 -249
- data/test/support/template_test.rb +0 -122
- data/test/support/templater/erb.txt +0 -2
- data/test/support/templater/erb.yml +0 -2
- data/test/support/templater/somefile.txt +0 -2
- data/test/support/templater_test.rb +0 -192
- data/test/task/config/template.yml +0 -4
- data/test/task_class_test.rb +0 -170
- data/test/task_execute_test.rb +0 -262
- data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
- data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
- data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
- data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
- data/test/test/file_methods/test_file_compare/expected/output_1.txt +0 -3
- data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
- data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
- data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
- data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods/test_yml_compare/expected/output_1.yml +0 -6
- data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
- data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
- data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
- data/test/test_test.rb +0 -373
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Tap
|
|
2
|
+
module Support
|
|
3
|
+
|
|
4
|
+
# Batchable encapsulates the methods used to support batching
|
|
5
|
+
# of tasks. See the 'Batches' section in the Tap::Task
|
|
6
|
+
# documentation for more details on how Batchable works in
|
|
7
|
+
# practice.
|
|
8
|
+
module Batchable
|
|
9
|
+
|
|
10
|
+
# Merges the batches for the specified objects. All objects
|
|
11
|
+
# sharing the individual object batches will be affected, even
|
|
12
|
+
# if they are not listed explicitly as an input.
|
|
13
|
+
#
|
|
14
|
+
# t1 = Tap::Task.new
|
|
15
|
+
# t2 = Tap::Task.new
|
|
16
|
+
# t3 = t2.initialize_batch_obj
|
|
17
|
+
#
|
|
18
|
+
# Batchable.batch(t1, t2)
|
|
19
|
+
# t3.batch # => [t1,t2,t3]
|
|
20
|
+
#
|
|
21
|
+
# Returns the new batch.
|
|
22
|
+
def self.batch(*tasks)
|
|
23
|
+
merged = []
|
|
24
|
+
batches = tasks.collect {|task| task.batch }.uniq
|
|
25
|
+
batches.each do |batch|
|
|
26
|
+
merged.concat(batch)
|
|
27
|
+
batch.clear
|
|
28
|
+
end
|
|
29
|
+
merged.uniq!
|
|
30
|
+
batches.each {|batch| batch.concat(merged) }
|
|
31
|
+
merged
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The object batch. (must be initializd by classes that
|
|
35
|
+
# include Batchable)
|
|
36
|
+
attr_reader :batch
|
|
37
|
+
|
|
38
|
+
# Returns true if the batch size is greater than one
|
|
39
|
+
# (the one being self).
|
|
40
|
+
def batched?
|
|
41
|
+
batch.length > 1
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns the index of the self in batch.
|
|
45
|
+
def batch_index
|
|
46
|
+
batch.index(self)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Initializes a new batch object and adds the object to batch.
|
|
50
|
+
# The object will be self if batch is empty (ie for the first
|
|
51
|
+
# call) or a duplicate of self if not.
|
|
52
|
+
def initialize_batch_obj
|
|
53
|
+
obj = (batch.empty? ? self : self.dup)
|
|
54
|
+
batch << obj
|
|
55
|
+
obj
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
autoload(:GetOptLong, 'getoptlong')
|
|
2
|
+
|
|
3
|
+
module Tap
|
|
4
|
+
module Support
|
|
5
|
+
# == UNDER CONSTRUCTION
|
|
6
|
+
#--
|
|
7
|
+
#
|
|
8
|
+
# ClassConfiguration holds the class configurations defined in a Tap::Task.
|
|
9
|
+
# The configurations are stored as an array of declarations_array like:
|
|
10
|
+
# [name, default, msg, declaration_class]. In addition, ClassConfiguration
|
|
11
|
+
# collapse the array of declarations_array into a hash, which acts as the default
|
|
12
|
+
# task configuration.
|
|
13
|
+
#
|
|
14
|
+
# Storing metadata about the configurations, such as the declaration_class,
|
|
15
|
+
# allows the creation of more user-friendly configuration files and facilitates
|
|
16
|
+
# incorporation into command-line applications.
|
|
17
|
+
#
|
|
18
|
+
# In general, users will not have to interact with ClassConfigurations directly.
|
|
19
|
+
#
|
|
20
|
+
# === Example
|
|
21
|
+
#
|
|
22
|
+
# class BaseTask < Tap::Configurable
|
|
23
|
+
# class_configurations [:one, 1]
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# BaseTask.configurations.hash # => {:one => 1}
|
|
27
|
+
#
|
|
28
|
+
# class SubTask < BaseTask
|
|
29
|
+
# class_configurations(
|
|
30
|
+
# [:one, 'one', "the first configuration"],
|
|
31
|
+
# [:two, 'two', "the second configuration"])
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# SubTask.configurations.hash # => {:one => 'one', :two => 'two'}
|
|
35
|
+
#
|
|
36
|
+
# Now you can see how the comments and declaring classes get used in the
|
|
37
|
+
# configuration files. Note that configuration keys are stringified
|
|
38
|
+
# for clarity (this is ok -- they will be symbolized when loaded by a
|
|
39
|
+
# task).
|
|
40
|
+
#
|
|
41
|
+
# [BaseTask.configurations.format_yaml]
|
|
42
|
+
# # BaseTask configuration
|
|
43
|
+
# one: 1
|
|
44
|
+
#
|
|
45
|
+
# [SubTask.configurations.format_yaml]
|
|
46
|
+
# # BaseTask configuration
|
|
47
|
+
# one: one # the first configuration
|
|
48
|
+
#
|
|
49
|
+
# # SubTask configuration
|
|
50
|
+
# two: two # the second configuration
|
|
51
|
+
#
|
|
52
|
+
#--
|
|
53
|
+
# TODO -
|
|
54
|
+
# Revisit config formatting... right now it's a bit jacked.
|
|
55
|
+
#++
|
|
56
|
+
class ClassConfiguration
|
|
57
|
+
include Enumerable
|
|
58
|
+
|
|
59
|
+
# The class receiving the configurations
|
|
60
|
+
attr_reader :receiver
|
|
61
|
+
|
|
62
|
+
# An array of [receiver, configuration keys] arrays tracking
|
|
63
|
+
# the order in which configurations were declared across all
|
|
64
|
+
# receivers
|
|
65
|
+
attr_reader :declarations_array
|
|
66
|
+
|
|
67
|
+
# An array of configuration keys declared by self
|
|
68
|
+
attr_reader :declarations
|
|
69
|
+
|
|
70
|
+
# A hash of the unprocessed default values
|
|
71
|
+
attr_reader :unprocessed_default
|
|
72
|
+
|
|
73
|
+
# A hash of the processed default values
|
|
74
|
+
attr_reader :default
|
|
75
|
+
|
|
76
|
+
# A hash of the processing blocks
|
|
77
|
+
attr_reader :process_blocks
|
|
78
|
+
|
|
79
|
+
# A placeholder to indicate when no value
|
|
80
|
+
# was specified during a call to add.
|
|
81
|
+
NO_VALUE = Object.new
|
|
82
|
+
|
|
83
|
+
def initialize(receiver, parent=nil)
|
|
84
|
+
@receiver = receiver
|
|
85
|
+
@default = parent != nil ? parent.default.dup : {}
|
|
86
|
+
@unprocessed_default = parent != nil ? parent.unprocessed_default.dup : {}
|
|
87
|
+
@process_blocks = parent != nil ? parent.process_blocks.dup : {}
|
|
88
|
+
|
|
89
|
+
# use same declarations array? freeze declarations to ensure order?
|
|
90
|
+
# definitely falls out of order if parents are modfied after initialization
|
|
91
|
+
@declarations_array = parent != nil ? parent.declarations_array.dup : []
|
|
92
|
+
@declarations = []
|
|
93
|
+
declarations_array << [receiver, @declarations]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns true if the key has been declared by some receiver.
|
|
97
|
+
# Note this is distinct from whether or not a particular config
|
|
98
|
+
# is currently in the default hash.
|
|
99
|
+
def declared?(key)
|
|
100
|
+
key = key.to_sym
|
|
101
|
+
declarations_array.each do |r,array|
|
|
102
|
+
return true if array.include?(key)
|
|
103
|
+
end
|
|
104
|
+
false
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Returns the class (receiver) that first added the config
|
|
108
|
+
# indicated by key.
|
|
109
|
+
def declaration_class(key)
|
|
110
|
+
key = key.to_sym
|
|
111
|
+
declarations_array.each do |r,array|
|
|
112
|
+
return r if array.include?(key)
|
|
113
|
+
end
|
|
114
|
+
nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns the configurations first declared by the specified receiver.
|
|
118
|
+
def declarations_for(receiver)
|
|
119
|
+
declarations_array.each do |r,array|
|
|
120
|
+
return array if r == receiver
|
|
121
|
+
end
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Adds a configuration. If specified, the value is processed
|
|
126
|
+
# by the process_block and recorded adefault
|
|
127
|
+
#
|
|
128
|
+
# The existing value and process_block for the
|
|
129
|
+
# configuration will not be overwritten unless specified. However,
|
|
130
|
+
# if a configuration is added without specifying a value and no previous
|
|
131
|
+
# default value exists, the default and unprocessed_default for the
|
|
132
|
+
# configuration will be set to nil.
|
|
133
|
+
#
|
|
134
|
+
#--
|
|
135
|
+
# Note -- existing blocks are NOT overwritten unless a new block is provided.
|
|
136
|
+
# This allows overide of the default value in subclasses while preserving the
|
|
137
|
+
# validation/processing code.
|
|
138
|
+
def add(key, value=NO_VALUE, &process_block)
|
|
139
|
+
key = key.to_sym
|
|
140
|
+
value = unprocessed_default[key] if value == NO_VALUE
|
|
141
|
+
|
|
142
|
+
declarations << key unless declared?(key)
|
|
143
|
+
process_blocks[key] = process_block if block_given?
|
|
144
|
+
unprocessed_default[key] = value
|
|
145
|
+
default[key] = process(key, value)
|
|
146
|
+
|
|
147
|
+
self
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def remove(key)
|
|
151
|
+
key = key.to_sym
|
|
152
|
+
|
|
153
|
+
process_blocks.delete(key)
|
|
154
|
+
unprocessed_default.delete(key)
|
|
155
|
+
default.delete(key)
|
|
156
|
+
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def merge(another)
|
|
161
|
+
# check for conflicts
|
|
162
|
+
another.each do |receiver, key|
|
|
163
|
+
dc = declaration_class(key)
|
|
164
|
+
next if dc == nil || dc == receiver
|
|
165
|
+
|
|
166
|
+
raise "configuration conflict: #{key} (#{receiver}) already declared by #{dc}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# add the new configurations
|
|
170
|
+
another.each do |receiver, key|
|
|
171
|
+
# preserve the declarations for receiver
|
|
172
|
+
unless declarations = declarations_for(receiver)
|
|
173
|
+
declarations = []
|
|
174
|
+
declarations_array << [receiver, declarations]
|
|
175
|
+
end
|
|
176
|
+
unless declarations.include?(key)
|
|
177
|
+
declarations << key
|
|
178
|
+
yield(key) if block_given?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
add(key, another.unprocessed_default[key], &another.process_blocks[key])
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def each # :yields: receiver, key
|
|
186
|
+
declarations_array.each do |receiver, keys|
|
|
187
|
+
keys.each {|key| yield(receiver, key) }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Sends value to the process block identified by key and returns the result.
|
|
192
|
+
# Returns value if no process block has been set for key.
|
|
193
|
+
def process(key, value)
|
|
194
|
+
block = process_blocks[key.to_sym]
|
|
195
|
+
block ? block.call(value) : value
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Nicely formats the configurations into yaml with messages and
|
|
199
|
+
# declaration class divisions.
|
|
200
|
+
def format_yaml
|
|
201
|
+
lines = []
|
|
202
|
+
declarations_array.each do |receiver, keys|
|
|
203
|
+
|
|
204
|
+
# do not consider keys that have been removed
|
|
205
|
+
keys = keys.delete_if {|key| !self.default.has_key?(key) }
|
|
206
|
+
next if keys.empty?
|
|
207
|
+
|
|
208
|
+
lines << "# #{receiver} configuration#{keys.length > 1 ? 's' : ''}"
|
|
209
|
+
|
|
210
|
+
class_doc = Tap::Support::TDoc[receiver]
|
|
211
|
+
configurations = (class_doc == nil ? [] : class_doc.configurations)
|
|
212
|
+
keys.each do |key|
|
|
213
|
+
tdoc_config = configurations.find {|config| config.name == key.to_s }
|
|
214
|
+
|
|
215
|
+
# yaml adds a header and a final newline which should be removed:
|
|
216
|
+
# {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
|
|
217
|
+
# {'key' => 'value'}.to_yaml[5...-1] # => "key: value"
|
|
218
|
+
yaml = {key.to_s => unprocessed_default[key]}.to_yaml[5...-1]
|
|
219
|
+
message = tdoc_config ? tdoc_config.comment : ""
|
|
220
|
+
|
|
221
|
+
lines << case
|
|
222
|
+
when message == nil || message.empty?
|
|
223
|
+
# if there is no message, simply add the yaml
|
|
224
|
+
yaml
|
|
225
|
+
when yaml !~ /\r?\n/ && message !~ /\r?\n/ && yaml.length < 25 && message.length < 30
|
|
226
|
+
# shorthand ONLY if the config and message can be expressed in a single line
|
|
227
|
+
message = message.gsub(/^#\s*/, "")
|
|
228
|
+
"%-25s # %s" % [yaml, message]
|
|
229
|
+
else
|
|
230
|
+
lines << ""
|
|
231
|
+
# comment out new lines and add the message
|
|
232
|
+
message.split(/\n/).each do |msg|
|
|
233
|
+
lines << "# #{msg.strip.gsub(/^#\s*/, '')}"
|
|
234
|
+
end
|
|
235
|
+
yaml
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# add a spacer line
|
|
240
|
+
lines << ""
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
lines.compact.join("\n")
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def opt_map(long_option)
|
|
247
|
+
raise ArgumentError.new("not a long option: #{long_option}") unless long_option =~ /^--(.*)$/
|
|
248
|
+
long = $1
|
|
249
|
+
|
|
250
|
+
each do |receiver, key|
|
|
251
|
+
return key if long == key.to_s
|
|
252
|
+
end
|
|
253
|
+
nil
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def to_opts
|
|
257
|
+
collect do |receiver, key|
|
|
258
|
+
# Note the receiver is used as a placeholder for desc,
|
|
259
|
+
# to be resolved using TDoc.
|
|
260
|
+
attributes = {
|
|
261
|
+
:long => key,
|
|
262
|
+
:short => nil,
|
|
263
|
+
:opt_type => GetoptLong::REQUIRED_ARGUMENT,
|
|
264
|
+
:desc => receiver
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
long = attributes[:long]
|
|
268
|
+
attributes[:long] = "--#{long}" unless long =~ /^-{2}/
|
|
269
|
+
|
|
270
|
+
short = attributes[:short].to_s
|
|
271
|
+
attributes[:short] = "-#{short}" unless short.empty? || short =~ /^-/
|
|
272
|
+
|
|
273
|
+
[attributes[:long], attributes[:short], attributes[:opt_type], attributes[:desc]]
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module Tap
|
|
2
|
+
module Support
|
|
3
|
+
|
|
4
|
+
# Configurable encapsulates all configuration-related methods used
|
|
5
|
+
# by Tasks. When Configurable is included in a class, the class itself
|
|
6
|
+
# is extended with Tap::Support::ConfigurableMethods, such that configs
|
|
7
|
+
# can be declared within the class definition.
|
|
8
|
+
#
|
|
9
|
+
# class ConfigurableClass
|
|
10
|
+
# include Configurable
|
|
11
|
+
#
|
|
12
|
+
# config :one, 'one'
|
|
13
|
+
# config :two, 'two'
|
|
14
|
+
# config :three, 'three'
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# ConfigurableClass.new.config # => {:one => 'one', :two => 'two', :three => 'three'}
|
|
18
|
+
#
|
|
19
|
+
# See the 'Configuration' section in the Tap::Task documentation for
|
|
20
|
+
# more details on how Configurable works in practice.
|
|
21
|
+
module Configurable
|
|
22
|
+
include Batchable
|
|
23
|
+
|
|
24
|
+
def self.included(mod)
|
|
25
|
+
mod.extend Support::ConfigurableMethods
|
|
26
|
+
mod.instance_variable_set(:@configurations, Support::ClassConfiguration.new(mod))
|
|
27
|
+
mod.instance_variable_set(:@source_files, [])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The application used to load config_file templates
|
|
31
|
+
# (and hence, to initialize batched objects).
|
|
32
|
+
attr_reader :app
|
|
33
|
+
|
|
34
|
+
# The name used to determine config_file, via
|
|
35
|
+
# app.config_filepath(name).
|
|
36
|
+
attr_reader :name
|
|
37
|
+
|
|
38
|
+
# The config file used to load config templates.
|
|
39
|
+
attr_reader :config_file
|
|
40
|
+
|
|
41
|
+
# A configuration hash.
|
|
42
|
+
attr_reader :config
|
|
43
|
+
|
|
44
|
+
# Initializes a new Configurable and associated batch objects. Batch
|
|
45
|
+
# objects will be initialized for each configuration template specified
|
|
46
|
+
# in config_file, where config_file = app.config_filepath(name).
|
|
47
|
+
def initialize(name=nil, config={}, app=App.instance)
|
|
48
|
+
@app = app
|
|
49
|
+
@batch = []
|
|
50
|
+
@config_file = app.config_filepath(name)
|
|
51
|
+
|
|
52
|
+
config.symbolize_keys! unless config.empty?
|
|
53
|
+
app.each_config_template(config_file) do |template|
|
|
54
|
+
template_config = template.empty? ? config : template.symbolize_keys.merge(config)
|
|
55
|
+
initialize_batch_obj(name, template_config)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Sets config with the given configuration overrides, merged with the class
|
|
60
|
+
# default configuration. Configurations are symbolized before they are merged,
|
|
61
|
+
# and validated as specified in the config declarations.
|
|
62
|
+
def config=(overrides)
|
|
63
|
+
@config = self.class.configurations.default.dup
|
|
64
|
+
overrides.each_pair {|key, value| set_config(key.to_sym, value) }
|
|
65
|
+
self.config
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Creates a new batched object and adds the object to batch. The batched object
|
|
69
|
+
# will be a duplicate of the current object but with a new name and/or
|
|
70
|
+
# configurations.
|
|
71
|
+
def initialize_batch_obj(name=nil, config={})
|
|
72
|
+
obj = super()
|
|
73
|
+
|
|
74
|
+
obj.name = name.nil? ? self.class.default_name : name
|
|
75
|
+
obj.config = config
|
|
76
|
+
|
|
77
|
+
obj
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
protected
|
|
81
|
+
|
|
82
|
+
attr_writer :name
|
|
83
|
+
|
|
84
|
+
# Sets the specified configuration, processing the input value using
|
|
85
|
+
# the block specified in the config declaration. The input key should
|
|
86
|
+
# be symbolized.
|
|
87
|
+
def set_config(key, value)
|
|
88
|
+
config[key] = self.class.configurations.process(key, value)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
module Tap
|
|
2
|
+
module Support
|
|
3
|
+
autoload(:TDoc, 'tap/support/tdoc')
|
|
4
|
+
|
|
5
|
+
# ConfigurableMethods encapsulates all class methods used to declare
|
|
6
|
+
# configurations in Tasks. ConfigurableMethods extends classes that
|
|
7
|
+
# include Tap::Support::Configurable.
|
|
8
|
+
#
|
|
9
|
+
# class ConfigurableClass
|
|
10
|
+
# include Configurable
|
|
11
|
+
#
|
|
12
|
+
# config :one, 'one'
|
|
13
|
+
# config :two, 'two'
|
|
14
|
+
# config :three, 'three'
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# ConfigurableClass.new.config # => {:one => 'one', :two => 'two', :three => 'three'}
|
|
18
|
+
#
|
|
19
|
+
# See the 'Configuration' section in the Tap::Task documentation for
|
|
20
|
+
# more details on how Configurable works in practice.
|
|
21
|
+
module ConfigurableMethods
|
|
22
|
+
|
|
23
|
+
# A Tap::Support::ClassConfiguration holding the class configurations.
|
|
24
|
+
attr_reader :configurations
|
|
25
|
+
|
|
26
|
+
# When subclassed, the configurations are duplicated and passed to
|
|
27
|
+
# the child class where they can be extended/modified without affecting
|
|
28
|
+
# the configurations of the parent class.
|
|
29
|
+
def inherited(child)
|
|
30
|
+
super
|
|
31
|
+
child.instance_variable_set(:@configurations, ClassConfiguration.new(child, @configurations))
|
|
32
|
+
child.instance_variable_set(:@source_files, source_files.dup)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# EXPERIMENTAL
|
|
36
|
+
attr_reader :source_files # :nodoc:
|
|
37
|
+
|
|
38
|
+
# EXPERIMENTAL
|
|
39
|
+
# Identifies source files for TDoc documentation.
|
|
40
|
+
def source_file(arg) # :nodoc:
|
|
41
|
+
source_files << arg
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Declares a configuration without any accessors.
|
|
45
|
+
#
|
|
46
|
+
# With no keys specified, sets config to make no
|
|
47
|
+
# accessors for each new configuration.
|
|
48
|
+
def declare_config(*keys)
|
|
49
|
+
if keys.empty?
|
|
50
|
+
self.config_mode = :none
|
|
51
|
+
else
|
|
52
|
+
keys.each {|key| configurations.add(key)}
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Creates a configuration writer for the input keys. Works like
|
|
57
|
+
# attr_writer, except the value is written to config, rather than
|
|
58
|
+
# a local variable. In addition, the config will be validated
|
|
59
|
+
# using validate_config upon setting the value.
|
|
60
|
+
#
|
|
61
|
+
# With no keys specified, sets config to create config_writer
|
|
62
|
+
# for each new configuration.
|
|
63
|
+
def config_writer(*keys)
|
|
64
|
+
if keys.empty?
|
|
65
|
+
self.config_mode = :config_writer
|
|
66
|
+
else
|
|
67
|
+
keys.each do |key|
|
|
68
|
+
configurations.add(key)
|
|
69
|
+
define_config_writer(key)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Creates a configuration reader for the input keys. Works like
|
|
75
|
+
# attr_reader, except the value is read from config, rather than
|
|
76
|
+
# a local variable.
|
|
77
|
+
#
|
|
78
|
+
# With no keys specified, sets config to create a config_reader
|
|
79
|
+
# for each new configuration.
|
|
80
|
+
def config_reader(*keys)
|
|
81
|
+
if keys.empty?
|
|
82
|
+
self.config_mode = :config_reader
|
|
83
|
+
else
|
|
84
|
+
keys.each do |key|
|
|
85
|
+
configurations.add(key)
|
|
86
|
+
define_config_reader(key)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Creates configuration accessors for the input keys. Works like
|
|
92
|
+
# attr_accessor, except the value is read from and written to config,
|
|
93
|
+
# rather than a local variable.
|
|
94
|
+
#
|
|
95
|
+
# With no keys specified, sets config to create a config_accessor
|
|
96
|
+
# for each new configuration.
|
|
97
|
+
def config_accessor(*keys)
|
|
98
|
+
if keys.empty?
|
|
99
|
+
self.config_mode = :config_accessor
|
|
100
|
+
else
|
|
101
|
+
keys.each do |key|
|
|
102
|
+
configurations.add(key)
|
|
103
|
+
define_config_reader(key)
|
|
104
|
+
define_config_writer(key)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Sets a class configuration. Configurations are inherited, but can
|
|
110
|
+
# be overridden or added in subclasses. Accessors are created by
|
|
111
|
+
# default, but this behavior can be modified by use of the other
|
|
112
|
+
# config methods.
|
|
113
|
+
#
|
|
114
|
+
# class SampleClass
|
|
115
|
+
# include Configurable
|
|
116
|
+
#
|
|
117
|
+
# config :key, 'value'
|
|
118
|
+
# config_reader
|
|
119
|
+
# config :reader_only
|
|
120
|
+
# end
|
|
121
|
+
#
|
|
122
|
+
# t = SampleClass.new
|
|
123
|
+
# t.respond_to?(:reader_only) # => true
|
|
124
|
+
# t.respond_to?(:reader_only=) # => false
|
|
125
|
+
#
|
|
126
|
+
# t.config # => {:key => 'value', :reader_only => nil}
|
|
127
|
+
# t.key # => 'value'
|
|
128
|
+
# t.key = 'another'
|
|
129
|
+
# t.config # => {:key => 'another', :reader_only => nil}
|
|
130
|
+
#
|
|
131
|
+
# A block can be specified for validation/pre-processing. All inputs
|
|
132
|
+
# set through the config accessors, as well as the instance config=
|
|
133
|
+
# method are processed by the block before they set the value in the
|
|
134
|
+
# config hash. The config value will be set to the return of the block.
|
|
135
|
+
#
|
|
136
|
+
# The Tap::Support::Validation module provides methods to perform
|
|
137
|
+
# common checks and transformations. These can be accessed through
|
|
138
|
+
# the class method 'c':
|
|
139
|
+
#
|
|
140
|
+
# class ValidatingClass
|
|
141
|
+
# include Configurable
|
|
142
|
+
#
|
|
143
|
+
# config :one, 'one', &c.check(String)
|
|
144
|
+
# config :two, 'two' do |v|
|
|
145
|
+
# v.upcase
|
|
146
|
+
# end
|
|
147
|
+
# end
|
|
148
|
+
#
|
|
149
|
+
# t = ValidatingClass.new
|
|
150
|
+
#
|
|
151
|
+
# # note the default values ARE processed
|
|
152
|
+
# t.config # => {:one => 'one', :two => 'TWO'}
|
|
153
|
+
# t.one = 1 # => ValidationError
|
|
154
|
+
# t.config = {:one => 1} # => ValidationError
|
|
155
|
+
#
|
|
156
|
+
# t.config = {:one => 'str', :two => 'str'}
|
|
157
|
+
# t.config # => {:one => 'str', :two => 'STR'}
|
|
158
|
+
#
|
|
159
|
+
def config(key, value=nil, &validation)
|
|
160
|
+
configurations.add(key, value, &validation)
|
|
161
|
+
|
|
162
|
+
case config_mode
|
|
163
|
+
when :config_accessor
|
|
164
|
+
define_config_writer(key)
|
|
165
|
+
define_config_reader(key)
|
|
166
|
+
when :config_writer
|
|
167
|
+
define_config_writer(key)
|
|
168
|
+
when :config_reader
|
|
169
|
+
define_config_reader(key)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def config_merge(klass)
|
|
174
|
+
configurations.merge(klass.configurations) do |key|
|
|
175
|
+
case config_mode
|
|
176
|
+
when :config_accessor
|
|
177
|
+
define_config_writer(key)
|
|
178
|
+
define_config_reader(key)
|
|
179
|
+
when :config_writer
|
|
180
|
+
define_config_writer(key)
|
|
181
|
+
when :config_reader
|
|
182
|
+
define_config_reader(key)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Returns the default name for the class: class.to_s.underscore
|
|
188
|
+
def default_name
|
|
189
|
+
@default_name ||= to_s.underscore
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Alias for Batchable.batch
|
|
193
|
+
def batch(*batchables)
|
|
194
|
+
Batchable.batch(*batchables)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Returns the TDoc documentation for self.
|
|
198
|
+
def tdoc
|
|
199
|
+
@tdoc ||= Tap::Support::TDoc[self]
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# EXPERIMENTAL
|
|
203
|
+
def help(opts=configurations.to_opts) # :nodoc:
|
|
204
|
+
return "could not find help for '#{self}'" if tdoc == nil
|
|
205
|
+
|
|
206
|
+
sections = tdoc.comment_sections(/Description|Usage/i, true)
|
|
207
|
+
%Q{#{self}
|
|
208
|
+
|
|
209
|
+
#{sections["Description"]}
|
|
210
|
+
Usage:
|
|
211
|
+
#{sections["Usage"]}
|
|
212
|
+
Options:
|
|
213
|
+
#{Tap::Script.usage_options(opts)}
|
|
214
|
+
|
|
215
|
+
}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# EXPERIMENTAL
|
|
219
|
+
def argv_enq(app=App.instance, &block) # :nodoc:
|
|
220
|
+
if block_given?
|
|
221
|
+
@argv_enq_block = block
|
|
222
|
+
return
|
|
223
|
+
end
|
|
224
|
+
return @argv_enq_block.call(app) if @argv_enq_block ||= nil
|
|
225
|
+
|
|
226
|
+
config = {}
|
|
227
|
+
opts = configurations.to_opts
|
|
228
|
+
opts << ['--help', nil, GetoptLong::NO_ARGUMENT, "Print this help."]
|
|
229
|
+
opts << ['--debug', nil, GetoptLong::NO_ARGUMENT, "Trace execution and debug"]
|
|
230
|
+
opts << ['--use', nil, GetoptLong::REQUIRED_ARGUMENT, "Loads inputs from file."]
|
|
231
|
+
opts << ['--iterate', nil, GetoptLong::NO_ARGUMENT, "Iterates over inputs."]
|
|
232
|
+
|
|
233
|
+
iterate = false
|
|
234
|
+
Tap::Script.handle_options(*opts) do |opt, value|
|
|
235
|
+
case opt
|
|
236
|
+
when '--help'
|
|
237
|
+
puts help(opts)
|
|
238
|
+
exit
|
|
239
|
+
|
|
240
|
+
when '--debug'
|
|
241
|
+
app.options.debug = true
|
|
242
|
+
|
|
243
|
+
when '--use'
|
|
244
|
+
hash = YAML.load_file(value)
|
|
245
|
+
hash.values.each do |args|
|
|
246
|
+
ARGV.concat(args)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
when '--iterate'
|
|
250
|
+
iterate = true
|
|
251
|
+
|
|
252
|
+
else
|
|
253
|
+
key = configurations.opt_map(opt)
|
|
254
|
+
config[key] = YAML.load(value)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# configure task
|
|
259
|
+
task = app.task(ARGV.shift, config)
|
|
260
|
+
iterate ? ARGV.each {|input| task.enq(input) } : task.enq(*ARGV)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
protected
|
|
264
|
+
|
|
265
|
+
attr_writer :config_mode
|
|
266
|
+
|
|
267
|
+
# Tracks the current configuration mode, to determine what
|
|
268
|
+
# in any accessors should be generated for the configuration.
|
|
269
|
+
# (default :config_accessor)
|
|
270
|
+
def config_mode
|
|
271
|
+
@config_mode ||= :config_accessor
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Alias for Tap::Support::Validation
|
|
275
|
+
def c
|
|
276
|
+
Validation
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
|
|
281
|
+
def define_config_reader(name, key=name) # :nodoc:
|
|
282
|
+
key = key.to_sym
|
|
283
|
+
define_method(name) do
|
|
284
|
+
config[key]
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def define_config_writer(name, key=name) # :nodoc:
|
|
289
|
+
key = key.to_sym
|
|
290
|
+
define_method("#{name}=") do |value|
|
|
291
|
+
set_config(key, value)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|