tap 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/History +12 -0
  2. data/MIT-LICENSE +0 -2
  3. data/README +23 -32
  4. data/bin/rap +116 -0
  5. data/bin/tap +6 -9
  6. data/cgi/run.rb +67 -0
  7. data/cmd/console.rb +1 -1
  8. data/cmd/destroy.rb +4 -4
  9. data/cmd/generate.rb +4 -4
  10. data/cmd/manifest.rb +61 -53
  11. data/cmd/run.rb +8 -75
  12. data/doc/Class Reference +130 -121
  13. data/doc/Command Reference +76 -124
  14. data/doc/Syntax Reference +290 -0
  15. data/doc/Tutorial +305 -237
  16. data/lib/tap/app.rb +140 -467
  17. data/lib/tap/constants.rb +2 -2
  18. data/lib/tap/declarations.rb +211 -0
  19. data/lib/tap/env.rb +171 -193
  20. data/lib/tap/exe.rb +100 -21
  21. data/lib/tap/file_task.rb +3 -3
  22. data/lib/tap/generator/base.rb +1 -1
  23. data/lib/tap/generator/destroy.rb +10 -10
  24. data/lib/tap/generator/generate.rb +29 -18
  25. data/lib/tap/generator/generators/command/command_generator.rb +2 -2
  26. data/lib/tap/generator/generators/command/templates/command.erb +2 -2
  27. data/lib/tap/generator/generators/config/config_generator.rb +3 -3
  28. data/lib/tap/generator/generators/config/templates/doc.erb +1 -1
  29. data/lib/tap/generator/generators/file_task/file_task_generator.rb +1 -1
  30. data/lib/tap/generator/generators/file_task/templates/task.erb +1 -1
  31. data/lib/tap/generator/generators/file_task/templates/test.erb +1 -1
  32. data/lib/tap/generator/generators/generator/generator_generator.rb +27 -0
  33. data/lib/tap/generator/generators/generator/templates/task.erb +27 -0
  34. data/lib/tap/generator/generators/root/root_generator.rb +13 -13
  35. data/lib/tap/generator/generators/root/templates/README +0 -0
  36. data/lib/tap/generator/generators/root/templates/Rakefile +2 -2
  37. data/lib/tap/generator/generators/root/templates/gemspec +4 -5
  38. data/lib/tap/generator/generators/root/templates/tapfile +11 -8
  39. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +1 -1
  40. data/lib/tap/generator/generators/task/task_generator.rb +1 -3
  41. data/lib/tap/generator/generators/task/templates/test.erb +1 -3
  42. data/lib/tap/patches/optparse/summarize.rb +62 -0
  43. data/lib/tap/root.rb +41 -29
  44. data/lib/tap/support/aggregator.rb +16 -3
  45. data/lib/tap/support/assignments.rb +10 -9
  46. data/lib/tap/support/audit.rb +58 -64
  47. data/lib/tap/support/class_configuration.rb +33 -44
  48. data/lib/tap/support/combinator.rb +125 -0
  49. data/lib/tap/support/configurable.rb +13 -14
  50. data/lib/tap/support/configurable_class.rb +21 -43
  51. data/lib/tap/support/configuration.rb +55 -9
  52. data/lib/tap/support/constant.rb +87 -13
  53. data/lib/tap/support/constant_manifest.rb +116 -0
  54. data/lib/tap/support/dependencies.rb +54 -0
  55. data/lib/tap/support/dependency.rb +44 -0
  56. data/lib/tap/support/executable.rb +247 -32
  57. data/lib/tap/support/executable_queue.rb +1 -1
  58. data/lib/tap/support/gems/rake.rb +29 -8
  59. data/lib/tap/support/gems.rb +10 -30
  60. data/lib/tap/support/instance_configuration.rb +29 -3
  61. data/lib/tap/support/intern.rb +46 -0
  62. data/lib/tap/support/join.rb +143 -0
  63. data/lib/tap/support/joins/fork.rb +19 -0
  64. data/lib/tap/support/joins/merge.rb +22 -0
  65. data/lib/tap/support/joins/sequence.rb +21 -0
  66. data/lib/tap/support/joins/switch.rb +25 -0
  67. data/lib/tap/support/joins/sync_merge.rb +63 -0
  68. data/lib/tap/support/joins.rb +15 -0
  69. data/lib/tap/support/lazy_attributes.rb +17 -2
  70. data/lib/tap/support/lazydoc/comment.rb +503 -0
  71. data/lib/tap/support/lazydoc/config.rb +17 -0
  72. data/lib/tap/support/lazydoc/definition.rb +36 -0
  73. data/lib/tap/support/lazydoc/document.rb +152 -0
  74. data/lib/tap/support/lazydoc/method.rb +24 -0
  75. data/lib/tap/support/lazydoc.rb +269 -343
  76. data/lib/tap/support/manifest.rb +121 -103
  77. data/lib/tap/support/minimap.rb +90 -0
  78. data/lib/tap/support/node.rb +56 -0
  79. data/lib/tap/support/parser.rb +436 -0
  80. data/lib/tap/support/schema.rb +359 -0
  81. data/lib/tap/support/shell_utils.rb +3 -5
  82. data/lib/tap/support/string_ext.rb +60 -0
  83. data/lib/tap/support/tdoc.rb +7 -2
  84. data/lib/tap/support/templater.rb +30 -16
  85. data/lib/tap/support/validation.rb +77 -8
  86. data/lib/tap/task.rb +431 -143
  87. data/lib/tap/tasks/dump.rb +15 -10
  88. data/lib/tap/tasks/load.rb +112 -0
  89. data/lib/tap/tasks/rake.rb +4 -41
  90. data/lib/tap/test/assertions.rb +38 -0
  91. data/lib/tap/test/env_vars.rb +1 -1
  92. data/lib/tap/test/extensions.rb +79 -0
  93. data/lib/tap/test/file_test.rb +420 -0
  94. data/lib/tap/test/file_test_class.rb +12 -0
  95. data/lib/tap/test/regexp_escape.rb +87 -0
  96. data/lib/tap/test/script_test.rb +46 -0
  97. data/lib/tap/test/script_tester.rb +115 -0
  98. data/lib/tap/test/subset_test.rb +260 -0
  99. data/lib/tap/test/subset_test_class.rb +99 -0
  100. data/lib/tap/test/{tap_methods.rb → tap_test.rb} +45 -43
  101. data/lib/tap/test/utils.rb +231 -0
  102. data/lib/tap/test.rb +53 -26
  103. data/lib/tap.rb +3 -20
  104. metadata +50 -27
  105. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +0 -15
  106. data/lib/tap/patches/rake/rake_test_loader.rb +0 -8
  107. data/lib/tap/patches/rake/testtask.rb +0 -57
  108. data/lib/tap/patches/ruby19/backtrace_filter.rb +0 -51
  109. data/lib/tap/patches/ruby19/parsedate.rb +0 -16
  110. data/lib/tap/support/batchable.rb +0 -47
  111. data/lib/tap/support/batchable_class.rb +0 -107
  112. data/lib/tap/support/command_line.rb +0 -98
  113. data/lib/tap/support/comment.rb +0 -270
  114. data/lib/tap/support/constant_utils.rb +0 -127
  115. data/lib/tap/support/declarations.rb +0 -111
  116. data/lib/tap/support/framework.rb +0 -83
  117. data/lib/tap/support/framework_class.rb +0 -180
  118. data/lib/tap/support/run_error.rb +0 -39
  119. data/lib/tap/support/summary.rb +0 -30
  120. data/lib/tap/test/file_methods.rb +0 -377
  121. data/lib/tap/test/script_methods/script_test.rb +0 -98
  122. data/lib/tap/test/script_methods.rb +0 -107
  123. data/lib/tap/test/subset_methods.rb +0 -420
  124. data/lib/tap/workflow.rb +0 -200
