rubigen 1.0.0

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