mattock 0.7.1 → 0.8.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.
- checksums.yaml +6 -14
- data/lib/mattock.rb +1 -1
- data/lib/mattock/command-task.rb +20 -5
- data/lib/mattock/command-tasklib.rb +7 -0
- data/lib/mattock/configurable/class-methods.rb +24 -1
- data/lib/mattock/configurable/instance-methods.rb +4 -0
- data/lib/mattock/remote-command-task.rb +11 -13
- data/spec/command-task.rb +12 -14
- data/spec/configurable.rb +9 -2
- metadata +33 -16
- data/lib/mattock/command-line.rb +0 -258
- data/lib/mattock/command-line/command-run-result.rb +0 -131
- data/lib/mattock/testing/mock-command-line.rb +0 -67
- data/lib/mattock/testing/record-commands.rb +0 -38
- data/spec/command-line.rb +0 -162
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
MmQ2ZDc1M2I4MzBjZjM2ZTE1YzliZDc3MWE5NjQ5YzVlODFhZGMwZWFkZDBl
|
10
|
-
MTA5NzFmYWRlNWU3ZDdkYmI3M2ZhNTg5MGM1N2YwMDAwNmFiZWUyZDgyYTJi
|
11
|
-
MmU1ODhhM2NkZGJkN2M0OTEwYTA5ODg2YTExODhjNjliOGNmM2Y=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
YmQ5OGQ1MGIzOGYxNmRhZGEzNDBjZjEzMjY0NTRmMGQ0NjMwM2M0MDEyMTgx
|
14
|
-
M2E4NTgyZTVmMmQ3NWY2MzAzYzQ2NmU2NzYwNWQzYTA0ZmM4NDlmNDE0OTM0
|
15
|
-
M2E3MjUxZTMxY2RhOGY5ZDVmNjM1YjYwOTI1OTE2MGY0OGY4OTY=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4c1151f03e2d5b1b2beae2dbe83fc913acc5781d
|
4
|
+
data.tar.gz: 76e9f884109d43f50acfdf12683f8f8922492410
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 64dd7f1524c69be09ec730ef8d12493d0f0db779a08bac00b1acbf1e17eb9d5c69040cae92e5897faf25b4fb5845d850c3d6207ca11c51ceb145adf1dd5b218f
|
7
|
+
data.tar.gz: 7bdd7d4d2c1010ea3fefad1fd0cf0641398207ff339dbc943bd0d2edadc6655baea1ed80f55ff1c074b40ee19f269acd958af36fd49f89d19d51fd59e34296c5
|
data/lib/mattock.rb
CHANGED
data/lib/mattock/command-task.rb
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
require 'mattock/task'
|
2
|
-
|
2
|
+
begin
|
3
|
+
require 'caliph'
|
4
|
+
rescue LoadError => le
|
5
|
+
if le.message =~ /caliph/
|
6
|
+
puts "Mattock's CommandTask (and subclasses) requires a gem called 'caliph' now. Add it to your Gemfile"
|
7
|
+
end
|
8
|
+
raise
|
9
|
+
end
|
3
10
|
|
4
11
|
module Mattock
|
5
12
|
module CommandTaskMixin
|
6
|
-
include CommandLineDSL
|
13
|
+
include Caliph::CommandLineDSL
|
7
14
|
|
8
15
|
def self.included(sub)
|
9
|
-
sub.extend CommandLineDSL
|
16
|
+
sub.extend Caliph::CommandLineDSL
|
10
17
|
sub.runtime_setting(:verify_command, nil)
|
11
18
|
sub.runtime_setting(:command)
|
12
19
|
end
|
@@ -39,12 +46,20 @@ module Mattock
|
|
39
46
|
command
|
40
47
|
end
|
41
48
|
|
49
|
+
def self.shell
|
50
|
+
@shell ||= Caliph.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def shell
|
54
|
+
CommandTaskMixin.shell
|
55
|
+
end
|
56
|
+
|
42
57
|
def action(args)
|
43
|
-
decorated(command).must_succeed!
|
58
|
+
shell.run(decorated(command)).must_succeed!
|
44
59
|
end
|
45
60
|
|
46
61
|
def check_verification_command
|
47
|
-
!decorated(verify_command).succeeds?
|
62
|
+
!shell.run(decorated(verify_command)).succeeds?
|
48
63
|
end
|
49
64
|
|
50
65
|
def needed?
|
@@ -107,7 +107,7 @@ module Mattock
|
|
107
107
|
|
108
108
|
attr_writer(name)
|
109
109
|
define_method(metadata.reader_method) do
|
110
|
-
|
110
|
+
metadata.value_on(self)
|
111
111
|
end
|
112
112
|
|
113
113
|
if existing = default_values.find{|field| field.name == name} and existing.default_value != default_value
|
@@ -193,6 +193,29 @@ module Mattock
|
|
193
193
|
}])
|
194
194
|
end
|
195
195
|
|
196
|
+
def from_hash(obj, hash) #XXX It'd be really nice if this could report unused fields
|
197
|
+
if Configurable > superclass
|
198
|
+
superclass.from_hash(obj, hash)
|
199
|
+
end
|
200
|
+
default_values.each do |field|
|
201
|
+
catch :next do
|
202
|
+
key = field.reader_method.to_s
|
203
|
+
value = hash.fetch(key.to_s) do
|
204
|
+
key = key.to_sym
|
205
|
+
hash.fetch(key) do
|
206
|
+
throw :next
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
existing_value = obj.__send__(field.reader_method)
|
211
|
+
if Configurable === existing_value and value.is_a? Hash
|
212
|
+
existing_value.from_hash(value)
|
213
|
+
else
|
214
|
+
obj.__send__(field.writer_method, value)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
196
219
|
|
197
220
|
def included(mod)
|
198
221
|
mod.extend ClassMethods
|
@@ -20,21 +20,19 @@ module Mattock
|
|
20
20
|
fail "Need remote server for #{self.class.name}" unless remote_server.address
|
21
21
|
|
22
22
|
raise "Empty remote command" if command_on_remote.nil?
|
23
|
-
|
24
|
-
cmd.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
cmd.options << "-o #{opt}"
|
34
|
-
end
|
23
|
+
cmd("ssh") do |cmd|
|
24
|
+
cmd.options << "-i #{id_file}" if id_file
|
25
|
+
cmd.options << "-l #{remote_server.user}" unless remote_server.user.nil?
|
26
|
+
cmd.options << remote_server.address
|
27
|
+
cmd.options << "-p #{remote_server.port}" #ok
|
28
|
+
cmd.options << "-n"
|
29
|
+
cmd.options << "-#{'v'*verbose}" if verbose > 0
|
30
|
+
unless ssh_options.empty?
|
31
|
+
ssh_options.each do |opt|
|
32
|
+
cmd.options << "-o #{opt}"
|
35
33
|
end
|
36
34
|
end
|
37
|
-
cmd
|
35
|
+
cmd - escaped_command(command_on_remote)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
data/spec/command-task.rb
CHANGED
@@ -1,28 +1,26 @@
|
|
1
1
|
require 'mattock/remote-command-task'
|
2
2
|
require 'mattock/bundle-command-task'
|
3
|
+
|
3
4
|
require 'mattock/testing/rake-example-group'
|
4
|
-
require '
|
5
|
+
require 'caliph/testing/mock-command-line'
|
5
6
|
|
6
7
|
describe Mattock::RemoteCommandTask do
|
7
8
|
include Mattock::RakeExampleGroup
|
8
|
-
include
|
9
|
+
include Caliph::CommandLineExampleGroup
|
9
10
|
|
10
11
|
let! :remote_task do
|
11
12
|
namespace :test do
|
12
13
|
Mattock::Rake::RemoteCommandTask.define_task do |t|
|
13
14
|
t.remote_server.address = "nowhere.com"
|
14
|
-
t.command =
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
cmd.redirect_stdout("/tmp/rubyfiles.txt")
|
22
|
-
end
|
23
|
-
end
|
15
|
+
t.command = t.cmd do |cmd|
|
16
|
+
cmd.from("cd", "a_dir")
|
17
|
+
cmd &= "ls"
|
18
|
+
cmd |= "grep"
|
19
|
+
cmd.options << "*.rb"
|
20
|
+
cmd.redirect_stderr("/dev/null")
|
21
|
+
cmd.redirect_stdout("/tmp/rubyfiles.txt")
|
24
22
|
end
|
25
|
-
t.verify_command =
|
23
|
+
t.verify_command = t.cmd("should_do")
|
26
24
|
end
|
27
25
|
end
|
28
26
|
end
|
@@ -43,7 +41,7 @@ end
|
|
43
41
|
|
44
42
|
describe Mattock::BundleCommandTask do
|
45
43
|
include Mattock::RakeExampleGroup
|
46
|
-
include
|
44
|
+
include Caliph::CommandLineExampleGroup
|
47
45
|
|
48
46
|
let! :bundle_task do
|
49
47
|
Mattock::BundleCommandTask.define_task(:bundle_test) do |t|
|
data/spec/configurable.rb
CHANGED
@@ -35,6 +35,13 @@ describe Mattock::Configurable do
|
|
35
35
|
hash[:two][:a].should == "a"
|
36
36
|
end
|
37
37
|
|
38
|
+
it "#from_hash" do
|
39
|
+
subject.from_hash({:one => 111, "two" => { :a => "aaa" }})
|
40
|
+
|
41
|
+
subject.one.should == 111
|
42
|
+
subject.two.a.should == "aaa"
|
43
|
+
end
|
44
|
+
|
38
45
|
it "should complain about unset required fields" do
|
39
46
|
expect do
|
40
47
|
subject.check_required
|
@@ -115,7 +122,7 @@ describe Mattock::Configurable do
|
|
115
122
|
it "should complain about missing fields" do
|
116
123
|
expect do
|
117
124
|
subject.check_required
|
118
|
-
end.to raise_error
|
125
|
+
end.to raise_error(/Required field/)
|
119
126
|
end
|
120
127
|
|
121
128
|
it "should inspect cleanly" do
|
@@ -134,7 +141,7 @@ describe Mattock::Configurable do
|
|
134
141
|
it "should complain about missing fields" do
|
135
142
|
expect do
|
136
143
|
subject.check_required
|
137
|
-
end.to raise_error
|
144
|
+
end.to raise_error(/Required field/)
|
138
145
|
end
|
139
146
|
end
|
140
147
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mattock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Judson Lester
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: corundum
|
@@ -56,20 +56,41 @@ dependencies:
|
|
56
56
|
name: tilt
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- -
|
59
|
+
- - '>'
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- -
|
66
|
+
- - '>'
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: caliph
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.3.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.3.1
|
83
|
+
description: |2+
|
84
|
+
If Rake won't do it by itself, you oughtta Mattock.
|
85
|
+
|
86
|
+
If you survived the pun, you might enjoy this gem.
|
87
|
+
|
88
|
+
Features:
|
89
|
+
|
90
|
+
* Extensions to Tasklibs to support powerful deerpaths.
|
91
|
+
* A commandline library that supports mocking for tests.
|
92
|
+
* A module to support common templating patterns
|
93
|
+
|
73
94
|
email:
|
74
95
|
- nyarly@gmail.com
|
75
96
|
executables: []
|
@@ -84,12 +105,9 @@ files:
|
|
84
105
|
- yard_templates/default/module/html/task_definition.erb
|
85
106
|
- yard_templates/default/layout/html/setup.rb
|
86
107
|
- yard_templates/default/layout/html/tasklib_list.erb
|
87
|
-
- lib/mattock/command-line.rb
|
88
|
-
- lib/mattock/command-line/command-run-result.rb
|
89
108
|
- lib/mattock/command-task.rb
|
109
|
+
- lib/mattock/command-tasklib.rb
|
90
110
|
- lib/mattock/testing/rake-example-group.rb
|
91
|
-
- lib/mattock/testing/mock-command-line.rb
|
92
|
-
- lib/mattock/testing/record-commands.rb
|
93
111
|
- lib/mattock/template-host.rb
|
94
112
|
- lib/mattock/yard_extensions.rb
|
95
113
|
- lib/mattock/remote-command-task.rb
|
@@ -109,7 +127,6 @@ files:
|
|
109
127
|
- lib/mattock.rb
|
110
128
|
- doc/README
|
111
129
|
- doc/Specifications
|
112
|
-
- spec/command-line.rb
|
113
130
|
- spec/command-task.rb
|
114
131
|
- spec/tasklib.rb
|
115
132
|
- spec/configurable.rb
|
@@ -128,17 +145,17 @@ rdoc_options:
|
|
128
145
|
- --main
|
129
146
|
- doc/README
|
130
147
|
- --title
|
131
|
-
- mattock-0.
|
148
|
+
- mattock-0.8.0 RDoc
|
132
149
|
require_paths:
|
133
150
|
- lib/
|
134
151
|
required_ruby_version: !ruby/object:Gem::Requirement
|
135
152
|
requirements:
|
136
|
-
- -
|
153
|
+
- - '>='
|
137
154
|
- !ruby/object:Gem::Version
|
138
155
|
version: '0'
|
139
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
157
|
requirements:
|
141
|
-
- -
|
158
|
+
- - '>='
|
142
159
|
- !ruby/object:Gem::Version
|
143
160
|
version: '0'
|
144
161
|
requirements: []
|
data/lib/mattock/command-line.rb
DELETED
@@ -1,258 +0,0 @@
|
|
1
|
-
require 'mattock/command-line/command-run-result'
|
2
|
-
|
3
|
-
module Mattock
|
4
|
-
class CommandLine
|
5
|
-
class << self
|
6
|
-
def define_chain_op(opname, klass)
|
7
|
-
define_method(opname) do |other|
|
8
|
-
unless CommandLine === other
|
9
|
-
other = CommandLine.new(*[*other])
|
10
|
-
end
|
11
|
-
chain = nil
|
12
|
-
if klass === self
|
13
|
-
chain = self
|
14
|
-
else
|
15
|
-
chain = klass.new
|
16
|
-
chain.add(self)
|
17
|
-
end
|
18
|
-
chain.add(other)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def define_op(opname)
|
23
|
-
CommandLine.define_chain_op(opname, self)
|
24
|
-
end
|
25
|
-
|
26
|
-
attr_accessor :output_stream
|
27
|
-
end
|
28
|
-
|
29
|
-
def initialize(executable, *options)
|
30
|
-
@output_stream = self.class.output_stream || $stderr
|
31
|
-
@executable = executable
|
32
|
-
@options = options
|
33
|
-
@redirections = []
|
34
|
-
@env = {}
|
35
|
-
yield self if block_given?
|
36
|
-
end
|
37
|
-
|
38
|
-
attr_accessor :name, :executable, :options, :env, :output_stream
|
39
|
-
attr_reader :redirections
|
40
|
-
|
41
|
-
alias_method :command_environment, :env
|
42
|
-
|
43
|
-
def set_env(name, value)
|
44
|
-
command_environment[name] = value
|
45
|
-
return self
|
46
|
-
end
|
47
|
-
|
48
|
-
def verbose
|
49
|
-
::Rake.verbose && ::Rake.verbose != ::Rake::FileUtilsExt::DEFAULT
|
50
|
-
end
|
51
|
-
|
52
|
-
def name
|
53
|
-
@name || executable
|
54
|
-
end
|
55
|
-
|
56
|
-
def command
|
57
|
-
([executable] + options_composition + @redirections).join(" ")
|
58
|
-
end
|
59
|
-
|
60
|
-
def string_format
|
61
|
-
(command_environment.map do |key, value|
|
62
|
-
[key, value].join("=")
|
63
|
-
end + [command]).join(" ")
|
64
|
-
end
|
65
|
-
|
66
|
-
def options_composition
|
67
|
-
options
|
68
|
-
end
|
69
|
-
|
70
|
-
def redirect_to(stream, path)
|
71
|
-
@redirections << "#{stream}>#{path}"
|
72
|
-
end
|
73
|
-
|
74
|
-
def redirect_from(path, stream)
|
75
|
-
@redirections << "#{stream}<#{path}"
|
76
|
-
end
|
77
|
-
|
78
|
-
def copy_stream_to(from, to)
|
79
|
-
@redirections << "#{from}>&#{to}"
|
80
|
-
end
|
81
|
-
|
82
|
-
def redirect_stdout(path)
|
83
|
-
redirect_to(1, path)
|
84
|
-
end
|
85
|
-
|
86
|
-
def redirect_stderr(path)
|
87
|
-
redirect_to(2, path)
|
88
|
-
end
|
89
|
-
|
90
|
-
def redirect_stdin(path)
|
91
|
-
redirect_from(path, 0)
|
92
|
-
end
|
93
|
-
|
94
|
-
def replace_us
|
95
|
-
output_steeam.puts "Ceding execution to: "
|
96
|
-
output_stream.puts string_format
|
97
|
-
Process.exec(command_environment, command)
|
98
|
-
end
|
99
|
-
|
100
|
-
def spawn_process
|
101
|
-
host_stdout, cmd_stdout = IO.pipe
|
102
|
-
host_stderr, cmd_stderr = IO.pipe
|
103
|
-
|
104
|
-
pid = Process.spawn(command_environment, command, :out => cmd_stdout, :err => cmd_stderr)
|
105
|
-
cmd_stdout.close
|
106
|
-
cmd_stderr.close
|
107
|
-
|
108
|
-
return pid, host_stdout, host_stderr
|
109
|
-
end
|
110
|
-
|
111
|
-
def collect_result(pid, host_stdout, host_stderr)
|
112
|
-
result = CommandRunResult.new(pid, self)
|
113
|
-
result.streams = {1 => host_stdout, 2 => host_stderr}
|
114
|
-
result.wait
|
115
|
-
return result
|
116
|
-
end
|
117
|
-
|
118
|
-
#If I wasn't worried about writing my own limited shell, I'd say e.g.
|
119
|
-
#Pipeline would be an explicit chain of pipes... which is probably as
|
120
|
-
#originally intended :/
|
121
|
-
def execute
|
122
|
-
collect_result(*spawn_process)
|
123
|
-
end
|
124
|
-
|
125
|
-
#Run a command in the background. The command can survive the caller
|
126
|
-
def spin_off
|
127
|
-
pid, out, err = spawn_process
|
128
|
-
Process.detach(pid)
|
129
|
-
return pid, out, err
|
130
|
-
end
|
131
|
-
|
132
|
-
#Run a command in parallel with the parent process - will kill it if it
|
133
|
-
#outlasts us
|
134
|
-
def background
|
135
|
-
pid, out, err = spawn_process
|
136
|
-
Process.detach(pid)
|
137
|
-
at_exit do
|
138
|
-
kill_process(pid)
|
139
|
-
end
|
140
|
-
return pid, out, err
|
141
|
-
end
|
142
|
-
|
143
|
-
def kill_process(pid)
|
144
|
-
Process.kill("INT", pid)
|
145
|
-
end
|
146
|
-
|
147
|
-
def complete(pid, out, err)
|
148
|
-
kill_process(pid)
|
149
|
-
collect_result(pid, out, err)
|
150
|
-
end
|
151
|
-
|
152
|
-
def report(message, newline=true)
|
153
|
-
output_stream.print(message + (newline ? "\n" : ""))
|
154
|
-
end
|
155
|
-
|
156
|
-
def run
|
157
|
-
report string_format + " ", false
|
158
|
-
result = execute
|
159
|
-
report "=> #{result.exit_code}"
|
160
|
-
report result.format_streams if verbose
|
161
|
-
return result
|
162
|
-
ensure
|
163
|
-
report "" if verbose
|
164
|
-
end
|
165
|
-
|
166
|
-
def succeeds?
|
167
|
-
run.succeeded?
|
168
|
-
end
|
169
|
-
|
170
|
-
def must_succeed!
|
171
|
-
run.must_succeed!
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
module CommandLineDSL
|
176
|
-
def cmd(*args, &block)
|
177
|
-
CommandLine.new(*args, &block)
|
178
|
-
end
|
179
|
-
|
180
|
-
def escaped_command(*args, &block)
|
181
|
-
ShellEscaped.new(CommandLine.new(*args, &block))
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
class ShellEscaped < CommandLine
|
186
|
-
def initialize(cmd)
|
187
|
-
@escaped = cmd
|
188
|
-
end
|
189
|
-
|
190
|
-
def command
|
191
|
-
"'" + @escaped.string_format.gsub(/'/,"\'") + "'"
|
192
|
-
end
|
193
|
-
|
194
|
-
def command_environment
|
195
|
-
{}
|
196
|
-
end
|
197
|
-
|
198
|
-
def name
|
199
|
-
@name || @escaped.name
|
200
|
-
end
|
201
|
-
|
202
|
-
def to_s
|
203
|
-
command
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
class CommandChain < CommandLine
|
208
|
-
def initialize
|
209
|
-
@commands = []
|
210
|
-
@command_environment = {}
|
211
|
-
super(nil)
|
212
|
-
end
|
213
|
-
|
214
|
-
attr_reader :commands
|
215
|
-
|
216
|
-
def add(cmd)
|
217
|
-
yield cmd if block_given?
|
218
|
-
@commands << cmd
|
219
|
-
self
|
220
|
-
end
|
221
|
-
|
222
|
-
#Honestly this is sub-optimal - biggest driver for considering the
|
223
|
-
#mini-shell approach here.
|
224
|
-
def command_environment
|
225
|
-
@command_environment = @commands.reverse.inject(@command_environment) do |env, command|
|
226
|
-
env.merge(command.command_environment)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def name
|
231
|
-
@name || @commands.last.name
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
class WrappingChain < CommandChain
|
236
|
-
define_op('-')
|
237
|
-
|
238
|
-
def command
|
239
|
-
@commands.map{|cmd| cmd.command}.join(" -- ")
|
240
|
-
end
|
241
|
-
end
|
242
|
-
|
243
|
-
class PrereqChain < CommandChain
|
244
|
-
define_op('&')
|
245
|
-
|
246
|
-
def command
|
247
|
-
@commands.map{|cmd| cmd.command}.join(" && ")
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
class PipelineChain < CommandChain
|
252
|
-
define_op('|')
|
253
|
-
|
254
|
-
def command
|
255
|
-
@commands.map{|cmd| cmd.command}.join(" | ")
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
@@ -1,131 +0,0 @@
|
|
1
|
-
module Mattock
|
2
|
-
class CommandLine
|
3
|
-
class CommandRunResult
|
4
|
-
def initialize(pid, command)
|
5
|
-
@command = command
|
6
|
-
@pid = pid
|
7
|
-
|
8
|
-
#####
|
9
|
-
@process_status = nil
|
10
|
-
@streams = {}
|
11
|
-
@consume_timeout = nil
|
12
|
-
end
|
13
|
-
attr_reader :process_status, :pid
|
14
|
-
attr_accessor :consume_timeout, :streams
|
15
|
-
|
16
|
-
def stdout
|
17
|
-
@streams[1]
|
18
|
-
end
|
19
|
-
|
20
|
-
def stderr
|
21
|
-
@streams[2]
|
22
|
-
end
|
23
|
-
|
24
|
-
def exit_code
|
25
|
-
@process_status.exitstatus
|
26
|
-
end
|
27
|
-
alias exit_status exit_code
|
28
|
-
|
29
|
-
def succeeded?
|
30
|
-
must_succeed!
|
31
|
-
return true
|
32
|
-
rescue
|
33
|
-
return false
|
34
|
-
end
|
35
|
-
|
36
|
-
def format_streams
|
37
|
-
"stdout:#{stdout.nil? || stdout.empty? ? "[empty]\n" : "\n#{stdout}"}" +
|
38
|
-
"stderr:#{stderr.nil? || stderr.empty? ? "[empty]\n" : "\n#{stderr}"}---"
|
39
|
-
end
|
40
|
-
|
41
|
-
def must_succeed!
|
42
|
-
case exit_code
|
43
|
-
when 0
|
44
|
-
return exit_code
|
45
|
-
else
|
46
|
-
fail "Command #{@command.inspect} failed with exit status #{exit_code}: \n#{format_streams}"
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def wait
|
51
|
-
@accumulators = {}
|
52
|
-
waits = {}
|
53
|
-
@buffered_echo = []
|
54
|
-
|
55
|
-
ioes = streams.values
|
56
|
-
ioes.each do |io|
|
57
|
-
@accumulators[io] = []
|
58
|
-
waits[io] = 3
|
59
|
-
end
|
60
|
-
begin_echoing = Time.now + (@consume_timeout || 3)
|
61
|
-
|
62
|
-
@live_ioes = ioes.dup
|
63
|
-
|
64
|
-
until @live_ioes.empty? do
|
65
|
-
newpid, @process_status = Process.waitpid2(pid, Process::WNOHANG)
|
66
|
-
|
67
|
-
unless @process_status.nil?
|
68
|
-
consume_buffers(@live_ioes)
|
69
|
-
break
|
70
|
-
end
|
71
|
-
|
72
|
-
timeout = 0
|
73
|
-
|
74
|
-
if !@buffered_echo.nil?
|
75
|
-
timeout = begin_echoing - Time.now
|
76
|
-
if timeout < 0
|
77
|
-
@command.report ""
|
78
|
-
@command.report "Long running command output:"
|
79
|
-
@command.report @buffered_echo.join
|
80
|
-
@buffered_echo = nil
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
if timeout > 0
|
85
|
-
result = IO::select(@live_ioes, [], @live_ioes, timeout)
|
86
|
-
else
|
87
|
-
result = IO::select(@live_ioes, [], @live_ioes, 1)
|
88
|
-
end
|
89
|
-
|
90
|
-
unless result.nil? #timeout
|
91
|
-
readable, _writeable, errored = *result
|
92
|
-
unless errored.empty?
|
93
|
-
raise "Error on IO: #{errored.inspect}"
|
94
|
-
end
|
95
|
-
|
96
|
-
consume_buffers(readable)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
if @process_status.nil?
|
101
|
-
newpid, @process_status = Process.waitpid2(pid)
|
102
|
-
end
|
103
|
-
|
104
|
-
ioes.each do |io|
|
105
|
-
io.close
|
106
|
-
end
|
107
|
-
@streams = Hash[ioes.each_with_index.map{|io, index| [index + 1, @accumulators[io].join]}]
|
108
|
-
end
|
109
|
-
|
110
|
-
def consume_buffers(readable)
|
111
|
-
if not(readable.nil? or readable.empty?)
|
112
|
-
readable.each do |io|
|
113
|
-
begin
|
114
|
-
while chunk = io.read_nonblock(4096)
|
115
|
-
if @buffered_echo.nil?
|
116
|
-
@command.report chunk, false
|
117
|
-
else
|
118
|
-
@buffered_echo << chunk
|
119
|
-
end
|
120
|
-
@accumulators[io] << chunk
|
121
|
-
end
|
122
|
-
rescue IO::WaitReadable => ex
|
123
|
-
rescue EOFError => ex
|
124
|
-
@live_ioes.delete(io)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'mattock/command-line'
|
2
|
-
|
3
|
-
module Mattock
|
4
|
-
class MockCommandResult < CommandLine::CommandRunResult
|
5
|
-
def self.create(*args)
|
6
|
-
if args.length == 1
|
7
|
-
args = [args[0], {1 => ""}]
|
8
|
-
end
|
9
|
-
|
10
|
-
if String == args[1]
|
11
|
-
args[1] = {1 => args[1]}
|
12
|
-
end
|
13
|
-
|
14
|
-
return self.new(*args)
|
15
|
-
end
|
16
|
-
|
17
|
-
def initialize(code, streams)
|
18
|
-
@streams = streams
|
19
|
-
@exit_code = code
|
20
|
-
end
|
21
|
-
|
22
|
-
attr_reader :exit_code, :streams
|
23
|
-
|
24
|
-
alias exit_status exit_code
|
25
|
-
end
|
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
|
-
|
33
|
-
module CommandLineExampleGroup
|
34
|
-
include CommandLineDSL
|
35
|
-
module MockingExecute
|
36
|
-
def execute
|
37
|
-
Mattock::CommandLine.execute(command)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
def self.included(group)
|
43
|
-
group.before :each do
|
44
|
-
@original_execute = Mattock::CommandLine.instance_method(:execute)
|
45
|
-
unless MockingExecute > Mattock::CommandLine
|
46
|
-
Mattock::CommandLine.send(:include, MockingExecute)
|
47
|
-
end
|
48
|
-
Mattock::CommandLine.send(:remove_method, :execute)
|
49
|
-
end
|
50
|
-
|
51
|
-
group.after :each do
|
52
|
-
Mattock::CommandLine.send(:define_method, :execute, @original_execute)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
#Registers indifference as to exactly what commands get called
|
57
|
-
def expect_some_commands
|
58
|
-
Mattock::CommandLine.should_receive(:execute).any_number_of_times.and_return(MockCommandResult.create(0))
|
59
|
-
end
|
60
|
-
|
61
|
-
#Registers an expectation about a command being run - expectations are
|
62
|
-
#ordered
|
63
|
-
def expect_command(cmd, *result)
|
64
|
-
Mattock::CommandLine.should_receive(:execute, :expected_from => caller(1)[0]).with(cmd).ordered.and_return(MockCommandResult.create(*result))
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'mattock/command-line'
|
2
|
-
|
3
|
-
module Mattock
|
4
|
-
class CommandLine
|
5
|
-
@@commands = []
|
6
|
-
alias original_execute execute
|
7
|
-
|
8
|
-
def execute
|
9
|
-
result = original_execute
|
10
|
-
@@commands << [command, result]
|
11
|
-
return result
|
12
|
-
end
|
13
|
-
|
14
|
-
class << self
|
15
|
-
attr_accessor :command_recording_path
|
16
|
-
|
17
|
-
def command_recording_path
|
18
|
-
@command_recording_path ||= ENV['MATTOCK_CMDREC']
|
19
|
-
end
|
20
|
-
|
21
|
-
def emit_recording
|
22
|
-
io = $stderr
|
23
|
-
if command_recording_path
|
24
|
-
io = File.open(command_recording_path, "w")
|
25
|
-
else
|
26
|
-
io.puts "Set MATTOCK_CMDREC to write to a path"
|
27
|
-
end
|
28
|
-
@@commands.each do |pair|
|
29
|
-
io.puts "[/#{pair[0]}/, #{[pair[1].exit_code, pair[1].streams].inspect}]"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
at_exit do
|
37
|
-
Mattock::CommandLine.emit_recording
|
38
|
-
end
|
data/spec/command-line.rb
DELETED
@@ -1,162 +0,0 @@
|
|
1
|
-
require 'mattock/command-line'
|
2
|
-
require 'mattock/testing/rake-example-group'
|
3
|
-
require 'mattock/testing/mock-command-line'
|
4
|
-
|
5
|
-
require 'mattock/testing/record-commands'
|
6
|
-
|
7
|
-
Mattock::CommandLine.command_recording_path = "/dev/null"
|
8
|
-
|
9
|
-
describe Mattock::CommandLine do
|
10
|
-
let :commandline do
|
11
|
-
Mattock::CommandLine.new('echo', "-n") do |cmd|
|
12
|
-
cmd.options << "Some text"
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
it "should have a name set" do
|
17
|
-
commandline.name.should == "echo"
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should produce a command string" do
|
21
|
-
commandline.command.should == "echo -n Some text"
|
22
|
-
end
|
23
|
-
|
24
|
-
it "should succeed" do
|
25
|
-
commandline.succeeds?.should be_true
|
26
|
-
end
|
27
|
-
|
28
|
-
it "should not complain about success" do
|
29
|
-
expect do
|
30
|
-
commandline.must_succeed!
|
31
|
-
end.to_not raise_error
|
32
|
-
end
|
33
|
-
|
34
|
-
describe Mattock::CommandLine::CommandRunResult do
|
35
|
-
let :result do
|
36
|
-
commandline.run
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should have a result code" do
|
40
|
-
result.exit_code.should == 0
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should have stdout" do
|
44
|
-
result.stdout.should == "Some text"
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
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
|
-
|
102
|
-
describe Mattock::CommandLineDSL do
|
103
|
-
include described_class
|
104
|
-
|
105
|
-
describe "using the - operator" do
|
106
|
-
let :command do
|
107
|
-
cmd("sudo") - ["gem", "install", "bundler"]
|
108
|
-
end
|
109
|
-
|
110
|
-
it "should define commands" do
|
111
|
-
command.should be_an_instance_of(Mattock::WrappingChain)
|
112
|
-
command.should have(2).commands
|
113
|
-
command.commands[0].should be_an_instance_of(Mattock::CommandLine)
|
114
|
-
command.commands[1].should be_an_instance_of(Mattock::CommandLine)
|
115
|
-
command.command.should == "sudo -- gem install bundler"
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe "using the | operator" do
|
120
|
-
let :command do
|
121
|
-
cmd("cat", "/etc/passwd") | ["grep", "root"]
|
122
|
-
end
|
123
|
-
|
124
|
-
it "should define commands" do
|
125
|
-
command.should be_an_instance_of(Mattock::PipelineChain)
|
126
|
-
command.should have(2).commands
|
127
|
-
command.commands[0].should be_an_instance_of(Mattock::CommandLine)
|
128
|
-
command.commands[1].should be_an_instance_of(Mattock::CommandLine)
|
129
|
-
command.command.should == "cat /etc/passwd | grep root"
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
describe "using the & operator" do
|
134
|
-
let :command do
|
135
|
-
cmd("cd", "/tmp/trash") & %w{rm -rf *}
|
136
|
-
end
|
137
|
-
|
138
|
-
it "should define commands" do
|
139
|
-
command.should be_an_instance_of(Mattock::PrereqChain)
|
140
|
-
command.should have(2).commands
|
141
|
-
command.commands[0].should be_an_instance_of(Mattock::CommandLine)
|
142
|
-
command.commands[1].should be_an_instance_of(Mattock::CommandLine)
|
143
|
-
command.command.should == "cd /tmp/trash && rm -rf *"
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
describe Mattock::CommandLine, "that fails" do
|
149
|
-
let :commandline do
|
150
|
-
Mattock::CommandLine.new("false")
|
151
|
-
end
|
152
|
-
|
153
|
-
it "should not succeed" do
|
154
|
-
commandline.succeeds?.should == false
|
155
|
-
end
|
156
|
-
|
157
|
-
it "should raise error if succeed demanded" do
|
158
|
-
expect do
|
159
|
-
commandline.must_succeed
|
160
|
-
end.to raise_error
|
161
|
-
end
|
162
|
-
end
|