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
@@ -0,0 +1,270 @@
1
+ require 'strscan'
2
+
3
+ module Tap
4
+ module Support
5
+ # Comment represents a comment parsed by Lazydoc.
6
+ class Comment
7
+
8
+ class << self
9
+
10
+ # Parses the input string into a comment, stopping at end_regexp
11
+ # or the first non-comment line. Also parses the next non-comment
12
+ # lines as the comment subject. Takes a string or a StringScanner
13
+ # and returns the new comment.
14
+ #
15
+ # comment_string = %Q{
16
+ # # comments spanning multiple
17
+ # # lines are collected
18
+ # #
19
+ # # while indented lines
20
+ # # are preserved individually
21
+ # #
22
+ # this is the subject line
23
+ #
24
+ # # this line is not parsed as it
25
+ # # is after a non-comment line
26
+ # }
27
+ #
28
+ # c = Comment.parse(comment_string)
29
+ # c.lines
30
+ # # => [
31
+ # # ['comments spanning multiple', 'lines are collected'],
32
+ # # [''],
33
+ # # [' while indented lines'],
34
+ # # [' are preserved individually'],
35
+ # # [''],
36
+ # # []]
37
+ # c.subject # => "this is the subject line"
38
+ #
39
+ def parse(str, parse_subject=true) # :yields: fragment
40
+ scanner = case str
41
+ when StringScanner then str
42
+ when String then StringScanner.new(str)
43
+ else raise TypeError, "can't convert #{str.class} into StringScanner or String"
44
+ end
45
+
46
+ comment = Comment.new
47
+ while scanner.scan(/\r?\n?[ \t]*#[ \t]?(([ \t]*).*?)\r?$/)
48
+ fragment = scanner[1]
49
+ indent = scanner[2]
50
+
51
+ # collect continuous description line
52
+ # fragments and join into a single line
53
+ if block_given? && yield(fragment)
54
+ # break on comment if the description end is reached
55
+ parse_subject = false
56
+ break
57
+ else
58
+ categorize(fragment, indent) {|f| comment.push(f) }
59
+ end
60
+ end
61
+
62
+ if parse_subject
63
+ scanner.skip(/\s+/)
64
+ unless scanner.peek(1) == '#'
65
+ comment.subject = scanner.scan(/.+?$/)
66
+ comment.subject.strip! unless comment.subject == nil
67
+ end
68
+ end
69
+
70
+ comment
71
+ end
72
+
73
+ # Scans the line checking if it is a comment. If so, scan
74
+ # yields the parse fragments to the block which correspond
75
+ # to the type of comment input (continuation, indent, etc).
76
+ # Returns true if the line is a comment, false otherwise.
77
+ #
78
+ # Scan may be used to build a comment from an array of lines:
79
+ #
80
+ # lines = [
81
+ # "# comments spanning multiple",
82
+ # "# lines are collected",
83
+ # "#",
84
+ # "# while indented lines",
85
+ # "# are preserved individually",
86
+ # "# ",
87
+ # "not a comment line",
88
+ # "# skipped since the loop breaks",
89
+ # "# at the first non-comment line"]
90
+ #
91
+ # c = Comment.new
92
+ # lines.each do |line|
93
+ # break unless Comment.scan(line) do |fragment|
94
+ # # c.unshift will also work if building in reverse
95
+ # c.push(fragment)
96
+ # end
97
+ # end
98
+ #
99
+ # c.lines
100
+ # # => [
101
+ # # ['comments spanning multiple', 'lines are collected'],
102
+ # # [''],
103
+ # # [' while indented lines'],
104
+ # # [' are preserved individually'],
105
+ # # [''],
106
+ # # []]
107
+ #
108
+ def scan(line) # :yields: fragment
109
+ return false unless line =~ /^[ \t]*#[ \t]?(([ \t]*).*?)\r?$/
110
+ categorize($1, $2) do |fragment|
111
+ yield(fragment)
112
+ end
113
+ true
114
+ end
115
+
116
+ def wrap(lines, cols=80, tabsize=2)
117
+ lines = lines.split(/\r?\n/) unless lines.kind_of?(Array)
118
+
119
+ lines.collect do |line|
120
+ line = line.gsub(/\t/, " " * tabsize) unless tabsize == nil
121
+
122
+ if line.strip.empty?
123
+ line
124
+ else
125
+ # wrapping algorithm is slightly modified from
126
+ # http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
127
+ line.gsub(/(.{1,#{cols}})( +|$\r?\n?)|(.{1,#{cols}})/, "\\1\\3\n").split(/\s*\n/)
128
+ end
129
+ end.flatten
130
+ end
131
+
132
+ private
133
+
134
+ def categorize(fragment, indent)
135
+ case
136
+ when fragment == indent
137
+ # empty comment line
138
+ yield [""]
139
+ yield []
140
+ when indent.empty?
141
+ # continuation line
142
+ yield fragment.rstrip
143
+ else
144
+ # indented line
145
+ yield [fragment.rstrip]
146
+ yield []
147
+ end
148
+ end
149
+ end
150
+
151
+ # An array of line fragment arrays.
152
+ attr_reader :lines
153
+
154
+ # The next non-comment line after the comment ends.
155
+ # This is the line that would receive the comment
156
+ # in RDoc documentation.
157
+ attr_accessor :subject
158
+
159
+ # Returns the line number for the subject line, if known.
160
+ attr_accessor :line_number
161
+
162
+ def initialize(line_number=nil)
163
+ @lines = []
164
+ @subject = nil
165
+ @line_number = line_number
166
+ end
167
+
168
+ # Pushes the fragment onto the last line array. If fragment is an
169
+ # array itself, then fragment will be pushed onto lines.
170
+ #
171
+ # c = Comment.new
172
+ # c.push "some line"
173
+ # c.push "fragments"
174
+ # c.push ["a", "whole", "new line"]
175
+ # c.lines # => [["some line", "fragments"], ["a", "whole", "new line"]]
176
+ #
177
+ def push(fragment)
178
+ lines << [] if lines.empty?
179
+
180
+ case fragment
181
+ when Array
182
+ if lines[-1].empty?
183
+ lines[-1] = fragment
184
+ else
185
+ lines.push fragment
186
+ end
187
+ else
188
+ lines[-1].push fragment
189
+ end
190
+ end
191
+
192
+ # Alias for push.
193
+ def <<(fragment)
194
+ push(fragment)
195
+ end
196
+
197
+ # Unshifts the fragment to the first line array. If fragment is an
198
+ # array itself, then fragment will be unshifted onto lines.
199
+ #
200
+ # c = Comment.new
201
+ # c.unshift "some line"
202
+ # c.unshift "fragments"
203
+ # c.unshift ["a", "whole", "new line"]
204
+ # c.lines # => [["a", "whole", "new line"], ["fragments", "some line"]]
205
+ #
206
+ def unshift(fragment)
207
+ lines << [] if lines.empty?
208
+
209
+ case fragment
210
+ when Array
211
+ if lines[0].empty?
212
+ lines[0] = fragment
213
+ else
214
+ lines.unshift fragment
215
+ end
216
+ else
217
+ lines[0].unshift fragment
218
+ end
219
+ end
220
+
221
+ def prepend(comment_line)
222
+ Comment.scan(comment_line) {|f| unshift(f) }
223
+ end
224
+
225
+ def append(comment_line)
226
+ Comment.scan(comment_line) {|f| push(f) }
227
+ end
228
+
229
+ # Removes leading and trailing lines that are empty ([])
230
+ # or whitespace (['']). Returns self.
231
+ def trim
232
+ lines.shift while !lines.empty? && (lines[0].empty? || lines[0].join.strip.empty?)
233
+ lines.pop while !lines.empty? && (lines[-1].empty? || lines[-1].join.strip.empty?)
234
+ self
235
+ end
236
+
237
+ # True if there are no fragments in self.
238
+ def empty?
239
+ !lines.find {|array| !array.empty?}
240
+ end
241
+
242
+ def wrap(cols=80, tabsize=2, line_sep="\n", fragment_sep=" ", strip=true)
243
+ resolved_lines = Comment.wrap(to_s(fragment_sep, nil, strip), cols, tabsize)
244
+ line_sep ? resolved_lines.join(line_sep) : resolved_lines
245
+ end
246
+
247
+ # Returns lines as a string where line fragments are joined by
248
+ # fragment_sep and lines are joined by line_sep.
249
+ def to_s(fragment_sep=" ", line_sep="\n", strip=true)
250
+ resolved_lines = lines.collect {|line| line.join(fragment_sep)}
251
+
252
+ # strip leading an trailing whitespace lines
253
+ if strip
254
+ resolved_lines.shift while !resolved_lines.empty? && resolved_lines[0].empty?
255
+ resolved_lines.pop while !resolved_lines.empty? && resolved_lines[-1].empty?
256
+ end
257
+
258
+ line_sep ? resolved_lines.join(line_sep) : resolved_lines
259
+ end
260
+
261
+ def ==(another)
262
+ another.kind_of?(Comment) &&
263
+ self.line_number == another.line_number &&
264
+ self.subject == another.subject &&
265
+ self.lines == another.lines
266
+ end
267
+
268
+ end
269
+ end
270
+ end
@@ -1,92 +1,113 @@
1
+ require 'tap/support/configurable_class'
2
+
1
3
  module Tap
2
4
  module Support
3
5
 
4
- # Configurable encapsulates all configuration-related methods used
5
- # by Tasks. When Configurable is included in a class, the class itself
6
- # is extended with Tap::Support::ConfigurableMethods, such that configs
7
- # can be declared within the class definition.
6
+ # Configurable enables the specification of configurations within a class definition.
8
7
  #
9
- # class ConfigurableClass
8
+ # class ConfigClass
10
9
  # include Configurable
11
10
  #
12
11
  # config :one, 'one'
13
12
  # config :two, 'two'
14
13
  # config :three, 'three'
14
+ #
15
+ # def initialize(overrides={})
16
+ # initialize_config(overrides)
17
+ # end
18
+ # end
19
+ #
20
+ # c = ConfigClass.new
21
+ # c.config.class # => InstanceConfiguration
22
+ # c.config # => {:one => 'one', :two => 'two', :three => 'three'}
23
+ #
24
+ # The <tt>config</tt> object acts as a kind of forwarding hash; declared configurations
25
+ # map to accessors while undeclared configurations are stored internally:
26
+ #
27
+ # c.config[:one] = 'ONE'
28
+ # c.one # => 'ONE'
29
+ #
30
+ # c.one = 1
31
+ # c.config # => {:one => 1, :two => 'two', :three => 'three'}
32
+ #
33
+ # c.config[:undeclared] = 'value'
34
+ # c.config.store # => {:undeclared => 'value'}
35
+ #
36
+ # The writer method for a configuration can be modified by providing a block to config.
37
+ # The Validation module provides a number of common validation and string-transform
38
+ # blocks which can be accessed through the class method 'c':
39
+ #
40
+ # class SubClass < ConfigClass
41
+ # config(:one, 'one') {|v| v.upcase }
42
+ # config :two, 2, &c.integer
15
43
  # end
16
44
  #
17
- # ConfigurableClass.new.config # => {:one => 'one', :two => 'two', :three => 'three'}
45
+ # s = SubClass.new
46
+ # s.config # => {:one => 'ONE', :two => 2, :three => 'three'}
47
+ #
48
+ # s.one = 'aNothER'
49
+ # s.one # => 'ANOTHER'
50
+ #
51
+ # s.two = -2
52
+ # s.two # => -2
53
+ # s.two = "3"
54
+ # s.two # => 3
55
+ # s.two = nil # !> ValidationError
56
+ # s.two = 'str' # !> ValidationError
57
+ #
58
+ # As shown above, configurations are inherited from the parent and can be
59
+ # overridden in subclasses. See ConfigurableClass for more details.
18
60
  #
19
- # See the 'Configuration' section in the Tap::Task documentation for
20
- # more details on how Configurable works in practice.
21
61
  module Configurable
22
- include Batchable
23
-
62
+
63
+ # Extends including classes with ConfigurableClass
24
64
  def self.included(mod)
25
- mod.extend Support::BatchableMethods
26
- mod.extend Support::ConfigurableMethods
27
- mod.instance_variable_set(:@configurations, Support::ClassConfiguration.new(mod))
28
- mod.instance_variable_set(:@source_files, [])
65
+ mod.extend Support::ConfigurableClass if mod.kind_of?(Class)
29
66
  end
30
-
31
- # The application used to load config_file templates
32
- # (and hence, to initialize batched objects).
33
- attr_reader :app
34
67
 
35
- # The name used to determine config_file, via
36
- # app.config_filepath(name).
37
- attr_reader :name
38
-
39
- # The config file used to load config templates.
40
- attr_reader :config_file
41
-
42
- # A configuration hash.
68
+ # The instance configurations for self
43
69
  attr_reader :config
44
70
 
45
- # Initializes a new Configurable and associated batch objects. Batch
46
- # objects will be initialized for each configuration template specified
47
- # in config_file, where config_file = app.config_filepath(name).
48
- def initialize(name=nil, config={}, app=App.instance)
49
- @app = app
50
- @batch = []
51
- @config_file = app.config_filepath(name)
52
-
53
- config.symbolize_keys! unless config.empty?
54
- app.each_config_template(config_file) do |template|
55
- template_config = template.empty? ? config : template.symbolize_keys.merge(config)
56
- initialize_batch_obj(name, template_config)
71
+ # Reconfigures self with the given configuration overrides. Only
72
+ # the specified configs are modified. Override keys are symbolized.
73
+ #
74
+ # Returns self.
75
+ def reconfigure(overrides={})
76
+ keys = (config.class_config.ordered_keys + overrides.keys) & overrides.keys
77
+ keys.each do |key|
78
+ config[key.to_sym] = overrides[key]
57
79
  end
80
+
81
+ self
58
82
  end
59
-
60
- # Sets config with the given configuration overrides, merged with the class
61
- # default configuration. Configurations are symbolized before they are merged,
62
- # and validated as specified in the config declarations.
63
- def config=(overrides)
64
- @config = self.class.configurations.default.dup
65
- overrides.each_pair {|key, value| set_config(key.to_sym, value) }
66
- self.config
83
+
84
+ # Reinitializes config with a copy of orig.config (this assures
85
+ # that duplicates have their own copy of configurations,
86
+ # separate from the original object).
87
+ def initialize_copy(orig)
88
+ super
89
+ initialize_config(orig.config)
67
90
  end
68
91
 
69
- # Creates a new batched object and adds the object to batch. The batched object
70
- # will be a duplicate of the current object but with a new name and/or
71
- # configurations.
72
- def initialize_batch_obj(name=nil, config={})
73
- obj = super()
92
+ protected
74
93
 
75
- obj.name = name.nil? ? self.class.default_name : name
76
- obj.config = config
94
+ # Initializes config to an InstanceConfiguration specific for self.
95
+ # Default config values are assigned or overridden if specified in
96
+ # overrides. Override keys are symbolized.
97
+ def initialize_config(overrides={})
98
+ class_config = self.class.configurations
99
+ @config = class_config.instance_config
100
+
101
+ overrides.each_pair do |key, value|
102
+ config[key.to_sym] = value
103
+ end
104
+
105
+ class_config.each_pair do |key, value|
106
+ next if config.has_key?(key)
107
+ config[key] = value.default
108
+ end
77
109
 
78
- obj
79
- end
80
-
81
- protected
82
-
83
- attr_writer :name
84
-
85
- # Sets the specified configuration, processing the input value using
86
- # the block specified in the config declaration. The input key should
87
- # be symbolized.
88
- def set_config(key, value)
89
- config[key] = self.class.configurations.process(key, value)
110
+ config.bind(self)
90
111
  end
91
112
  end
92
113
  end
@@ -0,0 +1,296 @@
1
+ require 'tap/support/class_configuration'
2
+ require 'tap/support/validation'
3
+ require 'tap/support/lazydoc'
4
+
5
+ module Tap
6
+ module Support
7
+ autoload(:Templater, 'tap/support/templater')
8
+
9
+ # ConfigurableClass encapsulates class methods used to declare class configurations.
10
+ # When configurations are declared using the config method, ConfigurableClass
11
+ # generates accessors in the class, much like attr_accessor.
12
+ #
13
+ # class ConfigurableClass
14
+ # extend ConfigurableClass
15
+ # config :one, 'one'
16
+ # end
17
+ #
18
+ # ConfigurableClass.configurations.to_hash # => {:one => 'one'}
19
+ #
20
+ # c = ConfigurableClass.new
21
+ # c.respond_to?('one') # => true
22
+ # c.respond_to?('one=') # => true
23
+ #
24
+ # If a block is given, the block will be used to create the writer method
25
+ # for the config. Used in this manner, config defines a <tt>config_key=</tt> method
26
+ # wherein <tt>@config_key</tt> will be set to the return value of the block.
27
+ #
28
+ # class AnotherConfigurableClass
29
+ # extend ConfigurableClass
30
+ # config(:one, 'one') {|value| value.upcase }
31
+ # end
32
+ #
33
+ # ac = AnotherConfigurableClass.new
34
+ # ac.one = 'value'
35
+ # ac.one # => 'VALUE'
36
+ #
37
+ # The block has class-context in this case. To have instance-context, use the
38
+ # config_attr method which defines the writer method using the block directly.
39
+ #
40
+ # class YetAnotherConfigurableClass
41
+ # extend ConfigurableClass
42
+ # config_attr(:one, 'one') {|value| @one = value.reverse }
43
+ # end
44
+ #
45
+ # ac = YetAnotherConfigurableClass.new
46
+ # ac.one = 'value'
47
+ # ac.one # => 'eulav'
48
+ #
49
+ module ConfigurableClass
50
+
51
+ # A ClassConfiguration holding the class configurations.
52
+ attr_reader :configurations
53
+
54
+ # The source_file for self. By default the first file
55
+ # to define the class inheriting ConfigurableClass.
56
+ attr_accessor :source_file
57
+
58
+ # Sets the source_file for base and initializes base.configurations.
59
+ def self.extended(base)
60
+ caller.each_with_index do |line, index|
61
+ case line
62
+ when /\/configurable.rb/ then next
63
+ when /^(([A-z]:)?[^:]+):(\d+)/
64
+ base.instance_variable_set(:@source_file, File.expand_path($1))
65
+ break
66
+ end
67
+ end
68
+
69
+ base.instance_variable_set(:@configurations, ClassConfiguration.new(base))
70
+ end
71
+
72
+ # When subclassed, the parent.configurations are duplicated and passed to
73
+ # the child class where they can be extended/modified without affecting
74
+ # the configurations of the parent class.
75
+ def inherited(child)
76
+ unless child.instance_variable_defined?(:@source_file)
77
+ caller.first =~ /^(([A-z]:)?[^:]+):(\d+)/
78
+ child.instance_variable_set(:@source_file, File.expand_path($1))
79
+ end
80
+
81
+ child.instance_variable_set(:@configurations, ClassConfiguration.new(child, @configurations))
82
+ super
83
+ end
84
+
85
+ # Returns the lazydoc for source_file
86
+ def lazydoc(resolve=false)
87
+ Lazydoc.resolve(configurations.code_comments) if resolve
88
+ Lazydoc[source_file]
89
+ end
90
+
91
+ # Loads the contents of path as YAML. Returns an empty hash if the path
92
+ # is empty, does not exist, or is not a file.
93
+ def load_config(path)
94
+ return {} if path == nil || !File.exists?(path) || File.directory?(path)
95
+
96
+ YAML.load_file(path) || {}
97
+ end
98
+
99
+ protected
100
+
101
+ # Declares a class configuration and generates the associated accessors.
102
+ # If a block is given, the <tt>key=</tt> method will set <tt>@key</tt>
103
+ # to the return of the block, which executes in class-context.
104
+ # Configurations are inherited, and can be overridden in subclasses.
105
+ #
106
+ # class SampleClass
107
+ # include Tap::Support::Configurable
108
+ #
109
+ # config :str, 'value'
110
+ # config(:upcase, 'value') {|input| input.upcase }
111
+ # end
112
+ #
113
+ # # An equivalent class to illustrate class-context
114
+ # class EquivalentClass
115
+ # attr_accessor :str
116
+ # attr_reader :upcase
117
+ #
118
+ # UPCASE_BLOCK = lambda {|input| input.upcase }
119
+ #
120
+ # def upcase=(input)
121
+ # @upcase = UPCASE_BLOCK.call(input)
122
+ # end
123
+ # end
124
+ #
125
+ def config(key, value=nil, options={}, &block)
126
+ if block_given?
127
+ # add arg_type implied by block, if necessary
128
+ options[:arg_type] = arg_type(block) if options[:arg_type] == nil
129
+ options[:arg_name] = arg_name(block) if options[:arg_name] == nil
130
+
131
+ instance_variable = "@#{key}".to_sym
132
+ config_attr(key, value, options) do |input|
133
+ instance_variable_set(instance_variable, block.call(input))
134
+ end
135
+ else
136
+ config_attr(key, value, options)
137
+ end
138
+ end
139
+
140
+ # Declares a class configuration and generates the associated accessors.
141
+ # If a block is given, the <tt>key=</tt> method will perform the block with
142
+ # instance-context. Configurations are inherited, and can be overridden
143
+ # in subclasses.
144
+ #
145
+ # class SampleClass
146
+ # include Tap::Support::Configurable
147
+ #
148
+ # def initialize
149
+ # initialize_config
150
+ # end
151
+ #
152
+ # config_attr :str, 'value'
153
+ # config_attr(:upcase, 'value') {|input| @upcase = input.upcase }
154
+ # end
155
+ #
156
+ # # An equivalent class to illustrate instance-context
157
+ # class EquivalentClass
158
+ # attr_accessor :str
159
+ # attr_reader :upcase
160
+ #
161
+ # def upcase=(input)
162
+ # @upcase = input.upcase
163
+ # end
164
+ # end
165
+ #
166
+ # Instances of a Configurable class may set configurations through config.
167
+ # The config object is an InstanceConfiguration which forwards read/write
168
+ # operations to the configuration accessors. For example:
169
+ #
170
+ # s = SampleClass.new
171
+ # s.config.class # => Tap::Support::InstanceConfiguration
172
+ # s.str # => 'value'
173
+ # s.config[:str] # => 'value'
174
+ #
175
+ # s.str = 'one'
176
+ # s.config[:str] # => 'one'
177
+ #
178
+ # s.config[:str] = 'two'
179
+ # s.str # => 'two'
180
+ #
181
+ # Alternative reader and writer methods may be specified as an option;
182
+ # in this case config_attr assumes the methods are declared elsewhere
183
+ # and will not define the associated accessors.
184
+ #
185
+ # class AlternativeClass
186
+ # include Tap::Support::Configurable
187
+ #
188
+ # config_attr :sym, 'value', :reader => :get_sym, :writer => :set_sym
189
+ #
190
+ # def initialize
191
+ # initialize_config
192
+ # end
193
+ #
194
+ # def get_sym
195
+ # @sym
196
+ # end
197
+ #
198
+ # def set_sym(input)
199
+ # @sym = input.to_sym
200
+ # end
201
+ # end
202
+ #
203
+ # alt = AlternativeClass.new
204
+ # alt.respond_to?(:sym) # => false
205
+ # alt.respond_to?(:sym=) # => false
206
+ #
207
+ # alt.config[:sym] = 'one'
208
+ # alt.get_sym # => :one
209
+ #
210
+ # alt.set_sym('two')
211
+ # alt.config[:sym] # => :two
212
+ #
213
+ # Idiosyncratically, true, false, and nil may also be provided as
214
+ # reader/writer options. Specifying true is the same as using the
215
+ # default. Specifying false or nil prevents config_attr from
216
+ # defining accessors, but the configuration still expects to use
217
+ # the default reader/writer methods (ie <tt>key</tt> and <tt>key=</tt>)
218
+ # which must be defined elsewhere.
219
+ def config_attr(key, value=nil, options={}, &block)
220
+
221
+ # add arg_type implied by block, if necessary
222
+ options[:arg_type] = arg_type(block) if block_given? && options[:arg_type] == nil
223
+ options[:arg_name] = arg_name(block) if block_given? && options[:arg_name] == nil
224
+
225
+ # define the default public reader method
226
+ if !options.has_key?(:reader) || options[:reader] == true
227
+ attr_reader(key)
228
+ public key
229
+ end
230
+
231
+ # define the public writer method
232
+ case
233
+ when options.has_key?(:writer) && options[:writer] != true
234
+ raise ArgumentError.new("block may not be specified with writer") if block_given?
235
+ when block_given?
236
+ define_method("#{key}=", &block)
237
+ public "#{key}="
238
+ else
239
+ attr_writer(key)
240
+ public "#{key}="
241
+ end
242
+
243
+ # remove any true, false, nil reader/writer declarations...
244
+ # implicitly reverting the option to the default reader
245
+ # and writer methods
246
+ [:reader, :writer].each do |option|
247
+ case options[option]
248
+ when true, false, nil then options.delete(option)
249
+ end
250
+ end
251
+
252
+ # register with TDoc so that all extra documentation can be extracted
253
+ caller.each_with_index do |line, index|
254
+ case line
255
+ when /in .config.$/ then next
256
+ when /^(([A-z]:)?[^:]+):(\d+)/
257
+ options[:desc] = Lazydoc.register($1, $3.to_i - 1)
258
+ break
259
+ end
260
+ end if options[:desc] == nil
261
+
262
+ configurations.add(key, value, options)
263
+ end
264
+
265
+ # Alias for Tap::Support::Validation
266
+ def c
267
+ Validation
268
+ end
269
+
270
+ private
271
+
272
+ # Returns special argument types for standard validation
273
+ # blocks, such as switch (Validation::SWITCH) and list
274
+ # (Validation::LIST).
275
+ def arg_type(block) # :nodoc:
276
+ case block
277
+ when Validation::SWITCH then :switch
278
+ when Validation::FLAG then :flag
279
+ when Validation::LIST then :list
280
+ else nil
281
+ end
282
+ end
283
+
284
+ # Returns special argument names for standard validation
285
+ # blocks, such as switch (Validation::ARRAY) and list
286
+ # (Validation::HASH).
287
+ def arg_name(block) # :nodoc:
288
+ case block
289
+ when Validation::ARRAY then "'[a, b, c]'"
290
+ when Validation::HASH then "'{one: 1, two: 2}'"
291
+ else nil
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end