Skip to content
This repository
Browse code

Add "-m/--template" option to Rails generator to apply template to ge…

…nerated application.

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
  • Loading branch information...
commit e8cc4b116c460c524961a07da92da3f323854c15 1 parent 2014d91
Jeremy McAnally authored December 02, 2008 lifo committed December 02, 2008
43  railties/CHANGELOG
... ...
@@ -1,5 +1,48 @@
1 1
 *2.3.0 [Edge]*
2 2
 
  3
+* Add "-m/--template" option to Rails generator to apply a template to the generated application. [Jeremy McAnally]
  4
+
  5
+    This has been extracted from rg - http://github.com/jeremymcanally/rg
  6
+    
  7
+    Example:
  8
+    
  9
+        # template.rb
  10
+        
  11
+        # Install plugins from git or svn
  12
+        plugin "will-paginate", :git => "git://github.com/mislav/will_paginate.git"
  13
+        plugin "old-restful-auth", :svn => "http://svn.techno-weenie.net/projects/plugins/restful_authentication/"
  14
+    
  15
+        # Add gems to environment.rb
  16
+        gem "jeremymcanally-context"
  17
+        gem "bluecloth"
  18
+    
  19
+        # Vendor file. Data in a string or...
  20
+        vendor("borrowed.rb", <<CODE
  21
+          def helpful_method
  22
+            do_something_helpful_here
  23
+          end
  24
+        CODE
  25
+    
  26
+        # ...file data from block return value.
  27
+        # #initializer creates a new initializer file
  28
+        initializer("crypto.rb") do
  29
+          salt = "--#{Time.now}--#{rand}--#{srand(Time.now.to_i)}"
  30
+    
  31
+          "SPECIAL_SALT = '#{salt}'"
  32
+        end
  33
+    
  34
+    Usage:
  35
+    
  36
+      To use a template, provide a file path or URL:
  37
+    
  38
+      1. Using a local file :
  39
+
  40
+        rails <application name> -m /path/to/my/template.rb
  41
+        
  42
+      2. Or directly from a URL :
  43
+
  44
+        rails <application name> --template=http://gist.github.com/31208.txt
  45
+
3 46
 * Extracted the process scripts (inspector, reaper, spawner) into the plugin irs_process_scripts [DHH]
4 47
 
5 48
 * Changed Rails.root to return a Pathname object (allows for Rails.root.join('app', 'controllers') => "#{RAILS_ROOT}/app/controllers") #1482 [Damian Janowski/?]
1  railties/bin/rails
@@ -8,6 +8,7 @@ if %w(--version -v).include? ARGV.first
8 8
 end
9 9
 
10 10
 freeze   = ARGV.any? { |option| %w(--freeze -f).include?(option) }
  11
+
11 12
 app_path = ARGV.first
12 13
 
13 14
 require File.dirname(__FILE__) + '/../lib/rails_generator'
3  railties/lib/rails_generator/base.rb
@@ -154,6 +154,9 @@ def destination_path(relative_destination)
154 154
         File.join(destination_root, relative_destination)
155 155
       end
156 156
 
  157
+      def after_generate
  158
+      end
  159
+
157 160
       protected
158 161
         # Convenience method for generator subclasses to record a manifest.
159 162
         def record
1  railties/lib/rails_generator/commands.rb
@@ -40,6 +40,7 @@ class Base < DelegateClass(Rails::Generator::Base)
40 40
         # Replay action manifest.  RewindBase subclass rewinds manifest.
41 41
         def invoke!
42 42
           manifest.replay(self)
  43
+          after_generate
43 44
         end
44 45
 
45 46
         def dependency(generator_name, args, runtime_options = {})
12  railties/lib/rails_generator/generators/applications/app/app_generator.rb
... ...
@@ -1,4 +1,5 @@
1 1
 require 'rbconfig'
  2
+require File.dirname(__FILE__) + '/template_runner'
2 3
 require 'digest/md5' 
3 4
 require 'active_support/secure_random'
4 5
 
@@ -37,6 +38,12 @@ def manifest
37 38
     end
38 39
   end
39 40
 
  41
+  def after_generate
  42
+    if options[:template]
  43
+      Rails::TemplateRunner.new(@destination_root, options[:template])
  44
+    end
  45
+  end
  46
+
40 47
   protected
41 48
     def banner
42 49
       "Usage: #{$0} /path/to/your/app [options]"
@@ -60,6 +67,11 @@ def add_options!(opt)
60 67
       opt.on("-f", "--freeze",
61 68
             "Freeze Rails in vendor/rails from the gems generating the skeleton",
62 69
             "Default: false") { |v| options[:freeze] = v }
  70
+
  71
+      opt.on("-m", "--template=path", String,
  72
+            "Use an application template that lives at path (can be a filesystem path or URL).",
  73
+            "Default: (none)") { |v| options[:template] = v }
  74
+
63 75
     end
64 76
 
65 77
 
16  railties/lib/rails_generator/generators/applications/app/scm/git.rb
... ...
@@ -0,0 +1,16 @@
  1
+module Rails
  2
+  class Git < Scm
  3
+    def self.clone(repos, branch=nil)
  4
+      `git clone #{repos}`
  5
+
  6
+      if branch
  7
+        `cd #{repos.split('/').last}/`
  8
+        `git checkout #{branch}`
  9
+      end
  10
+    end
  11
+
  12
+    def self.run(command)
  13
+      `git #{command}`
  14
+    end
  15
+  end
  16
+end
8  railties/lib/rails_generator/generators/applications/app/scm/scm.rb
... ...
@@ -0,0 +1,8 @@
  1
+module Rails
  2
+  class Scm
  3
+    private
  4
+      def self.hash_to_parameters(hash)
  5
+        hash.collect { |key, value| "--#{key} #{(value.kind_of?(String) ? value : "")}"}.join(" ")
  6
+      end
  7
+  end
  8
+end
7  railties/lib/rails_generator/generators/applications/app/scm/svn.rb
... ...
@@ -0,0 +1,7 @@
  1
+module Rails
  2
+  class Svn < Scm
  3
+    def self.checkout(repos, branch = nil)
  4
+      `svn checkout #{repos}/#{branch || "trunk"}`
  5
+    end
  6
+  end
  7
+end
376  railties/lib/rails_generator/generators/applications/app/template_runner.rb
... ...
@@ -0,0 +1,376 @@
  1
+require File.dirname(__FILE__) + '/scm/scm'
  2
+require File.dirname(__FILE__) + '/scm/git'
  3
+require File.dirname(__FILE__) + '/scm/svn'
  4
+
  5
+require 'open-uri'
  6
+require 'fileutils'
  7
+
  8
+module Rails
  9
+  class TemplateRunner
  10
+    attr_reader :behavior, :description, :root
  11
+
  12
+    def initialize(root, template) # :nodoc:
  13
+      @root = Dir.pwd + "/" + root
  14
+
  15
+      puts "applying template: #{template}"
  16
+
  17
+      load_template(template)
  18
+
  19
+      puts "#{template} applied."
  20
+    end
  21
+
  22
+    def load_template(template)
  23
+      begin
  24
+        code = open(template).read
  25
+        in_root { self.instance_eval(code) }
  26
+      rescue LoadError
  27
+        raise "The template [#{template}] could not be loaded."
  28
+      end
  29
+    end
  30
+
  31
+    # Create a new file in the Rails project folder.  Specify the
  32
+    # relative path from RAILS_ROOT.  Data is the return value of a block
  33
+    # or a data string.
  34
+    #
  35
+    # ==== Examples
  36
+    #
  37
+    #   file("lib/fun_party.rb") do
  38
+    #     hostname = ask("What is the virtual hostname I should use?")
  39
+    #     "vhost.name = #{hostname}"
  40
+    #   end
  41
+    #
  42
+    #   file("config/apach.conf", "your apache config")
  43
+    #
  44
+    def file(filename, data = nil, &block)
  45
+      puts "creating file #{filename}"
  46
+      dir, file = [File.dirname(filename), File.basename(filename)]
  47
+
  48
+      inside(dir) do
  49
+        File.open(file, "w") do |f|
  50
+          if block_given?
  51
+            f.write(block.call)
  52
+          else
  53
+            f.write(data)
  54
+          end
  55
+        end
  56
+      end
  57
+    end
  58
+
  59
+    # Install a plugin.  You must provide either a Subversion url or Git url.
  60
+    #
  61
+    # ==== Examples
  62
+    #
  63
+    #   plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git'
  64
+    #   plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
  65
+    #
  66
+    def plugin(name, options)
  67
+      puts "installing plugin #{name}"
  68
+
  69
+      if options[:git] || options[:svn]
  70
+        in_root do
  71
+          `script/plugin install #{options[:svn] || options[:git]}`
  72
+        end
  73
+      else
  74
+        puts "! no git or svn provided for #{name}.  skipping..."
  75
+      end
  76
+    end
  77
+
  78
+    # Adds an entry into config/environment.rb for the supplied gem :
  79
+    #
  80
+    #   1. Provide a git repository URL...
  81
+    #
  82
+    #     gem 'will-paginate', :git => 'git://github.com/mislav/will_paginate.git'
  83
+    #
  84
+    #   2. Provide a subversion repository URL...
  85
+    #
  86
+    #     gem 'will-paginate', :svn => 'svn://svnhub.com/mislav/will_paginate/trunk'
  87
+    #
  88
+    #   3. Provide a gem name and use your system sources to install and unpack it.
  89
+    #
  90
+    #     gem 'ruby-openid'
  91
+    #
  92
+    def gem(name, options = {})
  93
+      puts "adding gem #{name}"
  94
+
  95
+      sentinel = 'Rails::Initializer.run do |config|'
  96
+      gems_code = "config.gem '#{name}'"
  97
+
  98
+      if options.any?
  99
+        opts = options.inject([]) {|result, h| result << [":#{h[0]} => '#{h[1]}'"] }.join(", ")
  100
+        gems_code << ", #{opts}"
  101
+      end
  102
+
  103
+      in_root do
  104
+        gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
  105
+          "#{match}\n  #{gems_code}"
  106
+        end
  107
+      end
  108
+    end
  109
+
  110
+    # Run a command in git.
  111
+    #
  112
+    # ==== Examples
  113
+    #
  114
+    #   git :init
  115
+    #   git :add => "this.file that.rb"
  116
+    #   git :add => "onefile.rb", :rm => "badfile.cxx"
  117
+    #
  118
+    def git(command = {})
  119
+      puts "running git #{command}"
  120
+
  121
+      in_root do
  122
+        if command.is_a?(Symbol)
  123
+          Git.run(command.to_s)
  124
+        else
  125
+          command.each do |command, options|
  126
+            Git.run("#{command} #{options}")
  127
+          end
  128
+        end
  129
+      end
  130
+    end
  131
+
  132
+    # Create a new file in the vendor/ directory. Code can be specified
  133
+    # in a block or a data string can be given.
  134
+    #
  135
+    # ==== Examples
  136
+    #
  137
+    #   vendor("sekrit.rb") do
  138
+    #     sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
  139
+    #     "salt = '#{sekrit_salt}'"
  140
+    #   end
  141
+    #
  142
+    #   vendor("foreign.rb", "# Foreign code is fun")
  143
+    #
  144
+    def vendor(filename, data = nil, &block)
  145
+      puts "vendoring file #{filename}"
  146
+      inside("vendor") do |folder|
  147
+        File.open("#{folder}/#{filename}", "w") do |f|
  148
+          if block_given?
  149
+            f.write(block.call)
  150
+          else
  151
+            f.write(data)
  152
+          end
  153
+        end
  154
+      end
  155
+    end
  156
+
  157
+    # Create a new file in the lib/ directory. Code can be specified
  158
+    # in a block or a data string can be given.
  159
+    #
  160
+    # ==== Examples
  161
+    #
  162
+    #   lib("crypto.rb") do
  163
+    #     "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
  164
+    #   end
  165
+    #
  166
+    #   lib("foreign.rb", "# Foreign code is fun")
  167
+    #
  168
+    def lib(filename, data = nil)
  169
+      puts "add lib file #{filename}"
  170
+      inside("lib") do |folder|
  171
+        File.open("#{folder}/#{filename}", "w") do |f|
  172
+          if block_given?
  173
+            f.write(block.call)
  174
+          else
  175
+            f.write(data)
  176
+          end
  177
+        end
  178
+      end
  179
+    end
  180
+
  181
+    # Create a new Rakefile with the provided code (either in a block or a string).
  182
+    #
  183
+    # ==== Examples
  184
+    #
  185
+    #   rakefile("bootstrap.rake") do
  186
+    #     project = ask("What is the UNIX name of your project?")
  187
+    #
  188
+    #     <<-TASK
  189
+    #       namespace :#{project} do
  190
+    #         task :bootstrap do
  191
+    #           puts "i like boots!"
  192
+    #         end
  193
+    #       end
  194
+    #     TASK
  195
+    #   end
  196
+    #
  197
+    #   rakefile("seed.rake", "puts 'im plantin ur seedz'")
  198
+    #
  199
+    def rakefile(filename, data = nil, &block)
  200
+      puts "adding rakefile #{filename}"
  201
+      inside("lib/tasks") do |folder|
  202
+        File.open("#{folder}/#{filename}", "w") do |f|
  203
+          if block_given?
  204
+            f.write(block.call)
  205
+          else
  206
+            f.write(data)
  207
+          end
  208
+        end
  209
+      end
  210
+    end
  211
+
  212
+    # Create a new initializer with the provided code (either in a block or a string).
  213
+    #
  214
+    # ==== Examples
  215
+    #
  216
+    #   initializer("globals.rb") do
  217
+    #     data = ""
  218
+    #
  219
+    #     ['MY_WORK', 'ADMINS', 'BEST_COMPANY_EVAR'].each do
  220
+    #       data << "#{const} = :entp"
  221
+    #     end
  222
+    #
  223
+    #     data
  224
+    #   end
  225
+    #
  226
+    #   initializer("api.rb", "API_KEY = '123456'")
  227
+    #
  228
+    def initializer(filename, data = nil, &block)
  229
+      puts "adding initializer #{filename}"
  230
+      inside("config/initializers") do |folder|
  231
+        File.open("#{folder}/#{filename}", "w") do |f|
  232
+          if block_given?
  233
+            f.write(block.call)
  234
+          else
  235
+            f.write(data)
  236
+          end
  237
+        end
  238
+      end
  239
+    end
  240
+
  241
+    # Generate something using a generator from Rails or a plugin.
  242
+    # The second parameter is the argument string that is passed to
  243
+    # the generator or an Array that is joined.
  244
+    #
  245
+    # ==== Example
  246
+    #
  247
+    #   generate(:authenticated, "user session")
  248
+    #
  249
+    def generate(what, args = nil)
  250
+      puts "generating #{what}"
  251
+      args = args.join(" ") if args.class == Array
  252
+
  253
+      in_root { `#{root}/script/generate #{what} #{args}` }
  254
+    end
  255
+
  256
+    # Executes a command
  257
+    #
  258
+    # ==== Example
  259
+    #
  260
+    #   inside('vendor') do
  261
+    #     run('ln -s ~/edge rails)
  262
+    #   end
  263
+    #
  264
+    def run(command)
  265
+      puts "executing #{command} from #{Dir.pwd}"
  266
+      `#{command}`
  267
+    end
  268
+
  269
+    # Runs the supplied rake task
  270
+    #
  271
+    # ==== Example
  272
+    #
  273
+    #   rake("db:migrate")
  274
+    #   rake("db:migrate", "production")
  275
+    #
  276
+    def rake(command, env = 'development')
  277
+      puts "running rake task #{command}"
  278
+      in_root { `rake #{command} RAILS_ENV=#{env}` }
  279
+    end
  280
+
  281
+    # Just run the capify command in root
  282
+    #
  283
+    # ==== Example
  284
+    #
  285
+    #   capify!
  286
+    #
  287
+    def capify!
  288
+      in_root { `capify .` }
  289
+    end
  290
+
  291
+    # Add Rails to /vendor/rails
  292
+    #
  293
+    # ==== Example
  294
+    #
  295
+    #   freeze!
  296
+    #
  297
+    def freeze!(args = {})
  298
+      puts "vendoring rails edge"
  299
+      in_root { `rake rails:freeze:edge` }
  300
+    end
  301
+
  302
+    # Make an entry in Rails routing file conifg/routes.rb
  303
+    #
  304
+    # === Example
  305
+    #
  306
+    #   route "map.root :controller => :welcome"
  307
+    #
  308
+    def route(routing_code)
  309
+      sentinel = 'ActionController::Routing::Routes.draw do |map|'
  310
+
  311
+      in_root do
  312
+        gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
  313
+          "#{match}\n  #{routing_code}\n"
  314
+        end
  315
+      end
  316
+    end
  317
+
  318
+    protected
  319
+
  320
+    # Get a user's input
  321
+    #
  322
+    # ==== Example
  323
+    #
  324
+    #   answer = ask("Should I freeze the latest Rails?")
  325
+    #   freeze! if ask("Should I freeze the latest Rails?") == "yes"
  326
+    #
  327
+    def ask(string)
  328
+      puts string
  329
+      gets.strip
  330
+    end
  331
+
  332
+    # Do something in the root of the Rails application or
  333
+    # a provided subfolder; the full path is yielded to the block you provide.
  334
+    # The path is set back to the previous path when the method exits.
  335
+    def inside(dir = '', &block)
  336
+      folder = File.join(root, dir)
  337
+      FileUtils.mkdir_p(folder) unless File.exist?(folder)
  338
+      FileUtils.cd(folder) { block.arity == 1 ? yield(folder) : yield }
  339
+    end
  340
+
  341
+    def in_root
  342
+      FileUtils.cd(root) { yield }
  343
+    end
  344
+
  345
+    # Helper to test if the user says yes(y)?
  346
+    #
  347
+    # ==== Example
  348
+    #
  349
+    #   freeze! if yes?("Should I freeze the latest Rails?")
  350
+    #
  351
+    def yes?(question)
  352
+      answer = ask(question).downcase
  353
+      answer == "y" || answer == "yes"
  354
+    end
  355
+
  356
+    # Helper to test if the user does NOT say yes(y)?
  357
+    #
  358
+    # ==== Example
  359
+    #
  360
+    #   capify! if no?("Will you be using vlad to deploy your application?")
  361
+    #
  362
+    def no?(question)
  363
+      !yes?(question)
  364
+    end
  365
+
  366
+    def gsub_file(relative_destination, regexp, *args, &block)
  367
+      path = destination_path(relative_destination)
  368
+      content = File.read(path).gsub(regexp, *args, &block)
  369
+      File.open(path, 'wb') { |file| file.write(content) }
  370
+    end
  371
+
  372
+    def destination_path(relative_destination)
  373
+      File.join(root, relative_destination)
  374
+    end
  375
+  end
  376
+end

8 notes on commit e8cc4b1

Matthew Rudy Jacobs

oh wow. I didn’t realise you could comment on individual lines.

Massiveness.

Mislav Marohnić

OMG you’re increasing Rails’ LOC, nooooo

Mark Turner

ReallY? OMG ITS HUGER!

Chris Kampmeier

Wow, this is super cool, nice work j-mac

Pete Nicholls
vendor("borrowed.rb", <<CODE
  ..snip..
+ CODE

Missing right-paren?

Jeremy McAnally

Oopsie.

Michael Bumann

yeah! great to see RG go into core! It feels like the old days when I’ve run the rails command for the first time ;)

Sam Granieri

This is pretty cool. I wonder why RG was chosen over Suprails?

Michael Koziarski
Owner

@samgranieri: Jeremy reached out and suggested getting it included. We’ll be happy to take any patches to implement cool functionality in suprails but not in rails.

Bradley Grzesiak

@samgranieri Thanks for the verbal support for Suprails, but Jeremy did the more intelligent thing and got it included into rails. I didn't try to do that with Suprails because I thought it would never get pulled into core. oops. =)

Please sign in to comment.
Something went wrong with that request. Please try again.