tap 0.9.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. data/History +37 -30
  2. data/MIT-LICENSE +1 -1
  3. data/README +92 -44
  4. data/bin/tap +62 -75
  5. data/cmd/console.rb +42 -0
  6. data/cmd/destroy.rb +16 -0
  7. data/cmd/generate.rb +16 -0
  8. data/cmd/run.rb +126 -0
  9. data/doc/Class Reference +362 -0
  10. data/doc/Command Reference +153 -0
  11. data/doc/Tutorial +237 -0
  12. data/lib/tap.rb +6 -45
  13. data/lib/tap/app.rb +126 -500
  14. data/lib/tap/constants.rb +2 -29
  15. data/lib/tap/env.rb +555 -250
  16. data/lib/tap/file_task.rb +60 -103
  17. data/lib/tap/generator/base.rb +109 -0
  18. data/lib/tap/generator/destroy.rb +37 -0
  19. data/lib/tap/generator/generate.rb +61 -0
  20. data/lib/tap/generator/generators/command/command_generator.rb +16 -12
  21. data/lib/tap/generator/generators/command/templates/command.erb +13 -19
  22. data/lib/tap/generator/generators/config/config_generator.rb +18 -27
  23. data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
  24. data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
  25. data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -11
  26. data/lib/tap/generator/generators/file_task/templates/file.txt +11 -2
  27. data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
  28. data/lib/tap/generator/generators/file_task/templates/task.erb +24 -31
  29. data/lib/tap/generator/generators/file_task/templates/test.erb +18 -22
  30. data/lib/tap/generator/generators/root/root_generator.rb +45 -31
  31. data/lib/tap/generator/generators/root/templates/Rakefile +64 -41
  32. data/lib/tap/generator/generators/root/templates/gemspec +27 -0
  33. data/lib/tap/generator/generators/root/templates/tapfile +8 -0
  34. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -0
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  36. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +21 -28
  38. data/lib/tap/generator/generators/task/templates/task.erb +13 -23
  39. data/lib/tap/generator/generators/task/templates/test.erb +15 -18
  40. data/lib/tap/generator/manifest.rb +14 -0
  41. data/lib/tap/patches/rake/rake_test_loader.rb +0 -0
  42. data/lib/tap/patches/rake/testtask.rb +0 -0
  43. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -0
  44. data/lib/tap/patches/ruby19/parsedate.rb +0 -0
  45. data/lib/tap/root.rb +260 -21
  46. data/lib/tap/support/aggregator.rb +11 -11
  47. data/lib/tap/support/assignments.rb +172 -0
  48. data/lib/tap/support/audit.rb +20 -18
  49. data/lib/tap/support/batchable.rb +21 -10
  50. data/lib/tap/support/batchable_class.rb +107 -0
  51. data/lib/tap/support/class_configuration.rb +154 -239
  52. data/lib/tap/support/command_line.rb +97 -102
  53. data/lib/tap/support/comment.rb +270 -0
  54. data/lib/tap/support/configurable.rb +86 -65
  55. data/lib/tap/support/configurable_class.rb +296 -0
  56. data/lib/tap/support/configuration.rb +122 -0
  57. data/lib/tap/support/constant.rb +70 -0
  58. data/lib/tap/support/constant_utils.rb +127 -0
  59. data/lib/tap/support/declarations.rb +111 -0
  60. data/lib/tap/support/executable.rb +30 -17
  61. data/lib/tap/support/executable_queue.rb +0 -0
  62. data/lib/tap/support/framework.rb +71 -0
  63. data/lib/tap/support/framework_class.rb +199 -0
  64. data/lib/tap/support/instance_configuration.rb +147 -0
  65. data/lib/tap/support/lazydoc.rb +428 -0
  66. data/lib/tap/support/manifest.rb +89 -0
  67. data/lib/tap/support/run_error.rb +0 -0
  68. data/lib/tap/support/shell_utils.rb +33 -9
  69. data/lib/tap/support/summary.rb +30 -0
  70. data/lib/tap/support/tdoc.rb +339 -134
  71. data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -0
  72. data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -0
  73. data/lib/tap/support/templater.rb +180 -0
  74. data/lib/tap/support/validation.rb +409 -76
  75. data/lib/tap/support/versions.rb +5 -3
  76. data/lib/tap/task.rb +78 -174
  77. data/lib/tap/tasks/dump.rb +56 -0
  78. data/lib/tap/tasks/rake.rb +93 -0
  79. data/lib/tap/test.rb +3 -3
  80. data/lib/tap/test/env_vars.rb +2 -2
  81. data/lib/tap/test/file_methods.rb +19 -20
  82. data/lib/tap/test/script_methods.rb +144 -0
  83. data/lib/tap/test/subset_methods.rb +1 -1
  84. data/lib/tap/test/tap_methods.rb +28 -62
  85. data/lib/tap/workflow.rb +22 -39
  86. metadata +48 -179
  87. data/Basic Overview +0 -151
  88. data/Command Reference +0 -99
  89. data/Rakefile +0 -127
  90. data/Tutorial +0 -287
  91. data/lib/tap/cmd/console.rb +0 -31
  92. data/lib/tap/cmd/destroy.rb +0 -20
  93. data/lib/tap/cmd/generate.rb +0 -20
  94. data/lib/tap/cmd/run.rb +0 -151
  95. data/lib/tap/dump.rb +0 -57
  96. data/lib/tap/generator.rb +0 -91
  97. data/lib/tap/generator/generators/command/USAGE +0 -6
  98. data/lib/tap/generator/generators/config/USAGE +0 -21
  99. data/lib/tap/generator/generators/config/templates/config.erb +0 -1
  100. data/lib/tap/generator/generators/file_task/USAGE +0 -3
  101. data/lib/tap/generator/generators/file_task/templates/file.yml +0 -3
  102. data/lib/tap/generator/generators/generator/USAGE +0 -0
  103. data/lib/tap/generator/generators/generator/generator_generator.rb +0 -21
  104. data/lib/tap/generator/generators/generator/templates/generator.erb +0 -32
  105. data/lib/tap/generator/generators/generator/templates/usage.erb +0 -1
  106. data/lib/tap/generator/generators/root/USAGE +0 -0
  107. data/lib/tap/generator/generators/root/templates/ReadMe.txt +0 -0
  108. data/lib/tap/generator/generators/root/templates/tap.yml +0 -80
  109. data/lib/tap/generator/generators/task/USAGE +0 -3
  110. data/lib/tap/generator/generators/workflow/USAGE +0 -0
  111. data/lib/tap/generator/generators/workflow/templates/task.erb +0 -16
  112. data/lib/tap/generator/generators/workflow/templates/test.erb +0 -7
  113. data/lib/tap/generator/generators/workflow/workflow_generator.rb +0 -6
  114. data/lib/tap/generator/options.rb +0 -26
  115. data/lib/tap/generator/usage.rb +0 -26
  116. data/lib/tap/support/batchable_methods.rb +0 -34
  117. data/lib/tap/support/command_line_methods.rb +0 -76
  118. data/lib/tap/support/configurable_methods.rb +0 -224
  119. data/lib/tap/support/logger.rb +0 -88
  120. data/lib/tap/support/rake.rb +0 -43
  121. data/lib/tap/support/tdoc/config_attr.rb +0 -362
  122. data/test/app/config/another/task.yml +0 -1
  123. data/test/app/config/batch.yml +0 -2
  124. data/test/app/config/empty.yml +0 -0
  125. data/test/app/config/erb.yml +0 -2
  126. data/test/app/config/some/task.yml +0 -1
  127. data/test/app/config/template.yml +0 -2
  128. data/test/app/config/version-0.1.yml +0 -1
  129. data/test/app/config/version.yml +0 -1
  130. data/test/app/lib/app_test_task.rb +0 -3
  131. data/test/app_test.rb +0 -1849
  132. data/test/env/test_configure/recurse_a.yml +0 -2
  133. data/test/env/test_configure/recurse_b.yml +0 -2
  134. data/test/env/test_configure/tap.yml +0 -23
  135. data/test/env/test_load_env_config/dir/tap.yml +0 -3
  136. data/test/env/test_load_env_config/recurse_a.yml +0 -2
  137. data/test/env/test_load_env_config/recurse_b.yml +0 -2
  138. data/test/env/test_load_env_config/tap.yml +0 -3
  139. data/test/env_test.rb +0 -198
  140. data/test/file_task/config/batch.yml +0 -2
  141. data/test/file_task/config/configured.yml +0 -1
  142. data/test/file_task/old_file_one.txt +0 -0
  143. data/test/file_task/old_file_two.txt +0 -0
  144. data/test/file_task_test.rb +0 -1291
  145. data/test/root/alt_lib/alt_module.rb +0 -4
  146. data/test/root/file.txt +0 -0
  147. data/test/root/glob/one.txt +0 -0
  148. data/test/root/glob/two.txt +0 -0
  149. data/test/root/lib/absolute_alt_filepath.rb +0 -2
  150. data/test/root/lib/alternative_filepath.rb +0 -2
  151. data/test/root/lib/another_module.rb +0 -2
  152. data/test/root/lib/nested/some_module.rb +0 -4
  153. data/test/root/lib/no_module_included.rb +0 -0
  154. data/test/root/lib/some/module.rb +0 -4
  155. data/test/root/lib/some_class.rb +0 -2
  156. data/test/root/lib/some_module.rb +0 -3
  157. data/test/root/load_path/load_path_module.rb +0 -2
  158. data/test/root/load_path/skip_module.rb +0 -2
  159. data/test/root/mtime/older.txt +0 -0
  160. data/test/root/unload/full_path.rb +0 -2
  161. data/test/root/unload/loaded_by_nested.rb +0 -2
  162. data/test/root/unload/nested/nested_load.rb +0 -6
  163. data/test/root/unload/nested/nested_with_ext.rb +0 -4
  164. data/test/root/unload/nested/relative_path.rb +0 -4
  165. data/test/root/unload/older.rb +0 -2
  166. data/test/root/unload/unload_base.rb +0 -9
  167. data/test/root/versions/another.yml +0 -0
  168. data/test/root/versions/file-0.1.2.yml +0 -0
  169. data/test/root/versions/file-0.1.yml +0 -0
  170. data/test/root/versions/file.yml +0 -0
  171. data/test/root_test.rb +0 -718
  172. data/test/support/aggregator_test.rb +0 -99
  173. data/test/support/audit_test.rb +0 -445
  174. data/test/support/batchable_test.rb +0 -74
  175. data/test/support/class_configuration_test.rb +0 -331
  176. data/test/support/command_line_test.rb +0 -58
  177. data/test/support/configurable/config/configured.yml +0 -2
  178. data/test/support/configurable_test.rb +0 -295
  179. data/test/support/executable_queue_test.rb +0 -103
  180. data/test/support/executable_test.rb +0 -38
  181. data/test/support/logger_test.rb +0 -31
  182. data/test/support/rake_test.rb +0 -37
  183. data/test/support/shell_utils_test.rb +0 -24
  184. data/test/support/tdoc_test.rb +0 -370
  185. data/test/support/validation_test.rb +0 -54
  186. data/test/support/versions_test.rb +0 -103
  187. data/test/tap_test_helper.rb +0 -57
  188. data/test/tap_test_suite.rb +0 -7
  189. data/test/task/config/batch.yml +0 -2
  190. data/test/task/config/batched.yml +0 -2
  191. data/test/task/config/configured.yml +0 -1
  192. data/test/task/config/example.yml +0 -1
  193. data/test/task_base_test.rb +0 -24
  194. data/test/task_syntax_test.rb +0 -300
  195. data/test/task_test.rb +0 -320
  196. data/test/test/env_vars_test.rb +0 -48
  197. data/test/test/file_methods/test_assert_files/expected/one.txt +0 -1
  198. data/test/test/file_methods/test_assert_files/expected/two.txt +0 -1
  199. data/test/test/file_methods/test_assert_files/input/one.txt +0 -1
  200. data/test/test/file_methods/test_assert_files/input/two.txt +0 -1
  201. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/one.txt +0 -1
  202. data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/two.txt +0 -1
  203. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +0 -1
  204. data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/two.txt +0 -1
  205. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +0 -1
  206. data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +0 -1
  207. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/expected/one.txt +0 -1
  208. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +0 -1
  209. data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +0 -1
  210. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +0 -1
  211. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +0 -1
  212. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +0 -1
  213. data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +0 -1
  214. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +0 -1
  215. data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +0 -1
  216. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  217. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  218. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  219. data/test/test/file_methods_doc/test_sub/expected/one.txt +0 -1
  220. data/test/test/file_methods_doc/test_sub/expected/two.txt +0 -1
  221. data/test/test/file_methods_doc/test_sub/input/one.txt +0 -1
  222. data/test/test/file_methods_doc/test_sub/input/two.txt +0 -1
  223. data/test/test/file_methods_doc_test.rb +0 -29
  224. data/test/test/file_methods_test.rb +0 -275
  225. data/test/test/subset_methods_test.rb +0 -171
  226. data/test/test/tap_methods/test_assert_files/expected/task/name/a.txt +0 -1
  227. data/test/test/tap_methods/test_assert_files/expected/task/name/b.txt +0 -1
  228. data/test/test/tap_methods/test_assert_files/input/a.txt +0 -1
  229. data/test/test/tap_methods/test_assert_files/input/b.txt +0 -1
  230. data/test/test/tap_methods_test.rb +0 -399
  231. data/test/workflow_test.rb +0 -120
  232. data/vendor/rails_generator.rb +0 -56
  233. data/vendor/rails_generator/base.rb +0 -263
  234. data/vendor/rails_generator/commands.rb +0 -581
  235. data/vendor/rails_generator/generated_attribute.rb +0 -42
  236. data/vendor/rails_generator/lookup.rb +0 -209
  237. data/vendor/rails_generator/manifest.rb +0 -53
  238. data/vendor/rails_generator/options.rb +0 -143
  239. data/vendor/rails_generator/scripts.rb +0 -83
  240. data/vendor/rails_generator/scripts/destroy.rb +0 -7
  241. data/vendor/rails_generator/scripts/generate.rb +0 -7
  242. data/vendor/rails_generator/scripts/update.rb +0 -12
  243. data/vendor/rails_generator/simple_logger.rb +0 -46
  244. data/vendor/rails_generator/spec.rb +0 -44
