Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 530 lines (461 sloc) 19.971 kb
5f4149e @ffmike Cleanup and reorganization
authored
1 require 'open-uri'
2 require 'yaml'
3
d18f6a1 @ffmike Separate the template from the template framework
authored
4 module Rails
5 class TemplateRunner
5f4149e @ffmike Cleanup and reorganization
authored
6
7 # Logging
d18f6a1 @ffmike Separate the template from the template framework
authored
8 # Turn on for noisy logging during template generation
6f78ddf @ffmike Turn off debug logging ...again...
authored
9 DEBUG_LOGGING = false
d18f6a1 @ffmike Separate the template from the template framework
authored
10
5f4149e @ffmike Cleanup and reorganization
authored
11 def debug_log(msg)
12 if DEBUG_LOGGING
13 log msg
14 end
15 end
16
17 # Accessors and Initialization
18 def current_app_name
19 current_app_name = File.basename(File.expand_path(root))
20 end
21
c655fac @ffmike Introduce template_identifier
authored
22 # generic accessors for any template
292efe7 @ffmike Expose gems and plugins hashes from the framework to individual templ…
authored
23 attr_accessor :template_paths, :template_options, :template_identifier, :gems, :plugins
c655fac @ffmike Introduce template_identifier
authored
24 # specific accessors for current template
5f4149e @ffmike Cleanup and reorganization
authored
25 # TODO: This list should be data driven
26 attr_accessor :rails_branch, :database, :exception_handling, :monitoring, :branch_management, :rails_strategy, :link_rails_root,
27 :ie6_blocking, :javascript_library, :template_engine, :compass_css_framework, :design, :require_activation,
28 :mocking, :smtp_address, :smtp_domain, :smtp_username, :smtp_password, :capistrano_user, :capistrano_repo_host, :capistrano_production_host,
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
29 :capistrano_staging_host, :exceptional_api_key, :hoptoad_api_key, :newrelic_api_key, :notifier_email_from, :default_url_options_host,
a2c5012 @ffmike ActionMailer initialization updates
authored
30 :controller_type, :branches, :post_creation, :github_username, :github_token, :github_public, :admin_data_user_id, :admin_data_password,
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
31 :mail_in_development, :cap_gun_address, :cap_gun_port, :cap_gun_user_name, :cap_gun_password, :cap_gun_recipients
32
5f4149e @ffmike Cleanup and reorganization
authored
33 def add_template_path(path, placement = :prepend)
34 if placement == :prepend
35 @template_paths.unshift path
36 elsif placement == :append
37 @template_paths.push path
38 end
39 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
40
5f4149e @ffmike Cleanup and reorganization
authored
41 # TODO: List of attributes should be data driven
42 def init_template_framework(template, root)
43 @template_paths = [File.expand_path(File.dirname(template), File.join(root,'..'))]
9abafdf @ffmike Move and rename the template file
authored
44 @template_paths << File.join(File.expand_path(File.dirname(template), File.join(root,'..')), '../..')
c655fac @ffmike Introduce template_identifier
authored
45 @template_identifier = 'default'
5f4149e @ffmike Cleanup and reorganization
authored
46 end
47
c655fac @ffmike Introduce template_identifier
authored
48 def set_template_identifier(identifier)
49 @template_identifier = identifier
50 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
51
5f4149e @ffmike Cleanup and reorganization
authored
52 def load_options
53 # Option set-up
54 @template_options = load_template_config_file('config.yml')
55
56 @rails_branch = template_options["rails_branch"]
57 @rails_branch = "2-3-stable" if @rails_branch.nil?
58
59 @database = template_options["database"].nil? ? ask("Which database? postgresql (default), mysql, sqlite").downcase : template_options["database"]
60 @database = "postgresql" if @database.nil?
61
62 @exception_handling = template_options["exception_handling"].nil? ? ask("Which exception reporting? exceptional (default), hoptoad").downcase : template_options["exception_handling"]
63 @exception_handling = "exceptional" if @exception_handling.nil?
64
65 @monitoring = template_options["monitoring"].nil? ? ask("Which monitoring? new_relic (default), scout").downcase : template_options["monitoring"]
66 @monitoring = "new_relic" if @monitoring.nil?
67
68 @branch_management = template_options["branch_management"].nil? ? ask("Which branch management? piston (default), braid, git, none").downcase : template_options["branch_management"]
69 @branch_management = "piston" if @branch_management.nil?
70
71 @rails_strategy = template_options["rails_strategy"].nil? ? ask("Which Rails strategy? vendored (default), gem").downcase : template_options["rails_strategy"]
72 @rails_strategy = "vendored" if @rails_strategy.nil?
73
74 @link_rails_root = template_options["link_rails_root"]
75 @link_rails_root = "~/rails" if @link_rails_root.nil?
76
77 @ie6_blocking = template_options["ie6_blocking"].nil? ? ask("Which IE 6 blocking? none, light (default), ie6nomore").downcase : template_options["ie6_blocking"]
78 @ie6_blocking = "light" if @ie6_blocking.nil?
79
80 @javascript_library = template_options["javascript_library"].nil? ? ask("Which javascript library? prototype (default), jquery").downcase : template_options["javascript_library"]
81 @javascript_library = "prototype" if @javascript_library.nil?
82
83 @template_engine = template_options["template_engine"].nil? ? ask("Which template engine? erb (default), haml").downcase : template_options["template_engine"]
84 @template_engine = "erb" if @template_engine.nil?
85
86 @compass_css_framework = template_options["compass_css_framework"]
87 @compass_css_framework = "blueprint" if @compass_css_framework.nil?
88
89 @design = template_options["design"].nil? ? ask("Which design? none (default), bluetrip, compass").downcase : template_options["design"]
90 @design = "none" if @design.nil?
91
92 @require_activation = (template_options["require_activation"].to_s == "true")
93
94 @mocking = template_options["mocking"].nil? ? ask("Which mocking library? rr, mocha (default)").downcase : template_options["mocking"]
95 @mocking = "mocha" if @mocking.nil?
96
618fb8b @ffmike Make inherited_resources optional rather than default
authored
97 @controller_type = template_options["controller_type"].nil? ? ask("Which controller strategy? rails (default), inherited_resources").downcase : template_options["controller_type"]
98 @controller_type = "default" if @controller_type.nil? || @controller_type == 'rails'
99
a2c5012 @ffmike ActionMailer initialization updates
authored
100 @mail_in_development = template_options["mail_in_development"].nil? ? ask("Which development mail trap? inaction_mailer (default), mock_smtp").downcase : template_options["mail_in_development"]
101 @mail_in_development = "inaction_mailer" if @mail_in_development.nil?
102
6353745 @ffmike Add post-creation hook to build GitHub repository. See config.yml for…
authored
103 @github_username = template_options["github_username"]
104 @github_token = template_options["github_token"]
105 @github_public = template_options["github_public"]
5f4149e @ffmike Cleanup and reorganization
authored
106 @smtp_address = template_options["smtp_address"]
107 @smtp_domain = template_options["smtp_domain"]
108 @smtp_username = template_options["smtp_username"]
109 @smtp_password = template_options["smtp_password"]
110 @capistrano_user = template_options["capistrano_user"]
111 @capistrano_repo_host = template_options["capistrano_repo_host"]
112 @capistrano_production_host = template_options["capistrano_production_host"]
113 @capistrano_staging_host = template_options["capistrano_staging_host"]
114 @exceptional_api_key = template_options["exceptional_api_key"]
115 @hoptoad_api_key = template_options["hoptoad_api_key"]
116 @newrelic_api_key = template_options["newrelic_api_key"]
117 @notifier_email_from = template_options["notifier_email_from"]
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
118 @default_url_options_host = template_options["default_url_options_host"]
147f387 @ffmike Adjust admin_data initializer to match current plugin
authored
119 @admin_data_user_id = template_options["admin_data_user_id"]
120 @admin_data_password = template_options["admin_data_password"]
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
121 @cap_gun_address = template_options["cap_gun_address"]
122 @cap_gun_port = template_options["cap_gun_port"]
123 @cap_gun_user_name = template_options["cap_gun_user_name"]
124 @cap_gun_password = template_options["cap_gun_password"]
125 @cap_gun_recipients = template_options["cap_gun_recipients"]
5f4149e @ffmike Cleanup and reorganization
authored
126
5e8d536 @ffmike Move branches setting to template framework, add post-creation setting
authored
127 @branches = template_options["git_branches"]
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
128
5e8d536 @ffmike Move branches setting to template framework, add post-creation setting
authored
129 @post_creation = template_options["post_creation"]
5f4149e @ffmike Cleanup and reorganization
authored
130 end
131
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
132 # File Management
d18f6a1 @ffmike Separate the template from the template framework
authored
133 def download(from, to = from.split("/").last)
134 #run "curl -s -L #{from} > #{to}"
135 file to, open(from).read
136 rescue
137 puts "Can't get #{from} - Internet down?"
138 exit!
139 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
140
d18f6a1 @ffmike Separate the template from the template framework
authored
141 # grab an arbitrary file from github
142 def file_from_repo(github_user, repo, sha, filename, to = filename)
143 download("http://github.com/#{github_user}/#{repo}/raw/#{sha}/#{filename}", to)
144 end
145
146 def load_from_file_in_template(file_name, parent_binding = nil, file_group = 'default', file_type = :pattern)
147 base_name = file_name.gsub(/^\./, '')
148 begin
149 if file_type == :config
150 contents = {}
151 else
152 contents = ''
153 end
154 paths = template_paths
155
156 paths.each do |template_path|
157 full_file_name = File.join(template_path, file_type.to_s.pluralize, file_group, base_name)
158 debug_log "Searching for #{full_file_name} ... "
159
160 next unless File.exists? full_file_name
161 debug_log "Found!"
162
163 if file_type == :config
164 contents = open(full_file_name) { |f| YAML.load(f) }
165 else
166 contents = open(full_file_name) { |f| f.read }
167 end
168 if contents && parent_binding
169 contents = eval("\"" + contents.gsub('"','\\"') + "\"", parent_binding)
170 end
171 # file loaded, stop searching
172 break if contents
173
174 end
175 contents
618fb8b @ffmike Make inherited_resources optional rather than default
authored
176 rescue => ex
d18f6a1 @ffmike Separate the template from the template framework
authored
177 debug_log "Error in load_from_file_in_template #{file_name}"
618fb8b @ffmike Make inherited_resources optional rather than default
authored
178 debug_log ex.message
d18f6a1 @ffmike Separate the template from the template framework
authored
179 end
180 end
181
182 # Load a snippet from a file
a2c5012 @ffmike ActionMailer initialization updates
authored
183 def load_snippet(snippet_name, snippet_group = "default", parent_binding = nil)
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
184 load_from_file_in_template(snippet_name, parent_binding, snippet_group, :snippet)
d18f6a1 @ffmike Separate the template from the template framework
authored
185 end
186
187 # Load a pattern from a file, potentially with string interpolation
188 def load_pattern(pattern_name, pattern_group = "default", parent_binding = nil)
189 load_from_file_in_template(pattern_name, parent_binding, pattern_group, :pattern)
190 end
191
192 # YAML.load a configuration from a file
c655fac @ffmike Introduce template_identifier
authored
193 def load_template_config_file(config_file_name, config_file_group = template_identifier)
d18f6a1 @ffmike Separate the template from the template framework
authored
194 load_from_file_in_template(config_file_name, nil, config_file_group, :config )
195 end
196
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
197 # SCM and Branch Management
5f4149e @ffmike Cleanup and reorganization
authored
198 def commit_state(comment)
199 git :add => "."
200 git :commit => "-am '#{comment}'"
201 end
202
d18f6a1 @ffmike Separate the template from the template framework
authored
203 # sudo gem install piston on your dev box before using these
204 # Piston locking support with git requires Piston 2.0.3+
205 # Piston branch management with git 1.6.3 requires Piston 2.0.5+
206
207 # Use Piston to install and lock a plugin:
208 # piston_plugin 'stuff', :git => 'git://github.com/whoever/stuff.git'
209 # Use Piston to install a plugin without locking:
210 # piston_plugin 'stuff', :git => 'git://github.com/whoever/stuff.git', :lock => false
211 def piston_plugin(name, options={})
212 lock = options.fetch(:lock, true)
213
214 if options[:git] || options[:svn]
215 in_root do
216 run("piston import #{options[:svn] || options[:git]} vendor/plugins/#{name}")
217 run("piston lock vendor/plugins/#{name}") if lock
218 commit_state("Added pistoned #{name}")
219 end
220 log "plugin installed #{'and locked ' if lock}with Piston:", name
221 else
222 log "! no git or svn provided for #{name}. skipping..."
223 end
224 end
225
226 # Use Piston to install and lock current Rails edge (master):
227 # piston_rails
228 # Use Piston to install but not lock current Rails edge (master):
229 # piston_rails :lock => false
230 # Use Piston to install and lock edge of a specific Rails branch:
231 # piston_rails :branch => "2-3-stable"
232 # Use Piston to install but not lock edge of a specific Rails branch:
233 # piston_rails, :branch => "2-3-stable", :lock => false
234 def piston_rails(options={})
235 lock = options.fetch(:lock, true)
236
237 if options[:branch]
238 in_root do
239 run("piston import --commit #{options[:branch]} git://github.com/rails/rails.git vendor/rails")
240 commit_state("Added pistoned Rails using the edge of the #{options[:branch]} branch")
241 if lock
242 run("piston lock vendor/rails")
243 commit_state("Locked pistoned rails")
244 end
245 end
246 else
247 in_root do
248 run("piston import git://github.com/rails/rails.git vendor/rails")
249 commit_state("Added pistoned Rails edge")
250 if lock
251 run("piston lock vendor/rails")
252 commit_state("Locked pistoned rails")
253 end
254 end
255 end
256
257 log "rails installed #{'and locked ' if lock}with Piston", options[:branch]
258 end
259
260 # braid support is experimental and largely untested
261 def braid_plugin(name, options={})
262 if options[:git]
263 in_root do
264 run("braid add -p #{options[:git]}")
265 commit_state("Added braided #{name}")
266 end
267 log "plugin installed with Braid:", name
268 else
269 log "! no git provided for #{name}. skipping..."
270 end
271 end
272
273 def braid_rails(options={})
274 if options[:branch]
275 log "! branch support for Braid is not yet implemented"
276 else
277 in_root do
278 run("braid add git://github.com/rails/rails.git vendor/rails")
279 log "rails installed with Braid"
280 end
281 end
282 end
283
284 # cloning rails is experimental and somewhat untested
285 def clone_rails(options={})
286 if options[:submodule]
287 in_root do
288 if options[:branch] && options[:branch] != "master"
289 git :submodule => "add git://github.com/rails/rails.git vendor/rails -b #{options[:branch]}"
290 else
291 git :submodule => "add git://github.com/rails/rails.git vendor/rails"
292 end
293 end
294 else
295 inside 'vendor' do
296 run('git clone git://github.com/rails/rails.git')
297 end
298 if options[:branch] && options[:branch] != "master"
299 inside 'vendor/rails' do
300 run("git branch --track #{options[:branch]} origin/#{options[:branch]}")
301 run("git checkout #{options[:branch]}")
302 end
303 end
304 end
305
306 log "rails installed #{'and submoduled ' if options[:submodule]}from GitHub", options[:branch]
307 end
308
496a6ba @ffmike Move a couple of things from the Lark template to the template framework
authored
309 # setup the specified branches in the git repo
310 def git_branch_setup
311 if !branches.nil?
312 default_branch = "master"
313 branches.each do |name, default|
314 if name != "master"
315 git :branch => name
316 default_branch = name if !default.nil?
317 end
318 end
319 git :checkout => default_branch if default_branch != "master"
320 log "set up branches #{branches.keys.join(', ')}"
321 end
322 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
323
5f4149e @ffmike Cleanup and reorganization
authored
324 # Rails Management
325
d18f6a1 @ffmike Separate the template from the template framework
authored
326 # update rails bits in application after vendoring a new copy of rails
327 # we need to do this the hard way because we want to overwrite without warning
328 # TODO: Can we introspect the actual rake:update task to get a current list of subtasks?
329 def update_app
330 in_root do
331 run("echo 'a' | rake rails:update:scripts")
332 run("echo 'a' | rake rails:update:javascripts")
333 run("echo 'a' | rake rails:update:configs")
334 run("echo 'a' | rake rails:update:application_controller")
335 end
336 end
337
5f4149e @ffmike Cleanup and reorganization
authored
338 # remove the prototype framework
339 def remove_prototype
340 run "rm public/javascripts/controls.js"
341 run "rm public/javascripts/dragdrop.js"
342 run "rm public/javascripts/effects.js"
343 run "rm public/javascripts/prototype.js"
d18f6a1 @ffmike Separate the template from the template framework
authored
344 end
345
346 def install_plugin (name, options)
347 case @branch_management
348 when 'none'
349 plugin name, options
350 when 'piston'
351 piston_plugin name, options
352 when 'braid'
353 braid_plugin name, options
354 when 'git'
355 plugin name, options.merge(:submodule => true)
356 end
357 end
358
359 def install_rails (options)
360 case @branch_management
361 when 'none'
362 clone_rails options
363 when 'piston'
364 piston_rails options
365 when 'braid'
366 braid_rails options
367 when 'git'
368 clone_rails options.merge(:submodule => true)
369 end
370 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
371
5f4149e @ffmike Cleanup and reorganization
authored
372 # Mocking generators
d18f6a1 @ffmike Separate the template from the template framework
authored
373
374 def generate_stub(object_name, method_name, return_value)
375 if @mocking == "rr"
376 "stub(#{object_name}).#{method_name}{ #{return_value} }"
377 elsif @mocking == "mocha"
378 "#{object_name}.stubs(:#{method_name}).returns(#{return_value})"
379 end
380 end
381
382 def generate_any_instance_stub(object_name, method_name, return_value)
383 if @mocking == "rr"
384 "stub.instance_of(#{object_name}).#{method_name}{ #{return_value} }"
385 elsif @mocking == "mocha"
386 "#{object_name}.any_instance.stubs(:#{method_name}).returns(#{return_value})"
387 end
388 end
389
390 def generate_expectation(object_name, method_name, parameter = nil)
391 if parameter
392 if @mocking == "rr"
393 "mock(#{object_name}).#{method_name}(#{parameter})"
394 elsif @mocking == "mocha"
395 "#{object_name}.expects(:#{method_name}).with(#{parameter})"
396 end
397 else
398 if @mocking == "rr"
399 "mock(#{object_name}).#{method_name}"
400 elsif @mocking == "mocha"
401 "#{object_name}.expects(:#{method_name})"
402 end
403 end
404 end
405
406 def generate_pure_stub(stub_name)
407 if @mocking == "rr"
408 "stub!('#{stub_name}')"
409 elsif @mocking == "mocha"
410 "stub('#{stub_name}')"
411 end
412 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
413
500fa93 @ffmike One-step Heroku app creation
authored
414 # Heroku management
496a6ba @ffmike Move a couple of things from the Lark template to the template framework
authored
415
416 # Run a command with the Heroku gem.
417 #
418 # ==== Examples
419 #
420 # heroku :create
421 # heroku :rake => "db:migrate"
422 #
423 def heroku(command = {})
424 in_root do
425 if command.is_a?(Symbol)
426 log 'running', "heroku #{command}"
427 run "heroku #{command}"
428 else
429 command.each do |command, options|
430 log 'running', "heroku #{command} #{options}"
431 run("heroku #{command} #{options}")
432 end
433 end
500fa93 @ffmike One-step Heroku app creation
authored
434 end
435 end
496a6ba @ffmike Move a couple of things from the Lark template to the template framework
authored
436
437 # post-creation hooks
438 def execute_post_creation_hooks
439 if !post_creation.nil?
440 post_creation.each do |name, options|
441 if name == 'heroku'
442 git :checkout => "master"
443 rake "gems:specify", :env => "production"
444 commit_state "added gem manifest"
445 heroku :create
446 git :push => "heroku master"
447 heroku :rake => "db:migrate"
448 heroku :restart
449 heroku :open
450 log "set up application at Heroku"
451 end
452 if name == 'github'
453 run "curl -F 'login=#{github_username}' -F 'token=#{github_token}' -F 'name=#{current_app_name}' -F 'public=#{github_public}' http://github.com/api/v2/json/repos/create"
454 git :remote => "add origin git@github.com:#{github_username}/#{current_app_name}.git"
455 git :push => "origin master"
456 if !branches.nil?
457 default_branch = "master"
458 branches.each do |name, default|
459 if name != "master"
460 git :push => "origin #{name}"
461 default_branch = name if !default.nil?
462 end
463 end
464 git :checkout => default_branch if default_branch != "master"
465 end
466 log "set up application at GitHub"
467 end
468 end
469 end
470 end
eaac9f0 @ffmike Move gem handling from template to framework.
authored
471
472 # Gem management
473 def install_gems
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
474 @gems = load_template_config_file('gems.yml')
292efe7 @ffmike Expose gems and plugins hashes from the framework to individual templ…
authored
475 install_on_current(@gems)
476 add_to_project(@gems)
eaac9f0 @ffmike Move gem handling from template to framework.
authored
477 end
478
479 # If the geminstaller gem is present, use to to bootstrap the other
480 # needed gems on to the dev box so that rake succeeds
481 def install_on_current(gems)
482 begin
483 require 'geminstaller'
484 # Transform the gem array to the form that geminstaller wants to see
485 gem_array = []
486 gems.each do |name, value|
487 if value[:if].nil? || eval(value[:if])
488 h = Hash.new
489 h["name"] = name
490 if value[:options] && value[:options][:version]
491 h["version"] = value[:options][:version]
492 end
493 gem_array.push h
494 end
495 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
496
497 if !gem_array.empty?
eaac9f0 @ffmike Move gem handling from template to framework.
authored
498 geminstaller_hash = {"defaults"=>{"install_options"=>"--no-ri --no-rdoc"}, "gems"=> gem_array}
499 in_root do
500 File.open( 'geminstaller.yml', 'w' ) do |out|
501 YAML.dump( geminstaller_hash, out )
502 end
503 run 'geminstaller'
504 log "installed gems on current machine"
505 end
506 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
507
eaac9f0 @ffmike Move gem handling from template to framework.
authored
508 rescue LoadError
509 end
510 end
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
511
eaac9f0 @ffmike Move gem handling from template to framework.
authored
512 def add_to_project(gems)
513 gems.each do |name, value|
514 if value[:if].nil? || eval(value[:if])
515 gem name, value[:options]
516 end
517 end
518 end
e46497f @ffmike Move plugin load from template to framework
authored
519
520 #Plugin management
521 def install_plugins
c7ecfda @ffmike Add cap_gun for deployment notifications
authored
522 @plugins = load_template_config_file('plugins.yml')
292efe7 @ffmike Expose gems and plugins hashes from the framework to individual templ…
authored
523 @plugins.each do |name, value|
e46497f @ffmike Move plugin load from template to framework
authored
524 if value[:if].nil? || eval(value[:if])
525 install_plugin name, value[:options]
526 end
527 end
528 end
d18f6a1 @ffmike Separate the template from the template framework
authored
529 end
530 end
Something went wrong with that request. Please try again.