Skip to content
Browse files

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...
1 parent 2014d91 commit e8cc4b116c460c524961a07da92da3f323854c15 @jm jm committed with lifo Dec 2, 2008
View
43 railties/CHANGELOG
@@ -1,5 +1,48 @@
*2.3.0 [Edge]*
+* Add "-m/--template" option to Rails generator to apply a template to the generated application. [Jeremy McAnally]
+
+ This has been extracted from rg - http://github.com/jeremymcanally/rg
+
+ Example:
+
+ # template.rb
+
+ # Install plugins from git or svn
+ plugin "will-paginate", :git => "git://github.com/mislav/will_paginate.git"
+ plugin "old-restful-auth", :svn => "http://svn.techno-weenie.net/projects/plugins/restful_authentication/"
+
+ # Add gems to environment.rb
+ gem "jeremymcanally-context"
+ gem "bluecloth"
+
+ # Vendor file. Data in a string or...
+ vendor("borrowed.rb", <<CODE
+ def helpful_method
+ do_something_helpful_here
+ end
+ CODE
+
+ # ...file data from block return value.
+ # #initializer creates a new initializer file
+ initializer("crypto.rb") do
+ salt = "--#{Time.now}--#{rand}--#{srand(Time.now.to_i)}"
+
+ "SPECIAL_SALT = '#{salt}'"
+ end
+
+ Usage:
+
+ To use a template, provide a file path or URL:
+
+ 1. Using a local file :
+
+ rails <application name> -m /path/to/my/template.rb
+
+ 2. Or directly from a URL :
+
+ rails <application name> --template=http://gist.github.com/31208.txt
+
* Extracted the process scripts (inspector, reaper, spawner) into the plugin irs_process_scripts [DHH]
* Changed Rails.root to return a Pathname object (allows for Rails.root.join('app', 'controllers') => "#{RAILS_ROOT}/app/controllers") #1482 [Damian Janowski/?]
View
1 railties/bin/rails
@@ -8,6 +8,7 @@ if %w(--version -v).include? ARGV.first
end
freeze = ARGV.any? { |option| %w(--freeze -f).include?(option) }
+
@greatseth
greatseth added a note Dec 2, 2008

BLOAT WTF BBQ

@mislav
Ruby on Rails member
mislav added a note Dec 2, 2008

OMG you’re increasing Rails’ LOC, nooooo

@amerine
amerine added a note Dec 2, 2008

ReallY? OMG ITS HUGER!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
app_path = ARGV.first
require File.dirname(__FILE__) + '/../lib/rails_generator'
View
3 railties/lib/rails_generator/base.rb
@@ -154,6 +154,9 @@ def destination_path(relative_destination)
File.join(destination_root, relative_destination)
end
+ def after_generate
+ end
+
protected
# Convenience method for generator subclasses to record a manifest.
def record
View
1 railties/lib/rails_generator/commands.rb
@@ -40,6 +40,7 @@ class Base < DelegateClass(Rails::Generator::Base)
# Replay action manifest. RewindBase subclass rewinds manifest.
def invoke!
manifest.replay(self)
+ after_generate
end
def dependency(generator_name, args, runtime_options = {})
View
12 railties/lib/rails_generator/generators/applications/app/app_generator.rb
@@ -1,4 +1,5 @@
require 'rbconfig'
+require File.dirname(__FILE__) + '/template_runner'
require 'digest/md5'
require 'active_support/secure_random'
@@ -37,6 +38,12 @@ def manifest
end
end
+ def after_generate
+ if options[:template]
+ Rails::TemplateRunner.new(@destination_root, options[:template])
+ end
+ end
+
protected
def banner
"Usage: #{$0} /path/to/your/app [options]"
@@ -60,6 +67,11 @@ def add_options!(opt)
opt.on("-f", "--freeze",
"Freeze Rails in vendor/rails from the gems generating the skeleton",
"Default: false") { |v| options[:freeze] = v }
+
+ opt.on("-m", "--template=path", String,
+ "Use an application template that lives at path (can be a filesystem path or URL).",
+ "Default: (none)") { |v| options[:template] = v }
+
end
View
16 railties/lib/rails_generator/generators/applications/app/scm/git.rb
@@ -0,0 +1,16 @@
+module Rails
+ class Git < Scm
+ def self.clone(repos, branch=nil)
+ `git clone #{repos}`
+
+ if branch
+ `cd #{repos.split('/').last}/`
+ `git checkout #{branch}`
+ end
+ end
+
+ def self.run(command)
+ `git #{command}`
+ end
+ end
+end
View
8 railties/lib/rails_generator/generators/applications/app/scm/scm.rb
@@ -0,0 +1,8 @@
+module Rails
+ class Scm
+ private
+ def self.hash_to_parameters(hash)
+ hash.collect { |key, value| "--#{key} #{(value.kind_of?(String) ? value : "")}"}.join(" ")
+ end
+ end
+end
View
7 railties/lib/rails_generator/generators/applications/app/scm/svn.rb
@@ -0,0 +1,7 @@
+module Rails
+ class Svn < Scm
+ def self.checkout(repos, branch = nil)
+ `svn checkout #{repos}/#{branch || "trunk"}`
+ end
+ end
+end
View
376 railties/lib/rails_generator/generators/applications/app/template_runner.rb
@@ -0,0 +1,376 @@
+require File.dirname(__FILE__) + '/scm/scm'
+require File.dirname(__FILE__) + '/scm/git'
+require File.dirname(__FILE__) + '/scm/svn'
+
+require 'open-uri'
+require 'fileutils'
+
+module Rails
+ class TemplateRunner
+ attr_reader :behavior, :description, :root
+
+ def initialize(root, template) # :nodoc:
+ @root = Dir.pwd + "/" + root
+
+ puts "applying template: #{template}"
+
+ load_template(template)
+
+ puts "#{template} applied."
+ end
+
+ def load_template(template)
+ begin
+ code = open(template).read
+ in_root { self.instance_eval(code) }
+ rescue LoadError
+ raise "The template [#{template}] could not be loaded."
+ end
+ end
+
+ # Create a new file in the Rails project folder. Specify the
+ # relative path from RAILS_ROOT. Data is the return value of a block
+ # or a data string.
+ #
+ # ==== Examples
+ #
+ # file("lib/fun_party.rb") do
+ # hostname = ask("What is the virtual hostname I should use?")
+ # "vhost.name = #{hostname}"
+ # end
+ #
+ # file("config/apach.conf", "your apache config")
+ #
+ def file(filename, data = nil, &block)
+ puts "creating file #{filename}"
+ dir, file = [File.dirname(filename), File.basename(filename)]
+
+ inside(dir) do
+ File.open(file, "w") do |f|
+ if block_given?
+ f.write(block.call)
+ else
+ f.write(data)
+ end
+ end
+ end
+ end
+
+ # Install a plugin. You must provide either a Subversion url or Git url.
+ #
+ # ==== Examples
+ #
+ # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git'
+ # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
+ #
+ def plugin(name, options)
+ puts "installing plugin #{name}"
+
+ if options[:git] || options[:svn]
+ in_root do
+ `script/plugin install #{options[:svn] || options[:git]}`
+ end
+ else
+ puts "! no git or svn provided for #{name}. skipping..."
+ end
+ end
+
+ # Adds an entry into config/environment.rb for the supplied gem :
+ #
+ # 1. Provide a git repository URL...
+ #
+ # gem 'will-paginate', :git => 'git://github.com/mislav/will_paginate.git'
+ #
+ # 2. Provide a subversion repository URL...
+ #
+ # gem 'will-paginate', :svn => 'svn://svnhub.com/mislav/will_paginate/trunk'
+ #
+ # 3. Provide a gem name and use your system sources to install and unpack it.
+ #
+ # gem 'ruby-openid'
+ #
+ def gem(name, options = {})
+ puts "adding gem #{name}"
+
+ sentinel = 'Rails::Initializer.run do |config|'
+ gems_code = "config.gem '#{name}'"
+
+ if options.any?
+ opts = options.inject([]) {|result, h| result << [":#{h[0]} => '#{h[1]}'"] }.join(", ")
+ gems_code << ", #{opts}"
+ end
+
+ in_root do
+ gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
+ "#{match}\n #{gems_code}"
+ end
+ end
+ end
+
+ # Run a command in git.
+ #
+ # ==== Examples
+ #
+ # git :init
+ # git :add => "this.file that.rb"
+ # git :add => "onefile.rb", :rm => "badfile.cxx"
+ #
+ def git(command = {})
+ puts "running git #{command}"
+
+ in_root do
+ if command.is_a?(Symbol)
+ Git.run(command.to_s)
+ else
+ command.each do |command, options|
+ Git.run("#{command} #{options}")
+ end
+ end
+ end
+ end
+
+ # Create a new file in the vendor/ directory. Code can be specified
+ # in a block or a data string can be given.
+ #
+ # ==== Examples
+ #
+ # vendor("sekrit.rb") do
+ # sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
+ # "salt = '#{sekrit_salt}'"
+ # end
+ #
+ # vendor("foreign.rb", "# Foreign code is fun")
+ #
+ def vendor(filename, data = nil, &block)
+ puts "vendoring file #{filename}"
+ inside("vendor") do |folder|
+ File.open("#{folder}/#{filename}", "w") do |f|
+ if block_given?
+ f.write(block.call)
+ else
+ f.write(data)
+ end
+ end
+ end
+ end
+
+ # Create a new file in the lib/ directory. Code can be specified
+ # in a block or a data string can be given.
+ #
+ # ==== Examples
+ #
+ # lib("crypto.rb") do
+ # "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
+ # end
+ #
+ # lib("foreign.rb", "# Foreign code is fun")
+ #
+ def lib(filename, data = nil)
+ puts "add lib file #{filename}"
+ inside("lib") do |folder|
+ File.open("#{folder}/#{filename}", "w") do |f|
+ if block_given?
+ f.write(block.call)
+ else
+ f.write(data)
+ end
+ end
+ end
+ end
+
+ # Create a new Rakefile with the provided code (either in a block or a string).
+ #
+ # ==== Examples
+ #
+ # rakefile("bootstrap.rake") do
+ # project = ask("What is the UNIX name of your project?")
+ #
+ # <<-TASK
+ # namespace :#{project} do
+ # task :bootstrap do
+ # puts "i like boots!"
+ # end
+ # end
+ # TASK
+ # end
+ #
+ # rakefile("seed.rake", "puts 'im plantin ur seedz'")
+ #
+ def rakefile(filename, data = nil, &block)
+ puts "adding rakefile #{filename}"
+ inside("lib/tasks") do |folder|
+ File.open("#{folder}/#{filename}", "w") do |f|
+ if block_given?
+ f.write(block.call)
+ else
+ f.write(data)
+ end
+ end
+ end
+ end
+
+ # Create a new initializer with the provided code (either in a block or a string).
+ #
+ # ==== Examples
+ #
+ # initializer("globals.rb") do
+ # data = ""
+ #
+ # ['MY_WORK', 'ADMINS', 'BEST_COMPANY_EVAR'].each do
+ # data << "#{const} = :entp"
+ # end
+ #
+ # data
+ # end
+ #
+ # initializer("api.rb", "API_KEY = '123456'")
+ #
+ def initializer(filename, data = nil, &block)
+ puts "adding initializer #{filename}"
+ inside("config/initializers") do |folder|
+ File.open("#{folder}/#{filename}", "w") do |f|
+ if block_given?
+ f.write(block.call)
+ else
+ f.write(data)
+ end
+ end
+ end
+ end
+
+ # Generate something using a generator from Rails or a plugin.
+ # The second parameter is the argument string that is passed to
+ # the generator or an Array that is joined.
+ #
+ # ==== Example
+ #
+ # generate(:authenticated, "user session")
+ #
+ def generate(what, args = nil)
+ puts "generating #{what}"
+ args = args.join(" ") if args.class == Array
+
+ in_root { `#{root}/script/generate #{what} #{args}` }
+ end
+
+ # Executes a command
+ #
+ # ==== Example
+ #
+ # inside('vendor') do
+ # run('ln -s ~/edge rails)
+ # end
+ #
+ def run(command)
+ puts "executing #{command} from #{Dir.pwd}"
+ `#{command}`
+ end
+
+ # Runs the supplied rake task
+ #
+ # ==== Example
+ #
+ # rake("db:migrate")
+ # rake("db:migrate", "production")
+ #
+ def rake(command, env = 'development')
+ puts "running rake task #{command}"
+ in_root { `rake #{command} RAILS_ENV=#{env}` }
+ end
+
+ # Just run the capify command in root
+ #
+ # ==== Example
+ #
+ # capify!
+ #
+ def capify!
+ in_root { `capify .` }
+ end
+
+ # Add Rails to /vendor/rails
+ #
+ # ==== Example
+ #
+ # freeze!
+ #
+ def freeze!(args = {})
+ puts "vendoring rails edge"
+ in_root { `rake rails:freeze:edge` }
+ end
+
+ # Make an entry in Rails routing file conifg/routes.rb
+ #
+ # === Example
+ #
+ # route "map.root :controller => :welcome"
+ #
+ def route(routing_code)
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
+
+ in_root do
+ gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
+ "#{match}\n #{routing_code}\n"
+ end
+ end
+ end
+
+ protected
+
+ # Get a user's input
+ #
+ # ==== Example
+ #
+ # answer = ask("Should I freeze the latest Rails?")
+ # freeze! if ask("Should I freeze the latest Rails?") == "yes"
+ #
+ def ask(string)
+ puts string
+ gets.strip
+ end
+
+ # Do something in the root of the Rails application or
+ # a provided subfolder; the full path is yielded to the block you provide.
+ # The path is set back to the previous path when the method exits.
+ def inside(dir = '', &block)
+ folder = File.join(root, dir)
+ FileUtils.mkdir_p(folder) unless File.exist?(folder)
+ FileUtils.cd(folder) { block.arity == 1 ? yield(folder) : yield }
+ end
+
+ def in_root
+ FileUtils.cd(root) { yield }
+ end
+
+ # Helper to test if the user says yes(y)?
+ #
+ # ==== Example
+ #
+ # freeze! if yes?("Should I freeze the latest Rails?")
+ #
+ def yes?(question)
+ answer = ask(question).downcase
+ answer == "y" || answer == "yes"
+ end
+
+ # Helper to test if the user does NOT say yes(y)?
+ #
+ # ==== Example
+ #
+ # capify! if no?("Will you be using vlad to deploy your application?")
+ #
+ def no?(question)
+ !yes?(question)
+ end
+
+ def gsub_file(relative_destination, regexp, *args, &block)
+ path = destination_path(relative_destination)
+ content = File.read(path).gsub(regexp, *args, &block)
+ File.open(path, 'wb') { |file| file.write(content) }
+ end
+
+ def destination_path(relative_destination)
+ File.join(root, relative_destination)
+ end
+ end
+end

8 comments on commit e8cc4b1

@matthewrudy

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

Massiveness.

@chrisk
chrisk commented on e8cc4b1 Dec 2, 2008

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

@Aupajo
Aupajo commented on e8cc4b1 Dec 3, 2008
vendor("borrowed.rb", <<CODE
  ..snip..
+ CODE

Missing right-paren?

@jm
Ruby on Rails member
jm commented on e8cc4b1 Dec 3, 2008

Oopsie.

@bumi
bumi commented on e8cc4b1 Dec 3, 2008

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 ;)

@samgranieri

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

@NZKoz
Ruby on Rails member
NZKoz commented on e8cc4b1 Dec 3, 2008

@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.

@listrophy

@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.