mattock 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/mattock/cascading-definition.rb +31 -9
- data/lib/mattock/command-line.rb +88 -14
- data/lib/mattock/command-task.rb +17 -0
- data/lib/mattock/remote-command-task.rb +1 -0
- data/lib/mattock/task.rb +15 -4
- data/lib/mattock/testing/mock-command-line.rb +28 -30
- data/lib/mattock/testing/record-commands.rb +7 -7
- data/spec/command-line.rb +54 -1
- data/spec/configurable.rb +0 -1
- metadata +10 -10
@@ -26,21 +26,38 @@ module Mattock
|
|
26
26
|
def initialize(*tasklibs)
|
27
27
|
@runtime = false
|
28
28
|
setup_defaults
|
29
|
-
default_configuration(*tasklibs)
|
30
29
|
|
31
|
-
|
30
|
+
confirm_steps(:default_configuration, :resolve_configuration, :confirm_configuration) do
|
31
|
+
default_configuration(*tasklibs)
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
yield self if block_given?
|
34
|
+
|
35
|
+
resolve_configuration
|
36
|
+
confirm_configuration
|
37
|
+
end
|
35
38
|
|
36
39
|
define
|
37
40
|
end
|
38
41
|
|
42
|
+
def confirm_steps(*steps)
|
43
|
+
@steps = steps
|
44
|
+
yield
|
45
|
+
unless @steps.empty?
|
46
|
+
#Otherwise, it's very easy to forget the 'super' statement and leave
|
47
|
+
#essential configuration out. The result is really confusing
|
48
|
+
raise "#{self.class.name} didn't run superclass step#{@steps.length == 1 ? "" : "s"}: #{@steps.inspect} (put a 'super' in appropriate methods)"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def confirm_step(step)
|
53
|
+
@steps.delete(step)
|
54
|
+
end
|
39
55
|
|
40
56
|
#@param [TaskLib] tasklibs Other libs upon which this one depends to set
|
41
57
|
# its defaults
|
42
58
|
#Sets default values for library settings
|
43
59
|
def default_configuration(*tasklibs)
|
60
|
+
confirm_step(:default_configuration)
|
44
61
|
end
|
45
62
|
|
46
63
|
#Called after the configuration block has been called, so secondary
|
@@ -52,6 +69,7 @@ module Mattock
|
|
52
69
|
#the Rakefile, or if bin_dir and command_name are set, we can put those
|
53
70
|
#together.
|
54
71
|
def resolve_configuration
|
72
|
+
confirm_step(:resolve_configuration)
|
55
73
|
end
|
56
74
|
|
57
75
|
#Last step before definition: confirm that all the configuration settings
|
@@ -59,6 +77,7 @@ module Mattock
|
|
59
77
|
#given values. Very much shortens the debugging cycle when using TaskLibs
|
60
78
|
#if this is well written.
|
61
79
|
def confirm_configuration
|
80
|
+
confirm_step(:confirm_configuration)
|
62
81
|
check_required
|
63
82
|
end
|
64
83
|
|
@@ -88,16 +107,19 @@ module Mattock
|
|
88
107
|
!!@runtime
|
89
108
|
end
|
90
109
|
|
91
|
-
def resolve_runtime_configuration
|
92
|
-
end
|
93
|
-
|
94
110
|
def finalize_configuration
|
95
111
|
return if @finalized
|
96
112
|
@runtime = true
|
97
113
|
configuration_block[self]
|
98
|
-
resolve_runtime_configuration
|
99
|
-
|
114
|
+
confirm_steps(:resolve_runtime_configuration, :confirm_configuration) do
|
115
|
+
resolve_runtime_configuration
|
116
|
+
confirm_configuration
|
117
|
+
end
|
100
118
|
@finalized = true
|
101
119
|
end
|
120
|
+
|
121
|
+
def resolve_runtime_configuration
|
122
|
+
confirm_step(:resolve_runtime_configuration)
|
123
|
+
end
|
102
124
|
end
|
103
125
|
end
|
data/lib/mattock/command-line.rb
CHANGED
@@ -27,12 +27,16 @@ module Mattock
|
|
27
27
|
return false
|
28
28
|
end
|
29
29
|
|
30
|
+
def format_streams
|
31
|
+
"stdout:#{stdout.empty? ? "[empty]\n" : "\n#{stdout}"}stderr:#{stderr.empty? ? "[empty]\n" : "\n#{stderr}"}---"
|
32
|
+
end
|
33
|
+
|
30
34
|
def must_succeed!
|
31
35
|
case exit_code
|
32
36
|
when 0
|
33
37
|
return exit_code
|
34
38
|
else
|
35
|
-
fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{
|
39
|
+
fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{format_streams}"
|
36
40
|
end
|
37
41
|
end
|
38
42
|
end
|
@@ -62,10 +66,14 @@ module Mattock
|
|
62
66
|
@executable = executable
|
63
67
|
@options = options
|
64
68
|
@redirections = []
|
69
|
+
@env = {}
|
65
70
|
yield self if block_given?
|
66
71
|
end
|
67
72
|
|
68
|
-
attr_accessor :name, :executable, :options
|
73
|
+
attr_accessor :name, :executable, :options, :env
|
74
|
+
attr_reader :redirections
|
75
|
+
|
76
|
+
alias_method :command_environment, :env
|
69
77
|
|
70
78
|
def verbose
|
71
79
|
Rake.verbose && Rake.verbose != Rake::FileUtilsExt::DEFAULT
|
@@ -79,6 +87,12 @@ module Mattock
|
|
79
87
|
([executable] + options_composition + @redirections).join(" ")
|
80
88
|
end
|
81
89
|
|
90
|
+
def string_format
|
91
|
+
(command_environment.map do |key, value|
|
92
|
+
[key, value].join("=")
|
93
|
+
end + [command]).join(" ")
|
94
|
+
end
|
95
|
+
|
82
96
|
def options_composition
|
83
97
|
options
|
84
98
|
end
|
@@ -107,19 +121,59 @@ module Mattock
|
|
107
121
|
redirect_from(path, 0)
|
108
122
|
end
|
109
123
|
|
110
|
-
def
|
111
|
-
|
112
|
-
|
124
|
+
def spawn_process
|
125
|
+
host_stdout, cmd_stdout = IO.pipe
|
126
|
+
host_stderr, cmd_stderr = IO.pipe
|
127
|
+
|
128
|
+
pid = Process.spawn(command_environment, command, :out => cmd_stdout, :err => cmd_stderr)
|
129
|
+
cmd_stdout.close
|
130
|
+
cmd_stderr.close
|
131
|
+
|
132
|
+
return pid, host_stdout, host_stderr
|
133
|
+
end
|
134
|
+
|
135
|
+
def collect_result(pid, host_stdout, host_stderr)
|
113
136
|
pid, status = Process.wait2(pid)
|
114
|
-
|
115
|
-
|
137
|
+
|
138
|
+
stdout = host_stdout.read
|
139
|
+
stderr = host_stderr.read
|
140
|
+
result = CommandRunResult.new(command, status, {1 => stdout, 2 => stderr})
|
141
|
+
host_stdout.close
|
142
|
+
host_stderr.close
|
143
|
+
|
116
144
|
return result
|
117
145
|
end
|
118
146
|
|
147
|
+
#If I wasn't worried about writing my own limited shell, I'd say e.g.
|
148
|
+
#Pipeline would be an explicit chain of pipes... which is probably as
|
149
|
+
#originally intended :/
|
150
|
+
def execute
|
151
|
+
collect_result(*spawn_process)
|
152
|
+
end
|
153
|
+
|
154
|
+
def background
|
155
|
+
pid, out, err = spawn_process
|
156
|
+
at_exit do
|
157
|
+
kill_process(pid)
|
158
|
+
Process.detach(pid)
|
159
|
+
end
|
160
|
+
return pid, out, err
|
161
|
+
end
|
162
|
+
|
163
|
+
def kill_process(pid)
|
164
|
+
Process.kill("INT", pid)
|
165
|
+
end
|
166
|
+
|
167
|
+
def complete(pid, out, err)
|
168
|
+
kill_process(pid)
|
169
|
+
collect_result(pid, out, err)
|
170
|
+
end
|
171
|
+
|
119
172
|
def run
|
120
|
-
print
|
121
|
-
result =
|
122
|
-
|
173
|
+
print string_format + " "
|
174
|
+
result = execute
|
175
|
+
puts "=> #{result.exit_code}"
|
176
|
+
puts result.format_streams if verbose
|
123
177
|
return result
|
124
178
|
ensure
|
125
179
|
puts if verbose
|
@@ -135,8 +189,12 @@ module Mattock
|
|
135
189
|
end
|
136
190
|
|
137
191
|
module CommandLineDSL
|
138
|
-
def cmd(*args)
|
139
|
-
CommandLine.new(*args)
|
192
|
+
def cmd(*args, &block)
|
193
|
+
CommandLine.new(*args, &block)
|
194
|
+
end
|
195
|
+
|
196
|
+
def escaped_command(*args, &block)
|
197
|
+
ShellEscaped.new(CommandLine.new(*args, &block))
|
140
198
|
end
|
141
199
|
end
|
142
200
|
|
@@ -146,18 +204,26 @@ module Mattock
|
|
146
204
|
end
|
147
205
|
|
148
206
|
def command
|
149
|
-
"'" + @escaped.
|
207
|
+
"'" + @escaped.string_format.gsub(/'/,"\'") + "'"
|
208
|
+
end
|
209
|
+
|
210
|
+
def command_environment
|
211
|
+
{}
|
150
212
|
end
|
151
213
|
|
152
214
|
def name
|
153
215
|
@name || @escaped.name
|
154
216
|
end
|
217
|
+
|
218
|
+
def to_s
|
219
|
+
command
|
220
|
+
end
|
155
221
|
end
|
156
222
|
|
157
223
|
class CommandChain < CommandLine
|
158
224
|
def initialize
|
159
225
|
@commands = []
|
160
|
-
|
226
|
+
super(nil)
|
161
227
|
end
|
162
228
|
|
163
229
|
attr_reader :commands
|
@@ -168,6 +234,14 @@ module Mattock
|
|
168
234
|
self
|
169
235
|
end
|
170
236
|
|
237
|
+
#Honestly this is sub-optimal - biggest driver for considering the
|
238
|
+
#mini-shell approach here.
|
239
|
+
def command_environment
|
240
|
+
@commands.reverse.inject({}) do |env, command|
|
241
|
+
env.merge(command.command_environment)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
171
245
|
def name
|
172
246
|
@name || @commands.last.name
|
173
247
|
end
|
data/lib/mattock/command-task.rb
CHANGED
@@ -10,6 +10,23 @@ module Mattock
|
|
10
10
|
runtime_setting(:verify_command, nil)
|
11
11
|
runtime_setting(:command)
|
12
12
|
|
13
|
+
def resolve_runtime_configuration
|
14
|
+
super
|
15
|
+
#If there's a second troublesome command, this becomes a class-level
|
16
|
+
#array
|
17
|
+
if not verify_command.nil? and verify_command.name == "bundle"
|
18
|
+
unless BundleCommandTask === self
|
19
|
+
warn "Verify command is 'bundle' - this sometimes has unexpected results. Consider BundleCommandTask"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if command.name == "bundle"
|
24
|
+
unless BundleCommandTask === self
|
25
|
+
warn "Command is 'bundle' - this sometimes has unexpected results. Consider BundleCommandTask"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
13
30
|
def verify_command
|
14
31
|
if @verify_command.respond_to?(:call)
|
15
32
|
@verify_command = @verify_command.call
|
data/lib/mattock/task.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'mattock/cascading-definition'
|
2
|
+
require 'singleton' #Rake fails to require this properly
|
2
3
|
require 'rake/task'
|
3
4
|
require 'rake/file_task'
|
5
|
+
require 'rake/file_creation_task'
|
6
|
+
require 'rake/multi_task'
|
4
7
|
|
5
8
|
module Mattock
|
6
9
|
# A configurable subclass of Rake::Task, such that you can use a
|
@@ -38,6 +41,7 @@ module Mattock
|
|
38
41
|
end
|
39
42
|
|
40
43
|
def resolve_configuration
|
44
|
+
super
|
41
45
|
if @extracted_task_args.empty?
|
42
46
|
self.task_args = [task_name]
|
43
47
|
else
|
@@ -83,16 +87,23 @@ module Mattock
|
|
83
87
|
"#{self.class.name}: #{self.task_args.inspect}"
|
84
88
|
end
|
85
89
|
|
90
|
+
attr_accessor :rake_task
|
86
91
|
def define
|
87
|
-
|
92
|
+
self.rake_task = task_class.define_task(*task_args) do
|
88
93
|
finalize_configuration
|
89
|
-
|
94
|
+
copy_settings_to(rake_task)
|
95
|
+
rake_task.action
|
90
96
|
end
|
91
|
-
|
92
|
-
|
97
|
+
copy_settings_to(rake_task)
|
98
|
+
rake_task.source_task = self
|
93
99
|
end
|
94
100
|
end
|
95
101
|
|
102
|
+
#I'm having misgivings about this design choice. Rightly, this is probably a
|
103
|
+
#"Task Definer" that knows what class to ::define_task and then mixin a
|
104
|
+
#module to handle the original purpose of being able to override e.g.
|
105
|
+
##needed? There's a lot of client code that relies on this pattern now,
|
106
|
+
#though.
|
96
107
|
class Task < Rake::Task
|
97
108
|
include TaskMixin
|
98
109
|
end
|
@@ -24,45 +24,43 @@ module Mattock
|
|
24
24
|
alias exit_status exit_code
|
25
25
|
end
|
26
26
|
|
27
|
+
class CommandLine
|
28
|
+
def self.execute(*args)
|
29
|
+
fail "Command line executed in specs without 'expect_command' or 'expect_some_commands'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
27
33
|
module CommandLineExampleGroup
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
module MockingExecute
|
35
|
+
def execute
|
36
|
+
Mattock::CommandLine.execute(command)
|
37
|
+
end
|
38
|
+
end
|
33
39
|
|
34
|
-
before :each do
|
35
|
-
Mattock::CommandLine.should_receive(:execute) do |cmd|
|
36
|
-
pattern, res = pairs.shift
|
37
|
-
pattern.should =~ cmd
|
38
|
-
Mattock::MockCommandResult.create(*res)
|
39
|
-
end.any_number_of_times
|
40
|
-
end
|
41
40
|
|
42
|
-
|
43
|
-
|
41
|
+
def self.included(group)
|
42
|
+
group.before :each do
|
43
|
+
@original_execute = Mattock::CommandLine.instance_method(:execute)
|
44
|
+
unless MockingExecute > Mattock::CommandLine
|
45
|
+
Mattock::CommandLine.send(:include, MockingExecute)
|
44
46
|
end
|
47
|
+
Mattock::CommandLine.send(:remove_method, :execute)
|
45
48
|
end
|
46
|
-
end
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
50
|
+
group.after :each do
|
51
|
+
Mattock::CommandLine.send(:define_method, :execute, @original_execute)
|
52
|
+
end
|
51
53
|
end
|
52
54
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
match do |list|
|
58
|
-
list.empty?
|
59
|
-
end
|
55
|
+
#Registers indifference as to exactly what commands get called
|
56
|
+
def expect_some_commands
|
57
|
+
Mattock::CommandLine.should_receive(:execute).any_number_of_times.and_return(MockCommandResult.create(0))
|
58
|
+
end
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
#Registers an expectation about a command being run - expectations are
|
61
|
+
#ordered
|
62
|
+
def expect_command(cmd, *result)
|
63
|
+
Mattock::CommandLine.should_receive(:execute, :expected_from => caller(1)[0]).with(cmd).ordered.and_return(MockCommandResult.create(*result))
|
65
64
|
end
|
66
|
-
include Matchers
|
67
65
|
end
|
68
66
|
end
|
@@ -3,15 +3,15 @@ require 'mattock/command-line'
|
|
3
3
|
module Mattock
|
4
4
|
class CommandLine
|
5
5
|
@@commands = []
|
6
|
-
|
7
|
-
alias original_execute execute
|
6
|
+
alias original_execute execute
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
def execute
|
9
|
+
result = original_execute
|
10
|
+
@@commands << [command, result]
|
11
|
+
return result
|
12
|
+
end
|
14
13
|
|
14
|
+
class << self
|
15
15
|
attr_accessor :command_recording_path
|
16
16
|
|
17
17
|
def command_recording_path
|
data/spec/command-line.rb
CHANGED
@@ -46,6 +46,59 @@ describe Mattock::CommandLine do
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
+
describe Mattock::CommandLine, "setting environment variables" do
|
50
|
+
let :commandline do
|
51
|
+
Mattock::CommandLine.new("env") do |cmd|
|
52
|
+
cmd.env["TEST_ENV"] = "indubitably"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
let :result do
|
57
|
+
commandline.run
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should succeed" do
|
61
|
+
result.succeeded?.should be_true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should alter the command's environment variables" do
|
65
|
+
result.stdout.should =~ /TEST_ENV.*indubitably/
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe Mattock::PipelineChain do
|
71
|
+
let :commandline do
|
72
|
+
Mattock::PipelineChain.new do |chain|
|
73
|
+
chain.add Mattock::CommandLine.new("env")
|
74
|
+
chain.add Mattock::CommandLine.new("cat") do |cmd|
|
75
|
+
cmd.env["TEST_ENV"] = "indubitably"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
let :result do
|
81
|
+
commandline.run
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should produce the right command" do
|
85
|
+
commandline.command.should == 'env | cat'
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should produce a runnable command with format_string" do
|
89
|
+
commandline.string_format.should == 'TEST_ENV=indubitably env | cat'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should succeed" do
|
93
|
+
result.succeeded?.should be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should alter the command's environment variables" do
|
97
|
+
result.stdout.should =~ /TEST_ENV.*indubitably/
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
49
102
|
describe Mattock::CommandLineDSL do
|
50
103
|
include described_class
|
51
104
|
|
@@ -77,7 +130,7 @@ describe Mattock::CommandLineDSL do
|
|
77
130
|
end
|
78
131
|
end
|
79
132
|
|
80
|
-
describe "using the
|
133
|
+
describe "using the & operator" do
|
81
134
|
let :command do
|
82
135
|
cmd("cd", "/tmp/trash") & %w{rm -rf *}
|
83
136
|
end
|
data/spec/configurable.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mattock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: corundum
|
16
|
-
requirement: &
|
16
|
+
requirement: &84431630 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.0.1
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *84431630
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: valise
|
27
|
-
requirement: &
|
27
|
+
requirement: &84446980 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -35,10 +35,10 @@ dependencies:
|
|
35
35
|
- 6
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
|
-
version_requirements: *
|
38
|
+
version_requirements: *84446980
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: tilt
|
41
|
-
requirement: &
|
41
|
+
requirement: &84446570 !ruby/object:Gem::Requirement
|
42
42
|
none: false
|
43
43
|
requirements:
|
44
44
|
- - ! '>'
|
@@ -48,7 +48,7 @@ dependencies:
|
|
48
48
|
- 0
|
49
49
|
type: :runtime
|
50
50
|
prerelease: false
|
51
|
-
version_requirements: *
|
51
|
+
version_requirements: *84446570
|
52
52
|
description: ! " If Rake won't do it by itself, you oughtta Mattock.\n\n If you
|
53
53
|
survived the pun, you might enjoy this gem.\n\n Features:\n\n * Extensions to
|
54
54
|
Tasklibs to support powerful deerpaths.\n * A commandline library that supports
|
@@ -101,7 +101,7 @@ rdoc_options:
|
|
101
101
|
- --main
|
102
102
|
- doc/README
|
103
103
|
- --title
|
104
|
-
- mattock-0.3.
|
104
|
+
- mattock-0.3.1 RDoc
|
105
105
|
require_paths:
|
106
106
|
- lib/
|
107
107
|
required_ruby_version: !ruby/object:Gem::Requirement
|
@@ -112,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
112
|
version: '0'
|
113
113
|
segments:
|
114
114
|
- 0
|
115
|
-
hash:
|
115
|
+
hash: -637040849
|
116
116
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|