Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dont echo assignment expressions #12

Merged
merged 1 commit into from
Aug 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion lib/irb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#
#
require "e2mmap"
require "ripper"

require "irb/init"
require "irb/context"
Expand Down Expand Up @@ -410,6 +411,35 @@ def IRB.irb_abort(irb, exception = Abort)
end

class Irb
ASSIGNMENT_NODE_TYPES = [
# Local, instance, global, class, constant, instance, and index assignment:
# "foo = bar",
# "@foo = bar",
# "$foo = bar",
# "@@foo = bar",
# "::Foo = bar",
# "a::Foo = bar",
# "Foo = bar"
# "foo.bar = 1"
# "foo[1] = bar"
:assign,

# Operation assignment:
# "foo += bar"
# "foo -= bar"
# "foo ||= bar"
# "foo &&= bar"
:opassign,

# Multiple assignment:
# "foo, bar = 1, 2
:massign,
]
# Note: instance and index assignment expressions could also be written like:
# "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former
# be parsed as :assign and echo will be suppressed, but the latter is
# parsed as a :method_add_arg and the output won't be suppressed

# Creates a new irb session
def initialize(workspace = nil, input_method = nil, output_method = nil)
@context = Context.new(self, workspace, input_method, output_method)
Expand Down Expand Up @@ -494,7 +524,7 @@ def eval_input
begin
line.untaint
@context.evaluate(line, line_no, exception: exc)
output_value if @context.echo?
output_value if @context.echo? && (@context.echo_on_assignment? || !assignment_expression?(line))
rescue Interrupt => exc
rescue SystemExit, SignalException
raise
Expand Down Expand Up @@ -705,6 +735,18 @@ def inspect
format("#<%s: %s>", self.class, ary.join(", "))
end

def assignment_expression?(line)
# Try to parse the line and check if the last of possibly multiple
# expressions is an assignment type.

# If the expression is invalid, Ripper.sexp should return nil which will
# result in false being returned. Any valid expression should return an
# s-expression where the second selement of the top level array is an
# array of parsed expressions. The first element of each expression is the
# expression's type.
ASSIGNMENT_NODE_TYPES.include?(Ripper.sexp(line)&.dig(1,-1,0))
onlynone marked this conversation as resolved.
Show resolved Hide resolved
end

ATTR_TTY = "\e[%sm"
def ATTR_TTY.[](*a) self % a.join(";"); end
ATTR_PLAIN = ""
Expand Down
15 changes: 15 additions & 0 deletions lib/irb/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ def initialize(irb, workspace = nil, input_method = nil, output_method = nil)
if @echo.nil?
@echo = true
end

@echo_on_assignment = IRB.conf[:ECHO_ON_ASSIGNMENT]
if @echo_on_assignment.nil?
@echo_on_assignment = false
end
end

# The top-level workspace, see WorkSpace#main
Expand Down Expand Up @@ -236,6 +241,15 @@ def main
# puts "omg"
# # omg
attr_accessor :echo
# Whether to echo for assignment expressions
#
# Uses IRB.conf[:ECHO_ON_ASSIGNMENT] if available, or defaults to +false+.
#
# a = "omg"
# IRB.CurrentContext.echo_on_assignment = true
# a = "omg"
# #=> omg
attr_accessor :echo_on_assignment
# Whether verbose messages are displayed or not.
#
# A copy of the default <code>IRB.conf[:VERBOSE]</code>
Expand All @@ -261,6 +275,7 @@ def main
alias ignore_sigint? ignore_sigint
alias ignore_eof? ignore_eof
alias echo? echo
alias echo_on_assignment? echo_on_assignment

# Returns whether messages are displayed or not.
def verbose?
Expand Down
5 changes: 5 additions & 0 deletions lib/irb/init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def IRB.init_config(ap_path)
@CONF[:IGNORE_SIGINT] = true
@CONF[:IGNORE_EOF] = false
@CONF[:ECHO] = nil
@CONF[:ECHO_ON_ASSIGNMENT] = nil
@CONF[:VERBOSE] = nil

