aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails_generator/base.rb
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/rails_generator/base.rb
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/rails_generator/base.rb')
-rw-r--r--railties/lib/rails_generator/base.rb195
1 files changed, 195 insertions, 0 deletions
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