diff --git a/lib/irb.rb b/lib/irb.rb index daa0d64f2..2c1331177 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -915,7 +915,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 @@ -935,7 +935,7 @@ def debug_break def debug_readline(binding) workspace = IRB::WorkSpace.new(binding) context.workspace = workspace - context.workspace.load_commands_to_main + context.workspace.load_helper_methods_to_main @line_no += 1 # When users run: @@ -1002,7 +1002,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? @@ -1058,9 +1058,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 @@ -1086,23 +1084,40 @@ def each_top_level_statement def build_statement(code) 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_class, arg = parse_command(code)) + 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_or_alias, arg = code.strip.split(/\s/, 2) + return unless code.lines.size == 1 && command_or_alias + return if COMMAND_LIKE_ASSIGN_REGEXP.match?(code) + + # Check visibility + local_variable = @context.local_variables.include?(command_or_alias.to_sym) + public_method = !!@context.main.public_method(command_or_alias) rescue false + private_method = !public_method && !!@context.main.method(command_or_alias) rescue false + return unless ExtendCommandBundle.execute_as_command?( + command_or_alias, + public_method: public_method, + private_method: private_method, + local_variable: local_variable + ) + + command_name = @context.command_aliases[command_or_alias.to_sym] || command_or_alias + command_class = ExtendCommandBundle.load_command(command_name) + [command_class, arg] if command_class + end + + def command?(code) + !!parse_command(code) end def configure_io @@ -1120,8 +1135,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/cmd/backtrace.rb b/lib/irb/cmd/backtrace.rb index f63289461..7bd4a403b 100644 --- a/lib/irb/cmd/backtrace.rb +++ b/lib/irb/cmd/backtrace.rb @@ -7,12 +7,8 @@ module IRB module ExtendCommand 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}") end end end diff --git a/lib/irb/cmd/break.rb b/lib/irb/cmd/break.rb index df259a90c..766626c86 100644 --- a/lib/irb/cmd/break.rb +++ b/lib/irb/cmd/break.rb @@ -7,12 +7,8 @@ module IRB module ExtendCommand 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}") end end end diff --git a/lib/irb/cmd/catch.rb b/lib/irb/cmd/catch.rb index 40b62c753..6cae61270 100644 --- a/lib/irb/cmd/catch.rb +++ b/lib/irb/cmd/catch.rb @@ -7,12 +7,8 @@ module IRB module ExtendCommand 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}") end end end diff --git a/lib/irb/cmd/chws.rb b/lib/irb/cmd/chws.rb index 31045f9bb..e91673033 100644 --- a/lib/irb/cmd/chws.rb +++ b/lib/irb/cmd/chws.rb @@ -16,7 +16,7 @@ class CurrentWorkingWorkspace < Nop category "Workspace" description "Show the current workspace." - def execute(*obj) + def execute_as_ruby(*obj) irb_context.main end end @@ -25,7 +25,7 @@ class ChangeWorkspace < Nop category "Workspace" description "Change the current workspace to an object." - def execute(*obj) + def execute_as_ruby(*obj) irb_context.change_workspace(*obj) irb_context.main end diff --git a/lib/irb/cmd/context.rb b/lib/irb/cmd/context.rb new file mode 100644 index 000000000..706517974 --- /dev/null +++ b/lib/irb/cmd/context.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_relative "nop" + +module IRB + module ExtendCommand + class Context < Nop + 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/cmd/continue.rb b/lib/irb/cmd/continue.rb index 9136177ee..b5d15adb6 100644 --- a/lib/irb/cmd/continue.rb +++ b/lib/irb/cmd/continue.rb @@ -7,8 +7,8 @@ module IRB module ExtendCommand class Continue < DebugCommand - def execute(*args) - super(do_cmds: ["continue", *args].join(" ")) + def execute_as_ruby(*args) + execute_debug_command(do_cmds: ["continue", *args].join(" ")) end end end diff --git a/lib/irb/cmd/debug.rb b/lib/irb/cmd/debug.rb index e236084ca..fc99f2b90 100644 --- a/lib/irb/cmd/debug.rb +++ b/lib/irb/cmd/debug.rb @@ -14,7 +14,11 @@ class Debug < Nop binding.method(:irb).source_location.first, ].map { |file| /\A#{Regexp.escape(file)}:\d+:in `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/cmd/delete.rb b/lib/irb/cmd/delete.rb index aeb26d257..23e5facb6 100644 --- a/lib/irb/cmd/delete.rb +++ b/lib/irb/cmd/delete.rb @@ -7,8 +7,8 @@ module IRB module ExtendCommand class Delete < DebugCommand - def execute(*args) - super(pre_cmds: ["delete", *args].join(" ")) + def execute_as_ruby(*arg) + execute_debug_command(pre_cmds: ["delete", *args].join(" ")) end end end diff --git a/lib/irb/cmd/edit.rb b/lib/irb/cmd/edit.rb index 69606beea..30dcdc7a6 100644 --- a/lib/irb/cmd/edit.rb +++ b/lib/irb/cmd/edit.rb @@ -10,19 +10,8 @@ class Edit < Nop category "Misc" description 'Open a file with the editor command defined with `ENV["VISUAL"]` or `ENV["EDITOR"]`.' - 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) + path = unwrap_string_literal(arg) if path.nil? && (irb_path = @irb_context.irb_path) path = irb_path diff --git a/lib/irb/cmd/exit.rb b/lib/irb/cmd/exit.rb new file mode 100644 index 000000000..d02b99322 --- /dev/null +++ b/lib/irb/cmd/exit.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require_relative "nop" + +module IRB + module ExtendCommand + class Exit < Nop + category "IRB" + description "Quits the current irb context." + + def execute(arg) + IRB.CurrentContext.exit(arg.to_i) + end + end + end +end diff --git a/lib/irb/cmd/finish.rb b/lib/irb/cmd/finish.rb index 29f100feb..83a8aa93a 100644 --- a/lib/irb/cmd/finish.rb +++ b/lib/irb/cmd/finish.rb @@ -7,8 +7,8 @@ module IRB module ExtendCommand class Finish < DebugCommand - def execute(*args) - super(do_cmds: ["finish", *args].join(" ")) + def execute_as_ruby(*args) + execute_debug_command(do_cmds: ["finish", *args].join(" ")) end end end diff --git a/lib/irb/cmd/help.rb b/lib/irb/cmd/help.rb index 64b885c38..3fa65998a 100644 --- a/lib/irb/cmd/help.rb +++ b/lib/irb/cmd/help.rb @@ -14,7 +14,7 @@ class Help < ShowDoc For command help, please use `show_cmds` for now. MSG - def execute(*names) + def execute(arg) warn DEPRECATION_MESSAGE super end diff --git a/lib/irb/cmd/history.rb b/lib/irb/cmd/history.rb index 5b712fa44..61cd6fb46 100644 --- a/lib/irb/cmd/history.rb +++ b/lib/irb/cmd/history.rb @@ -12,14 +12,12 @@ class History < Nop 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/cmd/info.rb b/lib/irb/cmd/info.rb index 2c0a32b34..48b3c887e 100644 --- a/lib/irb/cmd/info.rb +++ b/lib/irb/cmd/info.rb @@ -7,12 +7,8 @@ module IRB module ExtendCommand 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}") end end end diff --git a/lib/irb/cmd/irb_info.rb b/lib/irb/cmd/irb_info.rb index 5b905a09b..f4a7fe9ac 100644 --- a/lib/irb/cmd/irb_info.rb +++ b/lib/irb/cmd/irb_info.rb @@ -10,7 +10,7 @@ class IrbInfo < Nop 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/cmd/load.rb b/lib/irb/cmd/load.rb index a3e797a7e..8499a2faa 100644 --- a/lib/irb/cmd/load.rb +++ b/lib/irb/cmd/load.rb @@ -23,7 +23,7 @@ class Load < LoaderCommand category "IRB" description "Load a Ruby file." - def execute(file_name = nil, priv = nil) + def execute_as_ruby(file_name = nil, priv = nil) raise_cmd_argument_error unless file_name irb_load(file_name, priv) end @@ -32,7 +32,7 @@ def execute(file_name = nil, priv = nil) class Require < LoaderCommand category "IRB" description "Require a Ruby file." - def execute(file_name = nil) + def execute_as_ruby(file_name = nil) raise_cmd_argument_error unless file_name rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") @@ -65,7 +65,7 @@ class Source < LoaderCommand category "IRB" description "Loads a given file in the current session." - def execute(file_name = nil) + def execute_as_ruby(file_name = nil) raise_cmd_argument_error unless file_name source_file(file_name) diff --git a/lib/irb/cmd/ls.rb b/lib/irb/cmd/ls.rb index 791b1c1b2..d6d214530 100644 --- a/lib/irb/cmd/ls.rb +++ b/lib/irb/cmd/ls.rb @@ -14,20 +14,20 @@ class Ls < Nop category "Context" description "Show methods, constants, and variables. `-g [query]` or `-G [query]` allows you to filter out the output." - 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+(?.+)$/) + arg = @irb_context.workspace.binding.eval(match[:args]) + grep = Regexp.new(match[:grep]) + (arg,), _kwargs = ruby_args(arg) else - args + (arg,), kwargs = ruby_args(arg) + grep = kwargs[:grep] 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 : [] + obj = arg.nil? ? irb_context.workspace.main : arg + locals = arg.nil? ? 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) diff --git a/lib/irb/cmd/measure.rb b/lib/irb/cmd/measure.rb index 4e1125a0a..ff404e4ad 100644 --- a/lib/irb/cmd/measure.rb +++ b/lib/irb/cmd/measure.rb @@ -12,15 +12,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|\{/) 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/cmd/next.rb b/lib/irb/cmd/next.rb index d29c82e7f..03fd4ecc8 100644 --- a/lib/irb/cmd/next.rb +++ b/lib/irb/cmd/next.rb @@ -7,8 +7,8 @@ module IRB module ExtendCommand class Next < DebugCommand - def execute(*args) - super(do_cmds: ["next", *args].join(" ")) + def execute_as_ruby(*args) + execute_debug_command(do_cmds: ["next", *args].join(" ")) end end end diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb index 7fb197c51..a71bb4f78 100644 --- a/lib/irb/cmd/nop.rb +++ b/lib/irb/cmd/nop.rb @@ -10,6 +10,10 @@ module IRB module ExtendCommand class CommandArgumentError < StandardError; end + def self.extract_ruby_args(*args, **kwargs) + throw :EXTRACT_RUBY_ARGS, [args, kwargs] + end + class Nop class << self def category(category = nil) @@ -21,18 +25,16 @@ def description(description = nil) @description = description if description @description end - - private - - def string_literal?(args) - sexp = Ripper.sexp(args) - sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - end end - def self.execute(irb_context, *opts, **kwargs, &block) + def self.execute(irb_context, arg) command = new(irb_context) - command.execute(*opts, **kwargs, &block) + if command.respond_to?(:execute_as_ruby) + args, kwargs = command.ruby_args(arg) + command.execute_as_ruby(*args, **kwargs) + else + command.execute(arg) + end rescue CommandArgumentError => e puts e.message end @@ -43,7 +45,26 @@ def initialize(irb_context) attr_reader :irb_context - def execute(*opts) + def unwrap_string_literal(str) + return unless str + + 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) + # When arg includes `;` for example: "1, kw: (2; 3); 4", + # `IRB::ExtendCommand.extract_ruby_args 1, kw: (2; 3); 4` will return 4, but we want `[[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/cmd/pushws.rb b/lib/irb/cmd/pushws.rb index 59996ceb0..9902acd0b 100644 --- a/lib/irb/cmd/pushws.rb +++ b/lib/irb/cmd/pushws.rb @@ -15,7 +15,7 @@ class Workspaces < Nop category "Workspace" description "Show workspaces." - def execute(*obj) + def execute(_arg) irb_context.workspaces.collect{|ws| ws.main} end end @@ -24,9 +24,9 @@ class PushWorkspace < Workspaces category "Workspace" description "Push an object to the workspace stack." - def execute(*obj) + def execute_as_ruby(*obj) irb_context.push_workspace(*obj) - super + execute(nil) end end @@ -34,9 +34,9 @@ class PopWorkspace < Workspaces category "Workspace" description "Pop a workspace from the workspace stack." - def execute(*obj) + def execute_as_ruby(*obj) irb_context.pop_workspace(*obj) - super + execute(nil) end end end diff --git a/lib/irb/cmd/show_cmds.rb b/lib/irb/cmd/show_cmds.rb index a8d899e4a..770a39095 100644 --- a/lib/irb/cmd/show_cmds.rb +++ b/lib/irb/cmd/show_cmds.rb @@ -12,7 +12,7 @@ class ShowCmds < Nop category "IRB" description "List all available commands and their description." - def execute(*args) + def execute(_arg) commands_info = IRB::ExtendCommandBundle.all_commands_info commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } diff --git a/lib/irb/cmd/show_doc.rb b/lib/irb/cmd/show_doc.rb index 99dd9ab95..f45ad26a5 100644 --- a/lib/irb/cmd/show_doc.rb +++ b/lib/irb/cmd/show_doc.rb @@ -5,21 +5,11 @@ module IRB module ExtendCommand class ShowDoc < Nop - 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 "Enter the mode to look up RI documents." - def execute(*names) + def execute(arg) + name = unwrap_string_literal(arg) require 'rdoc/ri/driver' unless ShowDoc.const_defined?(:Ri) @@ -27,15 +17,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/cmd/show_source.rb b/lib/irb/cmd/show_source.rb index 826cb11ed..9562104e1 100644 --- a/lib/irb/cmd/show_source.rb +++ b/lib/irb/cmd/show_source.rb @@ -11,18 +11,8 @@ class ShowSource < Nop category "Context" description "Show the source code of a given method or constant." - 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) + 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/cmd/step.rb b/lib/irb/cmd/step.rb index 2bc74a9d7..53162e2ed 100644 --- a/lib/irb/cmd/step.rb +++ b/lib/irb/cmd/step.rb @@ -7,8 +7,8 @@ module IRB module ExtendCommand class Step < DebugCommand - def execute(*args) - super(do_cmds: ["step", *args].join(" ")) + def execute_as_ruby(*args) + execute_debug_command(do_cmds: ["step", *args].join(" ")) end end end diff --git a/lib/irb/cmd/subirb.rb b/lib/irb/cmd/subirb.rb index 5ffd64641..96e87acc8 100644 --- a/lib/irb/cmd/subirb.rb +++ b/lib/irb/cmd/subirb.rb @@ -11,10 +11,6 @@ module IRB module ExtendCommand class MultiIRBCommand < Nop - def execute(*args) - extend_irb_context - end - private def print_deprecated_warning @@ -38,7 +34,7 @@ class IrbCommand < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Start a child IRB." - def execute(*obj) + def execute_as_ruby(*obj) print_deprecated_warning if irb_context.with_debugger @@ -46,7 +42,7 @@ def execute(*obj) return end - super + extend_irb_context IRB.irb(nil, *obj) end end @@ -55,7 +51,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 @@ -63,7 +59,7 @@ def execute return end - super + extend_irb_context IRB.JobManager end end @@ -72,7 +68,7 @@ class Foreground < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Switches to the session of the given number." - def execute(key = nil) + def execute_as_ruby(key = nil) print_deprecated_warning if irb_context.with_debugger @@ -80,7 +76,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) @@ -91,7 +87,7 @@ class Kill < MultiIRBCommand category "Multi-irb (DEPRECATED)" description "Kills the session with the given number." - def execute(*keys) + def execute_as_ruby(*keys) print_deprecated_warning if irb_context.with_debugger @@ -99,7 +95,7 @@ def execute(*keys) return end - super + extend_irb_context IRB.JobManager.kill(*keys) end end diff --git a/lib/irb/cmd/whereami.rb b/lib/irb/cmd/whereami.rb index 8f56ba073..9f63bb900 100644 --- a/lib/irb/cmd/whereami.rb +++ b/lib/irb/cmd/whereami.rb @@ -10,7 +10,7 @@ class Whereami < Nop 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 c3690fcac..ebe593d71 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -611,17 +611,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/extend-command.rb b/lib/irb/extend-command.rb index 072069d4c..160bddc6a 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -7,41 +7,23 @@ module IRB # :nodoc: # 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 - # Quits the current irb context - # - # +ret+ is the optional signal or message to send to Context#exit - # - # Same as IRB.CurrentContext.exit. - def irb_exit(ret = 0) - irb_context.exit(ret) - end - - # 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], - [:irb_quit, :irb_exit, OVERRIDE_PRIVATE_ONLY], - [:exit, :irb_exit, OVERRIDE_PRIVATE_ONLY], - [:quit, :irb_exit, OVERRIDE_PRIVATE_ONLY], - ] - - @EXTEND_COMMANDS = [ + [ + :irb_context, :Context, "cmd/context", + [:context, NO_OVERRIDE], + [:conf, NO_OVERRIDE], + ], + [ + :irb_exit, :Exit, "cmd/exit", + [:exit, OVERRIDE_PRIVATE_ONLY], + [:quit, OVERRIDE_PRIVATE_ONLY], + [:irb_quit, OVERRIDE_PRIVATE_ONLY], + ], [ :irb_current_working_workspace, :CurrentWorkingWorkspace, "cmd/chws", [:cwws, NO_OVERRIDE], @@ -200,6 +182,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.to_sym] + when OVERRIDE_ALL, nil + 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_names ||= command_override_policies.keys.map(&:to_s) + end @@commands = [] @@ -242,79 +248,6 @@ def self.load_command(command) end 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::ExtendCommand::#{cmd_class}.execute(irb_context, *opts, **kwargs, &b) - end - ], nil, __FILE__, line - - for ali, flag in aliases - @ALIASES.push [ali, cmd_name, flag] - end - 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 # Extends methods for the Context module diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb index b12110600..4b15012b3 100644 --- a/lib/irb/statement.rb +++ b/lib/irb/statement.rb @@ -16,7 +16,7 @@ def should_be_handled_by_debugger? raise NotImplementedError end - def evaluable_code + def execute(context, line_no) raise NotImplementedError end @@ -38,17 +38,16 @@ def is_assignment? @is_assignment end - def evaluable_code - @code + def execute(context, line_no) + context.evaluate(@code, line_no) end end class Command < Statement - def initialize(code, command, arg, command_class) - @code = code - @command = command - @arg = arg + def initialize(original_code, command_class, arg) @command_class = command_class + @arg = arg + @code = original_code end def is_assignment? @@ -65,15 +64,9 @@ def should_be_handled_by_debugger? IRB::ExtendCommand::DebugCommand > @command_class || IRB::ExtendCommand::Help == @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 2bf3d5e0f..a4256de3d 100644 --- a/lib/irb/workspace.rb +++ b/lib/irb/workspace.rb @@ -108,8 +108,11 @@ 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? + # Extend (== pollute) the main object for compatibility + main.extend ExtendCommandBundle + end end # Evaluate the given +statements+ within the context of this workspace.