@@ -1,47 +0,0 @@
1
- require 'tap/support/batchable_class'
2
-
3
- module Tap
4
- module Support
5
-
6
- # Batchable encapsulates the methods used to support batching
7
- # of tasks. Classes including Batchable should call <tt>super</tt>
8
- # during initialization to initialize batch, or they should
9
- # initialize batch themselves.
10
- #
11
- # See the 'Batches' section in the Tap::Task documentation for
12
- # details on how Batchable works in practice.
13
- module Batchable
14
-
15
- def self.included(mod)
16
- mod.extend Support::BatchableClass if mod.kind_of?(Class)
17
- end
18
-
19
- # The object batch.
20
- attr_reader :batch
21
-
22
- def initialize(batch=[])
23
- @batch = batch
24
- @batch << self
25
- end
26
-
27
- # Returns true if the batch size is greater than one
28
- # (the one being self).
29
- def batched?
30
- batch.length > 1
31
- end
32
-
33
- # Returns the index of the self in batch.
34
- def batch_index
35
- batch.index(self)
36
- end
37
-
38
- # Initializes a new batch object and adds the object to batch.
39
- # The object will be a duplicate of self.
40
- def initialize_batch_obj
41
- obj = self.dup
42
- batch << obj
43
- obj
44
- end
45
- end
46
- end
47
- end
@@ -1,107 +0,0 @@
1
- module Tap
2
- module Support
3
-
4
- # BatchableClass encapsulates class methods related to Batchable.
5
- module BatchableClass
6
-
7
- # Merges the batches for the specified objects. All objects
8
- # sharing the individual object batches will be affected, even
9
- # if they are not listed explicitly as an input.
10
- #
11
- # t1 = Tap::Task.new
12
- # t2 = Tap::Task.new
13
- # t3 = t2.initialize_batch_obj
14
- #
15
- # Tap::Task.batch(t1, t2)
16
- # t3.batch # => [t1,t2,t3]
17
- #
18
- # Returns the new batch.
19
- def batch(*batchables)
20
- merged = []
21
- batches = batchables.collect {|batchable| batchable.batch }.uniq
22
- batches.each do |batch|
23
- merged.concat(batch)
24
- batch.clear
25
- end
26
- merged.uniq!
27
- batches.each {|batch| batch.concat(merged) }
28
- merged
29
- end
30
-
31
- protected
32
-
33
- # Redefines the specified method(s) as batched methods. The existing method
34
- # is renamed as <tt>unbatched_method</tt> and <tt>method</tt> redefined to
35
- # call <tt>unbatched_method</tt> on each object in the batch.
36
- #
37
- # def process(one, two)
38
- # ...
39
- # end
40
- # batch_function(:process)
41
- #
42
- # Is equivalent to:
43
- #
44
- # def unbatched_process(one, two)
45
- # ...
46
- # end
47
- #
48
- # def process(one, two)
49
- # batch.each do |t|
50
- # t.unbatched_process(one, two)
51
- # end
52
- # self
53
- # end
54
- #
55
- # The batched method will accept/pass as many arguments as are defined for
56
- # the unbatched method. Splats are supported, and blocks are supported too
57
- # by passing a block to batch_function:
58
- #
59
- # def process(arg, *args, &block)
60
- # ...
61
- # end
62
- # batch_function(:process) {}
63
- #
64
- # Is equivalent to:
65
- #
66
- # def unbatched_process(arg, *args, &block)
67
- # ...
68
- # end
69
- #
70
- # def process(*args, &block)
71
- # batch.each do |t|
72
- # t.unbatched_process(*args, &block)
73
- # end
74
- # self
75
- # end
76
- #
77
- # Obviously there are limitations to batch_function, most notably batching
78
- # functions with default values. In these cases, batch functionality
79
- # must be implemented manually.
80
- def batch_function(*methods)
81
- methods.each do |method_name|
82
- unbatched_method = "unbatched_#{method_name}"
83
- if method_defined?(unbatched_method)
84
- raise "unbatched method already defined: #{unbatched_method}"
85
- end
86
-
87
- arity = instance_method(method_name).arity
88
- args = case
89
- when arity < 0 then "*args"
90
- else Array.new(arity) {|index| "arg#{index}" }.join(", ")
91
- end
92
- args += ", &block" if block_given?
93
-
94
- class_eval %Q{
95
- alias #{unbatched_method} #{method_name}
96
- def #{method_name}(#{args})
97
- batch.each do |t|
98
- t.#{unbatched_method}(#{args})
99
- end
100
- self
101
- end
102
- }
103
- end
104
- end
105
- end
106
- end
107
- end
@@ -1,98 +0,0 @@
1
- require 'optparse'
2
-
3
- module Tap
4
- module Support
5
-
6
- # Under Construction
7
- module CommandLine
8
- module_function
9
-
10
- # Parses the input string as YAML, if the string matches the YAML document
11
- # specifier (ie it begins with "---\s*\n"). Otherwise returns the string.
12
- #
13
- # str = {'key' => 'value'}.to_yaml # => "--- \nkey: value\n"
14
- # Tap::Script.parse_yaml(str) # => {'key' => 'value'}
15
- # Tap::Script.parse_yaml("str") # => "str"
16
- def parse_yaml(str)
17
- str =~ /\A---\s*\n/ ? YAML.load(str) : str
18
- end
19
-
20
- SPLIT_ARGV_REGEXP = /\A-{2}(\+*)\z/
21
-
22
- def split(argv)
23
- current = []
24
- current_split = []
25
- splits = [current_split]
26
-
27
- argv.each do |arg|
28
- if arg =~ SPLIT_ARGV_REGEXP
29
- current_split << current unless current.empty?
30
- current = []
31
- current_split = (splits[$1.length] ||= [])
32
- else
33
- current << arg
34
- end
35
- end
36
-
37
- current_split << current unless current.empty?
38
- splits.delete_if {|split| split.nil? || split.empty? }
39
- splits
40
- end
41
-
42
- def shift(argv)
43
- index = nil
44
- argv.each_with_index do |arg, i|
45
- if arg !~ /\A-/
46
- index = i
47
- break
48
- end
49
- end
50
- index == nil ? nil : argv.delete_at(index)
51
- end
52
-
53
- def usage(path, cols=80)
54
- parse_usage(File.read(path), cols)
55
- end
56
-
57
- def parse_usage(str, cols=80)
58
- scanner = StringScanner.new(str)
59
- scanner.scan(/^#!.*?$/)
60
- Comment.parse(scanner, false).wrap(cols, 2).strip
61
- end
62
-
63
- def configv(config)
64
- desc = config.desc
65
- desc.extend(OptParseComment) if desc.kind_of?(Comment)
66
-
67
- [config.short, argtype(config), desc].compact
68
- end
69
-
70
- def argtype(config)
71
- case config.arg_type
72
- when :optional
73
- "#{config.long} [#{config.arg_name}]"
74
- when :switch
75
- config.long(true)
76
- when :flag
77
- config.long
78
- when :list
79
- "#{config.long} a,b,c"
80
- when :mandatory, nil
81
- "#{config.long} #{config.arg_name}"
82
- else
83
- raise "unknown arg_type: #{config.arg_type}"
84
- end
85
- end
86
-
87
- module OptParseComment
88
- def empty?
89
- to_str.empty?
90
- end
91
-
92
- def to_str
93
- subject.to_s =~ /#(.*)$/ ? $1.strip : ""
94
- end
95
- end
96
- end
97
- end
98
- end
@@ -1,270 +0,0 @@
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,127 +0,0 @@
1
- module Tap
2
- module Support
3
-
4
- # ConstantUtils provides methods for transforming strings into constants.
5
- # Several methods are directly taken from or based heavily on the
6
- # ActiveSupport {Inflections}[http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html]
7
- # module and should not cause conflicts if ActiveSupport is loaded
8
- # alongside Tap.
9
- #
10
- # ActiveSupport is distributed with an MIT-LICENSE:
11
- #
12
- # Copyright (c) 2004-2008 David Heinemeier Hansson
13
- #
14
- # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
15
- # associated documentation files (the "Software"), to deal in the Software without restriction,
16
- # including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
17
- # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
18
- # subject to the following conditions:
19
- #
20
- # The above copyright notice and this permission notice shall be included in all copies or substantial
21
- # portions of the Software.
22
- #
23
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24
- # LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
25
- # NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26
- # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
- # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
- #
29
- module ConstantUtils
30
-
31
- # camelize converts self to UpperCamelCase. If the argument to
32
- # camelize is set to :lower then camelize produces lowerCamelCase.
33
- # camelize will also convert '/' to '::' which is useful for
34
- # converting paths to namespaces.
35
- def camelize(first_letter = :upper)
36
- case first_letter
37
- when :upper then self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
38
- when :lower then self.first + camelize[1..-1]
39
- end
40
- end
41
-
42
- # The reverse of camelize. Makes an underscored, lowercase form
43
- # from self. underscore will also change '::' to '/' to convert
44
- # namespaces to paths.
45
- def underscore
46
- self.gsub(/::/, '/').
47
- gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
48
- gsub(/([a-z\d])([A-Z])/,'\1_\2').
49
- tr("-", "_").
50
- downcase
51
- end
52
-
53
- # constantize tries to find a declared constant with the name specified
54
- # by self. It raises a NameError when the name is not in CamelCase
55
- # or is not initialized.
56
- def constantize
57
- case RUBY_VERSION
58
- when /^1.9/
59
-
60
- # a check is necessary to maintain the 1.8 behavior
61
- # in 1.9, where ancestor constants may be returned
62
- # by a direct evaluation
63
- const_name.split("::").inject(Object) do |current, const|
64
- const = const.to_sym
65
-
66
- current.const_get(const).tap do |c|
67
- unless current.const_defined?(const, false)
68
- raise NameError.new("uninitialized constant #{const_name}")
69
- end
70
- end
71
- end
72
-
73
- else
74
- Object.module_eval("::#{const_name}", __FILE__, __LINE__)
75
- end
76
- end
77
-
78
- # Tries to constantize self; if a NameError is raised, try_constantize
79
- # passes control to the block. Control is only passed if the NameError
80
- # is for one of the constants in self.
81
- def try_constantize
82
- begin
83
- constantize
84
- rescue(NameError)
85
- error_name = $!.name.to_s
86
- missing_const = const_name.split(/::/).inject(Object) do |current, const|
87
- if current.const_defined?(const)
88
- current.const_get(const)
89
- else
90
- break(const)
91
- end
92
- end
93
-
94
- # check that the error_name is the first missing constant
95
- raise $! unless missing_const == error_name
96
- yield(const_name)
97
- end
98
- end
99
-
100
- def constants_split
101
- camel_cased_word = camelize
102
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
103
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
104
- end
105
-
106
- constants = $1.split(/::/)
107
- current = Object
108
- while !constants.empty?
109
- break unless current.const_defined?(constants[0])
110
- current = current.const_get(constants.shift)
111
- end
112
-
113
- [current, constants]
114
- end
115
-
116
- protected
117
-
118
- def const_name
119
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
120
- raise NameError, "#{inspect} is not a valid constant name!"
121
- end
122
- $1
123
- end
124
-
125
- end
126
- end
127
- end