backticks 0.5.0 → 1.0.0rc1

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 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