1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Initial command structure.

This commit is contained in:
Kasper Timm Hansen 2016-05-28 22:04:13 +02:00
parent b724dbbb8b
commit 6813edc7d9
25 changed files with 1182 additions and 166 deletions

View file

@ -7,9 +7,11 @@ Rails::AppLoader.exec_app
require "rails/ruby_version_check" require "rails/ruby_version_check"
Signal.trap("INT") { puts; exit(1) } Signal.trap("INT") { puts; exit(1) }
require "rails/command"
if ARGV.first == "plugin" if ARGV.first == "plugin"
ARGV.shift ARGV.shift
require "rails/commands/plugin" Rails::Command.invoke :plugin, ARGV
else else
require "rails/commands/application" Rails::Command.invoke :application, ARGV
end end

View file

@ -0,0 +1,96 @@
require "active_support"
require "active_support/core_ext/enumerable"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/hash/transform_values"
require "thor"
module Rails
module Command
autoload :Behavior, "rails/command/behavior"
autoload :Base, "rails/command/base"
include Behavior
class << self
def hidden_commands # :nodoc:
@hidden_commands ||= []
end
def environment
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
end
# Receives a namespace, arguments and the behavior to invoke the command.
def invoke(namespace, args = [], **config)
namespace = namespace.to_s
namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace)
namespace = "version" if %w( -v --version ).include? namespace
if command = find_by_namespace(namespace)
command.perform(namespace, args, config)
else
find_by_namespace("rake").perform(namespace, args, config)
end
end
# Rails finds namespaces similar to thor, it only adds one rule:
#
# Command names must end with "_command.rb". This is required because Rails
# looks in load paths and loads the command just before it's going to be used.
#
# find_by_namespace :webrat, :rails, :integration
#
# Will search for the following commands:
#
# "rails:webrat", "webrat:integration", "webrat"
#
# Notice that "rails:commands:webrat" could be loaded as well, what
# Rails looks for is the first and last parts of the namespace.
def find_by_namespace(name) # :nodoc:
lookups = [ name, "rails:#{name}" ]
lookup(lookups)
namespaces = subclasses.index_by(&:namespace)
namespaces[(lookups & namespaces.keys).first]
end
# Returns the root of the Rails engine or app running the command.
def root
if defined?(ENGINE_ROOT)
Pathname.new(ENGINE_ROOT)
elsif defined?(APP_PATH)
Pathname.new(File.expand_path("../..", APP_PATH))
end
end
def print_commands # :nodoc:
sorted_groups.each { |b, n| print_list(b, n) }
end
def sorted_groups # :nodoc:
lookup!
groups = (subclasses - hidden_commands).group_by { |c| c.namespace.split(":").first }
groups.transform_values! { |commands| commands.flat_map(&:printing_commands).sort }
rails = groups.delete("rails")
[[ "rails", rails ]] + groups.sort.to_a
end
protected
def command_type
@command_type ||= "command"
end
def lookup_paths
@lookup_paths ||= %w( rails/commands commands )
end
def file_lookup_paths
@file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_command.rb" ]
end
end
end
end

View file

@ -0,0 +1,18 @@
module Rails
module Command
module Actions
private
# Change to the application's path if there is no config.ru file in current directory.
# This allows us to run `rails server` from other directories, but still get
# the main config.ru and properly set the tmp directory.
def set_application_directory!
Dir.chdir(File.expand_path("../../", APP_PATH)) unless File.exist?(File.expand_path("config.ru"))
end
def require_application_and_environment!
require APP_PATH
Rails.application.require_environment!
end
end
end
end

View file

