expectr 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "rdoc", "~> 3.12"
11
+ gem "bundler", "~> 1.0.0"
12
+ gem "jeweler", "~> 1.8.3"
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Chris Wuest
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = expectr
2
+
3
+ Expect (see http://expect.nist.gov/) implementation in Ruby. This version differs from the included Expect module by providing a more granular method of managing process, output, and implementing Expect's "interact" command.
4
+
5
+ == Contributing to expectr
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2012 Chris Wuest. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = "expectr"
17
+ gem.homepage = "http://github.com/cwuest/expectr"
18
+ gem.license = "MIT"
19
+ gem.summary = %Q{Expect for Ruby}
20
+ gem.description = %Q{Implementation of NIST Expect in Ruby, allowing for fine-grained control of several aspects of program flow and implementing "interact" mode.}
21
+ gem.email = "chris.wuest@rackspace.com"
22
+ gem.authors = ["Chris Wuest"]
23
+ # dependencies defined in Gemfile
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rake/testtask'
28
+ Rake::TestTask.new(:test) do |test|
29
+ test.libs << 'lib' << 'test'
30
+ test.test_files = Dir.glob('test/**/test_*.rb')
31
+ #test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ task :default => :test
36
+
37
+ require 'rdoc/task'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "expectr #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.0
data/lib/expectr.rb ADDED
@@ -0,0 +1,265 @@
1
+ # = expectr.rb
2
+ #
3
+ # Copyright (c) Chris Wuest <chris@chriswuest.com>
4
+ # Expectr is freely distributable under the terms of an MIT-style license.
5
+ # See COPYING or http://www.opensource.org/licenses/mit-license.php.
6
+
7
+ begin
8
+ require 'pty'
9
+ rescue LoadError
10
+ require 'popen4'
11
+ end
12
+
13
+ require 'timeout'
14
+ require 'thread'
15
+
16
+ # == Description
17
+ # Expectr is an implementation of the Expect library in ruby (see
18
+ # http://expect.nist.gov).
19
+ #
20
+ # Expectr contrasts with Ruby's built-in Expect class by avoiding tying in
21
+ # with IO and instead creating a new object entirely to allow for more
22
+ # fine-grained control over the execution and display of the program being
23
+ # run.
24
+ #
25
+ # == Examples
26
+ # === Simple task automation
27
+ #
28
+ # Connect via telnet to remote.example.com, run my_command, and return the
29
+ # output
30
+ #
31
+ # exp = Expectr.new "telnet remote.example.com"
32
+ # exp.expect "username:"
33
+ # exp.send "example\r"
34
+ # exp.expect "password:"
35
+ # exp.send "my_password\r"
36
+ # exp.expect "%"
37
+ # exp.send "my_command\r"
38
+ # exp.expect "%"
39
+ # exp.send "logout"
40
+ #
41
+ # output = exp.discard
42
+ #
43
+ # === Interactive control
44
+ # Silently connect via ssh to remote.example.com, log in automatically, then
45
+ # relinquish control to the user. Expect slow networking, so increase
46
+ # timeout.
47
+ #
48
+ # exp = Expectr.new "ssh remote.example.com", :timeout=>45, :flush_buffer=>false
49
+ #
50
+ # match = exp.expect /password|yes\/no/
51
+ # case match.to_s
52
+ # when /password/
53
+ # exp.send "my_password\r"
54
+ # when /yes\/no/
55
+ # exp.send "yes\r"
56
+ # exp.expect /password/
57
+ # exp.send "my_password\r"
58
+ # else
59
+ # puts "Cannot connect to remote.example.com!"
60
+ # die
61
+ # end
62
+ #
63
+ # exp.expect "$"
64
+ # exp.interact
65
+ #
66
+ class Expectr
67
+ # Amount of time in seconds a call to +expect+ may last (default 30)
68
+ attr_accessor :timeout
69
+ # Size of buffer in bytes to attempt to read in at once (default 8 KiB)
70
+ attr_accessor :buffer_size
71
+ # Whether to flush program output to STDOUT (default true)
72
+ attr_accessor :flush_buffer
73
+ # PID of running process
74
+ attr_reader :pid
75
+ # Active buffer to match against
76
+ attr_reader :buffer
77
+ # Buffer passed since last +expect+ match
78
+ attr_reader :discard
79
+
80
+ #
81
+ # === Synopsis
82
+ #
83
+ # Expectr.new(cmd, args)
84
+ #
85
+ # === Arguments
86
+ # +cmd+::
87
+ # Command to be executed (String)
88
+ # +args+::
89
+ # Hash of modifiers for Expectr. Meaningful values are:
90
+ # * :buffer_size::
91
+ # Amount of data to read at a time. Default 8 KiB
92
+ # * :flush_buffer::
93
+ # Flush buffer to STDOUT during execution? Default true
94
+ # * :timeout::
95
+ # Timeout in seconds for each +expect+ call. Default 30
96
+ #
97
+ # === Description
98
+ #
99
+ # Spawn +cmd+ and attach to STDIN and STDOUT for new process. Fall back
100
+ # to using Open4 if PTY is not present (this is the case on Windows
101
+ # implementations of ruby.
102
+ #
103
+ # === BUGS
104
+ #
105
+ # * Passing a File object to be executed does not work
106
+ #
107
+ def initialize(cmd, args={})
108
+ raise ArgumentError, "String Expected" unless cmd.kind_of?(String)
109
+ args[0] = {} unless args[0]
110
+ @buffer = String.new
111
+ @discard = String.new
112
+ @timeout = args[:timeout] || 30
113
+ @flush_buffer = args[:flush_buffer].nil? ? true : args[:flush_buffer]
114
+ @buffer_size = args[:buffer_size] || 8192
115
+ @out_mutex = Mutex.new
116
+ @out_update = false
117
+
118
+ [@buffer, @discard].each {|x| x.encode! "UTF-8" }
119
+
120
+ if defined? PTY
121
+ @stdout,@stdin,@pid = PTY.spawn cmd
122
+ else
123
+ cmd << " 2>&1" if cmd[/2\s*>/].nil?
124
+ @pid, @stdin, @stdout, stderr = Open4::popen4 cmd
125
+ end
126
+
127
+ Thread.new do
128
+ while @pid > 0
129
+ unless select([@stdout], nil, nil, @timeout).nil?
130
+ buf = ''
131
+
132
+ begin
133
+ @stdout.sysread(@buffer_size, buf)
134
+ rescue Errno::EIO #Application went away.
135
+ Process.wait @pid
136
+ @pid = 0
137
+ break
138
+ end
139
+
140
+ buf.encode! "UTF-8"
141
+ print_buffer buf
142
+
143
+ @out_mutex.synchronize do
144
+ @buffer << buf
145
+ @out_update = true
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ #
153
+ # Clear output buffer
154
+ #
155
+ def clear_buffer
156
+ @out_mutex.synchronize do
157
+ @buffer = ''
158
+ @out_update = false
159
+ end
160
+ end
161
+
162
+ #
163
+ # === Synopsis
164
+ #
165
+ # Expectr#interact
166
+ #
167
+ # === Description
168
+ #
169
+ # Relinquish control of the running process to the controlling terminal,
170
+ # acting simply as a pass-through for the life of the process.
171
+ #
172
+ # === Bugs
173
+ #
174
+ # * Interrupts are not handled and passed through to the application
175
+ #
176
+ def interact
177
+ @flush_buffer = true
178
+ old_tty = `stty -g`
179
+ `stty -icanon min 1 time 0 -echo`
180
+
181
+ in_thread = Thread.new do
182
+ input = ''
183
+ while @pid > 0
184
+ if select([STDIN], nil, nil, 1)
185
+ @stdin.syswrite(STDIN.getc)
186
+ end
187
+ end
188
+ end
189
+
190
+ in_thread.join
191
+ `stty #{old_tty}`
192
+ return nil
193
+ end
194
+
195
+ #
196
+ # Send +str+ to application
197
+ #
198
+ def send(str)
199
+ @stdin.syswrite str
200
+ end
201
+
202
+ #
203
+ # === Synopsis
204
+ #
205
+ # Expectr#expect /regexp/, recoverable=false
206
+ # Expectr#expect "String", recoverable=true
207
+ #
208
+ # === Arguments
209
+ #
210
+ # +pattern+::
211
+ # String or regexp to match against
212
+ # +recoverable+::
213
+ # Determines if execution can continue after a timeout
214
+ #
215
+ # === Description
216
+ #
217
+ # Wait +timeout+ seconds to match +pattern+ in +buffer+. If timeout is
218
+ # reached, raise an error unless +recoverable+ is true.
219
+ #
220
+ # === Bugs
221
+ #
222
+ # * Interrupts are not handled and passed through to the application
223
+ #
224
+ def expect(pattern, recoverable = false)
225
+ match = nil
226
+
227
+ case pattern
228
+ when String
229
+ pattern = Regexp.new(Regexp.quote(pattern))
230
+ when Regexp
231
+ else raise TypeError, "Pattern class should be String or Regexp, passed: #{pattern.class}"
232
+ end
233
+
234
+ begin
235
+ Timeout::timeout(@timeout) do
236
+ while match.nil?
237
+ if @out_update
238
+ @out_mutex.synchronize do
239
+ match = pattern.match @buffer
240
+ @out_update = false
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ @out_mutex.synchronize do
247
+ @discard = @buffer[0..match.begin(0)-1]
248
+ @buffer = @buffer[match.end(0)..-1]
249
+ @out_update = true
250
+ end
251
+ rescue Timeout::Error => details
252
+ raise details unless recoverable
253
+ end
254
+
255
+ return match
256
+ end
257
+
258
+ #
259
+ # Print buffer to STDOUT only if +flush_buffer+ is true
260
+ #
261
+ def print_buffer(buf)
262
+ print buf if @flush_buffer
263
+ STDOUT.flush
264
+ end
265
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'expectr'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,59 @@
1
+ require 'helper'
2
+
3
+ class TestExpectr < Test::Unit::TestCase
4
+ def setup
5
+ @exp = Expectr.new "ls /bin", :flush_buffer => false, :timeout => 2, :buffer_size => 4096
6
+ end
7
+
8
+ def test_execution
9
+ assert_equal @exp.flush_buffer, false
10
+ assert_equal @exp.timeout, 2
11
+ assert_equal @exp.buffer_size, 4096
12
+ end
13
+
14
+ def test_match
15
+ assert_not_equal @exp.expect(/sh/), nil
16
+ assert_not_equal @exp.discard, ''
17
+ end
18
+
19
+ def test_match_failure
20
+ assert_raises(Timeout::Error) { @exp.expect /ThisFileShouldNotExist/ }
21
+ assert_nothing_raised { @exp.expect /ThisFileShouldNotExist/, true }
22
+ end
23
+
24
+ def test_send
25
+ exp = Expectr.new "bc", :flush_buffer => false
26
+ exp.send "20+301\n"
27
+ exp.expect /321/
28
+ end
29
+
30
+ def test_clear_buffer
31
+ sleep 1
32
+ assert_not_equal @exp.buffer, ''
33
+ @exp.clear_buffer
34
+ assert_equal @exp.buffer, ''
35
+ end
36
+
37
+ def test_pid_set
38
+ assert @exp.pid > 0
39
+ end
40
+
41
+ def test_interact
42
+ exp = Expectr.new "bc", :flush_buffer => false
43
+ [
44
+ Thread.new {
45
+ sleep 1
46
+ exp.interact
47
+ },
48
+ Thread.new {
49
+ sleep 2
50
+ assert_equal exp.flush_buffer, true
51
+ exp.flush_buffer = false
52
+ exp.send "300+21\n"
53
+ exp.send "quit\n"
54
+ }
55
+ ].each {|x| x.join}
56
+
57
+ assert_not_nil exp.expect /321/
58
+ end
59
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: expectr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Wuest
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: shoulda
16
+ requirement: &69922000513180 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *69922000513180
25
+ - !ruby/object:Gem::Dependency
26
+ name: rdoc
27
+ requirement: &69922000512700 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *69922000512700
36
+ - !ruby/object:Gem::Dependency
37
+ name: bundler
38
+ requirement: &69922000512220 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.0.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *69922000512220
47
+ - !ruby/object:Gem::Dependency
48
+ name: jeweler
49
+ requirement: &69922000511740 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.8.3
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *69922000511740
58
+ description: Implementation of NIST Expect in Ruby, allowing for fine-grained control
59
+ of several aspects of program flow and implementing "interact" mode.
60
+ email: chris.wuest@rackspace.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files:
64
+ - LICENSE.txt
65
+ - README.rdoc
66
+ files:
67
+ - .document
68
+ - Gemfile
69
+ - LICENSE.txt
70
+ - README.rdoc
71
+ - Rakefile
72
+ - VERSION
73
+ - lib/expectr.rb
74
+ - test/helper.rb
75
+ - test/test_expectr.rb
76
+ homepage: http://github.com/cwuest/expectr
77
+ licenses:
78
+ - MIT
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ segments:
90
+ - 0
91
+ hash: 1045150250599227842
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ requirements: []
99
+ rubyforge_project:
100
+ rubygems_version: 1.8.10
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Expect for Ruby
104
+ test_files: []