aboutsummaryrefslogblamecommitdiffstats
path: root/railties/lib/rails_generator/base.rb
blob: 54b4b32fd38e60ba4dd59d37f0fed026e5b85429 (plain) (tree)
1
2

                                                  




































































                                                                            

                                          





































































































                                                                              





                                                                                              

           



                                                                   
                                 
                                                                           














                                                             
require 'active_support/class_attribute_accessors'
require 'active_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 defined? ::RAILS_ROOT
          @destination_root = ::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_without_nesting, @singular_name, @plural_name = inflect_names(base_name)
          if @class_nesting.empty?
            @class_name = @class_name_without_nesting
          else
            @class_name = "#{@class_nesting}::#{@class_name_without_nesting}"
          end
        end

        # Extract modules from filesystem-style or ruby-style path:
        #   good/fun/stuff
        #   Good::Fun::Stuff
        # produce the same results.
        def extract_modules(name)
          modules = name.include?('/') ? name.split('/') : 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