, ["~> 0.11.6"])
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/bin/beet:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'rubygems'
4 | require 'thor'
5 | begin
6 | if RUBY_VERSION <= '1.8.6'
7 | require 'ruby-debug'
8 | end
9 | rescue LoadError
10 | end
11 | $:.unshift(File.dirname(__FILE__) + '/../lib')
12 | require 'beet'
13 | require 'pp'
14 |
15 | WIN32 = (RUBY_PLATFORM =~ /win32|mingw|bccwin|cygwin/) rescue nil
16 | SUDO = (WIN32 || ENV['SUDOLESS']) ? '': 'sudo '
17 |
18 | class BeetRunner < Thor
19 | map "-g" => :generate
20 | map "-j" => :just_recipe
21 | map "-h" => :help
22 | map "-v" => :version
23 | map "-l" => :list
24 | map "-d" => :display
25 | map "--list" => :list
26 | map "--display" => :display
27 |
28 | desc 'generate [app_name]', "the main app generate method"
29 | method_options %w(recipes -r) => :string, %w(gems) => :string, %w(template -t) => :string, %w(save -s) => :string, %w(use -u) => :string
30 | def generate(app_name, project_type=:rails)
31 | executor = Beet::Executor.new(app_name, options.merge('project_type' => project_type))
32 | executor.start
33 | end
34 |
35 | desc 'just_recipe', "when you just need a recipe"
36 | method_options %w(recipes -r) => :string, %w(gems) => :string, %w(save -s) => :string, %w(use -u) => :string
37 | def just_recipe(app_name='.')
38 | executor = Beet::Executor.new(app_name, options.merge('generate' => false))
39 | executor.start
40 | end
41 |
42 | desc 'list', "list what recipes and templates beet knows about"
43 | def list
44 | paths = []
45 | paths << File.dirname(__FILE__) + '/../lib/beet/recipes/**/*.rb'
46 | paths << ENV['BEET_RECIPES_DIR'] if ENV['BEET_RECIPES_DIR']
47 | recipes = paths.map do |path|
48 | Dir.glob(path)
49 | end.flatten.compact
50 | puts "\nRecipes: (e.g. beet -g app -r rails/git)"
51 | pp recipes
52 | puts "\nTemplates: (e.g. beet -g app -t bort)"
53 | pp TEMPLATE_LOCATIONS.sort.map {|array| array[0] + " => " + array[1]}
54 | end
55 |
56 | desc 'display', "Display the code for the recipes/templates"
57 | method_options %w(recipes -r) => :string, %w(template -t) => :string
58 | def display(app_name='.')
59 | executor = Beet::Executor.new(app_name,options.merge('generate' => false, 'display' => true))
60 | executor.start
61 | end
62 |
63 | desc 'version', "the current version of beet"
64 | def version
65 | version_file = File.dirname(__FILE__) + '/../VERSION'
66 | if File.exists?(version_file) and version = File.read(version_file)
67 | puts "Beet version: #{version}"
68 | end
69 | end
70 |
71 | desc 'help', 'help output'
72 | def help
73 | puts %{
74 | Usage: #{$0} /path/to/your/app [options]
75 |
76 | Options:
77 | -g, --generate Run the generate command to build a project
78 | -j, --just_recipe Run the just_recipe command to only run specified recipes, templates, etc.
79 | -d, --display Instead of running, show the template or recipe body
80 | -r, --recipes Recipes to use
81 | -s, --save Save current set of options
82 | -u, --use Use a saved set of options
83 | --gems Gems to include
84 |
85 | Beet Info:
86 | -v, --version Show the Beet version number and quit.
87 | -l, --list Show the various recipes known to Beet and quit.
88 | -h, --help Show this help message and quit.
89 |
90 | General Options:
91 |
92 | Description:
93 | Beet is used to quickly generate applications.
94 |
95 | Example:
96 | beet generate example_app --recipes="rails/authlogic, rails/clean_files, rails/git"
97 |
98 | Same thing but shorter:
99 |
100 | beet -g example_app -r=rails/authlogic,rails/clean_files,rails/git
101 | }
102 | end
103 | end
104 | def method_missing(*args)
105 | unless @activesupport_required
106 | require 'activesupport'
107 | @activesupport_required = true
108 | m = args.shift
109 | send(m, *args)
110 | else
111 | super
112 | end
113 | end
114 |
115 | BeetRunner.start
116 |
117 |
--------------------------------------------------------------------------------
/lib/beet/gem_location_map.rb:
--------------------------------------------------------------------------------
1 | GEM_LOCATIONS = {
2 | 'restful-authentcation' => 'git://github.com/technoweenie/restful-authentication.git',
3 | 'authlogic' => 'git://github.com/binarylogic/authlogic.git',
4 | 'clearance' => 'git://github.com/thoughtbot/clearance.git',
5 | 'will_paginate' => 'git://github.com/mislav/will_paginate.git',
6 | 'paperclip' => 'git://github.com/thoughtbot/paperclip.git',
7 | 'attachment_fu' => 'git://github.com/technoweenie/attachment_fu.git',
8 | 'rspec' => 'git://github.com/dchelimsky/rspec.git',
9 | 'rspec-rails' => 'git://github.com/dchelimsky/rspec-rails.git',
10 | 'cucumber' => 'git://github.com/aslakhellesoy/cucumber.git',
11 | 'shoulda' => 'git://github.com/thoughtbot/shoulda.git',
12 | 'matchy' => 'git://github.com/jeremymcanally/matchy.git',
13 | 'context' => 'git://github.com/jeremymcanally/context.git',
14 | 'exception-notifier' => 'git://github.com/Lipsiasoft/exception-notifier.git',
15 | 'asset_packager' => 'git://github.com/sbecker/asset_packager.git',
16 | 'acts_as_list' => 'git://github.com/rails/acts_as_list.git',
17 | 'acts_as_tree' => 'git://github.com/rails/acts_as_tree.git',
18 | 'webrat' => 'git://github.com/brynary/webrat.git',
19 | 'rcov' => 'git://github.com/relevance/rcov.git',
20 | 'rcov_plugin' => 'git://github.com/commondream/rcov_plugin.git',
21 | 'flog' => 'git://github.com/seattlerb/flog.git',
22 | 'flay' => 'git://github.com/seattlerb/flay.git',
23 | 'hoe' => 'git://github.com/seattlerb/hoe.git',
24 | 'integrity' => 'git://github.com/integrity/integrity.git',
25 | 'active_merchant' => 'git://github.com/Shopify/active_merchant.git',
26 | 'spree' => 'git://github.com/railsdog/spree.git',
27 | 'exception_notification' => 'git://github.com/rails/exception_notification.git',
28 | 'hoptoad_notifier' => 'git://github.com/thoughtbot/hoptoad_notifier.git',
29 | 'exception_logger' => 'git://github.com/defunkt/exception_logger.git',
30 | 'jeweler' => 'git://github.com/technicalpickles/jeweler.git',
31 | 'newgem' => 'git://github.com/drnic/newgem.git',
32 | 'bones' => 'git://github.com/TwP/bones.git',
33 | 'echoe' => 'git://github.com/fauna/echoe.git',
34 | 'nokogiri' => 'git://github.com/tenderlove/nokogiri.git',
35 | 'hpricot' => 'git://github.com/why/hpricot.git',
36 | 'httparty' => 'git://github.com/jnunemaker/httparty.git',
37 | 'rest-client' => 'git://github.com/adamwiggins/rest-client.git',
38 | 'typoeus' => 'git://github.com/pauldix/typhoeus.git',
39 | 'redcloth' => 'git://github.com/jgarber/redcloth.git',
40 | 'rdiscount' => 'git://github.com/rtomayko/rdiscount.git',
41 | 'bluecloth' => 'git://github.com/mislav/bluecloth.git',
42 | 'rr' => 'git://github.com/btakita/rr.git',
43 | 'mocha' => 'git://github.com/floehopper/mocha.git',
44 | 'stump' => 'git://github.com/jeremymcanally/stump.git',
45 | 'active_scaffold' => 'git://github.com/activescaffold/active_scaffold.git',
46 | 'delayed_job' => 'git://github.com/tobi/delayed_job.git',
47 | 'starling' => 'git://github.com/starling/starling.git',
48 | 'amqp' => 'git://github.com/tmm1/amqp.git',
49 | 'negative-captcha' => 'git://github.com/subwindow/negative-captcha.git',
50 | 'factory_girl' => 'git://github.com/thoughtbot/factory_girl.git',
51 | 'machinist' => 'git://github.com/notahat/machinist.git',
52 | 'thinking-sphinx' => 'git://github.com/freelancing-god/thinking-sphinx.git',
53 | 'acts-as-taggable-on' => 'git://github.com/mbleigh/acts-as-taggable-on.git',
54 | 'is_taggable' => 'git://github.com/giraffesoft/is_taggable.git',
55 | 'forgery' => 'git://github.com/sevenwire/forgery.git',
56 | 'faker' => 'git://github.com/yyyc514/faker.git',
57 | 'whenever' => 'git://github.com/javan/whenever.git',
58 | 'aasm' => 'git://github.com/rubyist/aasm.git',
59 | 'workflow' => 'git://github.com/ryan-allen/workflow.git',
60 | 'chef' => 'git://github.com/opscode/chef.git',
61 | 'passenger' => 'git://github.com/FooBarWidget/passenger.git',
62 | 'thin' => 'git://github.com/macournoyer/thin.git',
63 | 'happymapper' => 'git://github.com/jnunemaker/happymapper.git',
64 | 'simple-navigation' => {:source => 'git://github.com/andi/simple-navigation.git', :lib => 'simple_navigation'}
65 | }
66 |
--------------------------------------------------------------------------------
/lib/beet/file_system.rb:
--------------------------------------------------------------------------------
1 | require 'fileutils'
2 | module Beet
3 | module FileSystem
4 | # Create a new file in the project folder. Specify the
5 | # relative path from the project's root. Data is the return value of a block
6 | # or a data string.
7 | #
8 | # ==== Examples
9 | #
10 | # file("lib/fun_party.rb") do
11 | # hostname = ask("What is the virtual hostname I should use?")
12 | # "vhost.name = #{hostname}"
13 | # end
14 | #
15 | # file("config/apach.conf", "your apache config")
16 | #
17 | def file(filename, data = nil, log_action = true, &block)
18 | log 'file', filename if log_action
19 | dir, file = [File.dirname(filename), File.basename(filename)]
20 |
21 | inside(dir) do
22 | File.open(file, "w") do |f|
23 | if block_given?
24 | f.write(block.call)
25 | else
26 | f.write(data)
27 | end
28 | end
29 | end
30 | end
31 |
32 | # Create a new file in the lib/ directory. Code can be specified
33 | # in a block or a data string can be given.
34 | #
35 | # ==== Examples
36 | #
37 | # lib("crypto.rb") do
38 | # "crypted_special_value = '#{rand}--#{Time.now}--#{rand(1337)}--'"
39 | # end
40 | #
41 | # lib("foreign.rb", "# Foreign code is fun")
42 | #
43 | def lib(filename, data = nil, &block)
44 | log 'lib', filename
45 | file("lib/#{filename}", data, false, &block)
46 | end
47 |
48 | # Create a new Rakefile with the provided code (either in a block or a string).
49 | #
50 | # ==== Examples
51 | #
52 | # rakefile("bootstrap.rake") do
53 | # project = ask("What is the UNIX name of your project?")
54 | #
55 | # <<-TASK
56 | # namespace :#{project} do
57 | # task :bootstrap do
58 | # puts "i like boots!"
59 | # end
60 | # end
61 | # TASK
62 | # end
63 | #
64 | # rakefile("seed.rake", "puts 'im plantin ur seedz'")
65 | #
66 | def rakefile(filename, data = nil, &block)
67 | log 'rakefile', filename
68 | file("lib/tasks/#{filename}", data, false, &block)
69 | end
70 |
71 | # Do something in the root of the project or
72 | # a provided subfolder; the full path is yielded to the block you provide.
73 | # The path is set back to the previous path when the method exits.
74 | def inside(dir = '', &block)
75 | folder = File.join(root, dir)
76 | FileUtils.mkdir_p(folder) unless File.exist?(folder)
77 | FileUtils.cd(folder) { block.arity == 1 ? yield(folder) : yield }
78 | end
79 |
80 | def in_root
81 | FileUtils.cd(root) { yield }
82 | end
83 |
84 | # Run a regular expression replacement on a file
85 | #
86 | # ==== Example
87 | #
88 | # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
89 | #
90 | def gsub_file(relative_destination, regexp, *args, &block)
91 | #path = destination_path(relative_destination)
92 | path = relative_destination
93 | content = File.read(path)
94 | check_for = args.first || yield('')
95 | regex = Regexp.new(regexp.source + Regexp.escape(check_for))
96 | return if content =~ regex # if we can match the text and its leadin regex, don't add again
97 | content = content.gsub(regexp, *args, &block)
98 | File.open(path, 'wb') { |file| file.write(content) }
99 | end
100 |
101 | # Append text to a file
102 | #
103 | # ==== Example
104 | #
105 | # append_file 'config/environments/test.rb', 'config.gem "rspec"'
106 | #
107 | def append_file(relative_destination, data)
108 | path = destination_path(relative_destination)
109 | File.open(path, 'ab') { |file| file.write(data) }
110 | end
111 |
112 | # Add text after matching line
113 | #
114 | # ==== Example
115 | #
116 | # add_after 'config/environment.rb', '# config.gem "aws-s3", :lib => "aws/s3"'
117 | #
118 | def add_after(filename, matching_text, data=nil, &block)
119 | gsub_file filename, /(\s*#{Regexp.escape(matching_text)})/mi do |match|
120 | "#{match}\n#{data || block.call}"
121 | end
122 | end
123 |
124 | # Add text before matching line
125 | #
126 | # ==== Example
127 | #
128 | # add_before 'config/environment.rb', '# config.gem "aws-s3", :lib => "aws/s3"'
129 | #
130 | def add_before(filename, matching_text, data=nil, &block)
131 | gsub_file filename, /^(\s*#{Regexp.escape(matching_text)})/mi do |match|
132 | "#{data || block.call}#{match}"
133 | end
134 | end
135 |
136 | protected
137 |
138 | def destination_path(relative_destination)
139 | File.join(root, relative_destination)
140 | end
141 |
142 | end
143 | end
144 |
--------------------------------------------------------------------------------
/lib/beet/rails.rb:
--------------------------------------------------------------------------------
1 | module Beet
2 | module Rails
3 | # Make an entry in Rails routing file conifg/routes.rb
4 | #
5 | # === Example
6 | #
7 | # route "map.root :controller => :welcome"
8 | #
9 | def route(routing_code)
10 | log 'route', routing_code
11 | sentinel = 'ActionController::Routing::Routes.draw do |map|'
12 |
13 | in_root do
14 | gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
15 | "#{match}\n #{routing_code}\n"
16 | end
17 | end
18 | end
19 |
20 | # Add Rails to /vendor/rails
21 | #
22 | # ==== Example
23 | #
24 | # freeze!
25 | #
26 | def freeze!(args = {})
27 | log 'vendor', 'rails edge'
28 | in_root { run('rake rails:freeze:edge', false) }
29 | end
30 |
31 | # Install a plugin. You must provide either a Subversion url or Git url.
32 | # For a Git-hosted plugin, you can specify if it should be added as a submodule instead of cloned.
33 | #
34 | # ==== Examples
35 | #
36 | # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git'
37 | # plugin 'restful-authentication', :git => 'git://github.com/technoweenie/restful-authentication.git', :submodule => true
38 | # plugin 'restful-authentication', :svn => 'svn://svnhub.com/technoweenie/restful-authentication/trunk'
39 | #
40 | def plugin(name, options)
41 | log 'plugin', name
42 |
43 | if options[:git] && options[:submodule]
44 | in_root do
45 | Git.run("submodule add #{options[:git]} vendor/plugins/#{name}")
46 | end
47 | elsif options[:git] || options[:svn]
48 | in_root do
49 | run_ruby_script("script/plugin install #{options[:svn] || options[:git]}", false)
50 | end
51 | else
52 | log "! no git or svn provided for #{name}. skipping..."
53 | end
54 | end
55 |
56 | # Adds an entry into config/environment.rb for the supplied gem :
57 | def gem(name, options = {})
58 | log 'gem', name
59 | env = options.delete(:env)
60 |
61 | gems_code = "config.gem '#{name}'"
62 |
63 | if options.any?
64 | opts = options.inject([]) {|result, h| result << [":#{h[0]} => #{h[1].inspect.gsub('"',"'")}"] }.sort.join(", ")
65 | gems_code << ", #{opts}"
66 | end
67 |
68 | environment gems_code, :env => env
69 | end
70 |
71 | # Adds a line inside the Initializer block for config/environment.rb. Used by #gem
72 | # If options :env is specified, the line is appended to the corresponding
73 | # file in config/environments/#{env}.rb
74 | def environment(data = nil, options = {}, &block)
75 | sentinel = 'Rails::Initializer.run do |config|'
76 |
77 | data = block.call if !data && block_given?
78 |
79 | in_root do
80 | if options[:env].nil?
81 | gsub_file 'config/environment.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
82 | "#{match}\n " << data
83 | end
84 | else
85 | Array.wrap(options[:env]).each do|env|
86 | append_file "config/environments/#{env}.rb", "\n#{data}"
87 | end
88 | end
89 | end
90 | end
91 |
92 | # Create a new file in the vendor/ directory. Code can be specified
93 | # in a block or a data string can be given.
94 | #
95 | # ==== Examples
96 | #
97 | # vendor("sekrit.rb") do
98 | # sekrit_salt = "#{Time.now}--#{3.years.ago}--#{rand}--"
99 | # "salt = '#{sekrit_salt}'"
100 | # end
101 | #
102 | # vendor("foreign.rb", "# Foreign code is fun")
103 | #
104 | def vendor(filename, data = nil, &block)
105 | log 'vendoring', filename
106 | file("vendor/#{filename}", data, false, &block)
107 | end
108 |
109 |
110 | # Create a new initializer with the provided code (either in a block or a string).
111 | #
112 | # ==== Examples
113 | #
114 | # initializer("globals.rb") do
115 | # data = ""
116 | #
117 | # ['MY_WORK', 'ADMINS', 'BEST_COMPANY_EVAR'].each do
118 | # data << "#{const} = :entp"
119 | # end
120 | #
121 | # data
122 | # end
123 | #
124 | # initializer("api.rb", "API_KEY = '123456'")
125 | #
126 | def initializer(filename, data = nil, &block)
127 | log 'initializer', filename
128 | file("config/initializers/#{filename}", data, false, &block)
129 | end
130 |
131 | # Generate something using a generator from Rails or a plugin.
132 | # The second parameter is the argument string that is passed to
133 | # the generator or an Array that is joined.
134 | #
135 | # ==== Example
136 | #
137 | # generate(:authenticated, "user session")
138 | #
139 | def generate(what, *args)
140 | log 'generating', what
141 | argument = args.map {|arg| arg.to_s }.flatten.join(" ")
142 |
143 | in_root { run_ruby_script("script/generate #{what} #{argument}", false) }
144 | end
145 | end # Rails
146 | end # Beet
147 |
--------------------------------------------------------------------------------
/lib/beet/executor.rb:
--------------------------------------------------------------------------------
1 | require 'open-uri'
2 | require 'beet/logger'
3 |
4 | module Beet
5 | class Executor
6 | BEET_DATA_FILE = "~/.beet.yml"
7 | include Beet::Execution
8 | include Beet::FileSystem
9 | include Beet::Interaction
10 |
11 | include Beet::Rails
12 | include Beet::Capistrano
13 | include Beet::SCM
14 |
15 | attr_reader :root, :logger, :options, :template
16 | attr_accessor :recipes, :project_name, :gems, :todo_items
17 |
18 | def initialize(project_name, options={}) # :nodoc:
19 | @root = calculate_project_root(project_name)
20 | @project_name = ((project_name == '.') ? File.basename(Dir.pwd) : project_name)
21 | @logger = Beet::Logger.new
22 | @gems = []
23 | @template = options[:template]
24 | @options = options
25 | @todo_items = ''
26 | @recipes = []
27 | @project_type = options[:project_type]
28 | @generate = true unless options[:generate] == false
29 | @display = options[:display]
30 | extract_commands_from_options
31 | end
32 |
33 | def start
34 | if @options[:use]
35 | puts "Loading saved configuration: #{@options[:use]}"
36 | data = load_saved_recipe_file
37 | if config = data[@options[:use]]
38 | @gems.concat(config[:gems]) if config[:gems]
39 | @template = config[:template] if config[:template]
40 | @recipes.concat(config[:recipes]) if config[:recipes]
41 | end
42 | end
43 |
44 | if @display && @template
45 | puts open(TEMPLATE_LOCATIONS[@template]).read
46 | else
47 | case @project_type
48 | when :rails
49 | if @generate
50 | puts "Generating rails project #{project_name}..."
51 | if @template
52 | system("rails #{project_name} -m #{TEMPLATE_LOCATIONS[@template]}")
53 | else
54 | system("rails #{project_name}")
55 | end
56 | end
57 | end
58 |
59 | add_gems
60 |
61 | print_todo
62 |
63 | if @options[:save]
64 | save_run
65 | end
66 | end
67 |
68 | run_recipes
69 |
70 | end
71 |
72 | def run_recipes
73 | @recipes.each do |recipe|
74 | begin
75 | code = open(recipe).read
76 | if @display
77 | puts code
78 | else
79 | in_root { instance_eval(code)}
80 | end
81 | rescue LoadError, Errno::ENOENT => e
82 | raise "The recipe [#{recipe}] could not be loaded. Error: #{e}"
83 | end
84 | end
85 | end
86 |
87 | def log(*args)
88 | logger.log(*args)
89 | end
90 |
91 | def todo(string=nil, &block)
92 | self.todo_items << (string || block.call)
93 | end
94 |
95 | private
96 |
97 | def print_todo
98 | unless todo_items.empty?
99 | puts '#' * 30
100 | puts "TODO Items:"
101 | puts todo_items
102 | puts '#' * 30
103 | end
104 | end
105 |
106 | def calculate_project_root(project_name)
107 | # if the name looks like ~/projects/foobar then thats the root
108 | if project_name.include?('/')
109 | project_name
110 | # if we're running inside the app, then current dir is it
111 | elsif File.basename(Dir.pwd) == project_name
112 | Dir.pwd
113 | # assume the root is ./project_name
114 | else
115 | File.join(Dir.pwd, project_name)
116 | end
117 | end
118 |
119 | def add_gems
120 | if @gems
121 | @gems.each do |gem_data|
122 | duped_data = gem_data.clone # to avoid removing :name from @gems
123 | name = duped_data.delete(:name)
124 | gem(name, duped_data)
125 | end
126 | end
127 | end
128 |
129 | def extract_commands_from_options
130 | if @options[:gems]
131 | @options[:gems].split(/[\s,]+/).each do |gem|
132 | if gem_info = gem_location(gem)
133 | if gem_info.is_a?(Hash)
134 | @gems << {:name => gem}.merge(gem_info)
135 | else
136 | @gems << {:name => gem, :source => gem_info}
137 | end
138 | else
139 | @gems << {:name => gem}
140 | end
141 | end
142 | end
143 | if @options[:recipes]
144 | @options[:recipes].split(/[\s,]+/).each do |recipe|
145 | if file = recipe_location(recipe)
146 | @recipes << file
147 | else
148 | puts "Can't find recipe #{recipe}"
149 | end
150 | end
151 | end
152 | end
153 |
154 | def save_run
155 | require 'yaml'
156 | name = if options[:save] == true
157 | ask("Enter a name for this configuration: ")
158 | else
159 | options[:save]
160 | end
161 | data = load_saved_recipe_file
162 | data[name] = {:gems => @gems, :recipes => @recipes, :template => @template}
163 | write_saved_recipe_file(data)
164 | end
165 |
166 | def beet_data_file
167 | File.expand_path(BEET_DATA_FILE)
168 | end
169 |
170 | def load_saved_recipe_file
171 | if File.exists?(beet_data_file)
172 | YAML.load_file(beet_data_file)
173 | else
174 | {}
175 | end
176 | end
177 |
178 | def write_saved_recipe_file(data)
179 | File.open(beet_data_file, "wb") do |f|
180 | f.write(YAML::dump(data))
181 | end
182 | end
183 |
184 | def gem_location(gem_name)
185 | GEM_LOCATIONS[gem_name]
186 | end
187 |
188 | def recipe_location(recipe)
189 | return recipe if File.exists?(recipe) or recipe.include?('http://')
190 | locations = []
191 | locations << File.expand_path(ENV['BEET_RECIPES_DIR']) if ENV['BEET_RECIPES_DIR']
192 | locations << File.expand_path(File.join(File.dirname(__FILE__), 'recipes'))
193 | locations.each do |location|
194 | filename = File.join(location, "#{recipe}.rb")
195 | return filename if File.exists?(filename)
196 | end
197 | nil
198 | end
199 | end
200 | end
201 |
202 |
--------------------------------------------------------------------------------
/features/step_definitions/common_steps.rb:
--------------------------------------------------------------------------------
1 | Given /this will run inside a clean "(.*)" directory/ do |directory|
2 | @active_project_folder = File.expand_path File.join(File.dirname(__FILE__), '..', '..', directory)
3 | FileUtils.rm_rf @active_project_folder
4 | FileUtils.mkdir_p @active_project_folder
5 | end
6 |
7 | Given /^env variable \$([\w_]+) set to "(.*)"/ do |env_var, value|
8 | ENV[env_var] = value
9 | end
10 |
11 | Given /"(.*)" folder is deleted/ do |folder|
12 | in_project_folder { FileUtils.rm_rf folder }
13 | end
14 |
15 | When /^inside "(.*)" I run local executable "(.*)" with arguments "(.*)"$/ do |directory, executable, arguments|
16 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
17 | executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable))
18 | in_project_subfolder(directory) do
19 | system "ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
20 | end
21 | end
22 |
23 | When /^I invoke "(.*)" generator with arguments "(.*)"$/ do |generator, arguments|
24 | @stdout = StringIO.new
25 | in_project_folder do
26 | if Object.const_defined?("APP_ROOT")
27 | APP_ROOT.replace(FileUtils.pwd)
28 | else
29 | APP_ROOT = FileUtils.pwd
30 | end
31 | run_generator(generator, arguments.split(' '), SOURCES, :stdout => @stdout)
32 | end
33 | File.open(File.join(@tmp_root, "generator.out"), "w") do |f|
34 | @stdout.rewind
35 | f << @stdout.read
36 | end
37 | end
38 |
39 | When /^I run executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
40 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
41 | in_project_folder do
42 | system "#{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
43 | end
44 | end
45 |
46 | When /^I run project executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
47 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
48 | in_project_folder do
49 | system "ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}"
50 | end
51 | end
52 |
53 | When /^I run local executable "(.*)" with arguments "(.*)"/ do |executable, arguments|
54 | @stdout = File.expand_path(File.join(@tmp_root, "executable.out"))
55 | executable = File.expand_path(File.join(File.dirname(__FILE__), "/../../bin", executable))
56 | in_project_folder do
57 | `ruby #{executable} #{arguments} > #{@stdout} 2> #{@stdout}`
58 | end
59 | end
60 |
61 | When /^I invoke task "rake (.*)"/ do |task|
62 | @stdout = File.expand_path(File.join(@tmp_root, "tests.out"))
63 | in_project_folder do
64 | system "rake #{task} --trace > #{@stdout} 2> #{@stdout}"
65 | end
66 | end
67 |
68 | Then /^folder "(.*)" (is|is not) created/ do |folder, is|
69 | in_project_folder do
70 | File.exists?(folder).should(is == 'is' ? be_true : be_false)
71 | end
72 | end
73 |
74 | Then /^file "(.*)" (is|is not) created/ do |file, is|
75 | in_project_folder do
76 | File.exists?(file).should(is == 'is' ? be_true : be_false)
77 | end
78 | end
79 |
80 | Then /^file with name matching "(.*)" is created/ do |pattern|
81 | in_project_folder do
82 | Dir[pattern].should_not be_empty
83 | end
84 | end
85 |
86 | Then /^file "(.*)" contents (does|does not) match \/(.*)\// do |file, does, regex|
87 | in_project_folder do
88 | actual_output = File.read(file)
89 | (does == 'does') ?
90 | actual_output.should(match(/#{regex}/)) :
91 | actual_output.should_not(match(/#{regex}/))
92 | end
93 | end
94 |
95 | Then /gem file "(.*)" and generated file "(.*)" should be the same/ do |gem_file, project_file|
96 | File.exists?(gem_file).should be_true
97 | File.exists?(project_file).should be_true
98 | gem_file_contents = File.read(File.dirname(__FILE__) + "/../../#{gem_file}")
99 | project_file_contents = File.read(File.join(@active_project_folder, project_file))
100 | project_file_contents.should == gem_file_contents
101 | end
102 |
103 | Then /^(does|does not) invoke generator "(.*)"$/ do |does_invoke, generator|
104 | actual_output = File.read(@stdout)
105 | does_invoke == "does" ?
106 | actual_output.should(match(/dependency\s+#{generator}/)) :
107 | actual_output.should_not(match(/dependency\s+#{generator}/))
108 | end
109 |
110 | Then /I should see help option "(.*)"/ do |opt|
111 | actual_output = File.read(@stdout)
112 | actual_output.should match(/#{opt}/)
113 | end
114 |
115 | Then /^I should see$/ do |text|
116 | actual_output = File.read(@stdout)
117 | actual_output.should contain(text)
118 | end
119 |
120 | Then /^I should not see$/ do |text|
121 | actual_output = File.read(@stdout)
122 | actual_output.should_not contain(text)
123 | end
124 |
125 | Then /^I should see exactly$/ do |text|
126 | actual_output = File.read(@stdout)
127 | actual_output.should == text
128 | end
129 |
130 | Then /^I should see all (\d+) tests pass/ do |expected_test_count|
131 | expected = %r{^#{expected_test_count} tests, \d+ assertions, 0 failures, 0 errors}
132 | actual_output = File.read(@stdout)
133 | actual_output.should match(expected)
134 | end
135 |
136 | Then /^I should see all (\d+) examples pass/ do |expected_test_count|
137 | expected = %r{^#{expected_test_count} examples?, 0 failures}
138 | actual_output = File.read(@stdout)
139 | actual_output.should match(expected)
140 | end
141 |
142 | Then /^yaml file "(.*)" contains (\{.*\})/ do |file, yaml|
143 | in_project_folder do
144 | yaml = eval yaml
145 | YAML.load(File.read(file)).should == yaml
146 | end
147 | end
148 |
149 | Then /^Rakefile can display tasks successfully/ do
150 | @stdout = File.expand_path(File.join(@tmp_root, "rakefile.out"))
151 | in_project_folder do
152 | system "rake -T > #{@stdout} 2> #{@stdout}"
153 | end
154 | actual_output = File.read(@stdout)
155 | actual_output.should match(/^rake\s+\w+\s+#\s.*/)
156 | end
157 |
158 | Then /^task "rake (.*)" is executed successfully/ do |task|
159 | @stdout.should_not be_nil
160 | actual_output = File.read(@stdout)
161 | actual_output.should_not match(/^Don't know how to build task '#{task}'/)
162 | actual_output.should_not match(/Error/i)
163 | end
164 |
165 | Then /^gem spec key "(.*)" contains \/(.*)\// do |key, regex|
166 | in_project_folder do
167 | gem_file = Dir["pkg/*.gem"].first
168 | gem_spec = Gem::Specification.from_yaml(`gem spec #{gem_file}`)
169 | spec_value = gem_spec.send(key.to_sym)
170 | spec_value.to_s.should match(/#{regex}/)
171 | end
172 | end
173 |
--------------------------------------------------------------------------------
/lib/beet/recipes/rails/auth/authlogic.rb:
--------------------------------------------------------------------------------
1 | file "app/models/user_session.rb" do
2 | %{
3 | class UserSession < Authlogic::Session::Base
4 | logout_on_timeout true # default is false
5 | end
6 | }.strip
7 | end
8 |
9 | file "app/models/user.rb" do
10 | %{
11 | class User < ActiveRecord::Base
12 | acts_as_authentic do |c|
13 | c.logged_in_timeout = 10.minutes # default is 10.minutes
14 | end
15 | end
16 | }.strip
17 | end
18 |
19 | file "app/controllers/user_sessions_controller.rb" do
20 | %{
21 | class UserSessionsController < ApplicationController
22 | before_filter :require_no_user, :only => [:new, :create]
23 | before_filter :require_user, :only => :destroy
24 |
25 | def new
26 | @user_session = UserSession.new
27 | end
28 |
29 | def create
30 | @user_session = UserSession.new(params[:user_session])
31 | if @user_session.save
32 | flash[:notice] = "Login successful!"
33 | redirect_back_or_default account_url
34 | else
35 | render :action => :new
36 | end
37 | end
38 |
39 | def destroy
40 | current_user_session.destroy
41 | flash[:notice] = "Logout successful!"
42 | redirect_back_or_default new_user_session_url
43 | end
44 | end
45 | }.strip
46 | end
47 |
48 |
49 | file "app/views/user_sessions/new.html.erb" do
50 | %{
51 | Login
52 |
53 | <% form_for @user_session, :url => user_session_path do |f| %>
54 | <%= f.error_messages %>
55 | <%= f.label :email %>
56 | <%= f.text_field :email %>
57 |
58 | <%= f.label :password %>
59 | <%= f.password_field :password %>
60 |
61 | <%= f.check_box :remember_me %><%= f.label :remember_me %>
62 |
63 | <%= f.submit "Login" %>
64 | <% end %>
65 | }.strip
66 | end
67 |
68 | # Setup some routes
69 | route 'map.resource :user_session'
70 | route 'map.resource :account, :controller => "users"'
71 | route 'map.resources :users'
72 | route 'map.register "/register", :controller => "users", :action => "new"'
73 | route 'map.login "/login", :controller => "user_sessions", :action => "new"'
74 | route 'map.logout "/logout", :controller => "user_sessions", :action => "destroy"'
75 |
76 | file "app/controllers/application_controller.rb" do
77 | %{
78 | # Filters added to this controller apply to all controllers in the application.
79 | # Likewise, all the methods added will be available for all controllers.
80 |
81 | class ApplicationController < ActionController::Base
82 | helper :all
83 | helper_method :current_user_session, :current_user
84 | filter_parameter_logging :password, :password_confirmation
85 |
86 | private
87 | def current_user_session
88 | return @current_user_session if defined?(@current_user_session)
89 | @current_user_session = UserSession.find
90 | end
91 |
92 | def current_user
93 | return @current_user if defined?(@current_user)
94 | @current_user = current_user_session && current_user_session.record
95 | end
96 |
97 | def require_user
98 | unless current_user
99 | store_location
100 | flash[:notice] = "You must be logged in to access this page"
101 | redirect_to new_user_session_url
102 | return false
103 | end
104 | end
105 |
106 | def require_no_user
107 | if current_user
108 | store_location
109 | flash[:notice] = "You must be logged out to access this page"
110 | redirect_to account_url
111 | return false
112 | end
113 | end
114 |
115 | def store_location
116 | session[:return_to] = request.request_uri
117 | end
118 |
119 | def redirect_back_or_default(default)
120 | redirect_to(session[:return_to] || default)
121 | session[:return_to] = nil
122 | end
123 | end
124 | }.strip
125 | end
126 |
127 | file "app/controllers/users_controller.rb" do
128 | %{
129 | class UsersController < ApplicationController
130 | before_filter :require_no_user, :only => [:new, :create]
131 | before_filter :require_user, :only => [:show, :edit, :update]
132 |
133 | def new
134 | @user = User.new
135 | end
136 |
137 | def create
138 | @user = User.new(params[:user])
139 | if @user.save
140 | flash[:notice] = "Account registered!"
141 | redirect_back_or_default account_url
142 | else
143 | render :action => :new
144 | end
145 | end
146 |
147 | def show
148 | @user = @current_user
149 | end
150 |
151 | def edit
152 | @user = @current_user
153 | end
154 |
155 | def update
156 | @user = @current_user # makes our views "cleaner" and more consistent
157 | if @user.update_attributes(params[:user])
158 | flash[:notice] = "Account updated!"
159 | redirect_to account_url
160 | else
161 | render :action => :edit
162 | end
163 | end
164 | end
165 | }.strip
166 | end
167 |
168 | file "app/views/users/_form.html.erb" do
169 | %{
170 | <%= form.label :email %>
171 | <%= form.text_field :email %>
172 |
173 | <%= form.label :password, form.object.new_record? ? nil : "Change password" %>
174 | <%= form.password_field :password %>
175 |
176 | <%= form.label :password_confirmation %>
177 | <%= form.password_field :password_confirmation %>
178 | }.strip
179 | end
180 |
181 | file "app/views/users/edit.html.erb" do
182 | %{
183 | Edit My Account
184 |
185 | <% form_for @user, :url => account_path do |f| %>
186 | <%= f.error_messages %>
187 | <%= render :partial => "form", :object => f %>
188 | <%= f.submit "Update" %>
189 | <% end %>
190 |
191 |
<%= link_to "My Profile", account_path %>
192 | }.strip
193 | end
194 |
195 | file "app/views/users/new.html.erb" do
196 | %{
197 | Register
198 |
199 | <% form_for @user, :url => account_path do |f| %>
200 | <%= f.error_messages %>
201 | <%= render :partial => "form", :object => f %>
202 | <%= f.submit "Register" %>
203 | <% end %>
204 | }.strip
205 | end
206 |
207 | file "app/views/users/show.html.erb" do
208 | %{
209 |
210 | Email:
211 | <%=h @user.email %>
212 |
213 |
214 |
215 | Login count:
216 | <%=h @user.login_count %>
217 |
218 |
219 |
220 | Last request at:
221 | <%=h @user.last_request_at %>
222 |
223 |
224 |
225 | Last login at:
226 | <%=h @user.last_login_at %>
227 |
228 |
229 |
230 | Current login at:
231 | <%=h @user.current_login_at %>
232 |
233 |
234 |
235 | Last login ip:
236 | <%=h @user.last_login_ip %>
237 |
238 |
239 |
240 | Current login ip:
241 | <%=h @user.current_login_ip %>
242 |
243 |
244 |
245 | <%= link_to 'Edit', edit_account_path %>
246 | }.strip
247 | end
248 |
249 | # can't rely on internal rails migration generation, so we do it this way
250 |
251 | #Dir.chdir("script") #for ruby 1.9.2 08/07/2009 . no need for ruby1.9.1p129
252 | #run "./generate migration beet_authlogic_create_user" # for ruby 1.9.2 08/07/2009. no need for ruby1.9.1p129
253 |
254 | run "script/generate migration beet_authlogic_create_user"
255 |
256 | #now open it
257 | #Dir.chdir("..") # for ruby 1.9.2 08/07/2009. no need for ruby1.9.1p129
258 |
259 | file(Dir.glob('db/migrate/*beet_authlogic_create_user*').first) do
260 | %{
261 | class BeetAuthlogicCreateUser < ActiveRecord::Migration
262 | def self.up
263 | unless table_exists?(:users)
264 | create_table :users do |t|
265 | t.string :email, :null => false # optional, you can use login instead, or both
266 | t.string :crypted_password, :null => false # optional, see below
267 | t.string :password_salt, :null => false # optional, but highly recommended
268 | t.string :persistence_token, :null => false # required
269 | t.string :single_access_token, :null => false # optional, see Authlogic::Session::Params
270 | t.string :perishable_token, :null => false # optional, see Authlogic::Session::Perishability
271 |
272 | # Magic columns, just like ActiveRecord's created_at and updated_at. These are automatically maintained by Authlogic if they are present.
273 | t.integer :login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
274 | t.integer :failed_login_count, :null => false, :default => 0 # optional, see Authlogic::Session::MagicColumns
275 | t.datetime :last_request_at # optional, see Authlogic::Session::MagicColumns
276 | t.datetime :current_login_at # optional, see Authlogic::Session::MagicColumns
277 | t.datetime :last_login_at # optional, see Authlogic::Session::MagicColumns
278 | t.string :current_login_ip # optional, see Authlogic::Session::MagicColumns
279 | t.string :last_login_ip # optional, see Authlogic::Session::MagicColumns
280 | end
281 | end
282 | end
283 |
284 | def self.down
285 | drop_table :users
286 | end
287 | end
288 | }.strip
289 | end
290 |
291 |
292 | gem 'authlogic', :version => '~> 2.0.0'
293 |
294 | if yes?("Install using sudo?")
295 | rake "gems:install", :sudo => true
296 | else
297 | rake "gems:install"
298 | end
299 |
300 | rake "db:create:all"
301 | rake "db:migrate"
302 |
303 |
--------------------------------------------------------------------------------