Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit e8cc4b116c460c524961a07da92da3f323854c15 1 parent 2014d91
Jeremy McAnally jm authored lifo committed
43 railties/CHANGELOG
View
@@ -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/?]
1  railties/bin/rails
View
@@ -8,6 +8,7 @@ if %w(--version -v).include? ARGV.first
end
freeze = ARGV.any? { |option| %w(--freeze -f).include?(option) }
+

BLOAT WTF BBQ

Mislav Marohnić
mislav added a note

OMG you’re increasing Rails’ LOC, nooooo

Mark Turner
amerine added a note

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'
3  railties/lib/rails_generator/base.rb
View
@@ -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
1  railties/lib/rails_generator/commands.rb
View
@@ -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 = {})
12 railties/lib/rails_generator/generators/applications/app/app_generator.rb
View
@@ -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
16 railties/lib/rails_generator/generators/applications/app/scm/git.rb
View
@@ -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
8 railties/lib/rails_generator/generators/applications/app/scm/scm.rb
View
@@ -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
7 railties/lib/rails_generator/generators/applications/app/scm/svn.rb
View
@@ -0,0 +1,7 @@
+module Rails
+ class Svn < Scm
+ def self.checkout(repos, branch = nil)
+ `svn checkout #{repos}/#{branch || "trunk"}`
+ end
+ end
+end
376 railties/lib/rails_generator/generators/applications/app/template_runner.rb
View
@@ -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

Seth Thomas Rasmussen

BLOAT WTF BBQ

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
jm commented on e8cc4b1

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.