require 'digest/md5' require 'active_support/secure_random' require 'rails/version' unless defined?(Rails::VERSION) require 'rbconfig' require 'open-uri' require 'uri' module Rails 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, __FILE__, __LINE__ + 1 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 def configru template "config.ru" end def gitignore copy_file "gitignore", ".gitignore" end def app directory 'app' end def config empty_directory "config" inside "config" do template "routes.rb" template "application.rb" template "environment.rb" directory "environments" directory "initializers" directory "locales" end end def database_yml template "config/databases/#{@options[:database]}.yml", "config/database.yml" end def db directory "db" end def doc directory "doc" end def lib empty_directory "lib" empty_directory_with_gitkeep "lib/tasks" end def log empty_directory "log" inside "log" do %w( server production development test ).each do |file| create_file "#{file}.log" chmod "#{file}.log", 0666, :verbose => false end end end def public_directory directory "public", "public", :recursive => false end def images directory "public/images" end def stylesheets empty_directory_with_gitkeep "public/stylesheets" end def javascripts unless options[:skip_prototype] directory "public/javascripts" else empty_directory_with_gitkeep "public/javascripts" create_file "public/javascripts/application.js" end end def script directory "script" do |content| "#{shebang}\n" + content end chmod "script", 0755, :verbose => false end def test directory "test" end def tmp empty_directory "tmp" inside "tmp" do %w(sessions sockets cache pids).each do |dir| empty_directory(dir) end end end def vendor_plugins empty_directory_with_gitkeep "vendor/plugins" end end module Generators # We need to store the RAILS_DEV_PATH in a constant, otherwise the path # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) RESERVED_NAMES = %w[application destroy benchmarker profiler plugin runner test] class AppGenerator < Base DATABASES = %w( mysql oracle postgresql sqlite3 frontbase ibm_db ) JDBC_DATABASES = %w( jdbcmysql jdbcsqlite3 jdbcpostgresql jdbc ) DATABASES.concat(JDBC_DATABASES) attr_accessor :rails_template add_shebang_option! argument :app_path, :type => :string class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" class_option :builder, :type => :string, :aliases => "-b", :desc => "Path to an application builder (can be a filesystem path or URL)" class_option :template, :type => :string, :aliases => "-m", :desc => "Path to an application template (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 :edge, :type => :boolean, :default => false, :desc => "Setup the application with Gemfile pointing to Rails repository" class_option :skip_gemfile, :type => :boolean, :default => false, :desc => "Don't create a Gemfile" class_option :skip_active_record, :type => :boolean, :aliases => "-O", :default => false, :desc => "Skip Active Record files" class_option :skip_test_unit, :type => :boolean, :aliases => "-T", :default => false, :desc => "Skip Test::Unit files" 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 :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? @original_wd = Dir.pwd super convert_database_option_for_jruby if !options[:skip_active_record] && !DATABASES.include?(options[:database]) raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." end end def create_root self.destination_root = File.expand_path(app_path, destination_root) valid_app_const? empty_directory '.' set_default_accessors! FileUtils.cd(destination_root) unless options[:pretend] end def create_root_files build(:readme) build(:rakefile) build(:configru) build(:gitignore) unless options[:skip_git] build(:gemfile) unless options[:skip_gemfile] end def create_app_files build(:app) end def create_config_files build(:config) end def create_boot_file template "config/boot.rb" end def create_active_record_files return if options[:skip_active_record] build(:database_yml) end def create_db_files build(:db) end def create_doc_files build(:doc) end def create_lib_files build(:lib) end def create_log_files build(:log) end def create_public_files build(:public_directory) end def create_public_image_files build(:images) end def create_public_stylesheets_files build(:stylesheets) end def create_prototype_files build(:javascripts) end def create_script_files build(:script) end def create_test_files build(:test) unless options[:skip_test_unit] end def create_tmp_files build(:tmp) end def create_vendor_files build(:vendor_plugins) end def finish_template build(:leftovers) end def apply_rails_template apply rails_template if rails_template rescue Thor::Error, LoadError, Errno::ENOENT => e raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" end def bundle_if_dev_or_edge bundle_command = File.basename(Thor::Util.ruby_command).sub(/ruby/, 'bundle') run "#{bundle_command} install" if dev_or_edge? end protected def self.banner "rails new #{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(File.expand_path(path, @original_wd)) {|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) end def set_default_accessors! self.rails_template = case options[:template] when /^https?:\/\// options[:template] when String File.expand_path(options[:template], Dir.pwd) else options[:template] end end # Define file as an alias to create_file for backwards compatibility. def file(*args, &block) create_file(*args, &block) end def app_name @app_name ||= defined_app_const_base? ? defined_app_name : File.basename(destination_root) end def defined_app_name defined_app_const_base.underscore end def defined_app_const_base Rails.respond_to?(:application) && defined?(Rails::Application) && Rails.application.is_a?(Rails::Application) && Rails.application.class.name.sub(/::Application$/, "") end alias :defined_app_const_base? :defined_app_const_base def app_const_base @app_const_base ||= defined_app_const_base || app_name.gsub(/\W/, '_').squeeze('_').camelize end def app_const @app_const ||= "#{app_const_base}::Application" end def valid_app_const? if app_const =~ /^\d/ raise Error, "Invalid application name #{app_name}. Please give a name which does not start with numbers." elsif RESERVED_NAMES.include?(app_name) raise Error, "Invalid application name #{app_name}. Please give a name which does not match one of the reserved rails words." elsif Object.const_defined?(app_const_base) raise Error, "Invalid application name #{app_name}, constant #{app_const_base} is already in use. Please choose another application name." end end def app_secret ActiveSupport::SecureRandom.hex(64) end def dev_or_edge? options.dev? || options.edge? end def gem_for_database # %w( mysql oracle postgresql sqlite3 frontbase ibm_db jdbcmysql jdbcsqlite3 jdbcpostgresql) case options[:database] when "oracle" then "ruby-oci8" when "postgresql" then "pg" when "sqlite3" then "sqlite3" when "frontbase" then "ruby-frontbase" when "mysql" then "mysql2" when "jdbcmysql" then "activerecord-jdbcmysql-adapter" when "jdbcsqlite3" then "activerecord-jdbcsqlite3-adapter" when "jdbcpostgresql" then "activerecord-jdbcpostgresql-adapter" when "jdbc" then "activerecord-jdbc-adapter" else options[:database] end end def convert_database_option_for_jruby if defined?(JRUBY_VERSION) case options[:database] when "oracle" then options[:database].replace "jdbc" when "postgresql" then options[:database].replace "jdbcpostgresql" when "mysql" then options[:database].replace "jdbcmysql" when "sqlite3" then options[:database].replace "jdbcsqlite3" end end end def version_constraint_for_database_gem case options[:database] when "mysql" then "~> 0.2.11" else nil end end def mysql_socket @mysql_socket ||= [ "/tmp/mysql.sock", # default "/var/run/mysqld/mysqld.sock", # debian/gentoo "/var/tmp/mysql.sock", # freebsd "/var/lib/mysql/mysql.sock", # fedora "/opt/local/lib/mysql/mysql.sock", # fedora "/opt/local/var/run/mysqld/mysqld.sock", # mac + darwinports + mysql "/opt/local/var/run/mysql4/mysqld.sock", # mac + darwinports + mysql4 "/opt/local/var/run/mysql5/mysqld.sock", # mac + darwinports + mysql5 "/opt/lampp/var/mysql/mysql.sock" # xampp for linux ].find { |f| File.exist?(f) } unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ end def empty_directory_with_gitkeep(destination, config = {}) empty_directory(destination, config) create_file("#{destination}/.gitkeep") unless options[:skip_git] end end end end