aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib
diff options
context:
space:
mode:
authorDavid Heinemeier Hansson <david@loudthinking.com>2005-02-07 13:14:05 +0000
committerDavid Heinemeier Hansson <david@loudthinking.com>2005-02-07 13:14:05 +0000
commitdaee6fd92ac16878f6806c3382a9e74592aa9656 (patch)
treed477c6502960cb141403f8b4640dd483b487e5df /railties/lib
parent838c5a3d82367977d13ced01f9e28c22ccff32ef (diff)
downloadrails-daee6fd92ac16878f6806c3382a9e74592aa9656.tar.gz
rails-daee6fd92ac16878f6806c3382a9e74592aa9656.tar.bz2
rails-daee6fd92ac16878f6806c3382a9e74592aa9656.zip
Added new generator framework that informs about its doings on generation and enables updating and destruction of generated artifacts. See the new script/destroy and script/update for more details #487 [bitsweat]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@518 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'railties/lib')
-rw-r--r--railties/lib/rails_generator.rb234
-rw-r--r--railties/lib/rails_generator/base.rb195
-rw-r--r--railties/lib/rails_generator/commands.rb409
-rw-r--r--railties/lib/rails_generator/generators/applications/app/USAGE16
-rw-r--r--railties/lib/rails_generator/generators/applications/app/app_generator.rb118
-rw-r--r--railties/lib/rails_generator/generators/components/controller/USAGE30
-rw-r--r--railties/lib/rails_generator/generators/components/controller/controller_generator.rb34
-rw-r--r--railties/lib/rails_generator/generators/components/controller/templates/controller.rb10
-rw-r--r--railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb17
-rw-r--r--railties/lib/rails_generator/generators/components/controller/templates/helper.rb2
-rw-r--r--railties/lib/rails_generator/generators/components/controller/templates/view.rhtml2
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/USAGE19
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb26
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml3
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb13
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb29
-rw-r--r--railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml3
-rw-r--r--railties/lib/rails_generator/generators/components/model/USAGE17
-rw-r--r--railties/lib/rails_generator/generators/components/model/model_generator.rb13
-rw-r--r--railties/lib/rails_generator/generators/components/model/templates/fixtures.yml10
-rw-r--r--railties/lib/rails_generator/generators/components/model/templates/model.rb2
-rw-r--r--railties/lib/rails_generator/generators/components/model/templates/unit_test.rb14
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/USAGE32
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb161
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb55
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml5
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb80
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml11
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/style.css53
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml7
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml24
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml6
-rw-r--r--railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml8
-rw-r--r--railties/lib/rails_generator/lookup.rb204
-rw-r--r--railties/lib/rails_generator/manifest.rb53
-rw-r--r--railties/lib/rails_generator/options.rb135
-rw-r--r--railties/lib/rails_generator/scripts.rb81
-rw-r--r--railties/lib/rails_generator/scripts/destroy.rb11
-rw-r--r--railties/lib/rails_generator/scripts/generate.rb11
-rw-r--r--railties/lib/rails_generator/scripts/update.rb15
-rw-r--r--railties/lib/rails_generator/simple_logger.rb46
-rw-r--r--railties/lib/rails_generator/spec.rb44
42 files changed, 2057 insertions, 201 deletions
diff --git a/railties/lib/rails_generator.rb b/railties/lib/rails_generator.rb
index 83a404fc0a..0875a22dee 100644
--- a/railties/lib/rails_generator.rb
+++ b/railties/lib/rails_generator.rb
@@ -1,201 +1,33 @@
-require 'fileutils'
-
-module Rails
- module Generator
- class GeneratorError < StandardError; end
- class UsageError < GeneratorError; end
-
- CONTRIB_ROOT = "#{RAILS_ROOT}/script/generators"
- BUILTIN_ROOT = "#{File.dirname(__FILE__)}/../generators"
- DEFAULT_SEARCH_PATHS = [CONTRIB_ROOT, BUILTIN_ROOT]
-
- class << self
- def instance(name, args = [], search_paths = DEFAULT_SEARCH_PATHS)
- # RAILS_ROOT constant must be set.
- unless Object.const_get(:RAILS_ROOT)
- raise GeneratorError, "RAILS_ROOT must be set. Did you require 'config/environment'?"
- end
-
- # Force canonical name.
- name = Inflector.underscore(name.downcase)
-
- # Search for filesystem path to requested generator.
- unless path = find_generator_path(name, search_paths)
- raise GeneratorError, "#{name} generator not found."
- end
-
- # Check for templates directory.
- template_root = "#{path}/templates"
- unless File.directory?(template_root)
- raise GeneratorError, "missing template directory #{template_root}"
- end
-
- # Require class file according to naming convention.
- require "#{path}/#{name}_generator.rb"
-
- # Find class according to naming convention. Allow Nesting::In::Modules.
- class_name = Inflector.classify("#{name}_generator")
- unless klass = find_generator_class(name)
- raise GeneratorError, "no #{class_name} class defined in #{path}/#{name}_generator.rb"
- end
-
- # Instantiate and return generator.
- klass.new(template_root, RAILS_ROOT, search_paths, args)
- end
-
-
- def builtin_generators
- generators([BUILTIN_ROOT])
- end
-
- def contrib_generators
- generators([CONTRIB_ROOT])
- end
-
- def generators(search_paths)
- generator_paths(search_paths).keys.uniq.sort
- end
-
- # Find all generator paths.
- def generator_paths(search_paths)
- @paths ||= {}
- unless @paths[search_paths]
- paths = Hash.new { |h,k| h[k] = [] }
- search_paths.each do |path|
- Dir["#{path}/[a-z]*"].each do |dir|
- paths[File.basename(dir)] << dir if File.directory?(dir)
- end
- end
- @paths[search_paths] = paths
- end
- @paths[search_paths]
- end
-
- def find_generator_path(name, search_paths)
- generator_paths(search_paths)[name].first
- end
-
- # Find all generator classes.
- def generator_classes
- classes = Hash.new { |h,k| h[k] = [] }
- class_re = /([^:]+)Generator$/
- ObjectSpace.each_object(Class) do |object|
- if md = class_re.match(object.name) and object < Rails::Generator::Base
- classes[Inflector.underscore(md.captures.first)] << object
- end
- end
- classes
- end
-
- def find_generator_class(name)
- generator_classes[name].first
- end
- end
-
-
- # Talk about generators.
- class Base
- attr_reader :template_root, :destination_root, :args, :options,
- :class_name, :singular_name, :plural_name
-
- alias_method :file_name, :singular_name
- alias_method :table_name, :plural_name
-
- def self.generator_name
- Inflector.underscore(name.gsub('Generator', ''))
- end
-
- def initialize(template_root, destination_root, search_paths, args)
- @template_root, @destination_root = template_root, destination_root
- usage if args.empty?
- @search_paths, @original_args = search_paths, args.dup
- @class_name, @singular_name, @plural_name = inflect_names(args.shift)
- @options = extract_options!(args)
- @args = args
- end
-
- # Checks whether the class name that was assigned to this generator
- # would cause a collision with a Class, Module or other constant
- # that is already used up by Ruby or RubyOnRails.
- def collision_with_builtin?
- builtin = Object.const_get(full_class_name) rescue nil
- type = case builtin
- when Class: "Class"
- when Module: "Module"
- else "Constant"
- end
-
- if builtin then
- "Sorry, you can't have a #{self.class.generator_name} named " +
- "'#{full_class_name}' because Ruby or Rails already has a #{type} with that name.\n" +
- "Please rerun the generator with a different name."
- end
- end
-
- # Returns the complete name that the resulting Class would have.
- # Used in collision_with_builtin(). The default guess is that it is
- # the same as class_name. Override this in your generator in case
- # it is wrong.
- def full_class_name
- class_name
- end
-
- protected
- # Look up another generator with the same arguments.
- def generator(name)
- Rails::Generator.instance(name, @original_args, @search_paths)
- end
-
- # Generate a file for a Rails application using an ERuby template.
- # Looks up and evalutes a template by name and writes the result
- # to a file relative to +destination_root+. The template
- # is evaluated in the context of the optional eval_binding argument.
- #
- # The ERB template uses explicit trim mode to best control the
- # proliferation of whitespace in generated code. <%- trims leading
- # whitespace; -%> trims trailing whitespace including one newline.
- def template(template_name, destination_path, eval_binding = nil)
- # Determine full paths for source and destination files.
- template_path = find_template_path(template_name)
- destination_path = File.join(destination_root, destination_path)
-
- # Create destination directories.
- FileUtils.mkdir_p(File.dirname(destination_path))
-
- # Render template and write result.
- eval_binding ||= binding
- contents = ERB.new(File.read(template_path), nil, '-').result(eval_binding)
- File.open(destination_path, 'w') { |file| file.write(contents) }
- end
-
- def usage
- raise UsageError.new, File.read(usage_path)
- end
-
- private
- def find_template_path(template_name)
- name, path = template_name.split('/', 2)
- if path.nil?
- File.join(template_root, name)
- elsif generator_path = Rails::Generator.find_generator_path(name, @search_paths)
- File.join(generator_path, 'templates', path)
- end
- end
-
- def inflect_names(name)
- camel = Inflector.camelize(Inflector.underscore(name))
- under = Inflector.underscore(camel)
- plural = Inflector.pluralize(under)
- [camel, under, plural]
- end
-
- def extract_options!(args)
- if args.last.is_a?(Hash) then args.pop else {} end
- end
-
- def usage_path
- "#{template_root}/../USAGE"
- end
- end
- end
-end
+#--
+# Copyright (c) 2004 Jeremy Kemper
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+$:.unshift(File.dirname(__FILE__))
+
+require 'support/core_ext'
+
+require 'rails_generator/base'
+require 'rails_generator/lookup'
+require 'rails_generator/commands'
+
+Rails::Generator::Base.send(:include, Rails::Generator::Lookup)
+Rails::Generator::Base.send(:include, Rails::Generator::Commands)
diff --git a/railties/lib/rails_generator/base.rb b/railties/lib/rails_generator/base.rb
new file mode 100644
index 0000000000..065ce63966
--- /dev/null
+++ b/railties/lib/rails_generator/base.rb
@@ -0,0 +1,195 @@
+require File.dirname(__FILE__) + '/../support/class_attribute_accessors'
+require File.dirname(__FILE__) + '/../support/inflector'
+require File.dirname(__FILE__) + '/options'
+require File.dirname(__FILE__) + '/manifest'
+require File.dirname(__FILE__) + '/spec'
+
+# Rails::Generator is a code generation platform tailored for the Rails
+# web application framework. Generators are easily invoked within Rails
+# applications to add and remove components such as models and controllers.
+# New generators are easy to create and may be distributed as RubyGems or
+# tarballs for inclusion system-wide, per-user, or per-application.
+#
+# Generators may subclass other generators to provide variations that
+# require little or no new logic but replace the template files.
+# The postback generator is an example: it subclasses the scaffold
+# generator and just replaces the code templates with its own.
+#
+# Now go forth and multiply^Wgenerate.
+module Rails
+ module Generator
+ class GeneratorError < StandardError; end
+ class UsageError < GeneratorError; end
+
+
+ # The base code generator is bare-bones. It sets up the source and
+ # destination paths and tells the logger whether to keep its trap shut.
+ # You're probably looking for NamedBase, a subclass meant for generating
+ # "named" components such as models, controllers, and mailers.
+ #
+ # Generators create a manifest of the actions they perform then hand
+ # the manifest to a command which replay the actions to do the heavy
+ # lifting. Create, destroy, and list commands are included. Since a
+ # single manifest may be used by any command, creating new generators is
+ # as simple as writing some code templates and declaring what you'd like
+ # to do with them.
+ #
+ # The manifest method must be implemented by subclasses, returning a
+ # Rails::Generator::Manifest. The record method is provided as a
+ # convenience for manifest creation. Example:
+ # class EliteGenerator < Rails::Generator::Base
+ # def manifest
+ # record do |m|
+ # m.do(some)
+ # m.things(in) { here }
+ # end
+ # end
+ # end
+ class Base
+ include Options
+
+ # Declare default options for the generator. These options
+ # are inherited to subclasses.
+ default_options :collision => :ask, :quiet => false
+
+ # A logger instance available everywhere in the generator.
+ cattr_accessor :logger
+
+ # Every generator that is dynamically looked up is tagged with a
+ # Spec describing where it was found.
+ class_inheritable_accessor :spec
+
+ attr_reader :source_root, :destination_root, :args
+
+ def initialize(runtime_args, runtime_options = {})
+ @args = runtime_args
+ parse!(@args, runtime_options)
+
+ # Derive source and destination paths.
+ @source_root = options[:source] || File.join(spec.path, 'templates')
+ if options[:destination]
+ @destination_root = options[:destination]
+ elsif Object.const_defined?(:RAILS_ROOT)
+ @destination_root = Object.const_get(:RAILS_ROOT)
+ end
+
+ # Silence the logger if requested.
+ logger.quiet = options[:quiet]
+
+ # Raise usage error if help is requested.
+ usage if options[:help]
+ end
+
+ # Generators must provide a manifest. Use the record method to create
+ # a new manifest and record your generator's actions.
+ def manifest
+ raise NotImplementedError, "No manifest for '#{spec.name}' generator."
+ end
+
+ # Return the full path from the source root for the given path.
+ # Example for source_root = '/source':
+ # source_path('some/path.rb') == '/source/some/path.rb'
+ #
+ # The given path may include a colon ':' character to indicate that
+ # the file belongs to another generator. This notation allows any
+ # generator to borrow files from another. Example:
+ # source_path('model:fixture.yml') = '/model/source/path/fixture.yml'
+ def source_path(relative_source)
+ # Check whether we're referring to another generator's file.
+ name, path = relative_source.split(':', 2)
+
+ # If not, return the full path to our source file.
+ if path.nil?
+ File.join(source_root, name)
+
+ # Otherwise, ask our referral for the file.
+ else
+ # FIXME: this is broken, though almost always true. Others'
+ # source_root are not necessarily the templates dir.
+ File.join(self.class.lookup(name).path, 'templates', path)
+ end
+ end
+
+ # Return the full path from the destination root for the given path.
+ # Example for destination_root = '/dest':
+ # destination_path('some/path.rb') == '/dest/some/path.rb'
+ def destination_path(relative_destination)
+ File.join(destination_root, relative_destination)
+ end
+
+ protected
+ # Convenience method for generator subclasses to record a manifest.
+ def record
+ Rails::Generator::Manifest.new(self) { |m| yield m }
+ end
+
+ # Override with your own usage banner.
+ def banner
+ "Usage: #{$0} #{spec.name} [options]"
+ end
+
+ # Read USAGE from file in generator base path.
+ def usage_message
+ File.read(File.join(spec.path, 'USAGE')) rescue ''
+ end
+ end
+
+
+ # The base generator for named components: models, controllers, mailers,
+ # etc. The target name is taken as the first argument and inflected to
+ # singular, plural, class, file, and table forms for your convenience.
+ # The remaining arguments are aliased to actions for controller and
+ # mailer convenience.
+ #
+ # If no name is provided, the generator raises a usage error with content
+ # optionally read from the USAGE file in the generator's base path.
+ #
+ # See Rails::Generator::Base for a discussion of Manifests and Commands.
+ class NamedBase < Base
+ attr_reader :name, :class_name, :singular_name, :plural_name
+ attr_reader :class_path, :class_nesting
+ alias_method :file_name, :singular_name
+ alias_method :table_name, :plural_name
+ alias_method :actions, :args
+
+ def initialize(runtime_args, runtime_options = {})
+ super
+
+ # Name argument is required.
+ usage if runtime_args.empty?
+
+ @args = runtime_args.dup
+ base_name = @args.shift
+ assign_names!(base_name)
+ end
+
+ protected
+ # Override with your own usage banner.
+ def banner
+ "Usage: #{$0} #{spec.name} #{spec.name.camelize}Name [options]"
+ end
+
+ private
+ def assign_names!(name)
+ @name = name
+ base_name, @class_path, @class_nesting = extract_modules(@name)
+ @class_name, @singular_name, @plural_name = inflect_names(base_name)
+ end
+
+ def extract_modules(name)
+ modules = name.split('/')
+ name = modules.pop
+ path = modules.map { |m| m.underscore }
+ nesting = modules.map { |m| m.camelize }.join('::')
+ [name, path, nesting]
+ end
+
+ def inflect_names(name)
+ camel = name.camelize
+ under = camel.underscore
+ plural = under.pluralize
+ [camel, under, plural]
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb
new file mode 100644
index 0000000000..ff68afce3e
--- /dev/null
+++ b/railties/lib/rails_generator/commands.rb
@@ -0,0 +1,409 @@
+require 'delegate'
+require 'optparse'
+require 'fileutils'
+require 'erb'
+
+module Rails
+ module Generator
+ module Commands
+ # Here's a convenient way to get a handle on generator commands.
+ # Command.instance('destroy', my_generator) instantiates a Destroy
+ # delegate of my_generator ready to do your dirty work.
+ def self.instance(command, generator)
+ const_get(command.to_s.camelize).new(generator)
+ end
+
+ # Even more convenient access to commands. Include Commands in
+ # the generator Base class to get a nice #command instance method
+ # which returns a delegate for the requested command.
+ def self.append_features(base)
+ base.send(:define_method, :command) do |command|
+ Commands.instance(command, self)
+ end
+ end
+
+
+ # Generator commands delegate Rails::Generator::Base and implement
+ # a standard set of actions. Their behavior is defined by the way
+ # they respond to these actions: Create brings life; Destroy brings
+ # death; List passively observes.
+ #
+ # Commands are invoked by replaying (or rewinding) the generator's
+ # manifest of actions. See Rails::Generator::Manifest and
+ # Rails::Generator::Base#manifest method that generator subclasses
+ # are required to override.
+ #
+ # Commands allows generators to "plug in" invocation behavior, which
+ # corresponds to the GoF Strategy pattern.
+ class Base < DelegateClass(Rails::Generator::Base)
+ # Replay action manifest. RewindBase subclass rewinds manifest.
+ def invoke!
+ manifest.replay(self)
+ end
+
+ def dependency(generator_name, args, runtime_options = {})
+ logger.dependency(generator_name) do
+ self.class.new(instance(generator_name, args, full_options(runtime_options))).invoke!
+ end
+ end
+
+ # Does nothing for all commands except Create.
+ def class_collisions(*class_names)
+ end
+
+ # Does nothing for all commands except Create.
+ def readme(*args)
+ end
+
+ private
+ # Ask the user interactively whether to force collision.
+ def force_file_collision?(destination)
+ $stdout.print "overwrite #{destination}? [Ynaq] "
+ case $stdin.gets
+ when /a/i
+ $stdout.puts "forcing #{spec.name}"
+ options[:collision] = :force
+ when /q/i
+ $stdout.puts "aborting #{spec.name}"
+ raise SystemExit
+ when /n/i then :skip
+ else :force
+ end
+ rescue
+ retry
+ end
+
+ def render_template_part(template_options)
+ # Getting Sandbox to evaluate part template in it
+ part_binding = template_options[:sandbox].call.sandbox_binding
+ part_rel_path = template_options[:insert]
+ part_path = source_path(part_rel_path)
+
+ # Render inner template within Sandbox binding
+ rendered_part = ERB.new(File.readlines(part_path).join, nil, '-').result(part_binding)
+ begin_mark = template_part_mark(template_options[:begin_mark], template_options[:mark_id])
+ end_mark = template_part_mark(template_options[:end_mark], template_options[:mark_id])
+ begin_mark + rendered_part + end_mark
+ end
+
+ def template_part_mark(name, id)
+ "<!--[#{name}:#{id}]-->\n"
+ end
+ end
+
+ # Base class for commands which handle generator actions in reverse, such as Destroy.
+ class RewindBase < Base
+ # Rewind action manifest.
+ def invoke!
+ manifest.rewind(self)
+ end
+ end
+
+
+ # Create is the premier generator command. It copies files, creates
+ # directories, renders templates, and more.
+ class Create < Base
+
+ # Check whether the given class names are already taken by
+ # Ruby or Rails. In the future, expand to check other namespaces
+ # such as the rest of the user's app.
+ def class_collisions(*class_names)
+ class_names.flatten.each do |class_name|
+ # Convert to string to allow symbol arguments.
+ class_name = class_name.to_s
+
+ # Skip empty strings.
+ next if class_name.strip.empty?
+
+ # Split the class from its module nesting.
+ nesting = class_name.split('::')
+ name = nesting.pop
+
+ # Extract the last Module in the nesting.
+ last = nesting.inject(Object) { |last, nest|
+ break unless last.const_defined?(nest)
+ last.const_get(nest)
+ }
+
+ # If the last Module exists, check whether the given
+ # class exists and raise a collision if so.
+ if last and last.const_defined?(name)
+ raise_class_collision(class_name)
+ end
+ end
+ end
+
+ # Copy a file from source to destination with collision checking.
+ #
+ # The file_options hash accepts :chmod and :shebang options.
+ # :chmod sets the permissions of the destination file:
+ # file 'config/empty.log', 'log/test.log', :chmod => 0664
+ # :shebang sets the #!/usr/bin/ruby line for scripts
+ # file 'bin/generate.rb', 'script/generate', :chmod => 0755, :shebang => '/usr/bin/env ruby'
+ #
+ # Collisions are handled by checking whether the destination file
+ # exists and either skipping the file, forcing overwrite, or asking
+ # the user what to do.
+ def file(relative_source, relative_destination, file_options = {})
+ # Determine full paths for source and destination files.
+ source = source_path(relative_source)
+ destination = destination_path(relative_destination)
+
+ # Check for and resolve file collisions.
+ if File.exists?(destination)
+
+ # Make a choice whether to overwrite the file. :force and
+ # :skip already have their mind made up, but give :ask a shot.
+ choice = case options[:collision].to_sym #|| :ask
+ when :ask then force_file_collision?(relative_destination)
+ when :force then :force
+ when :skip then :skip
+ else raise "Invalid collision option: #{options[:collision].inspect}"
+ end
+
+ # Take action based on our choice. Bail out if we chose to
+ # skip the file; otherwise, log our transgression and continue.
+ case choice
+ when :force then logger.force(relative_destination)
+ when :skip then return(logger.skip(relative_destination))
+ else raise "Invalid collision choice: #{choice}.inspect"
+ end
+
+ # File doesn't exist so log its unbesmirched creation.
+ else
+ logger.create relative_destination
+ end
+
+ # If we're pretending, back off now.
+ return if options[:pretend]
+
+ # Write destination file with optional shebang. Yield for content
+ # if block given so templaters may render the source file. If a
+ # shebang is requested, replace the existing shebang or insert a
+ # new one.
+ File.open(destination, 'w') do |df|
+ File.open(source) do |sf|
+ if block_given?
+ df.write(yield(sf))
+ else
+ line = sf.gets
+ if file_options[:shebang]
+ df.puts("#!#{file_options[:shebang]}")
+ df.puts(line) if line !~ /^#!/
+ else
+ df.puts(line)
+ end
+ df.write(sf.read)
+ end
+ end
+ end
+
+ # Optionally change permissions.
+ if file_options[:chmod]
+ FileUtils.chmod(file_options[:chmod], destination)
+ end
+ end
+
+ # Generate a file for a Rails application using an ERuby template.
+ # Looks up and evalutes a template by name and writes the result.
+ #
+ # The ERB template uses explicit trim mode to best control the
+ # proliferation of whitespace in generated code. <%- trims leading
+ # whitespace; -%> trims trailing whitespace including one newline.
+ #
+ # A hash of template options may be passed as the last argument.
+ # The options accepted by the file are accepted as well as :assigns,
+ # a hash of variable bindings. Example:
+ # template 'foo', 'bar', :assigns => { :action => 'view' }
+ #
+ # Template is implemented in terms of file. It calls file with a
+ # block which takes a file handle and returns its rendered contents.
+ def template(relative_source, relative_destination, template_options = {})
+ file(relative_source, relative_destination, template_options) do |file|
+ # Evaluate any assignments in a temporary, throwaway binding.
+ vars = template_options[:assigns] || {}
+ b = binding
+ vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
+
+ # Render the source file with the temporary binding.
+ ERB.new(file.read, nil, '-').result(b)
+ end
+ end
+
+ def complex_template(relative_source, relative_destination, template_options = {})
+ options = template_options.dup
+ options[:assigns] ||= {}
+ options[:assigns]['template_for_inclusion'] = render_template_part(template_options)
+ template(relative_source, relative_destination, options)
+ end
+
+ # Create a directory including any missing parent directories.
+ # Always directories which exist.
+ def directory(relative_path)
+ path = destination_path(relative_path)
+ if File.exists?(path)
+ logger.exists relative_path
+ else
+ logger.create relative_path
+ FileUtils.mkdir_p(path) unless options[:pretend]
+ end
+ end
+
+ # Display a README.
+ def readme(*relative_sources)
+ relative_sources.flatten.each do |relative_source|
+ logger.readme relative_source
+ puts File.read(source_path(relative_source)) unless options[:pretend]
+ end
+ end
+
+ private
+ # Raise a usage error with an informative WordNet suggestion.
+ # Thanks to Florian Gross (flgr).
+ def raise_class_collision(class_name)
+ message = <<end_message
+ The name '#{class_name}' is reserved by Ruby on Rails.
+ Please choose an alternative and run this generator again.
+end_message
+ if suggest = find_synonyms(class_name)
+ message << "\n Suggestions: \n\n"
+ message << suggest.join("\n")
+ end
+ raise UsageError, message
+ end
+
+ SYNONYM_LOOKUP_URI = "http://wordnet.princeton.edu/cgi-bin/webwn2.0?stage=2&word=%s&posnumber=1&searchtypenumber=2&senses=&showglosses=1"
+
+ # Look up synonyms on WordNet. Thanks to Florian Gross (flgr).
+ def find_synonyms(word)
+ require 'open-uri'
+ require 'timeout'
+ timeout(5) do
+ open(SYNONYM_LOOKUP_URI % word) do |stream|
+ data = stream.read.gsub("&nbsp;", " ").gsub("<BR>", "")
+ data.scan(/^Sense \d+\n.+?\n\n/m)
+ end
+ end
+ rescue Exception
+ return nil
+ end
+ end
+
+
+ # Undo the actions performed by a generator. Rewind the action
+ # manifest and attempt to completely erase the results of each action.
+ class Destroy < RewindBase
+ # Remove a file if it exists and is a file.
+ def file(relative_source, relative_destination, options = {})
+ destination = destination_path(relative_destination)
+ if File.exists?(destination)
+ logger.rm relative_destination
+ FileUtils.rm(destination) unless options[:pretend]
+ else
+ logger.missing relative_destination
+ return
+ end
+ end
+
+ # Templates are deleted just like files and the actions take the
+ # same parameters, so simply alias the file method.
+ alias_method :template, :file
+
+ # Remove each directory in the given path from right to left.
+ # Remove each subdirectory if it exists and is a directory.
+ def directory(relative_path)
+ parts = relative_path.split('/')
+ until parts.empty?
+ partial = File.join(parts)
+ path = destination_path(partial)
+ if File.exists?(path)
+ if Dir[File.join(path, '*')].empty?
+ logger.rmdir partial
+ FileUtils.rmdir(path) unless options[:pretend]
+ else
+ logger.notempty partial
+ end
+ else
+ logger.missing partial
+ end
+ parts.pop
+ end
+ end
+
+ def complex_template(*args)
+ # nothing should be done here
+ end
+ end
+
+
+ # List a generator's action manifest.
+ class List < Base
+ def dependency(generator_name, args, options = {})
+ logger.dependency "#{generator_name}(#{args.join(', ')}, #{options.inspect})"
+ end
+
+ def class_collisions(*class_names)
+ logger.class_collisions class_names.join(', ')
+ end
+
+ def file(relative_source, relative_destination, options = {})
+ logger.file relative_destination
+ end
+
+ def template(relative_source, relative_destination, options = {})
+ logger.template relative_destination
+ end
+
+ def complex_template(relative_source, relative_destination, options = {})
+ logger.template "#{options[:insert]} inside #{relative_destination}"
+ end
+
+ def directory(relative_path)
+ logger.directory "#{destination_path(relative_path)}/"
+ end
+
+ def readme(*args)
+ logger.readme args.join(', ')
+ end
+ end
+
+ # Update generator's action manifest.
+ class Update < Create
+ def file(relative_source, relative_destination, options = {})
+ # logger.file relative_destination
+ end
+
+ def template(relative_source, relative_destination, options = {})
+ # logger.template relative_destination
+ end
+
+ def complex_template(relative_source, relative_destination, template_options = {})
+
+ begin
+ dest_file = destination_path(relative_destination)
+ source_to_update = File.readlines(dest_file).join
+ rescue Errno::ENOENT
+ logger.missing relative_destination
+ return
+ end
+
+ logger.refreshing "#{template_options[:insert].gsub(/\.rhtml/,'')} inside #{relative_destination}"
+
+ begin_mark = Regexp.quote(template_part_mark(template_options[:begin_mark], template_options[:mark_id]))
+ end_mark = Regexp.quote(template_part_mark(template_options[:end_mark], template_options[:mark_id]))
+
+ # Refreshing inner part of the template with freshly rendered part.
+ rendered_part = render_template_part(template_options)
+ source_to_update.gsub!(/#{begin_mark}.*?#{end_mark}/m, rendered_part)
+
+ File.open(dest_file, 'w') { |file| file.write(source_to_update) }
+ end
+
+ def directory(relative_path)
+ # logger.directory "#{destination_path(relative_path)}/"
+ end
+ end
+
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/generators/applications/app/USAGE b/railties/lib/rails_generator/generators/applications/app/USAGE
new file mode 100644
index 0000000000..3bb55113fa
--- /dev/null
+++ b/railties/lib/rails_generator/generators/applications/app/USAGE
@@ -0,0 +1,16 @@
+Description:
+ The 'rails' command creates a new Rails application with a default
+ directory structure and configuration at the path you specify.
+
+Example:
+ rails ~/Code/Ruby/weblog
+
+ This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
+ See the README in the newly created application to get going.
+
+WARNING:
+ Only specify --without-gems if you did not use gems to install Rails.
+ Your application will expect to find activerecord, actionpack, and
+ actionmailer directories in the vendor directory. A popular way to track
+ the bleeding edge of Rails development is to checkout from source control
+ directly to the vendor directory. See http://dev.rubyonrails.com
diff --git a/railties/lib/rails_generator/generators/applications/app/app_generator.rb b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
new file mode 100644
index 0000000000..0beb11b237
--- /dev/null
+++ b/railties/lib/rails_generator/generators/applications/app/app_generator.rb
@@ -0,0 +1,118 @@
+class AppGenerator < Rails::Generator::Base
+ DEFAULT_SHEBANG = File.join(Config::CONFIG['bindir'],
+ Config::CONFIG['ruby_install_name'])
+
+ default_options :gem => true, :shebang => DEFAULT_SHEBANG
+ mandatory_options :source => "#{File.dirname(__FILE__)}/../.."
+
+ def initialize(runtime_args, runtime_options = {})
+ super
+ usage if args.empty?
+ @destination_root = args.shift
+ puts "eek! #{destination_root.inspect}"
+ end
+
+ def manifest
+ script_options = { :chmod => 0755, :shebang => options[:shebang] }
+
+ record do |m|
+ # Root directory and all subdirectories.
+ m.directory ''
+ BASEDIRS.each { |path| m.directory path }
+
+ # Root
+ m.file "fresh_rakefile", "Rakefile"
+ m.file "README", "README"
+ m.file "CHANGELOG", "CHANGELOG"
+
+ # Application
+ m.template "helpers/application.rb", "app/controllers/application.rb"
+ m.template "helpers/application_helper.rb", "app/helpers/application_helper.rb"
+ m.template "helpers/test_helper.rb", "test/test_helper.rb"
+
+ # database.yml and .htaccess
+ m.template "configs/database.yml", "config/database.yml"
+ m.template "configs/apache.conf", "public/.htaccess"
+
+ # Environments
+ if options[:gem]
+ m.file "environments/shared_for_gem.rb", "config/environment.rb"
+ else
+ m.file "environments/shared.rb", "config/environment.rb"
+ end
+ m.file "environments/production.rb", "config/environments/production.rb"
+ m.file "environments/development.rb", "config/environments/development.rb"
+ m.file "environments/test.rb", "config/environments/test.rb"
+
+ # Scripts
+ %w(console destroy generate server).each do |file|
+ m.file "bin/#{file}", "script/#{file}", script_options
+ end
+ if options[:gem]
+ m.file "bin/breakpointer_for_gem", "script/breakpointer", script_options
+ else
+ m.file "bin/breakpointer", "script/breakpointer", script_options
+ end
+
+ # Dispatches
+ m.file "dispatches/dispatch.rb", "public/dispatch.rb", script_options
+ m.file "dispatches/dispatch.rb", "public/dispatch.cgi", script_options
+ m.file "dispatches/dispatch.fcgi", "public/dispatch.fcgi", script_options
+
+ # HTML files
+ %w(404 500 index).each do |file|
+ m.template "html/#{file}.html", "public/#{file}.html"
+ end
+
+ # Docs
+ m.template "doc/index.html", "public/_doc/index.html"
+ m.file "doc/README_FOR_APP", "doc/README_FOR_APP"
+
+ # Logs
+ %w(apache production development test).each { |file|
+ m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
+ }
+ end
+ end
+
+ protected
+ def banner
+ "Usage: #{$0} /path/to/your/app [options]"
+ end
+
+ def add_options!(opt)
+ opt.separator ''
+ opt.separator 'Options:'
+ opt.on("--ruby [#{DEFAULT_SHEBANG}]",
+ "Path to the Ruby binary of your choice.") { |options[:shebang]| }
+ opt.on("--without-gems",
+ "Don't use the Rails gems for your app.",
+ "WARNING: see note below.") { |options[:gem]| }
+ end
+
+
+ # Installation skeleton. Intermediate directories are automatically
+ # created so don't sweat their absence here.
+ BASEDIRS = %w(
+ app/controllers
+ app/helpers
+ app/models
+ app/views/layouts
+ config/environments
+ db
+ doc
+ lib
+ log
+ public/_doc
+ public/images
+ public/javascripts
+ public/stylesheets
+ script
+ test/fixtures
+ test/functional
+ test/mocks/development
+ test/mocks/testing
+ test/unit
+ vendor
+ )
+end
diff --git a/railties/lib/rails_generator/generators/components/controller/USAGE b/railties/lib/rails_generator/generators/components/controller/USAGE
new file mode 100644
index 0000000000..ec64209135
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/controller/USAGE
@@ -0,0 +1,30 @@
+Description:
+ The controller generator creates stubs for a new controller and its views.
+
+ The generator takes a controller name and a list of views as arguments.
+ The controller name may be given in CamelCase or under_score and should
+ not be suffixed with 'Controller'. To create a controller within a
+ module, specify the controller name as 'module/controller'.
+
+ The generator creates a controller class in app/controllers with view
+ templates in app/views/controller_name, a helper class in app/helpers,
+ and a functional test suite in test/functional.
+
+Example:
+ ./script/generate controller CreditCard open debit credit close
+
+ Credit card controller with URLs like /credit_card/debit.
+ Controller: app/controllers/credit_card_controller.rb
+ Views: app/views/credit_card/debit.rhtml [...]
+ Helper: app/helpers/credit_card_helper.rb
+ Test: test/functional/credit_card_controller_test.rb
+
+Modules Example:
+ ./script/generate controller 'admin/credit_card' suspend late_fee
+
+ Credit card admin controller with URLs /admin/credit_card/suspend.
+ Controller: app/controllers/admin/credit_card_controller.rb
+ Views: app/views/admin/credit_card/debit.rhtml [...]
+ Helper: app/helpers/admin/credit_card_helper.rb
+ Test: test/functional/admin/credit_card_controller_test.rb
+
diff --git a/railties/lib/rails_generator/generators/components/controller/controller_generator.rb b/railties/lib/rails_generator/generators/components/controller/controller_generator.rb
new file mode 100644
index 0000000000..1f7e69d124
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/controller/controller_generator.rb
@@ -0,0 +1,34 @@
+class ControllerGenerator < Rails::Generator::NamedBase
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions "#{class_name}Controller", "#{class_name}ControllerTest", "#{class_name}Helper"
+
+ # Views directory even if there are no actions.
+ m.directory File.join('app/views', class_path, file_name)
+
+ # Controller class, functional test, and helper class.
+ m.template 'controller.rb',
+ File.join('app/controllers',
+ class_path,
+ "#{file_name}_controller.rb")
+
+ m.template 'functional_test.rb',
+ File.join('test/functional',
+ class_path,
+ "#{file_name}_controller_test.rb")
+
+ m.template 'helper.rb',
+ File.join('app/helpers',
+ class_path,
+ "#{file_name}_helper.rb")
+
+ # View template for each action.
+ actions.each do |action|
+ m.template 'view.rhtml',
+ File.join('app/views', class_path, file_name, "#{action}.rhtml"),
+ :assigns => { :action => action }
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/controller/templates/controller.rb b/railties/lib/rails_generator/generators/components/controller/templates/controller.rb
new file mode 100644
index 0000000000..da71b5f057
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/controller/templates/controller.rb
@@ -0,0 +1,10 @@
+class <%= class_name %>Controller < ApplicationController
+<% if options[:scaffold] -%>
+ scaffold :<%= singular_name %>
+<% end -%>
+<% for action in actions -%>
+
+ def <%= action %>
+ end
+<% end -%>
+end
diff --git a/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb b/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb
new file mode 100644
index 0000000000..c975cb3ce3
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/controller/templates/functional_test.rb
@@ -0,0 +1,17 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require '<%= file_name %>_controller'
+
+# Re-raise errors caught by the controller.
+class <%= class_name %>Controller; def rescue_action(e) raise e end; end
+
+class <%= class_name %>ControllerTest < Test::Unit::TestCase
+ def setup
+ @controller = <%= class_name %>Controller.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/controller/templates/helper.rb b/railties/lib/rails_generator/generators/components/controller/templates/helper.rb
new file mode 100644
index 0000000000..3fe2ecdc74
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/controller/templates/helper.rb
@@ -0,0 +1,2 @@
+module <%= class_name %>Helper
+end
diff --git a/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml b/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml
new file mode 100644
index 0000000000..7e7a7d53ce
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/controller/templates/view.rhtml
@@ -0,0 +1,2 @@
+<h1><%= class_name %>#<%= action %></h1>
+<p>Find me in app/views/<%= file_name %>/<%= action %>.rhtml</p>
diff --git a/railties/lib/rails_generator/generators/components/mailer/USAGE b/railties/lib/rails_generator/generators/components/mailer/USAGE
new file mode 100644
index 0000000000..50d3de19de
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/mailer/USAGE
@@ -0,0 +1,19 @@
+Description:
+ The mailer generator creates stubs for a new mailer and its views.
+
+ The generator takes a mailer name and a list of views as arguments.
+ The mailer name may be given in CamelCase or under_score and should
+ not be suffixed with 'Mailer'.
+
+ The generator creates a mailer class in app/models with view templates
+ in app/views/mailer_name, and a test suite with fixtures in test/unit.
+
+Example:
+ ./script/generate mailer Notifications signup forgot_password invoice
+
+ This will create a NotificationsMailer:
+ Mailer: app/models/notifications.rb
+ Views: app/views/notifications/signup.rhtml [...]
+ Test: test/unit/credit_card_controller_test.rb
+ Fixtures: test/fixtures/notifications/signup [...]
+
diff --git a/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb b/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb
new file mode 100644
index 0000000000..81d4599f7f
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/mailer/mailer_generator.rb
@@ -0,0 +1,26 @@
+class MailerGenerator < Rails::Generator::NamedBase
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions class_name, "#{class_name}Test"
+
+ # Mailer class and unit test.
+ m.template "mailer.rb", "app/models/#{file_name}.rb"
+ m.template "unit_test.rb", "test/unit/#{file_name}_test.rb"
+
+ # Views and fixtures directories.
+ m.directory "app/views/#{file_name}"
+ m.directory "test/fixtures/#{table_name}"
+
+ # View template and fixture for each action.
+ actions.each do |action|
+ m.template "view.rhtml",
+ "app/views/#{file_name}/#{action}.rhtml",
+ :assigns => { :action => action }
+ m.template "fixture.rhtml",
+ "test/fixtures/#{table_name}/#{action}",
+ :assigns => { :action => action }
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml b/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml
new file mode 100644
index 0000000000..b481906829
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/mailer/templates/fixture.rhtml
@@ -0,0 +1,3 @@
+<%= class_name %>#<%= action %>
+
+Find me in app/views/<%= file_name %>/<%= action %>.rhtml
diff --git a/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb b/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb
new file mode 100644
index 0000000000..81c19fa76d
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/mailer/templates/mailer.rb
@@ -0,0 +1,13 @@
+class <%= class_name %> < ActionMailer::Base
+<% for action in actions -%>
+
+ def <%= action %>(sent_on = Time.now)
+ @subject = '<%= class_name %>#<%= action %>'
+ @body = {}
+ @recipients = ''
+ @from = ''
+ @sent_on = sent_on
+ @headers = {}
+ end
+<% end -%>
+end
diff --git a/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb
new file mode 100644
index 0000000000..70fd3afe37
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/mailer/templates/unit_test.rb
@@ -0,0 +1,29 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require '<%= file_name %>'
+
+class <%= class_name %>Test < Test::Unit::TestCase
+ FIXTURES_PATH = File.dirname(__FILE__) + '/../fixtures'
+
+ def setup
+ ActionMailer::Base.delivery_method = :test
+ ActionMailer::Base.perform_deliveries = true
+ ActionMailer::Base.deliveries = []
+
+ @expected = TMail::Mail.new
+ end
+
+<% for action in actions -%>
+ def test_<%= action %>
+ @expected.subject = '<%= class_name %>#<%= action %>'
+ @expected.body = read_fixture('<%= action %>')
+ @expected.date = Time.now
+
+ assert_equal @expected.encoded, <%= class_name %>.create_<%= action %>(@expected.date).encoded
+ end
+
+<% end -%>
+ private
+ def read_fixture(action)
+ IO.readlines("#{FIXTURES_PATH}/<%= file_name %>/#{action}")
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml b/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml
new file mode 100644
index 0000000000..b481906829
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/mailer/templates/view.rhtml
@@ -0,0 +1,3 @@
+<%= class_name %>#<%= action %>
+
+Find me in app/views/<%= file_name %>/<%= action %>.rhtml
diff --git a/railties/lib/rails_generator/generators/components/model/USAGE b/railties/lib/rails_generator/generators/components/model/USAGE
new file mode 100644
index 0000000000..f0669104fe
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model/USAGE
@@ -0,0 +1,17 @@
+Description:
+ The model generator creates stubs for a new model.
+
+ The generator takes a model name as its argument. The model name may be
+ given in CamelCase or under_score and should not be suffixed with 'Model'.
+
+ The generator creates a model class in app/models, a test suite in
+ test/unit, and test fixtures in test/fixtures/model_name.yml.
+
+Example:
+ ./script/generate model Account
+
+ This will create an Account model:
+ Model: app/models/account.rb
+ Test: test/unit/account_test.rb
+ Fixtures: test/fixtures/accounts.yml
+
diff --git a/railties/lib/rails_generator/generators/components/model/model_generator.rb b/railties/lib/rails_generator/generators/components/model/model_generator.rb
new file mode 100644
index 0000000000..32577d08a3
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model/model_generator.rb
@@ -0,0 +1,13 @@
+class ModelGenerator < Rails::Generator::NamedBase
+ def manifest
+ record do |m|
+ # Check for class naming collisions.
+ m.class_collisions class_name, "#{class_name}Test"
+
+ # Model class, unit test, and fixtures.
+ m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
+ m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb")
+ m.template 'fixtures.yml', File.join('test/fixtures', class_path, "#{table_name}.yml")
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml b/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml
new file mode 100644
index 0000000000..fc3185dc46
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model/templates/fixtures.yml
@@ -0,0 +1,10 @@
+# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
+# Set the $base_id variable in the setup method of your tests.
+# It's used to ensure that ids don't clash in some databases.
+<%% $base_id ||= 100000 %>
+
+first_<%= singular_name %>:
+ id: <%%= $base_id %>
+
+another_<%= singular_name %>:
+ id: <%%= $base_id + 1 %>
diff --git a/railties/lib/rails_generator/generators/components/model/templates/model.rb b/railties/lib/rails_generator/generators/components/model/templates/model.rb
new file mode 100644
index 0000000000..8d4c89e912
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model/templates/model.rb
@@ -0,0 +1,2 @@
+class <%= class_name %> < ActiveRecord::Base
+end
diff --git a/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb b/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb
new file mode 100644
index 0000000000..db0fbf5d33
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/model/templates/unit_test.rb
@@ -0,0 +1,14 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class <%= class_name %>Test < Test::Unit::TestCase
+ fixtures :<%= table_name %>
+
+ def setup
+ $base_id = 1000001
+ end
+
+ # Replace this with your real tests.
+ def test_truth
+ assert true
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/scaffold/USAGE b/railties/lib/rails_generator/generators/components/scaffold/USAGE
new file mode 100644
index 0000000000..d1f29ad63c
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/USAGE
@@ -0,0 +1,32 @@
+Description:
+ The scaffold generator creates a controller to interact with a model.
+ If the model does not exist, it creates the model as well. The generated
+ code is equivalent to the "scaffold :model" declaration, making it easy
+ to migrate when you wish to customize your controller and views.
+
+ The generator takes a model name, an optional controller name, and a
+ list of views as arguments. Scaffolded actions and views are created
+ automatically. Any views left over generate empty stubs.
+
+ The scaffolded actions and views are:
+ index, list, show, new, create, edit, update, destroy
+
+ If a controller name is not given, the plural form of the model name
+ will be used. The model and controller names may be given in CamelCase
+ or under_score and should not be suffixed with 'Model' or 'Controller'.
+ Both model and controller names may be prefixed with a module like a
+ file path; see the Modules Example for usage.
+
+Example:
+ ./script/generate scaffold Account Bank debit credit
+
+ This will generate an Account model and BankController with a full test
+ suite and a basic user interface. Now create the accounts table in your
+ database and browse to http://localhost/bank/ -- voila, you're on Rails!
+
+Modules Example:
+ ./script/generate controller 'admin/credit_card' suspend late_fee
+
+ This will generate a CreditCard model and CreditCardController controller
+ in the admin module.
+
diff --git a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
new file mode 100644
index 0000000000..4445995b46
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb
@@ -0,0 +1,161 @@
+class ScaffoldingSandbox
+ include ActionView::Helpers::ActiveRecordHelper
+
+ attr_accessor :form_action, :singular_name, :suffix, :model_instance
+
+ def sandbox_binding
+ binding
+ end
+end
+
+class ActionView::Helpers::InstanceTag
+ def to_input_field_tag(field_type, options={})
+ field_meth = "#{field_type}_field"
+ "<%= #{field_meth} '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+options.inspect} %>"
+ end
+
+ def to_text_area_tag(options = {})
+ "<%= text_area '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>"
+ end
+
+ def to_date_select_tag(options = {})
+ "<%= date_select '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>"
+ end
+
+ def to_datetime_select_tag(options = {})
+ "<%= datetime_select '#{@object_name}', '#{@method_name}' #{options.empty? ? '' : ', '+ options.inspect} %>"
+ end
+end
+
+class ScaffoldGenerator < Rails::Generator::NamedBase
+ attr_reader :controller_name,
+ :controller_class_path,
+ :controller_class_nesting,
+ :controller_class_name,
+ :controller_singular_name,
+ :controller_plural_name
+ alias_method :controller_file_name, :controller_singular_name
+ alias_method :controller_table_name, :controller_plural_name
+
+ def initialize(runtime_args, runtime_options = {})
+ super
+ @controller_name = args.shift || @name.pluralize
+ base_name, @controller_class_path, @controller_class_nesting = extract_modules(@controller_name)
+ @controller_class_name, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
+ end
+
+ def manifest
+ record do |m|
+ # Depend on model generator but skip if the model exists.
+ m.dependency 'model', [@name], :collision => :skip
+
+ # Check for class naming collisions.
+ m.class_collisions "#{controller_class_name}Controller", "#{controller_class_name}ControllerTest", "#{controller_class_name}Helper"
+
+ # Views directory.
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
+
+ # Controller class, functional test, helper, and views.
+ m.template 'controller.rb',
+ File.join('app/controllers',
+ controller_class_path,
+ "#{controller_file_name}_controller.rb")
+
+ m.template 'functional_test.rb',
+ File.join('test/functional',
+ controller_class_path,
+ "#{controller_file_name}_controller_test.rb")
+
+ m.template 'controller:helper.rb',
+ File.join('app/helpers',
+ controller_class_path,
+ "#{controller_file_name}_helper.rb")
+
+ # Layout and stylesheet.
+ m.template 'layout.rhtml', "app/views/layouts/#{controller_file_name}.rhtml"
+ m.template 'style.css', 'public/stylesheets/scaffold.css'
+
+ # Scaffolded views.
+ scaffold_views.each do |action|
+ m.template "view_#{action}.rhtml",
+ File.join('app/views',
+ controller_class_path, controller_file_name,
+ "#{action}.rhtml"),
+ :assigns => { :action => action }
+ end
+
+ # Scaffolded forms.
+ scaffold_forms.each do |action|
+ m.complex_template "view_#{action}.rhtml",
+ File.join('app/views',
+ controller_class_path,
+ controller_file_name,
+ "#{action}.rhtml"),
+ :assigns => { :action => action },
+ :insert => 'form.rhtml',
+ :sandbox => lambda { create_sandbox(action) },
+ :begin_mark => 'form',
+ :end_mark => 'eoform',
+ :mark_id => singular_name
+ end
+
+ # Unscaffolded views.
+ unscaffolded_actions.each do |action|
+ m.template "controller:view.rhtml",
+ File.join('app/views',
+ controller_class_path, controller_file_name,
+ "#{action}.rhtml"),
+ :assigns => { :action => action }
+ end
+ end
+ end
+
+ protected
+ # Override with your own usage banner.
+ def banner
+ "Usage: #{$0} scaffold ModelName [ControllerName] [action, ...]"
+ end
+
+ def scaffold_views
+ %w(list show)
+ end
+
+ def scaffold_forms
+ %w(new edit)
+ end
+
+ def scaffold_actions
+ scaffold_views + %w(index create update destroy)
+ end
+
+ def unscaffolded_actions
+ args - scaffold_actions
+ end
+
+ def suffix
+ "_#{singular_name}" if options[:suffix]
+ end
+
+ def create_sandbox(action)
+ sandbox = ScaffoldingSandbox.new
+ action = if action == 'edit' then 'update' else 'create' end
+ sandbox.form_action = action
+ sandbox.singular_name = singular_name
+ begin
+ sandbox.model_instance = model_instance
+ sandbox.instance_variable_set("@#{singular_name}", sandbox.model_instance)
+ rescue ActiveRecord::StatementInvalid => e
+ logger.error "Before updating scaffolding from new DB schema, try creating a table for your model (#{class_name})"
+ raise SystemExit
+ end
+ sandbox.suffix = suffix
+ sandbox
+ end
+
+ def model_instance
+ unless Object.const_defined?(class_name)
+ Object.const_set(class_name, Class.new(ActiveRecord::Base))
+ end
+ Object.const_get(class_name).new
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb b/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb
new file mode 100644
index 0000000000..c409284cb1
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb
@@ -0,0 +1,55 @@
+class <%= controller_class_name %>Controller < ApplicationController
+<% unless suffix -%>
+
+ def index
+ list
+ render_action 'list'
+ end
+<% end -%>
+
+<% for action in unscaffolded_actions -%>
+ def <%= action %><%= suffix %>
+ end
+
+<% end -%>
+ def list<%= suffix %>
+ @<%= plural_name %> = <%= class_name %>.find_all
+ end
+
+ def show<%= suffix %>
+ @<%= singular_name %> = <%= class_name %>.find(@params['id'])
+ end
+
+ def new<%= suffix %>
+ @<%= singular_name %> = <%= class_name %>.new
+ end
+
+ def create<%= suffix %>
+ @<%= singular_name %> = <%= class_name %>.new(@params['<%= singular_name %>'])
+ if @<%= singular_name %>.save
+ flash['notice'] = '<%= class_name %> was successfully created.'
+ redirect_to :action => 'list<%= suffix %>'
+ else
+ render_action 'new<%= suffix %>'
+ end
+ end
+
+ def edit<%= suffix %>
+ @<%= singular_name %> = <%= class_name %>.find(@params['id'])
+ end
+
+ def update
+ @<%= singular_name %> = <%= class_name %>.find(@params['<%= singular_name %>']['id'])
+ if @<%= singular_name %>.update_attributes(@params['<%= singular_name %>'])
+ flash['notice'] = '<%= class_name %> was successfully updated.'
+ redirect_to :action => 'show<%= suffix %>', :id => @<%= singular_name %>.id
+ else
+ render_action 'edit<%= suffix %>'
+ end
+ end
+
+ def destroy<%= suffix %>
+ <%= class_name %>.find(@params['id']).destroy
+ redirect_to :action => 'list<%= suffix %>'
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml b/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml
new file mode 100644
index 0000000000..d314c5f6b5
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/form.rhtml
@@ -0,0 +1,5 @@
+<%%= start_form_tag :action => '<%= @form_action %><%= @suffix %>' %>
+<%%= hidden_field '<%= @singular_name %>', 'id' %>
+<%= all_input_tags(@model_instance, @singular_name, {}) %>
+<input type="submit" value="<%= @form_action.to_s.capitalize %>" />
+<%%= end_form_tag %>
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb b/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb
new file mode 100644
index 0000000000..ea9c8e4e94
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/functional_test.rb
@@ -0,0 +1,80 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require '<%= controller_file_name %>_controller'
+
+# Re-raise errors caught by the controller.
+class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
+
+class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase
+ fixtures :<%= table_name %>
+
+ def setup
+ $base_id = 1000001
+ @controller = <%= controller_class_name %>Controller.new
+ @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new
+ end
+
+<% for action in unscaffolded_actions -%>
+ def test_<%= action %>
+ process :<%= action %>
+ assert_rendered_file '<%= action %>'
+ end
+
+<% end -%>
+<% unless suffix -%>
+ def test_index
+ process :index
+ assert_rendered_file 'list'
+ end
+
+<% end -%>
+ def test_list<%= suffix %>
+ process :list<%= suffix %>
+ assert_rendered_file 'list<%= suffix %>'
+ assert_template_has '<%= plural_name %>'
+ end
+
+ def test_show<%= suffix %>
+ process :show<%= suffix %>, 'id' => $base_id
+ assert_rendered_file 'show'
+ assert_template_has '<%= singular_name %>'
+ assert_valid_record '<%= singular_name %>'
+ end
+
+ def test_new<%= suffix %>
+ process :new<%= suffix %>
+ assert_rendered_file 'new<%= suffix %>'
+ assert_template_has '<%= singular_name %>'
+ end
+
+ def test_create
+ num_<%= plural_name %> = <%= class_name %>.find_all.size
+
+ process :create<%= suffix %>, '<%= singular_name %>' => { }
+ assert_redirected_to :action => 'list<%= suffix %>'
+
+ assert_equal num_<%= plural_name %> + 1, <%= class_name %>.find_all.size
+ end
+
+ def test_edit<%= suffix %>
+ process :edit<%= suffix %>, 'id' => $base_id
+ assert_rendered_file 'edit<%= suffix %>'
+ assert_template_has '<%= singular_name %>'
+ assert_valid_record '<%= singular_name %>'
+ end
+
+ def test_update<%= suffix %>
+ process :update<%= suffix %>, '<%= singular_name %>' => { 'id' => $base_id }
+ assert_redirected_to :action => 'show<%= suffix %>', :id => $base_id
+ end
+
+ def test_destroy<%= suffix %>
+ assert_not_nil <%= class_name %>.find($base_id)
+
+ process :destroy, 'id' => $base_id
+ assert_redirected_to :action => 'list<%= suffix %>'
+
+ assert_raise(ActiveRecord::RecordNotFound) {
+ <%= singular_name %> = <%= class_name %>.find($base_id)
+ }
+ end
+end
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml b/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml
new file mode 100644
index 0000000000..c4815bd0a3
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/layout.rhtml
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <title><%= controller_class_name %>: <%%= controller.action_name %></title>
+ <link href="/stylesheets/scaffold.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+
+<%%= @content_for_layout %>
+
+</body>
+</html>
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/style.css b/railties/lib/rails_generator/generators/components/scaffold/templates/style.css
new file mode 100644
index 0000000000..95a3c4668c
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/style.css
@@ -0,0 +1,53 @@
+body { background-color: #fff; color: #333; }
+
+body, p, ol, ul, td {
+ font-family: verdana, arial, helvetica, sans-serif;
+ font-size: 13px;
+ line-height: 18px;
+}
+
+pre {
+ background-color: #eee;
+ padding: 10px;
+ font-size: 11px;
+}
+
+a { color: #000; }
+a:visited { color: #666; }
+a:hover { color: #fff; background-color:#000; }
+
+.fieldWithErrors {
+ padding: 2px;
+ background-color: red;
+ display: table;
+}
+
+#ErrorExplanation {
+ width: 400px;
+ border: 2px solid #red;
+ padding: 7px;
+ padding-bottom: 12px;
+ margin-bottom: 20px;
+ background-color: #f0f0f0;
+}
+
+#ErrorExplanation h2 {
+ text-align: left;
+ font-weight: bold;
+ padding: 5px 5px 5px 15px;
+ font-size: 12px;
+ margin: -7px;
+ background-color: #c00;
+ color: #fff;
+}
+
+#ErrorExplanation p {
+ color: #333;
+ margin-bottom: 0;
+ padding: 5px;
+}
+
+#ErrorExplanation ul li {
+ font-size: 12px;
+ list-style: square;
+}
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml b/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml
new file mode 100644
index 0000000000..4ad70f537a
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/view_edit.rhtml
@@ -0,0 +1,7 @@
+<h1>Editing <%= singular_name %></h1>
+
+<%%= error_messages_for '<%= singular_name %>' %>
+<%= template_for_inclusion %>
+
+<%%= link_to 'Show', :action => 'show<%= suffix %>', :id => @<%= singular_name %>.id %> |
+<%%= link_to 'Back', :action => 'list<%= suffix %>' %>
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml b/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml
new file mode 100644
index 0000000000..068fd67472
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml
@@ -0,0 +1,24 @@
+<h1>Listing <%= plural_name %></h1>
+
+<table>
+ <tr>
+<%% for column in <%= class_name %>.content_columns %>
+ <th><%%= column.human_name %></th>
+<%% end %>
+ </tr>
+
+<%% for <%= singular_name %> in @<%= plural_name %> %>
+ <tr>
+ <%% for column in <%= class_name %>.content_columns %>
+ <td><%%=h <%= singular_name %>[column.name] %></td>
+ <%% end %>
+ <td><%%= link_to 'Show', :action => 'show<%= suffix %>', :id => <%= singular_name %>.id %></td>
+ <td><%%= link_to 'Edit', :action => 'edit<%= suffix %>', :id => <%= singular_name %>.id %></td>
+ <td><%%= link_to 'Destroy', :action => 'destroy<%= suffix %>', :id => <%= singular_name %>.id %></td>
+ </tr>
+<%% end %>
+</table>
+
+<br />
+
+<%%= link_to 'New <%= singular_name %>', :action => 'new<%= suffix %>' %>
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml b/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml
new file mode 100644
index 0000000000..fcf5a3c54b
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/view_new.rhtml
@@ -0,0 +1,6 @@
+<h1>New <%= @singular_name %></h1>
+
+<%%= error_messages_for '<%= singular_name %>' %>
+<%= template_for_inclusion %>
+
+<%%= link_to 'Back', :action => 'list<%= suffix %>' %>
diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml b/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml
new file mode 100644
index 0000000000..ba8f3616dd
--- /dev/null
+++ b/railties/lib/rails_generator/generators/components/scaffold/templates/view_show.rhtml
@@ -0,0 +1,8 @@
+<%% for column in <%= class_name %>.content_columns %>
+<p>
+ <b><%%= column.human_name %>:</b> <%%= @<%= singular_name %>.send(column.name) %>
+</p>
+<%% end %>
+
+<%%= link_to 'Edit', :action => 'edit<%= suffix %>', :id => @<%= singular_name %>.id %> |
+<%%= link_to 'Back', :action => 'list<%= suffix %>' %>
diff --git a/railties/lib/rails_generator/lookup.rb b/railties/lib/rails_generator/lookup.rb
new file mode 100644
index 0000000000..ba47fd79be
--- /dev/null
+++ b/railties/lib/rails_generator/lookup.rb
@@ -0,0 +1,204 @@
+require File.dirname(__FILE__) + '/spec'
+
+class Object
+ class << self
+ # Lookup missing generators using const_missing. This allows any
+ # generator to reference another without having to know its location:
+ # RubyGems, ~/.rails/generators, and RAILS_ROOT/script/generators all
+ # cooperate to get the job done. The greatest use of const_missing
+ # autoloading is to easily subclass existing generators. Example:
+ # class HorsebackGenerator < PostbackGenerator
+ # We don't know whether the postback generator is built in, installed
+ # as a gem, or in the user's home directory, and we shouldn't have to.
+ unless respond_to?(:pre_generator_const_missing)
+ alias_method :pre_generator_const_missing, :const_missing
+
+ def const_missing(class_id)
+ if md = /(.+)Generator$/.match(class_id.to_s)
+ name = md.captures.first.demodulize.underscore
+ Rails::Generator::Base.lookup(name).klass
+ else
+ pre_generator_const_missing(class_id)
+ end
+ end
+ end
+ end
+end
+
+# User home directory lookup adapted from RubyGems.
+def Dir.user_home
+ if ENV['HOME']
+ ENV['HOME']
+ elsif ENV['USERPROFILE']
+ ENV['USERPROFILE']
+ elsif ENV['HOMEDRIVE'] and ENV['HOMEPATH']
+ "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
+ else
+ File.expand_path '~'
+ end
+end
+
+
+module Rails
+ module Generator
+
+ # Generator lookup is managed by a list of sources which return specs
+ # describing where to find and how to create generators. This module
+ # provides class methods for manipulating the source list and looking up
+ # generator specs, and an #instance wrapper for quickly instantiating
+ # generators by name.
+ #
+ # A spec is not a generator: it's a description of where to find
+ # the generator and how to create it. A source is anything that
+ # yields generators from #each. PathSource and GemSource are provided.
+ module Lookup
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ base.use_component_sources!
+ end
+
+ # Convenience method to instantiate another generator.
+ def instance(generator_name, args, runtime_options = {})
+ self.class.instance(generator_name, args, runtime_options)
+ end
+
+ module ClassMethods
+ # The list of sources where we look, in order, for generators.
+ def sources
+ read_inheritable_attribute(:sources) or use_component_sources!
+ end
+
+ # Add a source to the end of the list.
+ def append_sources(*args)
+ sources.concat(args.flatten)
+ invalidate_cache!
+ end
+
+ # Add a source to the beginning of the list.
+ def prepend_sources(*args)
+ write_inheritable_array(:sources, args.flatten + sources)
+ invalidate_cache!
+ end
+
+ # Reset the source list.
+ def reset_sources
+ write_inheritable_attribute(:sources, [])
+ invalidate_cache!
+ end
+
+ # Use application generators (app, ?).
+ def use_application_sources!
+ reset_sources
+ sources << PathSource.new(:builtin, "#{File.dirname(__FILE__)}/generators/applications")
+ end
+
+ # Use component generators (model, controller, etc).
+ # 1. Rails application. If RAILS_ROOT is defined we know we're
+ # generating in the context of a Rails application, so search
+ # RAILS_ROOT/script/generators.
+ # 2. User home directory. Search ~/.rails/generators.
+ # 3. RubyGems. Search for gems named *_generator.
+ # 4. Builtins. Model, controller, mailer, scaffold.
+ def use_component_sources!
+ reset_sources
+ sources << PathSource.new(:app, "#{Object.const_get(:RAILS_ROOT)}/script/generators") if Object.const_defined?(:RAILS_ROOT)
+ sources << PathSource.new(:user, "#{Dir.user_home}/.rails/generators")
+ sources << GemSource.new if Object.const_defined?(:Gem)
+ sources << PathSource.new(:builtin, "#{File.dirname(__FILE__)}/generators/components")
+ end
+
+ # Lookup knows how to find generators' Specs from a list of Sources.
+ # Searches the sources, in order, for the first matching name.
+ def lookup(generator_name)
+ @found ||= {}
+ generator_name = generator_name.to_s.downcase
+ @found[generator_name] ||= cache.find { |spec|
+ spec.name == generator_name
+ } or raise GeneratorError, "Couldn't find '#{generator_name}' generator"
+ end
+
+ # Convenience method to lookup and instantiate a generator.
+ def instance(generator_name, args = [], runtime_options = {})
+ lookup(generator_name).klass.new(args, full_options(runtime_options))
+ end
+
+ private
+ # Lookup and cache every generator from the source list.
+ def cache
+ @cache ||= sources.inject([]) { |cache, source| cache + source.map }
+ end
+
+ # Clear the cache whenever the source list changes.
+ def invalidate_cache!
+ @cache = nil
+ end
+ end
+ end
+
+ # Sources enumerate (yield from #each) generator specs which describe
+ # where to find and how to create generators. Enumerable is mixed in so,
+ # for example, source.collect will retrieve every generator.
+ # Sources may be assigned a label to distinguish them.
+ class Source
+ include Enumerable
+
+ attr_reader :label
+ def initialize(label)
+ @label = label
+ end
+
+ # The each method must be implemented in subclasses.
+ # The base implementation raises an error.
+ def each
+ raise NotImplementedError
+ end
+
+ # Return a convenient sorted list of all generator names.
+ def names
+ map { |spec| spec.name }.sort
+ end
+ end
+
+
+ # PathSource looks for generators in a filesystem directory.
+ class PathSource < Source
+ attr_reader :path
+
+ def initialize(label, path)
+ super label
+ @path = path
+ end
+
+ # Yield each eligible subdirectory.
+ def each
+ Dir["#{path}/[a-z]*"].each do |dir|
+ if File.directory?(dir)
+ yield Spec.new(File.basename(dir), dir, label)
+ end
+ end
+ end
+ end
+
+
+ # GemSource hits the mines to quarry for generators. The latest versions
+ # of gems named *_generator are selected.
+ class GemSource < Source
+ def initialize
+ super :RubyGems
+ end
+
+ # Yield latest versions of generator gems.
+ def each
+ Gem::cache.search(/_generator$/).inject({}) { |latest, gem|
+ hem = latest[gem.name]
+ latest[gem.name] = gem if hem.nil? or gem.version > hem.version
+ latest
+ }.values.each { |gem|
+ yield Spec.new(gem.name.sub(/_generator$/, ''), gem.full_gem_path, label)
+ }
+ end
+ end
+
+ end
+end
diff --git a/railties/lib/rails_generator/manifest.rb b/railties/lib/rails_generator/manifest.rb
new file mode 100644
index 0000000000..702effa76f
--- /dev/null
+++ b/railties/lib/rails_generator/manifest.rb
@@ -0,0 +1,53 @@
+module Rails
+ module Generator
+
+ # Manifest captures the actions a generator performs. Instantiate
+ # a manifest with an optional target object, hammer it with actions,
+ # then replay or rewind on the object of your choice.
+ #
+ # Example:
+ # manifest = Manifest.new { |m|
+ # m.make_directory '/foo'
+ # m.create_file '/foo/bar.txt'
+ # }
+ # manifest.replay(creator)
+ # manifest.rewind(destroyer)
+ class Manifest
+ attr_reader :target
+
+ # Take a default action target. Yield self if block given.
+ def initialize(target = nil)
+ @target, @actions = target, []
+ yield self if block_given?
+ end
+
+ # Record an action.
+ def method_missing(action, *args, &block)
+ @actions << [action, args, block]
+ end
+
+ # Replay recorded actions.
+ def replay(target = nil)
+ send_actions(target || @target, @actions)
+ end
+
+ # Rewind recorded actions.
+ def rewind(target = nil)
+ send_actions(target || @target, @actions.reverse)
+ end
+
+ # Erase recorded actions.
+ def erase
+ @actions = []
+ end
+
+ private
+ def send_actions(target, actions)
+ actions.each do |method, args, block|
+ target.send(method, *args, &block)
+ end
+ end
+ end
+
+ end
+end
diff --git a/railties/lib/rails_generator/options.rb b/railties/lib/rails_generator/options.rb
new file mode 100644
index 0000000000..afe2d31625
--- /dev/null
+++ b/railties/lib/rails_generator/options.rb
@@ -0,0 +1,135 @@
+require 'optparse'
+require File.dirname(__FILE__) + '/../support/class_inheritable_attributes'
+
+module Rails
+ module Generator
+ module Options
+ def self.append_features(base)
+ super
+ base.extend(ClassMethods)
+ class << base
+ if respond_to?(:inherited)
+ alias_method :inherited_without_options, :inherited
+ end
+ alias_method :inherited, :inherited_with_options
+ end
+ end
+
+ module ClassMethods
+ def inherited_with_options(sub)
+ inherited_without_options(sub) if respond_to?(:inherited_without_options)
+ sub.extend(Rails::Generator::Options::ClassMethods)
+ end
+
+ def mandatory_options(options = nil)
+ if options
+ write_inheritable_attribute(:mandatory_options, options)
+ else
+ read_inheritable_attribute(:mandatory_options) or write_inheritable_attribute(:mandatory_options, {})
+ end
+ end
+
+ def default_options(options = nil)
+ if options
+ write_inheritable_attribute(:default_options, options)
+ else
+ read_inheritable_attribute(:default_options) or write_inheritable_attribute(:default_options, {})
+ end
+ end
+
+ # Merge together our class options. In increasing precedence:
+ # default_options (class default options)
+ # runtime_options (provided as argument)
+ # mandatory_options (class mandatory options)
+ def full_options(runtime_options = {})
+ default_options.merge(runtime_options).merge(mandatory_options)
+ end
+
+ end
+
+ # Each instance has an options hash that's populated by #parse.
+ def options
+ @options ||= {}
+ end
+ attr_writer :options
+
+ protected
+ # Convenient access to class mandatory options.
+ def mandatory_options
+ self.class.mandatory_options
+ end
+
+ # Convenient access to class default options.
+ def default_options
+ self.class.default_options
+ end
+
+ # Merge together our instance options. In increasing precedence:
+ # default_options (class default options)
+ # options (instance options)
+ # runtime_options (provided as argument)
+ # mandatory_options (class mandatory options)
+ def full_options(runtime_options = {})
+ self.class.full_options(options.merge(runtime_options))
+ end
+
+ # Parse arguments into the options hash. Classes may customize
+ # parsing behavior by overriding these methods:
+ # #banner Usage: ./script/generate [options]
+ # #add_options! Options:
+ # some options..
+ # #add_general_options! General Options:
+ # general options..
+ def parse!(args, runtime_options = {})
+ self.options = {}
+
+ @option_parser = OptionParser.new do |opt|
+ opt.banner = banner
+ add_options!(opt)
+ add_general_options!(opt)
+ opt.parse!(args)
+ end
+
+ self.options = full_options(runtime_options)
+ args
+ end
+
+ # Raise a usage error. Override usage_message to provide a blurb
+ # after the option parser summary.
+ def usage
+ raise UsageError, "#{@option_parser}\n#{usage_message}"
+ end
+
+ def usage_message
+ ''
+ end
+
+ # Override with your own usage banner.
+ def banner
+ "Usage: #{$0} [options]"
+ end
+
+ # Override with a method that adds options to the parser.
+ # Added options should use the options hash. For example,
+ # def add_options!(opt)
+ # opt.on('-v', '--verbose') { |value| options[:verbose] = value }
+ # end
+ def add_options!(opt)
+ end
+
+ # Adds general options like -h and --quiet. Usually don't override.
+ def add_general_options!(opt)
+ opt.separator ''
+ opt.separator 'General Options:'
+
+ opt.on('-p', '--pretend', 'Run but do not make any changes.') { |options[:pretend]| }
+ opt.on('-f', '--force', 'Overwrite files that already exist.') { options[:collision] = :force }
+ opt.on('-s', '--skip', 'Skip files that already exist.') { options[:collision] = :skip }
+ opt.on('-q', '--quiet', 'Keep is like a secret with /dev/null.') { |options[:quiet]| }
+ opt.on('-t', '--backtrace', 'Debugging: show backtrace on errors.') { |options[:backtrace]| }
+ opt.on('-h', '--help', 'Show this help message.') { |options[:help]| }
+ end
+
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/scripts.rb b/railties/lib/rails_generator/scripts.rb
new file mode 100644
index 0000000000..007980dcb5
--- /dev/null
+++ b/railties/lib/rails_generator/scripts.rb
@@ -0,0 +1,81 @@
+require File.dirname(__FILE__) + '/options'
+
+module Rails
+ module Generator
+ module Scripts
+
+ # Generator scripts handle command-line invocation. Each script
+ # responds to an invoke! class method which handles option parsing
+ # and generator invocation.
+ class Base
+ include Options
+ default_options :collision => :ask, :quiet => false
+
+ # Run the generator script. Takes an array of unparsed arguments
+ # and a hash of parsed arguments, takes the generator as an option
+ # or first remaining argument, and invokes the requested command.
+ def run(args = [], runtime_options = {})
+ begin
+ parse!(args, runtime_options)
+
+ # Generator name is the only required option.
+ unless options[:generator]
+ usage if args.empty?
+ options[:generator] ||= args.shift
+ end
+
+ # Look up generator instance and invoke command on it.
+ Rails::Generator::Base.instance(options[:generator], args, options).command(options[:command]).invoke!
+ rescue => e
+ puts e
+ puts " #{e.backtrace.join("\n ")}\n" if options[:backtrace]
+ raise SystemExit
+ end
+ end
+
+ protected
+ # Override with your own script usage banner.
+ def banner
+ "Usage: #{$0} [options] generator [args]"
+ end
+
+ def usage_message
+ usage = "\nInstalled Generators\n"
+ Rails::Generator::Base.sources.each do |source|
+ label = source.label.to_s.capitalize
+ names = source.names
+ usage << " #{label}: #{names.join(', ')}\n" unless names.empty?
+ end
+
+ usage << <<end_blurb
+
+More are available at http://rubyonrails.org/show/Generators
+ 1. Download, for example, login_generator.zip
+ 2. Unzip to directory #{Dir.user_home}/.rails/generators/login
+ to use the generator with all your Rails apps
+end_blurb
+
+ if Object.const_defined?(:RAILS_ROOT)
+ usage << <<end_blurb
+ or to #{File.expand_path(RAILS_ROOT)}/script/generators/login
+ to use with this app only.
+end_blurb
+ end
+
+ usage << <<end_blurb
+ 3. Run generate with no arguments for usage information
+ #{$0} login
+
+Generator gems are also available:
+ 1. gem list generator -s http://gems.rubyonrails.org
+ 2. gem install login_generator -s http://gems.rubyonrails.org
+ 3. #{$0} login
+
+end_blurb
+ return usage
+ end
+ end # Base
+
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/scripts/destroy.rb b/railties/lib/rails_generator/scripts/destroy.rb
new file mode 100644
index 0000000000..fd8469fbc7
--- /dev/null
+++ b/railties/lib/rails_generator/scripts/destroy.rb
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/../scripts'
+
+module Rails::Generator::Scripts
+ class Destroy < Base
+ mandatory_options :command => :destroy
+
+ protected
+ def add_options!(opt)
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/scripts/generate.rb b/railties/lib/rails_generator/scripts/generate.rb
new file mode 100644
index 0000000000..329d6691df
--- /dev/null
+++ b/railties/lib/rails_generator/scripts/generate.rb
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/../scripts'
+
+module Rails::Generator::Scripts
+ class Generate < Base
+ mandatory_options :command => :create
+
+ protected
+ def add_options!(opt)
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/scripts/update.rb b/railties/lib/rails_generator/scripts/update.rb
new file mode 100644
index 0000000000..ad1ae8004a
--- /dev/null
+++ b/railties/lib/rails_generator/scripts/update.rb
@@ -0,0 +1,15 @@
+require File.dirname(__FILE__) + '/../scripts'
+
+module Rails::Generator::Scripts
+ class Update < Base
+ mandatory_options :command => :update
+
+ protected
+ def add_options!(opt)
+ end
+
+ def banner
+ "Usage: #{$0} [options] scaffold"
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/simple_logger.rb b/railties/lib/rails_generator/simple_logger.rb
new file mode 100644
index 0000000000..d750f07b84
--- /dev/null
+++ b/railties/lib/rails_generator/simple_logger.rb
@@ -0,0 +1,46 @@
+module Rails
+ module Generator
+ class SimpleLogger # :nodoc:
+ attr_reader :out
+ attr_accessor :quiet
+
+ def initialize(out = $stdout)
+ @out = out
+ @quiet = false
+ @level = 0
+ end
+
+ def log(status, message, &block)
+ @out.print("%12s %s%s\n" % [status, ' ' * @level, message]) unless quiet
+ indent(&block) if block_given?
+ end
+
+ def indent(&block)
+ @level += 1
+ if block_given?
+ begin
+ block.call
+ ensure
+ outdent
+ end
+ end
+ end
+
+ def outdent
+ @level -= 1
+ if block_given?
+ begin
+ block.call
+ ensure
+ indent
+ end
+ end
+ end
+
+ private
+ def method_missing(method, *args, &block)
+ log(method.to_s, args.first, &block)
+ end
+ end
+ end
+end
diff --git a/railties/lib/rails_generator/spec.rb b/railties/lib/rails_generator/spec.rb
new file mode 100644
index 0000000000..ad609b8fe3
--- /dev/null
+++ b/railties/lib/rails_generator/spec.rb
@@ -0,0 +1,44 @@
+module Rails
+ module Generator
+ # A spec knows where a generator was found and how to instantiate it.
+ # Metadata include the generator's name, its base path, and the source
+ # which yielded it (PathSource, GemSource, etc.)
+ class Spec
+ attr_reader :name, :path, :source
+
+ def initialize(name, path, source)
+ @name, @path, @source = name, path, source
+ end
+
+ # Look up the generator class. Require its class file, find the class
+ # in ObjectSpace, tag it with this spec, and return.
+ def klass
+ unless @klass
+ require class_file
+ @klass = lookup_class
+ @klass.spec = self
+ end
+ @klass
+ end
+
+ def class_file
+ "#{path}/#{name}_generator.rb"
+ end
+
+ def class_name
+ "#{name.camelize}Generator"
+ end
+
+ private
+ # Search for the first Class descending from Rails::Generator::Base
+ # whose name matches the requested class name.
+ def lookup_class
+ ObjectSpace.each_object(Class) do |obj|
+ return obj if obj.ancestors.include?(Rails::Generator::Base) and
+ obj.name.split('::').last == class_name
+ end
+ raise NameError, "Missing #{class_name} class in #{class_file}"
+ end
+ end
+ end
+end