aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/generators.rb
blob: 3cf4ccb79ac45d5940f690aaac24e4132490b466 (plain) (blame)
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
$:.unshift(activesupport_path) if File.directory?(activesupport_path)

begin
  require 'active_support/mini'
rescue LoadError
  require 'rubygems'
  gem 'activesupport'
  require 'active_support/mini'
end

$:.unshift(File.dirname(__FILE__))

# TODO Use vendored Thor
require 'rubygems'
gem 'josevalim-thor'
require 'thor'

require 'generators/base'
require 'generators/named_base'

module Rails
  module Generators
    DEFAULT_ALIASES = {
      :actions => '-a',
      :fixture_replacement => '-r',
      :orm => '-o',
      :resource_controller => '-c',
      :scaffold_controller => '-c',
      :stylesheets => '-y',
      :test_framework => '-t',
      :template_engine => '-e'
    }

    DEFAULT_OPTIONS = {
      :fixture => true,
      :force_plural => false,
      :helper => true,
      :integration_tool => :test_unit,
      :layout => true,
      :migration => true,
      :orm => :active_record,
      :performance_tool => :test_unit,
      :resource_controller => :controller,
      :scaffold_controller => :scaffold_controller,
      :singleton => false,
      :stylesheets => true,
      :test_framework => :test_unit,
      :template_engine => :erb,
      :timestamps => true
    }

    def self.aliases
      @@aliases ||= DEFAULT_ALIASES.dup
    end

    def self.options
      @@options ||= DEFAULT_OPTIONS.dup
    end

    # Remove the color from output.
    #
    def self.no_color!
      Thor::Base.shell = Thor::Shell::Basic
    end

    # Generators load paths used on lookup. The lookup happens as:
    #
    #   1) builtin generators
    #   2) frozen gems generators
    #   3) rubygems gems generators (not available yet)
    #   4) plugin generators
    #   5) lib generators
    #   6) ~/rails/generators
    #
    # TODO Add Rubygems generators (depends on dependencies system rework)
    # TODO Remove hardcoded paths for all, except (1).
    #
    def self.load_path
      @@load_path ||= begin
        paths = []
        paths << File.expand_path(File.join(File.dirname(__FILE__), "generators"))
        if defined?(RAILS_ROOT)
          paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "*", "lib", "generators")]
          paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")]
          paths << File.join(RAILS_ROOT, "lib", "generators")
        end
        paths << File.join(Thor::Util.user_home, ".rails", "generators")
        paths
      end
    end
    load_path # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths.

    # Receives a namespace and tries different combinations to find a generator.
    #
    # ==== Examples
    #
    #   find_by_namespace :webrat, :rails, :integration
    #
    # Will search for the following generators:
    #
    #   "rails:generators:webrat", "webrat:generators:integration", "webrat"
    #
    # If the namespace has ":" included we consider that a absolute namespace
    # was given and the lookup above does not happen. Just the name is searched.
    #
    # Finally, it deals with one kind of shortcut:
    #
    #   find_by_namespace "test_unit:model"
    #
    # It will search for generators at:
    #
    #   "test_unit:generators:model", "test_unit:model"
    #
    def self.find_by_namespace(name, base=nil, context=nil)
      name, attempts = name.to_s, []

      if name.count(':') == 0
        attempts << "#{base}:generators:#{name}"    if base
        attempts << "#{name}:generators:#{context}" if context
      end
      attempts << name.sub(':', ':generators:') if name.count(':') == 1
      attempts << name

      unloaded = attempts - namespaces
      lookup(unloaded)

      attempts.each do |namespace|
        klass = Thor::Util.find_by_namespace(namespace)
        return klass if klass
      end

      nil
    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={})
      if klass = find_by_namespace(namespace, "rails")
        args << "--help" if klass.arguments.any? { |a| a.required? } && args.empty?
        klass.start args, config
      else
        puts "Could not find generator #{namespace}."
      end
    end

    # Show help message with available generators.
    #
    def self.help
      rails = Rails::Generators.builtin.map do |group, name|
        name if group == "rails"
      end
      rails.compact!
      rails.sort!

      puts "Please select a generator."
      puts "Builtin: #{rails.join(', ')}."

      # Load paths and remove builtin
      paths, others = load_path.dup, []
      paths.shift

      paths.each do |path|
        tail = [ "*", "*", "*_generator.rb" ]

        until tail.empty?
          others += Dir[File.join(path, *tail)].collect do |file|
            file.split('/')[-tail.size, 2].join(':').sub(/_generator\.rb$/, '')
          end
          tail.shift
        end
      end

      others.sort!
      puts "Others: #{others.join(', ')}." unless others.empty?
    end

    protected

      # Return all defined namespaces.
      #
      def self.namespaces
        Thor::Base.subclasses.map(&:namespace)
      end

      # Keep builtin generators in an Array[Array[group, name]].
      #
      def self.builtin
        Dir[File.dirname(__FILE__) + '/generators/*/*'].collect do |file|
          file.split('/')[-2, 2]
        end
      end

      # Receives namespaces in an array and tries to find matching generators
      # in the load path. Each path is traversed into directory lookups. For
      # example:
      #
      #   rails:generators:model
      #
      # Becomes:
      #
      #   generators/rails/model/model_generator.rb
      #   generators/rails/model_generator.rb
      #   generators/model_generator.rb
      #
      def self.lookup(attempts)
        attempts.each do |attempt|
          generators_path = ['.']

          paths = attempt.gsub(':generators:', ':').split(':')
          name  = "#{paths.last}_generator.rb"

          until paths.empty?
            generators_path.unshift File.join(*paths)
            paths.pop
          end

          generators_path.uniq!
          generators_path = "{#{generators_path.join(',')}}"

          self.load_path.each do |path|
            Dir[File.join(path, generators_path, name)].each do |file|
              begin
                require file
              rescue Exception => e
                warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}"
              end
            end
          end
        end
      end

  end
end