@ -0,0 +1,130 @@
require "thor"
require "erb"
require "active_support/core_ext/string/filters"
require "active_support/core_ext/string/inflections"
require "rails/command/actions"
module Rails
module Command
class Base < Thor
class Error < Thor::Error # :nodoc:
end
include Actions
class << self
# Tries to get the description from a USAGE file one folder above the command
# root.
def desc(usage = nil, description = nil)
if usage
super
else
@desc ||= ERB.new(File.read(usage_path)).result(binding) if usage_path
end
end
# Convenience method to get the namespace from the class name. It's the
# same as Thor default except that the Command at the end of the class
# is removed.
def namespace(name = nil)
if name
super
else
@namespace ||= super.chomp("_command").sub(/:command:/, ":")
end
end
# Convenience method to hide this command from the available ones when
# running rails command.
def hide_command!
Rails::Command.hidden_commands << self
end
def inherited(base) #:nodoc:
super
if base.name && base.name !~ /Base$/
Rails::Command.subclasses << base
end
end
def perform(command, args, config) # :nodoc:
command = nil if Thor::HELP_MAPPINGS.include?(args.first)
dispatch(command, args.dup, nil, config)
end
def printing_commands
namespace.sub(/^rails:/, "")
end
def executable
"bin/rails #{command_name}"
end
# Use Rails' default banner.
def banner(*)
"#{executable} #{arguments.map(&:usage).join(' ')} [options]".squish!
end
# Sets the base_name taking into account the current class namespace.
#
# Rails::Command::TestCommand.base_name # => 'rails'
def base_name
@base_name ||= begin
if base = name.to_s.split("::").first
base.underscore
end
end
end
# Return command name without namespaces.
#
# Rails::Command::TestCommand.command_name # => 'test'
def command_name
@command_name ||= begin
if command = name.to_s.split("::").last
command.chomp!("Command")
command.underscore
end
end
end
# Path to lookup a USAGE description in a file.
def usage_path
if default_command_root
path = File.join(default_command_root, "USAGE")
path if File.exist?(path)
end
end
# Default file root to place extra files a command might need, placed
# one folder above the command file.
#
# For a `Rails::Command::TestCommand` placed in `rails/command/test_command.rb`
# would return `rails/test`.
def default_command_root
path = File.expand_path(File.join(base_name, command_name), __dir__)
path if File.exist?(path)
end
private
# Allow the command method to be called perform.
def create_command(meth)
if meth == "perform"
alias_method command_name, meth
else
# Prevent exception about command without usage.
# Some commands define their documentation differently.
@usage ||= ""
@desc ||= ""
super
end
end
end
end
end
end

View file

@ -0,0 +1,123 @@
require "active_support"
module Rails
module Command
module Behavior #:nodoc:
extend ActiveSupport::Concern
class_methods do
# Remove the color from output.
def no_color!
Thor::Base.shell = Thor::Shell::Basic
end
# Track all command subclasses.
def subclasses
@subclasses ||= []
end
protected
# This code is based directly on the Text gem implementation.
# Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
#
# Returns a value representing the "cost" of transforming str1 into str2
def levenshtein_distance(str1, str2)
s = str1
t = str2
n = s.length
m = t.length
return m if (0 == n)
return n if (0 == m)
d = (0..m).to_a
x = nil
# avoid duplicating an enumerable object in the loop
str2_codepoint_enumerable = str2.each_codepoint
str1.each_codepoint.with_index do |char1, i|
e = i+1
str2_codepoint_enumerable.with_index do |char2, j|
cost = (char1 == char2) ? 0 : 1
x = [
d[j+1] + 1, # insertion
e + 1, # deletion
d[j] + cost # substitution
].min
d[j] = e
e = x
end
d[m] = x
end
x
end
# Prints a list of generators.
def print_list(base, namespaces) #:nodoc:
return if namespaces.empty?
puts "#{base.camelize}:"
namespaces.each do |namespace|
puts(" #{namespace}")
end
puts
end
# Receives namespaces in an array and tries to find matching generators
# in the load path.
def lookup(namespaces) #:nodoc:
paths = namespaces_to_paths(namespaces)
paths.each do |raw_path|
lookup_paths.each do |base|
path = "#{base}/#{raw_path}_#{command_type}"
begin
require path
return
rescue LoadError => e
raise unless e.message =~ /#{Regexp.escape(path)}$/
rescue Exception => e
warn "[WARNING] Could not load #{command_type} #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
end
end
end
end
# This will try to load any command in the load path to show in help.
def lookup! #:nodoc:
$LOAD_PATH.each do |base|
Dir[File.join(base, *file_lookup_paths)].each do |path|
begin
path = path.sub("#{base}/", "")
require path
rescue Exception
# No problem
end
end
end
end
# Convert namespaces to paths by replacing ":" for "/" and adding
# an extra lookup. For example, "rails:model" should be searched
# in both: "rails/model/model_generator" and "rails/model_generator".
def namespaces_to_paths(namespaces) #:nodoc:
paths = []
namespaces.each do |namespace|
pieces = namespace.split(":")
paths << pieces.dup.push(pieces.last).join("/")
paths << pieces.join("/")
end
paths.uniq!
paths
end
end
end
end
end

