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.
@@ -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: