cape 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +7 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +18 -0
  6. data/History.markdown +7 -0
  7. data/MIT-LICENSE.markdown +10 -0
  8. data/README.markdown +157 -0
  9. data/Rakefile +49 -0
  10. data/cape.gemspec +36 -0
  11. data/features/dsl/each_rake_task/with_defined_namespace_argument.feature +43 -0
  12. data/features/dsl/each_rake_task/with_defined_task_argument.feature +37 -0
  13. data/features/dsl/each_rake_task/with_undefined_argument.feature +31 -0
  14. data/features/dsl/each_rake_task/without_arguments.feature +81 -0
  15. data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_defined_namespace_argument.feature +93 -0
  16. data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_defined_task_argument.feature +66 -0
  17. data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/with_undefined_argument.feature +39 -0
  18. data/features/dsl/mirror_rake_tasks/inside_capistrano_namespace/without_arguments.feature +263 -0
  19. data/features/dsl/mirror_rake_tasks/with_defined_namespace_argument.feature +85 -0
  20. data/features/dsl/mirror_rake_tasks/with_defined_task_argument.feature +60 -0
  21. data/features/dsl/mirror_rake_tasks/with_undefined_argument.feature +35 -0
  22. data/features/dsl/mirror_rake_tasks/without_arguments.feature +243 -0
  23. data/features/step_definitions.rb +33 -0
  24. data/features/support/env.rb +1 -0
  25. data/features/task_invocation/nonparameterized.feature +69 -0
  26. data/features/task_invocation/parameterized.feature +70 -0
  27. data/lib/cape.rb +22 -0
  28. data/lib/cape/capistrano.rb +86 -0
  29. data/lib/cape/core_ext.rb +10 -0
  30. data/lib/cape/core_ext/hash.rb +24 -0
  31. data/lib/cape/core_ext/symbol.rb +25 -0
  32. data/lib/cape/dsl.rb +81 -0
  33. data/lib/cape/rake.rb +60 -0
  34. data/lib/cape/strings.rb +25 -0
  35. data/lib/cape/version.rb +6 -0
  36. data/spec/cape/capistrano_spec.rb +0 -0
  37. data/spec/cape/core_ext/hash_spec.rb +12 -0
  38. data/spec/cape/core_ext/symbol_spec.rb +7 -0
  39. data/spec/cape/dsl_spec.rb +128 -0
  40. data/spec/cape/rake_spec.rb +0 -0
  41. data/spec/cape/strings_spec.rb +44 -0
  42. data/spec/cape/task_spec.rb +0 -0
  43. data/spec/cape/version_spec.rb +6 -0
  44. data/spec/cape_spec.rb +5 -0
  45. metadata +192 -0
