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.
- data/History +37 -30
- data/MIT-LICENSE +1 -1
- data/README +92 -44
- data/bin/tap +62 -75
- data/cmd/console.rb +42 -0
- data/cmd/destroy.rb +16 -0
- data/cmd/generate.rb +16 -0
- data/cmd/run.rb +126 -0
- data/doc/Class Reference +362 -0
- data/doc/Command Reference +153 -0
- data/doc/Tutorial +237 -0
- data/lib/tap.rb +6 -45
- data/lib/tap/app.rb +126 -500
- data/lib/tap/constants.rb +2 -29
- data/lib/tap/env.rb +555 -250
- data/lib/tap/file_task.rb +60 -103
- data/lib/tap/generator/base.rb +109 -0
- data/lib/tap/generator/destroy.rb +37 -0
- data/lib/tap/generator/generate.rb +61 -0
- data/lib/tap/generator/generators/command/command_generator.rb +16 -12
- data/lib/tap/generator/generators/command/templates/command.erb +13 -19
- data/lib/tap/generator/generators/config/config_generator.rb +18 -27
- data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
- data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
- data/lib/tap/generator/generators/file_task/file_task_generator.rb +16 -11
- data/lib/tap/generator/generators/file_task/templates/file.txt +11 -2
- data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
- data/lib/tap/generator/generators/file_task/templates/task.erb +24 -31
- data/lib/tap/generator/generators/file_task/templates/test.erb +18 -22
- data/lib/tap/generator/generators/root/root_generator.rb +45 -31
- data/lib/tap/generator/generators/root/templates/Rakefile +64 -41
- data/lib/tap/generator/generators/root/templates/gemspec +27 -0
- data/lib/tap/generator/generators/root/templates/tapfile +8 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -0
- data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
- data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
- data/lib/tap/generator/generators/task/task_generator.rb +21 -28
- data/lib/tap/generator/generators/task/templates/task.erb +13 -23
- data/lib/tap/generator/generators/task/templates/test.erb +15 -18
- data/lib/tap/generator/manifest.rb +14 -0
- data/lib/tap/patches/rake/rake_test_loader.rb +0 -0
- data/lib/tap/patches/rake/testtask.rb +0 -0
- data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -0
- data/lib/tap/patches/ruby19/parsedate.rb +0 -0
- data/lib/tap/root.rb +260 -21
- data/lib/tap/support/aggregator.rb +11 -11
- data/lib/tap/support/assignments.rb +172 -0
- data/lib/tap/support/audit.rb +20 -18
- data/lib/tap/support/batchable.rb +21 -10
- data/lib/tap/support/batchable_class.rb +107 -0
- data/lib/tap/support/class_configuration.rb +154 -239
- data/lib/tap/support/command_line.rb +97 -102
- data/lib/tap/support/comment.rb +270 -0
- data/lib/tap/support/configurable.rb +86 -65
- data/lib/tap/support/configurable_class.rb +296 -0
- data/lib/tap/support/configuration.rb +122 -0
- data/lib/tap/support/constant.rb +70 -0
- data/lib/tap/support/constant_utils.rb +127 -0
- data/lib/tap/support/declarations.rb +111 -0
- data/lib/tap/support/executable.rb +30 -17
- data/lib/tap/support/executable_queue.rb +0 -0
- data/lib/tap/support/framework.rb +71 -0
- data/lib/tap/support/framework_class.rb +199 -0
- data/lib/tap/support/instance_configuration.rb +147 -0
- data/lib/tap/support/lazydoc.rb +428 -0
- data/lib/tap/support/manifest.rb +89 -0
- data/lib/tap/support/run_error.rb +0 -0
- data/lib/tap/support/shell_utils.rb +33 -9
- data/lib/tap/support/summary.rb +30 -0
- data/lib/tap/support/tdoc.rb +339 -134
- data/lib/tap/support/tdoc/tdoc_html_generator.rb +0 -0
- data/lib/tap/support/tdoc/tdoc_html_template.rb +0 -0
- data/lib/tap/support/templater.rb +180 -0
- data/lib/tap/support/validation.rb +409 -76
- data/lib/tap/support/versions.rb +5 -3
- data/lib/tap/task.rb +78 -174
- data/lib/tap/tasks/dump.rb +56 -0
- data/lib/tap/tasks/rake.rb +93 -0
- data/lib/tap/test.rb +3 -3
- data/lib/tap/test/env_vars.rb +2 -2
- data/lib/tap/test/file_methods.rb +19 -20
- data/lib/tap/test/script_methods.rb +144 -0
- data/lib/tap/test/subset_methods.rb +1 -1
- data/lib/tap/test/tap_methods.rb +28 -62
- data/lib/tap/workflow.rb +22 -39
- metadata +48 -179
- data/Basic Overview +0 -151
- data/Command Reference +0 -99
- data/Rakefile +0 -127
- data/Tutorial +0 -287
- data/lib/tap/cmd/console.rb +0 -31
- data/lib/tap/cmd/destroy.rb +0 -20
- data/lib/tap/cmd/generate.rb +0 -20
- data/lib/tap/cmd/run.rb +0 -151
- data/lib/tap/dump.rb +0 -57
- data/lib/tap/generator.rb +0 -91
- data/lib/tap/generator/generators/command/USAGE +0 -6
- data/lib/tap/generator/generators/config/USAGE +0 -21
- data/lib/tap/generator/generators/config/templates/config.erb +0 -1
- data/lib/tap/generator/generators/file_task/USAGE +0 -3
- data/lib/tap/generator/generators/file_task/templates/file.yml +0 -3
- data/lib/tap/generator/generators/generator/USAGE +0 -0
- data/lib/tap/generator/generators/generator/generator_generator.rb +0 -21
- data/lib/tap/generator/generators/generator/templates/generator.erb +0 -32
- data/lib/tap/generator/generators/generator/templates/usage.erb +0 -1
- data/lib/tap/generator/generators/root/USAGE +0 -0
- data/lib/tap/generator/generators/root/templates/ReadMe.txt +0 -0
- data/lib/tap/generator/generators/root/templates/tap.yml +0 -80
- data/lib/tap/generator/generators/task/USAGE +0 -3
- data/lib/tap/generator/generators/workflow/USAGE +0 -0
- data/lib/tap/generator/generators/workflow/templates/task.erb +0 -16
- data/lib/tap/generator/generators/workflow/templates/test.erb +0 -7
- data/lib/tap/generator/generators/workflow/workflow_generator.rb +0 -6
- data/lib/tap/generator/options.rb +0 -26
- data/lib/tap/generator/usage.rb +0 -26
- data/lib/tap/support/batchable_methods.rb +0 -34
- data/lib/tap/support/command_line_methods.rb +0 -76
- data/lib/tap/support/configurable_methods.rb +0 -224
- data/lib/tap/support/logger.rb +0 -88
- data/lib/tap/support/rake.rb +0 -43
- data/lib/tap/support/tdoc/config_attr.rb +0 -362
- data/test/app/config/another/task.yml +0 -1
- data/test/app/config/batch.yml +0 -2
- data/test/app/config/empty.yml +0 -0
- data/test/app/config/erb.yml +0 -2
- data/test/app/config/some/task.yml +0 -1
- data/test/app/config/template.yml +0 -2
- data/test/app/config/version-0.1.yml +0 -1
- data/test/app/config/version.yml +0 -1
- data/test/app/lib/app_test_task.rb +0 -3
- data/test/app_test.rb +0 -1849
- data/test/env/test_configure/recurse_a.yml +0 -2
- data/test/env/test_configure/recurse_b.yml +0 -2
- data/test/env/test_configure/tap.yml +0 -23
- data/test/env/test_load_env_config/dir/tap.yml +0 -3
- data/test/env/test_load_env_config/recurse_a.yml +0 -2
- data/test/env/test_load_env_config/recurse_b.yml +0 -2
- data/test/env/test_load_env_config/tap.yml +0 -3
- data/test/env_test.rb +0 -198
- data/test/file_task/config/batch.yml +0 -2
- data/test/file_task/config/configured.yml +0 -1
- data/test/file_task/old_file_one.txt +0 -0
- data/test/file_task/old_file_two.txt +0 -0
- data/test/file_task_test.rb +0 -1291
- data/test/root/alt_lib/alt_module.rb +0 -4
- data/test/root/file.txt +0 -0
- data/test/root/glob/one.txt +0 -0
- data/test/root/glob/two.txt +0 -0
- data/test/root/lib/absolute_alt_filepath.rb +0 -2
- data/test/root/lib/alternative_filepath.rb +0 -2
- data/test/root/lib/another_module.rb +0 -2
- data/test/root/lib/nested/some_module.rb +0 -4
- data/test/root/lib/no_module_included.rb +0 -0
- data/test/root/lib/some/module.rb +0 -4
- data/test/root/lib/some_class.rb +0 -2
- data/test/root/lib/some_module.rb +0 -3
- data/test/root/load_path/load_path_module.rb +0 -2
- data/test/root/load_path/skip_module.rb +0 -2
- data/test/root/mtime/older.txt +0 -0
- data/test/root/unload/full_path.rb +0 -2
- data/test/root/unload/loaded_by_nested.rb +0 -2
- data/test/root/unload/nested/nested_load.rb +0 -6
- data/test/root/unload/nested/nested_with_ext.rb +0 -4
- data/test/root/unload/nested/relative_path.rb +0 -4
- data/test/root/unload/older.rb +0 -2
- data/test/root/unload/unload_base.rb +0 -9
- data/test/root/versions/another.yml +0 -0
- data/test/root/versions/file-0.1.2.yml +0 -0
- data/test/root/versions/file-0.1.yml +0 -0
- data/test/root/versions/file.yml +0 -0
- data/test/root_test.rb +0 -718
- data/test/support/aggregator_test.rb +0 -99
- data/test/support/audit_test.rb +0 -445
- data/test/support/batchable_test.rb +0 -74
- data/test/support/class_configuration_test.rb +0 -331
- data/test/support/command_line_test.rb +0 -58
- data/test/support/configurable/config/configured.yml +0 -2
- data/test/support/configurable_test.rb +0 -295
- data/test/support/executable_queue_test.rb +0 -103
- data/test/support/executable_test.rb +0 -38
- data/test/support/logger_test.rb +0 -31
- data/test/support/rake_test.rb +0 -37
- data/test/support/shell_utils_test.rb +0 -24
- data/test/support/tdoc_test.rb +0 -370
- data/test/support/validation_test.rb +0 -54
- data/test/support/versions_test.rb +0 -103
- data/test/tap_test_helper.rb +0 -57
- data/test/tap_test_suite.rb +0 -7
- data/test/task/config/batch.yml +0 -2
- data/test/task/config/batched.yml +0 -2
- data/test/task/config/configured.yml +0 -1
- data/test/task/config/example.yml +0 -1
- data/test/task_base_test.rb +0 -24
- data/test/task_syntax_test.rb +0 -300
- data/test/task_test.rb +0 -320
- data/test/test/env_vars_test.rb +0 -48
- data/test/test/file_methods/test_assert_files/expected/one.txt +0 -1
- data/test/test/file_methods/test_assert_files/expected/two.txt +0 -1
- data/test/test/file_methods/test_assert_files/input/one.txt +0 -1
- data/test/test/file_methods/test_assert_files/input/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_can_have_no_expected_files_if_specified/input/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_different_content/expected/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_different_content/input/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/expected/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_expected_file/input/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/expected/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_missing_output_file/input/two.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/one.txt +0 -1
- data/test/test/file_methods/test_assert_files_fails_for_no_expected_files/input/two.txt +0 -1
- data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
- data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
- data/test/test/file_methods_doc/test_sub/expected/one.txt +0 -1
- data/test/test/file_methods_doc/test_sub/expected/two.txt +0 -1
- data/test/test/file_methods_doc/test_sub/input/one.txt +0 -1
- data/test/test/file_methods_doc/test_sub/input/two.txt +0 -1
- data/test/test/file_methods_doc_test.rb +0 -29
- data/test/test/file_methods_test.rb +0 -275
- data/test/test/subset_methods_test.rb +0 -171
- data/test/test/tap_methods/test_assert_files/expected/task/name/a.txt +0 -1
- data/test/test/tap_methods/test_assert_files/expected/task/name/b.txt +0 -1
- data/test/test/tap_methods/test_assert_files/input/a.txt +0 -1
- data/test/test/tap_methods/test_assert_files/input/b.txt +0 -1
- data/test/test/tap_methods_test.rb +0 -399
- data/test/workflow_test.rb +0 -120
- data/vendor/rails_generator.rb +0 -56
- data/vendor/rails_generator/base.rb +0 -263
- data/vendor/rails_generator/commands.rb +0 -581
- data/vendor/rails_generator/generated_attribute.rb +0 -42
- data/vendor/rails_generator/lookup.rb +0 -209
- data/vendor/rails_generator/manifest.rb +0 -53
- data/vendor/rails_generator/options.rb +0 -143
- data/vendor/rails_generator/scripts.rb +0 -83
- data/vendor/rails_generator/scripts/destroy.rb +0 -7
- data/vendor/rails_generator/scripts/generate.rb +0 -7
- data/vendor/rails_generator/scripts/update.rb +0 -12
- data/vendor/rails_generator/simple_logger.rb +0 -46
- 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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
#
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|