shellany 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +13 -0
- data/lib/shellany.rb +5 -0
- data/lib/shellany/sheller.rb +144 -0
- data/lib/shellany/version.rb +3 -0
- data/shellany.gemspec +22 -0
- data/spec/lib/shellany/sheller_spec.rb +141 -0
- data/spec/shellany_spec.rb +5 -0
- data/spec/spec_helper.rb +24 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 68dc8a476d023b6991ff72a15f075abfc03ed915
|
4
|
+
data.tar.gz: 56aa1c504f9fce4486a246188b24122910129fe6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 195ad943cf64c1c16071ebc9ec28f58fca1d5aaf88f7b0f672ee2d1f0a7863147c31abaa67b1ae1bcc6e28c6843e02cacf7dcea39adbe7231fd318bc8d02f512
|
7
|
+
data.tar.gz: 896b8ff16cb676c696e173fbe2bf950004b6fd44a51aa1d97e974401e5195742d8b034adcfeae7b8c82e936362428aa8c5046a1827f5350d3f098ebc5190d3cf
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Cezary Baginski
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Shellany
|
2
|
+
|
3
|
+
Shellany captures command output.
|
4
|
+
|
5
|
+
## Features:
|
6
|
+
|
7
|
+
- portability (should work on recent JRuby versions)
|
8
|
+
- capturing stdout, stderr in a convenient way
|
9
|
+
- returning the result in a convenient way
|
10
|
+
- detecting if a shell is needed (though incomplete/primitive implementation)
|
11
|
+
- prevents running the same command multiple times
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'shellany'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install shellany
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
Basic usage:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'shellany/sheller'
|
35
|
+
|
36
|
+
Shellany::Sheller.stdout("echo abc") # => "abc"
|
37
|
+
Shellany::Sheller.stderr("touch /foo") # => "touch: cannot touch ‘/aef’: Permission denied
|
38
|
+
Shellany::Sheller.run("false") # => false
|
39
|
+
Shellany::Sheller.system("clear") # => clears screen (no capture done)
|
40
|
+
```
|
41
|
+
|
42
|
+
Using Sheller object:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
require 'shellany/sheller'
|
46
|
+
|
47
|
+
sh = Shellany::Sheller.new('grep /etc/passed|tail -n 1') # does nothing
|
48
|
+
|
49
|
+
sh.stdout # shows output (runs the command since it wasn't run)
|
50
|
+
sh.stderr # shows stderr (does not run the command)
|
51
|
+
sh.ok? # returns true if exit code was zero (does not run the command)
|
52
|
+
```
|
53
|
+
|
54
|
+
## Project status
|
55
|
+
|
56
|
+
Only developed enough for Guard to run, though pull requests are more than welcome.
|
57
|
+
|
58
|
+
Especially for:
|
59
|
+
|
60
|
+
- better API
|
61
|
+
- better shell detection code
|
62
|
+
- better support for various system() arguments
|
63
|
+
- better support for redireciton handling
|
64
|
+
- better support for shell detection (e.g. Windows)
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
1. Fork it ( https://github.com/[my-github-username]/shellany/fork )
|
69
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
70
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
71
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
72
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/shellany.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module Shellany
|
4
|
+
# The Guard sheller abstract the actual subshell
|
5
|
+
# calls and allow easier stubbing.
|
6
|
+
#
|
7
|
+
class Sheller
|
8
|
+
attr_reader :status
|
9
|
+
|
10
|
+
# Creates a new Guard::Sheller object.
|
11
|
+
#
|
12
|
+
# @param [String] args a command to run in a subshell
|
13
|
+
# @param [Array<String>] args an array of command parts to run in a subshell
|
14
|
+
# @param [*String] args a list of command parts to run in a subshell
|
15
|
+
#
|
16
|
+
def initialize(*args)
|
17
|
+
fail ArgumentError, "no command given" if args.empty?
|
18
|
+
@command = args
|
19
|
+
@ran = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Shortcut for new(command).run
|
23
|
+
#
|
24
|
+
def self.run(*args)
|
25
|
+
new(*args).run
|
26
|
+
end
|
27
|
+
|
28
|
+
# Shortcut for new(command).run.stdout
|
29
|
+
#
|
30
|
+
def self.stdout(*args)
|
31
|
+
new(*args).stdout
|
32
|
+
end
|
33
|
+
|
34
|
+
# Shortcut for new(command).run.stderr
|
35
|
+
#
|
36
|
+
def self.stderr(*args)
|
37
|
+
new(*args).stderr
|
38
|
+
end
|
39
|
+
|
40
|
+
# Runs the command.
|
41
|
+
#
|
42
|
+
# @return [Boolean] whether or not the command succeeded.
|
43
|
+
#
|
44
|
+
def run
|
45
|
+
unless ran?
|
46
|
+
status, output, errors = self.class._system_with_capture(*@command)
|
47
|
+
@ran = true
|
48
|
+
@stdout = output
|
49
|
+
@stderr = errors
|
50
|
+
@status = status
|
51
|
+
end
|
52
|
+
|
53
|
+
ok?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns true if the command has already been run, false otherwise.
|
57
|
+
#
|
58
|
+
# @return [Boolean] whether or not the command has already been run
|
59
|
+
#
|
60
|
+
def ran?
|
61
|
+
@ran
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns true if the command succeeded, false otherwise.
|
65
|
+
#
|
66
|
+
# @return [Boolean] whether or not the command succeeded
|
67
|
+
#
|
68
|
+
def ok?
|
69
|
+
run unless ran?
|
70
|
+
|
71
|
+
@status && @status.success?
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the command's output.
|
75
|
+
#
|
76
|
+
# @return [String] the command output
|
77
|
+
#
|
78
|
+
def stdout
|
79
|
+
run unless ran?
|
80
|
+
|
81
|
+
@stdout
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the command's error output.
|
85
|
+
#
|
86
|
+
# @return [String] the command output
|
87
|
+
#
|
88
|
+
def stderr
|
89
|
+
run unless ran?
|
90
|
+
|
91
|
+
@stderr
|
92
|
+
end
|
93
|
+
|
94
|
+
# No output capturing
|
95
|
+
#
|
96
|
+
# NOTE: `$stdout.puts system('cls')` on Windows won't work like
|
97
|
+
# it does for on systems with ansi terminals, so we need to be
|
98
|
+
# able to call Kernel.system directly.
|
99
|
+
def self.system(*args)
|
100
|
+
_system_with_no_capture(*args)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self._system_with_no_capture(*args)
|
104
|
+
Kernel.system(*args)
|
105
|
+
result = $?
|
106
|
+
errors = (result == 0) || "Guard failed to run: #{args.inspect}"
|
107
|
+
[result, nil, errors]
|
108
|
+
end
|
109
|
+
|
110
|
+
def self._system_with_capture(*args)
|
111
|
+
# We use popen3, because it started working on recent versions
|
112
|
+
# of JRuby, while JRuby doesn't handle options to Kernel.system
|
113
|
+
args = _shellize_if_needed(args)
|
114
|
+
|
115
|
+
stdout, stderr, status = nil
|
116
|
+
Open3.popen3(*args) do |_stdin, _stdout, _stderr, _thr|
|
117
|
+
stdout = _stdout.read
|
118
|
+
stderr = _stderr.read
|
119
|
+
status = _thr.value
|
120
|
+
end
|
121
|
+
|
122
|
+
[status, stdout, stderr]
|
123
|
+
rescue Errno::ENOENT, IOError => e
|
124
|
+
[nil, nil, "Guard::Sheller failed (#{e.inspect})"]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Only needed on JRUBY, because MRI properly detects ';' and metachars
|
128
|
+
def self._shellize_if_needed(args)
|
129
|
+
return args unless RUBY_PLATFORM == "java"
|
130
|
+
return args unless args.size == 1
|
131
|
+
return args unless /[;<>]/ =~ args.first
|
132
|
+
|
133
|
+
# NOTE: Sheller was originally meant for Guard (which basically only uses
|
134
|
+
# UNIX commands anyway) and JRuby doesn't support options to
|
135
|
+
# Kernel.system (and doesn't automatically shell when there's a
|
136
|
+
# metacharacter in the command).
|
137
|
+
#
|
138
|
+
# So ... I'm assuming /bin/sh exists - if not, PRs are welcome,
|
139
|
+
# because I have no clue what to do if /bin/sh doesn't exist.
|
140
|
+
# (use ENV["RUBYSHELL"] ? Detect cmd.exe ?)
|
141
|
+
["/bin/sh", "-c", args.first]
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/shellany.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'shellany/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "shellany"
|
8
|
+
spec.version = Shellany::VERSION
|
9
|
+
spec.authors = ["Cezary Baginski"]
|
10
|
+
spec.email = ["cezary@chronomantic.net"]
|
11
|
+
spec.summary = %q{Simple, somewhat portable command capturing}
|
12
|
+
spec.description = %q{MRI+JRuby compatible command output capturing}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require "shellany/sheller"
|
2
|
+
|
3
|
+
RSpec.describe Shellany::Sheller, :sheller_specs do
|
4
|
+
before do
|
5
|
+
allow(Kernel).to receive(:system) do |args|
|
6
|
+
fail "Stub called with: #{args.inspect}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { described_class }
|
11
|
+
|
12
|
+
context "without a command" do
|
13
|
+
[:new, :run, :stdout, :stderr].each do |meth|
|
14
|
+
describe ".#{meth}" do
|
15
|
+
specify do
|
16
|
+
expect { subject.send(meth) }.
|
17
|
+
to raise_error ArgumentError, "no command given"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "with shell (string) cmd returning success" do
|
24
|
+
let(:cmd) { "ls -l" }
|
25
|
+
let(:output) { "foo.rb\n" }
|
26
|
+
let(:errors) { "" }
|
27
|
+
let(:result) do
|
28
|
+
[instance_double(Process::Status, success?: true), output, errors]
|
29
|
+
end
|
30
|
+
|
31
|
+
context "when constructed with a cmd" do
|
32
|
+
subject { described_class.new(cmd) }
|
33
|
+
|
34
|
+
describe "#run" do
|
35
|
+
it "runs the command given to constructor" do
|
36
|
+
expect(described_class).to receive(:_system_with_capture).
|
37
|
+
with(cmd).and_return(result)
|
38
|
+
subject.run
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with array cmd returning success" do
|
45
|
+
let(:cmd) { %w(ls -l) }
|
46
|
+
let(:output) { "foo.rb\n" }
|
47
|
+
let(:errors) { "" }
|
48
|
+
let(:result) do
|
49
|
+
[instance_double(Process::Status, success?: true), output, errors]
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "when used as class" do
|
53
|
+
describe ".run" do
|
54
|
+
it "runs the given command" do
|
55
|
+
expect(described_class).to receive(:_system_with_capture).
|
56
|
+
with(*cmd) { result }
|
57
|
+
subject.run(*cmd)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ".new" do
|
62
|
+
it "does not run anything" do
|
63
|
+
expect(described_class).to_not receive(:_system_with_capture)
|
64
|
+
subject
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe ".stdout" do
|
69
|
+
before do
|
70
|
+
allow(described_class).to receive(:_system_with_capture).
|
71
|
+
with(*cmd) { result }
|
72
|
+
end
|
73
|
+
|
74
|
+
it "runs command and returns output" do
|
75
|
+
expect(subject.stdout(*cmd)).to eq "foo.rb\n"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe ".stderr" do
|
80
|
+
before do
|
81
|
+
allow(described_class).to receive(:_system_with_capture).
|
82
|
+
with(*cmd) { result }
|
83
|
+
end
|
84
|
+
|
85
|
+
it "runs command and returns errors" do
|
86
|
+
expect(subject.stderr(*cmd)).to eq ""
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when constructed with a cmd" do
|
92
|
+
subject { described_class.new(*cmd) }
|
93
|
+
|
94
|
+
it "does not run anything" do
|
95
|
+
expect(described_class).to_not receive(:_system_with_capture)
|
96
|
+
subject
|
97
|
+
end
|
98
|
+
|
99
|
+
describe "#run" do
|
100
|
+
it "runs the command given to constructor" do
|
101
|
+
expect(described_class).to receive(:_system_with_capture).
|
102
|
+
with(*cmd) { result }
|
103
|
+
subject.run
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#stdout" do
|
108
|
+
before do
|
109
|
+
allow(described_class).to receive(:_system_with_capture).
|
110
|
+
with(*cmd) { result }
|
111
|
+
end
|
112
|
+
|
113
|
+
it "runs command and returns output" do
|
114
|
+
expect(subject.stdout).to eq "foo.rb\n"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#stderr" do
|
119
|
+
before do
|
120
|
+
allow(described_class).to receive(:_system_with_capture).
|
121
|
+
with(*cmd) { result }
|
122
|
+
end
|
123
|
+
|
124
|
+
it "runs command and returns output" do
|
125
|
+
expect(subject.stderr).to eq ""
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "#ok?" do
|
130
|
+
before do
|
131
|
+
allow(described_class).to receive(:_system_with_capture).
|
132
|
+
with(*cmd) { result }
|
133
|
+
end
|
134
|
+
|
135
|
+
it "runs command and returns output" do
|
136
|
+
expect(subject).to be_ok
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
RSpec.configure do |config|
|
2
|
+
config.expect_with :rspec do |expectations|
|
3
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
4
|
+
end
|
5
|
+
|
6
|
+
config.mock_with :rspec do |mocks|
|
7
|
+
mocks.verify_partial_doubles = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.filter_run :focus
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
|
13
|
+
config.disable_monkey_patching!
|
14
|
+
|
15
|
+
config.warnings = true
|
16
|
+
|
17
|
+
config.default_formatter = 'doc' if config.files_to_run.one?
|
18
|
+
|
19
|
+
# config.profile_examples = 10
|
20
|
+
|
21
|
+
config.order = :random
|
22
|
+
|
23
|
+
Kernel.srand config.seed
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shellany
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cezary Baginski
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
description: MRI+JRuby compatible command output capturing
|
28
|
+
email:
|
29
|
+
- cezary@chronomantic.net
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- ".rspec"
|
36
|
+
- ".travis.yml"
|
37
|
+
- Gemfile
|
38
|
+
- LICENSE.txt
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- lib/shellany.rb
|
42
|
+
- lib/shellany/sheller.rb
|
43
|
+
- lib/shellany/version.rb
|
44
|
+
- shellany.gemspec
|
45
|
+
- spec/lib/shellany/sheller_spec.rb
|
46
|
+
- spec/shellany_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
homepage: ''
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.4.3
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Simple, somewhat portable command capturing
|
72
|
+
test_files:
|
73
|
+
- spec/lib/shellany/sheller_spec.rb
|
74
|
+
- spec/shellany_spec.rb
|
75
|
+
- spec/spec_helper.rb
|