mattock 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- yield self if block_given?
30
+ confirm_steps(:default_configuration, :resolve_configuration, :confirm_configuration) do
31
+ default_configuration(*tasklibs)
32
32
 
33
- resolve_configuration
34
- confirm_configuration
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
- confirm_configuration
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
@@ -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#{streams.inspect}"
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 self.execute(command)
111
- pipe = IO.popen(command)
112
- pid = pipe.pid
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
- result = CommandRunResult.new(command, status, {1 => pipe.read})
115
- pipe.close
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 command + " " if verbose
121
- result = self.class.execute(command)
122
- print "=> #{result.exit_code}" if verbose
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.command.gsub(/'/,"\'") + "'"
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
- yield self if block_given?
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
@@ -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
@@ -10,6 +10,7 @@ module Mattock
10
10
  runtime_setting(:remote_target)
11
11
 
12
12
  def resolve_runtime_configuration
13
+ super
13
14
  self.remote_target ||= [remote_server.user, remote_server.address].compact.join('@') unless remote_server.address.nil?
14
15
  end
15
16
 
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
- task = task_class.define_task(*task_args) do
92
+ self.rake_task = task_class.define_task(*task_args) do
88
93
  finalize_configuration
89
- task.action
94
+ copy_settings_to(rake_task)
95
+ rake_task.action
90
96
  end
91
- task.source_task = self
92
- copy_settings_to(task)
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
- def self.included(group)
29
- group.class_eval do
30
- let :pairs do
31
- []
32
- end
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
- after :each do
43
- pairs.should have_all_been_called
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
- def expect_command(cmd, *result)
49
- raise ArgumentError, "Regexp expected: not #{cmd.inspect}" unless Regexp === cmd
50
- pairs << [cmd, result]
50
+ group.after :each do
51
+ Mattock::CommandLine.send(:define_method, :execute, @original_execute)
52
+ end
51
53
  end
52
54
 
53
- module Matchers
54
- extend RSpec::Matchers::DSL
55
-
56
- define :have_all_been_called do
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
- failure_message_for_should do |list|
62
- "Expected all commands to be run, but: #{list.map{|item| item[0].source.inspect}.join(", ")} #{list.length > 1 ? "were" : "was"} not."
63
- end
64
- end
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
- class << self
7
- alias original_execute execute
6
+ alias original_execute execute
8
7
 
9
- def execute(command)
10
- result = original_execute(command)
11
- @@commands << [command, result]
12
- return result
13
- end
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 && operator" do
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
@@ -32,7 +32,6 @@ describe Mattock::Configurable do
32
32
 
33
33
  it "should complain about unset required fields" do
34
34
  expect do
35
- p subject
36
35
  subject.check_required
37
36
  end.to raise_error
38
37
  end
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.0
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-17 00:00:00.000000000 Z
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: &82047460 !ruby/object:Gem::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: *82047460
24
+ version_requirements: *84431630
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: valise
27
- requirement: &82046930 !ruby/object:Gem::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: *82046930
38
+ version_requirements: *84446980
39
39
  - !ruby/object:Gem::Dependency
40
40
  name: tilt
41
- requirement: &82046620 !ruby/object:Gem::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: *82046620
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.0 RDoc
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: 900034003
115
+ hash: -637040849
116
116
  required_rubygems_version: !ruby/object:Gem::Requirement
117
117
  none: false
118
118
  requirements: