apimaster 0.0.1 → 0.0.2

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 (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