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

389 lines
12 KiB
Ruby
Raw Normal View History

activesupport_path = File.expand_path('../../../../activesupport/lib', __FILE__)
$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
require 'thor/group'
require 'active_support'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/kernel/singleton_class'
2009-12-27 17:43:06 -05:00
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/deep_merge'
require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/inflections'
module Rails
module Generators
autoload :Actions, 'rails/generators/actions'
autoload :ActiveModel, 'rails/generators/active_model'
autoload :Base, 'rails/generators/base'
autoload :Migration, 'rails/generators/migration'
autoload :NamedBase, 'rails/generators/named_base'
autoload :ResourceHelpers, 'rails/generators/resource_helpers'
autoload :TestCase, 'rails/generators/test_case'
2011-06-07 05:36:11 -04:00
mattr_accessor :namespace
DEFAULT_ALIASES = {
2012-10-14 06:03:39 -04:00
rails: {
actions: '-a',
orm: '-o',
javascripts: '-j',
javascript_engine: '-je',
resource_controller: '-c',
scaffold_controller: '-c',
stylesheets: '-y',
stylesheet_engine: '-se',
scaffold_stylesheet: '-ss',
2012-10-14 06:03:39 -04:00
template_engine: '-e',
test_framework: '-t'
},
2012-10-14 06:03:39 -04:00
test_unit: {
fixture_replacement: '-r',
}
}
DEFAULT_OPTIONS = {
2012-10-14 06:03:39 -04:00
rails: {
api: false,
2012-10-14 06:03:39 -04:00
assets: true,
force_plural: false,
helper: true,
integration_tool: nil,
javascripts: true,
javascript_engine: :js,
orm: false,
resource_controller: :controller,
resource_route: true,
scaffold_controller: :scaffold_controller,
stylesheets: true,
stylesheet_engine: :css,
scaffold_stylesheet: true,
2012-10-14 06:03:39 -04:00
test_framework: false,
template_engine: :erb
}
}
def self.configure!(config) #:nodoc:
api_only! if config.api_only
no_color! unless config.colorize_logging
aliases.deep_merge! config.aliases
options.deep_merge! config.options
2010-02-01 04:48:59 -05:00
fallbacks.merge! config.fallbacks
templates_path.concat config.templates
templates_path.uniq!
hide_namespaces(*config.hidden_namespaces)
end
2012-03-14 17:30:01 -04:00
def self.templates_path #:nodoc:
@templates_path ||= []
end
2009-07-15 18:17:28 -04:00
def self.aliases #:nodoc:
@aliases ||= DEFAULT_ALIASES.dup
end
2009-07-15 18:17:28 -04:00
def self.options #:nodoc:
@options ||= DEFAULT_OPTIONS.dup
end
2009-07-15 18:17:28 -04:00
# Hold configured generators fallbacks. If a plugin developer wants a
# generator group to fallback to another group in case of missing generators,
# they can add a fallback.
#
# For example, shoulda is considered a test_framework and is an extension
# of test_unit. However, most part of shoulda generators are similar to
# test_unit ones.
#
# Shoulda then can tell generators to search for test_unit generators when
# some of them are not available by adding a fallback:
#
# Rails::Generators.fallbacks[:shoulda] = :test_unit
def self.fallbacks
@fallbacks ||= {}
2009-07-15 18:17:28 -04:00
end
def self.api_only!
hide_namespaces "assets", "helper", "css", "js"
options[:rails].merge!(
api: true,
helper: false,
template_engine: nil
)
end
# Remove the color from output.
def self.no_color!
Thor::Base.shell = Thor::Shell::Basic
end
2009-07-03 06:10:09 -04:00
# 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.
#
2009-07-02 05:08:07 -04:00
# 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.
2009-07-15 18:17:28 -04:00
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] }]
2010-01-18 10:14:32 -05:00
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}'. "
Improve generator name suggestions a bit. Previously, the Levenshtein distances to most commands were wrongly calculated due to a big shortcut. This might be included in the original code for the performance sake, but I'm not sure that's something we should take over accuracy in Rails. Before 'foo' to 'assets' #=> 3 'foo' to 'controller' #=> 3 'foo' to 'generator' #=> 3 'foo' to 'helper' #=> 3 'foo' to 'integration_test' #=> 3 'foo' to 'jbuilder' #=> 3 'foo' to 'job' #=> 2 'foo' to 'mailer' #=> 3 'foo' to 'migration' #=> 3 'foo' to 'model' #=> 3 'foo' to 'resource' #=> 3 'foo' to 'resource_route' #=> 3 'foo' to 'scaffold' #=> 3 'foo' to 'scaffold_controller' #=> 3 'foo' to 'task' #=> 4 'foo' to 'active_record:migration' #=> 3 'foo' to 'active_record:model' #=> 3 'foo' to 'coffee:assets' #=> 3 'foo' to 'css:assets' #=> 3 'foo' to 'css:scaffold' #=> 3 'foo' to 'erb:controller' #=> 3 'foo' to 'erb:mailer' #=> 3 'foo' to 'erb:scaffold' #=> 3 'foo' to 'js:assets' #=> 3 'foo' to 'scss:assets' #=> 3 'foo' to 'scss:scaffold' #=> 3 'foo' to 'test_unit:controller' #=> 3 'foo' to 'test_unit:generator' #=> 3 'foo' to 'test_unit:helper' #=> 3 'foo' to 'test_unit:integration' #=> 3 'foo' to 'test_unit:job' #=> 3 'foo' to 'test_unit:mailer' #=> 3 'foo' to 'test_unit:model' #=> 3 'foo' to 'test_unit:plugin' #=> 3 'foo' to 'test_unit:scaffold' #=> 3 After 'foo' to 'assets' #=> 6 'foo' to 'controller' #=> 8 'foo' to 'generator' #=> 8 'foo' to 'helper' #=> 6 'foo' to 'integration_test' #=> 15 'foo' to 'jbuilder' #=> 8 'foo' to 'job' #=> 2 'foo' to 'mailer' #=> 6 'foo' to 'migration' #=> 8 'foo' to 'model' #=> 4 'foo' to 'resource' #=> 7 'foo' to 'resource_route' #=> 12 'foo' to 'scaffold' #=> 6 'foo' to 'scaffold_controller' #=> 16 'foo' to 'task' #=> 4 'foo' to 'active_record:migration' #=> 21 'foo' to 'active_record:model' #=> 17 'foo' to 'coffee:assets' #=> 12 'foo' to 'css:assets' #=> 10 'foo' to 'css:scaffold' #=> 10 'foo' to 'erb:controller' #=> 12 'foo' to 'erb:mailer' #=> 10 'foo' to 'erb:scaffold' #=> 10 'foo' to 'js:assets' #=> 9 'foo' to 'scss:assets' #=> 11 'foo' to 'scss:scaffold' #=> 11 'foo' to 'test_unit:controller' #=> 18 'foo' to 'test_unit:generator' #=> 18 'foo' to 'test_unit:helper' #=> 16 'foo' to 'test_unit:integration' #=> 20 'foo' to 'test_unit:job' #=> 12 'foo' to 'test_unit:mailer' #=> 16 'foo' to 'test_unit:model' #=> 14 'foo' to 'test_unit:plugin' #=> 16 'foo' to 'test_unit:scaffold' #=> 16 Besides that, the conjunction "or" of the message now appears only between the last two suggestions.
2015-01-07 09:22:45 -05:00
msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.to_sentence(last_word_connector: " or ") }\n"
msg << "Run `rails generate --help` for more options."
puts msg
end
end
2014-05-29 15:40:02 -04:00
# 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
# invoked with the shorter "migration", others are private to other generators
# such as "css:scaffold".
def self.hidden_namespaces
@hidden_namespaces ||= begin
orm = options[:rails][:orm]
test = options[:rails][:test_framework]
template = options[:rails][:template_engine]
css = options[:rails][:stylesheet_engine]
[
"rails",
2012-04-17 18:17:02 -04:00
"resource_route",
"#{orm}:migration",
"#{orm}:model",
"#{test}:controller",
"#{test}:helper",
"#{test}:integration",
"#{test}:mailer",
"#{test}:model",
"#{test}:scaffold",
"#{test}:view",
"#{template}:controller",
"#{template}:scaffold",
"#{template}:mailer",
"#{css}:scaffold",
"#{css}:assets",
"css:assets",
"css:scaffold"
]
end
end
class << self
def hide_namespaces(*namespaces)
hidden_namespaces.concat(namespaces)
end
alias hide_namespace hide_namespaces
end
2009-07-02 05:08:07 -04:00
# Show help message with available generators.
def self.help(command = 'generate')
puts "Usage: rails #{command} GENERATOR [args] [options]"
2010-01-18 17:41:18 -05:00
puts
puts "General options:"
puts " -h, [--help] # Print generator's options and usage"
2010-01-18 17:41:18 -05:00
puts " -p, [--pretend] # Run but do not make any changes"
puts " -f, [--force] # Overwrite files that already exist"
puts " -s, [--skip] # Skip files that already exist"
puts " -q, [--quiet] # Suppress status output"
2010-01-18 17:41:18 -05:00
puts
puts "Please choose a generator below."
2010-01-18 10:14:32 -05:00
puts
print_generators
end
def self.public_namespaces
lookup!
subclasses.map(&:namespace)
end
def self.print_generators
sorted_groups.each { |b, n| print_list(b, n) }
end
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
2010-01-18 10:14:32 -05:00
rails = groups.delete("rails")
rails.map! { |n| n.sub(/^rails:/, '') }
rails.delete("app")
rails.delete("plugin")
2010-01-18 10:14:32 -05:00
hidden_namespaces.each { |n| groups.delete(n.to_s) }
[["rails", rails]] + groups.sort.to_a
2009-07-02 05:08:07 -04:00
end
2009-07-03 06:10:09 -04:00
protected
# This code is based directly on the Text gem implementation
# 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
end
x
end
2010-01-18 10:14:32 -05:00
# Prints a list of generators.
def self.print_list(base, namespaces) #:nodoc:
namespaces = namespaces.reject do |n|
hidden_namespaces.include?(n)
end
2010-01-18 10:14:32 -05:00
return if namespaces.empty?
puts "#{base.camelize}:"
namespaces.each do |namespace|
puts(" #{namespace}")
end
2010-01-18 10:14:32 -05:00
puts
end
# Try fallbacks for the given base.
def self.invoke_fallbacks_for(name, base) #:nodoc:
return nil unless base && fallbacks[base.to_sym]
invoked_fallbacks = []
Array(fallbacks[base.to_sym]).each do |fallback|
next if invoked_fallbacks.include?(fallback)
invoked_fallbacks << fallback
klass = find_by_namespace(name, fallback)
return klass if klass
end
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
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
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("/")
2009-07-03 06:10:09 -04:00
end
paths.uniq!
paths
2009-07-03 06:10:09 -04:00
end
end
end