View file

@ -0,0 +1,32 @@
require "active_support"
module Rails
module Command
module EnvironmentArgument #:nodoc:
extend ActiveSupport::Concern
included do
argument :environment, optional: true, banner: "environment"
end
private
def extract_environment_option_from_argument
if environment
self.options = options.merge(environment: acceptable_environment(environment))
end
end
def acceptable_environment(env = nil)
if available_environments.include? env
env
else
%w(production development test).detect { |e| e =~ /^#{env}/ } || env
end
end
def available_environments
Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") }
end
end
end
end

View file

@ -1,4 +1,4 @@
ARGV << "--help" if ARGV.empty? require "rails/command"
aliases = { aliases = {
"g" => "generate", "g" => "generate",
@ -13,6 +13,4 @@ aliases = {
command = ARGV.shift command = ARGV.shift
command = aliases[command] || command command = aliases[command] || command
require "rails/commands/commands_tasks" Rails::Command.invoke command, ARGV
Rails::CommandsTasks.new(ARGV).run_command!(command)

View file

@ -0,0 +1,29 @@
require "rails/generators"
require "rails/generators/rails/app/app_generator"
module Rails
module Generators
class AppGenerator # :nodoc:
# We want to exit on failure to be kind to other libraries
# This is only when accessing via CLI
def self.exit_on_failure?
true
end
end
end
module Command
class ApplicationCommand < Base
hide_command!
def help
perform # Punt help output to the generator.
end
def perform(*args)
Rails::Generators::AppGenerator.start \
Rails::Generators::ARGVScrubber.new(args).prepare!
end
end
end
end

View file

@ -0,0 +1,89 @@
require "irb"
require "irb/completion"
require "rails/command/environment_argument"
module Rails
class Console
module BacktraceCleaner
def filter_backtrace(bt)
if result = super
Rails.backtrace_cleaner.filter([result]).first
end
end
end
def self.start(*args)
new(*args).start
end
attr_reader :options, :app, :console
def initialize(app, options = {})
@app = app
@options = options
app.sandbox = sandbox?
app.load_console
@console = app.config.console || IRB
if @console == IRB
IRB::WorkSpace.prepend(BacktraceCleaner)
end
end
def sandbox?
options[:sandbox]
end
def environment
options[:environment]
end
alias_method :environment?, :environment
def set_environment!
Rails.env = environment
end
def start
set_environment! if environment?
if sandbox?
puts "Loading #{Rails.env} environment in sandbox (Rails #{Rails.version})"
puts "Any modifications you make will be rolled back on exit"
else
puts "Loading #{Rails.env} environment (Rails #{Rails.version})"
end
if defined?(console::ExtendCommandBundle)
console::ExtendCommandBundle.include(Rails::ConsoleMethods)
end
console.start
end
end
module Command
class ConsoleCommand < Base
include EnvironmentArgument
class_option :sandbox, aliases: "-s", type: :boolean, default: false,
desc: "Rollback database modifications on exit."
class_option :environment, aliases: "-e", type: :string, default: Rails::Command.environment,
desc: "Specifies the environment to run this console under (test/development/production)."
def perform
extract_environment_option_from_argument
# RAILS_ENV needs to be set before config/application is required.
ENV["RAILS_ENV"] = options[:environment]
ARGV.clear # Clear ARGV so IRB doesn't freak.
require_application_and_environment!
Rails::Console.start(Rails.application, options)
end
end
end
end

View file

@ -0,0 +1,160 @@
require "erb"
require "yaml"
require "rails/command/environment_argument"
module Rails
class DBConsole
attr_reader :arguments
def self.start(*args)
new(*args).start
end
def initialize(arguments = ARGV, options = {})
@arguments, @options = arguments, options
end
def start
ENV["RAILS_ENV"] = @options[:environment] || environment
case config["adapter"]
when /^(jdbc)?mysql/
args = {
"host" => "--host",
"port" => "--port",
"socket" => "--socket",
"username" => "--user",
"encoding" => "--default-character-set",
"sslca" => "--ssl-ca",
"sslcert" => "--ssl-cert",
"sslcapath" => "--ssl-capath",
"sslcipher" => "--ssl-cipher",
"sslkey" => "--ssl-key"
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
if config["password"] && @options["include_password"]
args << "--password=#{config['password']}"
elsif config["password"] && !config["password"].to_s.empty?
args << "-p"
end
args << config["database"]
find_cmd_and_exec(["mysql", "mysql5"], *args)
when /^postgres|^postgis/
ENV["PGUSER"] = config["username"] if config["username"]
ENV["PGHOST"] = config["host"] if config["host"]
ENV["PGPORT"] = config["port"].to_s if config["port"]
ENV["PGPASSWORD"] = config["password"].to_s if config["password"] && @options["include_password"]
find_cmd_and_exec("psql", config["database"])
when "sqlite3"
args = []
args << "-#{@options['mode']}" if @options["mode"]
args << "-header" if @options["header"]
args << File.expand_path(config["database"], Rails.respond_to?(:root) ? Rails.root : nil)
find_cmd_and_exec("sqlite3", *args)
when "oracle", "oracle_enhanced"
logon = ""
if config["username"]
logon = config["username"]
logon << "/#{config['password']}" if config["password"] && @options["include_password"]
logon << "@#{config['database']}" if config["database"]
end
find_cmd_and_exec("sqlplus", logon)
when "sqlserver"
args = []
args += ["-D", "#{config['database']}"] if config["database"]
args += ["-U", "#{config['username']}"] if config["username"]
args += ["-P", "#{config['password']}"] if config["password"]
if config["host"]
host_arg = "#{config['host']}"
host_arg << ":#{config['port']}" if config["port"]
args += ["-S", host_arg]
end
find_cmd_and_exec("sqsh", *args)
else
abort "Unknown command-line client for #{config['database']}."
end
end
def config
@config ||= begin
if configurations[environment].blank?
raise ActiveRecord::AdapterNotSpecified, "'#{environment}' database is not configured. Available configuration: #{configurations.inspect}"
else
configurations[environment]
end
end
end
def environment
Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment
end
protected
def configurations
require APP_PATH
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
ActiveRecord::Base.configurations
end
def find_cmd_and_exec(commands, *args)
commands = Array(commands)
dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR)
unless (ext = RbConfig::CONFIG["EXEEXT"]).empty?
commands = commands.map { |cmd| "#{cmd}#{ext}" }
end
full_path_command = nil
found = commands.detect do |cmd|
dirs_on_path.detect do |path|
full_path_command = File.join(path, cmd)
File.file?(full_path_command) && File.executable?(full_path_command)
end
end
if found
exec full_path_command, *args
else
abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
end
end
end
module Command
class DbconsoleCommand < Base
include EnvironmentArgument
class_option :include_password, aliases: "-p", type: :boolean,
desc: "Automatically provide the password from database.yml"
class_option :mode, enum: %w( html list line column ), type: :string,
desc: "Automatically put the sqlite3 database in the specified mode (html, list, line, column)."
class_option :header, type: :string
class_option :environment, aliases: "-e", type: :string, default: Rails::Command.environment,
desc: "Specifies the environment to run this console under (test/development/production)."
def perform
extract_environment_option_from_argument
Rails::DBConsole.start(args, options)
end
end
end
end

View file

@ -0,0 +1,21 @@
require "rails/generators"
module Rails
module Command
class DestroyCommand < Base
def help # :nodoc:
Rails::Generators.help self.class.command_name
end
def perform(*)
generator = args.shift
return help unless generator
require_application_and_environment!
Rails.application.load_generators
Rails::Generators.invoke generator, args, behavior: :revoke, destination_root: Rails.root
end
end
end
end

View file

@ -0,0 +1,21 @@
require "rails/generators"
module Rails
module Command
class GenerateCommand < Base
def help # :nodoc:
Rails::Generators.help self.class.command_name
end
def perform(*)
generator = args.shift
return help unless generator
require_application_and_environment!
load_generators
Rails::Generators.invoke generator, args, behavior: :invoke, destination_root: Rails::Command.root
end
end
end
end

View file

@ -0,0 +1,16 @@
Usage: bin/rails COMMAND [args] [options]
The most common rails commands are:
generate Generate new code (short-cut alias: "g")
console Start the Rails console (short-cut alias: "c")
server Start the Rails server (short-cut alias: "s")
test Run tests (short-cut alias: "t")
dbconsole Start a console for the database specified in config/database.yml
(short-cut alias: "db")
new Create a new Rails application. "rails new my_app" creates a
new application called MyApp in "./my_app"
All commands can be run with -h (or --help) for more information.
In addition to those commands, there are:

View file

@ -0,0 +1,13 @@
module Rails
module Command
class HelpCommand < Base
hide_command!
def help(*)
puts self.class.desc
Rails::Command.print_commands
end
end
end
end

View file

@ -0,0 +1,15 @@
module Rails
module Command
class NewCommand < Base
def help
Rails::Command.invoke :application, [ "--help" ]
end
def perform(*)
puts "Can't initialize a new Rails application within the directory of another, please change to a non-Rails directory first.\n"
puts "Type 'rails' for help."
exit 1
end
end
end
end

View file

@ -0,0 +1,43 @@
module Rails
module Command
class PluginCommand < Base
hide_command!
def help
run_plugin_generator %w( --help )
end
def self.banner(*) # :nodoc:
"#{executable} new [options]"
end
class_option :rc, type: :boolean, default: File.join("~", ".railsrc"),
desc: "Initialize the plugin command with previous defaults. Uses .railsrc in your home directory by default."
class_option :no_rc, desc: "Skip evaluating .railsrc."
def perform(type = nil, *plugin_args)
plugin_args << "--help" unless type == "new"
unless options.key?("no_rc") # Thor's not so indifferent access hash.
railsrc = File.expand_path(options[:rc])
if File.exist?(railsrc)
extra_args = File.read(railsrc).split(/\n+/).flat_map(&:split)
puts "Using #{extra_args.join(" ")} from #{railsrc}"
plugin_args.insert(1, *extra_args)
end
end
run_plugin_generator plugin_args
end
private
def run_plugin_generator(plugin_args)
require "rails/generators"
require "rails/generators/rails/plugin/plugin_generator"
Rails::Generators::PluginGenerator.start plugin_args
end
end
end
end

View file

@ -0,0 +1,49 @@
module Rails
module Command
class RakeCommand < Base
namespace "rake"
class << self
def printing_commands
formatted_rake_tasks.map(&:first)
end
def perform(task, *)
require_rake
ARGV.unshift(task) # Prepend the task, so Rake knows how to run it.
Rake.application.standard_exception_handling do
Rake.application.init("rails")
Rake.application.load_rakefile
Rake.application.top_level
end
end
private
def rake_tasks
require_rake
return @rake_tasks if defined?(@rake_tasks)
ActiveSupport::Deprecation.silence do
Rails::Command.require_application_and_environment!
end
Rake::TaskManager.record_task_metadata = true
Rake.application.instance_variable_set(:@name, "rails")
Rails.application.load_tasks
@rake_tasks = Rake.application.tasks.select(&:comment)
end
def formatted_rake_tasks
rake_tasks.map { |t| [ t.name_with_args, t.comment ] }
end
def require_rake
require "rake" # Defer booting Rake until we know it's needed.
end
end
end
end
end

View file

@ -0,0 +1,17 @@
Examples:
Run `puts Rails.env` after loading the app:
<%= executable %> 'puts Rails.env'
Run the Ruby file located at `path/to/filename.rb` after loading the app:
<%= executable %> path/to/filename.rb
<% if RbConfig::CONFIG['host_os'] !~ /mswin|mingw/ %>
You can also use the runner command as a shebang line for your executables:
#!/usr/bin/env <%= File.expand_path(executable) %>
Product.all.each { |p| p.price *= 2 ; p.save! }
<% end %>

View file

@ -0,0 +1,45 @@
module Rails
module Command
class RunnerCommand < Base
class_option :environment, aliases: "-e", type: :string,
default: Rails::Command.environment.dup,
desc: "The environment for the runner to operate under (test/development/production)"
def help
super
puts self.class.desc
end
def self.banner(*)
"#{super} [<'Some.ruby(code)'> | <filename.rb>]"
end
def perform(code_or_file = nil)
unless code_or_file
help
exit 1
end
ENV["RAILS_ENV"] = options[:environment]
require_application_and_environment!
Rails.application.load_runner
if File.exist?(code_or_file)
$0 = code_or_file
Kernel.load code_or_file
else
begin
eval(code_or_file, binding, __FILE__, __LINE__)
rescue SyntaxError, NameError => error
$stderr.puts "Please specify a valid ruby command or the path of a script to run."
$stderr.puts "Run '#{self.class.executable} -h' for help."
$stderr.puts
$stderr.puts error
exit 1
end
end
end
end
end
end

View file

@ -0,0 +1,159 @@
require "fileutils"
require "optparse"
require "action_dispatch"
require "rails"
require "rails/dev_caching"
module Rails
class Server < ::Rack::Server
class Options
DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
def parse!(args)
args, options = args.dup, {}
option_parser(options).parse! args
options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development"
options[:server] = args.shift
options
end
def option_parser(options) # :nodoc:
OptionParser.new do |opts|
opts.banner = "Usage: rails server [mongrel, thin etc] [options]"
opts.separator ""
opts.separator "Options:"
opts.on("-p", "--port=port", Integer,
"Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v }
opts.on("-b", "--binding=IP", String,
"Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v }
opts.on("-c", "--config=file", String,
"Uses a custom rackup configuration.") { |v| options[:config] = v }
opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true }
opts.on("-e", "--environment=name", String,
"Specifies the environment to run this server under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
opts.on("-P", "--pid=pid", String,
"Specifies the PID file.",
"Default: tmp/pids/server.pid") { |v| options[:pid] = v }
opts.on("-C", "--[no-]dev-caching",
"Specifies whether to perform caching in development.",
"true or false") { |v| options[:caching] = v }
opts.separator ""
opts.on("-h", "--help", "Shows this help message.") { puts opts; exit }
end
end
end
def initialize(*)
super
set_environment
end
# TODO: this is no longer required but we keep it for the moment to support older config.ru files.
def app
@app ||= begin
app = super
app.respond_to?(:to_app) ? app.to_app : app
end
end
def opt_parser
Options.new
end
def set_environment
ENV["RAILS_ENV"] ||= options[:environment]
end
def start
print_boot_information
trap(:INT) { exit }
create_tmp_directories
setup_dev_caching
log_to_stdout if options[:log_stdout]
super
ensure
# The '-h' option calls exit before @options is set.
# If we call 'options' with it unset, we get double help banners.
puts "Exiting" unless @options && options[:daemonize]
end
def middleware
Hash.new([])
end
def default_options
super.merge( Port: ENV.fetch("PORT", 3000).to_i,
DoNotReverseLookup: true,
environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup,
daemonize: false,
caching: nil,
pid: Options::DEFAULT_PID_PATH,
restart_cmd: restart_command)
end
private
def setup_dev_caching
if options[:environment] == "development"
Rails::DevCaching.enable_by_argument(options[:caching])
end
end
def print_boot_information
url = "#{options[:SSLEnable] ? 'https' : 'http'}://#{options[:Host]}:#{options[:Port]}"
puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}"
puts "=> Rails #{Rails.version} application starting in #{Rails.env} on #{url}"
puts "=> Run `rails server -h` for more startup options"
end
def create_tmp_directories
%w(cache pids sockets).each do |dir_to_make|
FileUtils.mkdir_p(File.join(Rails.root, "tmp", dir_to_make))
end
end
def log_to_stdout
wrapped_app # touch the app so the logger is set up
console = ActiveSupport::Logger.new(STDOUT)
console.formatter = Rails.logger.formatter
console.level = Rails.logger.level
unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT)
Rails.logger.extend(ActiveSupport::Logger.broadcast(console))
end
end
def restart_command
"bin/rails server #{ARGV.join(' ')}"
end
end
module Command
class ServerCommand < Base
def help # :nodoc:
puts Rails::Server::Options.new.option_parser(Hash.new)
end
def perform
set_application_directory!
Rails::Server.new.tap do |server|
# Require application after server sets environment to propagate
# the --environment option.
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
end
end
end
end

View file

@ -0,0 +1,18 @@
require "rails/command"
require "rails/test_unit/minitest_plugin"
module Rails
module Command
class TestCommand < Base
def help # :nodoc:
perform # Hand over help printing to minitest.
end
def perform(*)
$LOAD_PATH << Rails::Command.root.join("test")
exit Minitest.run(ARGV)
end
end
end
end

View file

@ -0,0 +1,9 @@
module Rails
module Command
class VersionCommand < Base
def perform
Rails::Command.invoke :application, [ "--version" ]
end
end
end
end

View file

@ -2,6 +2,7 @@ activesupport_path = File.expand_path("../../../../activesupport/lib", __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) $:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require "thor/group" require "thor/group"
require "rails/command"
require "active_support" require "active_support"
require "active_support/core_ext/object/blank" require "active_support/core_ext/object/blank"
@ -13,6 +14,8 @@ require "active_support/core_ext/string/inflections"
module Rails module Rails
module Generators module Generators
include Rails::Command::Behavior
autoload :Actions, "rails/generators/actions" autoload :Actions, "rails/generators/actions"
autoload :ActiveModel, "rails/generators/active_model" autoload :ActiveModel, "rails/generators/active_model"
autoload :Base, "rails/generators/base" autoload :Base, "rails/generators/base"
@ -127,67 +130,6 @@ module Rails
Thor::Base.shell = Thor::Shell::Basic Thor::Base.shell = Thor::Shell::Basic
end end
# Track all generators subclasses.
def self.subclasses
@subclasses ||= []
end
# Rails finds namespaces similar to thor, it only adds one rule:
#
# Generators names must end with "_generator.rb". This is required because Rails
# looks in load paths and loads the generator just before it's going to be used.
#
# find_by_namespace :webrat, :rails, :integration
#
# Will search for the following generators:
#
# "rails:webrat", "webrat:integration", "webrat"
#
# Notice that "rails:generators:webrat" could be loaded as well, what
# Rails looks for is the first and last parts of the namespace.
def self.find_by_namespace(name, base=nil, context=nil) #:nodoc:
lookups = []
lookups << "#{base}:#{name}" if base
lookups << "#{name}:#{context}" if context
unless base || context
unless name.to_s.include?(?:)
lookups << "#{name}:#{name}"
lookups << "rails:#{name}"
end
lookups << "#{name}"
end
lookup(lookups)
namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
lookups.each do |namespace|
klass = namespaces[namespace]
return klass if klass
end
invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
end
# Receives a namespace, arguments and the behavior to invoke the generator.
# It's used as the default entry point for generate, destroy and update
# commands.
def self.invoke(namespace, args=ARGV, config={})
names = namespace.to_s.split(":")
if klass = find_by_namespace(names.pop, names.any? && names.join(":"))
args << "--help" if args.empty? && klass.arguments.any?(&:required?)
klass.start(args, config)
else
options = sorted_groups.flat_map(&:last)
suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
msg = "Could not find generator '#{namespace}'. "
msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n"
msg << "Run `rails generate --help` for more options."
puts msg
end
end
# Returns an array of generator namespaces that are hidden. # Returns an array of generator namespaces that are hidden.
# Generator namespaces may be hidden for a variety of reasons. # Generator namespaces may be hidden for a variety of reasons.
# Some are aliased such as "rails:migration" and can be # Some are aliased such as "rails:migration" and can be
@ -260,11 +202,13 @@ module Rails
def self.sorted_groups def self.sorted_groups
namespaces = public_namespaces namespaces = public_namespaces
namespaces.sort! namespaces.sort!
groups = Hash.new { |h,k| h[k] = [] } groups = Hash.new { |h,k| h[k] = [] }
namespaces.each do |namespace| namespaces.each do |namespace|
base = namespace.split(":").first base = namespace.split(":").first
groups[base] << namespace groups[base] << namespace
end end
rails = groups.delete("rails") rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, "") } rails.map! { |n| n.sub(/^rails:/, "") }
rails.delete("app") rails.delete("app")
@ -275,61 +219,66 @@ module Rails
[[ "rails", rails ]] + groups.sort.to_a [[ "rails", rails ]] + groups.sort.to_a
end end
protected # Rails finds namespaces similar to thor, it only adds one rule:
# This code is based directly on the Text gem implementation.
# Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
# #
# Returns a value representing the "cost" of transforming str1 into str2 # Generators names must end with "_generator.rb". This is required because Rails
def self.levenshtein_distance(str1, str2) # looks in load paths and loads the generator just before it's going to be used.
s = str1 #
t = str2 # find_by_namespace :webrat, :rails, :integration
n = s.length #
m = t.length # Will search for the following generators:
#
# "rails:webrat", "webrat:integration", "webrat"
#
# Notice that "rails:generators:webrat" could be loaded as well, what
# Rails looks for is the first and last parts of the namespace.
def self.find_by_namespace(name, base = nil, context = nil) #:nodoc:
lookups = []
lookups << "#{base}:#{name}" if base
lookups << "#{name}:#{context}" if context
return m if (0 == n) unless base || context
return n if (0 == m) unless name.to_s.include?(?:)
lookups << "#{name}:#{name}"
d = (0..m).to_a lookups << "rails:#{name}"
x = nil end
lookups << "#{name}"
# avoid duplicating an enumerable object in the loop
str2_codepoint_enumerable = str2.each_codepoint
str1.each_codepoint.with_index do |char1, i|
e = i+1
str2_codepoint_enumerable.with_index do |char2, j|
cost = (char1 == char2) ? 0 : 1
x = [
d[j+1] + 1, # insertion
e + 1, # deletion
d[j] + cost # substitution
].min
d[j] = e
e = x
end end
d[m] = x lookup(lookups)
namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
lookups.each do |namespace|
klass = namespaces[namespace]
return klass if klass
end end
x invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
end end
# Prints a list of generators. # Receives a namespace, arguments and the behavior to invoke the generator.
def self.print_list(base, namespaces) #:nodoc: # It's used as the default entry point for generate, destroy and update
namespaces = namespaces.reject do |n| # commands.
hidden_namespaces.include?(n) def self.invoke(namespace, args=ARGV, config={})
names = namespace.to_s.split(":")
if klass = find_by_namespace(names.pop, names.any? && names.join(":"))
args << "--help" if args.empty? && klass.arguments.any?(&:required?)
klass.start(args, config)
else
options = sorted_groups.flat_map(&:last)
suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3)
msg = "Could not find generator '#{namespace}'. "
msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n"
msg << "Run `rails generate --help` for more options."
puts msg
end
end end
return if namespaces.empty? protected
puts "#{base.camelize}:" def self.print_list(base, namespaces)
namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) }
namespaces.each do |namespace| super
puts(" #{namespace}")
end
puts
end end
# Try fallbacks for the given base. # Try fallbacks for the given base.
@ -348,53 +297,16 @@ module Rails
nil nil
end end
# Receives namespaces in an array and tries to find matching generators def self.command_type
# in the load path. @command_type ||= "generator"
def self.lookup(namespaces) #:nodoc:
paths = namespaces_to_paths(namespaces)
paths.each do |raw_path|
["rails/generators", "generators"].each do |base|
path = "#{base}/#{raw_path}_generator"
begin
require path
return
rescue LoadError => e
raise unless e.message =~ /#{Regexp.escape(path)}$/
rescue Exception => e
warn "[WARNING] Could not load generator #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
end
end
end
end end
# This will try to load any generator in the load path to show in help. def self.lookup_paths
def self.lookup! #:nodoc: @lookup_paths ||= %w( rails/generators generators )
$LOAD_PATH.each do |base|
Dir[File.join(base, "{rails/generators,generators}", "**", "*_generator.rb")].each do |path|
begin
path = path.sub("#{base}/", "")
require path
rescue Exception
# No problem
end
end
end
end end
# Convert namespaces to paths by replacing ":" for "/" and adding def self.file_lookup_paths
# an extra lookup. For example, "rails:model" should be searched @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ]
# in both: "rails/model/model_generator" and "rails/model_generator".
def self.namespaces_to_paths(namespaces) #:nodoc:
paths = []
namespaces.each do |namespace|
pieces = namespace.split(":")
paths << pieces.dup.push(pieces.last).join("/")
paths << pieces.join("/")
end
paths.uniq!
paths
end end
end end
end end

View file

@ -1,4 +1,5 @@
require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/module/attribute_accessors"
require "active_support/core_ext/hash/keys"
require "rails/test_unit/reporter" require "rails/test_unit/reporter"
require "rails/test_unit/test_requirer" require "rails/test_unit/test_requirer"
require "shellwords" require "shellwords"

View file

@ -82,7 +82,7 @@ module ApplicationTests
def test_runner_detects_bad_script_name def test_runner_detects_bad_script_name
output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` } output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` }
assert_not $?.success? assert_not $?.success?
assert_match "undefined local variable or method `iuiqwiourowe' for main:Object", output assert_match "undefined local variable or method `iuiqwiourowe' for", output
end end
def test_environment_with_rails_env def test_environment_with_rails_env