backticks 0.5.0 → 1.0.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -1
- data/lib/backticks/command.rb +43 -15
- data/lib/backticks/runner.rb +6 -13
- data/lib/backticks/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a446a43c0be0d08f7ad35f3f9a2fa7791f66229d
|
4
|
+
data.tar.gz: 2fc8f0e3176dae7628cb08225c63f95d97002e8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96d973bd4ef27a7bfcfecf7f8cb9e7ea0de2bb05f758aa42fb49c5438ba372230c5a77d07737da4e9492c0109533992ca9f1ad9ccf779fe442e7907d8bd5a7b7
|
7
|
+
data.tar.gz: 378d42242e3efbc5817c89266a34e620ca59ddfe6fc92411b56671fe5aa7104088a5f934235f8b1f9dbcb66c589ef71f8c1c1904f5a755074392317817c59172
|
data/README.md
CHANGED
@@ -81,9 +81,16 @@ r = Backticks::Runner.new(buffered:true)
|
|
81
81
|
|
82
82
|
# or later on
|
83
83
|
r.buffered = false
|
84
|
+
|
85
|
+
# you can also specify invididual stream names to buffer
|
86
|
+
r.buffered = [:stderr]
|
84
87
|
```
|
85
88
|
|
86
|
-
|
89
|
+
When you read the `buffered` attribute of a Runner, it returns the list of
|
90
|
+
stream names that are buffered; this means that even if you _write_ a boolean
|
91
|
+
to this attribute, you will _read_ an Array of Symbol.
|
92
|
+
|
93
|
+
### Interactivity and Real-Time Capture
|
87
94
|
|
88
95
|
If you set `interactive:true` on the Runner, the console of the calling (Ruby)
|
89
96
|
process is "tied" to the child's I/O streams, allowing the user to interact
|
@@ -98,6 +105,26 @@ require 'io/console'
|
|
98
105
|
STDOUT.raw! ; Backticks::Runner.new(interactive:true).run('vi').join
|
99
106
|
```
|
100
107
|
|
108
|
+
You can use the `Command#tap` method to intercept I/O streams in real time,
|
109
|
+
with or without interactivity. To start the tap, pass a block to the method.
|
110
|
+
Your block should accept two parameters: a Symbol stream name (:stdin,
|
111
|
+
:stdout, :stderr) and a String with binary encoding that contains fresh
|
112
|
+
input or output.
|
113
|
+
|
114
|
+
The result of your block is used to decide what to do with the input/output:
|
115
|
+
nil means "discard" and a modified string is captured in place of the original.
|
116
|
+
|
117
|
+
Try loading the README in an editor with all of the vowels scrambled. Scroll
|
118
|
+
around and notice how words change when they leave and enter the screen!
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
STDOUT.raw! ; cmd = Backticks::Runner.new(interactive:true).run('vi', 'README.md') ; cmd.tap do |io, bytes|
|
122
|
+
vowels = 'aeiou'
|
123
|
+
bytes.gsub!(/[#{vowels}]/) { vowels[rand(vowels.size)] } if io == :stdout
|
124
|
+
bytes
|
125
|
+
end ; cmd.join ; nil
|
126
|
+
```
|
127
|
+
|
101
128
|
### Literally Overriding Ruby's Backticks
|
102
129
|
|
103
130
|
It's a terrible idea, but you can use this gem to change the behavior of
|
data/lib/backticks/command.rb
CHANGED
@@ -20,12 +20,18 @@ module Backticks
|
|
20
20
|
# @return [Integer] child process ID
|
21
21
|
attr_reader :pid
|
22
22
|
|
23
|
-
# @return [String] all data captured (so far) from child's stdin/stdout/stderr
|
24
|
-
attr_reader :captured_input, :captured_output, :captured_error
|
25
|
-
|
26
23
|
# @return [nil,Process::Status] result of command if it has ended; nil if still running
|
27
24
|
attr_reader :status
|
28
25
|
|
26
|
+
# @return [String] all input that has been captured so far
|
27
|
+
attr_reader :captured_input
|
28
|
+
|
29
|
+
# @return [String] all output that has been captured so far
|
30
|
+
attr_reader :captured_output
|
31
|
+
|
32
|
+
# @return [String] all output to stderr that has been captured so far
|
33
|
+
attr_reader :captured_error
|
34
|
+
|
29
35
|
# Watch a running command.
|
30
36
|
def initialize(pid, stdin, stdout, stderr)
|
31
37
|
@pid = pid
|
@@ -38,10 +44,23 @@ module Backticks
|
|
38
44
|
@captured_error = String.new.force_encoding(Encoding::BINARY)
|
39
45
|
end
|
40
46
|
|
47
|
+
# @return [String]
|
48
|
+
def to_s
|
49
|
+
"#<Backticks::Command(@pid=#{pid},@status=#{@status || 'nil'})>"
|
50
|
+
end
|
51
|
+
|
41
52
|
def interactive?
|
42
53
|
!@stdin.nil?
|
43
54
|
end
|
44
55
|
|
56
|
+
# Provide a callback to monitor input and output in real time.
|
57
|
+
# @yield
|
58
|
+
# @yieldparam
|
59
|
+
def tap(&block)
|
60
|
+
raise StandardError.new("Tap is already set (#{@tap}); cannot set twice") if @tap && @tap != block
|
61
|
+
@tap = block
|
62
|
+
end
|
63
|
+
|
45
64
|
# Block until the command exits, or until limit seconds have passed. If
|
46
65
|
# interactive is true, pass user input to the command and print its output
|
47
66
|
# to Ruby's output streams. If the time limit expires, return `nil`;
|
@@ -73,14 +92,13 @@ module Backticks
|
|
73
92
|
# - the command produces fresh output on stdout or stderr
|
74
93
|
# - the user passes some input to the command (if interactive)
|
75
94
|
# - the process exits
|
76
|
-
# - the time limit elapses (if provided)
|
95
|
+
# - the time limit elapses (if provided) OR 60 seconds pass
|
77
96
|
#
|
78
97
|
# Return up to CHUNK bytes of fresh output from the process, or return nil
|
79
98
|
# if no fresh output was produced
|
80
99
|
#
|
81
100
|
# @param [Float,Integer] number of seconds to wait before returning nil
|
82
101
|
# @return [String,nil] fresh bytes from stdout/stderr, or nil if no output
|
83
|
-
private
|
84
102
|
def capture(limit=nil)
|
85
103
|
streams = [@stdout, @stderr]
|
86
104
|
streams << STDIN if interactive?
|
@@ -91,15 +109,19 @@ module Backticks
|
|
91
109
|
tf = FOREVER
|
92
110
|
end
|
93
111
|
|
94
|
-
ready, _, _ = IO.select(streams, [], [],
|
112
|
+
ready, _, _ = IO.select(streams, [], [], 0)
|
95
113
|
|
96
114
|
# proxy STDIN to child's stdin
|
97
115
|
if ready && ready.include?(STDIN)
|
98
|
-
|
99
|
-
if
|
100
|
-
@
|
101
|
-
|
116
|
+
data = STDIN.readpartial(CHUNK) rescue nil
|
117
|
+
if data
|
118
|
+
data = @tap.call(:stdin, data) if @tap
|
119
|
+
if data
|
120
|
+
@captured_input << data
|
121
|
+
@stdin.write(data)
|
122
|
+
end
|
102
123
|
else
|
124
|
+
@tap.call(:stdin, nil) if @tap
|
103
125
|
# our own STDIN got closed; proxy this fact to the child
|
104
126
|
@stdin.close unless @stdin.closed?
|
105
127
|
end
|
@@ -109,9 +131,12 @@ module Backticks
|
|
109
131
|
if ready && ready.include?(@stdout)
|
110
132
|
data = @stdout.readpartial(CHUNK) rescue nil
|
111
133
|
if data
|
112
|
-
@
|
113
|
-
|
114
|
-
|
134
|
+
data = @tap.call(:stdout, data) if @tap
|
135
|
+
if data
|
136
|
+
@captured_output << data
|
137
|
+
STDOUT.write(data) if interactive?
|
138
|
+
fresh_output = data
|
139
|
+
end
|
115
140
|
end
|
116
141
|
end
|
117
142
|
|
@@ -119,8 +144,11 @@ module Backticks
|
|
119
144
|
if ready && ready.include?(@stderr)
|
120
145
|
data = @stderr.readpartial(CHUNK) rescue nil
|
121
146
|
if data
|
122
|
-
@
|
123
|
-
|
147
|
+
data = @tap.call(:stderr, data) if @tap
|
148
|
+
if data
|
149
|
+
@captured_error << data
|
150
|
+
STDERR.write(data) if interactive?
|
151
|
+
end
|
124
152
|
end
|
125
153
|
end
|
126
154
|
fresh_output
|
data/lib/backticks/runner.rb
CHANGED
@@ -15,12 +15,8 @@ module Backticks
|
|
15
15
|
# process so the user can view their output and send input to them.
|
16
16
|
# Commands' output is still captured normally when they are interactive.
|
17
17
|
#
|
18
|
-
# Note
|
19
|
-
#
|
20
|
-
# buffers pipe I/O. If you want to send some input to your command, you
|
21
|
-
# may need to send a LOT of input before it receives any; the same problem
|
22
|
-
# applies to reading your command's output. If you set interactive to
|
23
|
-
# true, you usually want to set buffered to false!
|
18
|
+
# Note: if you set `interactive` to true, then stdin and stdout will be
|
19
|
+
# unbuffered regardless of how you have set `buffered`!
|
24
20
|
#
|
25
21
|
# @return [Boolean]
|
26
22
|
attr_accessor :interactive
|
@@ -29,7 +25,10 @@ module Backticks
|
|
29
25
|
# a pseudoterminal.
|
30
26
|
#
|
31
27
|
# This may be a Boolean, or it may be an Array of stream names from the
|
32
|
-
# set [:stdin, stdout, stderr].
|
28
|
+
# set [:stdin, :stdout, :stderr].
|
29
|
+
#
|
30
|
+
# Note: if you set `interactive` to true, then stdin and stdout will be
|
31
|
+
# unbuffered regardless of how you have set `buffered`!
|
33
32
|
#
|
34
33
|
# @return [Array] list of symbolic stream names
|
35
34
|
attr_reader :buffered
|
@@ -66,12 +65,6 @@ module Backticks
|
|
66
65
|
end
|
67
66
|
end
|
68
67
|
|
69
|
-
# @deprecated
|
70
|
-
def command(*sugar)
|
71
|
-
warn 'Backticks::Runner#command is deprecated; please call #run instead'
|
72
|
-
run(*sugar)
|
73
|
-
end
|
74
|
-
|
75
68
|
# Run a command whose parameters are expressed using some Rubyish sugar.
|
76
69
|
# This method accepts an arbitrary number of positional parameters; each
|
77
70
|
# parameter can be a Hash, an array, or a simple Object. Arrays and simple
|
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: 0.
|
4
|
+
version: 1.0.0rc1
|
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-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -90,9 +90,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
90
|
version: '2.0'
|
91
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
92
|
requirements:
|
93
|
-
- - "
|
93
|
+
- - ">"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version:
|
95
|
+
version: 1.3.1
|
96
96
|
requirements: []
|
97
97
|
rubyforge_project:
|
98
98
|
rubygems_version: 2.4.5
|