├── .gemtest ├── .rspec ├── .yardopts ├── lib ├── cape │ ├── version.rb │ ├── core_ext.rb │ ├── core_ext │ │ ├── hash.rb │ │ └── symbol.rb │ ├── util.rb │ ├── hash_list.rb │ ├── recipe_definition.rb │ ├── rake.rb │ ├── capistrano.rb │ ├── dsl.rb │ └── xterm.rb └── cape.rb ├── .gitignore ├── features ├── support │ └── env.rb ├── dsl │ ├── mirror_rake_tasks │ │ ├── with_undefined_task_or_namespace.feature │ │ ├── with_valid_options.feature │ │ ├── with_valid_options_and_cd.feature │ │ ├── with_rename.feature │ │ ├── with_cd_and_environment_variables.feature │ │ ├── with_valid_options_and_environment_variables.feature │ │ ├── with_rename_and_cd.feature │ │ ├── with_rename_and_valid_options.feature │ │ ├── with_environment_variables.feature │ │ ├── with_valid_options_and_cd_and_environment_variables.feature │ │ ├── with_rename_and_environment_variables.feature │ │ ├── with_rename_and_valid_options_and_cd.feature │ │ ├── inside_capistrano_namespace.feature │ │ ├── with_rename_and_cd_and_environment_variables.feature │ │ ├── with_rename_and_valid_options_and_environment_variables.feature │ │ ├── with_rename_and_valid_options_and_cd_and_environment_variables.feature │ │ ├── with_defined_task.feature │ │ ├── with_defined_task_and_cd.feature │ │ ├── with_defined_task_and_valid_options.feature │ │ ├── with_defined_task_and_environment_variables.feature │ │ ├── with_defined_task_and_valid_options_and_cd.feature │ │ ├── with_defined_task_and_rename.feature │ │ ├── with_defined_task_and_cd_and_environment_variables.feature │ │ ├── with_defined_task_and_valid_options_and_environment_variables.feature │ │ ├── with_defined_task_and_rename_and_cd.feature │ │ ├── with_defined_task_and_rename_and_valid_options.feature │ │ ├── with_defined_task_and_valid_options_and_cd_and_environment_variables.feature │ │ ├── with_defined_task_and_rename_and_environment_variables.feature │ │ ├── with_defined_task_and_rename_and_valid_options_and_cd.feature │ │ ├── with_defined_task_and_rename_and_cd_and_environment_variables.feature │ │ ├── with_defined_task_and_rename_and_valid_options_and_environment_variables.feature │ │ ├── with_defined_task_and_rename_and_valid_options_and_cd_and_environment_variables.feature │ │ ├── with_cd.feature │ │ ├── with_defined_namespace.feature │ │ └── unqualified.feature │ ├── each_rake_task │ │ ├── with_undefined_task_or_namespace.feature │ │ ├── with_defined_task.feature │ │ ├── with_defined_namespace.feature │ │ └── unqualified.feature │ └── rake_executable.feature └── step_definitions.rb ├── gemfiles ├── rake_v0.9.3.gemfile ├── rake_v10.x.gemfile ├── capistrano_v2.x.gemfile ├── rake_v0.9.3.gemfile.lock ├── rake_v10.x.gemfile.lock └── capistrano_v2.x.gemfile.lock ├── spec ├── cape │ ├── version_spec.rb │ ├── core_ext │ │ ├── hash_spec.rb │ │ └── symbol_spec.rb │ ├── capistrano_spec.rb │ ├── util_spec.rb │ ├── recipe_definition_spec.rb │ ├── hash_list_spec.rb │ ├── xterm_spec.rb │ ├── rake_spec.rb │ └── dsl_spec.rb ├── cape_spec.rb └── spec_helper.rb ├── Appraisals ├── .travis.yml ├── Gemfile ├── License.markdown ├── Guardfile ├── cape.gemspec ├── History.markdown ├── Rakefile └── README.markdown /.gemtest: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --file History.markdown --file License.markdown --no-private --protected --title "Cape" 2 | -------------------------------------------------------------------------------- /lib/cape/version.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | # The version of Cape. 4 | VERSION = '1.8.0' 5 | 6 | end 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .rbx 3 | .ruby-gemset 4 | .ruby-version 5 | .yardoc 6 | Gemfile.lock 7 | doc 8 | pkg 9 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'aruba/cucumber' 2 | 3 | Before do 4 | @aruba_timeout_seconds = 10 5 | end 6 | -------------------------------------------------------------------------------- /gemfiles/rake_v0.9.3.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rake", "0.9.3" 6 | 7 | gemspec :path=>"../" -------------------------------------------------------------------------------- /gemfiles/rake_v10.x.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rake", "~> 10" 6 | 7 | gemspec :path=>"../" -------------------------------------------------------------------------------- /gemfiles/capistrano_v2.x.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "capistrano", "~> 2" 6 | 7 | gemspec :path=>"../" -------------------------------------------------------------------------------- /spec/cape/version_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/version' 3 | 4 | describe 'Cape::VERSION' do 5 | specify { expect(Cape::VERSION).to match(/^\d+\.\d+\.\d+/) } 6 | end 7 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise 'capistrano-v2.x' do 2 | gem 'capistrano', '~> 2' 3 | end 4 | 5 | appraise 'rake-v0.9.3' do 6 | gem 'rake', '0.9.3' 7 | end 8 | 9 | appraise 'rake-v10.x' do 10 | gem 'rake', '~> 10' 11 | end 12 | -------------------------------------------------------------------------------- /lib/cape/core_ext.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | # Contains extensions to core types. 4 | # 5 | # @api private 6 | module CoreExt 7 | 8 | autoload :Hash, 'cape/core_ext/hash' 9 | autoload :Symbol, 'cape/core_ext/symbol' 10 | 11 | end 12 | 13 | end 14 | -------------------------------------------------------------------------------- /spec/cape/core_ext/hash_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/core_ext/hash' 3 | 4 | describe Hash do 5 | subject(:hash) { {:foo => 'bar', :baz => 'qux', :quux => 'corge'} } 6 | 7 | describe '#slice with keys that are present and those that are not' do 8 | it 'returns the expected subset hash' do 9 | expect(hash.slice(:baz, :fizzle, :quux)).to eq(:baz => 'qux', 10 | :quux => 'corge') 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/cape/core_ext/symbol_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/core_ext/symbol' 3 | 4 | describe Symbol do 5 | describe '#<=>' do 6 | describe 'with a lower symbol' do 7 | specify { expect(:foo <=> :bar).to eq(1) } 8 | end 9 | 10 | describe 'with a higher symbol' do 11 | specify { expect(:baz <=> :qux).to eq(-1) } 12 | end 13 | 14 | describe 'with itself' do 15 | specify { expect(:quux <=> :quux).to eq(0) } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | bundler_args: --without debug doc tooling 3 | gemfile: 4 | - gemfiles/capistrano_v2.x.gemfile 5 | - gemfiles/rake_v0.9.3.gemfile 6 | - gemfiles/rake_v10.x.gemfile 7 | rvm: 8 | - 1.8.7 9 | - 1.9.2 10 | - 1.9.3 11 | - 2.0.0 12 | - ruby-head 13 | - ree 14 | - jruby-18mode 15 | - jruby-19mode 16 | - jruby-head 17 | script: "bundle exec rake test" 18 | matrix: 19 | allow_failures: 20 | - rvm: ruby-head 21 | - rvm: jruby-18mode 22 | - rvm: jruby-19mode 23 | - rvm: jruby-head 24 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_undefined_task_or_namespace.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with an undefined task or namespace 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: do not mirror any Rake tasks 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks 'lon' 13 | end 14 | """ 15 | When I run `cap -vT` 16 | Then the output should not contain "lon" 17 | -------------------------------------------------------------------------------- /lib/cape/core_ext/hash.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | module CoreExt 4 | 5 | # Adds methods missing from Ruby's Hash core class. 6 | module Hash 7 | 8 | # Returns a copy of the Hash containing values only for the specified 9 | # _keys_. 10 | # 11 | # @param [Array] keys zero or more hash keys 12 | # 13 | # @return [Hash] a subset of the Hash 14 | def slice(*keys) 15 | ::Hash[select { |key, value| keys.include? key }] 16 | end 17 | 18 | end 19 | 20 | end 21 | 22 | end 23 | 24 | unless ::Hash.instance_methods.collect(&:to_s).include?('slice') 25 | ::Hash.class_eval do 26 | include Cape::CoreExt::Hash 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/cape/capistrano_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/capistrano' 3 | require 'cape/rake' 4 | 5 | describe Cape::Capistrano do 6 | subject(:capistrano) { capistrano_class.new } 7 | 8 | let(:capistrano_class) { described_class } 9 | 10 | describe 'without specified attributes' do 11 | describe '#rake' do 12 | specify { expect(capistrano.rake).to eq(Cape::Rake.new) } 13 | end 14 | end 15 | 16 | describe 'with specified attributes' do 17 | subject(:capistrano) { 18 | capistrano_class.new :rake => 'the specified value of #rake' 19 | } 20 | 21 | describe '#rake' do 22 | specify { expect(capistrano.rake).to eq('the specified value of #rake') } 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/cape/core_ext/symbol.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | module CoreExt 4 | 5 | # Adds methods missing from Ruby's Symbol core class. 6 | module Symbol 7 | 8 | # Compares the String representation of the Symbol to that of another. 9 | # 10 | # @param [Symbol] other 11 | # 12 | # @return [0] the Symbol is equal to _other_ 13 | # @return [-1] the Symbol is lesser than _other_ 14 | # @return [1] the Symbol is greater than _other_ 15 | def <=>(other) 16 | to_s <=> other.to_s 17 | end 18 | 19 | end 20 | 21 | end 22 | 23 | end 24 | 25 | unless ::Symbol.instance_methods.collect(&:to_s).include?('<=>') 26 | ::Symbol.class_eval do 27 | include Cape::CoreExt::Symbol 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/cape_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape' 3 | require 'cape/dsl' 4 | 5 | describe Cape do 6 | let(:cape_module) { described_class } 7 | 8 | Cape::DSL.public_instance_methods.each do |m| 9 | specify { expect(cape_module).to respond_to(m) } 10 | end 11 | end 12 | 13 | describe '#Cape' do 14 | it 'yields the Cape module if given a unary block' do 15 | yielded = nil 16 | Cape do |c| 17 | yielded = c 18 | end 19 | expect(yielded).to eq(Cape) 20 | end 21 | 22 | it 'accepts a nullary block' do 23 | Cape do 24 | end 25 | end 26 | 27 | it 'expires the Rake tasks cache when leaving the block' do 28 | Cape do 29 | expect(rake).to receive(:expire_cache!).once 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /features/dsl/each_rake_task/with_undefined_task_or_namespace.feature: -------------------------------------------------------------------------------- 1 | Feature: The #each_rake_task DSL method with an undefined task or namespace 2 | 3 | In order to use the metadata of Rake tasks in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: do not enumerate any Rake tasks 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | each_rake_task 'lon' do |t| 13 | $stdout.puts '', "Name: #{t[:name].inspect}" 14 | if t[:parameters] 15 | $stdout.puts "Parameters: #{t[:parameters].inspect}" 16 | end 17 | if t[:description] 18 | $stdout.puts "Description: #{t[:description].inspect}" 19 | end 20 | end 21 | end 22 | """ 23 | When I run `cap -vT` 24 | Then the output should not contain "lon" 25 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_valid_options.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with valid options 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Rake task with its implementation 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.options[:roles] = :app 16 | end 17 | end 18 | """ 19 | When I run `cap long` 20 | Then the output should contain: 21 | """ 22 | * executing `long' 23 | """ 24 | And the output should contain: 25 | """ 26 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 27 | """ 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :debug do 6 | gem 'ruby-debug', :require => false, :platforms => [:mri_18, :jruby] 7 | gem 'debugger', :require => false, :platforms => [:mri_19, :mri_20] 8 | end 9 | 10 | group :doc do 11 | gem 'yard', '~> 0', :require => false, :platforms => [:ruby, :mswin, :mingw] 12 | gem 'rdiscount', :require => false, :platforms => [:ruby, :mswin, :mingw] 13 | 14 | gem 'relish', '~> 0', :require => false, :platforms => :mri_19 15 | end 16 | 17 | group :tooling do 18 | gem 'appraisal', '~> 0', :require => false 19 | gem 'guard-cucumber', '~> 1', :require => false, :platforms => :mri_19 20 | gem 'guard-rspec', '~> 4', :require => false, :platforms => :mri_19 21 | if RUBY_PLATFORM =~ /darwin/i 22 | gem 'rb-fsevent', '~> 0', :require => false 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rspec --init` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # Require this file using `require "spec_helper"` to ensure that it is only 4 | # loaded once. 5 | # 6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 7 | RSpec.configure do |config| 8 | config.treat_symbols_as_metadata_keys_with_true_values = true 9 | config.run_all_when_everything_filtered = true 10 | config.filter_run :focus 11 | 12 | # Run specs in random order to surface order dependencies. If you find an 13 | # order dependency and want to debug it, you can fix the order by providing 14 | # the seed, which is printed after each run. 15 | # --seed 1234 16 | config.order = 'random' 17 | 18 | config.alias_it_should_behave_like_to :behaves_like, 'behaves like' 19 | 20 | config.expect_with :rspec do |c| 21 | c.syntax = :expect 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_valid_options_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with valid options and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Rake task with its implementation 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.options[:roles] = :app 16 | recipes.cd { release_path } 17 | end 18 | end 19 | """ 20 | When I run `cap long` 21 | Then the output should contain: 22 | """ 23 | * executing `long' 24 | """ 25 | And the output should contain: 26 | """ 27 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 28 | """ 29 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.rename do |task_name| 16 | "do_#{task_name}" 17 | end 18 | end 19 | end 20 | """ 21 | When I run `cap do_load` 22 | Then the output should contain: 23 | """ 24 | * executing `do_load' 25 | """ 26 | And the output should contain: 27 | """ 28 | `do_load' is only run for servers matching {}, but no servers matched 29 | """ 30 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a different directory and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Rake task with its implementation 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.cd { release_path } 17 | recipes.env['RAILS_ENV'] = lambda { rails_env } 18 | end 19 | end 20 | """ 21 | When I run `cap long` 22 | Then the output should contain: 23 | """ 24 | * executing `long' 25 | """ 26 | And the output should contain: 27 | """ 28 | `long' is only run for servers matching {}, but no servers matched 29 | """ 30 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_valid_options_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with valid options and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Rake task with its implementation 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.options[:roles] = :app 17 | recipes.env['RAILS_ENV'] = lambda { rails_env } 18 | end 19 | end 20 | """ 21 | When I run `cap long` 22 | Then the output should contain: 23 | """ 24 | * executing `long' 25 | """ 26 | And the output should contain: 27 | """ 28 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 29 | """ 30 | -------------------------------------------------------------------------------- /lib/cape/util.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | # Provides utility functions. 4 | # 5 | # @api private 6 | module Util 7 | 8 | # Conditionally transforms the specified _noun_ into its plural form. 9 | # 10 | # @param [String] singular_noun a singular noun 11 | # @param [Fixnum] count the quantity of _singular_noun_ 12 | # 13 | # @return [String] the plural of _singular_noun_, unless _count_ is +1+ 14 | def self.pluralize(singular_noun, count=2) 15 | return singular_noun if count == 1 16 | 17 | "#{singular_noun}s" 18 | end 19 | 20 | # Builds a list phrase from the elements of the specified _array_. 21 | # 22 | # @param [Array of String] array zero or more nouns 23 | # 24 | # @return [String] the elements of _array_, joined with commas and "and", as 25 | # appropriate 26 | def self.to_list_phrase(array) 27 | return array.join(' and ') if (array.length <= 2) 28 | 29 | [array[0...-1].join(', '), array[-1]].join ', and ' 30 | end 31 | 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.rename do |task_name| 16 | "do_#{task_name}" 17 | end 18 | recipes.cd { release_path } 19 | end 20 | end 21 | """ 22 | When I run `cap do_load` 23 | Then the output should contain: 24 | """ 25 | * executing `do_load' 26 | """ 27 | And the output should contain: 28 | """ 29 | `do_load' is only run for servers matching {}, but no servers matched 30 | """ 31 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_valid_options.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic and valid options 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.rename do |task_name| 16 | "do_#{task_name}" 17 | end 18 | recipes.options[:roles] = :app 19 | end 20 | end 21 | """ 22 | When I run `cap do_load` 23 | Then the output should contain: 24 | """ 25 | * executing `do_load' 26 | """ 27 | And the output should contain: 28 | """ 29 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 30 | """ 31 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Rake task with its implementation 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.env['RAILS_ENV'] = lambda { rails_env } 17 | recipes.env[nil] = 'foo' 18 | recipes.env['FOO'] = nil 19 | recipes.env['SOME_OTHER'] = 'var' 20 | end 21 | end 22 | """ 23 | When I run `cap long` 24 | Then the output should contain: 25 | """ 26 | * executing `long' 27 | """ 28 | And the output should contain: 29 | """ 30 | `long' is only run for servers matching {}, but no servers matched 31 | """ 32 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_valid_options_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with valid options, a different directory, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror the matching Rake task with its implementation 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.options[:roles] = :app 17 | recipes.cd { release_path } 18 | recipes.env['RAILS_ENV'] = lambda { rails_env } 19 | end 20 | end 21 | """ 22 | When I run `cap long` 23 | Then the output should contain: 24 | """ 25 | * executing `long' 26 | """ 27 | And the output should contain: 28 | """ 29 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 30 | """ 31 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.rename do |task_name| 17 | "do_#{task_name}" 18 | end 19 | recipes.env['RAILS_ENV'] = lambda { rails_env } 20 | end 21 | end 22 | """ 23 | When I run `cap do_load` 24 | Then the output should contain: 25 | """ 26 | * executing `do_load' 27 | """ 28 | And the output should contain: 29 | """ 30 | `do_load' is only run for servers matching {}, but no servers matched 31 | """ 32 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_valid_options_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic, valid options, and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.rename do |task_name| 16 | "do_#{task_name}" 17 | end 18 | recipes.options[:roles] = :app 19 | recipes.cd { release_path } 20 | end 21 | end 22 | """ 23 | When I run `cap do_load` 24 | Then the output should contain: 25 | """ 26 | * executing `do_load' 27 | """ 28 | And the output should contain: 29 | """ 30 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 31 | """ 32 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/inside_capistrano_namespace.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method, inside a Capistrano namespace 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror all Rake tasks 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | namespace :ns do 12 | Cape do |cape| 13 | cape.mirror_rake_tasks 14 | end 15 | end 16 | """ 17 | When I run `cap -vT` 18 | Then the output should contain: 19 | """ 20 | cap ns:long # My long task -- it has a ve... 21 | """ 22 | 23 | Scenario: mirror a Rake task with its implementation 24 | Given a full-featured Rakefile 25 | And a Capfile with: 26 | """ 27 | set :current_path, '/current/path' 28 | 29 | namespace :ns do 30 | Cape do |cape| 31 | cape.mirror_rake_tasks 32 | end 33 | end 34 | """ 35 | When I run `cap ns:long` 36 | Then the output should contain: 37 | """ 38 | * executing `ns:long' 39 | """ 40 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic, a different directory, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.rename do |task_name| 17 | "do_#{task_name}" 18 | end 19 | recipes.cd { release_path } 20 | recipes.env['RAILS_ENV'] = lambda { rails_env } 21 | end 22 | end 23 | """ 24 | When I run `cap do_load` 25 | Then the output should contain: 26 | """ 27 | * executing `do_load' 28 | """ 29 | And the output should contain: 30 | """ 31 | `do_load' is only run for servers matching {}, but no servers matched 32 | """ 33 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_valid_options_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic, valid options, and enviroment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :current_path, '/current/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.rename do |task_name| 17 | "do_#{task_name}" 18 | end 19 | recipes.options[:roles] = :app 20 | recipes.env['RAILS_ENV'] = lambda { rails_env } 21 | end 22 | end 23 | """ 24 | When I run `cap do_load` 25 | Then the output should contain: 26 | """ 27 | * executing `do_load' 28 | """ 29 | And the output should contain: 30 | """ 31 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 32 | """ 33 | -------------------------------------------------------------------------------- /License.markdown: -------------------------------------------------------------------------------- 1 | # The MIT License 2 | 3 | Source code for _Cape_ is Copyright © 2011–2015 [Nils Jonsson](mailto:cape@nilsjonsson.com) and [contributors](http://github.com/njonsson/cape/contributors "Cape contributors at GitHub"). 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /features/dsl/each_rake_task/with_defined_task.feature: -------------------------------------------------------------------------------- 1 | Feature: The #each_rake_task DSL method with an argument of a defined task 2 | 3 | In order to use the metadata of Rake tasks in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: enumerate only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | each_rake_task 'long' do |t| 13 | $stdout.puts '', "Name: #{t[:name].inspect}" 14 | if t[:parameters] 15 | $stdout.puts "Parameters: #{t[:parameters].inspect}" 16 | end 17 | if t[:description] 18 | $stdout.puts "Description: #{t[:description].inspect}" 19 | end 20 | end 21 | end 22 | """ 23 | When I run `cap -vT` 24 | Then the output should contain: 25 | """ 26 | 27 | Name: "long" 28 | Description: "My long task -- it has a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long description" 29 | """ 30 | And the output should not contain "with_one_arg" 31 | And the output should not contain "my_namespace" 32 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_rename_and_valid_options_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with renaming logic, valid options, a different directory, and enviroment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Ruby-method-shadowing Rake task with its implementation 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | set :rails_env, 'rails-env' 13 | 14 | Cape do 15 | mirror_rake_tasks do |recipes| 16 | recipes.rename do |task_name| 17 | "do_#{task_name}" 18 | end 19 | recipes.options[:roles] = :app 20 | recipes.cd { release_path } 21 | recipes.env['RAILS_ENV'] = lambda { rails_env } 22 | end 23 | end 24 | """ 25 | When I run `cap do_load` 26 | Then the output should contain: 27 | """ 28 | * executing `do_load' 29 | """ 30 | And the output should contain: 31 | """ 32 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 33 | """ 34 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long 13 | end 14 | """ 15 | When I run `cap -vT` 16 | Then the output should contain: 17 | """ 18 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 19 | """ 20 | And the output should not contain "with_one_arg" 21 | And the output should not contain "my_namespace" 22 | 23 | Scenario: mirror the matching Rake task with its implementation 24 | Given a full-featured Rakefile 25 | And a Capfile with: 26 | """ 27 | set :current_path, '/current/path' 28 | 29 | Cape do 30 | mirror_rake_tasks 'long' 31 | end 32 | """ 33 | When I run `cap long` 34 | Then the output should contain: 35 | """ 36 | * executing `long' 37 | """ 38 | And the output should contain: 39 | """ 40 | `long' is only run for servers matching {}, but no servers matched 41 | """ 42 | -------------------------------------------------------------------------------- /features/dsl/each_rake_task/with_defined_namespace.feature: -------------------------------------------------------------------------------- 1 | Feature: The #each_rake_task DSL method with an argument of a defined namespace 2 | 3 | In order to use the metadata of Rake tasks in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: enumerate only the Rake tasks in the matching namespace 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | each_rake_task :my_namespace do |t| 13 | $stdout.puts '', "Name: #{t[:name].inspect}" 14 | if t[:parameters] 15 | $stdout.puts "Parameters: #{t[:parameters].inspect}" 16 | end 17 | if t[:description] 18 | $stdout.puts "Description: #{t[:description].inspect}" 19 | end 20 | $stdout.puts 'Default' if t[:default] 21 | end 22 | end 23 | """ 24 | When I run `cap -vT` 25 | Then the output should contain: 26 | """ 27 | 28 | Name: "my_namespace" 29 | Description: "A task that shadows a namespace" 30 | Default 31 | 32 | Name: "my_namespace:in_a_namespace" 33 | Description: "My task in a namespace" 34 | 35 | Name: "my_namespace:my_nested_namespace:in_a_nested_namespace" 36 | Description: "My task in a nested namespace" 37 | """ 38 | And the output should not contain "long" 39 | -------------------------------------------------------------------------------- /spec/cape/util_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/util' 3 | 4 | describe Cape::Util do 5 | describe '.pluralize' do 6 | it "transforms 'foo' as expected" do 7 | expect(Cape::Util.pluralize('foo')).to eq('foos') 8 | end 9 | 10 | it "transforms 'foo' as expected for a count of 2" do 11 | expect(Cape::Util.pluralize('foo', 2)).to eq('foos') 12 | end 13 | 14 | it "does not transform 'foo' for a count of 1" do 15 | expect(Cape::Util.pluralize('foo', 1)).to eq('foo') 16 | end 17 | 18 | it "transforms 'foo' as expected for a count of 0" do 19 | expect(Cape::Util.pluralize('foo', 0)).to eq('foos') 20 | end 21 | 22 | it "transforms 'foo' as expected for a count of -1" do 23 | expect(Cape::Util.pluralize('foo', -1)).to eq('foos') 24 | end 25 | end 26 | 27 | describe '.to_list_phrase' do 28 | it 'makes the expected phrase of an empty array' do 29 | expect(Cape::Util.to_list_phrase([])).to eq('') 30 | end 31 | 32 | it 'makes the expected phrase of a 1-element array' do 33 | expect(Cape::Util.to_list_phrase(%w(foo))).to eq('foo') 34 | end 35 | 36 | it 'makes the expected phrase of a 2-element array' do 37 | expect(Cape::Util.to_list_phrase(%w(foo bar))).to eq('foo and bar') 38 | end 39 | 40 | it 'makes the expected phrase of a 3-element array' do 41 | array = %w(foo bar baz) 42 | expect(Cape::Util.to_list_phrase(array)).to eq('foo, bar, and baz') 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.cd { release_path } 14 | end 15 | end 16 | """ 17 | When I run `cap -vT` 18 | Then the output should contain: 19 | """ 20 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 21 | """ 22 | And the output should not contain "with_one_arg" 23 | And the output should not contain "my_namespace" 24 | 25 | Scenario: mirror the matching Rake task with its implementation 26 | Given a full-featured Rakefile 27 | And a Capfile with: 28 | """ 29 | set :release_path, '/release/path' 30 | 31 | Cape do 32 | mirror_rake_tasks :long do |recipes| 33 | recipes.cd { release_path } 34 | end 35 | end 36 | """ 37 | When I run `cap long` 38 | Then the output should contain: 39 | """ 40 | * executing `long' 41 | """ 42 | And the output should contain: 43 | """ 44 | `long' is only run for servers matching {}, but no servers matched 45 | """ 46 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_valid_options.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task and valid options 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.options[:roles] = :app 14 | end 15 | end 16 | """ 17 | When I run `cap -vT` 18 | Then the output should contain: 19 | """ 20 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 21 | """ 22 | And the output should not contain "with_one_arg" 23 | And the output should not contain "my_namespace" 24 | 25 | Scenario: mirror the matching Rake task with its implementation 26 | Given a full-featured Rakefile 27 | And a Capfile with: 28 | """ 29 | set :current_path, '/current/path' 30 | 31 | Cape do 32 | mirror_rake_tasks 'long' do |recipes| 33 | recipes.options[:roles] = :app 34 | end 35 | end 36 | """ 37 | When I run `cap long` 38 | Then the output should contain: 39 | """ 40 | * executing `long' 41 | """ 42 | And the output should contain: 43 | """ 44 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 45 | """ 46 | -------------------------------------------------------------------------------- /gemfiles/rake_v0.9.3.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | cape (1.8.0) 5 | 6 | GEM 7 | remote: http://rubygems.org/ 8 | specs: 9 | aruba (0.5.4) 10 | childprocess (>= 0.3.6) 11 | cucumber (>= 1.1.1) 12 | rspec-expectations (>= 2.7.0) 13 | builder (3.2.2) 14 | capistrano (2.15.5) 15 | highline 16 | net-scp (>= 1.0.0) 17 | net-sftp (>= 2.0.0) 18 | net-ssh (>= 2.0.14) 19 | net-ssh-gateway (>= 1.1.0) 20 | childprocess (0.3.9) 21 | ffi (~> 1.0, >= 1.0.11) 22 | cucumber (1.3.10) 23 | builder (>= 2.1.2) 24 | diff-lcs (>= 1.1.3) 25 | gherkin (~> 2.12) 26 | multi_json (>= 1.7.5, < 2.0) 27 | multi_test (>= 0.0.2) 28 | diff-lcs (1.2.5) 29 | ffi (1.9.3) 30 | gherkin (2.12.2) 31 | multi_json (~> 1.3) 32 | highline (1.6.20) 33 | json_pure (1.8.1) 34 | multi_json (1.8.4) 35 | multi_test (0.0.3) 36 | net-scp (1.1.2) 37 | net-ssh (>= 2.6.5) 38 | net-sftp (2.1.2) 39 | net-ssh (>= 2.6.5) 40 | net-ssh (2.7.0) 41 | net-ssh-gateway (1.2.0) 42 | net-ssh (>= 2.6.5) 43 | rake (0.9.3) 44 | rspec (2.14.1) 45 | rspec-core (~> 2.14.0) 46 | rspec-expectations (~> 2.14.0) 47 | rspec-mocks (~> 2.14.0) 48 | rspec-core (2.14.7) 49 | rspec-expectations (2.14.4) 50 | diff-lcs (>= 1.1.3, < 2.0) 51 | rspec-mocks (2.14.4) 52 | 53 | PLATFORMS 54 | ruby 55 | 56 | DEPENDENCIES 57 | aruba (~> 0) 58 | cape! 59 | capistrano (~> 2) 60 | json_pure 61 | rake (= 0.9.3) 62 | rspec (~> 2) 63 | -------------------------------------------------------------------------------- /gemfiles/rake_v10.x.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | cape (1.8.0) 5 | 6 | GEM 7 | remote: http://rubygems.org/ 8 | specs: 9 | aruba (0.5.4) 10 | childprocess (>= 0.3.6) 11 | cucumber (>= 1.1.1) 12 | rspec-expectations (>= 2.7.0) 13 | builder (3.2.2) 14 | capistrano (2.15.5) 15 | highline 16 | net-scp (>= 1.0.0) 17 | net-sftp (>= 2.0.0) 18 | net-ssh (>= 2.0.14) 19 | net-ssh-gateway (>= 1.1.0) 20 | childprocess (0.3.9) 21 | ffi (~> 1.0, >= 1.0.11) 22 | cucumber (1.3.10) 23 | builder (>= 2.1.2) 24 | diff-lcs (>= 1.1.3) 25 | gherkin (~> 2.12) 26 | multi_json (>= 1.7.5, < 2.0) 27 | multi_test (>= 0.0.2) 28 | diff-lcs (1.2.5) 29 | ffi (1.9.3) 30 | gherkin (2.12.2) 31 | multi_json (~> 1.3) 32 | highline (1.6.20) 33 | json_pure (1.8.1) 34 | multi_json (1.8.4) 35 | multi_test (0.0.3) 36 | net-scp (1.1.2) 37 | net-ssh (>= 2.6.5) 38 | net-sftp (2.1.2) 39 | net-ssh (>= 2.6.5) 40 | net-ssh (2.7.0) 41 | net-ssh-gateway (1.2.0) 42 | net-ssh (>= 2.6.5) 43 | rake (10.1.1) 44 | rspec (2.14.1) 45 | rspec-core (~> 2.14.0) 46 | rspec-expectations (~> 2.14.0) 47 | rspec-mocks (~> 2.14.0) 48 | rspec-core (2.14.7) 49 | rspec-expectations (2.14.4) 50 | diff-lcs (>= 1.1.3, < 2.0) 51 | rspec-mocks (2.14.4) 52 | 53 | PLATFORMS 54 | ruby 55 | 56 | DEPENDENCIES 57 | aruba (~> 0) 58 | cape! 59 | capistrano (~> 2) 60 | json_pure 61 | rake (~> 10) 62 | rspec (~> 2) 63 | -------------------------------------------------------------------------------- /gemfiles/capistrano_v2.x.gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: ../ 3 | specs: 4 | cape (1.8.0) 5 | 6 | GEM 7 | remote: http://rubygems.org/ 8 | specs: 9 | aruba (0.5.4) 10 | childprocess (>= 0.3.6) 11 | cucumber (>= 1.1.1) 12 | rspec-expectations (>= 2.7.0) 13 | builder (3.2.2) 14 | capistrano (2.15.5) 15 | highline 16 | net-scp (>= 1.0.0) 17 | net-sftp (>= 2.0.0) 18 | net-ssh (>= 2.0.14) 19 | net-ssh-gateway (>= 1.1.0) 20 | childprocess (0.3.9) 21 | ffi (~> 1.0, >= 1.0.11) 22 | cucumber (1.3.10) 23 | builder (>= 2.1.2) 24 | diff-lcs (>= 1.1.3) 25 | gherkin (~> 2.12) 26 | multi_json (>= 1.7.5, < 2.0) 27 | multi_test (>= 0.0.2) 28 | diff-lcs (1.2.5) 29 | ffi (1.9.3) 30 | gherkin (2.12.2) 31 | multi_json (~> 1.3) 32 | highline (1.6.20) 33 | json_pure (1.8.1) 34 | multi_json (1.8.4) 35 | multi_test (0.0.3) 36 | net-scp (1.1.2) 37 | net-ssh (>= 2.6.5) 38 | net-sftp (2.1.2) 39 | net-ssh (>= 2.6.5) 40 | net-ssh (2.7.0) 41 | net-ssh-gateway (1.2.0) 42 | net-ssh (>= 2.6.5) 43 | rake (10.1.1) 44 | rspec (2.14.1) 45 | rspec-core (~> 2.14.0) 46 | rspec-expectations (~> 2.14.0) 47 | rspec-mocks (~> 2.14.0) 48 | rspec-core (2.14.7) 49 | rspec-expectations (2.14.4) 50 | diff-lcs (>= 1.1.3, < 2.0) 51 | rspec-mocks (2.14.4) 52 | 53 | PLATFORMS 54 | ruby 55 | 56 | DEPENDENCIES 57 | aruba (~> 0) 58 | cape! 59 | capistrano (~> 2) 60 | json_pure 61 | rake (>= 0.9.3) 62 | rspec (~> 2) 63 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.env['RAILS_ENV'] = lambda { rails_env } 14 | end 15 | end 16 | """ 17 | When I run `cap -vT` 18 | Then the output should contain: 19 | """ 20 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 21 | """ 22 | And the output should not contain "with_one_arg" 23 | And the output should not contain "my_namespace" 24 | 25 | Scenario: mirror the matching Rake task with its implementation 26 | Given a full-featured Rakefile 27 | And a Capfile with: 28 | """ 29 | set :current_path, '/current/path' 30 | set :rails_env, 'rails-env' 31 | 32 | Cape do 33 | mirror_rake_tasks 'long' do |recipes| 34 | recipes.env['RAILS_ENV'] = lambda { rails_env } 35 | end 36 | end 37 | """ 38 | When I run `cap long` 39 | Then the output should contain: 40 | """ 41 | * executing `long' 42 | """ 43 | And the output should contain: 44 | """ 45 | `long' is only run for servers matching {}, but no servers matched 46 | """ 47 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | interactor :off 2 | 3 | guard :rspec, :all_after_pass => true, 4 | :all_on_start => false, 5 | :keep_failed => false, 6 | :cmd => "bundle exec rspec --debug --format progress" do 7 | # Run the corresponding spec (or all specs) when code changes. 8 | watch(%r{^lib/(.+)\.rb$}) do |match| 9 | Dir["spec/#{match[1]}_spec.rb"].first || 'spec' 10 | end 11 | 12 | # Run a spec when it changes. 13 | watch %r{^spec/.+_spec\.rb$} 14 | 15 | # Run all specs when the RSpec configuration changes. 16 | watch('.rspec' ) { 'spec' } 17 | watch('spec/spec_helper.rb') { 'spec' } 18 | 19 | # Run all specs when the bundle changes. 20 | watch('Gemfile.lock') { 'spec' } 21 | end 22 | 23 | guard :cucumber, :all_after_pass => true, 24 | :all_on_start => false, 25 | :keep_failed => false, 26 | :focus_on => 'focus' do 27 | # Run run all features when code changes. 28 | watch(%r{^lib/(.+)\.rb$}) { 'features' } 29 | 30 | # Run the corresponding feature (or all features) when a step definition 31 | # changes. 32 | watch(%r{^features/step_definitions\.rb$}) { 'features' } 33 | watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |match| 34 | Dir["features/**/#{match[1]}.feature"].first || 'features' 35 | end 36 | 37 | # Run a feature when it changes. 38 | watch %r{^features/.+\.feature$} 39 | 40 | # Run all features when the Cucumber configuration changes. 41 | watch(%r{^features/support/.+$}) { 'features' } 42 | 43 | # Run all features when the bundle changes. 44 | watch('Gemfile.lock') { 'features' } 45 | end 46 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_valid_options_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, valid options, and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.options[:roles] = :app 14 | recipes.cd { release_path } 15 | end 16 | end 17 | """ 18 | When I run `cap -vT` 19 | Then the output should contain: 20 | """ 21 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 22 | """ 23 | And the output should not contain "with_one_arg" 24 | And the output should not contain "my_namespace" 25 | 26 | Scenario: mirror the matching Rake task with its implementation 27 | Given a full-featured Rakefile 28 | And a Capfile with: 29 | """ 30 | set :release_path, '/release/path' 31 | 32 | Cape do 33 | mirror_rake_tasks :long do |recipes| 34 | recipes.options[:roles] = :app 35 | recipes.cd { release_path } 36 | end 37 | end 38 | """ 39 | When I run `cap long` 40 | Then the output should contain: 41 | """ 42 | * executing `long' 43 | """ 44 | And the output should contain: 45 | """ 46 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 47 | """ 48 | -------------------------------------------------------------------------------- /lib/cape.rb: -------------------------------------------------------------------------------- 1 | # Contains the implementation of Cape. 2 | module Cape 3 | 4 | autoload :Capistrano, 'cape/capistrano' 5 | autoload :CoreExt, 'cape/core_ext' 6 | autoload :DSL, 'cape/dsl' 7 | autoload :HashList, 'cape/hash_list' 8 | autoload :Rake, 'cape/rake' 9 | autoload :RecipeDefinition, 'cape/recipe_definition' 10 | autoload :Util, 'cape/util' 11 | autoload :VERSION, 'cape/version' 12 | autoload :XTerm, 'cape/xterm' 13 | 14 | extend DSL 15 | 16 | end 17 | 18 | # The method used to group Cape statements. 19 | # 20 | # @param [Proc] block Cape and Capistrano statements 21 | # 22 | # @return [Cape] the Cape module 23 | # 24 | # @yield [cape] a block containing Cape statements 25 | # @yieldparam [Cape::DSL] cape the Cape DSL; optional 26 | # 27 | # @example Basic Cape usage 28 | # # config/deploy.rb 29 | # 30 | # require 'cape' 31 | # 32 | # Cape do 33 | # mirror_rake_tasks 34 | # end 35 | # 36 | # @example Combining Cape statements with Capistrano statements 37 | # # config/deploy.rb 38 | # 39 | # require 'cape' 40 | # 41 | # namespace :rake_tasks do 42 | # # Use an argument with the Cape block, if you want to or need to. 43 | # Cape do |cape| 44 | # cape.mirror_rake_tasks 45 | # end 46 | # end 47 | def Cape(&block) 48 | Cape.module_eval do 49 | @outer_self = block.binding.eval('self', __FILE__, __LINE__) 50 | begin 51 | if 0 < block.arity 52 | block.call self 53 | else 54 | module_eval(&block) 55 | end 56 | ensure 57 | rake.expire_cache! 58 | end 59 | end 60 | Cape 61 | end 62 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task and renaming logic 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | end 17 | end 18 | """ 19 | When I run `cap -vT` 20 | Then the output should contain: 21 | """ 22 | cap do_load # A task that shadows a Ruby method. 23 | """ 24 | And the output should not contain "long" 25 | And the output should not contain "my_namespace" 26 | 27 | Scenario: mirror the matching Rake task with its implementation 28 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 29 | And a Capfile with: 30 | """ 31 | set :current_path, '/current/path' 32 | 33 | Cape do 34 | mirror_rake_tasks :load do |recipes| 35 | recipes.rename do |task_name| 36 | "do_#{task_name}" 37 | end 38 | end 39 | end 40 | """ 41 | When I run `cap do_load` 42 | Then the output should contain: 43 | """ 44 | * executing `do_load' 45 | """ 46 | And the output should contain: 47 | """ 48 | `do_load' is only run for servers matching {}, but no servers matched 49 | """ 50 | -------------------------------------------------------------------------------- /cape.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path('../lib', __FILE__) 3 | require 'cape/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'cape' 7 | s.version = Cape::VERSION 8 | s.summary = 'Dynamically generates Capistrano recipes for Rake tasks' 9 | s.description = <<-end_description.gsub(/^\s+/, '').chomp 10 | Cape dynamically generates Capistrano recipes for Rake tasks. 11 | It provides a DSL for reflecting on Rake tasks and mirroring 12 | them as documented Capistrano recipes. You can pass Rake task 13 | arguments via environment variables. 14 | end_description 15 | s.platform = Gem::Platform::RUBY 16 | s.authors = ['Nils Jonsson'] 17 | s.email = %w(cape@nilsjonsson.com) 18 | s.homepage = 'http://njonsson.github.io/cape' 19 | s.license = 'MIT' 20 | 21 | s.rubyforge_project = 'cape' 22 | 23 | s.required_ruby_version = '>= 1.8.7' 24 | 25 | s.files = `git ls-files`.split("\n") 26 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 27 | s.executables = `git ls-files -- bin/*`.split("\n").map do |f| 28 | File.basename f 29 | end 30 | s.require_paths = %w(lib) 31 | s.has_rdoc = true 32 | 33 | s.add_development_dependency 'aruba', '~> 0' 34 | s.add_development_dependency 'capistrano', '~> 2' 35 | s.add_development_dependency 'rake', '>= 0.9.3' 36 | s.add_development_dependency 'rspec', '~> 2' 37 | 38 | # Avoid Cucumber's warnings about JSON parser performance. 39 | s.add_development_dependency 'json_pure' 40 | end 41 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, a different directory, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.cd { release_path } 14 | recipes.env['RAILS_ENV'] = lambda { rails_env } 15 | end 16 | end 17 | """ 18 | When I run `cap -vT` 19 | Then the output should contain: 20 | """ 21 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 22 | """ 23 | And the output should not contain "with_one_arg" 24 | And the output should not contain "my_namespace" 25 | 26 | Scenario: mirror the matching Rake task with its implementation 27 | Given a full-featured Rakefile 28 | And a Capfile with: 29 | """ 30 | set :release_path, '/release/path' 31 | set :rails_env, 'rails-env' 32 | 33 | Cape do 34 | mirror_rake_tasks :long do |recipes| 35 | recipes.cd { release_path } 36 | recipes.env['RAILS_ENV'] = lambda { rails_env } 37 | end 38 | end 39 | """ 40 | When I run `cap long` 41 | Then the output should contain: 42 | """ 43 | * executing `long' 44 | """ 45 | And the output should contain: 46 | """ 47 | `long' is only run for servers matching {}, but no servers matched 48 | """ 49 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_valid_options_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, valid options, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.options[:roles] = :app 14 | recipes.env['RAILS_ENV'] = lambda { rails_env } 15 | end 16 | end 17 | """ 18 | When I run `cap -vT` 19 | Then the output should contain: 20 | """ 21 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 22 | """ 23 | And the output should not contain "with_one_arg" 24 | And the output should not contain "my_namespace" 25 | 26 | Scenario: mirror the matching Rake task with its implementation 27 | Given a full-featured Rakefile 28 | And a Capfile with: 29 | """ 30 | set :current_path, '/current/path' 31 | set :rails_env, 'rails-env' 32 | 33 | Cape do 34 | mirror_rake_tasks 'long' do |recipes| 35 | recipes.options[:roles] = :app 36 | recipes.env['RAILS_ENV'] = lambda { rails_env } 37 | end 38 | end 39 | """ 40 | When I run `cap long` 41 | Then the output should contain: 42 | """ 43 | * executing `long' 44 | """ 45 | And the output should contain: 46 | """ 47 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 48 | """ 49 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.cd { release_path } 17 | end 18 | end 19 | """ 20 | When I run `cap -vT` 21 | Then the output should contain: 22 | """ 23 | cap do_load # A task that shadows a Ruby method. 24 | """ 25 | And the output should not contain "long" 26 | And the output should not contain "my_namespace" 27 | 28 | Scenario: mirror the matching Rake task with its implementation 29 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 30 | And a Capfile with: 31 | """ 32 | set :release_path, '/release/path' 33 | 34 | Cape do 35 | mirror_rake_tasks :load do |recipes| 36 | recipes.rename do |task_name| 37 | "do_#{task_name}" 38 | end 39 | recipes.cd { release_path } 40 | end 41 | end 42 | """ 43 | When I run `cap do_load` 44 | Then the output should contain: 45 | """ 46 | * executing `do_load' 47 | """ 48 | And the output should contain: 49 | """ 50 | `do_load' is only run for servers matching {}, but no servers matched 51 | """ 52 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_valid_options.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, and valid options 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.options[:roles] = :app 17 | end 18 | end 19 | """ 20 | When I run `cap -vT` 21 | Then the output should contain: 22 | """ 23 | cap do_load # A task that shadows a Ruby method. 24 | """ 25 | And the output should not contain "long" 26 | And the output should not contain "my_namespace" 27 | 28 | Scenario: mirror the matching Rake task with its implementation 29 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 30 | And a Capfile with: 31 | """ 32 | set :current_path, '/current/path' 33 | 34 | Cape do 35 | mirror_rake_tasks :load do |recipes| 36 | recipes.rename do |task_name| 37 | "do_#{task_name}" 38 | end 39 | recipes.options[:roles] = :app 40 | end 41 | end 42 | """ 43 | When I run `cap do_load` 44 | Then the output should contain: 45 | """ 46 | * executing `do_load' 47 | """ 48 | And the output should contain: 49 | """ 50 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 51 | """ 52 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_valid_options_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, valid options, a different directory, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :long do |recipes| 13 | recipes.options[:roles] = :app 14 | recipes.cd { release_path } 15 | recipes.env['RAILS_ENV'] = lambda { rails_env } 16 | end 17 | end 18 | """ 19 | When I run `cap -vT` 20 | Then the output should contain: 21 | """ 22 | cap long # My long task -- it has a very, very, very, very, very, very, ver... 23 | """ 24 | And the output should not contain "with_one_arg" 25 | And the output should not contain "my_namespace" 26 | 27 | Scenario: mirror the matching Rake task with its implementation 28 | Given a full-featured Rakefile 29 | And a Capfile with: 30 | """ 31 | set :release_path, '/release/path' 32 | set :rails_env, 'rails-env' 33 | 34 | Cape do 35 | mirror_rake_tasks :long do |recipes| 36 | recipes.options[:roles] = :app 37 | recipes.cd { release_path } 38 | recipes.env['RAILS_ENV'] = lambda { rails_env } 39 | end 40 | end 41 | """ 42 | When I run `cap long` 43 | Then the output should contain: 44 | """ 45 | * executing `long' 46 | """ 47 | And the output should contain: 48 | """ 49 | `long' is only run for servers matching {:roles=>:app}, but no servers matched 50 | """ 51 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.env['RAILS_ENV'] = lambda { rails_env } 17 | end 18 | end 19 | """ 20 | When I run `cap -vT` 21 | Then the output should contain: 22 | """ 23 | cap do_load # A task that shadows a Ruby method. 24 | """ 25 | And the output should not contain "long" 26 | And the output should not contain "my_namespace" 27 | 28 | Scenario: mirror the matching Rake task with its implementation 29 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 30 | And a Capfile with: 31 | """ 32 | set :current_path, '/current/path' 33 | set :rails_env, 'rails-env' 34 | 35 | Cape do 36 | mirror_rake_tasks :load do |recipes| 37 | recipes.rename do |task_name| 38 | "do_#{task_name}" 39 | end 40 | recipes.env['RAILS_ENV'] = lambda { rails_env } 41 | end 42 | end 43 | """ 44 | When I run `cap do_load` 45 | Then the output should contain: 46 | """ 47 | * executing `do_load' 48 | """ 49 | And the output should contain: 50 | """ 51 | `do_load' is only run for servers matching {}, but no servers matched 52 | """ 53 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_valid_options_and_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, valid options, and a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.options[:roles] = :app 17 | recipes.cd { release_path } 18 | end 19 | end 20 | """ 21 | When I run `cap -vT` 22 | Then the output should contain: 23 | """ 24 | cap do_load # A task that shadows a Ruby method. 25 | """ 26 | And the output should not contain "long" 27 | And the output should not contain "my_namespace" 28 | 29 | Scenario: mirror the matching Rake task with its implementation 30 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 31 | And a Capfile with: 32 | """ 33 | set :release_path, '/release/path' 34 | 35 | Cape do 36 | mirror_rake_tasks :load do |recipes| 37 | recipes.rename do |task_name| 38 | "do_#{task_name}" 39 | end 40 | recipes.options[:roles] = :app 41 | recipes.cd { release_path } 42 | end 43 | end 44 | """ 45 | When I run `cap do_load` 46 | Then the output should contain: 47 | """ 48 | * executing `do_load' 49 | """ 50 | And the output should contain: 51 | """ 52 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 53 | """ 54 | -------------------------------------------------------------------------------- /features/dsl/each_rake_task/unqualified.feature: -------------------------------------------------------------------------------- 1 | Feature: The #each_rake_task DSL method 2 | 3 | In order to use the metadata of Rake tasks in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: enumerate all Rake tasks 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | each_rake_task do |t| 13 | $stdout.puts '', "Name: #{t[:name].inspect}" 14 | if t[:parameters] 15 | $stdout.puts "Parameters: #{t[:parameters].inspect}" 16 | end 17 | if t[:description] 18 | $stdout.puts "Description: #{t[:description].inspect}" 19 | end 20 | $stdout.puts 'Default' if t[:default] 21 | end 22 | end 23 | """ 24 | When I run `cap -vT` 25 | Then the output should contain: 26 | """ 27 | 28 | Name: "hidden_task" 29 | Description: "" 30 | 31 | Name: "long" 32 | Description: "My long task -- it has a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long description" 33 | 34 | Name: "my_namespace" 35 | Description: "A task that shadows a namespace" 36 | Default 37 | 38 | Name: "my_namespace:in_a_namespace" 39 | Description: "My task in a namespace" 40 | 41 | Name: "my_namespace:my_nested_namespace:in_a_nested_namespace" 42 | Description: "My task in a nested namespace" 43 | 44 | Name: "with_one_arg" 45 | Parameters: ["the_arg"] 46 | Description: "My task with one argument" 47 | 48 | Name: "with_three_args" 49 | Parameters: ["an_arg1", "an_arg2", "an_arg3"] 50 | Description: "My task with three arguments" 51 | 52 | Name: "with_two_args" 53 | Parameters: ["my_arg1", "my_arg2"] 54 | Description: "My task with two arguments" 55 | """ 56 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, a different directory, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.cd { release_path } 17 | recipes.env['RAILS_ENV'] = lambda { rails_env } 18 | end 19 | end 20 | """ 21 | When I run `cap -vT` 22 | Then the output should contain: 23 | """ 24 | cap do_load # A task that shadows a Ruby method. 25 | """ 26 | And the output should not contain "long" 27 | And the output should not contain "my_namespace" 28 | 29 | Scenario: mirror the matching Rake task with its implementation 30 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 31 | And a Capfile with: 32 | """ 33 | set :release_path, '/release/path' 34 | set :rails_env, 'rails-env' 35 | 36 | Cape do 37 | mirror_rake_tasks :load do |recipes| 38 | recipes.rename do |task_name| 39 | "do_#{task_name}" 40 | end 41 | recipes.cd { release_path } 42 | recipes.env['RAILS_ENV'] = lambda { rails_env } 43 | end 44 | end 45 | """ 46 | When I run `cap do_load` 47 | Then the output should contain: 48 | """ 49 | * executing `do_load' 50 | """ 51 | And the output should contain: 52 | """ 53 | `do_load' is only run for servers matching {}, but no servers matched 54 | """ 55 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_valid_options_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, valid options, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.options[:roles] = :app 17 | recipes.env['RAILS_ENV'] = lambda { rails_env } 18 | end 19 | end 20 | """ 21 | When I run `cap -vT` 22 | Then the output should contain: 23 | """ 24 | cap do_load # A task that shadows a Ruby method. 25 | """ 26 | And the output should not contain "long" 27 | And the output should not contain "my_namespace" 28 | 29 | Scenario: mirror the matching Rake task with its implementation 30 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 31 | And a Capfile with: 32 | """ 33 | set :current_path, '/current/path' 34 | set :rails_env, 'rails-env' 35 | 36 | Cape do 37 | mirror_rake_tasks :load do |recipes| 38 | recipes.rename do |task_name| 39 | "do_#{task_name}" 40 | end 41 | recipes.options[:roles] = :app 42 | recipes.env['RAILS_ENV'] = lambda { rails_env } 43 | end 44 | end 45 | """ 46 | When I run `cap do_load` 47 | Then the output should contain: 48 | """ 49 | * executing `do_load' 50 | """ 51 | And the output should contain: 52 | """ 53 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 54 | """ 55 | -------------------------------------------------------------------------------- /spec/cape/recipe_definition_spec.rb: -------------------------------------------------------------------------------- 1 | require 'cape/recipe_definition' 2 | 3 | describe Cape::RecipeDefinition do 4 | subject(:recipe_definition) { recipe_definition_class.new } 5 | 6 | let(:recipe_definition_class) { described_class } 7 | 8 | describe '#cd' do 9 | specify { expect(recipe_definition.cd).to be_nil } 10 | 11 | it 'is mutable' do 12 | recipe_definition.cd '/foo/bar' 13 | expect(recipe_definition.cd).to eq('/foo/bar') 14 | 15 | recipe_definition.cd lambda { '/foo/bar' } 16 | expect(recipe_definition.cd.call).to eq('/foo/bar') 17 | 18 | recipe_definition.cd { '/foo/bar' } 19 | expect(recipe_definition.cd.call).to eq('/foo/bar') 20 | end 21 | 22 | it 'complains about wrong arity' do 23 | expect { 24 | recipe_definition.cd do |foo, bar| 25 | end 26 | }.to raise_error(ArgumentError, 'Must have 0 parameters but has 2') 27 | end 28 | end 29 | 30 | describe '#env' do 31 | specify { expect(recipe_definition.env).to eq({}) } 32 | 33 | it 'is mutable' do 34 | recipe_definition.env['FOO'] = 'bar' 35 | expect(recipe_definition.env).to eq('FOO' => 'bar') 36 | end 37 | end 38 | 39 | describe '#options' do 40 | specify { expect(recipe_definition.options).to eq({}) } 41 | 42 | it 'is mutable' do 43 | recipe_definition.options[:some_option] = 'foo' 44 | expect(recipe_definition.options).to eq(:some_option => 'foo') 45 | end 46 | end 47 | 48 | describe '#rename' do 49 | specify { expect(recipe_definition.rename).to be_nil } 50 | 51 | it 'is mutable' do 52 | recipe_definition.rename do |task_name| 53 | "#{task_name}_recipe" 54 | end 55 | expect(recipe_definition.rename.call(:foo)).to eq('foo_recipe') 56 | end 57 | 58 | it 'complains about wrong arity' do 59 | expect { 60 | recipe_definition.rename do |foo, bar| 61 | end 62 | }.to raise_error(ArgumentError, 'Must have 1 parameter but has 2') 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/cape/hash_list_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/hash_list' 3 | 4 | describe Cape::HashList do 5 | subject(:hash_list) { hash_list_class.new } 6 | 7 | let(:hash_list_class) { described_class } 8 | 9 | describe 'that is empty' do 10 | specify { expect(hash_list).to be_empty } 11 | 12 | describe '#inspect' do 13 | specify { expect(hash_list.inspect).to eq('{}') } 14 | end 15 | 16 | describe '#to_a' do 17 | specify { expect(hash_list.to_a).to eq([]) } 18 | end 19 | 20 | describe '#to_hash' do 21 | specify { expect(hash_list.to_hash).to eq({}) } 22 | end 23 | 24 | describe 'when values are added out of order' do 25 | before :each do 26 | hash_list['foo'] = 'xxx' 27 | hash_list['foo'] = 'bar' 28 | hash_list['baz'] = 'qux' 29 | end 30 | 31 | specify { expect(hash_list).to eq({'foo' => 'bar', 'baz' => 'qux'}) } 32 | 33 | describe '#inspect' do 34 | specify { 35 | expect(hash_list.inspect).to eq('{"foo"=>"bar", "baz"=>"qux"}') 36 | } 37 | end 38 | 39 | describe '#to_a' do 40 | specify { expect(hash_list.to_a).to eq([%w(foo bar), %w(baz qux)]) } 41 | end 42 | 43 | describe '#to_hash' do 44 | specify { 45 | expect(hash_list.to_hash).to eq({'foo' => 'bar', 'baz' => 'qux'}) 46 | } 47 | end 48 | end 49 | end 50 | 51 | describe 'that has values out of order' do 52 | subject(:hash_list) { hash_list_class.new 'foo' => 'bar', 'baz' => 'qux' } 53 | 54 | specify { expect(hash_list).to eq({'foo' => 'bar', 'baz' => 'qux'}) } 55 | 56 | it 'indexes the values as expected' do 57 | expect(hash_list['foo']).to eq('bar') 58 | expect(hash_list['baz']).to eq('qux') 59 | expect(hash_list['not-found']).to be_nil 60 | end 61 | 62 | describe '#clear' do 63 | before :each do 64 | hash_list.clear 65 | end 66 | 67 | specify { expect(hash_list).to be_empty } 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_task_and_rename_and_valid_options_and_cd_and_environment_variables.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined task, renaming logic, valid options, a different directory, and environment variables 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the matching Rake task 8 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :load do |recipes| 13 | recipes.rename do |task_name| 14 | "do_#{task_name}" 15 | end 16 | recipes.options[:roles] = :app 17 | recipes.cd { release_path } 18 | recipes.env['RAILS_ENV'] = lambda { rails_env } 19 | end 20 | end 21 | """ 22 | When I run `cap -vT` 23 | Then the output should contain: 24 | """ 25 | cap do_load # A task that shadows a Ruby method. 26 | """ 27 | And the output should not contain "long" 28 | And the output should not contain "my_namespace" 29 | 30 | Scenario: mirror the matching Rake task with its implementation 31 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 32 | And a Capfile with: 33 | """ 34 | set :release_path, '/release/path' 35 | set :rails_env, 'rails-env' 36 | 37 | Cape do 38 | mirror_rake_tasks :load do |recipes| 39 | recipes.rename do |task_name| 40 | "do_#{task_name}" 41 | end 42 | recipes.options[:roles] = :app 43 | recipes.cd { release_path } 44 | recipes.env['RAILS_ENV'] = lambda { rails_env } 45 | end 46 | end 47 | """ 48 | When I run `cap do_load` 49 | Then the output should contain: 50 | """ 51 | * executing `do_load' 52 | """ 53 | And the output should contain: 54 | """ 55 | `do_load' is only run for servers matching {:roles=>:app}, but no servers matched 56 | """ 57 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_cd.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a different directory 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror a Rake task with its implementation, using a Capistrano variable inside a lambda 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | set :release_path, '/release/path' 12 | 13 | Cape do 14 | mirror_rake_tasks do |recipes| 15 | recipes.cd lambda { release_path } 16 | end 17 | end 18 | """ 19 | When I run `cap long` 20 | Then the output should contain: 21 | """ 22 | * executing `long' 23 | """ 24 | And the output should contain: 25 | """ 26 | `long' is only run for servers matching {}, but no servers matched 27 | """ 28 | 29 | Scenario: mirror a Rake task with its implementation, using a Capistrano variable inside a block 30 | Given a full-featured Rakefile 31 | And a Capfile with: 32 | """ 33 | set :release_path, '/release/path' 34 | 35 | Cape do 36 | mirror_rake_tasks do |recipes| 37 | recipes.cd { release_path } 38 | end 39 | end 40 | """ 41 | When I run `cap long` 42 | Then the output should contain: 43 | """ 44 | * executing `long' 45 | """ 46 | And the output should contain: 47 | """ 48 | `long' is only run for servers matching {}, but no servers matched 49 | """ 50 | 51 | Scenario: mirror a Rake task with its implementation, using a string 52 | Given a full-featured Rakefile 53 | And a Capfile with: 54 | """ 55 | Cape do 56 | mirror_rake_tasks do |recipes| 57 | recipes.cd '/release/path' 58 | end 59 | end 60 | """ 61 | When I run `cap long` 62 | Then the output should contain: 63 | """ 64 | * executing `long' 65 | """ 66 | And the output should contain: 67 | """ 68 | `long' is only run for servers matching {}, but no servers matched 69 | """ 70 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/with_defined_namespace.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method with a defined namespace 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror only the Rake tasks in the matching namespace 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks :my_namespace 13 | end 14 | """ 15 | When I run `cap -vT` 16 | Then the output should contain: 17 | """ 18 | cap my_namespace # A task that shadows a names... 19 | """ 20 | And the output should contain: 21 | """ 22 | cap my_namespace:in_a_namespace # My task in a namespace. 23 | """ 24 | And the output should contain: 25 | """ 26 | cap my_namespace:my_nested_namespace:in_a_nested_namespace # My task in a nested namespace. 27 | """ 28 | And the output should not contain "long" 29 | 30 | Scenario: mirror a Rake task that shadows the matching namespace with its implementation 31 | Given a full-featured Rakefile 32 | And a Capfile with: 33 | """ 34 | set :current_path, '/current/path' 35 | 36 | Cape do 37 | mirror_rake_tasks 'my_namespace' 38 | end 39 | """ 40 | When I run `cap my_namespace` 41 | Then the output should contain: 42 | """ 43 | * executing `my_namespace' 44 | """ 45 | And the output should contain: 46 | """ 47 | `my_namespace' is only run for servers matching {}, but no servers matched 48 | """ 49 | 50 | Scenario: mirror a Rake task in the matching namespace with its implementation 51 | Given a full-featured Rakefile 52 | And a Capfile with: 53 | """ 54 | set :current_path, '/current/path' 55 | 56 | Cape do 57 | mirror_rake_tasks :my_namespace 58 | end 59 | """ 60 | When I run `cap my_namespace:my_nested_namespace:in_a_nested_namespace` 61 | Then the output should contain: 62 | """ 63 | * executing `my_namespace:my_nested_namespace:in_a_nested_namespace' 64 | """ 65 | And the output should contain: 66 | """ 67 | `my_namespace:my_nested_namespace:in_a_nested_namespace' is only run for servers matching {}, but no servers matched 68 | """ 69 | -------------------------------------------------------------------------------- /features/step_definitions.rb: -------------------------------------------------------------------------------- 1 | Given 'a Capfile with:' do |content| 2 | preamble = <<-end_preamble 3 | $:.unshift #{File.expand_path('../../lib', __FILE__).inspect} 4 | require 'cape' 5 | 6 | end_preamble 7 | step('a file named "Capfile" with:', (preamble + content)) 8 | end 9 | 10 | Given 'a full-featured Rakefile' do 11 | step 'a file named "Rakefile" with:', <<-end_step 12 | desc 'My long task -- it has a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long description' 13 | task :long 14 | 15 | desc 'My task with one argument' 16 | task :with_one_arg, [:the_arg] 17 | 18 | desc 'A task that shadows a namespace' 19 | task :my_namespace 20 | 21 | namespace :my_namespace do 22 | desc 'My task in a namespace' 23 | task :in_a_namespace 24 | 25 | namespace :my_nested_namespace do 26 | desc 'My task in a nested namespace' 27 | task :in_a_nested_namespace 28 | end 29 | end 30 | 31 | desc 'My task with two arguments' 32 | task :with_two_args, [:my_arg1, :my_arg2] 33 | 34 | desc 'My task with three arguments' 35 | task :with_three_args, [:an_arg1, :an_arg2, :an_arg3] 36 | 37 | task :hidden_task 38 | end_step 39 | end 40 | 41 | Given 'a full-featured Rakefile defining a Ruby-method-shadowing task' do 42 | step 'a file named "Rakefile" with:', <<-end_step 43 | desc 'A task that shadows a Ruby method' 44 | task :load 45 | 46 | desc 'My long task -- it has a very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very, very long description' 47 | task :long 48 | 49 | desc 'My task with one argument' 50 | task :with_one_arg, [:the_arg] 51 | 52 | desc 'A task that shadows a namespace' 53 | task :my_namespace 54 | 55 | namespace :my_namespace do 56 | desc 'My task in a namespace' 57 | task :in_a_namespace 58 | 59 | namespace :my_nested_namespace do 60 | desc 'My task in a nested namespace' 61 | task :in_a_nested_namespace 62 | end 63 | end 64 | 65 | desc 'My task with two arguments' 66 | task :with_two_args, [:my_arg1, :my_arg2] 67 | 68 | desc 'My task with three arguments' 69 | task :with_three_args, [:an_arg1, :an_arg2, :an_arg3] 70 | 71 | task :hidden_task 72 | end_step 73 | end 74 | -------------------------------------------------------------------------------- /History.markdown: -------------------------------------------------------------------------------- 1 | # Version history for the _Cape_ project 2 | 3 | ## v1.8.0, Mon 11/18/2013 4 | 5 | * Add official support for MRI v1.9.2 and v2.0 6 | * Add official support for Rake v10.x 7 | * Don’t crash if the user attempts to mirror a task whose name collides with a Ruby method 8 | 9 | ## v1.7.0, Thu 3/07/2013 10 | 11 | * Introduce a new DSL that supports task renaming and path switching, and deprecate the old one 12 | 13 | ## v1.6.2, Fri 2/22/2013 14 | 15 | * Correctly update environment variables when set more than once 16 | 17 | ## v1.6.1, Mon 2/18/2013 18 | 19 | * Respect the specified order of environment variables in the remote command line 20 | 21 | ## v1.6.0, Wed 11/14/2012 22 | 23 | * Enable mirroring and enumeration of hidden Rake tasks for versions of Rake that make it possible 24 | 25 | ## v1.5.0, Tue 10/09/2012 26 | 27 | * Automatically detect and use Bundler when running Rake 28 | 29 | ## v1.4.0, Mon 2/06/2012 30 | 31 | * Cache the Rake task list to improve Capistrano responsiveness 32 | 33 | ## v1.3.0, Fri 2/03/2012 34 | 35 | * Don’t allow `nil` environment variables to pass through to the remote command line 36 | 37 | ## v1.2.0, Wed 2/01/2012 38 | 39 | * Add support in the DSL for specifying remote environment variables and Capistrano recipe options 40 | * Add support for Rake tasks that overlap with (have the same full name as) namespaces 41 | * Match Rake tasks properly: by the full name of the task rather than a substring thereof 42 | * Don’t choke on unexpected output from Rake 43 | * Silence Rake stderr output while enumerating Rake tasks 44 | * Tweak the wording of generated Capistrano recipe descriptions 45 | * Tighten RubyGem dependency specifications in an effort to avoid potential compatibility issues 46 | 47 | ## v1.1.0, Thu 12/29/2011 48 | 49 | * Allow environment variables for Rake task arguments to be optional 50 | 51 | ## v1.0.3, Thu 12/29/2011 52 | 53 | * Run Cucumber features from `gem test cape` 54 | 55 | ## v1.0.2, Thu 12/29/2011 56 | 57 | * Support Rake task arguments that contain whitespace 58 | 59 | ## v1.0.1, Tue 11/29/2011 60 | 61 | * Don’t run Cucumber features from `gem test cape` because they fail 62 | 63 | ## v1.0.0, Mon 11/28/2011 64 | 65 | (First release) 66 | -------------------------------------------------------------------------------- /lib/cape/hash_list.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | # A HashList is a collection of key-value pairs. It is similar to an Array, 4 | # except that indexing is done via arbitrary keys of any object type, not an 5 | # integer index. Hashes enumerate their values in the order that the 6 | # corresponding keys were inserted. 7 | # 8 | # This class exists because in Ruby v1.8.7 and earlier, Hash did not preserve 9 | # the insertion order of keys. 10 | # 11 | # @api private 12 | class HashList < ::Array 13 | 14 | # Constructs a new HashList using the specified _arguments_. 15 | # 16 | # @param [Hash] arguments attribute values 17 | def initialize(*arguments) 18 | super Hash[*arguments].to_a 19 | end 20 | 21 | # Compares a HashList to another object. 22 | # 23 | # @param [Object] other another object 24 | # 25 | # @return [true] the HashList has the same number of keys as _other_, and 26 | # each key-value pair is equal (according to Object#==) 27 | # @return [false] the HashList is not equal to _other_ 28 | def ==(other) 29 | other.is_a?(::Hash) ? (other == to_hash) : super(other) 30 | end 31 | 32 | # Retrieves a value from the HashList. 33 | # 34 | # @param [Object] key a key 35 | # 36 | # @return [Object] the value for _key_ 37 | # @return [nil] if there is no value for _key_ 38 | def [](key) 39 | entry = find do |pair| 40 | Array(pair).first == key 41 | end 42 | entry ? Array(entry).last : nil 43 | end 44 | 45 | # Sets a value in the HashList. 46 | # 47 | # @param [Object] key a key 48 | # @param [Object] value a value for _key_ 49 | # 50 | # @return [HashList] the HashList 51 | def []=(key, value) 52 | index = find_index do |pair| 53 | Array(pair).first == key 54 | end 55 | if index 56 | super(index, [key, value]) 57 | else 58 | self << [key, value] 59 | end 60 | self 61 | end 62 | 63 | # Provides a string representation of the HashList. 64 | # 65 | # @return [String] a string representation of the HashList 66 | def inspect 67 | entries = collect do |pair| 68 | Array(pair).collect(&:inspect).join '=>' 69 | end 70 | "{#{entries.join ', '}}" 71 | end 72 | 73 | # Converts the HashList to a Hash. 74 | # 75 | # @return [Hash] a hash 76 | def to_hash 77 | ::Hash[to_a] 78 | end 79 | 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /features/dsl/rake_executable.feature: -------------------------------------------------------------------------------- 1 | Feature: The #local_rake_executable and #remote_rake_executable DSL attributes 2 | 3 | In order to control which Rake executables are used locally and remotely, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: use a different Rake executable to enumerate Rake tasks 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape.local_rake_executable = 'echo "rake this-comes-from-overridden-rake # This comes from overridden Rake" #' 12 | 13 | Cape do 14 | $stdout.puts "We changed the local Rake executable to #{local_rake_executable.inspect}." 15 | $stdout.puts "We left the remote Rake executable as #{remote_rake_executable.inspect}." 16 | each_rake_task do |t| 17 | $stdout.puts '', "Name: #{t[:name].inspect}" 18 | if t[:parameters] 19 | $stdout.puts "Parameters: #{t[:parameters].inspect}" 20 | end 21 | if t[:description] 22 | $stdout.puts "Description: #{t[:description].inspect}" 23 | end 24 | end 25 | end 26 | """ 27 | When I run `cap -vT` 28 | Then the output should contain: 29 | """ 30 | We changed the local Rake executable to "echo \"rake this-comes-from-overridden-rake # This comes from overridden Rake\" #" 31 | """ 32 | And the output should contain: 33 | """ 34 | We left the remote Rake executable as "/usr/bin/env `/usr/bin/env bundle check >/dev/null 2>&1; case $? in 0|1 ) echo bundle exec ;; esac` rake" 35 | """ 36 | And the output should contain: 37 | """ 38 | 39 | Name: "this-comes-from-overridden-rake" 40 | Description: "This comes from overridden Rake" 41 | """ 42 | 43 | Scenario: use a different Rake executable to execute Rake tasks 44 | Given a full-featured Rakefile 45 | And a Capfile with: 46 | """ 47 | set :current_path, '/current/path' 48 | 49 | Cape.remote_rake_executable = 'echo "This comes from overridden Rake" #' 50 | 51 | Cape do 52 | $stdout.puts "We changed the remote Rake executable to #{remote_rake_executable.inspect}." 53 | $stdout.puts "We left the local Rake executable as #{local_rake_executable.inspect}." 54 | mirror_rake_tasks 55 | end 56 | """ 57 | When I run `cap long` 58 | Then the output should contain: 59 | """ 60 | We changed the remote Rake executable to "echo \"This comes from overridden Rake\" #" 61 | """ 62 | And the output should contain: 63 | """ 64 | We left the local Rake executable as "/usr/bin/env `/usr/bin/env bundle check >/dev/null 2>&1; case $? in 0|1 ) echo bundle exec ;; esac` rake" 65 | """ 66 | -------------------------------------------------------------------------------- /spec/cape/xterm_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/xterm' 3 | 4 | describe Cape::XTerm do 5 | let(:xterm_module) { described_class } 6 | 7 | describe '.format' do 8 | it 'does not try to format a nil argument' do 9 | expect(xterm_module.format(nil)).to be_nil 10 | end 11 | 12 | it 'does not try to format a nil argument with a recognized format' do 13 | expect(xterm_module.format(nil, :bold)).to be_nil 14 | end 15 | 16 | specify do 17 | expect { 18 | xterm_module.format nil, :this_does_not_exist 19 | }.to raise_error(ArgumentError, 'Unrecognized format :this_does_not_exist') 20 | end 21 | 22 | specify do 23 | expect { 24 | xterm_module.format 'foo', :this_does_not_exist 25 | }.to raise_error(ArgumentError, 'Unrecognized format :this_does_not_exist') 26 | end 27 | 28 | described_class::FORMATS.each do |format, code| 29 | it "formats a String argument with the #{format.inspect} format" do 30 | expect(xterm_module.format('foo', 31 | format)).to eq("\e[#{code}mfoo\e[0m") 32 | end 33 | end 34 | 35 | it "formats a String argument with the :bold and :foreground_red formats" do 36 | expect(xterm_module.format('foo', 37 | :bold, 38 | :foreground_red)).to eq("\e[1;31mfoo\e[0m") 39 | end 40 | end 41 | 42 | specify { expect(xterm_module).not_to respond_to(:this_does_not_exist) } 43 | 44 | specify do 45 | expect { 46 | xterm_module.this_does_not_exist 47 | }.to raise_error(NoMethodError, 48 | "undefined method `this_does_not_exist' for #{xterm_module.name}:Module") 49 | end 50 | 51 | described_class::FORMATS.each do |format, code| 52 | specify { expect(xterm_module).to respond_to(format) } 53 | 54 | describe ".#{format}" do 55 | it "formats a String argument with the #{format.inspect} format" do 56 | expect(xterm_module.send(format, 57 | 'foo')).to eq(xterm_module.format('foo', 58 | format)) 59 | end 60 | end 61 | end 62 | 63 | specify { expect(xterm_module).to respond_to(:bold_and_foreground_red) } 64 | 65 | describe '.bold_and_foreground_red' do 66 | it "formats a String argument with the :bold and :foreground_red formats" do 67 | expect(xterm_module.bold_and_foreground_red('foo')).to eq(xterm_module.format('foo', 68 | :bold, 69 | :foreground_red)) 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/cape/recipe_definition.rb: -------------------------------------------------------------------------------- 1 | require 'cape/hash_list' 2 | 3 | module Cape 4 | 5 | # Determines how a Capistrano recipe will be defined. 6 | class RecipeDefinition 7 | 8 | # The remote directory from which Rake tasks will be executed. 9 | # 10 | # @overload cd 11 | # The remote directory from which Rake tasks will be executed. 12 | # 13 | # @return [String, Proc] the value or a block that returns the value 14 | # 15 | # @overload cd(path) 16 | # Sets the remote directory from which Rake tasks will be executed. 17 | # 18 | # @param [String, Proc] path the value or a callable object that returns 19 | # the value 20 | # 21 | # @return [String, Proc] _path_ 22 | # 23 | # @raise [ArgumentError] _path_ is a callable object that has parameters 24 | # 25 | # @overload cd(&block) 26 | # Sets the remote directory from which Rake tasks will be executed. 27 | # 28 | # @yieldreturn [String] the value 29 | # 30 | # @return [String, Proc] _block_ 31 | # 32 | # @raise [ArgumentError] _block_ has parameters 33 | def cd(path=nil, &block) 34 | if (cd = (path || block)) 35 | if cd.respond_to?(:arity) 36 | case cd.arity 37 | when -1 38 | # Lambda: good 39 | when 0 40 | # Good 41 | else 42 | raise ::ArgumentError, "Must have 0 parameters but has #{cd.arity}" 43 | end 44 | end 45 | 46 | @cd = cd 47 | else 48 | @cd 49 | end 50 | end 51 | 52 | # A hash of remote environment variables. 53 | # 54 | # @return [HashList] the desired environment of the remote computer 55 | def env 56 | @env ||= HashList.new 57 | end 58 | 59 | # A hash of Capistrano recipe options to pass to the Capistrano +task+ 60 | # method. 61 | # 62 | # @return [HashList] the desired Capistrano recipe options 63 | # 64 | # @see http://github.com/capistrano/capistrano/blob/master/lib/capistrano/configuration/actions/invocation.rb#L99-L144 Valid Capistrano 'task' method options 65 | def options 66 | @options ||= HashList.new 67 | end 68 | 69 | # How Rake tasks will be named when they are mirrored as Capistrano recipes. 70 | # 71 | # @overload rename 72 | # How Rake tasks will be named when they are mirrored as Capistrano 73 | # recipes. 74 | # 75 | # @return [Proc] the block that generates a Capistrano recipe name from a 76 | # Rake task name 77 | # 78 | # @overload rename(&block) 79 | # Determines how Rake tasks will be named when they are mirrored as 80 | # Capistrano recipes. 81 | # 82 | # @yield [task_name] 83 | # @yieldparam task_name [String] the name of a Rake task 84 | # @yieldreturn [String] a name for the Capistrano recipe 85 | # 86 | # @return [Proc] _block_ 87 | # 88 | # @raise [ArgumentError] _block_ does not have exactly one parameter 89 | def rename(&block) 90 | if block 91 | unless (block.arity == 1) 92 | raise ::ArgumentError, "Must have 1 parameter but has #{block.arity}" 93 | end 94 | 95 | @rename = block 96 | else 97 | @rename 98 | end 99 | end 100 | 101 | end 102 | 103 | end 104 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'appraisal' 3 | rescue LoadError 4 | end 5 | begin 6 | require 'bundler/gem_tasks' 7 | rescue LoadError 8 | end 9 | 10 | begin 11 | require 'yard' 12 | rescue LoadError 13 | else 14 | namespace :build do 15 | YARD::Rake::YardocTask.new :doc 16 | end 17 | end 18 | 19 | def define_features_task(name, options) 20 | begin 21 | require 'cucumber/rake/task' 22 | rescue LoadError 23 | else 24 | Cucumber::Rake::Task.new name, options[:desc] do |t| 25 | t.bundler = false 26 | 27 | cucumber_opts = [t.cucumber_opts] 28 | cucumber_opts << "--backtrace" if options[:backtrace] 29 | if options.key?(:format) 30 | cucumber_opts << "--format #{options[:format]}" 31 | else 32 | cucumber_opts << '--format pretty' 33 | end 34 | cucumber_opts << "--tags #{options[:tags]}" if options.key?(:tags) 35 | t.cucumber_opts = cucumber_opts.join(' ') 36 | end 37 | end 38 | end 39 | 40 | tags = `grep -Ehr "^\\s*@\\S+\\s*$" features`.split("\n"). 41 | collect(&:strip). 42 | uniq. 43 | sort 44 | options = {:desc => 'Test features'} 45 | options[:tags] = '@focus' if tags.delete('@focus') 46 | define_features_task :features, options 47 | 48 | unless tags.empty? 49 | namespace :features do 50 | tags.each do |t| 51 | define_features_task t.gsub(/^@/, ''), 52 | :desc => "Test features tagged #{t}", :tags => t 53 | end 54 | end 55 | end 56 | 57 | def define_spec_task(name, options={}) 58 | desc options[:desc] 59 | begin 60 | require 'rspec/core/rake_task' 61 | rescue LoadError 62 | else 63 | RSpec::Core::RakeTask.new name do |t| 64 | t.rspec_opts ||= [] 65 | t.rspec_opts << "--backtrace" if options[:backtrace] 66 | unless options[:debug] == false 67 | available = %w(debugger ruby-debug).detect do |debugger_library| 68 | begin 69 | require debugger_library 70 | rescue LoadError 71 | false 72 | else 73 | true 74 | end 75 | end 76 | if available 77 | t.rspec_opts << '--debug' 78 | else 79 | require 'cape/xterm' 80 | $stderr.puts Cape::XTerm.bold('*** Debugging tools not installed') 81 | end 82 | end 83 | t.rspec_opts << "--format #{options[:format]}" if options.key?(:format) 84 | t.pattern = options[:pattern] || %w(spec/*_spec.rb spec/**/*_spec.rb) 85 | end 86 | end 87 | end 88 | 89 | define_spec_task :spec, :desc => 'Run specs' 90 | 91 | namespace :spec do 92 | uncommitted_specs = `git ls-files --modified --others *_spec.rb`.split("\n") 93 | desc = 'Run uncommitted specs' 94 | desc += ' (none)' if uncommitted_specs.empty? 95 | define_spec_task :uncommitted, :desc => desc, :pattern => uncommitted_specs 96 | end 97 | 98 | desc 'Run specs and test features' 99 | task '' => [:spec, :features] 100 | task :default => [:spec, :features] 101 | 102 | # Support the 'gem test' command. 103 | namespace :test do 104 | define_spec_task :spec, :desc => '', :backtrace => true, 105 | :debug => false, 106 | :format => :progress 107 | 108 | define_features_task :features, :desc => '', :backtrace => true, 109 | :format => :progress 110 | end 111 | task :test => %w(test:spec test:features) 112 | -------------------------------------------------------------------------------- /spec/cape/rake_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/rake' 3 | 4 | describe Cape::Rake do 5 | subject(:rake) { rake_class.new } 6 | 7 | let(:rake_class) { described_class } 8 | 9 | describe '::DEFAULT_EXECUTABLE' do 10 | subject(:constant) { rake_class::DEFAULT_EXECUTABLE } 11 | 12 | specify { expect(constant).to be_frozen } 13 | end 14 | 15 | describe '#==' do 16 | it('recognizes equivalent instances to be equal') { 17 | expect(rake_class.new).to eq(rake_class.new) 18 | } 19 | 20 | it('compares using #local_executable') { 21 | expect(rake_class.new).not_to eq(rake_class.new(:local_executable => 'foo')) 22 | } 23 | 24 | it('compares using #remote_executable') { 25 | expect(rake_class.new).not_to eq(rake_class.new(:remote_executable => 'foo')) 26 | } 27 | end 28 | 29 | describe '-- without specified attributes --' do 30 | describe '#local_executable' do 31 | specify { expect(rake.local_executable).to eq(rake_class::DEFAULT_EXECUTABLE) } 32 | end 33 | 34 | describe '#remote_executable' do 35 | specify { expect(rake.remote_executable).to eq(rake_class::DEFAULT_EXECUTABLE) } 36 | end 37 | end 38 | 39 | describe '-- with specified attributes --' do 40 | subject(:rake) { 41 | rake_class.new :local_executable => ('the specified value of ' + 42 | '#local_executable'), 43 | :remote_executable => ('the specified value of ' + 44 | '#remote_executable') 45 | } 46 | 47 | describe '#local_executable' do 48 | specify { expect(rake.local_executable).to eq('the specified value of #local_executable') } 49 | end 50 | 51 | describe '#remote_executable' do 52 | specify { expect(rake.remote_executable).to eq('the specified value of #remote_executable') } 53 | end 54 | end 55 | 56 | describe '-- with respect to caching --' do 57 | before :each do 58 | allow(rake).to receive(:fetch_output).and_return(output) 59 | end 60 | 61 | let(:output) { 62 | <<-end_output 63 | rake foo # foo 64 | rake bar # bar 65 | rake baz # baz 66 | end_output 67 | } 68 | 69 | describe '#each_task' do 70 | it 'builds and uses a cache' do 71 | expect(rake).to receive(:fetch_output).once.and_return(output) 72 | rake.each_task do |t| 73 | end 74 | rake.each_task do |t| 75 | end 76 | end 77 | 78 | it 'does not expire the cache' do 79 | expect(rake).not_to receive(:expire_cache!) 80 | rake.each_task do |t| 81 | end 82 | end 83 | 84 | it 'expires the cache in the event of an error' do 85 | expect(rake).to receive(:expire_cache!).once 86 | begin 87 | rake.each_task do |t| 88 | raise 'pow!' 89 | end 90 | rescue 91 | end 92 | end 93 | 94 | it 'does not swallow errors' do 95 | expect { 96 | rake.each_task do |t| 97 | raise ZeroDivisionError, 'pow!' 98 | end 99 | }.to raise_error(ZeroDivisionError, 'pow!') 100 | end 101 | end 102 | 103 | describe '#expire_cache!' do 104 | it 'expires the cache' do 105 | expect(rake).to receive(:fetch_output).twice.and_return(output) 106 | rake.each_task do |t| 107 | end 108 | rake.expire_cache! 109 | rake.each_task do |t| 110 | end 111 | end 112 | end 113 | 114 | describe '#local_executable=' do 115 | describe 'with the same value,' do 116 | it 'does not expire the cache' do 117 | expect(rake).not_to receive(:expire_cache!) 118 | rake.local_executable = rake.local_executable 119 | end 120 | end 121 | 122 | describe 'with a different value,' do 123 | it 'expires the cache' do 124 | expect(rake).to receive(:expire_cache!).once 125 | rake.local_executable = rake.local_executable + ' foo' 126 | end 127 | end 128 | end 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /lib/cape/rake.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | # An abstraction of the Rake installation and available tasks. 4 | class Rake 5 | 6 | # The default command used to run Rake. We use `bundle check` to detect the 7 | # presence of Bundler and a Bundler configuration. If Bundler is installed on 8 | # the computer and configured, we prepend `rake` with `bundle exec`. 9 | DEFAULT_EXECUTABLE = ( 10 | '/usr/bin/env ' + 11 | '`' + 12 | '/usr/bin/env bundle check >/dev/null 2>&1; ' + 13 | 'case $? in ' + 14 | # Exit code 0: bundle is defined and installed 15 | # Exit code 1: bundle is defined but not installed 16 | '0|1 ) ' + 17 | 'echo bundle exec ' + 18 | ';; ' + 19 | 'esac' + 20 | '` ' + 21 | 'rake' 22 | ).freeze 23 | 24 | # Sets the command used to run Rake on remote computers. 25 | # 26 | # @param [String] value the command used to run Rake on remote computers 27 | # 28 | # @return [String] _value_ 29 | attr_writer :remote_executable 30 | 31 | # Constructs a new Rake object with the specified _attributes_. 32 | # 33 | # @param [Hash] attributes attribute values 34 | def initialize(attributes={}) 35 | attributes.each do |name, value| 36 | send "#{name}=", value 37 | end 38 | end 39 | 40 | # Compares the Rake object to another. 41 | # 42 | # @param [Object] other another object 43 | # 44 | # @return [true] the {Rake} object is equal to _other_ 45 | # @return [false] the {Rake} object is unequal to _other_ 46 | def ==(other) 47 | other.kind_of?(Rake) && 48 | (other.local_executable == local_executable) && 49 | (other.remote_executable == remote_executable) 50 | end 51 | 52 | # Enumerates Rake tasks. 53 | # 54 | # @param [String, Symbol] task_expression the full name of a task or 55 | # namespace to filter 56 | # 57 | # @yield [task] a block that processes tasks 58 | # @yieldparam [Hash] task metadata on a task 59 | # 60 | # @return [Rake] the object 61 | def each_task(task_expression=nil) 62 | previous_task, this_task = nil, nil 63 | task_expression = task_expression ? 64 | ::Regexp.escape(task_expression.to_s) : 65 | '.+?' 66 | regexp = /^rake (#{task_expression}(?::.+?)?)(?:\[(.+?)\])?\s+#\s*(.*)/ 67 | each_output_line do |l| 68 | unless (matches = l.chomp.match(regexp)) 69 | next 70 | end 71 | 72 | previous_task = this_task 73 | this_task = {}.tap do |t| 74 | t[:name] = matches[1].strip 75 | t[:parameters] = matches[2].split(',') if matches[2] 76 | t[:description] = matches[3] 77 | end 78 | if previous_task 79 | all_but_last_segment = this_task[:name].split(':')[0...-1].join(':') 80 | previous_task[:default] = all_but_last_segment == 81 | previous_task[:name] 82 | yield previous_task 83 | end 84 | end 85 | yield this_task if this_task 86 | self 87 | end 88 | 89 | # Forces cached Rake task metadata (if any) to be discarded. 90 | def expire_cache! 91 | @output_lines = nil 92 | self 93 | end 94 | 95 | # The command used to run Rake on the local computer. 96 | # 97 | # @return [String] the command used to run Rake on the local computer 98 | # 99 | # @see DEFAULT_EXECUTABLE 100 | def local_executable 101 | @local_executable ||= DEFAULT_EXECUTABLE 102 | end 103 | 104 | # Sets the command used to run Rake on the local computer and discards any 105 | # cached Rake task metadata. 106 | # 107 | # @param [String] value the command used to run Rake on the local computer 108 | # 109 | # @return [String] _value_ 110 | # 111 | # @see #expire_cache! 112 | def local_executable=(value) 113 | unless @local_executable == value 114 | @local_executable = value 115 | expire_cache! 116 | end 117 | value 118 | end 119 | 120 | # The command used to run Rake on remote computers. 121 | # 122 | # @return [String] the command used to run Rake on remote computers 123 | # 124 | # @see DEFAULT_EXECUTABLE 125 | def remote_executable 126 | @remote_executable ||= DEFAULT_EXECUTABLE 127 | end 128 | 129 | private 130 | 131 | def each_output_line(&block) 132 | if @output_lines 133 | @output_lines.each(&block) 134 | return self 135 | end 136 | 137 | @output_lines = [] 138 | begin 139 | fetch_output.each_line do |l| 140 | @output_lines << l 141 | block.call(l) 142 | end 143 | rescue 144 | expire_cache! 145 | raise 146 | end 147 | self 148 | end 149 | 150 | def fetch_output 151 | `#{local_executable} --all --tasks 2>/dev/null || #{local_executable} --tasks 2>/dev/null` 152 | end 153 | end 154 | 155 | end 156 | -------------------------------------------------------------------------------- /spec/cape/dsl_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cape/dsl' 3 | require 'cape/capistrano' 4 | require 'cape/core_ext/hash' 5 | require 'cape/core_ext/symbol' 6 | require 'cape/rake' 7 | 8 | describe Cape::DSL do 9 | subject(:dsl) { 10 | Object.new.tap do |o| 11 | o.extend dsl_module 12 | end 13 | } 14 | 15 | let(:dsl_module) { described_class } 16 | 17 | let(:capistrano) { double(Cape::Capistrano).as_null_object } 18 | 19 | let(:rake) { double(Cape::Rake).as_null_object } 20 | 21 | let(:task_expression) { 'task:expression' } 22 | 23 | before :each do 24 | allow(Cape::Capistrano).to receive(:new).and_return(capistrano) 25 | allow(Cape::Rake ).to receive(:new).and_return(rake) 26 | end 27 | 28 | describe '#each_rake_task' do 29 | def do_each_rake_task(&block) 30 | dsl.each_rake_task(task_expression, &block) 31 | end 32 | 33 | it 'delegates to Rake#each_task' do 34 | expect(rake).to receive(:each_task). 35 | with(task_expression). 36 | and_yield({:name => task_expression}) 37 | do_each_rake_task do |t| 38 | expect(t).to eq(:name => task_expression) 39 | end 40 | end 41 | 42 | it 'returns itself' do 43 | expect(do_each_rake_task).to eq(dsl) 44 | end 45 | end 46 | 47 | describe '#mirror_rake_tasks' do 48 | before :each do 49 | allow(rake).to receive(:each_task).and_yield({:name => task_expression}) 50 | allow(dsl ).to receive(:raise_unless_capistrano) 51 | end 52 | 53 | def do_mirror_rake_tasks(*arguments, &block) 54 | dsl.mirror_rake_tasks(*arguments, &block) 55 | end 56 | 57 | describe 'with two scalar arguments --' do 58 | specify do 59 | expect { 60 | do_mirror_rake_tasks task_expression, task_expression 61 | }.to raise_error(ArgumentError, /^wrong number of arguments/) 62 | end 63 | end 64 | 65 | shared_examples_for "a successful call (#{Cape::DSL.name})" do |task_expression_in_use, 66 | options_in_use| 67 | specify 'by collecting Rake#each_task' do 68 | expect(rake).to receive(:each_task).with(task_expression_in_use) 69 | do_mirror_rake_tasks 70 | end 71 | 72 | specify 'by verifying that Capistrano is in use' do 73 | expect(dsl).to receive(:raise_unless_capistrano) 74 | do_mirror_rake_tasks 75 | end 76 | 77 | describe 'by sending Capistrano#define_rake_wrapper for Rake#each_task' do 78 | specify 'with the expected task' do 79 | expect(capistrano).to receive(:define_rake_wrapper). 80 | with { |task, named_arguments| task == {:name => task_expression} } 81 | do_mirror_rake_tasks 82 | end 83 | 84 | specify 'with the expected named options' do 85 | expect(capistrano).to receive(:define_rake_wrapper). 86 | with { |task, named_arguments| named_arguments.keys.sort == ([:binding] + options_in_use.keys).sort } 87 | do_mirror_rake_tasks 88 | end 89 | 90 | specify 'with a :binding option' do 91 | expect(capistrano).to receive(:define_rake_wrapper). 92 | with { |task, named_arguments| named_arguments[:binding].is_a? Binding } 93 | do_mirror_rake_tasks 94 | end 95 | 96 | specify 'with any provided extra options' do 97 | expect(capistrano).to receive(:define_rake_wrapper). 98 | with { |task, named_arguments| named_arguments.slice(*options_in_use.keys) == options_in_use } 99 | do_mirror_rake_tasks 100 | end 101 | end 102 | 103 | specify 'by returning itself' do 104 | expect(do_mirror_rake_tasks).to eq(dsl) 105 | end 106 | end 107 | 108 | describe 'with one scalar argument and a block --' do 109 | def do_mirror_rake_tasks 110 | super 'task:expression' do |env| 111 | env['AN_ENV_VAR'] = 'an environment variable' 112 | end 113 | end 114 | 115 | behaves_like("a successful call (#{Cape::DSL.name})", 116 | 'task:expression', 117 | {}) 118 | end 119 | 120 | describe 'with one scalar argument --' do 121 | def do_mirror_rake_tasks 122 | super 'task:expression' 123 | end 124 | 125 | behaves_like("a successful call (#{Cape::DSL.name})", 126 | 'task:expression', 127 | {}) 128 | end 129 | 130 | describe 'without arguments --' do 131 | def do_mirror_rake_tasks 132 | super 133 | end 134 | 135 | behaves_like("a successful call (#{Cape::DSL.name})", nil, {}) 136 | end 137 | end 138 | 139 | describe '#local_rake_executable' do 140 | before :each do 141 | allow(rake).to receive(:local_executable).and_return('foo') 142 | end 143 | 144 | it 'delegates to Rake#local_executable' do 145 | expect(rake).to receive(:local_executable) 146 | dsl.local_rake_executable 147 | end 148 | 149 | it 'returns the result of Rake#local_executable' do 150 | expect(dsl.local_rake_executable).to eq('foo') 151 | end 152 | end 153 | 154 | describe '#remote_rake_executable' do 155 | before :each do 156 | allow(rake).to receive(:remote_executable).and_return('foo') 157 | end 158 | 159 | it 'delegates to Rake#remote_executable' do 160 | expect(rake).to receive(:remote_executable) 161 | dsl.remote_rake_executable 162 | end 163 | 164 | it 'returns the result of Rake#remote_executable' do 165 | expect(dsl.remote_rake_executable).to eq('foo') 166 | end 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /lib/cape/capistrano.rb: -------------------------------------------------------------------------------- 1 | require 'cape/rake' 2 | require 'cape/recipe_definition' 3 | require 'cape/util' 4 | require 'cape/xterm' 5 | 6 | module Cape 7 | 8 | # An abstraction of the Capistrano installation. 9 | class Capistrano 10 | 11 | # A Cape abstraction of the Rake installation. 12 | attr_accessor :rake 13 | 14 | # Constructs a new Capistrano object with the specified _attributes_. 15 | # 16 | # @param [Hash] attributes attribute values 17 | def initialize(attributes={}) 18 | attributes.each do |name, value| 19 | send "#{name}=", value 20 | end 21 | self.rake ||= new_rake 22 | end 23 | 24 | # Defines a wrapper in Capistrano around the specified Rake _task_. 25 | # 26 | # @param [Hash] task metadata for a Rake task 27 | # @param [Hash] named_arguments 28 | # 29 | # @option task [String] :name the name of the Rake task 30 | # @option task [Array of String, nil] :parameters the names of the Rake 31 | # task's parameters, if any 32 | # @option task [String] :description documentation for the Rake 33 | # task 34 | # 35 | # @option named_arguments [Binding] :binding the Binding of your Capistrano 36 | # recipes file 37 | # 38 | # @yield [recipes] a block that customizes the Capistrano recipe(s) 39 | # generated for the Rake task(s); optional 40 | # @yieldparam [RecipeDefinition] recipes an interface for customizing the 41 | # Capistrano recipe(s) generated for 42 | # the Rake task(s) 43 | # 44 | # @return [Capistrano] the object 45 | # 46 | # @raise [ArgumentError] +named_arguments[:binding]+ is missing 47 | # 48 | # @note Any parameters that the Rake task has are integrated via environment variables, since Capistrano does not support recipe parameters per se. 49 | def define_rake_wrapper(task, named_arguments, &block) 50 | unless (binding = named_arguments[:binding]) 51 | raise ::ArgumentError, ':binding named argument is required' 52 | end 53 | 54 | capistrano_context = binding.eval('self', __FILE__, __LINE__) 55 | describe task, capistrano_context 56 | implement(task, capistrano_context, &block) 57 | end 58 | 59 | private 60 | 61 | def build_capistrano_description(task) 62 | return nil unless task[:description] 63 | 64 | description = [task[:description]] 65 | unless task[:description].empty? || task[:description].end_with?('.') 66 | description << '.' 67 | end 68 | 69 | unless (parameters = Array(task[:parameters])).empty? 70 | noun = Util.pluralize('variable', parameters.length) 71 | parameters_list = Util.to_list_phrase(parameters.collect(&:upcase)) 72 | singular = 'Rake task argument' 73 | noun_phrase = (parameters.length == 1) ? 74 | "a #{singular}" : 75 | Util.pluralize(singular) 76 | description << <<-end_fragment 77 | 78 | 79 | Set environment #{noun} #{parameters_list} if you want to pass #{noun_phrase}. 80 | end_fragment 81 | end 82 | 83 | description.join 84 | end 85 | 86 | def capture_recipe_definition(recipe_definition, &recipe_definition_block) 87 | recipe_definition_block.call(recipe_definition) if recipe_definition_block 88 | true 89 | end 90 | 91 | def describe(task, capistrano_context) 92 | if (description = build_capistrano_description(task)) 93 | capistrano_context.desc description 94 | end 95 | self 96 | end 97 | 98 | def implement(task, capistrano_context, &recipe_definition_block) 99 | name_tokens = tokenize_name(task) 100 | recipe_definition = new_recipe_definition 101 | capture_recipe_definition(recipe_definition, &recipe_definition_block) 102 | env = nil 103 | rake = self.rake 104 | block = lambda { |context| 105 | recipe_name = name_tokens.last 106 | if recipe_definition.rename 107 | recipe_name = recipe_definition.rename.call(name_tokens.last) 108 | end 109 | begin 110 | context.task recipe_name, recipe_definition.options do 111 | arguments = Array(task[:parameters]).collect do |a| 112 | if (value = ENV[a.upcase]) 113 | value = value.inspect 114 | end 115 | value 116 | end 117 | if arguments.empty? 118 | arguments = nil 119 | else 120 | arguments = "[#{arguments.join ','}]" 121 | end 122 | 123 | unless env 124 | env_strings = recipe_definition.env.collect do |var_name, var_value| 125 | if var_name.nil? || var_value.nil? 126 | nil 127 | else 128 | if var_value.is_a?(Proc) 129 | var_value = context.instance_eval do 130 | var_value.call 131 | end 132 | end 133 | "#{var_name}=#{var_value.inspect}" 134 | end 135 | end.compact 136 | env = env_strings.empty? ? nil : (' ' + env_strings.join(' ')) 137 | end 138 | 139 | path = recipe_definition.cd || context.current_path 140 | path = path.call if path.respond_to?(:call) 141 | command = "cd #{path} && #{rake.remote_executable} " + 142 | "#{task[:name]}#{arguments}#{env}" 143 | context.run command 144 | end 145 | rescue => e 146 | $stderr.puts XTerm.bold_and_foreground_red('*** WARNING:') + 147 | ' ' + 148 | XTerm.bold("You must use Cape's renaming API in order " + 149 | 'to mirror Rake task ') + 150 | XTerm.bold_and_underlined(task[:name]) + 151 | XTerm.bold(" (#{e.message})") 152 | end 153 | } 154 | # Nest the recipe inside its containing namespaces. 155 | name_tokens[0...-1].reverse.each do |namespace_token| 156 | inner_block = block 157 | block = lambda { |context| 158 | context.namespace(namespace_token, &inner_block) 159 | } 160 | end 161 | block.call capistrano_context 162 | self 163 | end 164 | 165 | def new_rake(*arguments) 166 | Rake.new(*arguments) 167 | end 168 | 169 | def new_recipe_definition(*arguments) 170 | RecipeDefinition.new(*arguments) 171 | end 172 | 173 | def tokenize_name(task) 174 | task[:name].split(':').tap do |result| 175 | result << 'default' if task[:default] 176 | end 177 | end 178 | 179 | end 180 | 181 | end 182 | -------------------------------------------------------------------------------- /lib/cape/dsl.rb: -------------------------------------------------------------------------------- 1 | require 'cape/capistrano' 2 | require 'cape/rake' 3 | 4 | module Cape 5 | 6 | # Provides methods for integrating Capistrano and Rake. 7 | module DSL 8 | 9 | # Enumerates Rake tasks. 10 | # 11 | # @param [String, Symbol] task_expression the full name of a task or 12 | # namespace to filter 13 | # @param [Proc] block a block that processes tasks 14 | # 15 | # @yield [task] a block that processes tasks 16 | # @yieldparam [Hash] task metadata on a task 17 | # 18 | # @return [DSL] the object 19 | # 20 | # @example Enumerating all Rake tasks 21 | # # config/deploy.rb 22 | # 23 | # require 'cape' 24 | # 25 | # Cape do 26 | # each_rake_task do |t| 27 | # # Do something interesting with this hash: 28 | # # * t[:name] -- the full name of the task 29 | # # * t[:parameters] -- the names of task arguments 30 | # # * t[:description] -- documentation on the task, including 31 | # # parameters 32 | # end 33 | # end 34 | # 35 | # @example Enumerating some Rake tasks 36 | # # config/deploy.rb 37 | # 38 | # require 'cape' 39 | # 40 | # Cape do 41 | # each_rake_task :foo do |t| 42 | # # Do something interesting with this hash: 43 | # # * t[:name] -- the full name of the task 44 | # # * t[:parameters] -- the names of task arguments 45 | # # * t[:description] -- documentation on the task, including 46 | # # parameters 47 | # end 48 | # end 49 | def each_rake_task(task_expression=nil, &block) 50 | rake.each_task(task_expression, &block) 51 | self 52 | end 53 | 54 | # The command used to run Rake on the local computer. 55 | # 56 | # @return [String] the command used to run Rake on the local computer 57 | # 58 | # @see Rake::DEFAULT_EXECUTABLE 59 | def local_rake_executable 60 | rake.local_executable 61 | end 62 | 63 | # Sets the command used to run Rake on the local computer. 64 | # 65 | # @param [String] value the command used to run Rake on the local computer 66 | # 67 | # @return [String] _value_ 68 | # 69 | # @example Changing the local Rake executable 70 | # require 'cape' 71 | # 72 | # Cape do 73 | # self.local_rake_executable = '/path/to/rake' 74 | # $stdout.puts 'We changed the local Rake executable to ' + 75 | # "#{local_rake_executable.inspect}." 76 | # end 77 | def local_rake_executable=(value) 78 | rake.local_executable = value 79 | end 80 | 81 | # Makes the use of a Cape block parameter optional by forwarding non-Cape 82 | # method calls to the containing binding. 83 | # 84 | # @param [Symbol, String] method the method called 85 | # @param [Array] args the arguments passed to _method_ 86 | # @param [Proc] block the block passed to _method_ 87 | # 88 | # @return the result of the forwarded method call 89 | def method_missing(method, *args, &block) 90 | @outer_self.send(method, *args, &block) 91 | end 92 | 93 | # Defines Rake tasks as Capistrano recipes. 94 | # 95 | # @param [String, Symbol] task_expression the full name of a Rake task or 96 | # namespace to filter 97 | # 98 | # @yield [recipes] a block that customizes the Capistrano recipe(s) 99 | # generated for the Rake task(s); optional 100 | # @yieldparam [RecipeDefinition] recipes an interface for customizing the 101 | # Capistrano recipe(s) generated for 102 | # the Rake task(s) 103 | # 104 | # @return [DSL] the object 105 | # 106 | # @note Any parameters that the Rake tasks have are integrated via environment variables, since Capistrano does not support recipe parameters per se. 107 | # 108 | # @example Mirroring all Rake tasks 109 | # # config/deploy.rb 110 | # 111 | # require 'cape' 112 | # 113 | # Cape do 114 | # # Create Capistrano recipes for all Rake tasks. 115 | # mirror_rake_tasks 116 | # end 117 | # 118 | # @example Mirroring some Rake tasks, but not others 119 | # # config/deploy.rb 120 | # 121 | # require 'cape' 122 | # 123 | # Cape do 124 | # # Create Capistrano recipes for the Rake task 'foo' and/or for the 125 | # # tasks in the 'foo' namespace. 126 | # mirror_rake_tasks :foo 127 | # end 128 | # 129 | # @example Mirroring Rake tasks that require renaming, Capistrano recipe options, path switching, and/or environment variables 130 | # # config/deploy.rb 131 | # 132 | # require 'cape' 133 | # 134 | # Cape do 135 | # # Display defined Rails routes on application server remote machines 136 | # # only. 137 | # mirror_rake_tasks :routes do |recipes| 138 | # recipes.options[:roles] = :app 139 | # end 140 | # 141 | # # Execute database migration on application server remote machines 142 | # # only, and set the 'RAILS_ENV' environment variable to the value of 143 | # # the Capistrano variable 'rails_env'. 144 | # mirror_rake_tasks 'db:migrate' do |recipes| 145 | # recipes.options[:roles] = :app 146 | # recipes.env['RAILS_ENV'] = lambda { rails_env } 147 | # end 148 | # 149 | # # Support a Rake task that must be run on application server remote 150 | # # machines only, and in the remote directory 'release_path' instead of 151 | # # the default, 'current_path'. 152 | # before 'deploy:symlink', :spec 153 | # mirror_rake_tasks :spec do |recipes| 154 | # recipes.cd { release_path } 155 | # recipes.options[:roles] = :app 156 | # end 157 | # 158 | # # Avoid collisions with the existing Ruby method #test, run tests on 159 | # # application server remote machines only, and set the 'RAILS_ENV' 160 | # # environment variable to the value of the Capistrano variable 161 | # # 'rails_env'. 162 | # mirror_rake_tasks :test do |recipes| 163 | # recipes.rename do |rake_task_name| 164 | # "#{rake_task_name}_task" 165 | # end 166 | # recipes.options[:roles] = :app 167 | # recipes.env['RAILS_ENV'] = lambda { rails_env } 168 | # end 169 | # end 170 | # 171 | # @example Mirroring Rake tasks into a Capistrano namespace 172 | # # config/deploy.rb 173 | # 174 | # require 'cape' 175 | # 176 | # namespace :rake_tasks do 177 | # Cape do |cape| 178 | # cape.mirror_rake_tasks 179 | # end 180 | # end 181 | def mirror_rake_tasks(task_expression=nil, &block) 182 | rake.each_task task_expression do |t| 183 | deployment_library.define_rake_wrapper(t, :binding => binding, &block) 184 | end 185 | self 186 | end 187 | 188 | # The command used to run Rake on remote computers. 189 | # 190 | # @return [String] the command used to run Rake on remote computers 191 | # 192 | # @see Rake::DEFAULT_EXECUTABLE 193 | def remote_rake_executable 194 | rake.remote_executable 195 | end 196 | 197 | # Sets the command used to run Rake on remote computers. 198 | # 199 | # @param [String] value the command used to run Rake on remote computers 200 | # 201 | # @return [String] _value_ 202 | # 203 | # @example Changing the remote Rake executable 204 | # require 'cape' 205 | # 206 | # Cape do 207 | # self.remote_rake_executable = '/path/to/rake' 208 | # $stdout.puts 'We changed the remote Rake executable to ' + 209 | # "#{remote_rake_executable.inspect}." 210 | # end 211 | def remote_rake_executable=(value) 212 | rake.remote_executable = value 213 | end 214 | 215 | protected 216 | 217 | # Returns an abstraction of the Rake installation and available tasks. 218 | def rake 219 | @rake ||= new_rake 220 | end 221 | 222 | private 223 | 224 | def deployment_library 225 | return @deployment_library if @deployment_library 226 | 227 | raise_unless_capistrano 228 | @deployment_library = new_capistrano(:rake => rake) 229 | end 230 | 231 | def new_capistrano(*arguments) 232 | Capistrano.new(*arguments) 233 | end 234 | 235 | def new_rake(*arguments) 236 | Rake.new(*arguments) 237 | end 238 | 239 | def raise_unless_capistrano 240 | if @outer_self.method(:task).owner.name !~ /^Capistrano::/ 241 | raise 'Use this in the context of Capistrano recipes' 242 | end 243 | end 244 | 245 | end 246 | 247 | end 248 | -------------------------------------------------------------------------------- /lib/cape/xterm.rb: -------------------------------------------------------------------------------- 1 | module Cape 2 | 3 | # Formats output for an xterm console. 4 | # 5 | # Convenience class methods are made available dynamically that permit multiple 6 | # formats to be applied. 7 | # 8 | # @example Apply bold and red-foreground formatting to a string 9 | # Cape::XTerm.bold_and_foreground_red 'foo' 10 | # 11 | # @api private 12 | module XTerm 13 | 14 | # The xterm formatting codes. 15 | FORMATS = {:bold => '1', 16 | :underlined => '4', 17 | :blinking => '5', 18 | :inverse => '7', 19 | :foreground_black => '30', 20 | :foreground_red => '31', 21 | :foreground_green => '32', 22 | :foreground_yellow => '33', 23 | :foreground_blue => '34', 24 | :foreground_magenta => '35', 25 | :foreground_cyan => '36', 26 | :foreground_gray => '37', 27 | :foreground_default => '39', 28 | :background_black => '40', 29 | :background_red => '41', 30 | :background_green => '42', 31 | :background_yellow => '43', 32 | :background_blue => '44', 33 | :background_magenta => '45', 34 | :background_cyan => '46', 35 | :background_gray => '47', 36 | :background_default => '49'} 37 | 38 | # @!method bold(string) 39 | # Formats the specified _string_ in bold type. 40 | # 41 | # @param [String] string the string to format in bold type 42 | # 43 | # @return [String] the formatted _string_ 44 | # 45 | # @!scope class 46 | # 47 | # @api private 48 | 49 | # @!method underlined(string) 50 | # Formats the specified _string_ in underlined type. 51 | # 52 | # @param [String] string the string to underline 53 | # 54 | # @return [String] the formatted _string_ 55 | # 56 | # @!scope class 57 | # 58 | # @api private 59 | 60 | # @!method blinking(string) 61 | # Formats the specified _string_ in blinking type. 62 | # 63 | # @param [String] string the string to blink 64 | # 65 | # @return [String] the formatted _string_ 66 | # 67 | # @!scope class 68 | # 69 | # @api private 70 | 71 | # @!method inverse(string) 72 | # Formats the specified _string_ in inverse colors. 73 | # 74 | # @param [String] string the string whose colors are to be inverted 75 | # 76 | # @return [String] the formatted _string_ 77 | # 78 | # @!scope class 79 | # 80 | # @api private 81 | 82 | # @!method foreground_black(string) 83 | # Formats the specified _string_ in black. 84 | # 85 | # @param [String] string the string to color 86 | # 87 | # @return [String] the formatted _string_ 88 | # 89 | # @!scope class 90 | # 91 | # @api private 92 | 93 | # @!method foreground_red(string) 94 | # Formats the specified _string_ in red. 95 | # 96 | # @param [String] string the string to color 97 | # 98 | # @return [String] the formatted _string_ 99 | # 100 | # @!scope class 101 | # 102 | # @api private 103 | 104 | # @!method foreground_green(string) 105 | # Formats the specified _string_ in green. 106 | # 107 | # @param [String] string the string to color 108 | # 109 | # @return [String] the formatted _string_ 110 | # 111 | # @!scope class 112 | # 113 | # @api private 114 | 115 | # @!method foreground_yellow(string) 116 | # Formats the specified _string_ in yellow. 117 | # 118 | # @param [String] string the string to color 119 | # 120 | # @return [String] the formatted _string_ 121 | # 122 | # @!scope class 123 | # 124 | # @api private 125 | 126 | # @!method foreground_blue(string) 127 | # Formats the specified _string_ in blue. 128 | # 129 | # @param [String] string the string to color 130 | # 131 | # @return [String] the formatted _string_ 132 | # 133 | # @!scope class 134 | # 135 | # @api private 136 | 137 | # @!method foreground_magenta(string) 138 | # Formats the specified _string_ in magenta. 139 | # 140 | # @param [String] string the string to color 141 | # 142 | # @return [String] the formatted _string_ 143 | # 144 | # @!scope class 145 | # 146 | # @api private 147 | 148 | # @!method foreground_cyan(string) 149 | # Formats the specified _string_ in cyan. 150 | # 151 | # @param [String] string the string to color 152 | # 153 | # @return [String] the formatted _string_ 154 | # 155 | # @!scope class 156 | # 157 | # @api private 158 | 159 | # @!method foreground_gray(string) 160 | # Formats the specified _string_ in gray. 161 | # 162 | # @param [String] string the string to color 163 | # 164 | # @return [String] the formatted _string_ 165 | # 166 | # @!scope class 167 | # 168 | # @api private 169 | 170 | # @!method foreground_default(string) 171 | # Formats the specified _string_ in the default foreground color. 172 | # 173 | # @param [String] string the string to color 174 | # 175 | # @return [String] the formatted _string_ 176 | # 177 | # @!scope class 178 | # 179 | # @api private 180 | 181 | # @!method background_black(string) 182 | # Formats the specified _string_ with a black background. 183 | # 184 | # @param [String] string the string to color 185 | # 186 | # @return [String] the formatted _string_ 187 | # 188 | # @!scope class 189 | # 190 | # @api private 191 | 192 | # @!method background_red(string) 193 | # Formats the specified _string_ with a red background. 194 | # 195 | # @param [String] string the string to color 196 | # 197 | # @return [String] the formatted _string_ 198 | # 199 | # @!scope class 200 | # 201 | # @api private 202 | 203 | # @!method background_green(string) 204 | # Formats the specified _string_ with a green background. 205 | # 206 | # @param [String] string the string to color 207 | # 208 | # @return [String] the formatted _string_ 209 | # 210 | # @!scope class 211 | # 212 | # @api private 213 | 214 | # @!method background_yellow(string) 215 | # Formats the specified _string_ with a yellow background. 216 | # 217 | # @param [String] string the string to color 218 | # 219 | # @return [String] the formatted _string_ 220 | # 221 | # @!scope class 222 | # 223 | # @api private 224 | 225 | # @!method background_blue(string) 226 | # Formats the specified _string_ with a blue background. 227 | # 228 | # @param [String] string the string to color 229 | # 230 | # @return [String] the formatted _string_ 231 | # 232 | # @!scope class 233 | # 234 | # @api private 235 | 236 | # @!method background_magenta(string) 237 | # Formats the specified _string_ with a magenta background. 238 | # 239 | # @param [String] string the string to color 240 | # 241 | # @return [String] the formatted _string_ 242 | # 243 | # @!scope class 244 | # 245 | # @api private 246 | 247 | # @!method background_cyan(string) 248 | # Formats the specified _string_ with a cyan background. 249 | # 250 | # @param [String] string the string to color 251 | # 252 | # @return [String] the formatted _string_ 253 | # 254 | # @!scope class 255 | # 256 | # @api private 257 | 258 | # @!method background_gray(string) 259 | # Formats the specified _string_ with a gray background. 260 | # 261 | # @param [String] string the string to color 262 | # 263 | # @return [String] the formatted _string_ 264 | # 265 | # @!scope class 266 | # 267 | # @api private 268 | 269 | # @!method background_default(string) 270 | # Formats the specified _string_ in the default background color. 271 | # 272 | # @param [String] string the string to color 273 | # 274 | # @return [String] the formatted _string_ 275 | # 276 | # @!scope class 277 | # 278 | # @api private 279 | 280 | # Applies the specified _formats_ to _string_. 281 | # 282 | # @param [String] string the string to format 283 | # @param [Array of Symbol] formats the formats to apply 284 | # 285 | # @return [String] the formatted _string_ 286 | def self.format(string, *formats) 287 | formatting_codes = formats.collect do |f| 288 | unless FORMATS.key?(f) 289 | raise ::ArgumentError, "Unrecognized format #{f.inspect}" 290 | end 291 | 292 | FORMATS[f] 293 | end 294 | 295 | return string if formatting_codes.empty? || string.nil? 296 | 297 | "\e[#{formatting_codes.join ';'}m#{string}\e[0m" 298 | end 299 | 300 | private 301 | 302 | def self.method_missing(method, *arguments, &block) 303 | unless respond_to_missing?(method, false) 304 | return super(method, *arguments, &block) 305 | end 306 | 307 | formats = method.to_s.split('_and_').collect(&:to_sym) 308 | format(*(arguments + formats)) 309 | end 310 | 311 | def self.respond_to_missing?(method, include_private) 312 | formats = method.to_s.split('_and_').collect(&:to_sym) 313 | formats.all? do |f| 314 | FORMATS.key? f 315 | end 316 | end 317 | 318 | if RUBY_VERSION <= '1.8.7' 319 | def self.respond_to?(method, include_private=false) 320 | respond_to_missing? method, include_private 321 | end 322 | end 323 | 324 | end 325 | 326 | end 327 | -------------------------------------------------------------------------------- /features/dsl/mirror_rake_tasks/unqualified.feature: -------------------------------------------------------------------------------- 1 | Feature: The #mirror_rake_tasks DSL method 2 | 3 | In order to include Rake tasks with descriptions in my Capistrano recipes, 4 | As a developer using Cape, 5 | I want to use the Cape DSL. 6 | 7 | Scenario: mirror all Rake tasks 8 | Given a full-featured Rakefile 9 | And a Capfile with: 10 | """ 11 | Cape do 12 | mirror_rake_tasks 13 | end 14 | """ 15 | When I run `cap -vT` 16 | Then the output should contain: 17 | """ 18 | cap long # My long task -- it has a ve... 19 | """ 20 | And the output should contain: 21 | """ 22 | cap with_one_arg # My task with one argument. 23 | """ 24 | And the output should contain: 25 | """ 26 | cap my_namespace # A task that shadows a names... 27 | """ 28 | And the output should contain: 29 | """ 30 | cap my_namespace:in_a_namespace # My task in a namespace. 31 | """ 32 | And the output should contain: 33 | """ 34 | cap my_namespace:my_nested_namespace:in_a_nested_namespace # My task in a nested namespace. 35 | """ 36 | And the output should contain: 37 | """ 38 | cap with_two_args # My task with two arguments. 39 | """ 40 | And the output should contain: 41 | """ 42 | cap with_three_args # My task with three arguments. 43 | """ 44 | And the output should contain: 45 | """ 46 | cap hidden_task # 47 | """ 48 | 49 | Scenario: mirror all Rake tasks except a Ruby-method-shadowing task 50 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 51 | And a Capfile with: 52 | """ 53 | Cape do 54 | mirror_rake_tasks 55 | end 56 | """ 57 | When I run `cap -vT` 58 | Then the output should contain: 59 | """ 60 | cap long # My long task -- it has a ve... 61 | """ 62 | And the output should contain: 63 | """ 64 | cap with_one_arg # My task with one argument. 65 | """ 66 | And the output should contain: 67 | """ 68 | cap my_namespace # A task that shadows a names... 69 | """ 70 | And the output should contain: 71 | """ 72 | cap my_namespace:in_a_namespace # My task in a namespace. 73 | """ 74 | And the output should contain: 75 | """ 76 | cap my_namespace:my_nested_namespace:in_a_nested_namespace # My task in a nested namespace. 77 | """ 78 | And the output should contain: 79 | """ 80 | cap with_two_args # My task with two arguments. 81 | """ 82 | And the output should contain: 83 | """ 84 | cap with_three_args # My task with three arguments. 85 | """ 86 | And the output should contain: 87 | """ 88 | cap hidden_task # 89 | """ 90 | And the output should not contain "cap load" 91 | And the output should contain: 92 | """ 93 | *** WARNING: You must use Cape's renaming API in order to mirror Rake task load (defining a task named `load' would shadow an existing method with that name) 94 | """ 95 | 96 | Scenario: mirror Rake task 'long' with its description 97 | Given a full-featured Rakefile 98 | And a Capfile with: 99 | """ 100 | Cape do 101 | mirror_rake_tasks 102 | end 103 | """ 104 | When I run `cap -e long` 105 | Then the output should contain exactly: 106 | """ 107 | ------------------------------------------------------------ 108 | cap long 109 | ------------------------------------------------------------ 110 | My long task -- it has a very, very, very, very, very, very, very, very, very, 111 | very, very, very, very, very, very, very, very, very, very, very, very, very, 112 | very, very, very, very long description. 113 | 114 | 115 | """ 116 | 117 | Scenario: mirror Rake task 'long' with its implementation 118 | Given a full-featured Rakefile 119 | And a Capfile with: 120 | """ 121 | set :current_path, '/current/path' 122 | 123 | Cape do 124 | mirror_rake_tasks 125 | end 126 | """ 127 | When I run `cap long` 128 | Then the output should contain: 129 | """ 130 | * executing `long' 131 | """ 132 | And the output should contain: 133 | """ 134 | `long' is only run for servers matching {}, but no servers matched 135 | """ 136 | 137 | Scenario: mirror Rake task 'long' with its description when a Ruby-method-shadowing task is defined 138 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 139 | And a Capfile with: 140 | """ 141 | Cape do 142 | mirror_rake_tasks 143 | end 144 | """ 145 | When I run `cap -e long` 146 | Then the output should contain exactly: 147 | """ 148 | ------------------------------------------------------------ 149 | cap long 150 | ------------------------------------------------------------ 151 | My long task -- it has a very, very, very, very, very, very, very, very, very, 152 | very, very, very, very, very, very, very, very, very, very, very, very, very, 153 | very, very, very, very long description. 154 | 155 | *** WARNING: You must use Cape's renaming API in order to mirror Rake task load (defining a task named `load' would shadow an existing method with that name) 156 | 157 | """ 158 | 159 | Scenario: mirror Rake task 'long' with its implementation when a Ruby-method-shadowing task is defined 160 | Given a full-featured Rakefile defining a Ruby-method-shadowing task 161 | And a Capfile with: 162 | """ 163 | set :current_path, '/current/path' 164 | 165 | Cape do 166 | mirror_rake_tasks 167 | end 168 | """ 169 | When I run `cap long` 170 | Then the output should contain: 171 | """ 172 | * executing `long' 173 | """ 174 | And the output should contain: 175 | """ 176 | `long' is only run for servers matching {}, but no servers matched 177 | """ 178 | And the output should contain: 179 | """ 180 | *** WARNING: You must use Cape's renaming API in order to mirror Rake task load (defining a task named `load' would shadow an existing method with that name) 181 | """ 182 | 183 | Scenario: mirror Rake task 'with_one_arg' with its description 184 | Given a full-featured Rakefile 185 | And a Capfile with: 186 | """ 187 | Cape do 188 | mirror_rake_tasks 189 | end 190 | """ 191 | When I run `cap -e with_one_arg` 192 | Then the output should contain exactly: 193 | """ 194 | ------------------------------------------------------------ 195 | cap with_one_arg 196 | ------------------------------------------------------------ 197 | My task with one argument. 198 | 199 | Set environment variable THE_ARG if you want to pass a Rake task argument. 200 | 201 | 202 | """ 203 | 204 | Scenario: mirror Rake task 'my_namespace' with its description 205 | Given a full-featured Rakefile 206 | And a Capfile with: 207 | """ 208 | Cape do 209 | mirror_rake_tasks 210 | end 211 | """ 212 | When I run `cap -e my_namespace` 213 | Then the output should contain exactly: 214 | """ 215 | ------------------------------------------------------------ 216 | cap my_namespace 217 | ------------------------------------------------------------ 218 | A task that shadows a namespace. 219 | 220 | 221 | """ 222 | 223 | Scenario: mirror Rake task 'my_namespace' with its implementation 224 | Given a full-featured Rakefile 225 | And a Capfile with: 226 | """ 227 | set :current_path, '/current/path' 228 | 229 | Cape do 230 | mirror_rake_tasks 231 | end 232 | """ 233 | When I run `cap my_namespace` 234 | Then the output should contain: 235 | """ 236 | * executing `my_namespace' 237 | """ 238 | And the output should contain: 239 | """ 240 | `my_namespace' is only run for servers matching {}, but no servers matched 241 | """ 242 | 243 | Scenario: mirror Rake task 'my_namespace:in_a_namespace' with its description 244 | Given a full-featured Rakefile 245 | And a Capfile with: 246 | """ 247 | Cape do 248 | mirror_rake_tasks 249 | end 250 | """ 251 | When I run `cap -e my_namespace:in_a_namespace` 252 | Then the output should contain exactly: 253 | """ 254 | ------------------------------------------------------------ 255 | cap my_namespace:in_a_namespace 256 | ------------------------------------------------------------ 257 | My task in a namespace. 258 | 259 | 260 | """ 261 | 262 | Scenario: mirror Rake task 'my_namespace:my_nested_namespace:in_a_nested_namespace' with its description 263 | Given a full-featured Rakefile 264 | And a Capfile with: 265 | """ 266 | Cape do 267 | mirror_rake_tasks 268 | end 269 | """ 270 | When I run `cap -e my_namespace:my_nested_namespace:in_a_nested_namespace` 271 | Then the output should contain exactly: 272 | """ 273 | ------------------------------------------------------------ 274 | cap my_namespace:my_nested_namespace:in_a_nested_namespace 275 | ------------------------------------------------------------ 276 | My task in a nested namespace. 277 | 278 | 279 | """ 280 | 281 | Scenario: mirror Rake task 'my_namespace:my_nested_namespace:in_a_nested_namespace' with its implementation 282 | Given a full-featured Rakefile 283 | And a Capfile with: 284 | """ 285 | set :current_path, '/current/path' 286 | 287 | Cape do 288 | mirror_rake_tasks 289 | end 290 | """ 291 | When I run `cap my_namespace:my_nested_namespace:in_a_nested_namespace` 292 | Then the output should contain: 293 | """ 294 | * executing `my_namespace:my_nested_namespace:in_a_nested_namespace' 295 | """ 296 | And the output should contain: 297 | """ 298 | `my_namespace:my_nested_namespace:in_a_nested_namespace' is only run for servers matching {}, but no servers matched 299 | """ 300 | 301 | Scenario: mirror Rake task 'with_two_args' with its description 302 | Given a full-featured Rakefile 303 | And a Capfile with: 304 | """ 305 | Cape do 306 | mirror_rake_tasks 307 | end 308 | """ 309 | When I run `cap -e with_two_args` 310 | Then the output should contain exactly: 311 | """ 312 | ------------------------------------------------------------ 313 | cap with_two_args 314 | ------------------------------------------------------------ 315 | My task with two arguments. 316 | 317 | Set environment variables MY_ARG1 and MY_ARG2 if you want to pass Rake task 318 | arguments. 319 | 320 | 321 | """ 322 | 323 | Scenario: mirror Rake task 'with_three_args' with its description 324 | Given a full-featured Rakefile 325 | And a Capfile with: 326 | """ 327 | Cape do 328 | mirror_rake_tasks 329 | end 330 | """ 331 | When I run `cap -e with_three_args` 332 | Then the output should contain exactly: 333 | """ 334 | ------------------------------------------------------------ 335 | cap with_three_args 336 | ------------------------------------------------------------ 337 | My task with three arguments. 338 | 339 | Set environment variables AN_ARG1, AN_ARG2, and AN_ARG3 if you want to pass Rake 340 | task arguments. 341 | 342 | 343 | """ 344 | 345 | Scenario: mirror Rake task 'with_three_args' with its implementation 346 | Given a full-featured Rakefile 347 | And a Capfile with: 348 | """ 349 | set :current_path, '/current/path' 350 | 351 | Cape do 352 | mirror_rake_tasks 353 | end 354 | """ 355 | When I run `cap with_three_args AN_ARG1="a value for an_arg1" AN_ARG2="a value for an_arg2" AN_ARG3="a value for an_arg3"` 356 | Then the output should contain: 357 | """ 358 | * executing `with_three_args' 359 | """ 360 | And the output should contain: 361 | """ 362 | `with_three_args' is only run for servers matching {}, but no servers matched 363 | """ 364 | 365 | Scenario: mirror Rake task 'with_three_args' with its implementation not enforcing arguments 366 | Given a full-featured Rakefile 367 | And a Capfile with: 368 | """ 369 | set :current_path, '/current/path' 370 | 371 | Cape do 372 | mirror_rake_tasks 373 | end 374 | """ 375 | When I run `cap with_three_args AN_ARG2="a value for an_arg2"` 376 | Then the output should contain: 377 | """ 378 | * executing `with_three_args' 379 | """ 380 | And the output should contain: 381 | """ 382 | `with_three_args' is only run for servers matching {}, but no servers matched 383 | """ 384 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | _____ __ 2 | / ___/___ / /_ __ _____ __ ______ 3 | / (_ // -_) __/ / // / _ | // / __/ 4 | \___/ \__/\__/ \_, /\___|_,_/_/ 5 | '-(+++++... /___/ ............ s++++++++. 6 | .(++++++++++++( -+++~~~~~ 'N++++++++++= B++++++++s 7 | '+++++++++++++++( DN=++++++< -NB+++++++++s Bz++++++++ 8 | +++++++++++++++++. 'NNN=++++++(-BNB+++++++++'BN========- 9 | =B=+++++++++sDBBBBs<. +NNNN=+++++( \____ 27 | ~BNh .+B+ / _ \/ _ \ = \ / 28 | ~Dz~. .~+zB=' \___/_//_/ / \ \__/ 29 | 1' 73 | 74 | ## Compatibility 75 | 76 | Cape [runs](http://travis-ci.org/njonsson/cape) on the following Ruby versions: 77 | 78 | * MRI v1.8.7, v1.9.2, v1.9.3, v2.0.0 79 | * REE v1.8.7 80 | 81 | Cape integrates with the following RubyGems: 82 | 83 | * Capistrano v2.x 84 | * Rake v0.8.7 and later 85 | 86 | **A Rake task that lacks a description can be mirrored or enumerated only if Rake v0.9.3 or newer is installed.** Older versions of Rake did not support enumerating such tasks. You may want to make Rake v0.9.3 a dependency of your project. 87 | 88 | # Gemfile 89 | 90 | source 'http://rubygems.org' 91 | 92 | gem 'rake', '>= 0.9.3' 93 | 94 | ## Examples 95 | 96 | Assume the following Rake tasks are defined. 97 | 98 | desc 'Rakes the leaves' 99 | task :leaves do 100 | $stdout.puts "Raking the leaves" 101 | end 102 | 103 | desc 'Rakes and bags the leaves' 104 | task :bag_leaves, [:paper_or_plastic] => :leaves do |task, arguments| 105 | $stdout.puts "Putting the leaves in a #{arguments[:paper_or_plastic]} bag" 106 | end 107 | 108 | Rake lists these tasks in the expected fashion. 109 | 110 | $ rake --tasks 111 | rake bag_leaves[paper_or_plastic] # Rakes and bags the leaves 112 | rake leaves # Rakes the leaves 113 | 114 | $ rake --describe bag_leaves 115 | rake bag_leaves[paper_or_plastic] 116 | Rakes and bags the leaves 117 | 118 | ### Simply mirror all Rake tasks as Capistrano recipes 119 | 120 | Add the following to your Capistrano recipes. Note that Cape statements must be contained in a `Cape` block. 121 | 122 | # config/deploy.rb 123 | 124 | require 'cape' 125 | 126 | Cape do 127 | # Create Capistrano recipes for all Rake tasks. 128 | mirror_rake_tasks 129 | end 130 | 131 | Now all your Rake tasks appear alongside your Capistrano recipes. 132 | 133 | $ cap --tasks 134 | cap deploy # Deploys your project. 135 | ... 136 | [other built-in Capistrano recipes] 137 | ... 138 | cap bag_leaves # Rakes and bags the leaves. 139 | cap leaves # Rakes the leaves. 140 | Some tasks were not listed, either because they have no description, 141 | or because they are only used internally by other tasks. To see all 142 | tasks, type `cap -vT'. 143 | 144 | Extended help may be available for these tasks. 145 | Type `cap -e taskname' to view it. 146 | 147 | Let’s use Capistrano to view the unabbreviated description of a Rake task recipe, including instructions for how to pass arguments to it. Note that Rake task parameters are automatically converted to environment variables. 148 | 149 | $ cap --explain bag_leaves 150 | ------------------------------------------------------------ 151 | cap bag_leaves 152 | ------------------------------------------------------------ 153 | Bags the leaves. 154 | 155 | Set environment variable PAPER_OR_PLASTIC if you want to pass a Rake task argument. 156 | 157 | Here’s how to invoke a task/recipe with arguments. On the local computer, via Rake: 158 | 159 | $ rake bag_leaves[plastic] 160 | (in /current/working/directory) 161 | Raking the leaves 162 | Putting the leaves in a plastic bag 163 | 164 | On remote computers, via Capistrano: 165 | 166 | $ cap bag_leaves PAPER_OR_PLASTIC=plastic 167 | * executing `bag_leaves' 168 | * executing "cd /path/to/currently/deployed/version/of/your/app && /usr/bin/env `/usr/bin/env bundle check >/dev/null 2>&1; case $? in 0|1 ) echo bundle exec ;; esac` rake bag_leaves[plastic]" 169 | servers: ["your.server.name"] 170 | [your.server.name] executing command 171 | ** [out :: your.server.name] (in /path/to/currently/deployed/version/of/your/app) 172 | ** [out :: your.server.name] Raking the leaves 173 | ** [out :: your.server.name] Putting the leaves in a plastic bag 174 | command finished in 1000ms 175 | 176 | ### Mirror some Rake tasks, but not others 177 | 178 | Cape lets you filter the Rake tasks to be mirrored. Note that Cape statements must be contained in a `Cape` block. 179 | 180 | # config/deploy.rb 181 | 182 | require 'cape' 183 | 184 | Cape do 185 | # Create Capistrano recipes for the Rake task 'foo' and/or for the tasks in 186 | # the 'foo' namespace. 187 | mirror_rake_tasks :foo 188 | end 189 | 190 | ### Mirror Rake tasks that require renaming, Capistrano recipe options, path switching, and/or environment variables 191 | 192 | Cape lets you customize mirrored Rake tasks to suit your needs. Note that Cape statements must be contained in a `Cape` block, and references to Capistrano variables such as `rails_env` and `release_path` must be contained in an inner block, lambda, or other callable object. 193 | 194 | # config/deploy.rb 195 | 196 | require 'cape' 197 | 198 | Cape do 199 | # Display defined Rails routes on application server remote machines only. 200 | mirror_rake_tasks :routes do |recipes| 201 | recipes.options[:roles] = :app 202 | end 203 | 204 | # Execute database migration on application server remote machines only, 205 | # and set the 'RAILS_ENV' environment variable to the value of the 206 | # Capistrano variable 'rails_env'. 207 | mirror_rake_tasks 'db:migrate' do |recipes| 208 | recipes.options[:roles] = :app 209 | recipes.env['RAILS_ENV'] = lambda { rails_env } 210 | end 211 | 212 | # Support a Rake task that must be run on application server remote 213 | # machines only, and in the remote directory 'release_path' instead of the 214 | # default, 'current_path'. 215 | before 'deploy:symlink', :spec 216 | mirror_rake_tasks :spec do |recipes| 217 | recipes.cd { release_path } 218 | recipes.options[:roles] = :app 219 | end 220 | 221 | # Avoid collisions with the existing Ruby method #test, run tests on 222 | # application server remote machines only, and set the 'RAILS_ENV' 223 | # environment variable to the value of the Capistrano variable 224 | # 'rails_env'. 225 | mirror_rake_tasks :test do |recipes| 226 | recipes.rename do |rake_task_name| 227 | "#{rake_task_name}_task" 228 | end 229 | recipes.options[:roles] = :app 230 | recipes.env['RAILS_ENV'] = lambda { rails_env } 231 | end 232 | end 233 | 234 | The above is equivalent to the following manually-defined Capistrano recipes. 235 | 236 | # config/deploy.rb 237 | 238 | # These translations to Capistrano are just for illustration. 239 | 240 | RAKE = '/usr/bin/env ' + 241 | '`' + 242 | '/usr/bin/env bundle check >/dev/null 2>&1; ' + 243 | 'case $? in ' + 244 | # Exit code 0: bundle is defined and installed 245 | # Exit code 1: bundle is defined but not installed 246 | '0|1 ) ' + 247 | 'echo bundle exec ' + 248 | ';; ' + 249 | 'esac' + 250 | '` ' + 251 | 'rake' 252 | 253 | task :routes, :roles => :app do 254 | run "cd #{current_path} && #{RAKE} routes" 255 | end 256 | 257 | namespace :db do 258 | task :migrate, :roles => :app do 259 | run "cd #{current_path} && #{RAKE} db:migrate RAILS_ENV=#{rails_env}" 260 | end 261 | end 262 | 263 | before 'deploy:symlink', :spec 264 | task :spec, :roles => :app do 265 | run "cd #{release_path} && #{RAKE} routes" 266 | end 267 | 268 | task :test_task, :roles => :app do 269 | run "cd #{current_path} && #{RAKE} test RAILS_ENV=#{rails_env}" 270 | end 271 | 272 | ### Mirror Rake tasks into a Capistrano namespace 273 | 274 | Cape plays friendly with the Capistrano DSL for organizing Rake tasks in Capistrano namespaces. Note that Cape statements must be contained in a `Cape` block. 275 | 276 | # config/deploy.rb 277 | 278 | require 'cape' 279 | 280 | namespace :rake_tasks do 281 | # Use an argument with the Cape block, if you want to or need to. 282 | Cape do |cape| 283 | cape.mirror_rake_tasks 284 | end 285 | end 286 | 287 | ### Iterate over available Rake tasks 288 | 289 | Cape lets you enumerate Rake tasks, optionally filtering them by task name or namespace. Note that Cape statements must be contained in a `Cape` block. 290 | 291 | # config/deploy.rb 292 | 293 | require 'cape' 294 | 295 | Cape do 296 | # Enumerate all Rake tasks. 297 | each_rake_task do |t| 298 | # Do something interesting with this hash: 299 | # * t[:name] -- the full name of the task 300 | # * t[:parameters] -- the names of task arguments 301 | # * t[:description] -- documentation on the task, including parameters 302 | end 303 | 304 | # Enumerate the Rake task 'foo' and/or the tasks in the 'foo' namespace. 305 | each_rake_task 'foo' do |t| 306 | # ... 307 | end 308 | end 309 | 310 | ### Configure Rake execution 311 | 312 | Cape lets you specify how Rake should be run on the local computer and on remote computers. But the default behavior is most likely just right for your needs: 313 | 314 | * It detects whether Bundler is installed on the computer 315 | * It detects whether the project uses Bundler to manage its dependencies 316 | * It runs Rake via Bundler if the above conditions are true; otherwise, it runs Rake directly 317 | 318 | Note that Cape statements must be contained in a `Cape` block. 319 | 320 | # config/deploy.rb 321 | 322 | require 'cape' 323 | 324 | # Configure Cape never to run Rake via Bundler, neither locally nor remotely. 325 | Cape.local_rake_executable = '/usr/bin/env rake' 326 | Cape.remote_rake_executable = '/usr/bin/env rake' 327 | 328 | Cape do 329 | # Create Capistrano recipes for all Rake tasks. 330 | mirror_rake_tasks 331 | end 332 | 333 | ## Contributing 334 | 335 | Report defects and feature requests on [GitHub Issues](http://github.com/njonsson/cape/issues). 336 | 337 | Your patches are welcome, and you will receive attribution here for good stuff. 338 | 339 | ## License 340 | 341 | Released under the [MIT License](http://github.com/njonsson/cape/blob/master/License.markdown). 342 | 343 | [Travis CI build status]: https://secure.travis-ci.org/njonsson/cape.png?branch=master 344 | [Gemnasium build status]: https://gemnasium.com/njonsson/cape.png 345 | [Code Climate report]: https://codeclimate.com/github/njonsson/cape.png 346 | [Inline docs]: http://inch-pages.github.io/github/njonsson/cape.png 347 | [RubyGems release]: https://badge.fury.io/rb/cape.png 348 | --------------------------------------------------------------------------------