@CONF[:EVAL_HISTORY] = nil
Expand Down Expand Up @@ -172,6 +173,10 @@ def IRB.parse_opts(argv: ::ARGV)
@CONF[:ECHO] = true
when "--noecho"
@CONF[:ECHO] = false
when "--echo-on-assignment"
@CONF[:ECHO_ON_ASSIGNMENT] = true
when "--noecho-on-assignment"
@CONF[:ECHO_ON_ASSIGNMENT] = false
when "--verbose"
@CONF[:VERBOSE] = true
when "--noverbose"
Expand Down
125 changes: 125 additions & 0 deletions test/irb/test_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def eof?
def encoding
Encoding.default_external
end

def reset
@line_no = 0
end
end

def setup
Expand Down Expand Up @@ -84,5 +88,126 @@ def test_eval_input
def test_default_config
assert_equal(true, @context.use_colorize?)
end

def test_assignment_expression
input = TestInputMethod.new
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)
[
"foo = bar",
"@foo = bar",
"$foo = bar",
"@@foo = bar",
"::Foo = bar",
"a::Foo = bar",
"Foo = bar",
"foo.bar = 1",
"foo[1] = bar",
"foo += bar",
"foo -= bar",
"foo ||= bar",
"foo &&= bar",
"foo, bar = 1, 2",
"foo.bar=(1)",
"foo; foo = bar",
"foo; foo = bar; ;\n ;",
"foo\nfoo = bar",
].each do |exp|
assert(
irb.assignment_expression?(exp),
"#{exp.inspect}: should be an assignment expression"
)
end

[
"foo",
"foo.bar",
"foo[0]",
"foo = bar; foo",
"foo = bar\nfoo",
].each do |exp|
refute(
irb.assignment_expression?(exp),
"#{exp.inspect}: should not be an assignment expression"
)
end
end

def test_echo_on_assignment
input = TestInputMethod.new([
"a = 1\n",
"a\n",
"a, b = 2, 3\n",
"a\n",
"b\n",
"b = 4\n",
"_\n"
])
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)

# The default
irb.context.echo = true
irb.context.echo_on_assignment = false
out, err = capture_io do
irb.eval_input
end
assert_empty err
assert_equal("=> 1\n=> 2\n=> 3\n=> 4\n", out)

# Everything is output, like before echo_on_assignment was introduced
input.reset
irb.context.echo = true
irb.context.echo_on_assignment = true
out, err = capture_io do
irb.eval_input
end
assert_empty err
assert_equal("=> 1\n=> 1\n=> [2, 3]\n=> 2\n=> 3\n=> 4\n=> 4\n", out)

# Nothing is output when echo is false
input.reset
irb.context.echo = false
irb.context.echo_on_assignment = false
out, err = capture_io do
irb.eval_input
end
assert_empty err
assert_equal("", out)

# Nothing is output when echo is false even if echo_on_assignment is true
input.reset
irb.context.echo = false
irb.context.echo_on_assignment = true
out, err = capture_io do
irb.eval_input
end
assert_empty err
assert_equal("", out)
end

def test_echo_on_assignment_conf
# Default
IRB.conf[:ECHO] = nil
IRB.conf[:ECHO_ON_ASSIGNMENT] = nil
input = TestInputMethod.new()
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)

assert(irb.context.echo?, "echo? should be true by default")
refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false by default")

# Explicitly set :ECHO to false
IRB.conf[:ECHO] = false
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)

refute(irb.context.echo?, "echo? should be false when IRB.conf[:ECHO] is set to false")
refute(irb.context.echo_on_assignment?, "echo_on_assignment? should be false by default")

# Explicitly set :ECHO_ON_ASSIGNMENT to true
IRB.conf[:ECHO] = nil
IRB.conf[:ECHO_ON_ASSIGNMENT] = true
irb = IRB::Irb.new(IRB::WorkSpace.new(Object.new), input)

assert(irb.context.echo?, "echo? should be true by default")
assert(irb.context.echo_on_assignment?, "echo_on_assignment? should be true when IRB.conf[:ECHO_ON_ASSIGNMENT] is set to true")
end
end
end
1 change: 1 addition & 0 deletions test/irb/test_raise_no_backtrace_exception.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def test_raise_exception
bundle_exec = ENV.key?('BUNDLE_GEMFILE') ? ['-rbundler/setup'] : []
assert_in_out_err(bundle_exec + %w[-rirb -W0 -e IRB.start(__FILE__) -- -f --], <<-IRB, /Exception: foo/, [])
e = Exception.new("foo")
puts e.inspect
onlynone marked this conversation as resolved.
Show resolved Hide resolved
def e.backtrace; nil; end
raise e
IRB
Expand Down