@@ -0,0 +1 @@
1
+ require 'aruba/cucumber'
@@ -0,0 +1,69 @@
1
+ Feature: Invoking parameterless Rake tasks via Capistrano
2
+
3
+ In order to invoke Rake tasks via Capistrano,
4
+ As a developer using Cape,
5
+ I want to use the `cap` command.
6
+
7
+ Scenario: invoke Rake task 'with_period'
8
+ Given a full-featured Rakefile
9
+ And a file named "Capfile" with:
10
+ """
11
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
12
+
13
+ # Uncomment if you are using Rails' asset pipeline
14
+ # load 'deploy/assets'
15
+
16
+ Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
17
+
18
+ load 'config/deploy' # remove this line to skip loading any of the default tasks
19
+ require 'cape'
20
+
21
+ Cape do
22
+ mirror_rake_tasks
23
+ end
24
+ """
25
+ And a file named "config/deploy.rb" with:
26
+ """
27
+ require 'cape'
28
+
29
+ Cape do
30
+ mirror_rake_tasks
31
+ end
32
+ """
33
+ When I run `cap with_period`
34
+ Then the output should contain:
35
+ """
36
+ * executing `with_period'
37
+ """
38
+
39
+ Scenario: invoke Rake task 'my_namespace:in_a_namespace'
40
+ Given a full-featured Rakefile
41
+ And a file named "Capfile" with:
42
+ """
43
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
44
+
45
+ # Uncomment if you are using Rails' asset pipeline
46
+ # load 'deploy/assets'
47
+
48
+ Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
49
+
50
+ load 'config/deploy' # remove this line to skip loading any of the default tasks
51
+ require 'cape'
52
+
53
+ Cape do
54
+ mirror_rake_tasks
55
+ end
56
+ """
57
+ And a file named "config/deploy.rb" with:
58
+ """
59
+ require 'cape'
60
+
61
+ Cape do
62
+ mirror_rake_tasks
63
+ end
64
+ """
65
+ When I run `cap my_namespace:in_a_namespace`
66
+ Then the output should contain:
67
+ """
68
+ * executing `my_namespace:in_a_namespace'
69
+ """
@@ -0,0 +1,70 @@
1
+ Feature: Invoking parameterized Rake tasks via Capistrano
2
+
3
+ In order to invoke Rake tasks via Capistrano,
4
+ As a developer using Cape,
5
+ I want to use the `cap` command.
6
+
7
+ Scenario: invoke Rake task 'with_one_arg' without an argument
8
+ Given a full-featured Rakefile
9
+ And a file named "Capfile" with:
10
+ """
11
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
12
+
13
+ # Uncomment if you are using Rails' asset pipeline
14
+ # load 'deploy/assets'
15
+
16
+ Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
17
+
18
+ load 'config/deploy' # remove this line to skip loading any of the default tasks
19
+ require 'cape'
20
+
21
+ Cape do
22
+ mirror_rake_tasks
23
+ end
24
+ """
25
+ And a file named "config/deploy.rb" with:
26
+ """
27
+ require 'cape'
28
+
29
+ Cape do
30
+ mirror_rake_tasks
31
+ end
32
+ """
33
+ When I run `cap with_one_arg`
34
+ Then the output should contain:
35
+ """
36
+ * executing `with_one_arg'
37
+ """
38
+ And the output should contain "Environment variable THE_ARG must be set (RuntimeError)"
39
+
40
+ Scenario: invoke Rake task 'with_one_arg' with its argument
41
+ Given a full-featured Rakefile
42
+ And a file named "Capfile" with:
43
+ """
44
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
45
+
46
+ # Uncomment if you are using Rails' asset pipeline
47
+ # load 'deploy/assets'
48
+
49
+ Dir['vendor/gems/*/recipes/*.rb','vendor/plugins/*/recipes/*.rb'].each { |plugin| load(plugin) }
50
+
51
+ load 'config/deploy' # remove this line to skip loading any of the default tasks
52
+ require 'cape'
53
+
54
+ Cape do
55
+ mirror_rake_tasks
56
+ end
57
+ """
58
+ And a file named "config/deploy.rb" with:
59
+ """
60
+ require 'cape'
61
+
62
+ Cape do
63
+ mirror_rake_tasks
64
+ end
65
+ """
66
+ When I run `cap with_one_arg THE_ARG="the arg goes here"`
67
+ Then the output should contain:
68
+ """
69
+ * executing `with_one_arg'
70
+ """
@@ -0,0 +1,22 @@
1
+ Dir.glob( File.expand_path( 'cape/*.rb', File.dirname( __FILE__ ))) do |f|
2
+ require "cape/#{File.basename f, '.rb'}"
3
+ end
4
+
5
+ # Contains the implementation of Cape.
6
+ module Cape
7
+
8
+ extend DSL
9
+
10
+ end
11
+
12
+ # The method used to group Cape statements in a block.
13
+ def Cape(&block)
14
+ Cape.module_eval do
15
+ @outer_self = block.binding.eval('self', __FILE__, __LINE__)
16
+ if 0 < block.arity
17
+ block.call self
18
+ else
19
+ module_eval(&block)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,86 @@
1
+ require 'cape/strings'
2
+
3
+ module Cape
4
+
5
+ # An abstraction of the Capistrano installation.
6
+ class Capistrano
7
+
8
+ # Defines the specified _task_ as a Capistrano task, provided a Binding
9
+ # named argument +:binding+ and a Cape::Rake named argument +:rake+. Any
10
+ # parameters the task has are converted to environment variables, since
11
+ # Capistrano does not have the concept of task parameters.
12
+ #
13
+ # The _task_ argument must be a Hash of the form:
14
+ #
15
+ # {:name => <String>,
16
+ # :parameters => <String Array or nil>,
17
+ # :description => <String>}
18
+ def define(task, named_arguments={})
19
+ unless (binding = named_arguments[:binding])
20
+ raise ::ArgumentError, ':binding named argument is required'
21
+ end
22
+ unless (rake = named_arguments[:rake])
23
+ raise ::ArgumentError, ':rake named argument is required'
24
+ end
25
+ roles = named_arguments[:roles] || :app
26
+
27
+ capistrano = binding.eval('self', __FILE__, __LINE__)
28
+ if (description = build_capistrano_description(task))
29
+ capistrano.desc description
30
+ end
31
+ implement task, roles, capistrano, rake
32
+ self
33
+ end
34
+
35
+ private
36
+
37
+ def build_capistrano_description(task)
38
+ return nil unless task[:description]
39
+
40
+ description = [task[:description]]
41
+ description << '.' unless task[:description].end_with?('.')
42
+ unless (parameters = Array(task[:parameters])).empty?
43
+ noun = Strings.pluralize('variable', parameters.length)
44
+ parameters_list = Strings.to_list_phrase(parameters.collect(&:upcase))
45
+ description << <<-end_description
46
+
47
+
48
+ You must set environment #{noun} #{parameters_list}.
49
+ end_description
50
+ end
51
+ description.join
52
+ end
53
+
54
+ def implement(task, roles, capistrano_context, rake)
55
+ name = task[:name].split(':')
56
+ # Define the task.
57
+ block = lambda { |context|
58
+ context.task name.last, :roles => roles do
59
+ arguments = Array(task[:parameters]).collect do |a|
60
+ unless (value = ENV[a.upcase])
61
+ fail "Environment variable #{a.upcase} must be set"
62
+ end
63
+ value
64
+ end
65
+ if arguments.empty?
66
+ arguments = nil
67
+ else
68
+ arguments = "[#{arguments.join ','}]"
69
+ end
70
+ context.run "cd #{context.current_path} && " +
71
+ "#{rake.remote_executable} #{name.join ':'}#{arguments}"
72
+ end
73
+ }
74
+ # Nest the task inside its containing namespaces.
75
+ name[0...-1].reverse.each do |namespace_token|
76
+ inner_block = block
77
+ block = lambda { |context|
78
+ context.namespace(namespace_token, &inner_block)
79
+ }
80
+ end
81
+ block.call capistrano_context
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,10 @@
1
+ Dir.glob( File.expand_path( 'core_ext/*.rb', File.dirname( __FILE__ ))) do |f|
2
+ require "cape/core_ext/#{File.basename f, '.rb'}"
3
+ end
4
+
5
+ module Cape
6
+
7
+ # Contains extensions to core types.
8
+ module CoreExt; end
9
+
10
+ end
@@ -0,0 +1,24 @@
1
+ module Cape
2
+
3
+ module CoreExt
4
+
5
+ # Contains extensions to the Hash core class.
6
+ module Hash
7
+
8
+ # Returns a copy of the Hash containing values only for the specified
9
+ # _keys_.
10
+ def slice(*keys)
11
+ ::Hash[select { |key, value| keys.include? key }]
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+
20
+ unless Hash.instance_methods.collect(&:to_s).include?('slice')
21
+ Hash.class_eval do
22
+ include Cape::CoreExt::Hash
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Cape
2
+
3
+ module CoreExt
4
+
5
+ # Contains extensions to the Symbol core class.
6
+ module Symbol
7
+
8
+ # Returns +0+ if the Symbol is equal to _other_, +-1+ if it is
9
+ # alphabetically lesser than _other_, and +1+ if it is alphabetically
10
+ # greater than _other_.
11
+ def <=>(other)
12
+ to_s <=> other.to_s
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ unless Symbol.instance_methods.collect(&:to_s).include?('<=>')
22
+ Symbol.class_eval do
23
+ include Cape::CoreExt::Symbol
24
+ end
25
+ end
@@ -0,0 +1,81 @@
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
+ # Yields each available Rake task to a block. The optional _task_expression_
10
+ # argument limits the list to a single task or a namespace containing
11
+ # multiple tasks.
12
+ #
13
+ # Tasks are yielded as Hash objects of the form:
14
+ #
15
+ # {:name => <String>,
16
+ # :parameters => <String Array or nil>,
17
+ # :description => <String>}
18
+ def each_rake_task(task_expression=nil, &block)
19
+ rake.each_task(task_expression, &block)
20
+ self
21
+ end
22
+
23
+ # Returns the command used to run Rake on the local computer. Defaults to
24
+ # Rake::DEFAULT_EXECUTABLE.
25
+ def local_rake_executable
26
+ rake.local_executable
27
+ end
28
+
29
+ # Sets the command used to run Rake on the local computer.
30
+ def local_rake_executable=(value)
31
+ rake.local_executable = value
32
+ end
33
+
34
+ # Defines each available Rake task as a Capistrano task. Any
35
+ # parameters the tasks have are converted to environment variables, since
36
+ # Capistrano does not have the concept of task parameters. The optional
37
+ # _task_expression_ argument limits the list to a single task or a namespace
38
+ # containing multiple tasks.
39
+ def mirror_rake_tasks(task_expression=nil)
40
+ d = nil
41
+ rake.each_task task_expression do |t|
42
+ (d ||= deployment_library).define t, :binding => binding, :rake => rake
43
+ end
44
+ self
45
+ end
46
+
47
+ # Returns the command used to run Rake on remote computers. Defaults to
48
+ # Rake::DEFAULT_EXECUTABLE.
49
+ def remote_rake_executable
50
+ rake.remote_executable
51
+ end
52
+
53
+ # Sets the command used to run Rake on remote computers.
54
+ def remote_rake_executable=(value)
55
+ rake.remote_executable = value
56
+ end
57
+
58
+ private
59
+
60
+ def deployment_library
61
+ raise_unless_capistrano
62
+ Capistrano.new
63
+ end
64
+
65
+ def method_missing(method, *args, &block)
66
+ @outer_self.send(method, *args, &block)
67
+ end
68
+
69
+ def raise_unless_capistrano
70
+ if @outer_self.method(:task).owner.name !~ /^Capistrano::/
71
+ raise 'Use this in the context of Capistrano recipes'
72
+ end
73
+ end
74
+
75
+ def rake
76
+ @rake ||= Rake.new
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,60 @@
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.
7
+ DEFAULT_EXECUTABLE = '/usr/bin/env rake'.freeze
8
+
9
+ # Sets the command used to run Rake on the local computer.
10
+ attr_writer :local_executable
11
+
12
+ # Sets the command used to run Rake on remote computers.
13
+ attr_writer :remote_executable
14
+
15
+ # Constructs a new Rake object with the specified _attributes_.
16
+ def initialize(attributes={})
17
+ attributes.each do |name, value|
18
+ send "#{name}=", value
19
+ end
20
+ end
21
+
22
+ # Yields each available Rake task to a block. The optional _task_expression_
23
+ # argument limits the list to a single task or a namespace containing
24
+ # multiple tasks.
25
+ #
26
+ # Tasks are yielded as Hash objects of the form:
27
+ #
28
+ # {:name => <String>,
29
+ # :parameters => <String Array or nil>,
30
+ # :description => <String>}
31
+ def each_task(task_expression=nil)
32
+ task_expression = " #{task_expression}" if task_expression
33
+ command = "#{local_executable} --tasks #{task_expression}"
34
+ `#{command}`.each_line do |l|
35
+ matches = l.chomp.match(/^rake (.+?)(?:\[(.+?)\])?\s+# (.+)/)
36
+ task = {}.tap do |t|
37
+ t[:name] = matches[1].strip
38
+ t[:parameters] = matches[2].split(',') if matches[2]
39
+ t[:description] = matches[3]
40
+ end
41
+ yield task
42
+ end
43
+ self
44
+ end
45
+
46
+ # Returns the command used to run Rake on the local computer. Defaults to
47
+ # DEFAULT_EXECUTABLE.
48
+ def local_executable
49
+ @local_executable ||= DEFAULT_EXECUTABLE
50
+ end
51
+
52
+ # Returns the command used to run Rake on remote computers. Defaults to
53
+ # DEFAULT_EXECUTABLE.
54
+ def remote_executable
55
+ @remote_executable ||= DEFAULT_EXECUTABLE
56
+ end
57
+
58
+ end
59
+
60
+ end