File without changes
File without changes
@@ -0,0 +1,180 @@
1
+ require 'ostruct'
2
+ require 'erb'
3
+
4
+ module Tap
5
+ module Support
6
+
7
+ # Templater is a convenience class for creating ERB templates. As
8
+ # an OpenStruct, attributes can be assigned/unassigned at will to
9
+ # a Templater. When the template is built, all the method of
10
+ # Templater (and hence all the assigned attributes) are available
11
+ # in the template.
12
+ #
13
+ # t = Templater.new( "key: <%= value %>")
14
+ # t.value = "default"
15
+ # t.build # => "key: default"
16
+ #
17
+ # t.value = "another"
18
+ # t.build # => "key: another"
19
+ #
20
+ # Templater includes the Templater::Utils utility methods.
21
+ #
22
+ # === ERB Redirection
23
+ #
24
+ # Templater hooks into the ERB templating mechanism by providing itself
25
+ # as the ERB output target (_erbout). ERB concatenates each line of an
26
+ # ERB template to _erbout, as can be seen when you look at the src code
27
+ # evaluated by ERB:
28
+ #
29
+ # e = ERB.new("<%= 1 + 2 %>")
30
+ # e.src # => "_erbout = ''; _erbout.concat(( 1 + 2 ).to_s); _erbout"
31
+ #
32
+ # By setting itself as _erbout, instances of Templater can redirect the
33
+ # output to a temporary target which can then be used in string
34
+ # transformations. For example, redirection allows indentation of
35
+ # nested content:
36
+ #
37
+ # template = %Q{
38
+ # # Un-nested content
39
+ # <% redirect do |target| %>
40
+ # # Nested content
41
+ # <% module_nest("Nesting::Module") { target } %>
42
+ # <% end %>
43
+ # }
44
+ #
45
+ # t = Templater.new(template)
46
+ # t.build
47
+ # # => %Q{
48
+ # # # Un-nested content
49
+ # # module Nesting
50
+ # # module Module
51
+ # # # Nested content
52
+ # #
53
+ # # end
54
+ # # end}
55
+ #
56
+ class Templater < OpenStruct
57
+
58
+ # Utility methods for Templater; mostly string manipulations
59
+ # useful in creating documentation.
60
+ module Utils
61
+
62
+ # yamlize converts the object to YAML (using to_yaml), omitting
63
+ # the header and final newline:
64
+ #
65
+ # {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
66
+ # yamlize {'key' => 'value'} # => "key: value"
67
+ def yamlize(object)
68
+ object.to_yaml[5...-1]
69
+ end
70
+
71
+ # Nest the return of the block in the nesting lines.
72
+ #
73
+ # nest([["\nmodule Some", "end\n"],["module Nested", "end"]]) { "class Const\nend" }
74
+ # # => %Q{
75
+ # # module Some
76
+ # # module Nested
77
+ # # class Const
78
+ # # end
79
+ # # end
80
+ # # end
81
+ # # }
82
+ #
83
+ def nest(nesting, indent=" ", line_sep="\n")
84
+ content = yield
85
+ return content if nesting.empty?
86
+
87
+ depth = nesting.length
88
+ lines = [indent * depth + content.gsub(/#{line_sep}/, line_sep + indent * depth)]
89
+
90
+ nesting.reverse_each do |(start_line, end_line)|
91
+ depth -= 1
92
+ lines.unshift(indent * depth + start_line)
93
+ lines << (indent * depth + end_line)
94
+ end
95
+
96
+ lines.join(line_sep)
97
+ end
98
+
99
+ # Nest the return of the block in the nesting module.
100
+ #
101
+ # module_nest('Some::Nested') { "class Const\nend" }
102
+ # # => %Q{
103
+ # # module Some
104
+ # # module Nested
105
+ # # class Const
106
+ # # end
107
+ # # end
108
+ # # end
109
+ # # }.strip
110
+ #
111
+ def module_nest(const_name, indent=" ", line_sep="\n")
112
+ nesting = const_name.split(/::/).collect do |name|
113
+ ["module #{name}", "end"]
114
+ end
115
+
116
+ nest(nesting, indent, line_sep) { yield }
117
+ end
118
+ end
119
+
120
+ include Utils
121
+
122
+ # Initialized a new Templater. An ERB or String may be provided as the
123
+ # template. If a String is provided, it will be used to initialize an
124
+ # ERB with a trim_mode of "<>".
125
+ def initialize(template, attributes={})
126
+ @template = case template
127
+ when ERB
128
+ if template.instance_variable_get(:@src).index('_erbout =') != 0
129
+ raise ArgumentError, "Templater does not work with ERB templates where eoutvar != '_erbout'"
130
+ end
131
+ template
132
+ when String then ERB.new(template, nil, "<>")
133
+ else raise ArgumentError, "cannot convert #{template.class} into an ERB template"
134
+ end
135
+
136
+ src = @template.instance_variable_get(:@src)
137
+ @template.instance_variable_set(:@src, "self." + src)
138
+
139
+ super(attributes)
140
+ end
141
+
142
+ # Returns self (not the underlying erbout storage that actually receives
143
+ # the output lines). In the ERB context, this method directs erb outputs
144
+ # to Templater#concat and into the redirect mechanism.
145
+ def _erbout
146
+ self
147
+ end
148
+
149
+ # Sets the underlying erbout storage to input.
150
+ def _erbout=(input)
151
+ @_erbout = input
152
+ end
153
+
154
+ # Redirects output of erb to the redirected_erbout string
155
+ # for the duration of the block. When redirect completes,
156
+ # the redirected_erbout is concatenated to the main
157
+ # erbout storage.
158
+ def redirect # :yields: redirected_erbout
159
+ current = @_erbout
160
+ @_erbout = ""
161
+ result = yield(@_erbout)
162
+ @_erbout = current
163
+ concat(result)
164
+ end
165
+
166
+ # Concatenates the specified input to the underlying erbout storage.
167
+ def concat(input)
168
+ @_erbout << input
169
+ end
170
+
171
+ # Build the template. All methods of self will be
172
+ # accessible in the template.
173
+ def build
174
+ @template.result(binding)
175
+ @_erbout
176
+ end
177
+
178
+ end
179
+ end
180
+ end
@@ -1,77 +1,410 @@
1
- autoload(:PP, 'pp')
2
-
3
- module Tap
4
- module Support
5
-
6
- # Validation generates blocks for common validations/processing of
7
- # configurations set through Configurable. These blocks can be passed
8
- # to the config declarations using an ampersand (&).
9
- #
10
- # See the 'Configuration' section in the Tap::Task documentation for
11
- # more details on how Validation works in practice.
12
- module Validation
13
-
14
- # Raised when Validation blocks fail.
15
- class ValidationError < ArgumentError
16
- def initialize(input, validations)
17
- validation_str = PP.singleline_pp(validations, "")
18
- super PP.singleline_pp(input, "expected #{validation_str} but was: ")
19
- end
20
- end
21
-
22
- module_function
23
-
24
- # Yaml conversion and checker. Valid if any of the validations
25
- # match in a case statement. Otherwise raises an error.
26
-
27
- # Returns input if any of the validations match the input, as
28
- # in a case statement. Raises a ValidationError otherwise.
29
- #
30
- # For example:
31
- #
32
- # validate(10, [Integer, nil])
33
- #
34
- # Does the same as:
35
- #
36
- # case 10
37
- # when Integer, nil then input
38
- # else raise ValidationError.new(...)
39
- # end
40
- #
41
- def validate(input, validations)
42
- case input
43
- when *validations then input
44
- else
45
- raise ValidationError.new(input, validations)
46
- end
47
- end
48
-
49
- # Returns a block that calls validate using the block input
50
- # and the input validations.
51
- def check(*validations)
52
- lambda {|input| validate(input, validations) }
53
- end
54
-
55
- # Returns a block that loads input strings as YAML, then
56
- # calls validate with the result and the input validations.
57
- # If the block input is not a string, the block input is
58
- # validated.
59
- #
60
- # b = yaml(Integer, nil)
61
- # b.class # => Proc
62
- # b.call(1) # => 1
63
- # b.call("1") # => 1
64
- # b.call(nil) # => nil
65
- # b.call("str") # => ValidationError
66
- #
67
- # Note: yaml is especially useful for validating configs
68
- # that may be specified as strings or as an actual object.
69
- def yaml(*validations)
70
- lambda do |input|
71
- res = input.kind_of?(String) ? YAML.load(input) : input
72
- validate(res, validations)
73
- end
74
- end
75
- end
76
- end
1
+ module Tap
2
+ module Support
3
+
4
+ # Validation generates blocks for common validations and transformations of
5
+ # configurations set through Configurable. In general these blocks allow
6
+ # configurations to be set to objects of a particular class, or to a string
7
+ # that can be loaded as YAML into such an object.
8
+ #
9
+ # integer = Validation.integer
10
+ # integer.class # => Proc
11
+ # integer.call(1) # => 1
12
+ # integer.call('1') # => 1
13
+ # integer.call(nil) # => ValidationError
14
+ #
15
+ #--
16
+ # Note the unusual syntax for declaring constants that are blocks
17
+ # defined by lambda... ex:
18
+ #
19
+ # block = lambda {}
20
+ # CONST = block
21
+ #
22
+ # This syntax plays well with RDoc, which otherwise gets jacked
23
+ # when you do it all in one step.
24
+ #++
25
+ module Validation
26
+
27
+ # Raised when Validation blocks fail.
28
+ class ValidationError < ArgumentError
29
+ def initialize(input, validations)
30
+ super case
31
+ when validations.empty?
32
+ "no validations specified"
33
+ else
34
+ validation_str = PP.singleline_pp(validations, "")
35
+ PP.singleline_pp(input, "expected #{validation_str} but was: ")
36
+ end
37
+ end
38
+ end
39
+
40
+ # Raised when yamlization fails.
41
+ class YamlizationError < ArgumentError
42
+ def initialize(input, error)
43
+ super "#{error} ('#{input}')"
44
+ end
45
+ end
46
+
47
+ module_function
48
+
49
+ # Yaml conversion and checker. Valid if any of the validations
50
+ # match in a case statement. Otherwise raises an error.
51
+
52
+ # Returns input if any of the validations match any of the
53
+ # inputs, as in a case statement. Raises a ValidationError
54
+ # otherwise. For example:
55
+ #
56
+ # validate(10, [Integer, nil])
57
+ #
58
+ # Does the same as:
59
+ #
60
+ # case 10
61
+ # when Integer, nil then input
62
+ # else raise ValidationError.new(...)
63
+ # end
64
+ #
65
+ # Note the validations input must be an Array or nil;
66
+ # validate will raise an ArgumentError otherwise.
67
+ # All inputs are considered VALID if validations == nil.
68
+ def validate(input, validations)
69
+ case validations
70
+ when Array
71
+
72
+ case input
73
+ when *validations then input
74
+ else raise ValidationError.new(input, validations)
75
+ end
76
+
77
+ when nil then input
78
+ else raise ArgumentError.new("validations must be nil, or an array of valid inputs")
79
+ end
80
+ end
81
+
82
+ # Attempts to load the input as YAML. Raises a YamlizationError
83
+ # for errors.
84
+ def yamlize(input)
85
+ begin
86
+ YAML.load(input)
87
+ rescue
88
+ raise YamlizationError.new(input, $!.message)
89
+ end
90
+ end
91
+
92
+ # Returns a block that calls validate using the block input
93
+ # and the input validations. Raises an error if no validations
94
+ # are specified.
95
+ def check(*validations)
96
+ raise ArgumentError.new("no validations specified") if validations.empty?
97
+ lambda {|input| validate(input, validations) }
98
+ end
99
+
100
+ # Returns a block that loads input strings as YAML, then
101
+ # calls validate with the result and the input validations.
102
+ # Non-string inputs are not converted.
103
+ #
104
+ # b = yaml(Integer, nil)
105
+ # b.class # => Proc
106
+ # b.call(1) # => 1
107
+ # b.call("1") # => 1
108
+ # b.call(nil) # => nil
109
+ # b.call("str") # => ValidationError
110
+ #
111
+ # If no validations are specified, the result will be
112
+ # returned without validation.
113
+ def yaml(*validations)
114
+ lambda do |input|
115
+ res = input.kind_of?(String) ? yamlize(input) : input
116
+ validations.empty? ? res : validate(res, validations)
117
+ end
118
+ end
119
+
120
+ # Returns a block loads a String input as YAML then
121
+ # validates the result is valid using the input
122
+ # validations. If the input is not a String, the
123
+ # input is validated directly.
124
+ def yamlize_and_check(*validations)
125
+ lambda do |input|
126
+ input = yamlize(input) if input.kind_of?(String)
127
+ validate(input, validations)
128
+ end
129
+ end
130
+
131
+ # Returns a block that checks the input is a string.
132
+ # Moreover, strings are re-evaluated as string
133
+ # literals using %Q.
134
+ #
135
+ # string.class # => Proc
136
+ # string.call('str') # => 'str'
137
+ # string.call('\n') # => "\n"
138
+ # string.call("\n") # => "\n"
139
+ # string.call("%s") # => "%s"
140
+ # string.call(nil) # => ValidationError
141
+ # string.call(:sym) # => ValidationError
142
+ #
143
+ def string(); STRING; end
144
+ string_validation_block = lambda do |input|
145
+ input = validate(input, [String])
146
+ eval %Q{"#{input}"}
147
+ end
148
+ STRING = string_validation_block
149
+
150
+ # Same as string but allows nil. Note the special
151
+ # behavior of the nil string '~' -- rather than
152
+ # being treated as a string, it is processed as nil
153
+ # to be consistent with the other [class]_or_nil
154
+ # methods.
155
+ #
156
+ # string_or_nil.call('~') # => nil
157
+ # string_or_nil.call(nil) # => nil
158
+ def string_or_nil(); STRING_OR_NIL; end
159
+ string_or_nil_validation_block = lambda do |input|
160
+ input = validate(input, [String, nil])
161
+ case input
162
+ when nil, '~' then nil
163
+ else eval %Q{"#{input}"}
164
+ end
165
+ end
166
+ STRING_OR_NIL = string_or_nil_validation_block
167
+
168
+ # Returns a block that checks the input is a symbol.
169
+ # String inputs are loaded as yaml first.
170
+ #
171
+ # symbol.class # => Proc
172
+ # symbol.call(:sym) # => :sym
173
+ # symbol.call(':sym') # => :sym
174
+ # symbol.call(nil) # => ValidationError
175
+ # symbol.call('str') # => ValidationError
176
+ #
177
+ def symbol(); SYMBOL; end
178
+ SYMBOL = yamlize_and_check(Symbol)
179
+
180
+ # Same as symbol but allows nil:
181
+ #
182
+ # symbol_or_nil.call('~') # => nil
183
+ # symbol_or_nil.call(nil) # => nil
184
+ def symbol_or_nil(); SYMBOL_OR_NIL; end
185
+ SYMBOL_OR_NIL = yamlize_and_check(Symbol, nil)
186
+
187
+ # Returns a block that checks the input is true, false or nil.
188
+ # String inputs are loaded as yaml first.
189
+ #
190
+ # boolean.class # => Proc
191
+ # boolean.call(true) # => true
192
+ # boolean.call(false) # => false
193
+ # boolean.call(nil) # => nil
194
+ #
195
+ # boolean.call('true') # => true
196
+ # boolean.call('yes') # => true
197
+ # boolean.call('FALSE') # => false
198
+ #
199
+ # boolean.call(1) # => ValidationError
200
+ # boolean.call("str") # => ValidationError
201
+ #
202
+ def boolean(); BOOLEAN; end
203
+ BOOLEAN = yamlize_and_check(true, false, nil)
204
+
205
+ def switch(); SWITCH; end
206
+ SWITCH = yamlize_and_check(true, false, nil)
207
+
208
+ def flag(); FLAG; end
209
+ FLAG = yamlize_and_check(true, false, nil)
210
+
211
+ # Returns a block that checks the input is an array.
212
+ # String inputs are loaded as yaml first.
213
+ #
214
+ # array.class # => Proc
215
+ # array.call([1,2,3]) # => [1,2,3]
216
+ # array.call('[1, 2, 3]') # => [1,2,3]
217
+ # array.call(nil) # => ValidationError
218
+ # array.call('str') # => ValidationError
219
+ #
220
+ def array(); ARRAY; end
221
+ ARRAY = yamlize_and_check(Array)
222
+
223
+ # Same as array but allows nil:
224
+ #
225
+ # array_or_nil.call('~') # => nil
226
+ # array_or_nil.call(nil) # => nil
227
+ def array_or_nil(); ARRAY_OR_NIL; end
228
+ ARRAY_OR_NIL = yamlize_and_check(Array, nil)
229
+
230
+ def list(); LIST; end
231
+ list_block = lambda do |input|
232
+ if input.kind_of?(String)
233
+ input = case processed_input = yamlize(input)
234
+ when Array then processed_input
235
+ else input.split(/,/).collect {|arg| yamlize(arg) }
236
+ end
237
+ end
238
+
239
+ validate(input, [Array])
240
+ end
241
+ LIST = list_block
242
+
243
+ # Returns a block that checks the input is a hash.
244
+ # String inputs are loaded as yaml first.
245
+ #
246
+ # hash.class # => Proc
247
+ # hash.call({'key' => 'value'}) # => {'key' => 'value'}
248
+ # hash.call('key: value') # => {'key' => 'value'}
249
+ # hash.call(nil) # => ValidationError
250
+ # hash.call('str') # => ValidationError
251
+ #
252
+ def hash(); HASH; end
253
+ HASH = yamlize_and_check(Hash)
254
+
255
+ # Same as hash but allows nil:
256
+ #
257
+ # hash_or_nil.call('~') # => nil
258
+ # hash_or_nil.call(nil) # => nil
259
+ def hash_or_nil(); HASH_OR_NIL; end
260
+ HASH_OR_NIL = yamlize_and_check(Hash, nil)
261
+
262
+ # Returns a block that checks the input is an integer.
263
+ # String inputs are loaded as yaml first.
264
+ #
265
+ # integer.class # => Proc
266
+ # integer.call(1) # => 1
267
+ # integer.call('1') # => 1
268
+ # integer.call(1.1) # => ValidationError
269
+ # integer.call(nil) # => ValidationError
270
+ # integer.call('str') # => ValidationError
271
+ #
272
+ def integer(); INTEGER; end
273
+ INTEGER = yamlize_and_check(Integer)
274
+
275
+ # Same as integer but allows nil:
276
+ #
277
+ # integer_or_nil.call('~') # => nil
278
+ # integer_or_nil.call(nil) # => nil
279
+ def integer_or_nil(); INTEGER_OR_NIL; end
280
+ INTEGER_OR_NIL = yamlize_and_check(Integer, nil)
281
+
282
+ # Returns a block that checks the input is a float.
283
+ # String inputs are loaded as yaml first.
284
+ #
285
+ # float.class # => Proc
286
+ # float.call(1.1) # => 1.1
287
+ # float.call('1.1') # => 1.1
288
+ # float.call('1.0e+6') # => 1e6
289
+ # float.call(1) # => ValidationError
290
+ # float.call(nil) # => ValidationError
291
+ # float.call('str') # => ValidationError
292
+ #
293
+ def float(); FLOAT; end
294
+ FLOAT = yamlize_and_check(Float)
295
+
296
+ # Same as float but allows nil:
297
+ #
298
+ # float_or_nil.call('~') # => nil
299
+ # float_or_nil.call(nil) # => nil
300
+ def float_or_nil(); FLOAT_OR_NIL; end
301
+ FLOAT_OR_NIL = yamlize_and_check(Float, nil)
302
+
303
+ # Returns a block that checks the input is a number.
304
+ # String inputs are loaded as yaml first.
305
+ #
306
+ # num.class # => Proc
307
+ # num.call(1.1) # => 1.1
308
+ # num.call(1) # => 1
309
+ # num.call(1e6) # => 1e6
310
+ # num.call('1.1') # => 1.1
311
+ # num.call('1.0e+6') # => 1e6
312
+ # num.call(nil) # => ValidationError
313
+ # num.call('str') # => ValidationError
314
+ #
315
+ def num(); NUMERIC; end
316
+ NUMERIC = yamlize_and_check(Numeric)
317
+
318
+ # Same as num but allows nil:
319
+ #
320
+ # num_or_nil.call('~') # => nil
321
+ # num_or_nil.call(nil) # => nil
322
+ def num_or_nil(); NUMERIC_OR_NIL; end
323
+ NUMERIC_OR_NIL = yamlize_and_check(Numeric, nil)
324
+
325
+ # Returns a block that checks the input is a regexp.
326
+ # String inputs are converted to regexps using
327
+ # Regexp#new.
328
+ #
329
+ # regexp.class # => Proc
330
+ # regexp.call(/regexp/) # => /regexp/
331
+ # regexp.call('regexp') # => /regexp/
332
+ #
333
+ # # use of ruby-specific flags can turn on/off
334
+ # # features like case insensitive matching
335
+ # regexp.call('(?i)regexp') # => /(?i)regexp/
336
+ #
337
+ def regexp(); REGEXP; end
338
+ regexp_block = lambda do |input|
339
+ input = Regexp.new(input) if input.kind_of?(String)
340
+ validate(input, [Regexp])
341
+ end
342
+ REGEXP = regexp_block
343
+
344
+ # Same as regexp but allows nil. Note the special
345
+ # behavior of the nil string '~' -- rather than
346
+ # being converted to a regexp, it is processed as
347
+ # nil to be consistent with the other [class]_or_nil
348
+ # methods.
349
+ #
350
+ # regexp_or_nil.call('~') # => nil
351
+ # regexp_or_nil.call(nil) # => nil
352
+ def regexp_or_nil(); REGEXP_OR_NIL; end
353
+ regexp_or_nil_block = lambda do |input|
354
+ input = case input
355
+ when nil, '~' then nil
356
+ when String then Regexp.new(input)
357
+ else input
358
+ end
359
+
360
+ validate(input, [Regexp, nil])
361
+ end
362
+ REGEXP_OR_NIL = regexp_or_nil_block
363
+
364
+ # Returns a block that checks the input is a range.
365
+ # String inputs are split into a beginning and
366
+ # end if possible, where each part is loaded as
367
+ # yaml before being used to construct a Range.a
368
+ #
369
+ # range.class # => Proc
370
+ # range.call(1..10) # => 1..10
371
+ # range.call('1..10') # => 1..10
372
+ # range.call('a..z') # => 'a'..'z'
373
+ # range.call('-10...10') # => -10...10
374
+ # range.call(nil) # => ValidationError
375
+ # range.call('1.10') # => ValidationError
376
+ # range.call('a....z') # => ValidationError
377
+ #
378
+ def range(); RANGE; end
379
+ range_block = lambda do |input|
380
+ if input.kind_of?(String) && input =~ /^([^.]+)(\.{2,3})([^.]+)$/
381
+ input = Range.new(yamlize($1), yamlize($3), $2.length == 3)
382
+ end
383
+ validate(input, [Range])
384
+ end
385
+ RANGE = range_block
386
+
387
+ # Same as range but allows nil:
388
+ #
389
+ # range_or_nil.call('~') # => nil
390
+ # range_or_nil.call(nil) # => nil
391
+ def range_or_nil(); RANGE_OR_NIL; end
392
+ range_or_nil_block = lambda do |input|
393
+ input = case input
394
+ when nil, '~' then nil
395
+ when String
396
+ if input =~ /^([^.]+)(\.{2,3})([^.]+)$/
397
+ Range.new(yamlize($1), yamlize($3), $2.length == 3)
398
+ else
399
+ input
400
+ end
401
+ else input
402
+ end
403
+
404
+ validate(input, [Range, nil])
405
+ end
406
+ RANGE_OR_NIL = range_or_nil_block
407
+
408
+ end
409
+ end
77
410
  end