rubigen 1.0.0

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