backticks 1.0.0rc1 → 1.0.0rc2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +2 -0
- data/Gemfile +5 -0
- data/README.md +8 -6
- data/lib/backticks/command.rb +42 -31
- data/lib/backticks/runner.rb +17 -5
- data/lib/backticks/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51c5bc30064cde134337b459ec829f6099a65c21
|
4
|
+
data.tar.gz: 950628c7a317d251eca8bc29c89cf474259b2f63
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6887deef8f9ce8f4c1c998951b135a78e567a6244eaa9b80cd132fbb22d7700bc1c64470b010cfe9cd91b10a4dcc9432cf0bd4b9a5df5fbc9d73e365217f9b66
|
7
|
+
data.tar.gz: cdb8c7c4e9d7776592921ca11c29c930c3d1687ecf82a0058ffc8088d14c0482fcec31dfac9fa56853807984834fd2d9c093cf0c5f82ab6898a58955eff695c7
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,15 +1,14 @@
|
|
1
|
-
|
1
|
+
![Build Status](https://travis-ci.org/xeger/backticks.svg) [![Coverage Status](https://coveralls.io/repos/xeger/backticks/badge.svg?branch=master&service=github)](https://coveralls.io/github/xeger/backticks?branch=master) [![Docs](https://img.shields.io/badge/docs-rubydoc-blue.svg)](http://www.rubydoc.info/gems/backticks)
|
2
2
|
|
3
3
|
Backticks is a powerful, intuitive OOP wrapper for invoking command-line processes and
|
4
4
|
interacting with them.
|
5
5
|
|
6
|
-
![Build Status](https://travis-ci.org/xeger/backticks.svg) [![Coverage Status](https://coveralls.io/repos/xeger/backticks/badge.svg?branch=master&service=github)](https://coveralls.io/github/xeger/backticks?branch=master)
|
7
|
-
|
8
6
|
"Powerful" comes from features that make Backticks especially well suited for time-sensitive
|
9
7
|
or record/playback applications:
|
10
8
|
- Uses [pseudoterminals](https://en.wikipedia.org/wiki/Pseudoterminal) for realtime stdout/stdin
|
11
9
|
- Captures input as well as output
|
12
10
|
- Separates stdout from stderr
|
11
|
+
- Allows realtime monitoring and transformation of input/output
|
13
12
|
|
14
13
|
"Intuitive" comes from a DSL that lets you provide command-line arguments as if they were
|
15
14
|
Ruby method arguments:
|
@@ -105,6 +104,8 @@ require 'io/console'
|
|
105
104
|
STDOUT.raw! ; Backticks::Runner.new(interactive:true).run('vi').join
|
106
105
|
```
|
107
106
|
|
107
|
+
### Intercepting and Modifying I/O
|
108
|
+
|
108
109
|
You can use the `Command#tap` method to intercept I/O streams in real time,
|
109
110
|
with or without interactivity. To start the tap, pass a block to the method.
|
110
111
|
Your block should accept two parameters: a Symbol stream name (:stdin,
|
@@ -112,14 +113,15 @@ Your block should accept two parameters: a Symbol stream name (:stdin,
|
|
112
113
|
input or output.
|
113
114
|
|
114
115
|
The result of your block is used to decide what to do with the input/output:
|
115
|
-
nil means "discard"
|
116
|
+
nil means "discard," any String means "use this in place of the original input
|
117
|
+
or output.""
|
116
118
|
|
117
|
-
Try loading
|
119
|
+
Try loading `README.md` in an editor with all of the vowels scrambled. Scroll
|
118
120
|
around and notice how words change when they leave and enter the screen!
|
119
121
|
|
120
122
|
```ruby
|
123
|
+
vowels = 'aeiou'
|
121
124
|
STDOUT.raw! ; cmd = Backticks::Runner.new(interactive:true).run('vi', 'README.md') ; cmd.tap do |io, bytes|
|
122
|
-
vowels = 'aeiou'
|
123
125
|
bytes.gsub!(/[#{vowels}]/) { vowels[rand(vowels.size)] } if io == :stdout
|
124
126
|
bytes
|
125
127
|
end ; cmd.join ; nil
|
data/lib/backticks/command.rb
CHANGED
@@ -6,13 +6,11 @@ module Backticks
|
|
6
6
|
# Interactive commands print their output to Ruby's STDOUT and STDERR
|
7
7
|
# in realtime, and also pass input from Ruby's STDIN to the command's stdin.
|
8
8
|
class Command
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
# You have been warned!
|
15
|
-
FOREVER = Time.at(2**32-1).freeze
|
9
|
+
# Duration that we use when a caller is willing to wait "forever" for
|
10
|
+
# a command to finish. This means that `#join` is buggy when used with
|
11
|
+
# commands that take longer than a year to complete. You have been
|
12
|
+
# warned!
|
13
|
+
FOREVER = 86_400 * 365
|
16
14
|
|
17
15
|
# Number of bytes to read from the command in one "chunk".
|
18
16
|
CHUNK = 1_024
|
@@ -32,52 +30,71 @@ module Backticks
|
|
32
30
|
# @return [String] all output to stderr that has been captured so far
|
33
31
|
attr_reader :captured_error
|
34
32
|
|
35
|
-
# Watch a running command
|
36
|
-
|
33
|
+
# Watch a running command by taking ownership of the IO objects that
|
34
|
+
# are passed in.
|
35
|
+
#
|
36
|
+
# @param [Integer] pid
|
37
|
+
# @param [IO] stdin
|
38
|
+
# @param [IO] stdout
|
39
|
+
# @param [IO] stderr
|
40
|
+
def initialize(pid, stdin, stdout, stderr, interactive:false)
|
37
41
|
@pid = pid
|
38
42
|
@stdin = stdin
|
39
43
|
@stdout = stdout
|
40
44
|
@stderr = stderr
|
45
|
+
@interactive = !!interactive
|
41
46
|
|
42
47
|
@captured_input = String.new.force_encoding(Encoding::BINARY)
|
43
48
|
@captured_output = String.new.force_encoding(Encoding::BINARY)
|
44
49
|
@captured_error = String.new.force_encoding(Encoding::BINARY)
|
45
50
|
end
|
46
51
|
|
47
|
-
# @return [String]
|
52
|
+
# @return [String] a basic string representation of this command
|
48
53
|
def to_s
|
49
54
|
"#<Backticks::Command(@pid=#{pid},@status=#{@status || 'nil'})>"
|
50
55
|
end
|
51
56
|
|
57
|
+
# @return [Boolean] true if this command is tied to STDIN/STDOUT
|
52
58
|
def interactive?
|
53
|
-
|
59
|
+
@interactive
|
60
|
+
end
|
61
|
+
|
62
|
+
# Block until the command completes; return true if its status
|
63
|
+
# was zero, false if nonzero.
|
64
|
+
#
|
65
|
+
# @return [Boolean]
|
66
|
+
def success?
|
67
|
+
join
|
68
|
+
status.success?
|
54
69
|
end
|
55
70
|
|
56
|
-
# Provide a callback to monitor input and output in real time.
|
71
|
+
# Provide a callback to monitor input and output in real time. This method
|
72
|
+
# saves a reference to block for later use; whenever the command generates
|
73
|
+
# output or receives input, the block is called back with the name of the
|
74
|
+
# stream on which I/O occurred and the actual data that was read or written.
|
57
75
|
# @yield
|
58
|
-
# @yieldparam
|
76
|
+
# @yieldparam [Symbol] stream one of :stdin, :stdout or :stderr
|
77
|
+
# @yieldparam [String] data fresh input from the designated stream
|
59
78
|
def tap(&block)
|
60
79
|
raise StandardError.new("Tap is already set (#{@tap}); cannot set twice") if @tap && @tap != block
|
61
80
|
@tap = block
|
62
81
|
end
|
63
82
|
|
64
83
|
# Block until the command exits, or until limit seconds have passed. If
|
65
|
-
# interactive is true,
|
66
|
-
# to
|
67
|
-
#
|
84
|
+
# interactive is true, proxy STDIN to the command and print its output
|
85
|
+
# to STDOUT. If the time limit expires, return `nil`; otherwise, return
|
86
|
+
# self.
|
87
|
+
#
|
88
|
+
# If the command has already exited when this method is called, return
|
89
|
+
# self immediately.
|
68
90
|
#
|
69
91
|
# @param [Float,Integer] limit number of seconds to wait before returning
|
70
|
-
def join(limit=
|
92
|
+
def join(limit=FOREVER)
|
71
93
|
return self if @status
|
72
94
|
|
73
|
-
|
74
|
-
tf = Time.now + limit
|
75
|
-
else
|
76
|
-
tf = FOREVER
|
77
|
-
end
|
78
|
-
|
95
|
+
tf = Time.now + limit
|
79
96
|
until (t = Time.now) >= tf
|
80
|
-
capture(tf
|
97
|
+
capture(tf-t)
|
81
98
|
res = Process.waitpid(@pid, Process::WNOHANG)
|
82
99
|
if res
|
83
100
|
@status = $?
|
@@ -103,13 +120,7 @@ module Backticks
|
|
103
120
|
streams = [@stdout, @stderr]
|
104
121
|
streams << STDIN if interactive?
|
105
122
|
|
106
|
-
|
107
|
-
tf = Time.now + limit
|
108
|
-
else
|
109
|
-
tf = FOREVER
|
110
|
-
end
|
111
|
-
|
112
|
-
ready, _, _ = IO.select(streams, [], [], 0)
|
123
|
+
ready, _, _ = IO.select(streams, [], [], limit)
|
113
124
|
|
114
125
|
# proxy STDIN to child's stdin
|
115
126
|
if ready && ready.include?(STDIN)
|
data/lib/backticks/runner.rb
CHANGED
@@ -24,19 +24,23 @@ module Backticks
|
|
24
24
|
# List of I/O streams that should be captured using a pipe instead of
|
25
25
|
# a pseudoterminal.
|
26
26
|
#
|
27
|
-
#
|
28
|
-
# set [:stdin, :stdout, :stderr]
|
27
|
+
# When read, this attribute is always an Array of stream names from the
|
28
|
+
# set `[:stdin, :stdout, :stderr]`.
|
29
29
|
#
|
30
|
-
# Note
|
30
|
+
# **Note**: if you set `interactive` to true, then stdin and stdout are
|
31
31
|
# unbuffered regardless of how you have set `buffered`!
|
32
32
|
#
|
33
33
|
# @return [Array] list of symbolic stream names
|
34
34
|
attr_reader :buffered
|
35
35
|
|
36
|
+
# @return [String,nil] PWD for new child processes, default is Dir.pwd
|
37
|
+
attr_accessor :chdir
|
38
|
+
|
36
39
|
# @return [#parameters] the CLI-translation object used by this runner
|
37
40
|
attr_reader :cli
|
38
41
|
|
39
42
|
# Create an instance of Runner.
|
43
|
+
#
|
40
44
|
# @option [#include?,Boolean] buffered list of names; true/false for all/none
|
41
45
|
# @option [#parameters] cli command-line parameter translator
|
42
46
|
# @option [Boolean] interactive true to tie parent stdout/stdin to child
|
@@ -55,6 +59,13 @@ module Backticks
|
|
55
59
|
self.interactive = options[:interactive]
|
56
60
|
end
|
57
61
|
|
62
|
+
# Control which streams are buffered (i.e. use a pipe) and which are
|
63
|
+
# unbuffered (i.e. use a pseudo-TTY).
|
64
|
+
#
|
65
|
+
# If you pass a Boolean argument, it is converted to an Array; therefore,
|
66
|
+
# the reader for this attribute always returns a list even if you wrote
|
67
|
+
# a boolean value.
|
68
|
+
#
|
58
69
|
# @param [Array,Boolean] buffered list of symbolic stream names; true/false for all/none
|
59
70
|
def buffered=(b)
|
60
71
|
@buffered = case b
|
@@ -109,7 +120,8 @@ module Backticks
|
|
109
120
|
PTY.open
|
110
121
|
end
|
111
122
|
|
112
|
-
|
123
|
+
dir = @chdir || Dir.pwd
|
124
|
+
pid = spawn(*argv, in: stdin_r, out: stdout_w, err: stderr_w, chdir: dir)
|
113
125
|
stdin_r.close
|
114
126
|
stdout_w.close
|
115
127
|
stderr_w.close
|
@@ -118,7 +130,7 @@ module Backticks
|
|
118
130
|
stdin = nil
|
119
131
|
end
|
120
132
|
|
121
|
-
Command.new(pid, stdin, stdout, stderr)
|
133
|
+
Command.new(pid, stdin, stdout, stderr, interactive:interactive)
|
122
134
|
end
|
123
135
|
end
|
124
136
|
end
|
data/lib/backticks/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backticks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.0rc2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Spataro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- ".gitignore"
|
63
63
|
- ".rspec"
|
64
64
|
- ".travis.yml"
|
65
|
+
- CHANGELOG.md
|
65
66
|
- CODE_OF_CONDUCT.md
|
66
67
|
- Gemfile
|
67
68
|
- README.md
|