command-runner 0.2.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.
- data/README.md +57 -0
- data/lib/command/runner/backends/backticks.rb +84 -0
- data/lib/command/runner/backends/fake.rb +58 -0
- data/lib/command/runner/backends/posix_spawn.rb +36 -0
- data/lib/command/runner/backends/spawn.rb +94 -0
- data/lib/command/runner/backends.rb +17 -0
- data/lib/command/runner/exceptions.rb +10 -0
- data/lib/command/runner/message.rb +101 -0
- data/lib/command/runner/version.rb +9 -0
- data/lib/command/runner.rb +133 -0
- data/spec/messenger_spec.rb +60 -0
- data/spec/spawn_spec.rb +31 -0
- metadata +106 -0
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Command Runner [](https://travis-ci.org/redjazz96/command-runner)
|
2
|
+
Runs commands.
|
3
|
+
|
4
|
+
```Ruby
|
5
|
+
require 'command/runner'
|
6
|
+
|
7
|
+
line = Command::Runner.new("echo", "hello")
|
8
|
+
message = line.pass # => #<Command::Runner::Message:...>
|
9
|
+
message.exit_code # => 0
|
10
|
+
message.stdout # => "hello\n"
|
11
|
+
message.time # => 0.00091773
|
12
|
+
message.process_id # => 9622
|
13
|
+
message.line # => "echo hello"
|
14
|
+
```
|
15
|
+
|
16
|
+
with interpolations...
|
17
|
+
|
18
|
+
```Ruby
|
19
|
+
line = Command::Runner.new("echo", "{interpolation}")
|
20
|
+
message = line.pass(:interpolation => "watermelons")
|
21
|
+
message.stdout # => "watermelons\n"
|
22
|
+
message.line # => "echo watermelons"
|
23
|
+
```
|
24
|
+
|
25
|
+
that escapes bad stuff...
|
26
|
+
|
27
|
+
```Ruby
|
28
|
+
message = line.pass(:interpolation => "`uname -a`")
|
29
|
+
message.stdout # => "`uname -a`\n"
|
30
|
+
message.line # => "echo \\`uname\\ -a\\`"
|
31
|
+
```
|
32
|
+
|
33
|
+
unless you don't want it to.
|
34
|
+
|
35
|
+
```Ruby
|
36
|
+
line = Command::Runner.new("echo", "{{interpolation}}")
|
37
|
+
message = line.pass(:interpolation => "`uname -a`")
|
38
|
+
message.stdout # => "Linux Hyperion 3.8.0-25-generic #37-Ubuntu SMP Thu Jun 6 20:47:07 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux\n"
|
39
|
+
message.line # => "echo `uname -a`"
|
40
|
+
```
|
41
|
+
|
42
|
+
It can also use different methods to run commands...
|
43
|
+
|
44
|
+
```Ruby
|
45
|
+
line = Command::Runner.new("echo", "something")
|
46
|
+
line.backends = Command::Runner::Backends::Spawn.new
|
47
|
+
line.pass
|
48
|
+
```
|
49
|
+
|
50
|
+
but defaults to the best one.
|
51
|
+
|
52
|
+
## Compatibility
|
53
|
+
It works on
|
54
|
+
|
55
|
+
- MRI 2.0.0 and 1.9.3
|
56
|
+
|
57
|
+
unless the travis build fails.
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Command
|
2
|
+
class Runner
|
3
|
+
module Backends
|
4
|
+
|
5
|
+
# A backend that uses ticks to do its bidding.
|
6
|
+
class Backticks
|
7
|
+
|
8
|
+
# Returns whether or not this backend is avialable on this
|
9
|
+
# platform.
|
10
|
+
def self.available?
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
# Initialize the fake backend.
|
15
|
+
def initialize
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Run the given command and arguments, in the given environment.
|
20
|
+
#
|
21
|
+
# @param command [String] the command to run.
|
22
|
+
# @param arguments [String] the arguments to pass to the
|
23
|
+
# command.
|
24
|
+
# @param env [Hash] the enviornment to run the command
|
25
|
+
# under.
|
26
|
+
# @param options [Hash] the options to run the command under.
|
27
|
+
# @return [Message] information about the process that ran.
|
28
|
+
def call(command, arguments, env = {}, options = {})
|
29
|
+
super
|
30
|
+
output = ""
|
31
|
+
start_time = nil
|
32
|
+
end_time = nil
|
33
|
+
|
34
|
+
with_modified_env(env) do
|
35
|
+
start_time = Time.now
|
36
|
+
output << `#{command} #{arguments}`
|
37
|
+
end_time = Time.now
|
38
|
+
end
|
39
|
+
|
40
|
+
Message.new :process_id => $?.pid,
|
41
|
+
:exit_code => $?.exitstatus,
|
42
|
+
:finished => true,
|
43
|
+
:time => (end_time - start_time).abs,
|
44
|
+
:env => env,
|
45
|
+
:options => {},
|
46
|
+
:stdout => output,
|
47
|
+
:line => line,
|
48
|
+
:executed => true,
|
49
|
+
:status => $?
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# If ClimateControl is installed on this system, it runs the
|
55
|
+
# given block with the given environment. If it's not, it
|
56
|
+
# just yields.
|
57
|
+
#
|
58
|
+
# @yield
|
59
|
+
# @return [Object]
|
60
|
+
def with_modified_env(env)
|
61
|
+
if defined?(ClimateControl) || climate_control?
|
62
|
+
ClimateControl.modify(env, &Proc.new)
|
63
|
+
else
|
64
|
+
yield
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Checks to see if ClimateControl is on this system.
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
def climate_control?
|
72
|
+
begin
|
73
|
+
require 'climate_control'
|
74
|
+
true
|
75
|
+
rescue LoadError
|
76
|
+
false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Command
|
2
|
+
class Runner
|
3
|
+
module Backends
|
4
|
+
|
5
|
+
# A fake backend. Used to a) define what backends should respond
|
6
|
+
# to, and b) provide default behavior for the backends.
|
7
|
+
#
|
8
|
+
# @abstract
|
9
|
+
class Fake
|
10
|
+
|
11
|
+
# Returns whether or not this backend is avialable on this
|
12
|
+
# platform.
|
13
|
+
#
|
14
|
+
# @abstract
|
15
|
+
def self.available?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
# Initialize the fake backend.
|
20
|
+
def initialize
|
21
|
+
@ran = []
|
22
|
+
|
23
|
+
raise NotAvailableBackendError unless self.class.available?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Run the given command and arguments, in the given environment.
|
27
|
+
#
|
28
|
+
# @abstract
|
29
|
+
# @note Does nothing.
|
30
|
+
# @param command [String] the command to run.
|
31
|
+
# @param arguments [String] the arguments to pass to the
|
32
|
+
# command.
|
33
|
+
# @param env [Hash] the enviornment to run the command
|
34
|
+
# under.
|
35
|
+
# @param options [Hash] the options to run the command under.
|
36
|
+
# @return [Message] information about the process that ran.
|
37
|
+
def call(command, arguments, env = {}, options = {})
|
38
|
+
@ran << [command, arguments]
|
39
|
+
|
40
|
+
Message.new :env => env, :options => options
|
41
|
+
end
|
42
|
+
|
43
|
+
# Determines whether or not the given command and arguments were
|
44
|
+
# ran with this backend.
|
45
|
+
#
|
46
|
+
# @see #call
|
47
|
+
# @param command [String]
|
48
|
+
# @param arguments [String]
|
49
|
+
# @return [Boolean]
|
50
|
+
def ran?(command, arguments)
|
51
|
+
@ran.include?([command, arguments])
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Command
|
2
|
+
class Runner
|
3
|
+
module Backends
|
4
|
+
|
5
|
+
# Spawns a process using POSIX::Spawn. This is the preferred
|
6
|
+
# method, as POSIX::Spawn is much faster than Process.spawn.
|
7
|
+
class PosixSpawn < Spawn
|
8
|
+
|
9
|
+
# Determines whether or not the PosixSpawn class is available as
|
10
|
+
# a backend. Does this by checking to see if posix-spawn has been
|
11
|
+
# installed on the local computer; if it hasn't, it returns
|
12
|
+
# false.
|
13
|
+
#
|
14
|
+
# @see Fake.available?
|
15
|
+
# @return [Boolean]
|
16
|
+
def self.available?
|
17
|
+
@_available ||= begin
|
18
|
+
require 'posix/spawn'
|
19
|
+
true
|
20
|
+
rescue LoadError => e
|
21
|
+
false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Spawns a process with the given line, environment, and options.
|
26
|
+
#
|
27
|
+
# @see Spawn#spawn
|
28
|
+
# @return [Numeric]
|
29
|
+
def spawn(env, line, options)
|
30
|
+
POSIX::Spawn.spawn(env, line, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Command
|
2
|
+
class Runner
|
3
|
+
module Backends
|
4
|
+
|
5
|
+
# Spawns a process using ruby's Process.spawn.
|
6
|
+
class Spawn < Fake
|
7
|
+
|
8
|
+
# (see Fake.available?)
|
9
|
+
def self.available?
|
10
|
+
Process.respond_to?(:spawn)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Initialize the backend.
|
14
|
+
def initialize
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Run the given command and arguments, in the given environment.
|
19
|
+
#
|
20
|
+
# @param (see Fake#call)
|
21
|
+
# @return (see Fake#call)
|
22
|
+
def call(command, arguments, env = {}, options = {})
|
23
|
+
super
|
24
|
+
stderr_r, stderr_w = IO.pipe
|
25
|
+
stdout_r, stdout_w = IO.pipe
|
26
|
+
stdin_r, stdin_w = IO.pipe
|
27
|
+
|
28
|
+
new_options = options.merge(:in => stdin_r,
|
29
|
+
:out => stdout_w, :err => stderr_w)
|
30
|
+
|
31
|
+
if new_options[:input]
|
32
|
+
stdin_w.write(new_options.delete(:input))
|
33
|
+
end
|
34
|
+
stdin_w.close
|
35
|
+
|
36
|
+
line = [command, arguments].join(' ')
|
37
|
+
|
38
|
+
start_time = Time.now
|
39
|
+
process_id = spawn(env, line, new_options)
|
40
|
+
|
41
|
+
future do
|
42
|
+
_, status = wait2(process_id)
|
43
|
+
end_time = Time.now
|
44
|
+
|
45
|
+
[stdout_w, stderr_w].each(&:close)
|
46
|
+
|
47
|
+
Message.new :process_id => process_id,
|
48
|
+
:exit_code => status.exitstatus,
|
49
|
+
:finished => true,
|
50
|
+
:time => (start_time - end_time).abs,
|
51
|
+
:env => env,
|
52
|
+
:options => options,
|
53
|
+
:stdout => stdout_r.read,
|
54
|
+
:stderr => stderr_r.read,
|
55
|
+
:line => line,
|
56
|
+
:executed => true,
|
57
|
+
:status => status
|
58
|
+
end
|
59
|
+
|
60
|
+
rescue Errno::ENOENT => e
|
61
|
+
Message.new :exit_code => 127,
|
62
|
+
:finished => true,
|
63
|
+
:time => -1,
|
64
|
+
:env => {},
|
65
|
+
:options => {},
|
66
|
+
:stdout => "",
|
67
|
+
:stderr => e.message,
|
68
|
+
:line => line,
|
69
|
+
:executed => false
|
70
|
+
end
|
71
|
+
|
72
|
+
# Spawn the given process, in the environment with the
|
73
|
+
# given options.
|
74
|
+
#
|
75
|
+
# @see Process.spawn
|
76
|
+
# @return [Numeric] the process id
|
77
|
+
def spawn(env, line, options)
|
78
|
+
Process.spawn(env, line, options)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Waits for the given process, and returns the process id and the
|
82
|
+
# status.
|
83
|
+
#
|
84
|
+
# @see Process.wait2
|
85
|
+
# @return [Array<(Numeric, Process::Status)>]
|
86
|
+
def wait2(process_id = -1)
|
87
|
+
Process.wait2(process_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Command
|
2
|
+
|
3
|
+
class Runner
|
4
|
+
|
5
|
+
# The different backends that the runner can take. Each of them have a
|
6
|
+
# different method of executing the process.
|
7
|
+
module Backends
|
8
|
+
|
9
|
+
autoload :Fake, "command/runner/backends/fake"
|
10
|
+
autoload :Spawn, "command/runner/backends/spawn"
|
11
|
+
autoload :Backticks, "command/runner/backends/backticks"
|
12
|
+
autoload :PosixSpawn, "command/runner/backends/posix_spawn"
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Command
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
# This contains information about the process that was run, such as
|
5
|
+
# its exit code, process id, and even time it took to run.
|
6
|
+
class Message
|
7
|
+
|
8
|
+
# Initialize the message with the given data about the process.
|
9
|
+
#
|
10
|
+
# @param data [Hash] the data about the process.
|
11
|
+
# @option data [Numeric] :exit_code (0) the code the process
|
12
|
+
# exited with.
|
13
|
+
# @option data [Numeric] :process_id (-1) the process id the
|
14
|
+
# process ran under.
|
15
|
+
# @option data [Numeric] :time (0) the amount of time the
|
16
|
+
# process took to run, in seconds.
|
17
|
+
# @option data [Boolean] :executed (false) whether or not the
|
18
|
+
# process was actually executed.
|
19
|
+
# @option data [Hash] :env ({}) the environment the process
|
20
|
+
# ran under.
|
21
|
+
# @option data [Hash] :options ({}) the options that the process
|
22
|
+
# was run under.
|
23
|
+
# @option data [String] :stdout ("\n") the output the command
|
24
|
+
# outputted on stdout. This does not include output on stderr.
|
25
|
+
# @option data [String] :stderr ("") the output the command
|
26
|
+
# outputted on stderr. This does not include output on stdout.
|
27
|
+
# @option data [String] :line ("") the line that was executed.
|
28
|
+
# @option data [Process::Status] :status (nil) the status of the
|
29
|
+
# process.
|
30
|
+
def initialize(data)
|
31
|
+
{
|
32
|
+
:executed => false,
|
33
|
+
:time => 0,
|
34
|
+
:process_id => -1,
|
35
|
+
:exit_code => 0,
|
36
|
+
:env => {},
|
37
|
+
:options => {},
|
38
|
+
:stdout => "\n",
|
39
|
+
:stderr => "",
|
40
|
+
:line => "",
|
41
|
+
:status => nil
|
42
|
+
}.merge(data).each do |k, v|
|
43
|
+
instance_variable_set(:"@#{k}", (v.dup rescue v))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Whether or not the process was actually executed.
|
48
|
+
#
|
49
|
+
# @return [Boolean]
|
50
|
+
def executed?
|
51
|
+
@executed
|
52
|
+
end
|
53
|
+
|
54
|
+
# Whether or not the exit code was non-zero.
|
55
|
+
#
|
56
|
+
# @return [Boolean]
|
57
|
+
def nonzero_exit?
|
58
|
+
exit_code != 0
|
59
|
+
end
|
60
|
+
|
61
|
+
# @!attribute [r] exit_code
|
62
|
+
# The code the process exited with.
|
63
|
+
#
|
64
|
+
# @return [Numeric]
|
65
|
+
# @!attribute [r] process_id
|
66
|
+
# The process id the process ran under.
|
67
|
+
#
|
68
|
+
# @return [Numeric]
|
69
|
+
# @!attribute [r] time
|
70
|
+
# The amount of time the process took to run, in seconds.
|
71
|
+
#
|
72
|
+
# @return [Numeric]
|
73
|
+
# @!attribute [r] stdout
|
74
|
+
# The standard out of the process that was executed.
|
75
|
+
#
|
76
|
+
# @return [String]
|
77
|
+
# @!attribute [r] stderr
|
78
|
+
# The standard error of the process that was executed.
|
79
|
+
#
|
80
|
+
# @return [String]
|
81
|
+
# @!attribute [r] options
|
82
|
+
# The options that the user passed when executing the process.
|
83
|
+
#
|
84
|
+
# @return [Hash]
|
85
|
+
# @!attribute [r] line
|
86
|
+
# The exact line that was executed.
|
87
|
+
#
|
88
|
+
# @return [String]
|
89
|
+
# @!attribute [r] status
|
90
|
+
# The status of the process.
|
91
|
+
#
|
92
|
+
# @return [Process::Status]
|
93
|
+
[:exit_code, :process_id, :time, :stdout, :stderr, :options,
|
94
|
+
:line, :status].each do |key|
|
95
|
+
define_method(key) { instance_variable_get(:"@#{key}") }
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
require 'future'
|
3
|
+
|
4
|
+
require 'command/runner/version'
|
5
|
+
require 'command/runner/message'
|
6
|
+
require 'command/runner/exceptions'
|
7
|
+
require 'command/runner/backends'
|
8
|
+
|
9
|
+
module Command
|
10
|
+
|
11
|
+
# This handles the execution of commands.
|
12
|
+
class Runner
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Gets the default backend to use with the messenger.
|
16
|
+
# Defaults to the best available backend.
|
17
|
+
#
|
18
|
+
# @return [#call] a backend to use.
|
19
|
+
def backend
|
20
|
+
@backend ||= best_backend
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the best backend for messenger to use.
|
24
|
+
#
|
25
|
+
# @return [#call] a backend to use.
|
26
|
+
def best_backend
|
27
|
+
if Backends::PosixSpawn.available?
|
28
|
+
Backends::PosixSpawn.new
|
29
|
+
elsif Backends::Spawn.available?
|
30
|
+
Backends::Spawn.new
|
31
|
+
elsif Backends::Backticks.available?
|
32
|
+
Backends::Backticks.new
|
33
|
+
else
|
34
|
+
Backends::Fake.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Sets the default backend to use with the messenger.
|
39
|
+
attr_writer :backend
|
40
|
+
end
|
41
|
+
|
42
|
+
# The command the messenger was initialized with.
|
43
|
+
#
|
44
|
+
# @return [String]
|
45
|
+
attr_reader :command
|
46
|
+
|
47
|
+
# The arguments the messenger was initialized with.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
attr_reader :arguments
|
51
|
+
|
52
|
+
# The options the messenger was initialized with.
|
53
|
+
#
|
54
|
+
# @return [Hash]
|
55
|
+
def options
|
56
|
+
@options.dup.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
# Gets the backend to be used by the messenger. If it is not defined
|
60
|
+
# on the instance, it'll get the class default.
|
61
|
+
#
|
62
|
+
# @see Messenger.backend
|
63
|
+
# @return [#call] a backend to use.
|
64
|
+
def backend
|
65
|
+
@backend || self.class.backend
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets the backend to be used by the messenger. This is local to the
|
69
|
+
# instance.
|
70
|
+
attr_writer :backend
|
71
|
+
|
72
|
+
# Initialize the messenger.
|
73
|
+
#
|
74
|
+
# @param command [String] the name of the command file to run.
|
75
|
+
# @param arguments [String] the arguments to pass to the command.
|
76
|
+
# may contain interpolated values, like +{key}+ or +{{key}}+.
|
77
|
+
# @param options [Hash] the options for the messenger.
|
78
|
+
def initialize(command, arguments, options = {})
|
79
|
+
@command = command
|
80
|
+
@arguments = arguments
|
81
|
+
@options = options
|
82
|
+
end
|
83
|
+
|
84
|
+
# Runs the command and arguments with the given interpolations;
|
85
|
+
# defaults to no interpolations.
|
86
|
+
def pass(interops = {}, options = {})
|
87
|
+
backend.call(*[contents(interops), options.delete(:env) || {}, options].flatten)
|
88
|
+
end
|
89
|
+
|
90
|
+
# The command line being run by the runner. Interpolates the
|
91
|
+
# arguments with the given interpolations.
|
92
|
+
#
|
93
|
+
# @see #interpolate
|
94
|
+
# @param interops [Hash] the interpolations to make.
|
95
|
+
# @return [Array<(String, String)>] the command line that will be run.
|
96
|
+
def contents(interops = {})
|
97
|
+
[command, interpolate(arguments, interops)]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Interpolates the given string with the given interpolations.
|
101
|
+
# The keys of the interpolations should be alphanumeric,
|
102
|
+
# including underscores and dashes. It will search the given
|
103
|
+
# string for +{key}+ and +{{key}}+; if it finds the former, it
|
104
|
+
# replaces it with the escaped value. If it finds the latter, it
|
105
|
+
# replaces it with the value directly.
|
106
|
+
#
|
107
|
+
# @param string [String] the string to interpolate.
|
108
|
+
# @param interops [Hash] the interpolations to make.
|
109
|
+
# @return [String] the interpolated string.
|
110
|
+
def interpolate(string, interops = {})
|
111
|
+
interops = interops.to_a.map { |(k, v)| { k.to_s => v } }.inject(&:merge) || {}
|
112
|
+
|
113
|
+
string.gsub(/(\{{1,2})([0-9a-zA-Z_\-]+)(\}{1,2})/) do |m|
|
114
|
+
if interops.key?($2) && $1.length == $3.length
|
115
|
+
if $1.length < 2 then escape(interops[$2].to_s) else interops[$2] end
|
116
|
+
else
|
117
|
+
m
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Escape the given string for a shell.
|
125
|
+
#
|
126
|
+
# @param string [String] the string to escape.
|
127
|
+
# @return [String] the escaped string.
|
128
|
+
def escape(string)
|
129
|
+
Shellwords.escape(string)
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
describe Command::Runner do
|
2
|
+
|
3
|
+
before :each do
|
4
|
+
Command::Runner.backend = Command::Runner::Backends::Fake.new
|
5
|
+
end
|
6
|
+
|
7
|
+
subject do
|
8
|
+
Command::Runner.new(command, arguments)
|
9
|
+
end
|
10
|
+
|
11
|
+
context "interpolating strings" do
|
12
|
+
let(:command) { "echo" }
|
13
|
+
let(:arguments) { "some {interpolation}" }
|
14
|
+
|
15
|
+
it "interpolates correctly" do
|
16
|
+
subject.contents(:interpolation => "test").should == ["echo", "some test"]
|
17
|
+
end
|
18
|
+
|
19
|
+
it "escapes bad values" do
|
20
|
+
subject.contents(:interpolation => "`bad value`").should == ["echo", "some \\`bad\\ value\\`"]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "doesn't interpolate interpolation values" do
|
24
|
+
subject.contents(:interpolation => "{other}", :other => "hi").should == ["echo", "some \\{other\\}"]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "double interpolated strings" do
|
29
|
+
let(:command) { "echo" }
|
30
|
+
let(:arguments) { "some {{interpolation}}" }
|
31
|
+
|
32
|
+
it "interpolates correctly" do
|
33
|
+
subject.contents(:interpolation => "test").should == ["echo", "some test"]
|
34
|
+
end
|
35
|
+
|
36
|
+
it "doesn't escape bad values" do
|
37
|
+
subject.contents(:interpolation => "`bad value`").should == ["echo", "some `bad value`"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "misinterpolated strings" do
|
42
|
+
let(:command) { "echo" }
|
43
|
+
let(:arguments) { "some {{interpolation}" }
|
44
|
+
|
45
|
+
it "doesn't interpolate" do
|
46
|
+
subject.contents(:interpolation => "test").should == ["echo", "some {{interpolation}"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "selects backends" do
|
51
|
+
it "selects the best backend" do
|
52
|
+
Command::Runner::Backends::PosixSpawn.stub(:available?).and_return(false)
|
53
|
+
Command::Runner::Backends::Spawn.stub(:available?).and_return(true)
|
54
|
+
Command::Runner.best_backend.should be_instance_of Command::Runner::Backends::Spawn
|
55
|
+
|
56
|
+
Command::Runner::Backends::PosixSpawn.stub(:available?).and_return(true)
|
57
|
+
Command::Runner.best_backend.should be_instance_of Command::Runner::Backends::PosixSpawn
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/spec/spawn_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
describe Command::Runner::Backends::Spawn do
|
2
|
+
|
3
|
+
next unless Process.respond_to? :spawn
|
4
|
+
|
5
|
+
it "is available" do
|
6
|
+
Command::Runner::Backends::Spawn.should be_available
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns a message" do
|
10
|
+
value = subject.call("echo", "hello")
|
11
|
+
value.should be_instance_of Command::Runner::Message
|
12
|
+
value.should be_executed
|
13
|
+
end
|
14
|
+
|
15
|
+
it "doesn't block" do
|
16
|
+
start_time = Time.now
|
17
|
+
value = subject.call("sleep", "0.5")
|
18
|
+
end_time = Time.now
|
19
|
+
|
20
|
+
(end_time - start_time).should be_within((1.0/100)).of(0)
|
21
|
+
value.time.should be_within((1.0/100)).of(0.5)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "can not be available" do
|
25
|
+
Command::Runner::Backends::Spawn.stub(:available?).and_return(false)
|
26
|
+
|
27
|
+
expect {
|
28
|
+
Command::Runner::Backends::Spawn.new
|
29
|
+
}.to raise_error(Command::Runner::NotAvailableBackendError)
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: command-runner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jeremy Rodi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-20 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: promise
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.3'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: ! " Runs a command or two in the shell with arguments that can be\n
|
63
|
+
\ interpolated with the interpolation syntax.\n"
|
64
|
+
email: redjazz96@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- README.md
|
70
|
+
- lib/command/runner/version.rb
|
71
|
+
- lib/command/runner/exceptions.rb
|
72
|
+
- lib/command/runner/backends.rb
|
73
|
+
- lib/command/runner/message.rb
|
74
|
+
- lib/command/runner/backends/fake.rb
|
75
|
+
- lib/command/runner/backends/backticks.rb
|
76
|
+
- lib/command/runner/backends/posix_spawn.rb
|
77
|
+
- lib/command/runner/backends/spawn.rb
|
78
|
+
- lib/command/runner.rb
|
79
|
+
- spec/messenger_spec.rb
|
80
|
+
- spec/spawn_spec.rb
|
81
|
+
homepage: http://github.com/redjazz96/command-runner
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.25
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Runs commands.
|
105
|
+
test_files: []
|
106
|
+
has_rdoc: false
|