From a86db0633df0b08889462a8b23215fbee10e1f21 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 24 Dec 2023 01:50:07 +0900 Subject: [PATCH 01/13] Command is not a method --- lib/irb.rb | 60 ++++++++++------ lib/irb/command.rb | 122 ++++++++++----------------------- lib/irb/command/backtrace.rb | 8 +-- lib/irb/command/base.rb | 35 +++++++--- lib/irb/command/break.rb | 8 +-- lib/irb/command/catch.rb | 8 +-- lib/irb/command/chws.rb | 11 ++- lib/irb/command/context.rb | 16 +++++ lib/irb/command/continue.rb | 4 +- lib/irb/command/debug.rb | 6 +- lib/irb/command/delete.rb | 4 +- lib/irb/command/edit.rb | 16 +---- lib/irb/command/finish.rb | 4 +- lib/irb/command/help.rb | 15 +--- lib/irb/command/history.rb | 10 ++- lib/irb/command/info.rb | 8 +-- lib/irb/command/irb_info.rb | 2 +- lib/irb/command/load.rb | 22 +++++- lib/irb/command/ls.rb | 28 +++++--- lib/irb/command/measure.rb | 16 +++-- lib/irb/command/next.rb | 4 +- lib/irb/command/pushws.rb | 15 ++-- lib/irb/command/show_doc.rb | 27 +++----- lib/irb/command/show_source.rb | 15 +--- lib/irb/command/step.rb | 4 +- lib/irb/command/subirb.rb | 35 ++++++---- lib/irb/command/whereami.rb | 2 +- lib/irb/context.rb | 12 ---- lib/irb/ext/change-ws.rb | 8 +-- lib/irb/ext/workspaces.rb | 7 +- lib/irb/statement.rb | 29 +++----- lib/irb/workspace.rb | 6 +- 32 files changed, 271 insertions(+), 296 deletions(-) create mode 100644 lib/irb/command/context.rb diff --git a/lib/irb.rb b/lib/irb.rb index c7d36e744..a97fefdd9 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -929,7 +929,7 @@ class Irb # Creates a new irb session def initialize(workspace = nil, input_method = nil) @context = Context.new(self, workspace, input_method) - @context.workspace.load_commands_to_main + @context.workspace.load_helper_methods_to_main @signal_status = :IN_IRB @scanner = RubyLex.new @line_no = 1 @@ -950,7 +950,7 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.replace_workspace(workspace) - context.workspace.load_commands_to_main + context.workspace.load_helper_methods_to_main @line_no += 1 # When users run: @@ -1028,7 +1028,7 @@ def eval_input return statement.code end - @context.evaluate(statement.evaluable_code, line_no) + statement.execute(@context, line_no) if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? @@ -1084,10 +1084,7 @@ def readmultiline end code << line - - # Accept any single-line input for symbol aliases or commands that transform - # args - return code if single_line_command?(code) + return code if command?(code) tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) return code if terminated @@ -1114,23 +1111,46 @@ def build_statement(code) end code.force_encoding(@context.io.encoding) - command_or_alias, arg = code.split(/\s/, 2) - # Transform a non-identifier alias (@, $) or keywords (next, break) - command_name = @context.command_aliases[command_or_alias.to_sym] - command = command_name || command_or_alias - command_class = ExtendCommandBundle.load_command(command) - - if command_class - Statement::Command.new(code, command, arg, command_class) + if (command, arg = parse_command(code)) + command_class = ExtendCommandBundle.load_command(command) + Statement::Command.new(code, command_class, arg) else is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) Statement::Expression.new(code, is_assignment_expression) end end - def single_line_command?(code) - command = code.split(/\s/, 2).first - @context.symbol_alias?(command) || @context.transform_args?(command) + ASSIGN_OPERATORS = %w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=] + COMMAND_LIKE_ASSIGN_REGEXP = /\A[a-z_]\w* #{Regexp.union(ASSIGN_OPERATORS)}( |$)/ + + def parse_command(code) + command_name, arg = code.strip.split(/\s/, 2) + return unless code.lines.size == 1 && command_name + return if COMMAND_LIKE_ASSIGN_REGEXP.match?(code) + + arg ||= '' + command = command_name.to_sym + # Command aliases are always command. example: $, @ + if (alias_name = @context.command_aliases[command]) + return [alias_name, arg] + end + + # Check visibility + local_variable = @context.local_variables.include?(command) + public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false + private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false + if ExtendCommandBundle.execute_as_command?( + command, + public_method: public_method, + private_method: private_method, + local_variable: local_variable + ) + [command, arg] + end + end + + def command?(code) + !!parse_command(code) end def configure_io @@ -1148,9 +1168,7 @@ def configure_io false end else - # Accept any single-line input for symbol aliases or commands that transform - # args - next true if single_line_command?(code) + next true if command?(code) _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) terminated diff --git a/lib/irb/command.rb b/lib/irb/command.rb index ef9304923..cd551b93f 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -12,29 +12,17 @@ module Command; end # Installs the default irb extensions command bundle. module ExtendCommandBundle - EXCB = ExtendCommandBundle # :nodoc: - - # See #install_alias_method. + # See ExtendCommandBundle.execute_as_command?. NO_OVERRIDE = 0 - # See #install_alias_method. OVERRIDE_PRIVATE_ONLY = 0x01 - # See #install_alias_method. OVERRIDE_ALL = 0x02 - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - def irb_context - IRB.CurrentContext - end - - @ALIASES = [ - [:context, :irb_context, NO_OVERRIDE], - [:conf, :irb_context, NO_OVERRIDE], - ] - - @EXTEND_COMMANDS = [ + [ + :irb_context, :Context, "command/context", + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], [ :irb_exit, :Exit, "command/exit", [:exit, OVERRIDE_PRIVATE_ONLY], @@ -204,6 +192,30 @@ def irb_context ], ] + def self.command_override_policies + @@command_override_policies ||= @EXTEND_COMMANDS.flat_map do |cmd_name, cmd_class, load_file, *aliases| + [[cmd_name, OVERRIDE_ALL]] + aliases + end.to_h + end + + def self.execute_as_command?(name, public_method:, private_method:, local_variable:) + case command_override_policies[name] + when OVERRIDE_ALL + true + when OVERRIDE_PRIVATE_ONLY + !public_method && !local_variable + when NO_OVERRIDE + !public_method && !private_method && !local_variable + end + end + + def self.has_helper_method? + IRB::ExtendCommandBundle.instance_methods.any? + end + + def self.command_names + command_override_policies.keys.map(&:to_s) + end @@commands = [] @@ -247,77 +259,13 @@ def self.load_command(command) nil end - # Installs the default irb commands. - def self.install_extend_commands - for args in @EXTEND_COMMANDS - def_extend_command(*args) - end - end - - # Evaluate the given +cmd_name+ on the given +cmd_class+ Class. - # - # Will also define any given +aliases+ for the method. - # - # The optional +load_file+ parameter will be required within the method - # definition. def self.def_extend_command(cmd_name, cmd_class, load_file, *aliases) - case cmd_class - when Symbol - cmd_class = cmd_class.id2name - when String - when Class - cmd_class = cmd_class.name - end - - line = __LINE__; eval %[ - def #{cmd_name}(*opts, **kwargs, &b) - Kernel.require_relative "#{load_file}" - ::IRB::Command::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) - end - ], nil, __FILE__, line + @EXTEND_COMMANDS.delete_if { |name,| name == cmd_name } + @EXTEND_COMMANDS << [cmd_name, cmd_class, load_file, *aliases] - for ali, flag in aliases - @ALIASES.push [ali, cmd_name, flag] - end + # Just clear memoized values + @@commands = [] + @@command_override_policies = nil end - - # Installs alias methods for the default irb commands, see - # ::install_extend_commands. - def install_alias_method(to, from, override = NO_OVERRIDE) - to = to.id2name unless to.kind_of?(String) - from = from.id2name unless from.kind_of?(String) - - if override == OVERRIDE_ALL or - (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or - (override == NO_OVERRIDE) && !respond_to?(to, true) - target = self - (class << self; self; end).instance_eval{ - if target.respond_to?(to, true) && - !target.respond_to?(EXCB.irb_original_method_name(to), true) - alias_method(EXCB.irb_original_method_name(to), to) - end - alias_method to, from - } - else - Kernel.warn "irb: warn: can't alias #{to} from #{from}.\n" - end - end - - def self.irb_original_method_name(method_name) # :nodoc: - "irb_" + method_name + "_org" - end - - # Installs alias methods for the default irb commands on the given object - # using #install_alias_method. - def self.extend_object(obj) - unless (class << obj; ancestors; end).include?(EXCB) - super - for ali, com, flg in @ALIASES - obj.install_alias_method(ali, com, flg) - end - end - end - - install_extend_commands end end diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb index 47e5e6072..610f9ee22 100644 --- a/lib/irb/command/backtrace.rb +++ b/lib/irb/command/backtrace.rb @@ -7,12 +7,8 @@ module IRB module Command class Backtrace < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["backtrace", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "backtrace #{arg}".rstrip) end end end diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index 3ce4f4d6c..c75f8e18c 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -10,6 +10,10 @@ module IRB module Command class CommandArgumentError < StandardError; end + def self.extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + class Base class << self def category(category = nil) @@ -29,19 +33,13 @@ def help_message(help_message = nil) private - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end - def highlight(text) Color.colorize(text, [:BOLD, :BLUE]) end end - def self.execute(irb_context, *opts, **kwargs, &block) - command = new(irb_context) - command.execute(*opts, **kwargs, &block) + def self.execute(irb_context, arg) + new(irb_context).execute(arg) rescue CommandArgumentError => e puts e.message end @@ -52,7 +50,26 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + def unwrap_string_literal(str) + return if str.empty? + + sexp = Ripper.sexp(str) + if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal + @irb_context.workspace.binding.eval(str).to_s + else + str + end + end + + def ruby_args(arg) + # 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}" + end || [[], {}] + end + + def execute(arg) #nop end end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb index fa200f2d7..42ee002ce 100644 --- a/lib/irb/command/break.rb +++ b/lib/irb/command/break.rb @@ -7,12 +7,8 @@ module IRB module Command class Break < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(args = nil) - super(pre_cmds: "break #{args}") + def execute(arg) + execute_debug_command(pre_cmds: "break #{arg}".rstrip) end end end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb index 6b2edff5e..655c77d8a 100644 --- a/lib/irb/command/catch.rb +++ b/lib/irb/command/catch.rb @@ -7,12 +7,8 @@ module IRB module Command class Catch < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["catch", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "catch #{arg}".rstrip) end end end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb index e1047686c..e0a406885 100644 --- a/lib/irb/command/chws.rb +++ b/lib/irb/command/chws.rb @@ -14,7 +14,7 @@ class CurrentWorkingWorkspace < Base category "Workspace" description "Show the current workspace." - def execute(*obj) + def execute(_arg) irb_context.main end end @@ -23,8 +23,13 @@ class ChangeWorkspace < Base category "Workspace" description "Change the current workspace to an object." - def execute(*obj) - irb_context.change_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.change_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.change_workspace(obj) + end irb_context.main end end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb new file mode 100644 index 000000000..b4fc80734 --- /dev/null +++ b/lib/irb/command/context.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module IRB + module Command + class Context < Base + category "IRB" + description "Displays current configuration." + + def execute(_arg) + # This command just displays the configuration. + # Modifying the configuration is achieved by sending a message to IRB.conf. + Pager.page_content(IRB.CurrentContext.inspect) + end + end + end +end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb index 8b6ffc860..49e4384eb 100644 --- a/lib/irb/command/continue.rb +++ b/lib/irb/command/continue.rb @@ -7,8 +7,8 @@ module IRB module Command class Continue < DebugCommand - def execute(*args) - super(do_cmds: ["continue", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "continue #{arg}".rstrip) end end end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb index bdf91766b..aeafe19b5 100644 --- a/lib/irb/command/debug.rb +++ b/lib/irb/command/debug.rb @@ -13,7 +13,11 @@ class Debug < Base binding.method(:irb).source_location.first, ].map { |file| /\A#{Regexp.escape(file)}:\d+:in (`|'Binding#)irb'\z/ } - def execute(pre_cmds: nil, do_cmds: nil) + def execute(_arg) + execute_debug_command + end + + def execute_debug_command(pre_cmds: nil, do_cmds: nil) if irb_context.with_debugger # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. if cmd = pre_cmds || do_cmds diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb index a36b4577b..4b45a51e7 100644 --- a/lib/irb/command/delete.rb +++ b/lib/irb/command/delete.rb @@ -7,8 +7,8 @@ module IRB module Command class Delete < DebugCommand - def execute(*args) - super(pre_cmds: ["delete", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "delete #{arg}".rstrip) end end end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb index ab8c62663..480100bfc 100644 --- a/lib/irb/command/edit.rb +++ b/lib/irb/command/edit.rb @@ -26,19 +26,9 @@ class Edit < Base edit Foo#bar HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.nil? || args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(*args) - path = args.first + def execute(arg) + # Accept string literal for backward compatibility + path = unwrap_string_literal(arg) if path.nil? path = @irb_context.irb_path diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb index 05501819e..c1d62357f 100644 --- a/lib/irb/command/finish.rb +++ b/lib/irb/command/finish.rb @@ -7,8 +7,8 @@ module IRB module Command class Finish < DebugCommand - def execute(*args) - super(do_cmds: ["finish", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "finish #{arg}".rstrip) end end end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 19113dbbf..5298d3047 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -6,18 +6,9 @@ class Help < Base category "Help" description "List all available commands. Use `help ` to get information about a specific command." - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(command_name = nil) + def execute(arg) + # Accept string literal for backward compatibility + command_name = unwrap_string_literal(arg) content = if command_name if command_class = ExtendCommandBundle.load_command(command_name) diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb index a47a8795d..90f87f910 100644 --- a/lib/irb/command/history.rb +++ b/lib/irb/command/history.rb @@ -12,14 +12,12 @@ class History < Base category "IRB" description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." - def self.transform_args(args) - match = args&.match(/(-g|-G)\s+(?.+)\s*\n\z/) - return unless match + def execute(arg) - "grep: #{Regexp.new(match[:grep]).inspect}" - end + if (match = arg&.match(/(-g|-G)\s+(?.+)\s*\n\z/)) + grep = Regexp.new(match[:grep]) + end - def execute(grep: nil) formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| next if grep && !input.match?(grep) diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb index a67be3eb8..897ee2c43 100644 --- a/lib/irb/command/info.rb +++ b/lib/irb/command/info.rb @@ -7,12 +7,8 @@ module IRB module Command class Info < DebugCommand - def self.transform_args(args) - args&.dump - end - - def execute(*args) - super(pre_cmds: ["info", *args].join(" ")) + def execute(arg) + execute_debug_command(pre_cmds: "info #{arg}".rstrip) end end end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb index cc93fdcbd..6d868de94 100644 --- a/lib/irb/command/irb_info.rb +++ b/lib/irb/command/irb_info.rb @@ -8,7 +8,7 @@ class IrbInfo < Base category "IRB" description "Show information about IRB." - def execute + def execute(_arg) str = "Ruby version: #{RUBY_VERSION}\n" str += "IRB version: #{IRB.version}\n" str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb index 9e89a7b7f..33e327f4a 100644 --- a/lib/irb/command/load.rb +++ b/lib/irb/command/load.rb @@ -21,7 +21,12 @@ class Load < LoaderCommand category "IRB" description "Load a Ruby file." - def execute(file_name = nil, priv = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil, priv = nil) raise_cmd_argument_error unless file_name irb_load(file_name, priv) end @@ -30,7 +35,13 @@ def execute(file_name = nil, priv = nil) class Require < LoaderCommand category "IRB" description "Require a Ruby file." - def execute(file_name = nil) + + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") @@ -63,7 +74,12 @@ class Source < LoaderCommand category "IRB" description "Loads a given file in the current session." - def execute(file_name = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(file_name = nil) raise_cmd_argument_error unless file_name source_file(file_name) diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index 6b6136c2f..3cf0d213b 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -20,27 +20,35 @@ class Ls < Base -g [query] Filter the output with a query. HELP_MESSAGE - def self.transform_args(args) - if match = args&.match(/\A(?.+\s|)(-g|-G)\s+(?[^\s]+)\s*\n\z/) - args = match[:args] - "#{args}#{',' unless args.chomp.empty?} grep: /#{match[:grep]}/" + def execute(arg) + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + if match[:args].empty? + use_main = true + else + obj = @irb_context.workspace.binding.eval(match[:args]) + end + grep = Regexp.new(match[:grep]) else - args + args, kwargs = ruby_args(arg) + use_main = args.empty? + obj = args.first + grep = kwargs[:grep] + end + + if use_main + obj = irb_context.workspace.main + locals = irb_context.workspace.binding.local_variables end - end - def execute(*arg, grep: nil) o = Output.new(grep: grep) - obj = arg.empty? ? irb_context.workspace.main : arg.first - locals = arg.empty? ? irb_context.workspace.binding.local_variables : [] klass = (obj.class == Class || obj.class == Module ? obj : obj.class) o.dump("constants", obj.constants) if obj.respond_to?(:constants) dump_methods(o, klass, obj) o.dump("instance variables", obj.instance_variables) o.dump("class variables", klass.class_variables) - o.dump("locals", locals) + o.dump("locals", locals) if locals o.print_result end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb index ee7927b01..70dc69cde 100644 --- a/lib/irb/command/measure.rb +++ b/lib/irb/command/measure.rb @@ -10,15 +10,19 @@ def initialize(*args) super(*args) end - def execute(type = nil, arg = nil) - # Please check IRB.init_config in lib/irb/init.rb that sets - # IRB.conf[:MEASURE_PROC] to register default "measure" methods, - # "measure :time" (abbreviated as "measure") and "measure :stackprof". - - if block_given? + def execute(arg) + if arg&.match?(/^do$|^do[^\w]|^\{/) warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' return end + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(type = nil, arg = nil) + # Please check IRB.init_config in lib/irb/init.rb that sets + # IRB.conf[:MEASURE_PROC] to register default "measure" methods, + # "measure :time" (abbreviated as "measure") and "measure :stackprof". case type when :off diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb index 6487c9d24..92d28e33e 100644 --- a/lib/irb/command/next.rb +++ b/lib/irb/command/next.rb @@ -7,8 +7,8 @@ module IRB module Command class Next < DebugCommand - def execute(*args) - super(do_cmds: ["next", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "next #{arg}".rstrip) end end end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb index 888fe466f..b51928c65 100644 --- a/lib/irb/command/pushws.rb +++ b/lib/irb/command/pushws.rb @@ -14,7 +14,7 @@ class Workspaces < Base category "Workspace" description "Show workspaces." - def execute(*obj) + def execute(_arg) inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| truncated_inspect(ws.main) end @@ -39,8 +39,13 @@ class PushWorkspace < Workspaces category "Workspace" description "Push an object to the workspace stack." - def execute(*obj) - irb_context.push_workspace(*obj) + def execute(arg) + if arg.empty? + irb_context.push_workspace + else + obj = eval(arg, irb_context.workspace.binding) + irb_context.push_workspace(obj) + end super end end @@ -49,8 +54,8 @@ class PopWorkspace < Workspaces category "Workspace" description "Pop a workspace from the workspace stack." - def execute(*obj) - irb_context.pop_workspace(*obj) + def execute(_arg) + irb_context.pop_workspace super end end diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb index 4dde28bee..f9393cd3b 100644 --- a/lib/irb/command/show_doc.rb +++ b/lib/irb/command/show_doc.rb @@ -3,17 +3,6 @@ module IRB module Command class ShowDoc < Base - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - category "Context" description "Look up documentation with RI." @@ -31,7 +20,9 @@ def transform_args(args) HELP_MESSAGE - def execute(*names) + def execute(arg) + # Accept string literal for backward compatibility + name = unwrap_string_literal(arg) require 'rdoc/ri/driver' unless ShowDoc.const_defined?(:Ri) @@ -39,15 +30,13 @@ def execute(*names) ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) end - if names.empty? + if name.nil? Ri.interactive else - names.each do |name| - begin - Ri.display_name(name.to_s) - rescue RDoc::RI::Error - puts $!.message - end + begin + Ri.display_name(name) + rescue RDoc::RI::Error + puts $!.message end end diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb index 32bdf74d3..c4c8fc004 100644 --- a/lib/irb/command/show_source.rb +++ b/lib/irb/command/show_source.rb @@ -24,18 +24,9 @@ class ShowSource < Base show_source Foo::BAR HELP_MESSAGE - class << self - def transform_args(args) - # Return a string literal as is for backward compatibility - if args.empty? || string_literal?(args) - args - else # Otherwise, consider the input as a String for convenience - args.strip.dump - end - end - end - - def execute(str = nil) + def execute(arg) + # Accept string literal for backward compatibility + str = unwrap_string_literal(arg) unless str.is_a?(String) puts "Error: Expected a string but got #{str.inspect}" return diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb index cce7d2b0f..514981302 100644 --- a/lib/irb/command/step.rb +++ b/lib/irb/command/step.rb @@ -7,8 +7,8 @@ module IRB module Command class Step < DebugCommand - def execute(*args) - super(do_cmds: ["step", *args].join(" ")) + def execute(arg) + execute_debug_command(do_cmds: "step #{arg}".rstrip) end end end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb index 5cc7b8c6f..24428a5c1 100644 --- a/lib/irb/command/subirb.rb +++ b/lib/irb/command/subirb.rb @@ -9,10 +9,6 @@ module IRB module Command class MultiIRBCommand < Base - def execute(*args) - extend_irb_context - end - private def print_deprecated_warning @@ -36,7 +32,12 @@ class IrbCommand < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Start a child IRB." - def execute(*obj) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*obj) print_deprecated_warning if irb_context.with_debugger @@ -44,7 +45,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -53,7 +54,7 @@ class Jobs < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "List of current sessions." - def execute + def execute(_arg) print_deprecated_warning if irb_context.with_debugger @@ -61,7 +62,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -70,7 +71,12 @@ class Foreground < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Switches to the session of the given number." - def execute(key = nil) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(key = nil) print_deprecated_warning if irb_context.with_debugger @@ -78,7 +84,7 @@ def execute(key = nil) return end - super + extend_irb_context raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key IRB.JobManager.switch(key) @@ -89,7 +95,12 @@ class Kill < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Kills the session with the given number." - def execute(*keys) + def execute(arg) + args, kwargs = ruby_args(arg) + execute_internal(*args, **kwargs) + end + + def execute_internal(*keys) print_deprecated_warning if irb_context.with_debugger @@ -97,7 +108,7 @@ def execute(*keys) return end - super + extend_irb_context IRB.JobManager.kill(*keys) end end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb index d6658d704..c8439f121 100644 --- a/lib/irb/command/whereami.rb +++ b/lib/irb/command/whereami.rb @@ -8,7 +8,7 @@ class Whereami < Base category "Context" description "Show the source code around binding.irb again." - def execute(*) + def execute(_arg) code = irb_context.workspace.code_around_binding if code puts code diff --git a/lib/irb/context.rb b/lib/irb/context.rb index 60dfb9668..e3c419245 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -646,17 +646,5 @@ def inspect # :nodoc: def local_variables # :nodoc: workspace.binding.local_variables end - - # Return true if it's aliased from the argument and it's not an identifier. - def symbol_alias?(command) - return nil if command.match?(/\A\w+\z/) - command_aliases.key?(command.to_sym) - end - - # Return true if the command supports transforming args - def transform_args?(command) - command = command_aliases.fetch(command.to_sym, command) - ExtendCommandBundle.load_command(command)&.respond_to?(:transform_args) - end end end diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb index 87fe03e23..60e8afe31 100644 --- a/lib/irb/ext/change-ws.rb +++ b/lib/irb/ext/change-ws.rb @@ -29,11 +29,9 @@ def change_workspace(*_main) return main end - replace_workspace(WorkSpace.new(_main[0])) - - if !(class< @command_class end - def evaluable_code - # Hook command-specific transformation to return valid Ruby code - if @command_class.respond_to?(:transform_args) - arg = @command_class.transform_args(@arg) - else - arg = @arg - end - - [@command, arg].compact.join(' ') + def execute(context, line_no) + ret = @command_class.execute(context, @arg) + context.set_last_value(ret) end end end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 4c3b5e425..395edc4cc 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -108,8 +108,10 @@ def initialize(*main) # IRB.conf[:__MAIN__] attr_reader :main - def load_commands_to_main - main.extend ExtendCommandBundle + def load_helper_methods_to_main + if ExtendCommandBundle.has_helper_method? && !(class< Date: Sun, 24 Dec 2023 01:50:13 +0900 Subject: [PATCH 02/13] Fix command test --- test/irb/test_command.rb | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index a8af0762a..b01f50e6c 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -373,17 +373,19 @@ def test_measure_toggle } } out, err = execute_lines( - "measure :foo", - "measure :on, :bar", - "3\n", + "measure :foo\n", + "1\n", + "measure :on, :bar\n", + "2\n", "measure :off, :foo\n", - "measure :off, :bar\n", "3\n", + "measure :off, :bar\n", + "4\n", conf: conf ) assert_empty err - assert_match(/\AFOO is added\.\n=> nil\nfoo\nBAR is added\.\n=> nil\nbar\nfoo\n=> 3\nbar\nfoo\n=> nil\nbar\n=> nil\n=> 3\n/, out) + assert_match(/\AFOO is added\.\n=> nil\nfoo\n=> 1\nBAR is added\.\n=> nil\nbar\nfoo\n=> 2\n=> nil\nbar\n=> 3\n=> nil\n=> 4\n/, out) end def test_measure_with_proc_warning @@ -402,7 +404,6 @@ def test_measure_with_proc_warning out, err = execute_lines( "3\n", "measure do\n", - "end\n", "3\n", conf: conf, main: c @@ -554,7 +555,8 @@ def test_popws_replaces_the_current_workspace_with_the_previous_one out, err = execute_lines( "pushws Foo.new\n", "popws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -573,7 +575,8 @@ class ChwsTest < WorkspaceCommandTestCase def test_chws_replaces_the_current_workspace out, err = execute_lines( "chws #{self.class}::Foo.new\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}::Foo") @@ -582,7 +585,8 @@ def test_chws_replaces_the_current_workspace def test_chws_does_nothing_when_receiving_no_argument out, err = execute_lines( "chws\n", - "cwws.class", + "cwws\n", + "_.class", ) assert_empty err assert_include(out, "=> #{self.class}") @@ -730,18 +734,18 @@ def test_ls_grep def test_ls_grep_empty out, err = execute_lines("ls\n") assert_empty err - assert_match(/whereami/, out) - assert_match(/show_source/, out) + assert_match(/assert/, out) + assert_match(/refute/, out) [ - "ls grep: /whereami/\n", - "ls -g whereami\n", - "ls -G whereami\n", + "ls grep: /assert/\n", + "ls -g assert\n", + "ls -G assert\n", ].each do |line| out, err = execute_lines(line) assert_empty err - assert_match(/whereami/, out) - assert_not_match(/show_source/, out) + assert_match(/assert/, out) + assert_not_match(/refute/, out) end end From e1595c0a799a7586bd9d0967bfeeaf2ec5286fa7 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 24 Dec 2023 04:32:59 +0900 Subject: [PATCH 03/13] Implement non-method command name completion --- lib/irb/completion.rb | 17 ++++++++++++++--- test/irb/test_completion.rb | 7 +++++++ test/irb/test_type_completor.rb | 7 +++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index b3813e893..8a1df1156 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -86,6 +86,14 @@ def retrieve_files_to_require_from_load_path ) end + def command_completions(preposing, target) + if preposing.empty? && !target.empty? + IRB::ExtendCommandBundle.command_names.select { _1.start_with?(target) } + else + [] + end + end + def retrieve_files_to_require_relative_from_current_dir @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') @@ -103,9 +111,11 @@ def inspect end def completion_candidates(preposing, target, _postposing, bind:) + commands = command_completions(preposing, target) result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - return [] unless result - result.completion_candidates.map { target + _1 } + return commands unless result + + commands | result.completion_candidates.map { target + _1 } end def doc_namespace(preposing, matched, _postposing, bind:) @@ -181,7 +191,8 @@ def completion_candidates(preposing, target, postposing, bind:) result = complete_require_path(target, preposing, postposing) return result if result end - retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } + commands = command_completions(preposing || '', target) + commands | retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } end def doc_namespace(_preposing, matched, _postposing, bind:) diff --git a/test/irb/test_completion.rb b/test/irb/test_completion.rb index 819446393..5fe7952b3 100644 --- a/test/irb/test_completion.rb +++ b/test/irb/test_completion.rb @@ -14,6 +14,13 @@ def doc_namespace(target, bind) IRB::RegexpCompletor.new.doc_namespace('', target, '', bind: bind) end + class CommandCompletionTest < CompletionTest + def test_command_completion + assert_include(IRB::RegexpCompletor.new.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(IRB::RegexpCompletor.new.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end + end + class MethodCompletionTest < CompletionTest def test_complete_string assert_include(completion_candidates("'foo'.up", binding), "'foo'.upcase") diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index cf4fc12c9..5104afdd4 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -9,6 +9,8 @@ return end +require 'irb/context' +require 'irb/command' require 'irb/completion' require 'tempfile' require_relative './helper' @@ -54,6 +56,11 @@ def test_empty_completion assert_equal [], candidates assert_doc_namespace('(', ')', nil) end + + def test_command_completion + assert_include(@completor.completion_candidates('', 'show_s', '', bind: binding), 'show_source') + assert_not_include(@completor.completion_candidates(';', 'show_s', '', bind: binding), 'show_source') + end end class TypeCompletorIntegrationTest < IntegrationTestCase From 9c868d2c15ea464fc5774795cfc6f76ffc4a7f71 Mon Sep 17 00:00:00 2001 From: tompng Date: Sun, 18 Feb 2024 02:46:02 +0900 Subject: [PATCH 04/13] Add test for ExtendCommandBundle.def_extend_command --- test/irb/test_command.rb | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index b01f50e6c..e442b14a9 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -210,6 +210,52 @@ def test_irb_info_lang end end + class ExtendCommandBundleCompatibilityTest < CommandTestCase + class FooBarCommand < IRB::Command::Base + category 'FooBarCategory' + description 'foobar_description' + def execute(_arg) + puts "FooBar executed" + end + end + + def setup + super + execute_lines("show_cmds\n") # To ensure command initialization is done + @EXTEND_COMMANDS_backup = IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).dup + @cvars_backup = IRB::ExtendCommandBundle.class_variables.to_h do |cvar| + [cvar, IRB::ExtendCommandBundle.class_variable_get(cvar)] + end + IRB::Command.const_set :FooBarCommand, FooBarCommand + end + + def teardown + super + IRB::ExtendCommandBundle.instance_variable_set(:@EXTEND_COMMANDS, @EXTEND_COMMANDS_backup) + @cvars_backup.each do |cvar, value| + IRB::ExtendCommandBundle.class_variable_set(cvar, value) + end + IRB::Command.send(:remove_const, :FooBarCommand) + end + + def test_def_extend_command + command = [:foobar, :FooBarCommand, nil, [:fbalias, IRB::ExtendCommandBundle::OVERRIDE_ALL]] + IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).push(command) + IRB::ExtendCommandBundle.def_extend_command(*command) + out, err = execute_lines("foobar\n") + assert_empty err + assert_include(out, "FooBar executed") + + out, err = execute_lines("fbalias\n") + assert_empty err + assert_include(out, "FooBar executed") + + out, err = execute_lines("show_cmds\n") + assert_include(out, "FooBarCategory") + assert_include(out, "foobar_description") + end + end + class MeasureTest < CommandTestCase def test_measure conf = { From 9cfb4fda8b387d74fcf30f5e0e58a176ba1caf3d Mon Sep 17 00:00:00 2001 From: tompng Date: Thu, 22 Feb 2024 23:57:18 +0900 Subject: [PATCH 05/13] Add helper method install test --- test/irb/test_command.rb | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index e442b14a9..80c076433 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -555,12 +555,26 @@ def test_pushws_switches_to_new_workspace_and_pushes_the_current_one_to_the_stac end def test_pushws_extends_the_new_workspace_with_command_bundle + IRB::ExtendCommandBundle.module_eval do + def foobar; end + end out, err = execute_lines( "pushws Object.new", "self.singleton_class.ancestors" ) assert_empty err assert_include(out, "IRB::ExtendCommandBundle") + ensure + IRB::ExtendCommandBundle.remove_method :foobar + end + + def test_pushws_does_not_extend_command_bundle_by_default + out, err = execute_lines( + "pushws Object.new\n", + "self.singleton_class.ancestors" + ) + assert_empty err + assert_not_include(out, "IRB::ExtendCommandBundle") end def test_pushws_prints_workspace_stack_when_no_arg_is_given @@ -1001,4 +1015,28 @@ def test_history_grep end + class HelperMethodInsallTest < CommandTestCase + def test_extend_command_bundle_not_installed_by_default + out, err = execute_lines("self.singleton_class.ancestors") + assert_empty err + assert_not_include(out, 'IRB::ExtendCommandBundle') + end + + def test_helper_method_install + IRB::ExtendCommandBundle.module_eval do + def foobar + "test_helper_method_foobar" + end + end + out, err = execute_lines("self.singleton_class.ancestors") + assert_empty err + assert_include(out, "IRB::ExtendCommandBundle") + + out, err = execute_lines("foobar.upcase") + assert_empty err + assert_include(out, '=> "TEST_HELPER_METHOD_FOOBAR"') + ensure + IRB::ExtendCommandBundle.remove_method :foobar + end + end end From 59b52a7fe0f8ee340d5e4e526137ac63ce141724 Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 1 Apr 2024 21:43:25 +0900 Subject: [PATCH 06/13] Remove spaces in command input parse --- lib/irb.rb | 2 +- test/irb/test_command.rb | 66 ++++++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index a97fefdd9..beea76c8f 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1124,7 +1124,7 @@ def build_statement(code) COMMAND_LIKE_ASSIGN_REGEXP = /\A[a-z_]\w* #{Regexp.union(ASSIGN_OPERATORS)}( |$)/ def parse_command(code) - command_name, arg = code.strip.split(/\s/, 2) + command_name, arg = code.strip.split(/\s+/, 2) return unless code.lines.size == 1 && command_name return if COMMAND_LIKE_ASSIGN_REGEXP.match?(code) diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index 80c076433..2c7d04ff8 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -210,15 +210,7 @@ def test_irb_info_lang end end - class ExtendCommandBundleCompatibilityTest < CommandTestCase - class FooBarCommand < IRB::Command::Base - category 'FooBarCategory' - description 'foobar_description' - def execute(_arg) - puts "FooBar executed" - end - end - + class CustomCommandTestCase < CommandTestCase def setup super execute_lines("show_cmds\n") # To ensure command initialization is done @@ -226,7 +218,6 @@ def setup @cvars_backup = IRB::ExtendCommandBundle.class_variables.to_h do |cvar| [cvar, IRB::ExtendCommandBundle.class_variable_get(cvar)] end - IRB::Command.const_set :FooBarCommand, FooBarCommand end def teardown @@ -235,6 +226,61 @@ def teardown @cvars_backup.each do |cvar, value| IRB::ExtendCommandBundle.class_variable_set(cvar, value) end + end + end + + class CommandArgTest < CustomCommandTestCase + class PrintArgCommand < IRB::Command::Base + category 'CommandTest' + description 'print_command_arg' + def execute(arg) + puts "arg=#{arg.inspect}" + end + end + + def test_arg + IRB::Command.const_set :PrintArgCommand, PrintArgCommand + IRB::ExtendCommandBundle.def_extend_command(:print_arg, :PrintArgCommand, nil, [:pa, IRB::ExtendCommandBundle::OVERRIDE_ALL]) + out, err = execute_lines("print_arg\n") + assert_empty err + assert_include(out, 'arg=""') + + out, err = execute_lines("print_arg \n") + assert_empty err + assert_include(out, 'arg=""') + + out, err = execute_lines("print_arg a r g\n") + assert_empty err + assert_include(out, 'arg="a r g"') + + out, err = execute_lines("print_arg a r g \n") + assert_empty err + assert_include(out, 'arg="a r g"') + + out, err = execute_lines("pa a r g \n") + assert_empty err + assert_include(out, 'arg="a r g"') + ensure + IRB::Command.send(:remove_const, :PrintArgCommand) + end + end + + class ExtendCommandBundleCompatibilityTest < CustomCommandTestCase + class FooBarCommand < IRB::Command::Base + category 'FooBarCategory' + description 'foobar_description' + def execute(_arg) + puts "FooBar executed" + end + end + + def setup + super + IRB::Command.const_set :FooBarCommand, FooBarCommand + end + + def teardown + super IRB::Command.send(:remove_const, :FooBarCommand) end From 47c2210555e565bd0895e563275db0e263a59258 Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 1 Apr 2024 22:05:36 +0900 Subject: [PATCH 07/13] Remove command arg unquote in help command --- lib/irb/command/help.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index 5298d3047..fac8074a2 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -6,18 +6,17 @@ class Help < Base category "Help" description "List all available commands. Use `help ` to get information about a specific command." - def execute(arg) + def execute(command_name) # Accept string literal for backward compatibility - command_name = unwrap_string_literal(arg) content = - if command_name + if command_name.empty? + help_message + else if command_class = ExtendCommandBundle.load_command(command_name) command_class.help_message || command_class.description else "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" end - else - help_message end Pager.page_content(content) end From eff505ef48ac1ecadc50e84b33c9cd348b87abac Mon Sep 17 00:00:00 2001 From: tompng Date: Thu, 4 Apr 2024 20:38:38 +0900 Subject: [PATCH 08/13] Simplify Statement and handle execution in IRB::Irb --- lib/irb.rb | 10 +++++++++- lib/irb/statement.rb | 17 +++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index beea76c8f..283611ed3 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1028,7 +1028,15 @@ def eval_input return statement.code end - statement.execute(@context, line_no) + case statement + when Statement::EmptyInput + # Do nothing + 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 if @context.echo? && !statement.suppresses_echo? if statement.is_assignment? diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index 1f0f5dfd4..f71cf5605 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -37,10 +37,6 @@ def should_be_handled_by_debugger? def code "" end - - def execute(context, line_no) - nil - end end class Expression < Statement @@ -60,17 +56,15 @@ def should_be_handled_by_debugger? def is_assignment? @is_assignment end - - def execute(context, line_no) - context.evaluate(@code, line_no) - end end class Command < Statement + attr_reader :command_class, :arg + def initialize(original_code, command_class, arg) + @code = original_code @command_class = command_class @arg = arg - @code = original_code end def is_assignment? @@ -85,11 +79,6 @@ def should_be_handled_by_debugger? require_relative 'command/debug' IRB::Command::DebugCommand > @command_class end - - def execute(context, line_no) - ret = @command_class.execute(context, @arg) - context.set_last_value(ret) - end end end end From 93b77d279223ceff5a8b1420a06a7f06a604680c Mon Sep 17 00:00:00 2001 From: tompng Date: Thu, 4 Apr 2024 20:43:59 +0900 Subject: [PATCH 09/13] Tweak require, const name --- lib/irb/command/base.rb | 2 +- test/irb/test_type_completor.rb | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb index c75f8e18c..ff74b5fb3 100644 --- a/lib/irb/command/base.rb +++ b/lib/irb/command/base.rb @@ -65,7 +65,7 @@ def ruby_args(arg) # 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}" + @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" end || [[], {}] end diff --git a/test/irb/test_type_completor.rb b/test/irb/test_type_completor.rb index 5104afdd4..5ed8988b3 100644 --- a/test/irb/test_type_completor.rb +++ b/test/irb/test_type_completor.rb @@ -9,9 +9,7 @@ return end -require 'irb/context' -require 'irb/command' -require 'irb/completion' +require 'irb' require 'tempfile' require_relative './helper' From 6b6c24b1f220ea8f410b85f0b776007980e6110c Mon Sep 17 00:00:00 2001 From: tompng Date: Thu, 4 Apr 2024 21:11:25 +0900 Subject: [PATCH 10/13] Always install CommandBundle module to main object --- lib/irb/workspace.rb | 2 +- test/irb/test_command.rb | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb index 395edc4cc..1490f7b47 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -109,7 +109,7 @@ def initialize(*main) attr_reader :main def load_helper_methods_to_main - if ExtendCommandBundle.has_helper_method? && !(class< Date: Thu, 4 Apr 2024 21:28:00 +0900 Subject: [PATCH 11/13] Remove considering local variable in command or expression check --- lib/irb.rb | 12 +----------- lib/irb/command.rb | 6 +++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 283611ed3..180c091e2 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -1128,13 +1128,9 @@ def build_statement(code) end end - ASSIGN_OPERATORS = %w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=] - COMMAND_LIKE_ASSIGN_REGEXP = /\A[a-z_]\w* #{Regexp.union(ASSIGN_OPERATORS)}( |$)/ - def parse_command(code) command_name, arg = code.strip.split(/\s+/, 2) return unless code.lines.size == 1 && command_name - return if COMMAND_LIKE_ASSIGN_REGEXP.match?(code) arg ||= '' command = command_name.to_sym @@ -1144,15 +1140,9 @@ def parse_command(code) end # Check visibility - local_variable = @context.local_variables.include?(command) public_method = !!Kernel.instance_method(:public_method).bind_call(@context.main, command) rescue false private_method = !public_method && !!Kernel.instance_method(:method).bind_call(@context.main, command) rescue false - if ExtendCommandBundle.execute_as_command?( - command, - public_method: public_method, - private_method: private_method, - local_variable: local_variable - ) + if ExtendCommandBundle.execute_as_command?(command, public_method: public_method, private_method: private_method) [command, arg] end end diff --git a/lib/irb/command.rb b/lib/irb/command.rb index cd551b93f..1e8fe4156 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -198,14 +198,14 @@ def self.command_override_policies end.to_h end - def self.execute_as_command?(name, public_method:, private_method:, local_variable:) + def self.execute_as_command?(name, public_method:, private_method:) case command_override_policies[name] when OVERRIDE_ALL true when OVERRIDE_PRIVATE_ONLY - !public_method && !local_variable + !public_method when NO_OVERRIDE - !public_method && !private_method && !local_variable + !public_method && !private_method end end From 1df08b75f7f8444f17d65f0c02c11fe1dac3851c Mon Sep 17 00:00:00 2001 From: tompng Date: Mon, 8 Apr 2024 12:57:03 +0900 Subject: [PATCH 12/13] Remove unused method, tweak --- lib/irb/command.rb | 4 ---- lib/irb/command/ls.rb | 6 +++--- lib/irb/statement.rb | 4 ---- test/irb/test_command.rb | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/irb/command.rb b/lib/irb/command.rb index 1e8fe4156..43cbda36b 100644 --- a/lib/irb/command.rb +++ b/lib/irb/command.rb @@ -209,10 +209,6 @@ def self.execute_as_command?(name, public_method:, private_method:) end end - def self.has_helper_method? - IRB::ExtendCommandBundle.instance_methods.any? - end - def self.command_names command_override_policies.keys.map(&:to_s) end diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb index 3cf0d213b..f6b196486 100644 --- a/lib/irb/command/ls.rb +++ b/lib/irb/command/ls.rb @@ -21,11 +21,11 @@ class Ls < Base HELP_MESSAGE def execute(arg) - if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) - if match[:args].empty? + if match = arg.match(/\A(?.+\s|)(-g|-G)\s+(?.+)$/) + if match[:target].empty? use_main = true else - obj = @irb_context.workspace.binding.eval(match[:args]) + obj = @irb_context.workspace.binding.eval(match[:target]) end grep = Regexp.new(match[:grep]) else diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index f71cf5605..a3391c12a 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -16,10 +16,6 @@ def should_be_handled_by_debugger? raise NotImplementedError end - def execute(context, line_no) - raise NotImplementedError - end - class EmptyInput < Statement def is_assignment? false diff --git a/test/irb/test_command.rb b/test/irb/test_command.rb index a72c492a5..ca90ec92f 100644 --- a/test/irb/test_command.rb +++ b/test/irb/test_command.rb @@ -213,7 +213,7 @@ def test_irb_info_lang class CustomCommandTestCase < CommandTestCase def setup super - execute_lines("show_cmds\n") # To ensure command initialization is done + execute_lines("help\n") # To ensure command initialization is done @EXTEND_COMMANDS_backup = IRB::ExtendCommandBundle.instance_variable_get(:@EXTEND_COMMANDS).dup @cvars_backup = IRB::ExtendCommandBundle.class_variables.to_h do |cvar| [cvar, IRB::ExtendCommandBundle.class_variable_get(cvar)] From 128f15623ff25f378a024c09a9f2ab0ebf28869d Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Thu, 11 Apr 2024 01:44:35 +0900 Subject: [PATCH 13/13] Remove outdated comment for help command arg Co-authored-by: Stan Lo --- lib/irb/command/help.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb index fac8074a2..c9f16e05b 100644 --- a/lib/irb/command/help.rb +++ b/lib/irb/command/help.rb @@ -7,7 +7,6 @@ class Help < Base description "List all available commands. Use `help ` to get information about a specific command." def execute(command_name) - # Accept string literal for backward compatibility content = if command_name.empty? help_message