peer_commander 0.1.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 +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +30 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +94 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/lib/peer_commander.rb +3 -0
- data/lib/peer_commander/command.rb +92 -0
- data/lib/peer_commander/command_runner.rb +57 -0
- data/lib/peer_commander/errors.rb +2 -0
- data/lib/peer_commander/errors/command_already_executed_error.rb +5 -0
- data/lib/peer_commander/errors/command_not_executed_error.rb +5 -0
- data/lib/peer_commander/parallel_executor.rb +63 -0
- data/lib/peer_commander/version.rb +3 -0
- data/peer_commander.gemspec +30 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e49ccbfcf6d4da8b75b671e3cd4b9c2f36ac08fd
|
4
|
+
data.tar.gz: ba81b657c8d86be0551449a50e8fd63068990a8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 486680f237079ba157c1756a057125b6af768a9607be30ed947762a5e53b3c2306cdc174c4d90c273e759adf529d772430aa1beb12f2ddd1500e6d079f5b395a
|
7
|
+
data.tar.gz: b2744de1e0247d9a5b8ff0eda42784dcbcf5336d4046ef9593d336e51ffa39edb95774ba7a98724aa689aa47ddd0b66c13be150c386428ef5516b71e3a9d1cfa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.4
|
3
|
+
|
4
|
+
Metrics/LineLength:
|
5
|
+
Max: 119
|
6
|
+
Exclude:
|
7
|
+
- "spec/**/*"
|
8
|
+
|
9
|
+
Metrics/BlockLength:
|
10
|
+
Exclude:
|
11
|
+
- "spec/**/*"
|
12
|
+
|
13
|
+
RSpec/ExampleLength:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
RSpec/LetSetup:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/DotPosition:
|
20
|
+
EnforcedStyle: trailing
|
21
|
+
|
22
|
+
Style/SingleLineBlockParams:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
Style/StringLiterals:
|
26
|
+
EnforcedStyle: double_quotes
|
27
|
+
Enabled: true
|
28
|
+
|
29
|
+
Style/FrozenStringLiteralComment:
|
30
|
+
Enabled: false
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Jonathan Harden
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Peer::Commander
|
2
|
+
|
3
|
+
Provides an interfacing for executing mutliple system commands with a configurable level of parallelism.
|
4
|
+
|
5
|
+
The result objects will have access to the output of the command as well as the return status.
|
6
|
+
|
7
|
+
The overall result of executing the commands will be successful if all of the commands executed completed with
|
8
|
+
a successful return code.
|
9
|
+
|
10
|
+
Internally commands are executed with Open3.capture2e, env, opts, and stdin\_data used to init the commands are
|
11
|
+
sent directly to Open3.capture2e, see the documentation for that class to understand the behaviour and options
|
12
|
+
available.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "peer_commander"
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Or install it yourself as:
|
27
|
+
|
28
|
+
$ gem install peer_commander
|
29
|
+
|
30
|
+
## Usage
|
31
|
+
|
32
|
+
This is a convoluted example to demonstrate all the features
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require "peer_commander"
|
36
|
+
|
37
|
+
commands = [
|
38
|
+
PeerCommander::Command.new("sleep 2"),
|
39
|
+
PeerCommander::Command.new("wibble"),
|
40
|
+
PeerCommander::Command.new("sleep 1"),
|
41
|
+
PeerCommander::Command.new("exit 1"),
|
42
|
+
PeerCommander::Command.new("echo $TEST_ENV", env: { "TEST_ENV" => "output from env var"}),
|
43
|
+
PeerCommander::Command.new("cat", stdin_data: "data passed to standard in"),
|
44
|
+
PeerCommander::Command.new("sleep 4", opts: { chdir: "/tmp" }), # This command will be executed in the directory /tmp
|
45
|
+
]
|
46
|
+
|
47
|
+
commander = PeerCommander::CommandRunner.new(commands)
|
48
|
+
|
49
|
+
results = commander.execute(parallelism: 4)
|
50
|
+
|
51
|
+
puts "SUCCESS" if commander.success?
|
52
|
+
puts "FAILED" if commander.failed?
|
53
|
+
|
54
|
+
commander.all_commands.each { |command| puts "Executed #{command.command}" }
|
55
|
+
|
56
|
+
commander.successful_commands.each do |command|
|
57
|
+
puts "Command [#{command.command}] succeeded in #{command.duration} seconds with output:"
|
58
|
+
puts command.output
|
59
|
+
puts "================================================="
|
60
|
+
end
|
61
|
+
|
62
|
+
commander.failed_commands.each do |command|
|
63
|
+
print "Command [#{command.command}] failed in #{command.duration} seconds "
|
64
|
+
|
65
|
+
if command.exception
|
66
|
+
puts "with exception #{command.exception}"
|
67
|
+
else
|
68
|
+
puts "with exit code #{command.exit_code} and output\n"
|
69
|
+
puts command.output
|
70
|
+
end
|
71
|
+
|
72
|
+
puts "================================================="
|
73
|
+
end
|
74
|
+
|
75
|
+
puts "Total operation took #{commander.duration} seconds"
|
76
|
+
```
|
77
|
+
|
78
|
+
## Version numbering
|
79
|
+
|
80
|
+
This project will follow strict semantic versioning (https://semver.org/spec/v2.0.0.html) once we reach version 0.1.
|
81
|
+
Until then any version change can break anything.
|
82
|
+
|
83
|
+
## Development
|
84
|
+
|
85
|
+
You can run `bin/rspec` or `bin/rake` to run all the tests, you can also run `bin/console` for an interactive prompt
|
86
|
+
that will allow you to experiment.
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
Bug reports and pull requests are welcome on Bitbucket at https://bitbucket.org/eviljonny/peer_commander/
|
91
|
+
|
92
|
+
## License
|
93
|
+
|
94
|
+
This project is released under the MIT license which can be found in the LICENSE file
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require_relative "../lib/peer_commander"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/bin/rspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rspec-core", "rspec")
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module PeerCommander
|
4
|
+
# A single command to be executed, with access to results statuses, output, and timings
|
5
|
+
class Command
|
6
|
+
attr_reader :command, :exception, :opts, :env, :stdin_data
|
7
|
+
|
8
|
+
# The env specified will be passed directly to Open3.capture2e as the env to run with
|
9
|
+
# The opts provided will also be passed directly to Open3.capture2e as opts
|
10
|
+
# stdin_data will also be passed directly to Open3.capture2e
|
11
|
+
def initialize(command, stdin_data: nil, env: {}, opts: {})
|
12
|
+
@command = command
|
13
|
+
@stdin_data = stdin_data
|
14
|
+
@env = env
|
15
|
+
@opts = opts
|
16
|
+
end
|
17
|
+
|
18
|
+
# Execute the command, will capture any exceptions that inherit from StandardError raised by the execution of
|
19
|
+
# the given command and store it in the exception attribute, NOTE: This means it _will not_ propogate the
|
20
|
+
# exception upwards
|
21
|
+
#
|
22
|
+
# Returns self
|
23
|
+
def execute
|
24
|
+
raise Errors::CommandAlreadyExecutedError if executed?
|
25
|
+
|
26
|
+
start_time = Time.now
|
27
|
+
|
28
|
+
begin
|
29
|
+
@command_output, @status = Open3.capture2e(env, command, execution_options)
|
30
|
+
rescue StandardError => e
|
31
|
+
@exception = e
|
32
|
+
ensure
|
33
|
+
@timing = Time.now - start_time
|
34
|
+
end
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Return true if the command was successful
|
40
|
+
def success?
|
41
|
+
raise Errors::CommandNotExecutedError unless executed?
|
42
|
+
|
43
|
+
exception.nil? && status.success?
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return true if the command failed
|
47
|
+
def failed?
|
48
|
+
!success?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the output (stdout and stderr interleaved) of running the command
|
52
|
+
def output
|
53
|
+
raise Errors::CommandNotExecutedError unless executed?
|
54
|
+
|
55
|
+
command_output
|
56
|
+
end
|
57
|
+
|
58
|
+
# How long the command took to run in seconds
|
59
|
+
def duration
|
60
|
+
raise Errors::CommandNotExecutedError unless executed?
|
61
|
+
|
62
|
+
timing
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns a truthy object if the command has been executed, or nil
|
66
|
+
# if it has not.
|
67
|
+
#
|
68
|
+
# If the command has been executed it will either return the status code or
|
69
|
+
# the exception that was raised
|
70
|
+
def executed?
|
71
|
+
exception || status
|
72
|
+
end
|
73
|
+
|
74
|
+
def exit_code
|
75
|
+
raise Errors::CommandNotExecutedError unless executed?
|
76
|
+
|
77
|
+
return nil if exception
|
78
|
+
|
79
|
+
status.exitstatus
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
attr_reader :status, :command_output, :timing
|
85
|
+
|
86
|
+
def execution_options
|
87
|
+
opts.tap do |options|
|
88
|
+
options[:stdin_data] = stdin_data unless stdin_data.nil?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative "command"
|
2
|
+
require_relative "parallel_executor"
|
3
|
+
|
4
|
+
module PeerCommander
|
5
|
+
# Runs the listed commands with a configurable level of parallelism
|
6
|
+
class CommandRunner
|
7
|
+
def initialize(commands)
|
8
|
+
@commands = commands
|
9
|
+
@command_results = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(parallelism: 1)
|
13
|
+
start = Time.now
|
14
|
+
@command_results = ParallelExecutor.new.execute(commands, parallelism)
|
15
|
+
@duration = Time.now - start
|
16
|
+
@command_results
|
17
|
+
end
|
18
|
+
|
19
|
+
def success?
|
20
|
+
raise Errors::CommandNotExecutedError if command_results.empty?
|
21
|
+
|
22
|
+
@command_results.all?(&:success?)
|
23
|
+
end
|
24
|
+
|
25
|
+
def failed?
|
26
|
+
!success?
|
27
|
+
end
|
28
|
+
|
29
|
+
def all_commands
|
30
|
+
raise Errors::CommandNotExecutedError if command_results.empty?
|
31
|
+
|
32
|
+
@command_results
|
33
|
+
end
|
34
|
+
|
35
|
+
def successful_commands
|
36
|
+
raise Errors::CommandNotExecutedError if command_results.empty?
|
37
|
+
|
38
|
+
@command_results.select(&:success?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def failed_commands
|
42
|
+
raise Errors::CommandNotExecutedError if command_results.empty?
|
43
|
+
|
44
|
+
@command_results.select(&:failed?)
|
45
|
+
end
|
46
|
+
|
47
|
+
def duration
|
48
|
+
raise Errors::CommandNotExecutedError if command_results.empty?
|
49
|
+
|
50
|
+
@duration
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
attr_reader :commands, :command_results
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "concurrent"
|
2
|
+
|
3
|
+
module PeerCommander
|
4
|
+
# Executes a given set of commands with a specified parallelism
|
5
|
+
class ParallelExecutor
|
6
|
+
SLEEP_DURATION = 0.2
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@futures = []
|
10
|
+
@command_results = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(commands, parallelism)
|
14
|
+
raise ArgumentError, "Parallelism must be at least 1" if parallelism < 1
|
15
|
+
|
16
|
+
@parallelism = parallelism
|
17
|
+
|
18
|
+
commands.each do |command|
|
19
|
+
wait_for_slot if all_slots_filled?
|
20
|
+
|
21
|
+
@futures << Concurrent::Future.execute { command.execute }
|
22
|
+
end
|
23
|
+
|
24
|
+
wait_for_all
|
25
|
+
|
26
|
+
command_results
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :parallelism, :futures, :command_results, :future_command_map
|
32
|
+
|
33
|
+
def wait_for_slot
|
34
|
+
while all_slots_filled?
|
35
|
+
sleep(SLEEP_DURATION)
|
36
|
+
remove_completed_commands
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def wait_for_all
|
41
|
+
sleep(SLEEP_DURATION) while futures.any?(&:incomplete?)
|
42
|
+
@command_results.push(*extract_results_from(futures))
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_completed_commands
|
46
|
+
completed = futures.select(&:complete?)
|
47
|
+
@futures -= completed
|
48
|
+
@command_results.push(*extract_results_from(completed))
|
49
|
+
end
|
50
|
+
|
51
|
+
def slot_available?
|
52
|
+
futures.size < parallelism
|
53
|
+
end
|
54
|
+
|
55
|
+
def all_slots_filled?
|
56
|
+
!slot_available?
|
57
|
+
end
|
58
|
+
|
59
|
+
def extract_results_from(futures_to_extract)
|
60
|
+
futures_to_extract.map(&:value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("lib", __dir__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require_relative "lib/peer_commander/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "peer_commander"
|
8
|
+
spec.version = PeerCommander::VERSION
|
9
|
+
spec.authors = ["Jonathan Harden"]
|
10
|
+
spec.email = ["jonathan.harden@mydrivesolutions.com"]
|
11
|
+
spec.required_ruby_version = ">= 2.4"
|
12
|
+
|
13
|
+
spec.licenses = ["MIT"]
|
14
|
+
|
15
|
+
spec.summary = "Run arbitrary system commands in parallel reporting on errors."
|
16
|
+
spec.homepage = "https://bitbucket.org/eviljonny/peer_commander/"
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
20
|
+
end
|
21
|
+
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_dependency "concurrent-ruby", "~> 1"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
27
|
+
spec.add_development_dependency "pry-byebug", "~> 3"
|
28
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: peer_commander
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Harden
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-06-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.16'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.16'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- jonathan.harden@mydrivesolutions.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".rubocop.yml"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/console
|
98
|
+
- bin/rake
|
99
|
+
- bin/rspec
|
100
|
+
- lib/peer_commander.rb
|
101
|
+
- lib/peer_commander/command.rb
|
102
|
+
- lib/peer_commander/command_runner.rb
|
103
|
+
- lib/peer_commander/errors.rb
|
104
|
+
- lib/peer_commander/errors/command_already_executed_error.rb
|
105
|
+
- lib/peer_commander/errors/command_not_executed_error.rb
|
106
|
+
- lib/peer_commander/parallel_executor.rb
|
107
|
+
- lib/peer_commander/version.rb
|
108
|
+
- peer_commander.gemspec
|
109
|
+
homepage: https://bitbucket.org/eviljonny/peer_commander/
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '2.4'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubyforge_project:
|
129
|
+
rubygems_version: 2.6.14.1
|
130
|
+
signing_key:
|
131
|
+
specification_version: 4
|
132
|
+
summary: Run arbitrary system commands in parallel reporting on errors.
|
133
|
+
test_files: []
|