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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8199c03291828b81d31df59a9e3341d20b13b27a
4
- data.tar.gz: 5a4058b1ada73100e623660cf0c201f1aba3accb
3
+ metadata.gz: a446a43c0be0d08f7ad35f3f9a2fa7791f66229d
4
+ data.tar.gz: 2fc8f0e3176dae7628cb08225c63f95d97002e8f
5
5
  SHA512:
6
- metadata.gz: 943ceaf445259b9c4fc67e94652fa5be8d613496abf198578842b81a97d5bb782d371bc5ac1b519c8143850fce1290a7f8e1245989c1a3cc71c76c4da237c279
7
- data.tar.gz: 2c6ac11905c9a76836acb555d8819f74947fbf90fa42a0104bdb8823b2b3dff36d44bda9df56206cd9676ced6d5df65a24b201d1aa2d23295968e8d62b8f9c43
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
- ### Interactivity
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
@@ -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, [], [], 1)
112
+ ready, _, _ = IO.select(streams, [], [], 0)
95
113
 
96
114
  # proxy STDIN to child's stdin
97
115
  if ready && ready.include?(STDIN)
98
- input = STDIN.readpartial(CHUNK) rescue nil
99
- if input
100
- @captured_input << input
101
- @stdin.write(input)
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
- @captured_output << data
113
- STDOUT.write(data) if interactive?
114
- fresh_output = data
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
- @captured_error << data
123
- STDERR.write(data) if interactive?
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
@@ -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 that interactivity doesn't work very well with unbuffered commands;
19
- # we use pipes to connect to the command's stdio, and the OS forcibly
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
@@ -1,3 +1,3 @@
1
1
  module Backticks
2
- VERSION = "0.5.0"
2
+ VERSION = "1.0.0rc1"
3
3
  end
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.5.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-06-18 00:00:00.000000000 Z
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: '0'
95
+ version: 1.3.1
96
96
  requirements: []
97
97
  rubyforge_project:
98
98
  rubygems_version: 2.4.5