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.
Files changed (185) hide show
  1. data/Basic Overview +151 -0
  2. data/Command Reference +99 -0
  3. data/History +24 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README +29 -57
  6. data/Rakefile +30 -37
  7. data/Tutorial +243 -191
  8. data/bin/tap +66 -35
  9. data/lib/tap.rb +47 -29
  10. data/lib/tap/app.rb +700 -342
  11. data/lib/tap/{script → cmd}/console.rb +0 -0
  12. data/lib/tap/{script → cmd}/destroy.rb +0 -0
  13. data/lib/tap/{script → cmd}/generate.rb +0 -0
  14. data/lib/tap/cmd/run.rb +156 -0
  15. data/lib/tap/constants.rb +4 -0
  16. data/lib/tap/dump.rb +57 -0
  17. data/lib/tap/env.rb +316 -0
  18. data/lib/tap/file_task.rb +106 -109
  19. data/lib/tap/generator.rb +4 -1
  20. data/lib/tap/generator/generators/command/USAGE +6 -0
  21. data/lib/tap/generator/generators/command/command_generator.rb +17 -0
  22. data/lib/tap/generator/generators/{script/templates/script.erb → command/templates/command.erb} +10 -10
  23. data/lib/tap/generator/generators/config/USAGE +21 -0
  24. data/lib/tap/generator/generators/config/config_generator.rb +17 -7
  25. data/lib/tap/generator/generators/file_task/USAGE +3 -0
  26. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -0
  27. data/lib/tap/generator/generators/file_task/templates/file.txt +2 -0
  28. data/lib/tap/generator/generators/file_task/templates/file.yml +3 -0
  29. data/lib/tap/generator/generators/file_task/templates/task.erb +26 -20
  30. data/lib/tap/generator/generators/file_task/templates/test.erb +20 -10
  31. data/lib/tap/generator/generators/generator/generator_generator.rb +1 -1
  32. data/lib/tap/generator/generators/generator/templates/generator.erb +21 -12
  33. data/lib/tap/generator/generators/root/templates/Rakefile +33 -24
  34. data/lib/tap/generator/generators/root/templates/tap.yml +28 -31
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +1 -0
  36. data/lib/tap/generator/generators/task/USAGE +3 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +18 -5
  38. data/lib/tap/generator/generators/task/templates/task.erb +7 -12
  39. data/lib/tap/generator/generators/task/templates/test.erb +10 -11
  40. data/lib/tap/generator/generators/workflow/templates/task.erb +1 -1
  41. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  42. data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
  43. data/lib/tap/patches/rake/testtask.rb +55 -0
  44. data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
  45. data/lib/tap/patches/ruby19/parsedate.rb +16 -0
  46. data/lib/tap/root.rb +172 -67
  47. data/lib/tap/script.rb +70 -336
  48. data/lib/tap/support/aggregator.rb +55 -0
  49. data/lib/tap/support/audit.rb +281 -280
  50. data/lib/tap/support/batchable.rb +59 -0
  51. data/lib/tap/support/class_configuration.rb +279 -0
  52. data/lib/tap/support/configurable.rb +92 -0
  53. data/lib/tap/support/configurable_methods.rb +296 -0
  54. data/lib/tap/support/executable.rb +98 -0
  55. data/lib/tap/support/executable_queue.rb +82 -0
  56. data/lib/tap/support/logger.rb +9 -15
  57. data/lib/tap/support/rake.rb +43 -54
  58. data/lib/tap/support/run_error.rb +32 -13
  59. data/lib/tap/support/shell_utils.rb +47 -0
  60. data/lib/tap/support/tdoc.rb +9 -8
  61. data/lib/tap/support/tdoc/config_attr.rb +40 -16
  62. data/lib/tap/support/validation.rb +77 -0
  63. data/lib/tap/support/versions.rb +36 -36
  64. data/lib/tap/task.rb +276 -482
  65. data/lib/tap/test.rb +20 -261
  66. data/lib/tap/test/env_vars.rb +7 -5
  67. data/lib/tap/test/file_methods.rb +126 -121
  68. data/lib/tap/test/subset_methods.rb +86 -45
  69. data/lib/tap/test/tap_methods.rb +271 -0
  70. data/lib/tap/workflow.rb +174 -46
  71. data/test/app/config/another/task.yml +1 -0
  72. data/test/app/config/erb.yml +2 -1
  73. data/test/app/config/some/task.yml +1 -0
  74. data/test/app/config/template.yml +2 -6
  75. data/test/app_test.rb +1241 -1008
  76. data/test/env/test_configure/recurse_a.yml +2 -0
  77. data/test/env/test_configure/recurse_b.yml +2 -0
  78. data/test/env/test_configure/tap.yml +23 -0
  79. data/test/env/test_load_env_config/dir/tap.yml +3 -0
  80. data/test/env/test_load_env_config/recurse_a.yml +2 -0
  81. data/test/env/test_load_env_config/recurse_b.yml +2 -0
  82. data/test/env/test_load_env_config/tap.yml +3 -0
  83. data/test/env_test.rb +198 -0
  84. data/test/file_task_test.rb +70 -53
  85. data/{lib/tap/generator/generators/package/USAGE → test/root/file.txt} +0 -0
  86. data/test/root_test.rb +621 -454
  87. data/test/script_test.rb +38 -174
  88. data/test/support/aggregator_test.rb +99 -0
  89. data/test/support/audit_test.rb +409 -416
  90. data/test/support/batchable_test.rb +74 -0
  91. data/test/support/{task_configuration_test.rb → class_configuration_test.rb} +106 -47
  92. data/test/{task/config/overriding.yml → support/configurable/config/configured.yml} +0 -0
  93. data/test/support/configurable_test.rb +295 -0
  94. data/test/support/executable_queue_test.rb +103 -0
  95. data/test/support/executable_test.rb +38 -0
  96. data/test/support/logger_test.rb +17 -17
  97. data/test/support/rake_test.rb +4 -2
  98. data/test/support/shell_utils_test.rb +24 -0
  99. data/test/support/tdoc_test.rb +265 -258
  100. data/test/support/validation_test.rb +54 -0
  101. data/test/support/versions_test.rb +38 -38
  102. data/test/tap_test_helper.rb +19 -5
  103. data/test/tap_test_suite.rb +5 -2
  104. data/test/task_base_test.rb +13 -104
  105. data/test/task_syntax_test.rb +300 -0
  106. data/test/task_test.rb +258 -381
  107. data/test/test/env_vars_test.rb +40 -40
  108. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/one.txt +0 -0
  109. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/expected/two.txt +0 -0
  110. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/one.txt +0 -0
  111. data/test/test/file_methods/{test_assert_output_files_equal → test_assert_files}/input/two.txt +0 -0
  112. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/one.txt +0 -0
  113. data/test/test/{test_file_task_test → file_methods/test_assert_files_can_have_no_expected_files_if_specified}/input/two.txt +0 -0
  114. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +1 -0
  115. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_different_content}/expected/two.txt +0 -0
  116. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +1 -0
  117. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +1 -0
  118. data/test/test/{test_file_task_test → file_methods/test_assert_files_fails_for_missing_expected_file}/expected/one.txt +0 -0
  119. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +1 -0
  120. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +1 -0
  121. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +1 -0
  122. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +1 -0
  123. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +1 -0
  124. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +1 -0
  125. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +1 -0
  126. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +1 -0
  127. data/test/test/file_methods_doc/test_sub/expected/one.txt +1 -0
  128. data/test/test/file_methods_doc/test_sub/expected/two.txt +1 -0
  129. data/test/test/file_methods_doc/test_sub/input/one.txt +1 -0
  130. data/test/test/file_methods_doc/test_sub/input/two.txt +1 -0
  131. data/test/test/file_methods_doc_test.rb +29 -0
  132. data/test/test/file_methods_test.rb +214 -143
  133. data/test/test/subset_methods_test.rb +111 -115
  134. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/a.txt +0 -0
  135. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/expected/task/name/b.txt +0 -0
  136. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/a.txt +0 -0
  137. data/test/test/{test_assert_expected_result_files → tap_methods/test_assert_files}/input/b.txt +0 -0
  138. data/test/test/tap_methods_test.rb +399 -0
  139. data/test/workflow_test.rb +101 -91
  140. metadata +86 -70
  141. data/lib/tap/generator/generators/package/package_generator.rb +0 -38
  142. data/lib/tap/generator/generators/package/templates/package.erb +0 -186
  143. data/lib/tap/generator/generators/script/USAGE +0 -0
  144. data/lib/tap/generator/generators/script/script_generator.rb +0 -17
  145. data/lib/tap/script/run.rb +0 -154
  146. data/lib/tap/support/batch_queue.rb +0 -162
  147. data/lib/tap/support/combinator.rb +0 -114
  148. data/lib/tap/support/task_configuration.rb +0 -169
  149. data/lib/tap/support/template.rb +0 -81
  150. data/lib/tap/support/templater.rb +0 -155
  151. data/lib/tap/version.rb +0 -4
  152. data/test/app/config/addition_template.yml +0 -6
  153. data/test/app_class_test.rb +0 -33
  154. data/test/check/binding_eval.rb +0 -23
  155. data/test/check/define_method_check.rb +0 -22
  156. data/test/check/dependencies_check.rb +0 -175
  157. data/test/check/inheritance_check.rb +0 -22
  158. data/test/support/batch_queue_test.rb +0 -320
  159. data/test/support/combinator_test.rb +0 -249
  160. data/test/support/template_test.rb +0 -122
  161. data/test/support/templater/erb.txt +0 -2
  162. data/test/support/templater/erb.yml +0 -2
  163. data/test/support/templater/somefile.txt +0 -2
  164. data/test/support/templater_test.rb +0 -192
  165. data/test/task/config/template.yml +0 -4
  166. data/test/task_class_test.rb +0 -170
  167. data/test/task_execute_test.rb +0 -262
  168. data/test/test/file_methods/test_assert_expected/expected/file.txt +0 -1
  169. data/test/test/file_methods/test_assert_expected/expected/folder/file.txt +0 -1
  170. data/test/test/file_methods/test_assert_expected/input/file.txt +0 -1
  171. data/test/test/file_methods/test_assert_expected/input/folder/file.txt +0 -1
  172. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  173. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  174. data/test/test/file_methods/test_file_compare/expected/output_1.txt +0 -3
  175. data/test/test/file_methods/test_file_compare/expected/output_2.txt +0 -1
  176. data/test/test/file_methods/test_file_compare/input/input_1.txt +0 -3
  177. data/test/test/file_methods/test_file_compare/input/input_2.txt +0 -3
  178. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  179. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  180. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  181. data/test/test/file_methods/test_yml_compare/expected/output_1.yml +0 -6
  182. data/test/test/file_methods/test_yml_compare/expected/output_2.yml +0 -6
  183. data/test/test/file_methods/test_yml_compare/input/input_1.yml +0 -4
  184. data/test/test/file_methods/test_yml_compare/input/input_2.yml +0 -4
  185. data/test/test_test.rb +0 -373
