dietrb 0.4.7 → 0.5.0
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/LICENSE +11 -5
- data/Rakefile +13 -9
- data/TODO +1 -0
- data/bin/dietrb +49 -35
- data/dietrb.gemspec +21 -11
- data/lib/irb.rb +0 -3
- data/lib/irb/completion.rb +1 -1
- data/lib/irb/context.rb +21 -58
- data/lib/irb/driver.rb +61 -0
- data/lib/irb/driver/readline.rb +26 -0
- data/lib/irb/driver/socket.rb +44 -0
- data/lib/irb/driver/tty.rb +58 -0
- data/lib/irb/ext/completion.rb +13 -12
- data/lib/irb/ext/history.rb +53 -58
- data/lib/irb/formatter.rb +3 -1
- data/lib/irb/version.rb +2 -2
- data/spec/context_spec.rb +29 -107
- data/spec/driver/readline_spec.rb +70 -0
- data/spec/driver/tty_spec.rb +70 -0
- data/spec/driver_spec.rb +65 -0
- data/spec/{colorize_spec.rb → ext/colorize_spec.rb} +1 -1
- data/spec/{completion_spec.rb → ext/completion_spec.rb} +12 -8
- data/spec/{history_spec.rb → ext/history_spec.rb} +42 -45
- data/spec/formatter_spec.rb +5 -3
- data/spec/regression/context_spec.rb +1 -1
- data/spec/source_spec.rb +25 -29
- data/spec/spec_helper.rb +69 -5
- metadata +21 -24
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'irb/driver/tty'
|
3
|
+
require 'irb/ext/history'
|
4
|
+
require 'irb/ext/completion'
|
5
|
+
|
6
|
+
module IRB
|
7
|
+
module Driver
|
8
|
+
class Readline < TTY
|
9
|
+
|
10
|
+
def initialize(input = $stdin, output = $stdout)
|
11
|
+
super
|
12
|
+
::Readline.input = @input
|
13
|
+
::Readline.output = @output
|
14
|
+
::Readline.completion_proc = IRB::Completion.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def readline
|
18
|
+
source = ::Readline.readline(context.prompt, true)
|
19
|
+
IRB::History.input(source)
|
20
|
+
source
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
IRB::Driver.current = IRB::Driver::Readline.new
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'irb/driver/tty'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module IRB
|
5
|
+
module Driver
|
6
|
+
class Socket
|
7
|
+
# Initializes with the object and binding that each new connection will
|
8
|
+
# get as Context. The binding is shared, so local variables will stay
|
9
|
+
# around. The benefit of this is that a socket based irb session is most
|
10
|
+
# probably used to debug a running application in development. In this
|
11
|
+
# scenario it could be beneficial to keep local vars in between sessions.
|
12
|
+
#
|
13
|
+
# TODO see if that actually works out ok.
|
14
|
+
def initialize(object, binding, host = '127.0.0.1', port = 7829)
|
15
|
+
@object, @binding = object, binding
|
16
|
+
@host, @port = host, port
|
17
|
+
@server = TCPServer.new(host, port)
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO libedit doesn't use the right input and output, so we can't use Readline for now!!
|
21
|
+
def run
|
22
|
+
$stderr.puts "[!] Running IRB server on #{@host}:#{@port}"
|
23
|
+
loop do
|
24
|
+
connection = @server.accept
|
25
|
+
Thread.new do
|
26
|
+
# assign driver with connection to current thread and start runloop
|
27
|
+
IRB::Driver.current = TTY.new(connection, connection)
|
28
|
+
irb(@object, @binding)
|
29
|
+
connection.close
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.irb(object, binding = nil)
|
38
|
+
unless @server
|
39
|
+
@server = IRB::Driver::Socket.new(object, binding)
|
40
|
+
@server.run
|
41
|
+
else
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'irb/driver'
|
2
|
+
|
3
|
+
module IRB
|
4
|
+
module Driver
|
5
|
+
class TTY
|
6
|
+
attr_reader :input, :output, :context_stack
|
7
|
+
|
8
|
+
def initialize(input = $stdin, output = $stdout)
|
9
|
+
@input = input
|
10
|
+
@output = output
|
11
|
+
@context_stack = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def context
|
15
|
+
@context_stack.last
|
16
|
+
end
|
17
|
+
|
18
|
+
def readline
|
19
|
+
@output.print(context.prompt)
|
20
|
+
@input.gets
|
21
|
+
end
|
22
|
+
|
23
|
+
# TODO make it take the current context instead of storing it
|
24
|
+
def consume
|
25
|
+
readline
|
26
|
+
rescue Interrupt
|
27
|
+
context.clear_buffer
|
28
|
+
""
|
29
|
+
end
|
30
|
+
|
31
|
+
# Feeds input into a given context.
|
32
|
+
#
|
33
|
+
# Ensures that the standard output object is a OutputRedirector, or a
|
34
|
+
# subclass thereof.
|
35
|
+
def run(context)
|
36
|
+
@context_stack << context
|
37
|
+
before, $stdout = $stdout, OutputRedirector.new unless $stdout.is_a?(OutputRedirector)
|
38
|
+
while line = consume
|
39
|
+
break unless context.process_line(line)
|
40
|
+
end
|
41
|
+
ensure
|
42
|
+
@context_stack.pop
|
43
|
+
$stdout = before if before
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
IRB::Driver.current = IRB::Driver::TTY.new
|
50
|
+
|
51
|
+
module Kernel
|
52
|
+
# Creates a new IRB::Context with the given +object+ and runs it.
|
53
|
+
def irb(object, binding = nil)
|
54
|
+
IRB::Driver.current.run(IRB::Context.new(object, binding))
|
55
|
+
end
|
56
|
+
|
57
|
+
private :irb
|
58
|
+
end
|
data/lib/irb/ext/completion.rb
CHANGED
@@ -36,22 +36,23 @@ module IRB
|
|
36
36
|
yield
|
37
37
|
}
|
38
38
|
|
39
|
+
attr_reader :source
|
40
|
+
|
41
|
+
def context
|
42
|
+
IRB::Driver.current.context
|
43
|
+
end
|
44
|
+
|
39
45
|
# Returns an array of possible completion results, with the current
|
40
46
|
# IRB::Context.
|
41
47
|
#
|
42
48
|
# This is meant to be used with Readline which takes a completion proc.
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
attr_reader :context, :source
|
48
|
-
|
49
|
-
def initialize(context, source)
|
50
|
-
@context, @source = context, source
|
49
|
+
def call(source)
|
50
|
+
@source = source
|
51
|
+
results
|
51
52
|
end
|
52
53
|
|
53
54
|
def evaluate(s)
|
54
|
-
|
55
|
+
context.__evaluate__(s)
|
55
56
|
end
|
56
57
|
|
57
58
|
def local_variables
|
@@ -59,7 +60,7 @@ module IRB
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def instance_methods
|
62
|
-
|
63
|
+
context.object.methods.map(&:to_s)
|
63
64
|
end
|
64
65
|
|
65
66
|
def instance_methods_of(klass)
|
@@ -190,5 +191,5 @@ if defined?(Readline)
|
|
190
191
|
# * Hash: = and >
|
191
192
|
Readline.basic_word_break_characters= " \t\n`<;|&("
|
192
193
|
end
|
193
|
-
Readline.completion_proc = IRB::Completion
|
194
|
-
end
|
194
|
+
# Readline.completion_proc = IRB::Completion
|
195
|
+
end
|
data/lib/irb/ext/history.rb
CHANGED
@@ -7,67 +7,62 @@
|
|
7
7
|
# Portions Copyright (C) 2006-2010 Ben Bleything <ben@bleything.net> (Kernel#history & Kernel#history!)
|
8
8
|
|
9
9
|
module IRB
|
10
|
-
|
10
|
+
module History
|
11
11
|
class << self
|
12
12
|
attr_accessor :file, :max_entries_in_overview
|
13
13
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
end
|
14
|
+
def setup
|
15
|
+
to_a.each do |source|
|
16
|
+
Readline::HISTORY.push(source)
|
17
|
+
end if Readline::HISTORY.to_a.empty?
|
18
18
|
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def initialize(context)
|
22
|
-
@context = context
|
23
19
|
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def input(source)
|
30
|
-
File.open(self.class.file, "a") { |f| f.puts(source) }
|
31
|
-
source
|
32
|
-
end
|
33
|
-
|
34
|
-
def to_a
|
35
|
-
file = self.class.file
|
36
|
-
File.exist?(file) ? File.read(file).split("\n") : []
|
37
|
-
end
|
38
|
-
|
39
|
-
def clear!
|
40
|
-
File.open(self.class.file, "w") { |f| f << "" }
|
41
|
-
Readline::HISTORY.clear
|
42
|
-
end
|
43
|
-
|
44
|
-
def history(number_of_entries = max_entries_in_overview)
|
45
|
-
history_size = Readline::HISTORY.size
|
46
|
-
start_index = 0
|
20
|
+
def context
|
21
|
+
IRB::Driver.current.context
|
22
|
+
end
|
47
23
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
else
|
52
|
-
end_index = history_size - 2
|
53
|
-
start_index = history_size - number_of_entries - 1
|
24
|
+
def input(source)
|
25
|
+
File.open(file, "a") { |f| f.puts(source) }
|
26
|
+
source
|
54
27
|
end
|
55
28
|
|
56
|
-
|
57
|
-
|
29
|
+
def to_a
|
30
|
+
File.exist?(file) ? File.read(file).split("\n") : []
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear!
|
34
|
+
File.open(file, "w") { |f| f << "" }
|
35
|
+
Readline::HISTORY.clear
|
36
|
+
end
|
37
|
+
|
38
|
+
def history(number_of_entries = max_entries_in_overview)
|
39
|
+
history_size = Readline::HISTORY.size
|
40
|
+
start_index = 0
|
41
|
+
|
42
|
+
# always remove one extra, because that's the `history' command itself
|
43
|
+
if history_size <= number_of_entries
|
44
|
+
end_index = history_size - 2
|
45
|
+
else
|
46
|
+
end_index = history_size - 2
|
47
|
+
start_index = history_size - number_of_entries - 1
|
48
|
+
end
|
49
|
+
|
50
|
+
start_index.upto(end_index) do |i|
|
51
|
+
puts "#{i}: #{Readline::HISTORY[i]}"
|
52
|
+
end
|
58
53
|
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def history!(entry_or_range)
|
62
|
-
# we don't want to execute history! again
|
63
|
-
@context.clear_buffer
|
64
54
|
|
65
|
-
|
66
|
-
|
67
|
-
|
55
|
+
def history!(entry_or_range)
|
56
|
+
# we don't want to execute history! again
|
57
|
+
context.clear_buffer
|
58
|
+
|
59
|
+
if entry_or_range.is_a?(Range)
|
60
|
+
entry_or_range.to_a.each do |i|
|
61
|
+
context.input_line(Readline::HISTORY[i])
|
62
|
+
end
|
63
|
+
else
|
64
|
+
context.input_line(Readline::HISTORY[entry_or_range])
|
68
65
|
end
|
69
|
-
else
|
70
|
-
@context.input_line(Readline::HISTORY[entry_or_range])
|
71
66
|
end
|
72
67
|
end
|
73
68
|
end
|
@@ -75,23 +70,23 @@ end
|
|
75
70
|
|
76
71
|
module Kernel
|
77
72
|
def history(number_of_entries = IRB::History.max_entries_in_overview)
|
78
|
-
IRB::History.
|
79
|
-
|
73
|
+
IRB::History.history(number_of_entries)
|
74
|
+
IRB::Context::IGNORE_RESULT
|
80
75
|
end
|
81
76
|
alias_method :h, :history
|
82
77
|
|
83
78
|
def history!(entry_or_range)
|
84
|
-
IRB::History.
|
85
|
-
|
79
|
+
IRB::History.history!(entry_or_range)
|
80
|
+
IRB::Context::IGNORE_RESULT
|
86
81
|
end
|
87
82
|
alias_method :h!, :history!
|
88
83
|
|
89
84
|
def clear_history!
|
90
|
-
IRB::History.
|
91
|
-
|
85
|
+
IRB::History.clear!
|
86
|
+
true
|
92
87
|
end
|
93
88
|
end
|
94
89
|
|
95
|
-
IRB::Context.processors << IRB::History
|
96
90
|
IRB::History.file = File.expand_path("~/.irb_history")
|
97
|
-
IRB::History.max_entries_in_overview = 50
|
91
|
+
IRB::History.max_entries_in_overview = 50
|
92
|
+
IRB::History.setup
|
data/lib/irb/formatter.rb
CHANGED
@@ -38,7 +38,9 @@ module IRB
|
|
38
38
|
|
39
39
|
def inspect_object(object)
|
40
40
|
if @inspect
|
41
|
-
object.respond_to?(:pretty_inspect) ? object.pretty_inspect : object.inspect
|
41
|
+
result = object.respond_to?(:pretty_inspect) ? object.pretty_inspect : object.inspect
|
42
|
+
result.strip!
|
43
|
+
result
|
42
44
|
else
|
43
45
|
address = object.__id__ * 2
|
44
46
|
address += 0x100000000 if address < 0
|
data/lib/irb/version.rb
CHANGED
data/spec/context_spec.rb
CHANGED
@@ -1,22 +1,6 @@
|
|
1
1
|
require File.expand_path('../spec_helper', __FILE__)
|
2
2
|
require 'tempfile'
|
3
3
|
|
4
|
-
def stub_Readline
|
5
|
-
class << Readline
|
6
|
-
attr_reader :received
|
7
|
-
|
8
|
-
def stub_input(*input)
|
9
|
-
@input = input
|
10
|
-
end
|
11
|
-
|
12
|
-
def readline(prompt, history)
|
13
|
-
@received = [prompt, history]
|
14
|
-
@input.shift
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
stub_Readline
|
19
|
-
|
20
4
|
class TestProcessor
|
21
5
|
def input(s)
|
22
6
|
s * 2
|
@@ -28,6 +12,7 @@ main = self
|
|
28
12
|
describe "IRB::Context" do
|
29
13
|
before do
|
30
14
|
@context = IRB::Context.new(main)
|
15
|
+
@context.extend(OutputStubMixin)
|
31
16
|
end
|
32
17
|
|
33
18
|
it "initializes with an object and stores a copy of its binding" do
|
@@ -41,46 +26,24 @@ describe "IRB::Context" do
|
|
41
26
|
it "initializes with an object and an explicit binding" do
|
42
27
|
context = IRB::Context.new(Object.new, TOPLEVEL_BINDING)
|
43
28
|
eval("class InTopLevel; end", context.binding)
|
44
|
-
lambda { ::InTopLevel }.
|
29
|
+
lambda { ::InTopLevel }.should_not raise_error(NameError)
|
45
30
|
end
|
46
31
|
|
47
32
|
it "initializes with an 'empty' state" do
|
48
33
|
@context.line.should == 1
|
49
|
-
@context.source.should
|
34
|
+
@context.source.class.should == IRB::Source
|
50
35
|
@context.source.to_s.should == ""
|
51
36
|
end
|
52
37
|
|
53
|
-
it "initializes with an instance of each processor" do
|
54
|
-
before = IRB::Context.processors.dup
|
55
|
-
begin
|
56
|
-
IRB::Context.processors << TestProcessor
|
57
|
-
@context = IRB::Context.new(main)
|
58
|
-
@context.processors.last.should.be.instance_of TestProcessor
|
59
|
-
ensure
|
60
|
-
IRB::Context.processors.replace(before)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
38
|
it "does not use the same binding copy of the top level object" do
|
65
|
-
lambda { eval("x", @context.binding) }.should
|
66
|
-
end
|
67
|
-
|
68
|
-
it "makes itself the current running context during the runloop and resigns once it's done" do
|
69
|
-
IRB::Context.current.should == nil
|
70
|
-
|
71
|
-
Readline.stub_input("current_during_run = IRB::Context.current")
|
72
|
-
@context.run
|
73
|
-
eval('current_during_run', @context.binding).should == @context
|
74
|
-
|
75
|
-
IRB::Context.current.should == nil
|
39
|
+
lambda { eval("x", @context.binding) }.should raise_error(NameError)
|
76
40
|
end
|
77
41
|
end
|
78
42
|
|
79
43
|
describe "IRB::Context, when evaluating source" do
|
80
44
|
before do
|
81
45
|
@context = IRB::Context.new(main)
|
82
|
-
|
83
|
-
def @context.puts(string); printed << "#{string}\n" end
|
46
|
+
@context.extend(OutputStubMixin)
|
84
47
|
IRB.formatter = IRB::Formatter.new
|
85
48
|
end
|
86
49
|
|
@@ -109,59 +72,43 @@ describe "IRB::Context, when evaluating source" do
|
|
109
72
|
lambda {
|
110
73
|
@context.evaluate("DoesNotExist")
|
111
74
|
@context.evaluate("raise Exception")
|
112
|
-
}.
|
75
|
+
}.should_not.raise_error
|
113
76
|
end
|
114
77
|
|
115
|
-
it "assigns the last raised exception to the
|
78
|
+
it "assigns the last raised exception to the variables `exception' / `e'" do
|
116
79
|
@context.evaluate("DoesNotExist")
|
117
|
-
|
118
|
-
|
119
|
-
|
80
|
+
@context.__evaluate__("exception").class.should == NameError
|
81
|
+
@context.__evaluate__("exception").message.should include('DoesNotExist')
|
82
|
+
@context.__evaluate__("e").should == @context.__evaluate__("exception")
|
120
83
|
end
|
121
84
|
|
122
85
|
it "prints the exception that occurs" do
|
123
86
|
@context.evaluate("DoesNotExist")
|
124
|
-
@context.printed.should
|
87
|
+
@context.printed.should =~ /^NameError:.+DoesNotExist/
|
125
88
|
end
|
126
89
|
|
127
90
|
it "uses the line number of the *first* line in the buffer, for the line parameter of eval" do
|
128
91
|
@context.process_line("DoesNotExist")
|
129
|
-
@context.printed.should
|
92
|
+
@context.printed.should =~ /\(irb\):1:in/
|
130
93
|
@context.process_line("class A")
|
131
94
|
@context.process_line("DoesNotExist")
|
132
95
|
@context.process_line("end")
|
133
|
-
@context.printed.should
|
96
|
+
@context.printed.should =~ /\(irb\):3:in.+\(irb\):2:in/m
|
134
97
|
end
|
135
98
|
|
136
|
-
it "
|
137
|
-
|
138
|
-
@context.
|
139
|
-
@context.printed.should ==
|
99
|
+
it "ignores the result if it's IRB::Context::IGNORE_RESULT" do
|
100
|
+
@context.evaluate(":bananas")
|
101
|
+
@context.evaluate("IRB::Context::IGNORE_RESULT").should == nil
|
102
|
+
@context.printed.should == "=> :bananas\n"
|
103
|
+
@context.evaluate("_").should == :bananas
|
140
104
|
end
|
141
105
|
end
|
142
106
|
|
143
107
|
describe "IRB::Context, when receiving input" do
|
144
108
|
before do
|
145
109
|
@context = IRB::Context.new(main)
|
146
|
-
|
147
|
-
|
148
|
-
it "prints the prompt, reads a line, saves it to the history and returns it" do
|
149
|
-
Readline.stub_input("def foo")
|
150
|
-
@context.readline.should == "def foo"
|
151
|
-
Readline.received.should == ["irb(main):001:0> ", true]
|
152
|
-
end
|
153
|
-
|
154
|
-
it "passes the input to all processors, which may return a new value" do
|
155
|
-
@context.processors << TestProcessor.new
|
156
|
-
Readline.stub_input("foo")
|
157
|
-
@context.readline.should == "foofoo"
|
158
|
-
end
|
159
|
-
|
160
|
-
it "processes the output" do
|
161
|
-
Readline.stub_input("def foo")
|
162
|
-
def @context.process_line(line); @received = line; false; end
|
163
|
-
@context.run
|
164
|
-
@context.instance_variable_get(:@received).should == "def foo"
|
110
|
+
@context.extend(InputStubMixin)
|
111
|
+
@context.extend(OutputStubMixin)
|
165
112
|
end
|
166
113
|
|
167
114
|
it "adds the received code to the source buffer" do
|
@@ -170,22 +117,10 @@ describe "IRB::Context, when receiving input" do
|
|
170
117
|
@context.source.to_s.should == "def foo\np :ok"
|
171
118
|
end
|
172
119
|
|
173
|
-
it "clears the source buffer
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
def Readline.readline(*args)
|
178
|
-
unless @raised
|
179
|
-
@raised = true
|
180
|
-
raise Interrupt
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
lambda { @context.run }.should.not.raise Interrupt
|
185
|
-
@context.source.to_s.should == ""
|
186
|
-
ensure
|
187
|
-
stub_Readline
|
188
|
-
end
|
120
|
+
it "clears the source buffer" do
|
121
|
+
@context.process_line("def foo")
|
122
|
+
@context.clear_buffer
|
123
|
+
@context.source.to_s.should == ""
|
189
124
|
end
|
190
125
|
|
191
126
|
it "increases the current line number" do
|
@@ -208,14 +143,11 @@ describe "IRB::Context, when receiving input" do
|
|
208
143
|
end
|
209
144
|
|
210
145
|
it "prints that a syntax error occurred on the last line and reset the buffer to the previous line" do
|
211
|
-
def @context.puts(str); @printed = str; end
|
212
|
-
|
213
146
|
@context.process_line("def foo")
|
214
147
|
@context.process_line(" };")
|
215
148
|
|
216
149
|
@context.source.to_s.should == "def foo"
|
217
|
-
printed
|
218
|
-
printed.should == "SyntaxError: compile error\n(irb):2: syntax error, unexpected '}'"
|
150
|
+
@context.printed.should == "SyntaxError: compile error\n(irb):2: syntax error, unexpected '}'\n"
|
219
151
|
end
|
220
152
|
|
221
153
|
it "returns whether or not the runloop should continue, but only if the level is 0" do
|
@@ -226,19 +158,9 @@ describe "IRB::Context, when receiving input" do
|
|
226
158
|
@context.process_line("quit").should == false
|
227
159
|
end
|
228
160
|
|
229
|
-
it "
|
230
|
-
|
231
|
-
|
232
|
-
@context.
|
233
|
-
@context.instance_variable_get(:@received).should.not == "def foo"
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
describe "Kernel::irb" do
|
238
|
-
it "creates a new context for the given object and runs it" do
|
239
|
-
Readline.stub_input("::IRBRan = self")
|
240
|
-
o = Object.new
|
241
|
-
irb(o)
|
242
|
-
IRBRan.should == o
|
161
|
+
it "inputs a line to be processed" do
|
162
|
+
expected = "#{@context.formatter.prompt(@context)}2 * 21\n=> 42\n"
|
163
|
+
@context.input_line("2 * 21")
|
164
|
+
@context.printed.should == expected
|
243
165
|
end
|
244
166
|
end
|