repl_runner 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +6 -0
- data/LICENSE +1 -0
- data/README.md +80 -0
- data/Rakefile +17 -0
- data/lib/repl_runner.rb +75 -0
- data/lib/repl_runner/config.rb +34 -0
- data/lib/repl_runner/default_config.rb +13 -0
- data/lib/repl_runner/multi_command_parser.rb +34 -0
- data/lib/repl_runner/multi_repl.rb +80 -0
- data/lib/repl_runner/pty_party.rb +51 -0
- data/lib/repl_runner/version.rb +3 -0
- data/repl_runner.gemspec +23 -0
- data/test/config_test.rb +28 -0
- data/test/multi_command_parser_test.rb +12 -0
- data/test/multi_repl_test.rb +13 -0
- data/test/pty_party_test.rb +18 -0
- data/test/repl_runner_test.rb +40 -0
- data/test/require/never-boots.rb +5 -0
- data/test/test_helper.rb +10 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 05eaef051153f7d7b06f9c68c4933a0955d58e6b
|
4
|
+
data.tar.gz: a371ba6c53712e73296da79c69c3f604fa7c841e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7d406e33b664960f9af50c4aea6a6ba3ebfbf3d848193f18732aad7c340f6d3e11c9125786b0b70fddcd72b482f57f90e2a432d6d6d7ea63c879b7739bc2a1a2
|
7
|
+
data.tar.gz: ecaee16aaf62250a286ffb4cf1e0deeda8cbecdaabb7d1b8015a537c6f89e1f68fe067ca2df8e6eefa9e72c5115012a2071db34601b149629ced825dc317a7bb
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
MIT Yo
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# REPL Runner
|
2
|
+
|
3
|
+
Drive irb, bash, or another REPL like environment programatically.
|
4
|
+
|
5
|
+
## Why?
|
6
|
+
|
7
|
+
I needed to be able to run `rails console` commands programatically for testing buildpacks with [Hatchet](http://github.com/heroku/hatchet).
|
8
|
+
|
9
|
+
## How?
|
10
|
+
|
11
|
+
Ruby includes the PTY library for creating and running [pseudo terminals](http://en.wikipediaorg/wiki/Pseudo_terminal). Unfortunately opening up a remote `irb` or `rails console` session and driving it without deadlocking your process is fairly maddeningly difficult. This library provides a safe abstraction for running commands and parsing the inputs from the outputs.
|
12
|
+
|
13
|
+
## Shouldn't this be Called TerminalRunner?
|
14
|
+
|
15
|
+
Well technically it should be called Pseudo Terminal Runner, but ReplRunner just has a certain ring to it.
|
16
|
+
|
17
|
+
## Install
|
18
|
+
|
19
|
+
In your gemfile add:
|
20
|
+
|
21
|
+
```
|
22
|
+
gem 'repl_runner'
|
23
|
+
```
|
24
|
+
|
25
|
+
Then run `$ bundle install`.
|
26
|
+
|
27
|
+
## Use
|
28
|
+
|
29
|
+
To open a remote rails console on heroku with the heroku toolbelt installed, you could drive it like this:
|
30
|
+
|
31
|
+
```
|
32
|
+
ReplRunner.new(:rails_console, "heroku run rails console -a testapp").run do |repl|
|
33
|
+
repl.run('a = 1 + 1') {|result| assert_match '2', result }
|
34
|
+
repl.run('"hello" + "world"') {|result| assert_match 'helloworld', result }
|
35
|
+
repl.run("a * 'foo'") {|result| assert_match 'foofoo', result}
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
The first argument `:rails_console` tells ReplReader what type of a session we are going to open up. The second `"heroku run rails console -a testapp"` is the command we want to use to start our psuedo remote terminal.
|
40
|
+
|
41
|
+
You can then call `run` on this and pass in a block. The block yields to a `MultiRepl` instance that can take the command `run` along with arguments to pass into the command line such as `'1+1'`
|
42
|
+
|
43
|
+
All outputs will be strings. Commands wait for all commands to finish before returning anything, this is why you supply the `run` command with a block. When the command is done the block will be executed and the result of the command passed to it. This helps us parse the results much more effectively. If you need an immediate return, it's possible but I wouldn't recommend it. As a result I've left that functionality out for now.
|
44
|
+
|
45
|
+
Also note that you will get the entire return including any prompts if you run an `irb` session locally with `.9.3` you might see a result like this:
|
46
|
+
|
47
|
+
```
|
48
|
+
$ irb
|
49
|
+
1.9.3p392 :001 > 1 + 1
|
50
|
+
=> 2
|
51
|
+
```
|
52
|
+
|
53
|
+
So when you run this via ReplRunner you will get a result string like this
|
54
|
+
|
55
|
+
```
|
56
|
+
ReplRunner.new(:irb) do |repl|
|
57
|
+
repl.run('1 + 1') {|result| puts result }
|
58
|
+
end
|
59
|
+
" => 2\r\r\n"
|
60
|
+
```
|
61
|
+
|
62
|
+
Note: if you don't pass in a second parameter i.e. only pass in `:irb` that exact command will be used to start your session.
|
63
|
+
|
64
|
+
## Configure
|
65
|
+
|
66
|
+
By default ReplRunner knows how to run `:rails_console`, `:bash`, and `:irb`. You can over-write existing defaults by re-defining them. You can register more custom commands you want like this:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
ReplRunner.register_commands(:rails_console, :irb) do |config|
|
70
|
+
config.terminate_command "exit" # the command you use to end the 'rails console'
|
71
|
+
config.startup_timeout 60 # seconds to boot
|
72
|
+
config.return_char "\n" # the character that submits the command
|
73
|
+
config.sync_stdout "STDOUT.sync = true" # force REPL to not buffer standard out
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
## License
|
78
|
+
|
79
|
+
MIT (The Georgia Tech of the North) License. Do whatever you want with this code: I'm not liable or responsible for anything.
|
80
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
|
7
|
+
task :default => [:test]
|
8
|
+
|
9
|
+
test_task = Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = false
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
STDOUT.sync = true
|
data/lib/repl_runner.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# stdlib
|
2
|
+
require 'timeout'
|
3
|
+
require 'pty'
|
4
|
+
|
5
|
+
# gems
|
6
|
+
require 'active_support/core_ext/object/blank'
|
7
|
+
|
8
|
+
class ReplRunner
|
9
|
+
attr_accessor :command, :repl
|
10
|
+
|
11
|
+
class NoResults < StandardError
|
12
|
+
def initialize(command, string)
|
13
|
+
msg = "No result found for command: #{command.inspect} in output: #{string.inspect}"
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnregisteredCommand < StandardError
|
19
|
+
def initialize(cmd_type)
|
20
|
+
msg = "Cannot find registered command type: #{cmd_type.inspect}"
|
21
|
+
super(msg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(cmd_type, command = nil, options = {})
|
26
|
+
command = cmd_type.to_s if command.nil?
|
27
|
+
cmd_type = cmd_type.chomp.gsub(/\s/, '_').to_sym if cmd_type.is_a?(String)
|
28
|
+
@command = command
|
29
|
+
@repl = nil
|
30
|
+
@config = known_configs[cmd_type]
|
31
|
+
raise UnregisteredCommand.new(cmd_type) unless @config
|
32
|
+
@options = options
|
33
|
+
end
|
34
|
+
|
35
|
+
def known_configs
|
36
|
+
self.class.known_configs
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def known_configs
|
41
|
+
@known_configs ||= {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def register_command(*commands, &block)
|
45
|
+
config = Config.new(commands)
|
46
|
+
commands.each do |command|
|
47
|
+
known_configs[command] = config
|
48
|
+
end
|
49
|
+
yield config
|
50
|
+
end
|
51
|
+
alias :register_commands :register_command
|
52
|
+
end
|
53
|
+
|
54
|
+
def start
|
55
|
+
@repl = MultiRepl.new(command, @config.to_options.merge(@options))
|
56
|
+
end
|
57
|
+
|
58
|
+
def close
|
59
|
+
repl.close
|
60
|
+
end
|
61
|
+
|
62
|
+
def run(&block)
|
63
|
+
repl = start
|
64
|
+
yield repl
|
65
|
+
repl.execute
|
66
|
+
ensure
|
67
|
+
close if repl
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'repl_runner/pty_party'
|
72
|
+
require 'repl_runner/multi_command_parser'
|
73
|
+
require 'repl_runner/multi_repl'
|
74
|
+
require 'repl_runner/config'
|
75
|
+
require 'repl_runner/default_config'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class ReplRunner
|
2
|
+
class Config
|
3
|
+
attr_accessor :commands
|
4
|
+
|
5
|
+
def initialize(*commands)
|
6
|
+
@commands = commands
|
7
|
+
end
|
8
|
+
|
9
|
+
def terminate_command(command)
|
10
|
+
@terminate_command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
def startup_timeout(command)
|
14
|
+
@startup_timeout = command
|
15
|
+
end
|
16
|
+
|
17
|
+
def return_char(char)
|
18
|
+
@return_char = char
|
19
|
+
end
|
20
|
+
|
21
|
+
def sync_stdout(string)
|
22
|
+
@sync_stdout = string
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_options
|
26
|
+
{
|
27
|
+
terminate_command: @terminate_command,
|
28
|
+
startup_timeout: @startup_timeout,
|
29
|
+
return_char: @return_char,
|
30
|
+
sync_stdout: @sync_stdout
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
ReplRunner.register_command(:rails_console, :irb) do |config|
|
2
|
+
config.terminate_command "exit" # the command you use to end the 'rails console'
|
3
|
+
config.startup_timeout 60 # seconds to boot
|
4
|
+
config.return_char "\n" # the character that submits the command
|
5
|
+
config.sync_stdout "STDOUT.sync = true" # force REPL to not buffer standard out
|
6
|
+
end
|
7
|
+
|
8
|
+
ReplRunner.register_command(:bash, :sh) do |config|
|
9
|
+
config.terminate_command "exit" # the command you use to end the 'bash' session
|
10
|
+
config.startup_timeout 60 # seconds to boot
|
11
|
+
config.return_char "\n" # the character that submits the command
|
12
|
+
config.sync_stdout nil # not needed
|
13
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class ReplRunner
|
2
|
+
class MultiCommandParser
|
3
|
+
attr_accessor :commands, :raw
|
4
|
+
|
5
|
+
def initialize(commands, terminate_command = nil)
|
6
|
+
@commands = commands
|
7
|
+
@terminate_command = terminate_command
|
8
|
+
@raw = ""
|
9
|
+
end
|
10
|
+
|
11
|
+
def command_to_regex(command)
|
12
|
+
/#{Regexp.quote(command)}\r*\n+/
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(string)
|
16
|
+
self.raw = string.dup
|
17
|
+
@parsed_result = []
|
18
|
+
|
19
|
+
# remove terminate command
|
20
|
+
string = string.gsub(command_to_regex(@terminate_command), '') if @terminate_command
|
21
|
+
# attack the string from the end
|
22
|
+
commands.reverse.each do |command|
|
23
|
+
regex = command_to_regex(command)
|
24
|
+
result_array = string.split(regex)
|
25
|
+
@parsed_result << result_array.pop
|
26
|
+
raise NoResults.new(command, raw) if @parsed_result.last.blank?
|
27
|
+
string = result_array.join('')
|
28
|
+
end
|
29
|
+
|
30
|
+
@parsed_result.reverse!
|
31
|
+
return @parsed_result
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class ReplRunner
|
2
|
+
# Takes in a command, to start a REPL session with PTY
|
3
|
+
# builds up a list of commands we want to run in our REPL
|
4
|
+
# executes them all at a time, reads in the result and returns results
|
5
|
+
# back to blocks
|
6
|
+
class MultiRepl
|
7
|
+
class UnexpectedExit < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
DEFAULT_STARTUP_TIMEOUT = 60
|
11
|
+
DEFAULT_RETURN_CHAR = "\n"
|
12
|
+
|
13
|
+
attr_accessor :command
|
14
|
+
|
15
|
+
def initialize(command, options = {})
|
16
|
+
@command_parser = options[:command_parser] || MultiCommandParser
|
17
|
+
@return_char = options[:return_char] || DEFAULT_RETURN_CHAR
|
18
|
+
@startup_timeout = options[:startup_timeout] || DEFAULT_STARTUP_TIMEOUT
|
19
|
+
@terminate_command = options[:terminate_command] or raise "must set default `terminate_command`"
|
20
|
+
@stync_stdout = options[:sync_stdout]
|
21
|
+
|
22
|
+
@command = command
|
23
|
+
@commands = []
|
24
|
+
@jobs = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def pty
|
28
|
+
@pty ||= PtyParty.new(command)
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(command, &block)
|
32
|
+
@commands << command
|
33
|
+
@jobs << (block || Proc.new {|result| })
|
34
|
+
end
|
35
|
+
|
36
|
+
def write(command)
|
37
|
+
pty.write("#{command}#{@return_char}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def alive?
|
41
|
+
!!::Process.kill(0, pty.pid) rescue false # kill zero will return the status of the proceess without killing it
|
42
|
+
end
|
43
|
+
|
44
|
+
def dead?
|
45
|
+
!alive?
|
46
|
+
end
|
47
|
+
|
48
|
+
def read
|
49
|
+
raise UnexpectedExit, "Repl: '#{@command}' exited unexpectedly" if dead?
|
50
|
+
@output = pty.read(@startup_timeout)
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_results
|
54
|
+
@parsed_results ||= @command_parser.new(@commands, @terminate_command).parse(read)
|
55
|
+
end
|
56
|
+
|
57
|
+
def close
|
58
|
+
pty.close
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute
|
62
|
+
write(@sync_stdout) if @sync_stdout
|
63
|
+
|
64
|
+
@commands.each do |command|
|
65
|
+
write(command)
|
66
|
+
end
|
67
|
+
|
68
|
+
write(@terminate_command)
|
69
|
+
|
70
|
+
|
71
|
+
output_array = parse_results
|
72
|
+
|
73
|
+
@jobs.each_with_index do |job, index|
|
74
|
+
job.call(output_array[index])
|
75
|
+
end
|
76
|
+
rescue NoResults => e
|
77
|
+
raise e, "Booting up REPL with command: #{command.inspect} \n#{e.message}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class ReplRunner
|
2
|
+
class PtyParty
|
3
|
+
attr_accessor :input, :output, :pid
|
4
|
+
TIMEOUT = 1
|
5
|
+
|
6
|
+
def initialize(command)
|
7
|
+
@output, @input, @pid = PTY.spawn(command)
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(cmd)
|
11
|
+
input.write(cmd)
|
12
|
+
rescue Errno::EIO => e
|
13
|
+
raise e, "#{e.message} | trying to write '#{cmd}'"
|
14
|
+
end
|
15
|
+
|
16
|
+
def run(cmd, timeout = TIMEOUT)
|
17
|
+
write(cmd)
|
18
|
+
return read(timeout)
|
19
|
+
end
|
20
|
+
|
21
|
+
def close(timeout = TIMEOUT)
|
22
|
+
Timeout::timeout(timeout) do
|
23
|
+
input.close
|
24
|
+
output.close
|
25
|
+
end
|
26
|
+
rescue Timeout::Error
|
27
|
+
# do nothing
|
28
|
+
ensure
|
29
|
+
Process.kill('TERM', pid) if pid.present?
|
30
|
+
end
|
31
|
+
|
32
|
+
# There be dragons - (You're playing with process deadlock)
|
33
|
+
#
|
34
|
+
# We want to read the whole output of the command
|
35
|
+
# First pull all contents from stdout (except we don't know how many there are)
|
36
|
+
# So we have to go until our process deadlocks, then we timeout and return the string
|
37
|
+
#
|
38
|
+
def read(timeout = TIMEOUT, str = "")
|
39
|
+
while true
|
40
|
+
Timeout::timeout(timeout) do
|
41
|
+
str << output.readline
|
42
|
+
break if output.eof?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
return str
|
47
|
+
rescue Timeout::Error, EOFError, Errno::EIO
|
48
|
+
return str
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/repl_runner.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/repl_runner/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Richard Schneeman"]
|
6
|
+
gem.email = ["richard@heroku.com"]
|
7
|
+
gem.description = %q{ Programatically drive REPL like interfaces, irb, bash, etc. }
|
8
|
+
gem.summary = %q{ Run your REPL like interfaces like never before}
|
9
|
+
gem.homepage = "https://github.com/schneems/repl_runner"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "repl_runner"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = ReplRunner::VERSION
|
17
|
+
gem.license = 'MIT' # the Georgia Tech of the North
|
18
|
+
|
19
|
+
gem.add_dependency "activesupport"
|
20
|
+
|
21
|
+
gem.add_development_dependency "rake"
|
22
|
+
gem.add_development_dependency "mocha"
|
23
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConfigTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@hash = {terminate_command: "exitYoSelf",
|
7
|
+
startup_timeout: 99,
|
8
|
+
return_char: "poof",
|
9
|
+
sync_stdout: "NSync"}
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_config
|
13
|
+
config = ReplRunner::Config.new(:irb, :rails_console)
|
14
|
+
config.terminate_command @hash[:terminate_command]
|
15
|
+
assert_equal @hash[:terminate_command], config.to_options[:terminate_command]
|
16
|
+
|
17
|
+
config.startup_timeout @hash[:startup_timeout]
|
18
|
+
assert_equal @hash[:startup_timeout], config.to_options[:startup_timeout]
|
19
|
+
|
20
|
+
config.return_char @hash[:return_char]
|
21
|
+
assert_equal @hash[:return_char], config.to_options[:return_char]
|
22
|
+
|
23
|
+
config.sync_stdout @hash[:sync_stdout]
|
24
|
+
assert_equal @hash[:sync_stdout], config.to_options[:sync_stdout]
|
25
|
+
|
26
|
+
assert_equal @hash, config.to_options
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MultiCommandParserTest < Test::Unit::TestCase
|
4
|
+
def test_removes_command_from_string
|
5
|
+
hash = {commands: ["1+1", "'hello' + 'world'"],
|
6
|
+
string: "1+1\r\n => 2 \r\n'hello' + 'world'\r\n => \"helloworld\" \r\n",
|
7
|
+
expect: [" => 2 \r\n", " => \"helloworld\" \r\n"]
|
8
|
+
}
|
9
|
+
cp = ReplRunner::MultiCommandParser.new(hash[:commands])
|
10
|
+
assert_equal hash[:expect], cp.parse(hash[:string])
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class MultiReplTest < Test::Unit::TestCase
|
4
|
+
def test_local_irb_stream
|
5
|
+
repl = ReplRunner::MultiRepl.new('irb', terminate_command: 'exit')
|
6
|
+
repl.run('111+111') {|r| assert_match '222', r }
|
7
|
+
repl.run("'hello' + 'world'") {|r| assert_match 'helloworld', r }
|
8
|
+
repl.run("a = 'foo'")
|
9
|
+
repl.run("b = 'bar'") {} # test empty block doesn't throw exceptions
|
10
|
+
repl.run("a * 5") {|r| assert_match 'foofoofoofoofoo', r }
|
11
|
+
repl.execute
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class StreamExecTest < Test::Unit::TestCase
|
4
|
+
def test_local_irb_stream
|
5
|
+
repl = ReplRunner::PtyParty.new("irb")
|
6
|
+
repl.run("STDOUT.sync = true\n")
|
7
|
+
assert_equal "1+1\r\n => 2 \r\n", repl.run("1+1\n")
|
8
|
+
assert_equal "'hello' + 'world'\r\n => \"helloworld\" \r\n", repl.run("'hello' + 'world'\n")
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_multi_command_read
|
12
|
+
repl = ReplRunner::PtyParty.new("irb")
|
13
|
+
repl.write("STDOUT.sync = true\n")
|
14
|
+
repl.write("1+1\n")
|
15
|
+
repl.write("exit\n")
|
16
|
+
assert_equal "STDOUT.sync = true\r\n1+1\r\nexit\r\n2.0.0-p0 :001 > STDOUT.sync = true\r\n => true \r\n2.0.0-p0 :002 > 1+1\r\n => 2 \r\n2.0.0-p0 :003 > exit\r\n", repl.read
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ReplRunnerTest < Test::Unit::TestCase
|
4
|
+
def test_local_irb_stream
|
5
|
+
ReplRunner.new(:irb).run do |repl|
|
6
|
+
repl.run('111+111') {|r| assert_match '222', r }
|
7
|
+
repl.run("'hello' + 'world'") {|r| assert_match 'helloworld', r }
|
8
|
+
repl.run("a = 'foo'")
|
9
|
+
repl.run("b = 'bar'") {} # test empty block doesn't throw exceptions
|
10
|
+
repl.run("a * 5") {|r| assert_match 'foofoofoofoofoo', r }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_ensure_exit
|
15
|
+
assert_raise(ReplRunner::NoResults) do
|
16
|
+
ReplRunner.new(:irb, "irb -r ./test/require/never-boots.rb", startup_timeout: 2).run do |repl|
|
17
|
+
repl.run('111+111') {|r| }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_bash
|
23
|
+
ReplRunner.new(:bash).run do |repl|
|
24
|
+
repl.run('ls') {|r| assert_match /Gemfile/, r}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_unknown_command
|
29
|
+
assert_raise ReplRunner::UnregisteredCommand do
|
30
|
+
ReplRunner.new(:zootsuite)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_heroku
|
35
|
+
# ReplRunner.new('rails console', "heroku run rails console -a test-app-1372231309-0734751").run do |repl|
|
36
|
+
# repl.run("'foo' * 5") {|r| assert_match /foofoofoofoofoo/, r }
|
37
|
+
# repl.run("'hello ' + 'world'") {|r| assert_match /hello world/, r }
|
38
|
+
# end
|
39
|
+
end
|
40
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: repl_runner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Schneeman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mocha
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: ' Programatically drive REPL like interfaces, irb, bash, etc. '
|
56
|
+
email:
|
57
|
+
- richard@heroku.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- .gitignore
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/repl_runner.rb
|
68
|
+
- lib/repl_runner/config.rb
|
69
|
+
- lib/repl_runner/default_config.rb
|
70
|
+
- lib/repl_runner/multi_command_parser.rb
|
71
|
+
- lib/repl_runner/multi_repl.rb
|
72
|
+
- lib/repl_runner/pty_party.rb
|
73
|
+
- lib/repl_runner/version.rb
|
74
|
+
- repl_runner.gemspec
|
75
|
+
- test/config_test.rb
|
76
|
+
- test/multi_command_parser_test.rb
|
77
|
+
- test/multi_repl_test.rb
|
78
|
+
- test/pty_party_test.rb
|
79
|
+
- test/repl_runner_test.rb
|
80
|
+
- test/require/never-boots.rb
|
81
|
+
- test/test_helper.rb
|
82
|
+
homepage: https://github.com/schneems/repl_runner
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.0.2
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: Run your REPL like interfaces like never before
|
106
|
+
test_files:
|
107
|
+
- test/config_test.rb
|
108
|
+
- test/multi_command_parser_test.rb
|
109
|
+
- test/multi_repl_test.rb
|
110
|
+
- test/pty_party_test.rb
|
111
|
+
- test/repl_runner_test.rb
|
112
|
+
- test/require/never-boots.rb
|
113
|
+
- test/test_helper.rb
|