tap 0.7.9 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/History +28 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README +71 -43
  4. data/Rakefile +81 -64
  5. data/Tutorial +235 -0
  6. data/bin/tap +80 -44
  7. data/lib/tap.rb +41 -12
  8. data/lib/tap/app.rb +243 -246
  9. data/lib/tap/file_task.rb +357 -118
  10. data/lib/tap/generator.rb +88 -29
  11. data/lib/tap/generator/generators/config/config_generator.rb +4 -2
  12. data/lib/tap/generator/generators/config/templates/config.erb +1 -2
  13. data/lib/tap/generator/generators/file_task/file_task_generator.rb +3 -18
  14. data/lib/tap/generator/generators/file_task/templates/task.erb +22 -15
  15. data/lib/tap/generator/generators/file_task/templates/test.erb +13 -2
  16. data/{test/test/inference_methods/test_assert_files_exist/input/input_1.txt → lib/tap/generator/generators/generator/USAGE} +0 -0
  17. data/lib/tap/generator/generators/generator/generator_generator.rb +21 -0
  18. data/lib/tap/generator/generators/generator/templates/generator.erb +23 -0
  19. data/lib/tap/generator/generators/generator/templates/usage.erb +1 -0
  20. data/{test/test/inference_methods/test_assert_files_exist/input/input_2.txt → lib/tap/generator/generators/package/USAGE} +0 -0
  21. data/lib/tap/generator/generators/package/package_generator.rb +38 -0
  22. data/lib/tap/generator/generators/package/templates/package.erb +186 -0
  23. data/lib/tap/generator/generators/root/root_generator.rb +14 -9
  24. data/lib/tap/generator/generators/root/templates/Rakefile +20 -14
  25. data/{test/test/inference_methods/test_infer_glob/expected/file.yml → lib/tap/generator/generators/root/templates/ReadMe.txt} +0 -0
  26. data/lib/tap/generator/generators/root/templates/tap.yml +82 -0
  27. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +0 -1
  28. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +2 -1
  29. data/{test/test/inference_methods/test_infer_glob/expected/file_1.txt → lib/tap/generator/generators/script/USAGE} +0 -0
  30. data/lib/tap/generator/generators/script/script_generator.rb +17 -0
  31. data/lib/tap/generator/generators/script/templates/script.erb +42 -0
  32. data/lib/tap/generator/generators/task/task_generator.rb +1 -1
  33. data/lib/tap/generator/generators/task/templates/task.erb +24 -16
  34. data/lib/tap/generator/generators/task/templates/test.erb +13 -17
  35. data/lib/tap/generator/generators/workflow/templates/task.erb +10 -10
  36. data/lib/tap/generator/generators/workflow/templates/test.erb +1 -1
  37. data/lib/tap/generator/generators/workflow/workflow_generator.rb +3 -18
  38. data/lib/tap/root.rb +108 -146
  39. data/lib/tap/script.rb +362 -0
  40. data/lib/tap/script/console.rb +28 -0
  41. data/lib/tap/script/destroy.rb +13 -1
  42. data/lib/tap/script/generate.rb +13 -1
  43. data/lib/tap/script/run.rb +100 -57
  44. data/lib/tap/support/batch_queue.rb +0 -3
  45. data/lib/tap/support/logger.rb +6 -3
  46. data/lib/tap/support/rake.rb +54 -0
  47. data/lib/tap/support/task_configuration.rb +169 -0
  48. data/lib/tap/support/tdoc.rb +198 -0
  49. data/lib/tap/support/tdoc/config_attr.rb +338 -0
  50. data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
  51. data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
  52. data/lib/tap/support/versions.rb +33 -1
  53. data/lib/tap/task.rb +339 -227
  54. data/lib/tap/test.rb +86 -128
  55. data/lib/tap/test/env_vars.rb +16 -5
  56. data/lib/tap/test/file_methods.rb +373 -0
  57. data/lib/tap/test/subset_methods.rb +299 -180
  58. data/lib/tap/version.rb +2 -1
  59. data/lib/tap/workflow.rb +2 -0
  60. data/test/app/lib/app_test_task.rb +1 -0
  61. data/test/app_test.rb +327 -83
  62. data/test/check/binding_eval.rb +23 -0
  63. data/test/check/define_method_check.rb +22 -0
  64. data/test/check/dependencies_check.rb +175 -0
  65. data/test/check/inheritance_check.rb +22 -0
  66. data/test/file_task_test.rb +524 -291
  67. data/test/{test/inference_methods/test_infer_glob/expected/file_2.txt → root/glob/one.txt} +0 -0
  68. data/test/root/glob/two.txt +0 -0
  69. data/test/root_test.rb +330 -262
  70. data/test/script_test.rb +194 -0
  71. data/test/support/audit_test.rb +5 -2
  72. data/test/support/combinator_test.rb +10 -10
  73. data/test/support/rake_test.rb +35 -0
  74. data/test/support/task_configuration_test.rb +272 -0
  75. data/test/support/tdoc_test.rb +363 -0
  76. data/test/support/templater_test.rb +2 -2
  77. data/test/support/versions_test.rb +32 -0
  78. data/test/tap_test_helper.rb +39 -0
  79. data/test/task_base_test.rb +115 -0
  80. data/test/task_class_test.rb +56 -4
  81. data/test/task_execute_test.rb +29 -0
  82. data/test/task_test.rb +89 -70
  83. data/test/test/env_vars_test.rb +48 -0
  84. data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/file.txt +0 -0
  85. data/test/test/{inference_methods → file_methods}/test_assert_expected/expected/folder/file.txt +0 -0
  86. data/test/test/{inference_methods → file_methods}/test_assert_expected/input/file.txt +0 -0
  87. data/test/test/{inference_methods → file_methods}/test_assert_expected/input/folder/file.txt +0 -0
  88. data/test/test/file_methods/test_assert_files_exist/input/input_1.txt +0 -0
  89. data/test/test/file_methods/test_assert_files_exist/input/input_2.txt +0 -0
  90. data/test/test/file_methods/test_assert_output_files_equal/expected/one.txt +1 -0
  91. data/test/test/file_methods/test_assert_output_files_equal/expected/two.txt +1 -0
  92. data/test/test/file_methods/test_assert_output_files_equal/input/one.txt +1 -0
  93. data/test/test/file_methods/test_assert_output_files_equal/input/two.txt +1 -0
  94. data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_1.txt +0 -0
  95. data/test/test/{inference_methods → file_methods}/test_file_compare/expected/output_2.txt +0 -0
  96. data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_1.txt +0 -0
  97. data/test/test/{inference_methods → file_methods}/test_file_compare/input/input_2.txt +0 -0
  98. data/test/test/file_methods/test_infer_glob/expected/file.yml +0 -0
  99. data/test/test/file_methods/test_infer_glob/expected/file_1.txt +0 -0
  100. data/test/test/file_methods/test_infer_glob/expected/file_2.txt +0 -0
  101. data/test/test/file_methods/test_method_glob/expected/file.yml +0 -0
  102. data/test/test/file_methods/test_method_glob/expected/file_1.txt +0 -0
  103. data/test/test/file_methods/test_method_glob/expected/file_2.txt +0 -0
  104. data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_1.yml +0 -0
  105. data/test/test/{inference_methods → file_methods}/test_yml_compare/expected/output_2.yml +0 -0
  106. data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_1.yml +0 -0
  107. data/test/test/{inference_methods → file_methods}/test_yml_compare/input/input_2.yml +0 -0
  108. data/test/test/file_methods_test.rb +204 -0
  109. data/test/test/subset_methods_test.rb +93 -33
  110. data/test/test/test_assert_expected_result_files/expected/task/name/a.txt +1 -0
  111. data/test/test/test_assert_expected_result_files/expected/task/name/b.txt +1 -0
  112. data/test/test/test_assert_expected_result_files/input/a.txt +1 -0
  113. data/test/test/test_assert_expected_result_files/input/b.txt +1 -0
  114. data/test/test/test_file_task_test/expected/one.txt +1 -0
  115. data/test/test/test_file_task_test/expected/two.txt +1 -0
  116. data/test/test/test_file_task_test/input/one.txt +1 -0
  117. data/test/test/test_file_task_test/input/two.txt +1 -0
  118. data/test/test_test.rb +143 -3
  119. data/test/workflow_test.rb +2 -0
  120. data/vendor/rails_generator.rb +56 -0
  121. data/vendor/rails_generator/base.rb +263 -0
  122. data/vendor/rails_generator/commands.rb +581 -0
  123. data/vendor/rails_generator/generated_attribute.rb +42 -0
  124. data/vendor/rails_generator/lookup.rb +209 -0
  125. data/vendor/rails_generator/manifest.rb +53 -0
  126. data/vendor/rails_generator/options.rb +143 -0
  127. data/vendor/rails_generator/scripts.rb +83 -0
  128. data/vendor/rails_generator/scripts/destroy.rb +7 -0
  129. data/vendor/rails_generator/scripts/generate.rb +7 -0
  130. data/vendor/rails_generator/scripts/update.rb +12 -0
  131. data/vendor/rails_generator/simple_logger.rb +46 -0
  132. data/vendor/rails_generator/spec.rb +44 -0
  133. metadata +180 -196
  134. data/lib/tap/generator/generators/root/templates/app.yml +0 -19
  135. data/lib/tap/generator/generators/root/templates/config/process_tap_request.yml +0 -4
  136. data/lib/tap/generator/generators/root/templates/lib/process_tap_request.rb +0 -26
  137. data/lib/tap/generator/generators/root/templates/public/images/nav.jpg +0 -0
  138. data/lib/tap/generator/generators/root/templates/public/stylesheets/color.css +0 -57
  139. data/lib/tap/generator/generators/root/templates/public/stylesheets/layout.css +0 -108
  140. data/lib/tap/generator/generators/root/templates/public/stylesheets/normalize.css +0 -40
  141. data/lib/tap/generator/generators/root/templates/public/stylesheets/typography.css +0 -21
  142. data/lib/tap/generator/generators/root/templates/server/config/environment.rb +0 -60
  143. data/lib/tap/generator/generators/root/templates/server/lib/tasks/clear_database_prerequisites.rake +0 -5
  144. data/lib/tap/generator/generators/root/templates/server/test/test_helper.rb +0 -53
  145. data/lib/tap/script/server.rb +0 -12
  146. data/lib/tap/support/rap.rb +0 -38
  147. data/lib/tap/test/inference_methods.rb +0 -298
  148. data/test/task/config/task_with_config.yml +0 -1
  149. data/test/test/inference_methods_test.rb +0 -311
