backticks 1.0.0rc1 → 1.0.0rc2
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 +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
|
+
 [](https://coveralls.io/github/xeger/backticks?branch=master) [](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
|
-
 [](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
|