mattock 0.3.0 → 0.3.1
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.
- 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:
|