@@ -0,0 +1,581 @@
1
+ require 'delegate'
2
+ require 'optparse'
3
+ require 'fileutils'
4
+ require 'tempfile'
5
+ require 'erb'
6
+
7
+ module Rails
8
+ module Generator
9
+ module Commands
10
+ # Here's a convenient way to get a handle on generator commands.
11
+ # Command.instance('destroy', my_generator) instantiates a Destroy
12
+ # delegate of my_generator ready to do your dirty work.
13
+ def self.instance(command, generator)
14
+ const_get(command.to_s.camelize).new(generator)
15
+ end
16
+
17
+ # Even more convenient access to commands. Include Commands in
18
+ # the generator Base class to get a nice #command instance method
19
+ # which returns a delegate for the requested command.
20
+ def self.included(base)
21
+ base.send(:define_method, :command) do |command|
22
+ Commands.instance(command, self)
23
+ end
24
+ end
25
+
26
+
27
+ # Generator commands delegate Rails::Generator::Base and implement
28
+ # a standard set of actions. Their behavior is defined by the way
29
+ # they respond to these actions: Create brings life; Destroy brings
30
+ # death; List passively observes.
31
+ #
32
+ # Commands are invoked by replaying (or rewinding) the generator's
33
+ # manifest of actions. See Rails::Generator::Manifest and
34
+ # Rails::Generator::Base#manifest method that generator subclasses
35
+ # are required to override.
36
+ #
37
+ # Commands allows generators to "plug in" invocation behavior, which
38
+ # corresponds to the GoF Strategy pattern.
39
+ class Base < DelegateClass(Rails::Generator::Base)
40
+ # Replay action manifest. RewindBase subclass rewinds manifest.
41
+ def invoke!
42
+ manifest.replay(self)
43
+ end
44
+
45
+ def dependency(generator_name, args, runtime_options = {})
46
+ logger.dependency(generator_name) do
47
+ self.class.new(instance(generator_name, args, full_options(runtime_options))).invoke!
48
+ end
49
+ end
50
+
51
+ # Does nothing for all commands except Create.
52
+ def class_collisions(*class_names)
53
+ end
54
+
55
+ # Does nothing for all commands except Create.
56
+ def readme(*args)
57
+ end
58
+
59
+ protected
60
+ def migration_directory(relative_path)
61
+ directory(@migration_directory = relative_path)
62
+ end
63
+
64
+ def existing_migrations(file_name)
65
+ Dir.glob("#{@migration_directory}/[0-9]*_*.rb").grep(/[0-9]+_#{file_name}.rb$/)
66
+ end
67
+
68
+ def migration_exists?(file_name)
69
+ not existing_migrations(file_name).empty?
70
+ end
71
+
72
+ def current_migration_number
73
+ Dir.glob("#{@migration_directory}/[0-9]*.rb").inject(0) do |max, file_path|
74
+ n = File.basename(file_path).split('_', 2).first.to_i
75
+ if n > max then n else max end
76
+ end
77
+ end
78
+
79
+ def next_migration_number
80
+ current_migration_number + 1
81
+ end
82
+
83
+ def next_migration_string(padding = 3)
84
+ "%.#{padding}d" % next_migration_number
85
+ end
86
+
87
+ def gsub_file(relative_destination, regexp, *args, &block)
88
+ path = destination_path(relative_destination)
89
+ content = File.read(path).gsub(regexp, *args, &block)
90
+ File.open(path, 'wb') { |file| file.write(content) }
91
+ end
92
+
93
+ private
94
+ # Ask the user interactively whether to force collision.
95
+ def force_file_collision?(destination, src, dst, file_options = {}, &block)
96
+ $stdout.print "overwrite #{destination}? [Ynaqd] "
97
+ case $stdin.gets
98
+ when /d/i
99
+ Tempfile.open(File.basename(destination), File.dirname(dst)) do |temp|
100
+ temp.write render_file(src, file_options, &block)
101
+ temp.rewind
102
+ $stdout.puts `#{diff_cmd} #{dst} #{temp.path}`
103
+ end
104
+ puts "retrying"
105
+ raise 'retry diff'
106
+ when /a/i
107
+ $stdout.puts "forcing #{spec.name}"
108
+ options[:collision] = :force
109
+ when /q/i
110
+ $stdout.puts "aborting #{spec.name}"
111
+ raise SystemExit
112
+ when /n/i then :skip
113
+ else :force
114
+ end
115
+ rescue
116
+ retry
117
+ end
118
+
119
+ def diff_cmd
120
+ ENV['RAILS_DIFF'] || 'diff -u'
121
+ end
122
+
123
+ def render_template_part(template_options)
124
+ # Getting Sandbox to evaluate part template in it
125
+ part_binding = template_options[:sandbox].call.sandbox_binding
126
+ part_rel_path = template_options[:insert]
127
+ part_path = source_path(part_rel_path)
128
+
129
+ # Render inner template within Sandbox binding
130
+ rendered_part = ERB.new(File.readlines(part_path).join, nil, '-').result(part_binding)
131
+ begin_mark = template_part_mark(template_options[:begin_mark], template_options[:mark_id])
132
+ end_mark = template_part_mark(template_options[:end_mark], template_options[:mark_id])
133
+ begin_mark + rendered_part + end_mark
134
+ end
135
+
136
+ def template_part_mark(name, id)
137
+ "<!--[#{name}:#{id}]-->\n"
138
+ end
139
+ end
140
+
141
+ # Base class for commands which handle generator actions in reverse, such as Destroy.
142
+ class RewindBase < Base
143
+ # Rewind action manifest.
144
+ def invoke!
145
+ manifest.rewind(self)
146
+ end
147
+ end
148
+
149
+
150
+ # Create is the premier generator command. It copies files, creates
151
+ # directories, renders templates, and more.
152
+ class Create < Base
153
+
154
+ # Check whether the given class names are already taken by
155
+ # Ruby or Rails. In the future, expand to check other namespaces
156
+ # such as the rest of the user's app.
157
+ def class_collisions(*class_names)
158
+ class_names.flatten.each do |class_name|
159
+ # Convert to string to allow symbol arguments.
160
+ class_name = class_name.to_s
161
+
162
+ # Skip empty strings.
163
+ next if class_name.strip.empty?
164
+
165
+ # Split the class from its module nesting.
166
+ nesting = class_name.split('::')
167
+ name = nesting.pop
168
+
169
+ # Extract the last Module in the nesting.
170
+ last = nesting.inject(Object) { |last, nest|
171
+ break unless last.const_defined?(nest)
172
+ last.const_get(nest)
173
+ }
174
+
175
+ # If the last Module exists, check whether the given
176
+ # class exists and raise a collision if so.
177
+ if last and last.const_defined?(name.camelize)
178
+ raise_class_collision(class_name)
179
+ end
180
+ end
181
+ end
182
+
183
+ # Copy a file from source to destination with collision checking.
184
+ #
185
+ # The file_options hash accepts :chmod and :shebang and :collision options.
186
+ # :chmod sets the permissions of the destination file:
187
+ # file 'config/empty.log', 'log/test.log', :chmod => 0664
188
+ # :shebang sets the #!/usr/bin/ruby line for scripts
189
+ # file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby'
190
+ # :collision sets the collision option only for the destination file:
191
+ # file 'settings/server.yml', 'config/server.yml', :collision => :skip
192
+ #
193
+ # Collisions are handled by checking whether the destination file
194
+ # exists and either skipping the file, forcing overwrite, or asking
195
+ # the user what to do.
196
+ def file(relative_source, relative_destination, file_options = {}, &block)
197
+ # Determine full paths for source and destination files.
198
+ source = source_path(relative_source)
199
+ destination = destination_path(relative_destination)
200
+ destination_exists = File.exists?(destination)
201
+
202
+ # If source and destination are identical then we're done.
203
+ if destination_exists and identical?(source, destination, &block)
204
+ return logger.identical(relative_destination)
205
+ end
206
+
207
+ # Check for and resolve file collisions.
208
+ if destination_exists
209
+
210
+ # Make a choice whether to overwrite the file. :force and
211
+ # :skip already have their mind made up, but give :ask a shot.
212
+ choice = case (file_options[:collision] || options[:collision]).to_sym #|| :ask
213
+ when :ask then force_file_collision?(relative_destination, source, destination, file_options, &block)
214
+ when :force then :force
215
+ when :skip then :skip
216
+ else raise "Invalid collision option: #{options[:collision].inspect}"
217
+ end
218
+
219
+ # Take action based on our choice. Bail out if we chose to
220
+ # skip the file; otherwise, log our transgression and continue.
221
+ case choice
222
+ when :force then logger.force(relative_destination)
223
+ when :skip then return(logger.skip(relative_destination))
224
+ else raise "Invalid collision choice: #{choice}.inspect"
225
+ end
226
+
227
+ # File doesn't exist so log its unbesmirched creation.
228
+ else
229
+ logger.create relative_destination
230
+ end
231
+
232
+ # If we're pretending, back off now.
233
+ return if options[:pretend]
234
+
235
+ # Write destination file with optional shebang. Yield for content
236
+ # if block given so templaters may render the source file. If a
237
+ # shebang is requested, replace the existing shebang or insert a
238
+ # new one.
239
+ File.open(destination, 'wb') do |dest|
240
+ dest.write render_file(source, file_options, &block)
241
+ end
242
+
243
+ # Optionally change permissions.
244
+ if file_options[:chmod]
245
+ FileUtils.chmod(file_options[:chmod], destination)
246
+ end
247
+
248
+ # Optionally add file to subversion
249
+ system("svn add #{destination}") if options[:svn]
250
+ end
251
+
252
+ # Checks if the source and the destination file are identical. If
253
+ # passed a block then the source file is a template that needs to first
254
+ # be evaluated before being compared to the destination.
255
+ def identical?(source, destination, &block)
256
+ return false if File.directory? destination
257
+ source = block_given? ? File.open(source) {|sf| yield(sf)} : IO.read(source)
258
+ destination = IO.read(destination)
259
+ source == destination
260
+ end
261
+
262
+ # Generate a file for a Rails application using an ERuby template.
263
+ # Looks up and evalutes a template by name and writes the result.
264
+ #
265
+ # The ERB template uses explicit trim mode to best control the
266
+ # proliferation of whitespace in generated code. <%- trims leading
267
+ # whitespace; -%> trims trailing whitespace including one newline.
268
+ #
269
+ # A hash of template options may be passed as the last argument.
270
+ # The options accepted by the file are accepted as well as :assigns,
271
+ # a hash of variable bindings. Example:
272
+ # template 'foo', 'bar', :assigns => { :action => 'view' }
273
+ #
274
+ # Template is implemented in terms of file. It calls file with a
275
+ # block which takes a file handle and returns its rendered contents.
276
+ def template(relative_source, relative_destination, template_options = {})
277
+ file(relative_source, relative_destination, template_options) do |file|
278
+ # Evaluate any assignments in a temporary, throwaway binding.
279
+ vars = template_options[:assigns] || {}
280
+ b = binding
281
+ vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
282
+
283
+ # Render the source file with the temporary binding.
284
+ ERB.new(file.read, nil, '-').result(b)
285
+ end
286
+ end
287
+
288
+ def complex_template(relative_source, relative_destination, template_options = {})
289
+ options = template_options.dup
290
+ options[:assigns] ||= {}
291
+ options[:assigns]['template_for_inclusion'] = render_template_part(template_options)
292
+ template(relative_source, relative_destination, options)
293
+ end
294
+
295
+ # Create a directory including any missing parent directories.
296
+ # Always directories which exist.
297
+ def directory(relative_path)
298
+ path = destination_path(relative_path)
299
+ if File.exists?(path)
300
+ logger.exists relative_path
301
+ else
302
+ logger.create relative_path
303
+ unless options[:pretend]
304
+ FileUtils.mkdir_p(path)
305
+
306
+ # Subversion doesn't do path adds, so we need to add
307
+ # each directory individually.
308
+ # So stack up the directory tree and add the paths to
309
+ # subversion in order without recursion.
310
+ if options[:svn]
311
+ stack=[relative_path]
312
+ until File.dirname(stack.last) == stack.last # dirname('.') == '.'
313
+ stack.push File.dirname(stack.last)
314
+ end
315
+ stack.reverse_each do |rel_path|
316
+ svn_path = destination_path(rel_path)
317
+ system("svn add -N #{svn_path}") unless File.directory?(File.join(svn_path, '.svn'))
318
+ end
319
+ end
320
+ end
321
+ end
322
+ end
323
+
324
+ # Display a README.
325
+ def readme(*relative_sources)
326
+ relative_sources.flatten.each do |relative_source|
327
+ logger.readme relative_source
328
+ puts File.read(source_path(relative_source)) unless options[:pretend]
329
+ end
330
+ end
331
+
332
+ # When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template.
333
+ def migration_template(relative_source, relative_destination, template_options = {})
334
+ migration_directory relative_destination
335
+ migration_file_name = template_options[:migration_file_name] || file_name
336
+ raise "Another migration is already named #{migration_file_name}: #{existing_migrations(migration_file_name).first}" if migration_exists?(migration_file_name)
337
+ template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options)
338
+ end
339
+
340
+ def route_resources(*resources)
341
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
342
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
343
+
344
+ logger.route "map.resources #{resource_list}"
345
+ unless options[:pretend]
346
+ gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
347
+ "#{match}\n map.resources #{resource_list}\n"
348
+ end
349
+ end
350
+ end
351
+
352
+ private
353
+ def render_file(path, options = {})
354
+ File.open(path, 'rb') do |file|
355
+ if block_given?
356
+ yield file
357
+ else
358
+ content = ''
359
+ if shebang = options[:shebang]
360
+ content << "#!#{shebang}\n"
361
+ if line = file.gets
362
+ content << "line\n" if line !~ /^#!/
363
+ end
364
+ end
365
+ content << file.read
366
+ end
367
+ end
368
+ end
369
+
370
+ # Raise a usage error with an informative WordNet suggestion.
371
+ # Thanks to Florian Gross (flgr).
372
+ def raise_class_collision(class_name)
373
+ message = <<end_message
374
+ The name '#{class_name}' is reserved by Ruby on Rails.
375
+ Please choose an alternative and run this generator again.
376
+ end_message
377
+ if suggest = find_synonyms(class_name)
378
+ message << "\n Suggestions: \n\n"
379
+ message << suggest.join("\n")
380
+ end
381
+ raise UsageError, message
382
+ end
383
+
384
+ SYNONYM_LOOKUP_URI = "http://wordnet.princeton.edu/cgi-bin/webwn2.0?stage=2&word=%s&posnumber=1&searchtypenumber=2&senses=&showglosses=1"
385
+
386
+ # Look up synonyms on WordNet. Thanks to Florian Gross (flgr).
387
+ def find_synonyms(word)
388
+ require 'open-uri'
389
+ require 'timeout'
390
+ timeout(5) do
391
+ open(SYNONYM_LOOKUP_URI % word) do |stream|
392
+ data = stream.read.gsub("&nbsp;", " ").gsub("<BR>", "")
393
+ data.scan(/^Sense \d+\n.+?\n\n/m)
394
+ end
395
+ end
396
+ rescue Exception
397
+ return nil
398
+ end
399
+ end
400
+
401
+
402
+ # Undo the actions performed by a generator. Rewind the action
403
+ # manifest and attempt to completely erase the results of each action.
404
+ class Destroy < RewindBase
405
+ # Remove a file if it exists and is a file.
406
+ def file(relative_source, relative_destination, file_options = {})
407
+ destination = destination_path(relative_destination)
408
+ if File.exists?(destination)
409
+ logger.rm relative_destination
410
+ unless options[:pretend]
411
+ if options[:svn]
412
+ # If the file has been marked to be added
413
+ # but has not yet been checked in, revert and delete
414
+ if options[:svn][relative_destination]
415
+ system("svn revert #{destination}")
416
+ FileUtils.rm(destination)
417
+ else
418
+ # If the directory is not in the status list, it
419
+ # has no modifications so we can simply remove it
420
+ system("svn rm #{destination}")
421
+ end
422
+ else
423
+ FileUtils.rm(destination)
424
+ end
425
+ end
426
+ else
427
+ logger.missing relative_destination
428
+ return
429
+ end
430
+ end
431
+
432
+ # Templates are deleted just like files and the actions take the
433
+ # same parameters, so simply alias the file method.
434
+ alias_method :template, :file
435
+
436
+ # Remove each directory in the given path from right to left.
437
+ # Remove each subdirectory if it exists and is a directory.
438
+ def directory(relative_path)
439
+ parts = relative_path.split('/')
440
+ until parts.empty?
441
+ partial = File.join(parts)
442
+ path = destination_path(partial)
443
+ if File.exists?(path)
444
+ if Dir[File.join(path, '*')].empty?
445
+ logger.rmdir partial
446
+ unless options[:pretend]
447
+ if options[:svn]
448
+ # If the directory has been marked to be added
449
+ # but has not yet been checked in, revert and delete
450
+ if options[:svn][relative_path]
451
+ system("svn revert #{path}")
452
+ FileUtils.rmdir(path)
453
+ else
454
+ # If the directory is not in the status list, it
455
+ # has no modifications so we can simply remove it
456
+ system("svn rm #{path}")
457
+ end
458
+ else
459
+ FileUtils.rmdir(path)
460
+ end
461
+ end
462
+ else
463
+ logger.notempty partial
464
+ end
465
+ else
466
+ logger.missing partial
467
+ end
468
+ parts.pop
469
+ end
470
+ end
471
+
472
+ def complex_template(*args)
473
+ # nothing should be done here
474
+ end
475
+
476
+ # When deleting a migration, it knows to delete every file named "[0-9]*_#{file_name}".
477
+ def migration_template(relative_source, relative_destination, template_options = {})
478
+ migration_directory relative_destination
479
+
480
+ migration_file_name = template_options[:migration_file_name] || file_name
481
+ unless migration_exists?(migration_file_name)
482
+ puts "There is no migration named #{migration_file_name}"
483
+ return
484
+ end
485
+
486
+
487
+ existing_migrations(migration_file_name).each do |file_path|
488
+ file(relative_source, file_path, template_options)
489
+ end
490
+ end
491
+
492
+ def route_resources(*resources)
493
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
494
+ look_for = "\n map.resources #{resource_list}\n"
495
+ logger.route "map.resources #{resource_list}"
496
+ gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
497
+ end
498
+ end
499
+
500
+
501
+ # List a generator's action manifest.
502
+ class List < Base
503
+ def dependency(generator_name, args, options = {})
504
+ logger.dependency "#{generator_name}(#{args.join(', ')}, #{options.inspect})"
505
+ end
506
+
507
+ def class_collisions(*class_names)
508
+ logger.class_collisions class_names.join(', ')
509
+ end
510
+
511
+ def file(relative_source, relative_destination, options = {})
512
+ logger.file relative_destination
513
+ end
514
+
515
+ def template(relative_source, relative_destination, options = {})
516
+ logger.template relative_destination
517
+ end
518
+
519
+ def complex_template(relative_source, relative_destination, options = {})
520
+ logger.template "#{options[:insert]} inside #{relative_destination}"
521
+ end
522
+
523
+ def directory(relative_path)
524
+ logger.directory "#{destination_path(relative_path)}/"
525
+ end
526
+
527
+ def readme(*args)
528
+ logger.readme args.join(', ')
529
+ end
530
+
531
+ def migration_template(relative_source, relative_destination, options = {})
532
+ migration_directory relative_destination
533
+ logger.migration_template file_name
534
+ end
535
+
536
+ def route_resources(*resources)
537
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
538
+ logger.route "map.resources #{resource_list}"
539
+ end
540
+ end
541
+
542
+ # Update generator's action manifest.
543
+ class Update < Create
544
+ def file(relative_source, relative_destination, options = {})
545
+ # logger.file relative_destination
546
+ end
547
+
548
+ def template(relative_source, relative_destination, options = {})
549
+ # logger.template relative_destination
550
+ end
551
+
552
+ def complex_template(relative_source, relative_destination, template_options = {})
553
+
554
+ begin
555
+ dest_file = destination_path(relative_destination)
556
+ source_to_update = File.readlines(dest_file).join
557
+ rescue Errno::ENOENT
558
+ logger.missing relative_destination
559
+ return
560
+ end
561
+
562
+ logger.refreshing "#{template_options[:insert].gsub(/\.rhtml/,'')} inside #{relative_destination}"
563
+
564
+ begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id]))
565
+ end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id]))
566
+
567
+ # Refreshing inner part of the template with freshly rendered part.
568
+ rendered_part = render_template_part(template_options)
569
+ source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part)
570
+
571
+ File.open(dest_file, 'w') { |file| file.write(source_to_update) }
572
+ end
573
+
574
+ def directory(relative_path)
575
+ # logger.directory "#{destination_path(relative_path)}/"
576
+ end
577
+ end
578
+
579
+ end
580
+ end
581
+ end