@@ -1,114 +0,0 @@
1
- require 'enumerator'
2
-
3
- module Tap
4
- module Support
5
-
6
- # Combinator provides a method for iterating over all possible combinations
7
- # of items in the input sets.
8
- #
9
- # c = Combinator.new [1,2], [3,4]
10
- # c.collect # => [[1,3], [1,4], [2,3], [2,4]]
11
- #
12
- # Combinator works by iteratively combining each element from the first set (a)
13
- # with each element from the second set (b). When more than two sets are given,
14
- # a Combinator will bundle all but the first set into a new Combinator which
15
- # then acts as the second set.
16
- #
17
- # # Note: each element in a set is arrayified
18
- # # to simplify creation of the combination.
19
- #
20
- # c = Combinator.new [1,2], [3,4], [5,6]
21
- # c.a # => [[1],[2]]
22
- # c.b.class # => Combinator
23
- # c.b.a # => [[3],[4]]
24
- # c.b.b # => [[5],[6]]
25
- #
26
- # Thus when iterating, each Combinator makes it's pairwise combinations and
27
- # works outwards to the final multi-set combinations.
28
- class Combinator
29
- include Enumerable
30
-
31
- attr_reader :a, :b
32
-
33
- # Creates a new Combinator. Input sets must be an Array, or nil.
34
- # Within the Array any objects are allowed for combination.
35
- def initialize(*sets)
36
- @a = make_set(sets.shift)
37
- @b = make_set(*sets)
38
- end
39
-
40
- # Returns the sets used to initialize the Combinator, minus any
41
- # nil or empty sets.
42
- def sets
43
- sets_in(a) + sets_in(b)
44
- end
45
-
46
- # True if the Combinator has no combinations.
47
- def empty?
48
- a.empty? && b.empty?
49
- end
50
-
51
- # Returns the number of combinations in the Combinator.
52
- def length
53
- if a.empty? || b.empty?
54
- if !a.empty?
55
- a.length
56
- elsif !b.empty?
57
- b.length
58
- else
59
- 0
60
- end
61
- else
62
- a.length * b.length
63
- end
64
- end
65
-
66
- # Passes each combination as an array to the input block.
67
- def each(&block)
68
- # if either set is empty, iterate only the non-empty set
69
- # note, the two-step check on each ensures that if both
70
- # sets are empty, then neither is iterated.
71
- if a.empty? || b.empty?
72
- if !a.empty?
73
- a.each {|aa| yield aa }
74
- elsif !b.empty?
75
- b.each {|bb| yield bb }
76
- end
77
- else
78
- # otherwise, iterate all combinations of both
79
- a.each do |aa|
80
- b.each do |bb|
81
- yield aa + bb
82
- end
83
- end
84
- end
85
- end
86
-
87
- private
88
-
89
- def sets_in(set)
90
- # recursively de-arrayifies each element in the array
91
- case set
92
- when Combinator then set.sets
93
- when Array then set.empty? ? [] : [set.collect {|s| s.first}]
94
- end
95
- end
96
-
97
- def make_set(*sets)
98
- # recieves an array of arrays or combinators
99
- return Combinator.new(*sets) if sets.length > 1
100
- return [] if sets.empty?
101
-
102
- # recursively arrayifies each element in an array
103
- set = sets.first
104
- case set
105
- when Array then set.collect {|s| [s]}
106
- when nil then []
107
- else
108
- raise ArgumentError.new("sets must be arrays or nil")
109
- end
110
- end
111
-
112
- end
113
- end
114
- end
@@ -1,169 +0,0 @@
1
- module Tap
2
- module Support
3
- # == UNDER CONSTRUCTION
4
- #
5
- # TaskConfiguration holds the class configurations defined in a Tap::Task.
6
- # The configurations are stored as an array of declarations like:
7
- # [name, default, msg, declaration_class]. In addition, TaskConfiguration
8
- # collapse the array of declarations into a hash, which acts as the default
9
- # task configuration.
10
- #
11
- # Storing metadata about the configurations, such as the declaration_class,
12
- # allows the creation of more user-friendly configuration files and facilitates
13
- # incorporation into command-line applications.
14
- #
15
- # In general, users will not have to interact with TaskConfigurations directly.
16
- #
17
- # == Example
18
- #
19
- # class BaseTask < Tap::Task
20
- # class_configurations [:one, 1]
21
- # end
22
- #
23
- # BaseTask.configurations.hash # => {:one => 1}
24
- #
25
- # class SubTask < BaseTask
26
- # class_configurations(
27
- # [:one, 'one', "the first configuration"],
28
- # [:two, 'two', "the second configuration"])
29
- # end
30
- #
31
- # SubTask.configurations.hash # => {:one => 'one', :two => 'two'}
32
- #
33
- # Now you can see how the comments and declaring classes get used in the
34
- # configuration files. Note that configuration keys are stringified
35
- # for clarity (this is ok -- they will be symbolized when loaded by a
36
- # task).
37
- #
38
- # [BaseTask.configurations.format_yaml]
39
- # # BaseTask configuration
40
- # one: 1
41
- #
42
- # [SubTask.configurations.format_yaml]
43
- # # BaseTask configuration
44
- # one: one # the first configuration
45
- #
46
- # # SubTask configuration
47
- # two: two # the second configuration
48
- #
49
- class TaskConfiguration
50
- attr_reader :declarations, :default, :attributes
51
-
52
- include Enumerable
53
-
54
- def initialize
55
- @declarations = {}
56
- @default = {}
57
- @attributes = {}
58
- end
59
-
60
- def dup
61
- # ensure the duplication does not pass along
62
- # new references to the same objects
63
- another = TaskConfiguration.new
64
- another.declarations = self.declarations.dup
65
- another.default = self.default.dup
66
- another.attributes = self.attributes.dup
67
- another
68
- end
69
-
70
- def empty?
71
- @declaration.empty?
72
- end
73
-
74
- def declared?(key)
75
- default.has_key?(key.to_sym)
76
- end
77
-
78
- def declare(key, declaration_class)
79
- key = key.to_sym
80
-
81
- # Check if the key is already declared...
82
- return if declared?(key)
83
-
84
- # Add a declaration array if no declaration have been
85
- # created for the class and append the key
86
- (declarations[declaration_class] ||= [declarations.length]) << key
87
- default[key] = nil
88
- attributes[key] = {}
89
- end
90
-
91
- def set(key, value, attributes={})
92
- key = key.to_sym
93
- raise "configurations cannot be set until they are declared" unless declared?(key)
94
-
95
- default[key] = value
96
- self.attributes[key].merge!(attributes)
97
- end
98
-
99
- def each # :yields: declaration_class, key, default, attributes
100
- declarations.each_pair do |declaration_class, keys|
101
- keys[1..-1].each do |key|
102
- yield(declaration_class, key, default[key], attributes[key])
103
- end
104
- end
105
- end
106
-
107
- # Nicely formats the configurations into yaml with messages and
108
- # declaration class divisions.
109
- def format_yaml
110
- lines = []
111
- declarations.each_pair do |declaration_class, keys|
112
- lines << "# #{declaration_class} configuration#{keys.length > 2 ? 's' : ''}"
113
-
114
- class_name = declaration_class.to_s
115
- class_path = class_name.underscore
116
- source_file = Dependencies.search_for_file(class_path)
117
-
118
- Tap::Support::TDoc.document(source_file)
119
- class_doc = Tap::Support::TDoc[declaration_class]
120
- configurations = (class_doc == nil ? [] : class_doc.configurations)
121
-
122
- keys[1..-1].each do |key|
123
- value = default[key]
124
- attribs = attributes[key]
125
- message = ""
126
- configurations.each do |config|
127
- if config.name == key.to_s
128
- message = config.comment
129
- break
130
- end
131
- end
132
-
133
- # yaml adds a header and a final newline which should be removed:
134
- # {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
135
- # {'key' => 'value'}.to_yaml[5...-1] # => "key: value"
136
- yaml = {key.to_s => value}.to_yaml[5...-1]
137
- #yaml = "##{yaml}" if value.nil?
138
-
139
- lines << case
140
- when message == nil || message.empty?
141
- # if there is no message, simply add the yaml
142
- yaml
143
- when yaml !~ /\r?\n/ && message !~ /\r?\n/ && yaml.length < 20 && message.length < 30
144
- # shorthand ONLY if the config and message can be expressed in a single line
145
- message = message.gsub(/^#\s*/, "")
146
- "%-20s # %s" % [yaml, message]
147
- else
148
- # comment out new lines and add the message
149
- message = message.gsub(/\n#\s*/, "\n# ")
150
- lines << "# #{message}"
151
- yaml
152
- end
153
- end
154
-
155
- # add a spacer line
156
- lines << ""
157
- end
158
-
159
- lines.join("\n")
160
- end
161
-
162
-
163
- protected
164
-
165
- attr_writer :declarations, :default, :attributes
166
-
167
- end
168
- end
169
- end
@@ -1,81 +0,0 @@
1
- require 'tap/support/combinator'
2
-
3
- module Tap
4
- module Support
5
-
6
- # Template provides the default methods for making hash templates using Templater.
7
- # Methods are provided to make variations of the template, as well as to pattern
8
- # templates based on combinations of possible values.
9
- #
10
- module Template
11
- attr_accessor :templater
12
-
13
- # Provides a standard way of creating a variations of a template and takes an
14
- # array of hashes with the specified variations. The template is treated as the
15
- # default and variations are pushed (<<) to the templater. The template is
16
- # cleared upon complete, so that it will be removed if used in the context of
17
- # a Templater.
18
- #
19
- # t = {"default" => "default value"}.extend Template
20
- # t.templater = []
21
- # t.variations([
22
- # {"default" => "non-default value"},
23
- # {"another" => "value"}])
24
- #
25
- # t.templater
26
- # # => [{"default" => "non-default value"},
27
- # {"default" => "default value", "another" => "value"}]
28
- #
29
- def variations(array)
30
- variations!(array)
31
- self.clear
32
- end
33
-
34
- # Same as variations but does NOT clear the template (ie the template will
35
- # be kept as specified, effectively acting as another variant). Note that
36
- # variations! is called by the 'variations!!' key.
37
- def variations!(array)
38
- array.each { |var| templater << self.merge(var) }
39
- end
40
-
41
- # Provides a standard way of creating variations of a template based on all
42
- # combinations of the keys in the input hash. The template itself is treated
43
- # as the default and variations are pushed (<<) to the templater. The template
44
- # is cleared upon complete, so that it will be removed if used in the context of
45
- # a Templater.
46
- #
47
- # t = {"default" => "default value"}.extend Template
48
- # t.templater = []
49
- # t.combinations(
50
- # "letter" => ["a", "b"],
51
- # "number" => [1, 2])
52
- #
53
- # t.templater
54
- # # => [{"default" => "default value", "letter" => "a", "number" => 1},
55
- # {"default" => "default value", "letter" => "a", "number" => 2},
56
- # {"default" => "default value", "letter" => "b", "number" => 1},
57
- # {"default" => "default value", "letter" => "b", "number" => 2}]
58
- def combinations(hash)
59
- combinations!(hash)
60
- self.clear
61
- end
62
-
63
- # Same as combinations but does NOT clear the template (ie the template will
64
- # be kept as specified, effectively acting as another variant). Note that
65
- # combinations! is called by the 'combinations!!' key.
66
- def combinations!(hash)
67
- keys, values = hash.to_a.transpose
68
- values.collect! do |value|
69
- value.kind_of?(Array) ? value : [value]
70
- end
71
-
72
- Combinator.new(*values).each do |c|
73
- template = {}
74
- keys.each_with_index {|key,i| template[key] = c[i] }
75
-
76
- templater << self.merge(template)
77
- end
78
- end
79
- end
80
- end
81
- end
@@ -1,155 +0,0 @@
1
- require 'tap/support/template'
2
-
3
- module Tap
4
- module Support
5
-
6
- # Templater generates permutations of a hash, treating it as a template for other
7
- # hashes. Templater looks for various flags on the end of keys to signify operations
8
- # like method execution ('!') and replacement ('=').
9
- #
10
- # module TemplateMethods
11
- # def method_call(input)
12
- # self["result"] = "#{input} was processed"
13
- # end
14
- # end
15
- #
16
- # t = Templater.new(
17
- # "key"=> "value",
18
- # "method_call!" => "method input",
19
- # "replacement=" => "replacement input")
20
- # t.run_methods(TemplateMethods)
21
- # t.run_replace do |input|
22
- # "#{input} was replaced"
23
- # end
24
- #
25
- # t.templates
26
- # # => [{"key" => "value",
27
- # "result" => "method input was processed",
28
- # "replacement" => "replacement input was replaced"}]
29
- #
30
- # == Creating Template Methods
31
- #
32
- # The default Template methods may not be sufficient for your needs, but making new
33
- # methods (as above) is easy. Some simple rules apply:
34
- #
35
- # - Any method can be called from run_methods provided it has a single input.
36
- # - Modules with the methods will extend the hash templates so 'self' indicates the
37
- # template itself.
38
- # - New templates should be added by pushing the template to the templater, which
39
- # will already be set by the time the method executes
40
- # (ex: self.templater << new_template).
41
- #
42
- # Also, it's important to note that ANY empty templates at the end of run_methods
43
- # are cleared from the templates array. Thus, if you want to remove a template from
44
- # within a method, simply clear self.
45
- #
46
- # See the Template code for examples.
47
- class Templater
48
-
49
- # Convenience method for making a new Templater, running methods
50
- # with the input modules, and making replacements using the input
51
- # block. Returns the resulting templates.
52
- def self.make_templates(hash, *modules, &block)
53
- t = Templater.new(hash)
54
- t.run_methods(*modules)
55
- t.run_replace(&block)
56
- t.templates
57
- end
58
-
59
- attr_reader :templates
60
-
61
- # Creates a new Templater with the input hash as the initial template.
62
- def initialize(hash)
63
- @templates = []
64
- self << hash
65
- end
66
-
67
- # Shortcut to add a template to the templater, unless the template is empty.
68
- def <<(template)
69
- self.templates << template unless template.empty?
70
- end
71
-
72
- # Iterates through the pairs in each template looking for keys matching the
73
- # regexp (using =~). When a matching key is found, it is removed from the
74
- # template and then the template, the key, and the corresponding value are
75
- # passed to the block for further processing. Iteration restarts after each
76
- # match and empty templates are removed upon completion. All templates are
77
- # extended with Template before iteration.
78
- #
79
- # Notes:
80
- # - run_templater is the foundation of the other run_* methods and may be used
81
- # for custom template processing.
82
- # - since =~ is used for comparison, only String keys will be selected
83
- # under normal circumstances.
84
- def run_templater(regexp) # :yields: template, key, value
85
- templates.each do |template|
86
- unless template.respond_to?(:templater)
87
- template.extend Template
88
- template.templater = self
89
- end
90
-
91
- template.each_pair do |key, value|
92
- next unless key =~ regexp
93
-
94
- # remove the key
95
- template.delete(key)
96
- yield(template, key, value)
97
-
98
- # restart the loop as key/value pairs may have changed
99
- retry
100
- end
101
- end
102
-
103
- templates.delete_if {|t| t.empty?}
104
-
105
- self
106
- end
107
-
108
- # Iterates through the pairs in each template looking for and executing method calls
109
- # as per run_templater. Method calls are indicated by an exclamation attached to the
110
- # key, like 'do_something!'.
111
- #
112
- # Templates are extended with the input modules before the method call. The modules
113
- # should provide the methods (an error will be raised if the specified method is not
114
- # defined). Note, templates will by default be extended with the Template module.
115
- #
116
- # module MethodModule
117
- # def method_call(input)
118
- # self["result"] = "#{input} was processed"
119
- # end
120
- # end
121
- #
122
- # t = Templater.new('method_call!' => 'input')
123
- # t.run_methods(MethodModule) # calls 'do_something' with the input 'some value',
124
- # # after removing the key-value pair
125
- # t.templates # => [{"result" => "input was processed"}]
126
- #
127
- # Notes:
128
- # - The method called is the method minus the exclamation, so 'do_something!' calls
129
- # 'do_something' within the template and 'do_something!!' calls 'do_something!'
130
- # - Iteration restarts after each method call; inserted method calls will be executed
131
- def run_methods(*modules)
132
- run_templater(/!$/) do |template, key, value|
133
- modules.each {|mod| template.extend mod}
134
- template.send(key.chop, value)
135
- end
136
- end
137
-
138
- # Iterates through the pairs in each template looking for replacement keys and
139
- # executing the block to make the appropriate replacement value, as per
140
- # run_templater. Replacement keys are indicated by an equals sign attached to
141
- # the key, like 'key='. The equals sign will be chopped off for these keys, and
142
- # the value replaced with the return value of the block.
143
- #
144
- # t = Templater.new('replacement=' => 'some value')
145
- # t.run_replace {|value| "#{value} was replaced"}
146
- # t.templates # => [{"replacement" => "some value was replaced"}]
147
- #
148
- def run_replace # :yields: value
149
- run_templater(/=$/) do |template, key, value|
150
- template[key.chop] = yield(value)
151
- end
152
- end
153
- end
154
- end
155
- end