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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a446a43c0be0d08f7ad35f3f9a2fa7791f66229d
4
- data.tar.gz: 2fc8f0e3176dae7628cb08225c63f95d97002e8f
3
+ metadata.gz: 51c5bc30064cde134337b459ec829f6099a65c21
4
+ data.tar.gz: 950628c7a317d251eca8bc29c89cf474259b2f63
5
5
  SHA512:
6
- metadata.gz: 96d973bd4ef27a7bfcfecf7f8cb9e7ea0de2bb05f758aa42fb49c5438ba372230c5a77d07737da4e9492c0109533992ca9f1ad9ccf779fe442e7907d8bd5a7b7
7
- data.tar.gz: 378d42242e3efbc5817c89266a34e620ca59ddfe6fc92411b56671fe5aa7104088a5f934235f8b1f9dbcb66c589ef71f8c1c1904f5a755074392317817c59172
6
+ metadata.gz: 6887deef8f9ce8f4c1c998951b135a78e567a6244eaa9b80cd132fbb22d7700bc1c64470b010cfe9cd91b10a4dcc9432cf0bd4b9a5df5fbc9d73e365217f9b66
7
+ data.tar.gz: cdb8c7c4e9d7776592921ca11c29c930c3d1687ecf82a0058ffc8088d14c0482fcec31dfac9fa56853807984834fd2d9c093cf0c5f82ab6898a58955eff695c7
@@ -1,7 +1,8 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 1.9
4
+ - 2.0
5
+ - 2.1
5
6
  - 2.2
6
7
  before_install: gem install bundler -v '~> 1.10'
7
8
  script: bundle exec rake spec
@@ -0,0 +1,2 @@
1
+ 1.0
2
+ ===
data/Gemfile CHANGED
@@ -3,6 +3,11 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in backticks.gemspec
4
4
  gemspec
5
5
 
6
+ group :development do
7
+ gem 'pry'
8
+ gem 'pry-byebug'
9
+ end
10
+
6
11
  group :test do
7
12
  gem 'coveralls', require: false
8
13
  end
data/README.md CHANGED
@@ -1,15 +1,14 @@
1
- # Introduction
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" and a modified string is captured in place of the original.
116
+ nil means "discard," any String means "use this in place of the original input
117
+ or output.""
116
118
 
117
- Try loading the README in an editor with all of the vowels scrambled. Scroll
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
@@ -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
- # Time value that is used internally when a user is willing to wait
10
- # "forever" for the command.
11
- #
12
- # Using a definite time-value helps simplify the looping logic internally,
13
- # but it does mean that this class will stop working in February of 2106.
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
- def initialize(pid, stdin, stdout, stderr)
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
- !@stdin.nil?
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, pass user input to the command and print its output
66
- # to Ruby's output streams. If the time limit expires, return `nil`;
67
- # otherwise, return self.
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=nil)
92
+ def join(limit=FOREVER)
71
93
  return self if @status
72
94
 
73
- if limit
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 - t)
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
- if limit
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)
@@ -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
- # This may be a Boolean, or it may be an Array of stream names from the
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: if you set `interactive` to true, then stdin and stdout will be
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
- pid = spawn(*argv, in: stdin_r, out: stdout_w, err: stderr_w)
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
@@ -1,3 +1,3 @@
1
1
  module Backticks
2
- VERSION = "1.0.0rc1"
2
+ VERSION = "1.0.0rc2"
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: 1.0.0rc1
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-07-29 00:00:00.000000000 Z
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