提交 c994a893 编写于 作者: K Kasper Timm Hansen 提交者: GitHub

Merge pull request #26414 from rails/rails-commands

Initial Rails Commands Infrastructure
......@@ -7,9 +7,11 @@
require "rails/ruby_version_check"
Signal.trap("INT") { puts; exit(1) }
require "rails/command"
if ARGV.first == "plugin"
ARGV.shift
require "rails/commands/plugin"
Rails::Command.invoke :plugin, ARGV
else
require "rails/commands/application"
Rails::Command.invoke :application, ARGV
end
require "active_support"
require "active_support/dependencies/autoload"
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
extend ActiveSupport::Autoload
autoload :Behavior
autoload :Base
include Behavior
class << self
def hidden_commands # :nodoc:
@hidden_commands ||= []
end
def environment # :nodoc:
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
module Rails
module Command
module Actions
# 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
if defined?(ENGINE_PATH)
def require_application_and_environment!
require ENGINE_PATH
end
def load_tasks
Rake.application.init("rails")
Rake.application.load_rakefile
end
def load_generators
engine = ::Rails::Engine.find(ENGINE_ROOT)
Rails::Generators.namespace = engine.railtie_namespace
engine.load_generators
end
else
def require_application_and_environment!
require APP_PATH
Rails.application.require_environment!
end
def load_tasks
Rails.application.load_tasks
end
def load_generators
Rails.application.load_generators
end
end
end
end
end
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
# Returns true when the app is a Rails engine.
def engine?
defined?(ENGINE_ROOT)
end
# 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("../commands", 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
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
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))
elsif !options[:environment]
self.options = options.merge(environment: Rails::Command.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
ARGV << "--help" if ARGV.empty?
require "rails/command"
aliases = {
"g" => "generate",
......@@ -13,6 +13,4 @@
command = ARGV.shift
command = aliases[command] || command
require "rails/commands/commands_tasks"
Rails::CommandsTasks.new(ARGV).run_command!(command)
Rails::Command.invoke command, ARGV
......@@ -11,7 +11,19 @@ def self.exit_on_failure?
end
end
end
end
args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!
Rails::Generators::AppGenerator.start args
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
require "rails/commands/rake_proxy"
require "rails/commands/common_commands_tasks"
require "active_support/core_ext/string/strip"
module Rails
# This is a class which takes in a rails command and initiates the appropriate
# initiation sequence.
#
# Warning: This class mutates ARGV because some commands require manipulating
# it before they are run.
class CommandsTasks # :nodoc:
include Rails::RakeProxy
include Rails::CommonCommandsTasks
attr_reader :argv
ADDITIONAL_COMMANDS = [
[ "destroy", 'Undo code generated with "generate" (short-cut alias: "d")' ],
[ "plugin new", "Generates skeleton for developing a Rails plugin" ],
[ "runner",
'Run a piece of code in the application environment (short-cut alias: "r")' ]
]
def initialize(argv)
@argv = argv
end
def plugin
require_command!("plugin")
end
def console
require_command!("console")
options = Rails::Console.parse_arguments(argv)
# RAILS_ENV needs to be set before config/application is required
ENV["RAILS_ENV"] = options[:environment] if options[:environment]
# shift ARGV so IRB doesn't freak
shift_argv!
require_application_and_environment!
Rails::Console.start(Rails.application, options)
end
def server
set_application_directory!
require_command!("server")
Rails::Server.new.tap do |server|
# We need to require application after the server sets environment,
# otherwise the --environment option given to the server won't propagate.
require APP_PATH
Dir.chdir(Rails.application.root)
server.start
end
end
def dbconsole
require_command!("dbconsole")
Rails::DBConsole.start
end
def runner
require_command!("runner")
end
def new
if %w(-h --help).include?(argv.first)
require_command!("application")
else
exit_with_initialization_warning!
end
end
private
def exit_with_initialization_warning!
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
def shift_argv!
argv.shift if argv.first && argv.first[0] != "-"
end
# 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 commands
ADDITIONAL_COMMANDS + formatted_rake_tasks
end
def command_whitelist
%w(plugin generate destroy console server dbconsole runner new version help test)
end
def help_message
<<-EOT.strip_heredoc
Usage: rails COMMAND [ARGS]
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:
EOT
end
def require_application_and_environment!
require APP_PATH
Rails.application.require_environment!
end
def load_tasks
Rails.application.load_tasks
end
def load_generators
Rails.application.load_generators
end
end
end
module Rails
module CommonCommandsTasks # :nodoc:
def run_command!(command)
command = parse_command(command)
if command_whitelist.include?(command)
send(command)
else
run_rake_task(command)
end
end
def generate
generate_or_destroy(:generate)
end
def destroy
generate_or_destroy(:destroy)
end
def test
require_command!("test")
end
def version
argv.unshift "--version"
require_command!("application")
end
def help
write_help_message
write_commands(commands)
end
private
def generate_or_destroy(command)
require "rails/generators"
require_application_and_environment!
load_generators
require_command!(command)
end
def require_command!(command)
require "rails/commands/#{command}"
end
def write_help_message
puts help_message
end
def write_commands(commands)
width = commands.map { |name, _| name.size }.max || 10
commands.each { |command| printf(" %-#{width}s %s\n", *command) }
end
def parse_command(command)
case command
when "--version", "-v"
"version"
when "--help", "-h"
"help"
else
command
end
end
end
end
require "optparse"
require "irb"
require "irb/completion"
require "rails/commands/console_helper"
require "rails/command/environment_argument"
module Rails
class Console
include ConsoleHelper
module BacktraceCleaner
def filter_backtrace(bt)
if result = super
......@@ -15,26 +13,13 @@ def filter_backtrace(bt)
end
end
class << self
def parse_arguments(arguments)
options = {}
OptionParser.new do |opt|
opt.banner = "Usage: rails console [environment] [options]"
opt.on("-s", "--sandbox", "Rollback database modifications on exit.") { |v| options[:sandbox] = v }
opt.on("-e", "--environment=name", String,
"Specifies the environment to run this console under (test/development/production).",
"Default: development") { |v| options[:environment] = v.strip }
opt.parse!(arguments)
end
set_options_env(arguments, options)
end
def self.start(*args)
new(*args).start
end
attr_reader :options, :app, :console
def initialize(app, options={})
def initialize(app, options = {})
@app = app
@options = options
......@@ -53,7 +38,7 @@ def sandbox?
end
def environment
options[:environment] ||= super
options[:environment]
end
alias_method :environment?, :environment
......@@ -77,4 +62,28 @@ def start
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,
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
require "active_support/concern"
module Rails
module ConsoleHelper # :nodoc:
extend ActiveSupport::Concern
module ClassMethods
def start(*args)
new(*args).start
end
private
def set_options_env(arguments, options)
if arguments.first && arguments.first[0] != "-"
env = arguments.first
if available_environments.include? env
options[:environment] = env
else
options[:environment] = %w(production development test).detect { |e| e =~ /^#{env}/ } || env
end
end
options
end
def available_environments
Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") }
end
end
def environment
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
end
end
end
require "erb"
require "yaml"
require "optparse"
require "rails/commands/console_helper"
require "rails/command/environment_argument"
module Rails
class DBConsole
include ConsoleHelper
attr_reader :arguments
class << self
def parse_arguments(arguments)
options = {}
OptionParser.new do |opt|
opt.banner = "Usage: rails dbconsole [environment] [options]"
opt.on("-p", "--include-password", "Automatically provide the password from database.yml") do |v|
options["include_password"] = true
end
opt.on("--mode [MODE]", ["html", "list", "line", "column"],
"Automatically put the sqlite3 database in the specified mode (html, list, line, column).") do |mode|
options["mode"] = mode
end
opt.on("--header") do |h|
options["header"] = h
end
opt.on("-h", "--help", "Show this help message.") do
puts opt
exit
end
opt.on("-e", "--environment=name", String,
"Specifies the environment to run this console under (test/development/production).",
"Default: development"
) { |v| options[:environment] = v.strip }
opt.parse!(arguments)
abort opt.to_s unless (0..1).include?(arguments.size)
end
set_options_env(arguments, options)
end
def self.start(*args)
new(*args).start
end
def initialize(arguments = ARGV)
@arguments = arguments
def initialize(options = {})
@options = options
end
def start
options = self.class.parse_arguments(arguments)
ENV["RAILS_ENV"] = options[:environment] || environment
ENV["RAILS_ENV"] = @options[:environment] || environment
case config["adapter"]
when /^(jdbc)?mysql/
......@@ -69,7 +31,7 @@ def start
"sslkey" => "--ssl-key"
}.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
if config["password"] && options["include_password"]
if config["password"] && @options["include_password"]
args << "--password=#{config['password']}"
elsif config["password"] && !config["password"].to_s.empty?
args << "-p"
......@@ -83,14 +45,14 @@ def start
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"]
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 << "-#{@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)
......@@ -100,7 +62,7 @@ def start
if config["username"]
logon = config["username"]
logon << "/#{config['password']}" if config["password"] && options["include_password"]
logon << "/#{config['password']}" if config["password"] && @options["include_password"]
logon << "@#{config['database']}" if config["database"]
end
......@@ -137,7 +99,7 @@ def config
end
def environment
Rails.respond_to?(:env) ? Rails.env : super
Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment
end
protected
......@@ -170,4 +132,27 @@ def find_cmd_and_exec(commands, *args)
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,
desc: "Specifies the environment to run this console under (test/development/production)."
def perform
extract_environment_option_from_argument
Rails::DBConsole.start(options)
end
end
end
end
require "rails/generators"
#if no argument/-h/--help is passed to rails destroy command, then
#it generates the help associated.
if [nil, "-h", "--help"].include?(ARGV.first)
Rails::Generators.help "destroy"
exit
end
name = ARGV.shift
Rails::Generators.invoke name, ARGV, behavior: :revoke, destination_root: Rails.root
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
require "rails/generators"
#if no argument/-h/--help is passed to rails generate command, then
#it generates the help associated.
if [nil, "-h", "--help"].include?(ARGV.first)
Rails::Generators.help "generate"
exit
end
name = ARGV.shift
root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root
Rails::Generators.invoke name, ARGV, behavior: :invoke, destination_root: root
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
Usage: bin/rails COMMAND [args] [options]
<% if engine? %>
The common Rails commands available for engines are:
generate Generate new code (short-cut alias: "g")
destroy Undo code generated with "generate" (short-cut alias: "d")
test Run tests (short-cut alias: "t")
All commands can be run with -h for more information.
If you want to run any commands that need to be run in context
of the application, like `bin/rails server` or `bin/rails console`,
you should do it from the application's directory (typically test/dummy).
<% else %>
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.
<% end %>
In addition to those commands, there are:
module Rails
module Command
class HelpCommand < Base
hide_command!
def help(*)
puts self.class.desc
Rails::Command.print_commands
end
end
end
end
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
if ARGV.first != "new"
ARGV[0] = "--help"
else
ARGV.shift
unless ARGV.delete("--no-rc")
customrc = ARGV.index { |x| x.include?("--rc=") }
railsrc = if customrc
File.expand_path(ARGV.delete_at(customrc).gsub(/--rc=/, ""))
else
File.join(File.expand_path("~"), ".railsrc")
end
if File.exist?(railsrc)
extra_args_string = File.read(railsrc)
extra_args = extra_args_string.split(/\n+/).flat_map(&:split)
puts "Using #{extra_args.join(" ")} from #{railsrc}"
ARGV.insert(1, *extra_args)
end
end
end
require "rails/generators"
require "rails/generators/rails/plugin/plugin_generator"
Rails::Generators::PluginGenerator.start
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
module Rails
module Command
class RakeCommand < Base
extend Rails::Command::Actions
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
require_application_and_environment!
end
Rake::TaskManager.record_task_metadata = true
Rake.application.instance_variable_set(:@name, "rails")
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
require "active_support"
module Rails
module RakeProxy #:nodoc:
private
def run_rake_task(command)
require_rake
ARGV.unshift(command) # Prepend the command, 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
def rake_tasks
require_rake
return @rake_tasks if defined?(@rake_tasks)
ActiveSupport::Deprecation.silence do
require_application_and_environment!
end
Rake::TaskManager.record_task_metadata = true
Rake.application.instance_variable_set(:@name, "rails")
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
require "optparse"
options = { environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup }
code_or_file = nil
command = "bin/rails runner"
if ARGV.first.nil?
ARGV.push "-h"
end
ARGV.clone.options do |opts|
opts.banner = "Usage: rails runner [options] [<'Some.ruby(code)'> | <filename.rb>]"
opts.separator ""
opts.on("-e", "--environment=name", String,
"Specifies the environment for the runner to operate under (test/development/production).",
"Default: development") { |v| options[:environment] = v }
opts.separator ""
opts.on("-h", "--help",
"Show this help message.") { $stdout.puts opts; exit }
opts.separator ""
opts.separator "Examples: "
opts.separator " rails runner 'puts Rails.env'"
opts.separator " This runs the code `puts Rails.env` after loading the app"
opts.separator ""
opts.separator " rails runner path/to/filename.rb"
opts.separator " This runs the Ruby file located at `path/to/filename.rb` after loading the app"
if RbConfig::CONFIG["host_os"] !~ /mswin|mingw/
opts.separator ""
opts.separator "You can also use runner as a shebang line for your executables:"
opts.separator " -------------------------------------------------------------"
opts.separator " #!/usr/bin/env #{File.expand_path(command)}"
opts.separator ""
opts.separator " Product.all.each { |p| p.price *= 2 ; p.save! }"
opts.separator " -------------------------------------------------------------"
end
opts.order! { |o| code_or_file ||= o } rescue retry
end
ARGV.delete(code_or_file)
ENV["RAILS_ENV"] = options[:environment]
require APP_PATH
Rails.application.require_environment!
Rails.application.load_runner
if code_or_file.nil?
$stderr.puts "Run '#{command} -h' for help."
exit 1
elsif 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 => e
$stderr.puts "Please specify a valid ruby command or the path of a script to run."
$stderr.puts "Run '#{command} -h' for help."
$stderr.puts
$stderr.puts e
exit 1
end
end
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 %>
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
......@@ -19,33 +19,35 @@ def parse!(args)
options
end
private
def option_parser(options)
OptionParser.new do |opts|
opts.banner = "Usage: rails server [puma, thin etc] [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
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(*)
......@@ -100,7 +102,6 @@ def default_options
end
private
def setup_dev_caching
if options[:environment] == "development"
Rails::DevCaching.enable_by_argument(options[:caching])
......@@ -136,4 +137,24 @@ 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
require "rails/test_unit/minitest_plugin"
if defined?(ENGINE_ROOT)
$LOAD_PATH << File.expand_path("test", ENGINE_ROOT)
else
$LOAD_PATH << File.expand_path("../../test", APP_PATH)
end
Minitest.run_via[:rails] = true
require "active_support/testing/autorun"
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")
Minitest.run_via[:rails] = true
require "active_support/testing/autorun"
end
end
end
end
module Rails
module Command
class VersionCommand < Base
def perform
Rails::Command.invoke :application, [ "--version" ]
end
end
end
end
require "rails/engine/commands_tasks"
ARGV << "--help" if ARGV.empty?
require "rails/command"
aliases = {
"g" => "generate",
......@@ -11,4 +9,4 @@
command = ARGV.shift
command = aliases[command] || command
Rails::Engine::CommandsTasks.new(ARGV).run_command!(command)
Rails::Command.invoke command, ARGV
require "rails/commands/rake_proxy"
require "rails/commands/common_commands_tasks"
require "active_support/core_ext/string/strip"
module Rails
class Engine
class CommandsTasks # :nodoc:
include Rails::RakeProxy
include Rails::CommonCommandsTasks
attr_reader :argv
def initialize(argv)
@argv = argv
end
private
def commands
formatted_rake_tasks
end
def command_whitelist
%w(generate destroy version help test)
end
def help_message
<<-EOT.strip_heredoc
Usage: rails COMMAND [ARGS]
The common Rails commands available for engines are:
generate Generate new code (short-cut alias: "g")
destroy Undo code generated with "generate" (short-cut alias: "d")
test Run tests (short-cut alias: "t")
All commands can be run with -h for more information.
If you want to run any commands that need to be run in context
of the application, like `rails server` or `rails console`,
you should do it from application's directory (typically test/dummy).
In addition to those commands, there are:
EOT
end
def require_application_and_environment!
require ENGINE_PATH
end
def load_tasks
Rake.application.init("rails")
Rake.application.load_rakefile
end
def load_generators
engine = ::Rails::Engine.find(ENGINE_ROOT)
Rails::Generators.namespace = engine.railtie_namespace
engine.load_generators
end
end
end
end
......@@ -2,6 +2,7 @@
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require "thor/group"
require "rails/command"
require "active_support"
require "active_support/core_ext/object/blank"
......@@ -13,6 +14,8 @@
module Rails
module Generators
include Rails::Command::Behavior
autoload :Actions, "rails/generators/actions"
autoload :ActiveModel, "rails/generators/active_model"
autoload :Base, "rails/generators/base"
......@@ -127,67 +130,6 @@ def self.no_color!
Thor::Base.shell = Thor::Shell::Basic
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.
# Generator namespaces may be hidden for a variety of reasons.
# Some are aliased such as "rails:migration" and can be
......@@ -260,11 +202,13 @@ def self.print_generators
def self.sorted_groups
namespaces = public_namespaces
namespaces.sort!
groups = Hash.new { |h,k| h[k] = [] }
namespaces.each do |namespace|
base = namespace.split(":").first
groups[base] << namespace
end
rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, "") }
rails.delete("app")
......@@ -272,64 +216,69 @@ def self.sorted_groups
hidden_namespaces.each { |n| groups.delete(n.to_s) }
[["rails", rails]] + groups.sort.to_a
[[ "rails", rails ]] + groups.sort.to_a
end
protected
# 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
# 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 self.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
unless base || context
unless name.to_s.include?(?:)
lookups << "#{name}:#{name}"
lookups << "rails:#{name}"
end
x
lookups << "#{name}"
end
# Prints a list of generators.
def self.print_list(base, namespaces) #:nodoc:
namespaces = namespaces.reject do |n|
hidden_namespaces.include?(n)
end
lookup(lookups)
namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }]
lookups.each do |namespace|
return if namespaces.empty?
puts "#{base.camelize}:"
klass = namespaces[namespace]
return klass if klass
end
namespaces.each do |namespace|
puts(" #{namespace}")
end
invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name)
end
puts
# 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
protected
def self.print_list(base, namespaces)
namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) }
super
end
# Try fallbacks for the given base.
......@@ -348,53 +297,16 @@ def self.invoke_fallbacks_for(name, base) #:nodoc:
nil
end
# Receives namespaces in an array and tries to find matching generators
# in the load path.
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
def self.command_type
@command_type ||= "generator"
end
# This will try to load any generator in the load path to show in help.
def self.lookup! #:nodoc:
$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
def self.lookup_paths
@lookup_paths ||= %w( rails/generators generators )
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 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
def self.file_lookup_paths
@file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ]
end
end
end
......@@ -82,7 +82,7 @@ def test_runner_detects_syntax_errors
def test_runner_detects_bad_script_name
output = Dir.chdir(app_path) { `bin/rails runner "iuiqwiourowe" 2>&1` }
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
def test_environment_with_rails_env
......
require "abstract_unit"
require "env_helpers"
require "rails/commands/console"
require "rails/command"
require "rails/commands/console/console_command"
class Rails::ConsoleTest < ActiveSupport::TestCase
include EnvHelpers
......@@ -102,13 +103,21 @@ def test_rails_env_is_development_when_argument_is_d
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
stubbed_console = Class.new(Rails::Console) do
def available_environments
Rails::Command::ConsoleCommand.class_eval do
alias_method :old_environments, :available_environments
define_method :available_environments do
["dev"]
end
end
options = stubbed_console.parse_arguments(["dev"])
assert_match("dev", options[:environment])
assert_match("dev", parse_arguments(["dev"])[:environment])
ensure
Rails::Command::ConsoleCommand.class_eval do
undef_method :available_environments
alias_method :available_environments, :old_environments
undef_method :old_environments
end
end
attr_reader :output
......@@ -148,6 +157,21 @@ def load_console
end
def parse_arguments(args)
Rails::Console.parse_arguments(args)
Rails::Command::ConsoleCommand.class_eval do
alias_method :old_perform, :perform
define_method(:perform) do
extract_environment_option_from_argument
options
end
end
Rails::Command.invoke(:console, args)
ensure
Rails::Command::ConsoleCommand.class_eval do
undef_method :perform
alias_method :perform, :old_perform
undef_method :old_perform
end
end
end
require "abstract_unit"
require "minitest/mock"
require "rails/commands/dbconsole"
require "rails/command"
require "rails/commands/dbconsole/dbconsole_command"
class Rails::DBConsoleTest < ActiveSupport::TestCase
def setup
......@@ -97,16 +98,14 @@ def test_env
end
def test_rails_env_is_development_when_argument_is_dev
Rails::DBConsole.stub(:available_environments, ["development", "test"]) do
options = Rails::DBConsole.send(:parse_arguments, ["dev"])
assert_match("development", options[:environment])
stub_available_environments([ "development", "test" ]) do
assert_match("development", parse_arguments([ "dev" ])[:environment])
end
end
def test_rails_env_is_dev_when_argument_is_dev_and_dev_env_is_present
Rails::DBConsole.stub(:available_environments, ["dev"]) do
options = Rails::DBConsole.send(:parse_arguments, ["dev"])
assert_match("dev", options[:environment])
stub_available_environments([ "dev" ]) do
assert_match("dev", parse_arguments([ "dev" ])[:environment])
end
end
......@@ -203,20 +202,16 @@ def test_unknown_command_line_client
def test_print_help_short
stdout = capture(:stdout) do
start({}, ["-h"])
Rails::Command.invoke(:dbconsole, ["-h"])
end
assert aborted
assert_equal "", output
assert_match(/Usage:.*dbconsole/, stdout)
assert_match(/bin\/rails dbconsole \[environment\]/, stdout)
end
def test_print_help_long
stdout = capture(:stdout) do
start({}, ["--help"])
Rails::Command.invoke(:dbconsole, ["--help"])
end
assert aborted
assert_equal "", output
assert_match(/Usage:.*dbconsole/, stdout)
assert_match(/bin\/rails dbconsole \[environment\]/, stdout)
end
attr_reader :aborted, :output
......@@ -230,21 +225,22 @@ def app_db_config(results)
end
end
def dbconsole
@dbconsole ||= Class.new(Rails::DBConsole) do
def make_dbconsole
Class.new(Rails::DBConsole) do
attr_reader :find_cmd_and_exec_args
def find_cmd_and_exec(*args)
@find_cmd_and_exec_args = args
end
end.new(nil)
end
end
attr_reader :dbconsole
def start(config = {}, argv = [])
dbconsole.stub(:config, config.stringify_keys) do
dbconsole.stub(:arguments, argv) do
capture_abort { dbconsole.start }
end
@dbconsole = make_dbconsole.new(parse_arguments(argv))
@dbconsole.stub(:config, config.stringify_keys) do
capture_abort { @dbconsole.start }
end
end
......@@ -258,4 +254,41 @@ def capture_abort
end
end
end
def stub_available_environments(environments)
Rails::Command::DbconsoleCommand.class_eval do
alias_method :old_environments, :available_environments
define_method :available_environments do
environments
end
end
yield
ensure
Rails::Command::DbconsoleCommand.class_eval do
undef_method :available_environments
alias_method :available_environments, :old_environments
undef_method :old_environments
end
end
def parse_arguments(args)
Rails::Command::DbconsoleCommand.class_eval do
alias_method :old_perform, :perform
define_method(:perform) do
extract_environment_option_from_argument
options
end
end
Rails::Command.invoke(:dbconsole, args)
ensure
Rails::Command::DbconsoleCommand.class_eval do
undef_method :perform
alias_method :perform, :old_perform
undef_method :old_perform
end
end
end
require "abstract_unit"
require "env_helpers"
require "rails/commands/server"
require "rails/command"
require "rails/commands/server/server_command"
class Rails::ServerTest < ActiveSupport::TestCase
include EnvHelpers
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册