pseudo-terminal 0.0.2
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.
- data/README.md +32 -0
- data/bin/pseudo-terminal +6 -0
- data/lib/pseudo-terminal.rb +4 -0
- data/lib/pseudo-terminal/buffer.rb +58 -0
- data/lib/pseudo-terminal/client.rb +68 -0
- data/lib/pseudo-terminal/string.rb +16 -0
- data/lib/pseudo-terminal/version.rb +3 -0
- data/spec/pseudo-terminal/buffer_spec.rb +50 -0
- data/spec/pseudo-terminal/client_spec.rb +45 -0
- data/spec/pseudo-terminal/string_spec.rb +30 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/support/display_message_matcher.rb +49 -0
- metadata +58 -0
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Pseudo Terminal
|
2
|
+
===============
|
3
|
+
|
4
|
+
[](http://travis-ci.org/wenzowski/pseudo-terminal)
|
5
|
+
|
6
|
+
This library wraps PTY to ease use of pseudo terminals on unix-based operating systems.
|
7
|
+
|
8
|
+
Sample Workflow
|
9
|
+
---------------
|
10
|
+
|
11
|
+
Create a new pseudo terminal, write a command, process result, and close the process:
|
12
|
+
|
13
|
+
require 'pseudo-terminal'
|
14
|
+
pt = PseudoTerminal.new # Create a new pseudo terminal.
|
15
|
+
puts pt << 'pwd' # Write command & print result.
|
16
|
+
pt.put('pwd') {|l| puts l} # Write command & print each line when it appears on the pipe.
|
17
|
+
pt.close # Close pseudo terminal and halt process.
|
18
|
+
|
19
|
+
|
20
|
+
Setup
|
21
|
+
-----
|
22
|
+
|
23
|
+
gem install pseudo-terminal
|
24
|
+
|
25
|
+
|
26
|
+
Meta
|
27
|
+
----
|
28
|
+
|
29
|
+
Created by Alexander Wenzowski
|
30
|
+
|
31
|
+
|
32
|
+
Released under the [MIT license](http://www.opensource.org/licenses/mit-license.php).
|
data/bin/pseudo-terminal
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pseudo-terminal/string'
|
2
|
+
|
3
|
+
class PseudoTerminal::Buffer
|
4
|
+
attr_accessor :masks, :raw, :lines, :segment
|
5
|
+
|
6
|
+
def initialize str_ready, masks=[]
|
7
|
+
@raw = ''
|
8
|
+
@segment = ''
|
9
|
+
@lines = []
|
10
|
+
@buff_a = []
|
11
|
+
@masks = masks
|
12
|
+
@ready = str_ready.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def << str
|
16
|
+
buffer = str.to_s
|
17
|
+
@raw << buffer
|
18
|
+
process_raw(buffer)
|
19
|
+
# lines
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def process_raw buffer
|
25
|
+
b = lines_from_raw(buffer)
|
26
|
+
b.each {|l| @lines << l}
|
27
|
+
end
|
28
|
+
|
29
|
+
def lines_from buffer
|
30
|
+
buffer.lines.to_a.each {|l| begin l.chomp! end while l.end_with_any? "\r\n".chars }
|
31
|
+
end
|
32
|
+
|
33
|
+
def lines_from_raw buffer
|
34
|
+
lines = lines_from buffer
|
35
|
+
lines = merge_line(lines) if !@segment.empty?
|
36
|
+
@segment << lines.pop if truncated
|
37
|
+
lines.each {|line| line.strip_ansi_escape_sequences!}
|
38
|
+
lines.each_with_index {|line, key| lines.delete_at key if line.match /^#{@ready}/}
|
39
|
+
@masks.each do |mask|
|
40
|
+
lines.each_with_index {|line, key| lines.delete_at key if line.match /^#{mask}/}
|
41
|
+
end
|
42
|
+
lines
|
43
|
+
end
|
44
|
+
|
45
|
+
def new_line
|
46
|
+
"\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
def truncated
|
50
|
+
!@raw.end_with?(new_line) && !@raw.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def merge_line buff_a
|
54
|
+
buff_a.first.prepend! @segment if buff_a.first
|
55
|
+
@segment = ''
|
56
|
+
buff_a
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'pty'
|
2
|
+
require 'pseudo-terminal/buffer'
|
3
|
+
|
4
|
+
module PseudoTerminal::Client
|
5
|
+
attr_accessor :r, :w, :b, :timeout
|
6
|
+
|
7
|
+
def initialize opt={}
|
8
|
+
opt[:ready] ||= '> '
|
9
|
+
opt[:sh] ||= "env PS1='#{opt[:ready]}' TERM=dumb sh -i"
|
10
|
+
opt[:timeout] ||= 1
|
11
|
+
@timeout = opt[:timeout]
|
12
|
+
@r, @w, @pid = PTY.spawn opt[:sh]
|
13
|
+
@b = PseudoTerminal::Buffer.new opt[:ready]
|
14
|
+
read
|
15
|
+
@b.masks << @b.lines.pop while @b.lines.empty? == false
|
16
|
+
end
|
17
|
+
|
18
|
+
def << command, &block
|
19
|
+
put command, &block
|
20
|
+
end
|
21
|
+
|
22
|
+
def put command, &block
|
23
|
+
@w.puts command
|
24
|
+
read &block
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_running?
|
28
|
+
begin
|
29
|
+
Process.getpgid @pid
|
30
|
+
true
|
31
|
+
rescue Errno::ESRCH
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def kill!
|
37
|
+
@r.close
|
38
|
+
@w.close
|
39
|
+
begin
|
40
|
+
Process.wait @pid
|
41
|
+
rescue PTY::ChildExited
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def read &block
|
46
|
+
begin
|
47
|
+
read_loop &block
|
48
|
+
rescue IO::WaitReadable
|
49
|
+
t = IO.select([@r], nil, nil, @timeout)
|
50
|
+
retry unless t.nil?
|
51
|
+
@b.lines
|
52
|
+
rescue IOError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def read_loop &block
|
60
|
+
begin
|
61
|
+
if block_given?
|
62
|
+
(@b << @r.read_nonblock(1024)).each {|line| yield(line)}
|
63
|
+
else
|
64
|
+
@b << @r.read_nonblock(1024)
|
65
|
+
end
|
66
|
+
end while true
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class String
|
2
|
+
def prepend! s
|
3
|
+
self.insert 0, s
|
4
|
+
end
|
5
|
+
|
6
|
+
def end_with_any? arr
|
7
|
+
r = false
|
8
|
+
arr.each {|str| r = true if self.end_with? str }
|
9
|
+
r
|
10
|
+
end
|
11
|
+
|
12
|
+
def strip_ansi_escape_sequences!
|
13
|
+
self.gsub!(/\e\]\d;(.*)\a/, '')
|
14
|
+
self.gsub!(/\e\[[^m]*m/, '')
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "pseudo-terminal/buffer"
|
3
|
+
|
4
|
+
describe PseudoTerminal::Buffer do
|
5
|
+
it 'should be instantiated without arguments' do
|
6
|
+
lambda { t = PseudoTerminal::Buffer.new(ready='> ') }.should_not raise_error
|
7
|
+
lambda { t = PseudoTerminal::Buffer.new(ready='', mask=[]) }.should_not raise_error
|
8
|
+
lambda { t = PseudoTerminal::Buffer.new }.should raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'when string appended to buffer' do
|
12
|
+
before :each do
|
13
|
+
@ready='> '
|
14
|
+
@b = PseudoTerminal::Buffer.new @ready
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is empty' do
|
18
|
+
(@b << '').should be_kind_of(Array)
|
19
|
+
(@b << '').should be_empty
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is ready' do
|
23
|
+
(@b << @ready).should be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has masked lines' do
|
27
|
+
@b.masks = ['error line 1', 'error line 2']
|
28
|
+
masks_str = ''
|
29
|
+
@b.masks.each {|mask| masks_str << "#{mask}\r\n"}
|
30
|
+
(@b << masks_str).should == []
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'contains newline (\r\n)' do
|
34
|
+
(@b << "str\r\n").should == ['str']
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'is truncated' do
|
38
|
+
(@b << '1').should be_empty
|
39
|
+
(@b << '2').should be_empty
|
40
|
+
(@b << "3\r\n").should == ['123']
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'lines are stored' do
|
44
|
+
(@b << "a\r\nb\r\nc\r\n").should == ['a','b','c']
|
45
|
+
(@b << "1\r\n2\r\n3\r\n").should == ['1','2','3']
|
46
|
+
@b.lines.should == ['a','b','c','1','2','3']
|
47
|
+
@b.raw.should == "a\r\nb\r\nc\r\n1\r\n2\r\n3\r\n"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "pseudo-terminal"
|
3
|
+
|
4
|
+
describe PseudoTerminal do
|
5
|
+
it 'should be instantiated without failures' do
|
6
|
+
lambda { t = PseudoTerminal.new; t.kill! }.should_not raise_error
|
7
|
+
lambda { t = PseudoTerminal.new({}); t.kill! }.should_not raise_error
|
8
|
+
lambda { t = PseudoTerminal.new(1); t.kill! }.should raise_error
|
9
|
+
lambda { t = PseudoTerminal.new(''); t.kill! }.should raise_error
|
10
|
+
lambda { t = PseudoTerminal.new([]); t.kill! }.should raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'terminal' do
|
14
|
+
t = PseudoTerminal.new
|
15
|
+
it 'is running' do
|
16
|
+
t.is_running?.should be_true
|
17
|
+
end
|
18
|
+
it 'is killed' do
|
19
|
+
t.kill!
|
20
|
+
t.is_running?.should be_false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when spawned terminal' do
|
25
|
+
before :each do
|
26
|
+
@t = PseudoTerminal.new timeout: 0.1
|
27
|
+
end
|
28
|
+
|
29
|
+
after :each do
|
30
|
+
@t.kill!
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has not been written to' do
|
34
|
+
@t.read.should be_empty
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has a command written to it' do
|
38
|
+
(@t << 'pwd').first.should == Dir.pwd
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has a command written to it while a block is passed to it' do
|
42
|
+
@t.put('pwd') {|line| line.should == Dir.pwd}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "pseudo-terminal/string"
|
3
|
+
|
4
|
+
describe String do
|
5
|
+
it 'string.prepend!' do
|
6
|
+
s = 'def'
|
7
|
+
s.prepend! 'abc'
|
8
|
+
s.should == 'abcdef'
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'does str end with' do
|
12
|
+
it 'null' do
|
13
|
+
'abc'.end_with_any?([]).should == false
|
14
|
+
''.end_with_any?(['abc']).should == false
|
15
|
+
end
|
16
|
+
it 'single-character' do
|
17
|
+
'abc'.end_with_any?(['c']).should == true
|
18
|
+
'abc'.end_with_any?(['b']).should == false
|
19
|
+
end
|
20
|
+
it 'multi-character string' do
|
21
|
+
'abc'.end_with_any?(['bc']).should == true
|
22
|
+
'abc'.end_with_any?(['ab']).should == false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
it 'strips ansi escape codes from string' do
|
26
|
+
s = "\e[34mtesting\e[39;49m\e[0m/\r\n\e]7;file://Alexanders-MacBook-Pro.local/private/tmp/refinerycms\a\e]7;file://Alexanders-MacBook-Pro.local/private/tmp/refinerycms\a\e[0;31m4805\e[0m \xE2\x98\x85 \e[1;30m01:13\e[0m \xE2\x98\x85 \e[0;32muser\e[0m@\e[0;32mdomain.com\e[0m:\e[0;36m~/code/pseudo-terminal\e[0m (\e[1;33mmaster\e[0m) \r\r\n\xC2\xBB "
|
27
|
+
s.strip_ansi_escape_sequences!
|
28
|
+
s.should == "testing/\r\n4805 \xE2\x98\x85 01:13 \xE2\x98\x85 user@domain.com:~/code/pseudo-terminal (master) \r\r\n\xC2\xBB "
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bundler/setup"
|
3
|
+
|
4
|
+
require "simplecov"
|
5
|
+
SimpleCov.start do
|
6
|
+
add_filter "/spec/"
|
7
|
+
end
|
8
|
+
|
9
|
+
require "rspec"
|
10
|
+
require "rr"
|
11
|
+
require "support/display_message_matcher"
|
12
|
+
|
13
|
+
RSpec.configure do |config|
|
14
|
+
config.color_enabled = true
|
15
|
+
config.include DisplayMessageMatcher
|
16
|
+
config.after { RR.reset }
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module DisplayMessageMatcher
|
2
|
+
|
3
|
+
def display_message(command, message)
|
4
|
+
DisplayMessageMatcher::DisplayMessage.new command, message
|
5
|
+
end
|
6
|
+
|
7
|
+
class DisplayMessage
|
8
|
+
def initialize(command, message)
|
9
|
+
@command = command
|
10
|
+
@message = message
|
11
|
+
end
|
12
|
+
|
13
|
+
def matches?(given_proc)
|
14
|
+
displayed_expected_message = false
|
15
|
+
@given_messages = []
|
16
|
+
|
17
|
+
@command.should_receive(:display).
|
18
|
+
any_number_of_times do |message, newline|
|
19
|
+
@given_messages << message
|
20
|
+
displayed_expected_message = displayed_expected_message ||
|
21
|
+
message == @message
|
22
|
+
end
|
23
|
+
|
24
|
+
given_proc.call
|
25
|
+
|
26
|
+
displayed_expected_message
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure_message
|
30
|
+
"expected #{ @command } to display the message #{ @message.inspect } but #{ given_messages }"
|
31
|
+
end
|
32
|
+
|
33
|
+
def negative_failure_message
|
34
|
+
"expected #{ @command } to not display the message #{ @message.inspect } but it was displayed"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def given_messages
|
40
|
+
if @given_messages.empty?
|
41
|
+
'no messages were displayed'
|
42
|
+
else
|
43
|
+
formatted_given_messages = @given_messages.map(&:inspect).join ', '
|
44
|
+
"the follow messages were displayed: #{ formatted_given_messages }"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pseudo-terminal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alexander Wenzowski
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-23 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Pseudo terminal library to ease interaction with PTY.
|
15
|
+
email: alexander@wenzowski.com
|
16
|
+
executables:
|
17
|
+
- pseudo-terminal
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- bin/pseudo-terminal
|
23
|
+
- lib/pseudo-terminal.rb
|
24
|
+
- lib/pseudo-terminal/buffer.rb
|
25
|
+
- lib/pseudo-terminal/client.rb
|
26
|
+
- lib/pseudo-terminal/string.rb
|
27
|
+
- lib/pseudo-terminal/version.rb
|
28
|
+
- spec/pseudo-terminal/buffer_spec.rb
|
29
|
+
- spec/pseudo-terminal/client_spec.rb
|
30
|
+
- spec/pseudo-terminal/string_spec.rb
|
31
|
+
- spec/spec.opts
|
32
|
+
- spec/spec_helper.rb
|
33
|
+
- spec/support/display_message_matcher.rb
|
34
|
+
homepage: http://github.com/wenzowski/pseudo-terminal
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.6
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: Pseudo terminal built on PTY.
|
58
|
+
test_files: []
|