aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarlhuda <carlhuda@engineyard.com>2010-04-30 13:08:28 -0700
committerCarlhuda <carlhuda@engineyard.com>2010-04-30 13:48:06 -0700
commit785493ffed41abcca0686bf05b0a0157e0844d47 (patch)
tree07ede62e2e5634814e161671d20cf18915ef1141
parent0761d1b0f7e89d49fe8cd6ab7eed3f084bd56fbd (diff)
downloadrails-785493ffed41abcca0686bf05b0a0157e0844d47.tar.gz
rails-785493ffed41abcca0686bf05b0a0157e0844d47.tar.bz2
rails-785493ffed41abcca0686bf05b0a0157e0844d47.zip
Provide a mechanism for overriding the entire generator via a class that handles the individual elements of generating a new Rails application
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb142
-rw-r--r--railties/test/generators/app_generator_test.rb134
2 files changed, 181 insertions, 95 deletions
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 29e78a898c..380057a10e 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -2,14 +2,44 @@ require 'digest/md5'
require 'active_support/secure_random'
require 'rails/version' unless defined?(Rails::VERSION)
require 'rbconfig'
+require 'open-uri'
+require 'uri'
module Rails
- class AppBuilder
+ module ActionMethods
+ attr_reader :options
+
def initialize(generator)
@generator = generator
@options = generator.options
end
+ private
+ %w(template copy_file directory empty_directory inside
+ empty_directory_with_gitkeep create_file chmod shebang).each do |method|
+ class_eval <<-RUBY
+ def #{method}(*args, &block)
+ @generator.send(:#{method}, *args, &block)
+ end
+ RUBY
+ end
+
+ # TODO: Remove once this is fully in place
+ def method_missing(meth, *args, &block)
+ STDERR.puts "Calling #{meth} with #{args.inspect} with #{block}"
+ @generator.send(meth, *args, &block)
+ end
+ end
+
+ class AppBuilder
+ def rakefile
+ template "Rakefile"
+ end
+
+ def readme
+ copy_file "README"
+ end
+
def gemfile
template "Gemfile"
end
@@ -81,7 +111,11 @@ module Rails
end
def javascripts
- directory "public/javascripts"
+ unless options[:skip_prototype]
+ directory "public/javascripts"
+ else
+ empty_directory_with_gitkeep "public/javascripts"
+ end
end
def script
@@ -108,23 +142,6 @@ module Rails
def vendor_plugins
empty_directory_with_gitkeep "vendor/plugins"
end
-
-
- private
- %w(template copy_file directory empty_directory inside
- empty_directory_with_gitkeep create_file chmod shebang).each do |method|
- class_eval <<-RUBY
- def #{method}(*args, &block)
- @generator.send(:#{method}, *args, &block)
- end
- RUBY
- end
-
- # TODO: Remove once this is fully in place
- def method_missing(meth, *args, &block)
- STDERR.puts "Calling #{meth} with #{args.inspect} with #{block}"
- @generator.send(meth, *args, &block)
- end
end
module Generators
@@ -142,52 +159,49 @@ module Rails
attr_accessor :rails_template
add_shebang_option!
- argument :app_path, :type => :string
+ argument :app_path, :type => :string
- class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3",
- :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})"
+ class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3",
+ :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :template, :type => :string, :aliases => "-m",
- :desc => "Path to an application template (can be a filesystem path or URL)."
+ class_option :builder, :type => :string, :aliases => "-b",
+ :desc => "Path to an application builder (can be a filesystem path or URL)"
- class_option :dev, :type => :boolean, :default => false,
- :desc => "Setup the application with Gemfile pointing to your Rails checkout"
+ class_option :template, :type => :string, :aliases => "-m",
+ :desc => "Path to an application template (can be a filesystem path or URL)."
- class_option :edge, :type => :boolean, :default => false,
- :desc => "Setup the application with Gemfile pointing to Rails repository"
+ class_option :dev, :type => :boolean, :default => false,
+ :desc => "Setup the application with Gemfile pointing to your Rails checkout"
- class_option :skip_gemfile, :type => :boolean, :default => false,
- :desc => "Don't create a Gemfile"
+ class_option :edge, :type => :boolean, :default => false,
+ :desc => "Setup the application with Gemfile pointing to Rails repository"
- class_option :skip_activerecord, :type => :boolean, :aliases => "-O", :default => false,
- :desc => "Skip ActiveRecord files"
+ class_option :skip_gemfile, :type => :boolean, :default => false,
+ :desc => "Don't create a Gemfile"
- class_option :skip_testunit, :type => :boolean, :aliases => "-T", :default => false,
- :desc => "Skip TestUnit files"
+ class_option :skip_activerecord, :type => :boolean, :aliases => "-O", :default => false,
+ :desc => "Skip ActiveRecord files"
- class_option :skip_prototype, :type => :boolean, :aliases => "-J", :default => false,
- :desc => "Skip Prototype files"
+ class_option :skip_testunit, :type => :boolean, :aliases => "-T", :default => false,
+ :desc => "Skip TestUnit files"
- class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false,
- :desc => "Skip Git ignores and keeps"
+ class_option :skip_prototype, :type => :boolean, :aliases => "-J", :default => false,
+ :desc => "Skip Prototype files"
+
+ class_option :skip_git, :type => :boolean, :aliases => "-G", :default => false,
+ :desc => "Skip Git ignores and keeps"
# Add bin/rails options
- class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
- :desc => "Show Rails version number and quit"
+ class_option :version, :type => :boolean, :aliases => "-v", :group => :rails,
+ :desc => "Show Rails version number and quit"
- class_option :help, :type => :boolean, :aliases => "-h", :group => :rails,
- :desc => "Show this help message and quit"
+ class_option :help, :type => :boolean, :aliases => "-h", :group => :rails,
+ :desc => "Show this help message and quit"
def initialize(*args)
raise Error, "Options should be given after the application name. For details run: rails --help" if args[0].blank?
super
- if builder = options[:builder]
- apply builder
- end
-
- @builder = AppBuilder.new(self)
-
if !options[:skip_activerecord] && !DATABASES.include?(options[:database])
raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."
end
@@ -203,8 +217,8 @@ module Rails
end
def create_root_files
- copy_file "README"
- template "Rakefile"
+ build(:readme)
+ build(:rakefile)
build(:configru)
build(:gitignore) unless options[:skip_git]
build(:gemfile) unless options[:skip_gemfile]
@@ -253,15 +267,10 @@ module Rails
def create_public_stylesheets_files
build(:stylesheets)
- empty_directory_with_gitkeep "public/stylesheets"
end
def create_prototype_files
- unless options[:skip_prototype]
- build(:javascripts)
- else
- empty_directory_with_gitkeep "public/javascripts"
- end
+ build(:javascripts)
end
def create_script_files
@@ -301,8 +310,27 @@ module Rails
"rails #{self.arguments.map(&:usage).join(' ')} [options]"
end
+ def builder
+ @builder ||= begin
+ if path = options[:builder]
+ if URI(path).is_a?(URI::HTTP)
+ contents = open(path, "Accept" => "application/x-thor-template") {|io| io.read }
+ else
+ contents = open(path) {|io| io.read }
+ end
+
+ prok = eval("proc { #{contents} }", TOPLEVEL_BINDING, path, 1)
+ instance_eval(&prok)
+ end
+
+ builder_class = defined?(::AppBuilder) ? ::AppBuilder : Rails::AppBuilder
+ builder_class.send(:include, ActionMethods)
+ builder_class.new(self)
+ end
+ end
+
def build(meth, *args)
- @builder.send(meth, *args) if @builder.respond_to?(meth)
+ builder.send(meth, *args) if builder.respond_to?(meth)
end
def set_default_accessors!
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index 1e5aa156ce..1a93867013 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -2,6 +2,40 @@ require 'abstract_unit'
require 'generators/generators_test_helper'
require 'rails/generators/rails/app/app_generator'
+DEFAULT_APP_FILES = %w(
+ .gitignore
+ Gemfile
+ Rakefile
+ config.ru
+ app/controllers
+ app/helpers
+ app/models
+ app/views/layouts
+ config/environments
+ config/initializers
+ config/locales
+ db
+ doc
+ lib
+ lib/tasks
+ log
+ public/images
+ public/javascripts
+ public/stylesheets
+ script/rails
+ test/fixtures
+ test/functional
+ test/integration
+ test/performance
+ test/unit
+ vendor
+ vendor/plugins
+ tmp/sessions
+ tmp/sockets
+ tmp/cache
+ tmp/pids
+)
+
class AppGeneratorTest < Rails::Generators::TestCase
include GeneratorsTestHelper
arguments [destination_root]
@@ -20,39 +54,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_application_skeleton_is_created
run_generator
- %w(
- .gitignore
- Gemfile
- Rakefile
- config.ru
- app/controllers
- app/helpers
- app/models
- app/views/layouts
- config/environments
- config/initializers
- config/locales
- db
- doc
- lib
- lib/tasks
- log
- public/images
- public/javascripts
- public/stylesheets
- script/rails
- test/fixtures
- test/functional
- test/integration
- test/performance
- test/unit
- vendor
- vendor/plugins
- tmp/sessions
- tmp/sockets
- tmp/cache
- tmp/pids
- ).each{ |path| assert_file path }
+ DEFAULT_APP_FILES.each{ |path| assert_file path }
end
def test_application_controller_and_layout_files
@@ -66,7 +68,7 @@ class AppGeneratorTest < Rails::Generators::TestCase
content = capture(:stderr){ run_generator(["--skip-activerecord", destination_root]) }
assert_equal "Options should be given after the application name. For details run: rails --help\n", content
end
-
+
def test_name_collision_raises_an_error
content = capture(:stderr){ run_generator [File.join(destination_root, "generate")] }
assert_equal "Invalid application name generate. Please give a name which does not match one of the reserved rails words.\n", content
@@ -197,10 +199,66 @@ class AppGeneratorTest < Rails::Generators::TestCase
assert_file 'Gemfile', /^gem\s+["']rails["'],\s+:git\s+=>\s+["']#{Regexp.escape("git://github.com/rails/rails.git")}["']$/
end
- protected
+protected
- def action(*args, &block)
- silence(:stdout){ generator.send(*args, &block) }
- end
+ def action(*args, &block)
+ silence(:stdout){ generator.send(*args, &block) }
+ end
end
+
+class CustomAppGeneratorTest < Rails::Generators::TestCase
+ include GeneratorsTestHelper
+ tests Rails::Generators::AppGenerator
+
+ arguments [destination_root]
+
+ def setup
+ super
+ Rails::Generators::AppGenerator.instance_variable_set('@desc', nil)
+ @bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle')
+ end
+
+ def teardown
+ super
+ Rails::Generators::AppGenerator.instance_variable_set('@desc', nil)
+ Object.class_eval { remove_const :AppBuilder if const_defined?(:AppBuilder) }
+ end
+
+ def test_builder_option_with_empty_app_builder
+ FileUtils.cd(Rails.root)
+ run_generator([destination_root, "-b", "#{Rails.root}/lib/empty_builder.rb"])
+ DEFAULT_APP_FILES.each{ |path| assert_no_file path }
+ end
+
+ def test_builder_option_with_simple_app_builder
+ FileUtils.cd(Rails.root)
+ run_generator([destination_root, "-b", "#{Rails.root}/lib/simple_builder.rb"])
+ (DEFAULT_APP_FILES - ['config.ru']).each{ |path| assert_no_file path }
+ assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }]
+ end
+
+ def test_builder_option_with_tweak_app_builder
+ FileUtils.cd(Rails.root)
+ run_generator([destination_root, "-b", "#{Rails.root}/lib/tweak_builder.rb"])
+ DEFAULT_APP_FILES.each{ |path| assert_file path }
+ assert_file "config.ru", %[run proc { |env| [200, { "Content-Type" => "text/html" }, ["Hello World"]] }]
+ end
+
+ def test_builder_option_with_http
+ path = "http://gist.github.com/103208.txt"
+ template = "class AppBuilder; end"
+ template.instance_eval "def read; self; end" # Make the string respond to read
+
+ generator([destination_root], :builder => path).expects(:open).with(path, 'Accept' => 'application/x-thor-template').returns(template)
+ capture(:stdout) { generator.invoke }
+
+ DEFAULT_APP_FILES.each{ |path| assert_no_file path }
+ end
+
+protected
+
+ def action(*args, &block)
+ silence(:stdout){ generator.send(*args, &block) }
+ end
+end \ No newline at end of file