cape 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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