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 ADDED
@@ -0,0 +1,57 @@
1
+ # Command Runner [![Build Status](https://travis-ci.org/redjazz96/command-runner.png?branch=master)](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,10 @@
1
+ module Command
2
+
3
+ class Runner
4
+
5
+ # Raised when a backend is instantized on a platform that doesn't
6
+ # support it.
7
+ class NotAvailableBackendError < StandardError; end
8
+
9
+ end
10
+ 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,9 @@
1
+ module Command
2
+
3
+ class Runner
4
+
5
+ # The current version of Runner.
6
+ VERSION = "0.2.0"
7
+
8
+ end
9
+ 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
@@ -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