1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
require 'fileutils'
module Rails
module Generator
class GeneratorError < StandardError; end
class UsageError < GeneratorError; end
CONTRIB_ROOT = "#{RAILS_ROOT}/script/generators"
BUILTIN_ROOT = "#{File.dirname(__FILE__)}/../generators"
DEFAULT_SEARCH_PATHS = [CONTRIB_ROOT, BUILTIN_ROOT]
class << self
def instance(name, args = [], search_paths = DEFAULT_SEARCH_PATHS)
# RAILS_ROOT constant must be set.
unless Object.const_get(:RAILS_ROOT)
raise GeneratorError, "RAILS_ROOT must be set. Did you require 'config/environment'?"
end
# Force canonical name.
name = Inflector.underscore(name.downcase)
# Search for filesystem path to requested generator.
unless path = find_generator_path(name, search_paths)
raise GeneratorError, "#{name} generator not found."
end
# Check for templates directory.
template_root = "#{path}/templates"
unless File.directory?(template_root)
raise GeneratorError, "missing template directory #{template_root}"
end
# Require class file according to naming convention.
require "#{path}/#{name}_generator.rb"
# Find class according to naming convention. Allow Nesting::In::Modules.
class_name = Inflector.classify("#{name}_generator")
unless klass = find_generator_class(name)
raise GeneratorError, "no #{class_name} class defined in #{path}/#{name}_generator.rb"
end
# Instantiate and return generator.
klass.new(template_root, RAILS_ROOT, search_paths, args)
end
def builtin_generators
generators([BUILTIN_ROOT])
end
def contrib_generators
generators([CONTRIB_ROOT])
end
def generators(search_paths)
generator_paths(search_paths).keys.uniq.sort
end
# Find all generator paths.
def generator_paths(search_paths)
@paths ||= {}
unless @paths[search_paths]
paths = Hash.new { |h,k| h[k] = [] }
search_paths.each do |path|
Dir["#{path}/[a-z]*"].each do |dir|
paths[File.basename(dir)] << dir if File.directory?(dir)
end
end
@paths[search_paths] = paths
end
@paths[search_paths]
end
def find_generator_path(name, search_paths)
generator_paths(search_paths)[name].first
end
# Find all generator classes.
def generator_classes
classes = Hash.new { |h,k| h[k] = [] }
class_re = /([^:]+)Generator$/
ObjectSpace.each_object(Class) do |object|
if md = class_re.match(object.name) and object < Rails::Generator::Base
classes[Inflector.underscore(md.captures.first)] << object
end
end
classes
end
def find_generator_class(name)
generator_classes[name].first
end
end
# Talk about generators.
class Base
attr_reader :template_root, :destination_root, :args, :options,
:class_name, :singular_name, :plural_name
alias_method :file_name, :singular_name
alias_method :table_name, :plural_name
def self.generator_name
Inflector.underscore(name.gsub('Generator', ''))
end
def initialize(template_root, destination_root, search_paths, args)
@template_root, @destination_root = template_root, destination_root
usage if args.empty?
@search_paths, @original_args = search_paths, args.dup
@class_name, @singular_name, @plural_name = inflect_names(args.shift)
@options = extract_options!(args)
@args = args
end
# Checks whether the class name that was assigned to this generator
# would cause a collision with a Class, Module or other constant
# that is already used up by Ruby or RubyOnRails.
def collision_with_builtin?
builtin = Object.const_get(full_class_name) rescue nil
type = case builtin
when Class: "Class"
when Module: "Module"
else "Constant"
end
if builtin then
"Sorry, you can't have a #{self.class.generator_name} named " +
"'#{full_class_name}' because Ruby or Rails already has a #{type} with that name.\n" +
"Please rerun the generator with a different name."
end
end
# Returns the complete name that the resulting Class would have.
# Used in collision_with_builtin(). The default guess is that it is
# the same as class_name. Override this in your generator in case
# it is wrong.
def full_class_name
class_name
end
protected
# Look up another generator with the same arguments.
def generator(name)
Rails::Generator.instance(name, @original_args, @search_paths)
end
# Generate a file for a Rails application using an ERuby template.
# Looks up and evalutes a template by name and writes the result
# to a file relative to +destination_root+. The template
# is evaluated in the context of the optional eval_binding argument.
#
# The ERB template uses explicit trim mode to best control the
# proliferation of whitespace in generated code. <%- trims leading
# whitespace; -%> trims trailing whitespace including one newline.
def template(template_name, destination_path, eval_binding = nil)
# Determine full paths for source and destination files.
template_path = find_template_path(template_name)
destination_path = File.join(destination_root, destination_path)
# Create destination directories.
FileUtils.mkdir_p(File.dirname(destination_path))
# Render template and write result.
eval_binding ||= binding
contents = ERB.new(File.read(template_path), nil, '-').result(eval_binding)
File.open(destination_path, 'w') { |file| file.write(contents) }
end
def usage
raise UsageError.new, File.read(usage_path)
end
private
def find_template_path(template_name)
name, path = template_name.split('/', 2)
if path.nil?
File.join(template_root, name)
elsif generator_path = Rails::Generator.find_generator_path(name, @search_paths)
File.join(generator_path, 'templates', path)
end
end
def inflect_names(name)
camel = Inflector.camelize(Inflector.underscore(name))
under = Inflector.underscore(camel)
plural = Inflector.pluralize(under)
[camel, under, plural]
end
def extract_options!(args)
if args.last.is_a?(Hash) then args.pop else {} end
end
def usage_path
"#{template_root}/../USAGE"
end
end
end
end
|