apimaster 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +12 -0
  3. data/apimaster.gemspec +14 -0
  4. data/bin/apimaster +6 -0
  5. data/lib/apimaster.rb +21 -0
  6. data/lib/apimaster/application.rb +28 -0
  7. data/lib/apimaster/controllers/errors.rb +34 -0
  8. data/lib/apimaster/error.rb +79 -0
  9. data/lib/apimaster/generators/application.rb +112 -0
  10. data/lib/apimaster/generators/base.rb +206 -0
  11. data/lib/apimaster/generators/command.rb +697 -0
  12. data/lib/apimaster/generators/manifest.rb +51 -0
  13. data/lib/apimaster/generators/options.rb +162 -0
  14. data/lib/apimaster/generators/scripts.rb +64 -0
  15. data/lib/apimaster/generators/simple_logger.rb +44 -0
  16. data/lib/apimaster/generators/templates/Gemfile +21 -0
  17. data/lib/apimaster/generators/templates/LICENSE +20 -0
  18. data/lib/apimaster/generators/templates/README.md +10 -0
  19. data/lib/apimaster/generators/templates/Rakefile +19 -0
  20. data/lib/apimaster/generators/templates/TODO +4 -0
  21. data/lib/apimaster/generators/templates/app/controllers/index_controller.rb.erb +7 -0
  22. data/lib/apimaster/generators/templates/config.ru.erb +8 -0
  23. data/lib/apimaster/generators/templates/config/application.rb.erb +19 -0
  24. data/lib/apimaster/generators/templates/config/boot.rb.erb +17 -0
  25. data/lib/apimaster/generators/templates/config/initializer.rb.erb +13 -0
  26. data/lib/apimaster/generators/templates/config/patches.rb.erb +0 -0
  27. data/lib/apimaster/generators/templates/config/settings/app.yml.erb +3 -0
  28. data/lib/apimaster/generators/templates/config/settings/mongoid.yml.erb +66 -0
  29. data/lib/apimaster/generators/templates/config/settings/oauth.yml.erb +8 -0
  30. data/lib/apimaster/generators/templates/gitignore +10 -0
  31. data/lib/apimaster/generators/templates/lib/module.rb.erb +6 -0
  32. data/lib/apimaster/generators/templates/test/functional_test.rb.erb +2 -0
  33. data/lib/apimaster/generators/templates/test/test_helper.rb.erb +1 -0
  34. data/lib/apimaster/generators/templates/test/unit_test.rb.erb +2 -0
  35. data/lib/apimaster/generators/version.rb +3 -0
  36. data/lib/apimaster/helpers/headers.rb +30 -0
  37. data/lib/apimaster/helpers/request.rb +49 -0
  38. data/lib/apimaster/helpers/session.rb +36 -0
  39. data/lib/apimaster/mapper.rb +86 -0
  40. data/lib/apimaster/models/user.rb +33 -0
  41. data/lib/apimaster/models/user_mock.rb +13 -0
  42. data/lib/apimaster/setting.rb +68 -0
  43. metadata +45 -3
