-
Notifications
You must be signed in to change notification settings - Fork 119
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
Move away from method-based command implementation #824
Conversation
ab65d7e
to
66303ef
Compare
66303ef
to
177e4d4
Compare
68002e7
to
a6e4401
Compare
c6e4fff
to
b5ca3f2
Compare
b5ca3f2
to
9cfb4fd
Compare
lib/irb/statement.rb
Outdated
def evaluable_code | ||
@code | ||
def execute(context, line_no) | ||
context.evaluate(@code, line_no) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, statement objects should only provide information and the evaluation should still happen in the Irb
objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then we need to introduce this kind of branch, because we can't provide evaluable_code for command.
case statement
when Statement::Expression
context.evaluate(statement.code, line_no)
when Statement::Command
ret = statement.command_class.execute(context, statement.arg)
context.set_last_value(ret)
end
I think def execute
is better than this case-when.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still prefer having the case statement inside Irb#eval_input
than making statements perform the evaluation. Otherwise we're risking having a wrong coupling like RubyLex had, which is hard to eradicate as we both know.
Also, in the future (maybe even in the same release), we'll change commands to NOT have a return value. When that happens, it'd be even less beneficial to encapsulate things in statements.
d323b49
to
47c2210
Compare
lib/irb/command/base.rb
Outdated
# Use throw and catch to handle arg that includes `;` | ||
# For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] | ||
catch(:EXTRACT_RUBY_ARGS) do | ||
@irb_context.workspace.binding.eval "IRB::ExtendCommand.extract_ruby_args #{arg}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the new class:
@irb_context.workspace.binding.eval "IRB::ExtendCommand.extract_ruby_args #{arg}" | |
@irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" |
lib/irb/statement.rb
Outdated
def evaluable_code | ||
@code | ||
def execute(context, line_no) | ||
context.evaluate(@code, line_no) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still prefer having the case statement inside Irb#eval_input
than making statements perform the evaluation. Otherwise we're risking having a wrong coupling like RubyLex had, which is hard to eradicate as we both know.
Also, in the future (maybe even in the same release), we'll change commands to NOT have a return value. When that happens, it'd be even less beneficial to encapsulate things in statements.
lib/irb/workspace.rb
Outdated
def load_commands_to_main | ||
main.extend ExtendCommandBundle | ||
def load_helper_methods_to_main | ||
if ExtendCommandBundle.has_helper_method? && !(class<<main;ancestors;end).include?(ExtendCommandBundle) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to extend the context even if ExtendCommandBundle
has no methods as it'll simplify tests (e.g. we don't need 2 cases to test workspace commands). We'll also come up with a better solution for this when we support helper methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
I think it's beneficial to keep main object clean as possible, but I'll consider it later after helper method support
lib/irb.rb
Outdated
end | ||
|
||
# Check visibility | ||
local_variable = @context.local_variables.include?(command) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it's considerate to take local variables into account, I feel local variables should never override a command's invocation because:
- It's the behaviour of current IRB, Pry, and
debug
, so I'd expect most users to be aware of this limitation already. - Under the current behaviour, users can still use
p <var>
to retrieve the variable when a conflict happens. - However, under the this version users will NOT be able to call a command if a same-name local is present.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the limitation is fine, but if we're going to fix #803, to be consistent, I think local variables should take precedence over methods.
info = 1 # assignment is considered not a command in this pull request
=> 1
info + 1 # this will be a command call if we don't consider local variables
=> 2
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah then it's probably better to leave the behaviour in #803 as is. I'll close it if you also agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although there's many things to consider, I want to keep #803 open because it will be an enhancement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Co-authored-by: Stan Lo <[email protected]>
(ruby/irb#824) * Command is not a method * Fix command test * Implement non-method command name completion * Add test for ExtendCommandBundle.def_extend_command * Add helper method install test * Remove spaces in command input parse * Remove command arg unquote in help command * Simplify Statement and handle execution in IRB::Irb * Tweak require, const name * Always install CommandBundle module to main object * Remove considering local variable in command or expression check * Remove unused method, tweak * Remove outdated comment for help command arg Co-authored-by: Stan Lo <[email protected]> --------- ruby/irb@8fb776e379 Co-authored-by: Stan Lo <[email protected]>
This has a few benefits: - We can keep hiding the evaluation logic inside the Context level, which has always been the convention until #824 was merged recently. - Although not an official API, gems like `debug` and `mission_control-jobs` patch `Context#evaluate` to wrap their own logic around it. This implicit contract was broken after #824, and this change restores it. In addition to the refactor, I also converted some context-level evaluation tests into integration tests, which are more robust and easier to maintain.
This has a few benefits: - We can keep hiding the evaluation logic inside the Context level, which has always been the convention until #824 was merged recently. - Although not an official API, gems like `debug` and `mission_control-jobs` patch `Context#evaluate` to wrap their own logic around it. This implicit contract was broken after #824, and this change restores it. In addition to the refactor, I also converted some context-level evaluation tests into integration tests, which are more robust and easier to maintain.
(ruby/irb#824) * Command is not a method * Fix command test * Implement non-method command name completion * Add test for ExtendCommandBundle.def_extend_command * Add helper method install test * Remove spaces in command input parse * Remove command arg unquote in help command * Simplify Statement and handle execution in IRB::Irb * Tweak require, const name * Always install CommandBundle module to main object * Remove considering local variable in command or expression check * Remove unused method, tweak * Remove outdated comment for help command arg Co-authored-by: Stan Lo <[email protected]> --------- ruby/irb@8fb776e379 Co-authored-by: Stan Lo <[email protected]>
Command implementation without using
obj.extend IRB::ExtendCommandBundle
. Command is not a method anymore.Fixes #592
Continuation of #547
Command
command arg
was executed byeval("#{command} #{arg}")
oreval("command #{transformed_args(arg)}")
.This pull request will change command execution to
load_command(command).execute(arg)
.No main object method polluting by default
Now, IRB has no helper method by default.
If more than one helper method is defined in ExtendCommandBundle, (like Rails console adds(postponed)app
new_session
reload!
helper
controller
), IRB will reluctantly pollute main object.What kind of input is a command?
IRB has command override policy
NO_OVERRIDE
OVERRIDE_PRIVATE_ONLY
andOVERRIDE_ALL
for defined methods.New command recognition is:
command_name arg
might be a commandexit
Kernel.exit
is definedirb_exit
toexit
because it's ORVERRIDE_PRIVATE_ONLYexit
exit
defined by userirb_exit
toexit
irb_measure
measure
measure
definedmeasure off
measure
definedinfo 'a' + 'b'
logger.info("'a' + 'b'")
because IRB transform args even if command is not installed by NO_OVERRIDE policylogger.info('a' + 'b')
because info is NO_OVERRIDEshow_source * -s
show_source
definedshow_source("* -s")
because transorm_args is definedshow_source += 1
show_source("+= 1")
because transform_args is definedinfo += 1
Command completion
Command was a method, so completor can complete it without extra effort.
Now, command is not a method, so this pull request adds command name completion to both RegexpCompletor and TypeCompletor.
conf
andcontext
methodconf
context
method can be used asconf.eval_history = 100
before.It is now a command only to show configuration.
The source code comment says it just displays the configuration, and navigates to use
IRB.conf
for modifying it.But a different thing is written in the document that you can use
conf.eval_history = N
.We can:
/\A(conf|context)\./
vcontext.evaluate(code.gsub(/\A(conf|context)\./, 'IRB.CurrentContext.'))
conf
andcontext
as a method. Polluting main object problem remains.