@@ -0,0 +1,697 @@
1
+ require 'optparse'
2
+ require 'fileutils'
3
+ require 'tempfile'
4
+ require 'erb'
5
+ require 'digest/sha1'
6
+
7
+ module Apimaster::Generators
8
+
9
+ # Generator commands delegate Apimaster::Generators::Base and implement
10
+ # a standard set of actions. Their behavior is defined by the way
11
+ # they respond to these actions: Create brings life; Destroy brings
12
+ # death; List passively observes.
13
+ #
14
+ # Commands are invoked by replaying (or rewinding) the generator's
15
+ # manifest of actions. See Apimaster::Generators::Manifest and
16
+ # are required to override.
17
+ #
18
+ # Commands allows generators to "plug in" invocation behavior, which
19
+ # corresponds to the GoF Strategy pattern.
20
+ class Command < Base
21
+
22
+ # Return the full path from the source root for the given path.
23
+ # Example for source_root = '/source':
24
+ # source_path('some/path.rb') == '/source/some/path.rb'
25
+ #
26
+ # The given path may include a colon ':' character to indicate that
27
+ # the file belongs to another generator. This notation allows any
28
+ # generator to borrow files from another. Example:
29
+ # source_path('model:fixture.yml') = '/model/source/path/fixture.yml'
30
+ def source_path(relative_source)
31
+ # Check whether we're referring to another generator's file.
32
+ name, path = relative_source.split(':', 2)
33
+
34
+ # If not, return the full path to our source file.
35
+ if path.nil?
36
+ File.join(source_root, name)
37
+
38
+ # Otherwise, ask our referral for the file.
39
+ else
40
+ # FIXME: this is broken, though almost always true. Others'
41
+ # source_root are not necessarily the templates dir.
42
+ File.join(self.class.lookup(name).path, 'templates', path)
43
+ end
44
+ end
45
+
46
+ # Return the full path from the destination root for the given path.
47
+ # Example for destination_root = '/dest':
48
+ # destination_path('some/path.rb') == '/dest/some/path.rb'
49
+ def destination_path(relative_destination)
50
+ File.expand_path(File.join(destination_root, relative_destination))
51
+ end
52
+
53
+ # Replay action manifest. RewindBase subclass rewinds manifest.
54
+ def invoke!
55
+ manifest.replay(self)
56
+ after_generate
57
+ end
58
+
59
+ def dependency(generator_name, args, runtime_options = {})
60
+ logger.dependency(generator_name) do
61
+ self.class.new(instance(generator_name, args, full_options(runtime_options))).invoke!
62
+ end
63
+ end
64
+
65
+ # Does nothing for all commands except Create.
66
+ def class_collisions(*class_names)
67
+ end
68
+
69
+ # Does nothing for all commands except Create.
70
+ def readme(*args)
71
+ end
72
+
73
+ # Does nothing for all commands except Create.
74
+ def write_manifest
75
+ end
76
+
77
+ protected
78
+ def current_migration_number
79
+ Dir.glob("#{RAILS_ROOT}/#{@migration_directory}/[0-9]*_*.rb").inject(0) do |max, file_path|
80
+ n = File.basename(file_path).split('_', 2).first.to_i
81
+ if n > max then n else max end
82
+ end
83
+ end
84
+
85
+ def next_migration_number
86
+ current_migration_number + 1
87
+ end
88
+
89
+ def migration_directory(relative_path)
90
+ directory(@migration_directory = relative_path)
91
+ end
92
+
93
+ def existing_migrations(file_name)
94
+ Dir.glob("#{@migration_directory}/[0-9]*_*.rb").grep(/[0-9]+_#{file_name}.rb$/)
95
+ end
96
+
97
+ def migration_exists?(file_name)
98
+ not existing_migrations(file_name).empty?
99
+ end
100
+
101
+ def next_migration_string(padding = 3)
102
+ if ActiveRecord::Base.timestamped_migrations
103
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
104
+ else
105
+ "%.#{padding}d" % next_migration_number
106
+ end
107
+ end
108
+
109
+ def gsub_file(relative_destination, regexp, *args, &block)
110
+ path = destination_path(relative_destination)
111
+ content = File.read(path).gsub(regexp, *args, &block)
112
+ File.open(path, 'wb') { |file| file.write(content) }
113
+ end
114
+
115
+ private
116
+ # Ask the user interactively whether to force collision.
117
+ def force_file_collision?(destination, src, dst, file_options = {}, &block)
118
+ stdout.print "overwrite #{destination}? (enter \"h\" for help) [Ynaiqd] "
119
+ stdout.flush
120
+ case $stdin.gets.chomp
121
+ when /\Ad\z/i
122
+ Tempfile.open(File.basename(destination), File.dirname(dst)) do |temp|
123
+ temp.write render_file(src, file_options, &block)
124
+ temp.rewind
125
+ stdout.puts `#{diff_cmd} #{dst} #{temp.path}`
126
+ end
127
+ stdout.puts "retrying"
128
+ raise 'retry diff'
129
+ when /\Aa\z/i
130
+ stdout.puts "forcing #{spec.name}"
131
+ options[:collision] = :force
132
+ when /\Ai\z/i
133
+ stdout.puts "ignoring #{spec.name}"
134
+ options[:collision] = :skip
135
+ when /\Aq\z/i
136
+ stdout.puts "aborting #{spec.name}"
137
+ raise SystemExit
138
+ when /\An\z/i then :skip
139
+ when /\Ay\z/i then :force
140
+ else
141
+ stdout.puts <<-HELP.gsub(/^ /, '')
142
+ Y - yes, overwrite
143
+ n - no, do not overwrite
144
+ a - all, overwrite this and all others
145
+ i - ignore, skip any conflicts
146
+ q - quit, abort
147
+ d - diff, show the differences between the old and the new
148
+ h - help, show this help
149
+ HELP
150
+ raise 'retry'
151
+ end
152
+ rescue
153
+ retry
154
+ end
155
+
156
+ def diff_cmd
157
+ ENV['RAILS_DIFF'] || 'diff -u'
158
+ end
159
+
160
+ def render_template_part(template_options)
161
+ # Getting Sandbox to evaluate part template in it
162
+ part_binding = template_options[:sandbox].call.sandbox_binding
163
+ part_rel_path = template_options[:insert]
164
+ part_path = source_path(part_rel_path)
165
+
166
+ # Render inner template within Sandbox binding
167
+ rendered_part = ERB.new(File.readlines(part_path).join, nil, '-').result(part_binding)
168
+ begin_mark = template_part_mark(template_options[:begin_mark], template_options[:mark_id])
169
+ end_mark = template_part_mark(template_options[:end_mark], template_options[:mark_id])
170
+ begin_mark + rendered_part + end_mark
171
+ end
172
+
173
+ def template_part_mark(name, id)
174
+ "<!--[#{name}:#{id}]-->\n"
175
+ end
176
+ end
177
+
178
+ # Base class for commands which handle generator actions in reverse, such as Destroy.
179
+ class RewindBase < Command
180
+ # Rewind action manifest.
181
+ def invoke!
182
+ manifest.rewind(self)
183
+ end
184
+ end
185
+
186
+
187
+ # Create is the premier generator command. It copies files, creates
188
+ # directories, renders templates, and more.
189
+ class Create < Command
190
+
191
+ # Check whether the given class names are already taken.
192
+ # In the future, expand to check other namespaces
193
+ # such as the rest of the user's app.
194
+ def class_collisions(*class_names)
195
+ class_names.flatten.each do |class_name|
196
+ # Convert to string to allow symbol arguments.
197
+ class_name = class_name.to_s
198
+
199
+ # Skip empty strings.
200
+ next if class_name.strip.empty?
201
+
202
+ # Split the class from its module nesting.
203
+ nesting = class_name.split('::')
204
+ name = nesting.pop
205
+
206
+ # Extract the last Module in the nesting.
207
+ last = nesting.inject(Object) { |last, nest|
208
+ break unless last.const_defined?(nest)
209
+ last.const_get(nest)
210
+ }
211
+
212
+ # If the last Module exists, check whether the given
213
+ # class exists and raise a collision if so.
214
+ if last and last.const_defined?(name.camelize)
215
+ raise_class_collision(class_name)
216
+ end
217
+ end
218
+ end
219
+
220
+ # Copy a file from source to destination with collision checking.
221
+ #
222
+ # The file_options hash accepts :chmod and :shebang and :collision options.
223
+ # :chmod sets the permissions of the destination file:
224
+ # file 'config/empty.log', 'log/test.log', :chmod => 0664
225
+ # :shebang sets the #!/usr/bin/ruby line for scripts
226
+ # file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby'
227
+ # :collision sets the collision option only for the destination file:
228
+ # file 'settings/server.yml', 'config/server.yml', :collision => :skip
229
+ #
230
+ # Collisions are handled by checking whether the destination file
231
+ # exists and either skipping the file, forcing overwrite, or asking
232
+ # the user what to do.
233
+ def file(relative_source, relative_destination, file_options = {}, &block)
234
+ # Determine full paths for source and destination files.
235
+ source = source_path(relative_source)
236
+ destination = destination_path(relative_destination)
237
+ destination_exists = File.exist?(destination)
238
+
239
+ # If source and destination are identical then we're done.
240
+ if destination_exists and identical?(source, destination, &block)
241
+ return logger.identical(relative_destination)
242
+ end
243
+
244
+ # Check for and resolve file collisions.
245
+ if destination_exists
246
+
247
+ # Make a choice whether to overwrite the file. :force and
248
+ # :skip already have their mind made up, but give :ask a shot.
249
+ choice = case (file_options[:collision] || options[:collision]).to_sym #|| :ask
250
+ when :ask then force_file_collision?(relative_destination, source, destination, file_options, &block)
251
+ when :force then :force
252
+ when :skip then :skip
253
+ else raise "Invalid collision option: #{options[:collision].inspect}"
254
+ end
255
+
256
+ # Take action based on our choice. Bail out if we chose to
257
+ # skip the file; otherwise, log our transgression and continue.
258
+ case choice
259
+ when :force then logger.force(relative_destination)
260
+ when :skip then return(logger.skip(relative_destination))
261
+ else raise "Invalid collision choice: #{choice}.inspect"
262
+ end
263
+
264
+ # File doesn't exist so log its unbesmirched creation.
265
+ else
266
+ logger.create relative_destination
267
+ end
268
+
269
+ # If we're pretending, back off now.
270
+ return if options[:pretend]
271
+
272
+ # Write destination file with optional shebang. Yield for content
273
+ # if block given so templaters may render the source file. If a
274
+ # shebang is requested, replace the existing shebang or insert a
275
+ # new one.
276
+ File.open(destination, 'wb') do |dest|
277
+ dest.write render_file(source, file_options, &block)
278
+ end
279
+
280
+ # Optionally change permissions.
281
+ if file_options[:chmod]
282
+ FileUtils.chmod(file_options[:chmod], destination)
283
+ end
284
+
285
+ # Optionally add file to subversion or git
286
+ system("svn add #{destination}") if options[:svn]
287
+ end
288
+
289
+ def file_copy_each(files, path=nil, options = {})
290
+ path = path ? "#{path}/" : ""
291
+ files.each do |file_name|
292
+ file "#{path}#{file_name}", "#{path}#{file_name}", options
293
+ end
294
+ end
295
+
296
+ def folder(template_path, path=nil, options = {})
297
+ template_path = "/" if template_path.blank?
298
+ source = source_path(template_path)
299
+ files = Dir[source + '/*'].select { |file| File.file? file }.map { |file| file.sub(/^#{source}/,"") }
300
+ files.each do |file_name|
301
+ file "#{template_path}#{file_name}", "#{path}#{file_name}", options
302
+ end
303
+ system("git add -v #{relative_destination}") if options[:git]
304
+ end
305
+
306
+ # Checks if the source and the destination file are identical. If
307
+ # passed a block then the source file is a template that needs to first
308
+ # be evaluated before being compared to the destination.
309
+ def identical?(source, destination, &block)
310
+ return false if File.directory? destination
311
+ source = block_given? ? File.open(source) {|sf| yield(sf)} : IO.read(source)
312
+ destination = IO.read(destination)
313
+ source == destination
314
+ end
315
+
316
+ # Generate a file using an ERuby template.
317
+ # Looks up and evaluates a template by name and writes the result.
318
+ #
319
+ # The ERB template uses explicit trim mode to best control the
320
+ # proliferation of whitespace in generated code. <%- trims leading
321
+ # whitespace; -%> trims trailing whitespace including one newline.
322
+ #
323
+ # A hash of template options may be passed as the last argument.
324
+ # The options accepted by the file are accepted as well as :assigns,
325
+ # a hash of variable bindings. Example:
326
+ # template 'foo', 'bar', :assigns => { :action => 'view' }
327
+ #
328
+ # Template is implemented in terms of file. It calls file with a
329
+ # block which takes a file handle and returns its rendered contents.
330
+ def template(relative_source, relative_destination, template_options = {})
331
+ file(relative_source, relative_destination, template_options) do |file|
332
+ # Evaluate any assignments in a temporary, throwaway binding.
333
+ vars = template_options[:assigns] || {}
334
+ b = binding
335
+ vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
336
+
337
+ # Render the source file with the temporary binding.
338
+ ERB.new(file.read, nil, '-').result(b)
339
+ end
340
+ end
341
+
342
+ def template_copy_each(files, path = nil, options = {})
343
+ path = path ? "#{path}/" : ""
344
+ files.each do |file_name|
345
+ template "#{path}#{file_name}", "#{path}#{file_name.gsub(/\.erb$/,'')}", options
346
+ end
347
+ end
348
+
349
+ def complex_template(relative_source, relative_destination, template_options = {})
350
+ options = template_options.dup
351
+ options[:assigns] ||= {}
352
+ options[:assigns]['template_for_inclusion'] = render_template_part(template_options)
353
+ template(relative_source, relative_destination, options)
354
+ end
355
+
356
+ # Create a directory including any missing parent directories.
357
+ # Always skips directories which exist.
358
+ def directory(relative_path)
359
+ path = destination_path(relative_path)
360
+ if File.exist?(path)
361
+ logger.exists relative_path
362
+ else
363
+ logger.create relative_path
364
+ unless options[:pretend]
365
+ FileUtils.mkdir_p(path)
366
+ # git doesn't require adding the paths, adding the files later will
367
+ # automatically do a path add.
368
+
369
+ # Subversion doesn't do path adds, so we need to add
370
+ # each directory individually.
371
+ # So stack up the directory tree and add the paths to
372
+ # subversion in order without recursion.
373
+ if options[:svn]
374
+ stack = [relative_path]
375
+ until File.dirname(stack.last) == stack.last # dirname('.') == '.'
376
+ stack.push File.dirname(stack.last)
377
+ end
378
+ stack.reverse_each do |rel_path|
379
+ svn_path = destination_path(rel_path)
380
+ system("svn add -N #{svn_path}") unless File.directory?(File.join(svn_path, '.svn'))
381
+ end
382
+ end
383
+ end
384
+ end
385
+ end
386
+
387
+ # Display a README.
388
+ def readme(*relative_sources)
389
+ relative_sources.flatten.each do |relative_source|
390
+ logger.readme relative_source
391
+ stdout.puts File.read(source_path(relative_source)) unless options[:pretend]
392
+ end
393
+ end
394
+
395
+ def write_manifest(relative_destination)
396
+ files = ([relative_destination] + Dir["#{destination_root}/**/*"])
397
+ files.reject! { |file| File.directory?(file) }
398
+ files.map! { |path| path.sub("#{destination_root}/","") }
399
+ files = files.uniq.sort
400
+
401
+
402
+ destination = destination_path(relative_destination)
403
+ destination_exists = File.exists?(destination)
404
+
405
+ # Check for and resolve file collisions.
406
+ if destination_exists
407
+ # Always recreate the Manifest (perhaps we need to give the option... like normal files)
408
+ choice = :force
409
+ logger.force(relative_destination)
410
+
411
+ # File doesn't exist so log its unbesmirched creation.
412
+ else
413
+ logger.create relative_destination
414
+ end
415
+
416
+ # If we're pretending, back off now.
417
+ return if options[:pretend]
418
+
419
+ # Write destination file with optional shebang. Yield for content
420
+ # if block given so templaters may render the source file. If a
421
+ # shebang is requested, replace the existing shebang or insert a
422
+ # new one.
423
+ File.open(destination, 'wb') do |dest|
424
+ dest.write files.join("\n")
425
+ dest.write "\n"
426
+ end
427
+
428
+ # Optionally add file to subversion
429
+ system("svn add #{destination}") if options[:svn]
430
+ end
431
+
432
+ # When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template.
433
+ def migration_template(relative_source, relative_destination, template_options = {})
434
+ migration_directory relative_destination
435
+ migration_file_name = template_options[:migration_file_name] || file_name
436
+ raise "Another migration is already named #{migration_file_name}: #{existing_migrations(migration_file_name).first}" if migration_exists?(migration_file_name)
437
+ template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options)
438
+ end
439
+
440
+ def route_resources(*resources)
441
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
442
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
443
+
444
+ logger.route "map.resources #{resource_list}"
445
+ unless options[:pretend]
446
+ gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
447
+ "#{match}\n map.resources #{resource_list}\n"
448
+ end
449
+ end
450
+ end
451
+
452
+ private
453
+ def render_file(path, options = {})
454
+ File.open(path, 'rb') do |file|
455
+ if block_given?
456
+ yield file
457
+ else
458
+ content = ''
459
+ if shebang = options[:shebang]
460
+ content << "#!#{shebang}\n"
461
+ if line = file.gets
462
+ content << "line\n" if line !~ /^#!/
463
+ end
464
+ end
465
+ content << file.read
466
+ end
467
+ end
468
+ end
469
+
470
+ # Raise a usage error with an informative WordNet suggestion.
471
+ # Thanks to Florian Gross (flgr).
472
+ def raise_class_collision(class_name)
473
+ message = <<-end_message
474
+ The name '#{class_name}' is either already used in your application or reserved.
475
+ Please choose an alternative and run this generator again.
476
+ end_message
477
+ if suggest = find_synonyms(class_name)
478
+ if suggest.any?
479
+ message << "\n Suggestions: \n\n"
480
+ message << suggest.join("\n")
481
+ end
482
+ end
483
+ raise UsageError, message
484
+ end
485
+
486
+ SYNONYM_LOOKUP_URI = "http://wordnet.princeton.edu/perl/webwn?s=%s"
487
+
488
+ # Look up synonyms on WordNet. Thanks to Florian Gross (flgr).
489
+ def find_synonyms(word)
490
+ require 'open-uri'
491
+ require 'timeout'
492
+ timeout(5) do
493
+ open(SYNONYM_LOOKUP_URI % word) do |stream|
494
+ # Grab words linked to dictionary entries as possible synonyms
495
+ data = stream.read.gsub("&nbsp;", " ").scan(/<a href="webwn.*?">([\w ]*?)<\/a>/s).uniq
496
+ end
497
+ end
498
+ rescue Exception
499
+ return nil
500
+ end
501
+ end
502
+
503
+
504
+ # Undo the actions performed by a generator. Rewind the action
505
+ # manifest and attempt to completely erase the results of each action.
506
+ class Destroy < RewindBase
507
+ # Remove a file if it exists and is a file.
508
+ def file(relative_source, relative_destination, file_options = {})
509
+ destination = destination_path(relative_destination)
510
+ if File.exist?(destination)
511
+ logger.rm relative_destination
512
+ unless options[:pretend]
513
+ if options[:svn]
514
+ # If the file has been marked to be added
515
+ # but has not yet been checked in, revert and delete
516
+ if options[:svn][relative_destination]
517
+ system("svn revert #{destination}")
518
+ FileUtils.rm(destination)
519
+ else
520
+ # If the directory is not in the status list, it
521
+ # has no modifications so we can simply remove it
522
+ system("svn rm #{destination}")
523
+ end
524
+ elsif options[:git]
525
+ if options[:git][:new][relative_destination]
526
+ # file has been added, but not committed
527
+ system("git reset HEAD #{relative_destination}")
528
+ FileUtils.rm(destination)
529
+ elsif options[:git][:modified][relative_destination]
530
+ # file is committed and modified
531
+ system("git rm -f #{relative_destination}")
532
+ else
533
+ # If the directory is not in the status list, it
534
+ # has no modifications so we can simply remove it
535
+ system("git rm #{relative_destination}")
536
+ end
537
+ else
538
+ FileUtils.rm(destination)
539
+ end
540
+ end
541
+ else
542
+ logger.missing relative_destination
543
+ return
544
+ end
545
+ end
546
+
547
+ # Templates are deleted just like files and the actions take the
548
+ # same parameters, so simply alias the file method.
549
+ alias_method :template, :file
550
+
551
+ # Remove each directory in the given path from right to left.
552
+ # Remove each subdirectory if it exists and is a directory.
553
+ def directory(relative_path)
554
+ parts = relative_path.split('/')
555
+ until parts.empty?
556
+ partial = File.join(parts)
557
+ path = destination_path(partial)
558
+ if File.exist?(path)
559
+ if Dir[File.join(path, '*')].empty?
560
+ logger.rmdir partial
561
+ unless options[:pretend]
562
+ if options[:svn]
563
+ # If the directory has been marked to be added
564
+ # but has not yet been checked in, revert and delete
565
+ if options[:svn][relative_path]
566
+ system("svn revert #{path}")
567
+ FileUtils.rmdir(path)
568
+ else
569
+ # If the directory is not in the status list, it
570
+ # has no modifications so we can simply remove it
571
+ system("svn rm #{path}")
572
+ end
573
+ # I don't think git needs to remove directories?..
574
+ # or maybe they have special consideration...
575
+ else
576
+ FileUtils.rmdir(path)
577
+ end
578
+ end
579
+ else
580
+ logger.notempty partial
581
+ end
582
+ else
583
+ logger.missing partial
584
+ end
585
+ parts.pop
586
+ end
587
+ end
588
+
589
+ def complex_template(*args)
590
+ # nothing should be done here
591
+ end
592
+
593
+ # When deleting a migration, it knows to delete every file named "[0-9]*_#{file_name}".
594
+ def migration_template(relative_source, relative_destination, template_options = {})
595
+ migration_directory relative_destination
596
+
597
+ migration_file_name = template_options[:migration_file_name] || file_name
598
+ unless migration_exists?(migration_file_name)
599
+ stdout.puts "There is no migration named #{migration_file_name}"
600
+ return
601
+ end
602
+
603
+
604
+ existing_migrations(migration_file_name).each do |file_path|
605
+ file(relative_source, file_path, template_options)
606
+ end
607
+ end
608
+
609
+ def route_resources(*resources)
610
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
611
+ look_for = "\n map.resources #{resource_list}\n"
612
+ logger.route "map.resources #{resource_list}"
613
+ gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
614
+ end
615
+ end
616
+
617
+
618
+ # List a generator's action manifest.
619
+ class List < Command
620
+ def dependency(generator_name, args, options = {})
621
+ logger.dependency "#{generator_name}(#{args.join(', ')}, #{options.inspect})"
622
+ end
623
+
624
+ def class_collisions(*class_names)
625
+ logger.class_collisions class_names.join(', ')
626
+ end
627
+
628
+ def file(relative_source, relative_destination, options = {})
629
+ logger.file relative_destination
630
+ end
631
+
632
+ def template(relative_source, relative_destination, options = {})
633
+ logger.template relative_destination
634
+ end
635
+
636
+ def complex_template(relative_source, relative_destination, options = {})
637
+ logger.template "#{options[:insert]} inside #{relative_destination}"
638
+ end
639
+
640
+ def directory(relative_path)
641
+ logger.directory "#{destination_path(relative_path)}/"
642
+ end
643
+
644
+ def readme(*args)
645
+ logger.readme args.join(', ')
646
+ end
647
+
648
+ def migration_template(relative_source, relative_destination, options = {})
649
+ migration_directory relative_destination
650
+ logger.migration_template file_name
651
+ end
652
+
653
+ def route_resources(*resources)
654
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
655
+ logger.route "map.resources #{resource_list}"
656
+ end
657
+ end
658
+
659
+ # Update generator's action manifest.
660
+ class Update < Create
661
+ def file(relative_source, relative_destination, options = {})
662
+ # logger.file relative_destination
663
+ end
664
+
665
+ def template(relative_source, relative_destination, options = {})
666
+ # logger.template relative_destination
667
+ end
668
+
669
+ def complex_template(relative_source, relative_destination, template_options = {})
670
+
671
+ begin
672
+ dest_file = destination_path(relative_destination)
673
+ source_to_update = File.readlines(dest_file).join
674
+ rescue Errno::ENOENT
675
+ logger.missing relative_destination
676
+ return
677
+ end
678
+
679
+ logger.refreshing "#{template_options[:insert].gsub(/\.erb/,'')} inside #{relative_destination}"
680
+
681
+ begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id]))
682
+ end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id]))
683
+
684
+ # Refreshing inner part of the template with freshly rendered part.
685
+ rendered_part = render_template_part(template_options)
686
+ source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part)
687
+
688
+ File.open(dest_file, 'w') { |file| file.write(source_to_update) }
689
+ end
690
+
691
+ def directory(relative_path)
692
+ # logger.directory "#{destination_path(relative_path)}/"
693
+ end
694
+
695
+ end
696
+
697
+ end