From 25cbe4313729d53cd8254dca8e3ce0022ab499eb Mon Sep 17 00:00:00 2001 From: Ryan Fitzgerald Date: Sat, 11 Aug 2012 17:22:29 -0700 Subject: [PATCH] Move all commands into individual files --- lib/pry.rb | 1 + lib/pry/commands.rb | 35 +- lib/pry/default_commands/amend_line.rb | 43 ++ lib/pry/default_commands/bang.rb | 8 + lib/pry/default_commands/bang_pry.rb | 5 + lib/pry/default_commands/cat.rb | 170 ++++++ lib/pry/default_commands/cd.rb | 135 +++-- lib/pry/default_commands/commands.rb | 62 --- lib/pry/default_commands/context.rb | 98 ---- lib/pry/default_commands/easter_eggs.rb | 168 +++--- lib/pry/default_commands/edit.rb | 137 +++++ lib/pry/default_commands/edit_method.rb | 179 +++++++ lib/pry/default_commands/editing.rb | 463 ----------------- lib/pry/default_commands/exit.rb | 41 ++ lib/pry/default_commands/exit_all.rb | 14 + lib/pry/default_commands/exit_program.rb | 9 + lib/pry/default_commands/find_method.rb | 277 +++++----- lib/pry/default_commands/gem_cd.rb | 20 + lib/pry/default_commands/gem_install.rb | 28 + lib/pry/default_commands/gem_list.rb | 31 ++ lib/pry/default_commands/gems.rb | 84 --- lib/pry/default_commands/gist.rb | 342 ++++++------ lib/pry/default_commands/help.rb | 224 ++++---- lib/pry/default_commands/hist.rb | 202 ++++---- lib/pry/default_commands/import_set.rb | 12 + lib/pry/default_commands/input_and_output.rb | 306 ----------- lib/pry/default_commands/install_command.rb | 47 ++ lib/pry/default_commands/introspection.rb | 488 ------------------ lib/pry/default_commands/jump_to.rb | 17 + lib/pry/default_commands/ls.rb | 482 +++++++++-------- lib/pry/default_commands/misc.rb | 38 -- lib/pry/default_commands/navigating_pry.rb | 110 ---- lib/pry/default_commands/nesting.rb | 13 + lib/pry/default_commands/play.rb | 84 +++ lib/pry/default_commands/pry_backtrace.rb | 21 + lib/pry/default_commands/pry_version.rb | 5 + lib/pry/default_commands/raise_up.rb | 23 + lib/pry/default_commands/reload_method.rb | 15 + lib/pry/default_commands/reset.rb | 6 + lib/pry/default_commands/ri.rb | 46 ++ lib/pry/default_commands/save_file.rb | 95 ++++ lib/pry/default_commands/shell_command.rb | 20 + lib/pry/default_commands/shell_mode.rb | 16 + lib/pry/default_commands/show_command.rb | 42 ++ lib/pry/default_commands/show_doc.rb | 133 +++++ lib/pry/default_commands/show_input.rb | 7 + lib/pry/default_commands/show_source.rb | 128 +++++ lib/pry/default_commands/simple_prompt.rb | 10 + lib/pry/default_commands/stat.rb | 28 + lib/pry/default_commands/switch_to.rb | 11 + lib/pry/default_commands/toggle_color.rb | 6 + lib/pry/default_commands/whereami.rb | 145 +++--- lib/pry/default_commands/wtf.rb | 36 ++ lib/pry/helpers.rb | 1 + .../helpers/module_introspection_helpers.rb | 106 ++++ test/test_command.rb | 2 +- test/test_command_integration.rb | 4 +- test/test_command_set.rb | 5 +- test/test_default_commands/test_help.rb | 3 +- 59 files changed, 2598 insertions(+), 2689 deletions(-) create mode 100644 lib/pry/default_commands/amend_line.rb create mode 100644 lib/pry/default_commands/bang.rb create mode 100644 lib/pry/default_commands/bang_pry.rb create mode 100644 lib/pry/default_commands/cat.rb delete mode 100644 lib/pry/default_commands/commands.rb delete mode 100644 lib/pry/default_commands/context.rb create mode 100644 lib/pry/default_commands/edit.rb create mode 100644 lib/pry/default_commands/edit_method.rb delete mode 100644 lib/pry/default_commands/editing.rb create mode 100644 lib/pry/default_commands/exit.rb create mode 100644 lib/pry/default_commands/exit_all.rb create mode 100644 lib/pry/default_commands/exit_program.rb create mode 100644 lib/pry/default_commands/gem_cd.rb create mode 100644 lib/pry/default_commands/gem_install.rb create mode 100644 lib/pry/default_commands/gem_list.rb delete mode 100644 lib/pry/default_commands/gems.rb create mode 100644 lib/pry/default_commands/import_set.rb delete mode 100644 lib/pry/default_commands/input_and_output.rb create mode 100644 lib/pry/default_commands/install_command.rb delete mode 100644 lib/pry/default_commands/introspection.rb create mode 100644 lib/pry/default_commands/jump_to.rb delete mode 100644 lib/pry/default_commands/misc.rb delete mode 100644 lib/pry/default_commands/navigating_pry.rb create mode 100644 lib/pry/default_commands/nesting.rb create mode 100644 lib/pry/default_commands/play.rb create mode 100644 lib/pry/default_commands/pry_backtrace.rb create mode 100644 lib/pry/default_commands/pry_version.rb create mode 100644 lib/pry/default_commands/raise_up.rb create mode 100644 lib/pry/default_commands/reload_method.rb create mode 100644 lib/pry/default_commands/reset.rb create mode 100644 lib/pry/default_commands/ri.rb create mode 100644 lib/pry/default_commands/save_file.rb create mode 100644 lib/pry/default_commands/shell_command.rb create mode 100644 lib/pry/default_commands/shell_mode.rb create mode 100644 lib/pry/default_commands/show_command.rb create mode 100644 lib/pry/default_commands/show_doc.rb create mode 100644 lib/pry/default_commands/show_input.rb create mode 100644 lib/pry/default_commands/show_source.rb create mode 100644 lib/pry/default_commands/simple_prompt.rb create mode 100644 lib/pry/default_commands/stat.rb create mode 100644 lib/pry/default_commands/switch_to.rb create mode 100644 lib/pry/default_commands/toggle_color.rb create mode 100644 lib/pry/default_commands/wtf.rb create mode 100644 lib/pry/helpers/module_introspection_helpers.rb diff --git a/lib/pry.rb b/lib/pry.rb index 64df36f4..fb9cd522 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -169,6 +169,7 @@ require "coderay" require "optparse" require "slop" require "rbconfig" +require 'tempfile' begin require 'readline' diff --git a/lib/pry/commands.rb b/lib/pry/commands.rb index 978ab7ac..ee53a930 100644 --- a/lib/pry/commands.rb +++ b/lib/pry/commands.rb @@ -1,29 +1,10 @@ -require "pry/default_commands/misc" -require "pry/default_commands/help" -require "pry/default_commands/gems" -require "pry/default_commands/context" -require "pry/default_commands/commands" -require "pry/default_commands/input_and_output" -require "pry/default_commands/introspection" -require "pry/default_commands/editing" -require "pry/default_commands/navigating_pry" -require "pry/default_commands/easter_eggs" +# Default commands used by Pry. +Pry::Commands = Pry::CommandSet.new -require "pry/extended_commands/experimental" - -class Pry - - # Default commands used by Pry. - Commands = Pry::CommandSet.new do - import DefaultCommands::Misc - import DefaultCommands::Help - import DefaultCommands::Gems - import DefaultCommands::Context - import DefaultCommands::NavigatingPry - import DefaultCommands::Editing - import DefaultCommands::InputAndOutput - import DefaultCommands::Introspection - import DefaultCommands::EasterEggs - import DefaultCommands::Commands - end +Dir[File.expand_path('../default_commands/*.rb', __FILE__)].each do |file| + require file +end + +Dir[File.expand_path('../extended_commands/*.rb', __FILE__)].each do |file| + require file end diff --git a/lib/pry/default_commands/amend_line.rb b/lib/pry/default_commands/amend_line.rb new file mode 100644 index 00000000..e0d51e7a --- /dev/null +++ b/lib/pry/default_commands/amend_line.rb @@ -0,0 +1,43 @@ +class Pry + Pry::Commands.create_command(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/) do + description "Amend a line of input in multi-line mode." + command_options :interpolate => false, :listing => "amend-line" + + banner <<-'BANNER' + Amend a line of input in multi-line mode. `amend-line N`, where the N in `amend-line N` represents line to replace. + + Can also specify a range of lines using `amend-line N..M` syntax. Passing '!' as replacement content deletes the line(s) instead. + e.g amend-line 1 puts 'hello world! # replace line 1' + e.g amend-line 1..4 ! # delete lines 1..4 + e.g amend-line 3 >puts 'goodbye' # insert before line 3 + e.g amend-line puts 'hello again' # no line number modifies immediately preceding line + BANNER + + def process + start_line_number, end_line_number, replacement_line = *args + + if eval_string.empty? + raise CommandError, "No input to amend." + end + + replacement_line = "" if !replacement_line + input_array = eval_string.each_line.to_a + + end_line_number = start_line_number.to_i if !end_line_number + line_range = start_line_number ? (one_index_number(start_line_number.to_i)..one_index_number(end_line_number.to_i)) : input_array.size - 1 + + # delete selected lines if replacement line is '!' + if arg_string == "!" + input_array.slice!(line_range) + elsif arg_string.start_with?(">") + insert_slot = Array(line_range).first + input_array.insert(insert_slot, arg_string[1..-1] + "\n") + else + input_array[line_range] = arg_string + "\n" + end + eval_string.replace input_array.join + run "show-input" + end + end +end + diff --git a/lib/pry/default_commands/bang.rb b/lib/pry/default_commands/bang.rb new file mode 100644 index 00000000..df02294d --- /dev/null +++ b/lib/pry/default_commands/bang.rb @@ -0,0 +1,8 @@ +class Pry + Pry::Commands.create_command "!", "Clear the input buffer. Useful if the parsing process goes wrong and you get stuck in the read loop.", :use_prefix => false do + def process + output.puts "Input buffer cleared!" + eval_string.replace("") + end + end +end diff --git a/lib/pry/default_commands/bang_pry.rb b/lib/pry/default_commands/bang_pry.rb new file mode 100644 index 00000000..c9cd7ba0 --- /dev/null +++ b/lib/pry/default_commands/bang_pry.rb @@ -0,0 +1,5 @@ +class Pry + Pry::Commands.command "!pry", "Start a Pry session on current self; this even works mid multi-line expression." do + target.pry + end +end diff --git a/lib/pry/default_commands/cat.rb b/lib/pry/default_commands/cat.rb new file mode 100644 index 00000000..c4e9b8f7 --- /dev/null +++ b/lib/pry/default_commands/cat.rb @@ -0,0 +1,170 @@ +class Pry + Pry::Commands.create_command "cat", "Show code from a file, Pry's input buffer, or the last exception." do + banner <<-USAGE + Usage: cat FILE + cat --ex [STACK_INDEX] + cat --in [INPUT_INDEX_OR_RANGE] + + cat is capable of showing part or all of a source file, the context of the + last exception, or an expression from Pry's input history. + + cat --ex defaults to showing the lines surrounding the location of the last + exception. Invoking it more than once travels up the exception's backtrace, + and providing a number shows the context of the given index of the backtrace. + USAGE + + def options(opt) + opt.on :ex, "Show the context of the last exception.", :optional_argument => true, :as => Integer + opt.on :i, :in, "Show one or more entries from Pry's expression history.", :optional_argument => true, :as => Range, :default => -5..-1 + + opt.on :s, :start, "Starting line (defaults to the first line).", :optional_argument => true, :as => Integer + opt.on :e, :end, "Ending line (defaults to the last line).", :optional_argument => true, :as => Integer + opt.on :l, :'line-numbers', "Show line numbers." + opt.on :t, :type, "The file type for syntax highlighting (e.g., 'ruby' or 'python').", :argument => true, :as => Symbol + + opt.on :f, :flood, "Do not use a pager to view text longer than one screen." + end + + def process + handler = case + when opts.present?(:ex) + method :process_ex + when opts.present?(:in) + method :process_in + else + method :process_file + end + + output = handler.call do |code| + code.code_type = opts[:type] || :ruby + + code.between(opts[:start] || 1, opts[:end] || -1). + with_line_numbers(opts.present?(:'line-numbers') || opts.present?(:ex)) + end + + render_output(output, opts) + end + + def process_ex + window_size = Pry.config.default_window_size || 5 + ex = _pry_.last_exception + + raise CommandError, "No exception found." unless ex + + if opts[:ex].nil? + bt_index = ex.bt_index + ex.inc_bt_index + else + bt_index = opts[:ex] + ex.bt_index = bt_index + ex.inc_bt_index + end + + ex_file, ex_line = ex.bt_source_location_for(bt_index) + + raise CommandError, "The given backtrace level is out of bounds." unless ex_file + + if RbxPath.is_core_path?(ex_file) + ex_file = RbxPath.convert_path_to_full(ex_file) + end + + set_file_and_dir_locals(ex_file) + + start_line = ex_line - window_size + start_line = 1 if start_line < 1 + end_line = ex_line + window_size + + header = unindent <<-HEADER + #{text.bold 'Exception:'} #{ex.class}: #{ex.message} + -- + #{text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold("level: #{bt_index}")} of backtrace (of #{ex.backtrace.size - 1}). + + HEADER + + code = yield(Pry::Code.from_file(ex_file). + between(start_line, end_line). + with_marker(ex_line)) + + "#{header}#{code}" + end + + def process_in + normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length) + input_items = _pry_.input_array[normalized_range] || [] + + zipped_items = normalized_range.zip(input_items).reject { |_, s| s.nil? || s == "" } + + unless zipped_items.length > 0 + raise CommandError, "No expressions found." + end + + if zipped_items.length > 1 + contents = "" + zipped_items.each do |i, s| + contents << "#{text.bold(i.to_s)}:\n" + contents << yield(Pry::Code(s).with_indentation(2)).to_s + end + else + contents = yield(Pry::Code(zipped_items.first.last)) + end + + contents + end + + def process_file + file_name = args.shift + + unless file_name + raise CommandError, "Must provide a filename, --in, or --ex." + end + + file_name, line_num = file_name.split(':') + file_name = File.expand_path(file_name) + set_file_and_dir_locals(file_name) + + code = yield(Pry::Code.from_file(file_name)) + + code.code_type = opts[:type] || detect_code_type_from_file(file_name) + if line_num + code = code.around(line_num.to_i, + Pry.config.default_window_size || 7) + end + + code + end + + def detect_code_type_from_file(file_name) + name, ext = File.basename(file_name).split('.', 2) + + if ext + case ext + when "py" + :python + when "rb", "gemspec", "rakefile", "ru" + :ruby + when "js" + return :javascript + when "yml", "prytheme" + :yaml + when "groovy" + :groovy + when "c" + :c + when "cpp" + :cpp + when "java" + :java + else + :text + end + else + case name + when "Rakefile", "Gemfile" + :ruby + else + :text + end + end + end + end +end diff --git a/lib/pry/default_commands/cd.rb b/lib/pry/default_commands/cd.rb index cbdbbab3..bd1c708e 100644 --- a/lib/pry/default_commands/cd.rb +++ b/lib/pry/default_commands/cd.rb @@ -1,81 +1,76 @@ class Pry - module DefaultCommands - Cd = Pry::CommandSet.new do - create_command "cd" do - group "Context" - description "Move into a new context (object or scope)." + Pry::Commands.create_command "cd" do + group "Context" + description "Move into a new context (object or scope)." - banner <<-BANNER - Usage: cd [OPTIONS] [--help] + banner <<-BANNER + Usage: cd [OPTIONS] [--help] - Move into new context (object or scope). As in unix shells use - `cd ..` to go back, `cd /` to return to Pry top-level and `cd -` - to toggle between last two scopes). - Complex syntax (e.g `cd ../@x/y`) also supported. + Move into new context (object or scope). As in unix shells use + `cd ..` to go back, `cd /` to return to Pry top-level and `cd -` + to toggle between last two scopes). + Complex syntax (e.g `cd ../@x/y`) also supported. - e.g: `cd @x` - e.g: `cd ..` - e.g: `cd /` - e.g: `cd -` + e.g: `cd @x` + e.g: `cd ..` + e.g: `cd /` + e.g: `cd -` - https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope - BANNER + https://github.com/pry/pry/wiki/State-navigation#wiki-Changing_scope + BANNER - def process - # Extract command arguments. Delete blank arguments like " ", but - # don't delete empty strings like "". - path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } - stack = _pry_.binding_stack.dup - old_stack = state.old_stack || [] - - # Special case when we only get a single "/", return to root. - if path.empty? - state.old_stack = stack.dup unless old_stack.empty? - stack = [stack.first] - end - - path.each_with_index do |context, i| - begin - case context.chomp - when "" - state.old_stack = stack.dup - stack = [stack.first] - when "::" - state.old_stack = stack.dup - stack.push(TOPLEVEL_BINDING) - when "." - next - when ".." - unless stack.size == 1 - # Don't rewrite old_stack if we're in complex expression - # (e.g.: `cd 1/2/3/../4). - state.old_stack = stack.dup if path.first == ".." - stack.pop - end - when "-" - unless old_stack.empty? - # Interchange current stack and old stack with each other. - stack, state.old_stack = state.old_stack, stack - end - else - state.old_stack = stack.dup if i == 0 - stack.push(Pry.binding_for(stack.last.eval(context))) - end - - rescue RescuableException => e - # Restore old stack to its initial values. - state.old_stack = old_stack - - output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" - output.puts e.inspect - return - end - end - - _pry_.binding_stack = stack - end + def process + # Extract command arguments. Delete blank arguments like " ", but + # don't delete empty strings like "". + path = arg_string.split(/\//).delete_if { |a| a =~ /\A\s+\z/ } + stack = _pry_.binding_stack.dup + old_stack = state.old_stack || [] + # Special case when we only get a single "/", return to root. + if path.empty? + state.old_stack = stack.dup unless old_stack.empty? + stack = [stack.first] end + + path.each_with_index do |context, i| + begin + case context.chomp + when "" + state.old_stack = stack.dup + stack = [stack.first] + when "::" + state.old_stack = stack.dup + stack.push(TOPLEVEL_BINDING) + when "." + next + when ".." + unless stack.size == 1 + # Don't rewrite old_stack if we're in complex expression + # (e.g.: `cd 1/2/3/../4). + state.old_stack = stack.dup if path.first == ".." + stack.pop + end + when "-" + unless old_stack.empty? + # Interchange current stack and old stack with each other. + stack, state.old_stack = state.old_stack, stack + end + else + state.old_stack = stack.dup if i == 0 + stack.push(Pry.binding_for(stack.last.eval(context))) + end + + rescue RescuableException => e + # Restore old stack to its initial values. + state.old_stack = old_stack + + output.puts "Bad object path: #{arg_string.chomp}. Failed trying to resolve: #{context}" + output.puts e.inspect + return + end + end + + _pry_.binding_stack = stack end end end diff --git a/lib/pry/default_commands/commands.rb b/lib/pry/default_commands/commands.rb deleted file mode 100644 index 5e3eb1c8..00000000 --- a/lib/pry/default_commands/commands.rb +++ /dev/null @@ -1,62 +0,0 @@ -class Pry - module DefaultCommands - Commands = Pry::CommandSet.new do - create_command "import-set", "Import a command set" do - group "Commands" - def process(command_set_name) - raise CommandError, "Provide a command set name" if command_set.nil? - - set = target.eval(arg_string) - _pry_.commands.import set - end - end - - create_command "install-command", "Install a disabled command." do |name| - group 'Commands' - - banner <<-BANNER - Usage: install-command COMMAND - - Installs the gems necessary to run the given COMMAND. You will generally not - need to run this unless told to by an error message. - BANNER - - def process(name) - require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller - command = find_command(name) - - if command_dependencies_met?(command.options) - output.puts "Dependencies for #{command.name} are met. Nothing to do." - return - end - - output.puts "Attempting to install `#{name}` command..." - gems_to_install = Array(command.options[:requires_gem]) - - gems_to_install.each do |g| - next if gem_installed?(g) - output.puts "Installing `#{g}` gem..." - - begin - Gem::DependencyInstaller.new.install(g) - rescue Gem::GemNotFoundException - raise CommandError, "Required Gem: `#{g}` not found. Aborting command installation." - end - end - - Gem.refresh - gems_to_install.each do |g| - begin - require g - rescue LoadError - raise CommandError, "Required Gem: `#{g}` installed but not found?!. Aborting command installation." - end - end - - output.puts "Installation of `#{name}` successful! Type `help #{name}` for information" - end - end - end - end -end - diff --git a/lib/pry/default_commands/context.rb b/lib/pry/default_commands/context.rb deleted file mode 100644 index 40436ed5..00000000 --- a/lib/pry/default_commands/context.rb +++ /dev/null @@ -1,98 +0,0 @@ -require "pry/default_commands/ls" -require "pry/default_commands/cd" -require "pry/default_commands/find_method" -require "pry/default_commands/whereami" - -class Pry - module DefaultCommands - - Context = Pry::CommandSet.new do - import Ls - import Cd - import FindMethod - import Whereami - - create_command "pry-backtrace", "Show the backtrace for the Pry session." do - banner <<-BANNER - Usage: pry-backtrace [OPTIONS] [--help] - - Show the backtrace for the position in the code where Pry was started. This can be used to - infer the behavior of the program immediately before it entered Pry, just like the backtrace - property of an exception. - - (NOTE: if you are looking for the backtrace of the most recent exception raised, - just type: `_ex_.backtrace` instead, see https://github.com/pry/pry/wiki/Special-Locals) - - e.g: pry-backtrace - BANNER - - def process - output.puts "\n#{text.bold('Backtrace:')}\n--\n" - stagger_output _pry_.backtrace.join("\n") - end - end - - command "reset", "Reset the REPL to a clean state." do - output.puts "Pry reset." - exec "pry" - end - - create_command(/wtf([?!]*)/, "Show the backtrace of the most recent exception") do - options :listing => 'wtf?' - - banner <<-BANNER - Show's a few lines of the backtrace of the most recent exception (also available - as _ex_.backtrace). - - If you want to see more lines, add more question marks or exclamation marks: - - e.g. - pry(main)> wtf? - pry(main)> wtf?!???!?!? - - To see the entire backtrace, pass the -v/--verbose flag: - - e.g. - pry(main)> wtf -v - BANNER - - def options(opt) - opt.on(:v, :verbose, "Show the full backtrace.") - end - - def process - raise Pry::CommandError, "No most-recent exception" unless _pry_.last_exception - - output.puts "#{text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception}\n--" - if opts.verbose? - output.puts Code.new(_pry_.last_exception.backtrace, 0, :text).with_line_numbers.to_s - else - output.puts Code.new(_pry_.last_exception.backtrace.first([captures[0].size, 0.5].max * 10), 0, :text).with_line_numbers.to_s - end - end - end - - # N.B. using a regular expresion here so that "raise-up 'foo'" does the right thing. - create_command(/raise-up(!?\b.*)/, :listing => 'raise-up') do - description "Raise an exception out of the current pry instance." - banner <<-BANNER - Raise up, like exit, allows you to quit pry. Instead of returning a value however, it raises an exception. - If you don't provide the exception to be raised, it will use the most recent exception (in pry _ex_). - - e.g. `raise-up "get-me-out-of-here"` is equivalent to: - `raise "get-me-out-of-here" - raise-up` - - When called as raise-up! (with an exclamation mark), this command raises the exception through - any nested prys you have created by "cd"ing into objects. - BANNER - - def process - return stagger_output help if captures[0] =~ /(-h|--help)\b/ - # Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble' in a rubyesque manner - target.eval("_pry_.raise_up#{captures[0]}") - end - end - end - end -end diff --git a/lib/pry/default_commands/easter_eggs.rb b/lib/pry/default_commands/easter_eggs.rb index cecc8b35..bc83feef 100644 --- a/lib/pry/default_commands/easter_eggs.rb +++ b/lib/pry/default_commands/easter_eggs.rb @@ -1,95 +1,91 @@ class Pry - module DefaultCommands + Pry::Commands.instance_eval do + command "nyan-cat", "", :requires_gem => ["nyancat"] do + run ".nyancat" + end - EasterEggs = Pry::CommandSet.new do + command(/!s\/(.*?)\/(.*?)/, "") do |source, dest| + eval_string.gsub!(/#{source}/) { dest } + run "show-input" + end - command "nyan-cat", "", :requires_gem => ["nyancat"] do - run ".nyancat" + command "get-naked", "" do + text = %{ + -- + We dont have to take our clothes off to have a good time. + We could dance & party all night And drink some cherry wine. + -- Jermaine Stewart } + output.puts text + text + end + + command "east-coker", "" do + text = %{ + -- + Now the light falls + Across the open field, leaving the deep lane + Shuttered with branches, dark in the afternoon, + Where you lean against a bank while a van passes, + And the deep lane insists on the direction + Into the village, in the electric heat + Hypnotised. In a warm haze the sultry light + Is absorbed, not refracted, by grey stone. + The dahlias sleep in the empty silence. + Wait for the early owl. + -- T.S Eliot + } + output.puts text + text + end + + command "cohen-poem", "" do + text = %{ + -- + When this American woman, + whose thighs are bound in casual red cloth, + comes thundering past my sitting place + like a forest-burning Mongol tribe, + the city is ravished + and brittle buildings of a hundred years + splash into the street; + and my eyes are burnt + for the embroidered Chinese girls, + already old, + and so small between the thin pines + on these enormous landscapes, + that if you turn your head + they are lost for hours. + -- Leonard Cohen + } + output.puts text + text + end + + command "test-ansi", "" do + prev_color = Pry.color + Pry.color = true + + picture = unindent <<-'EOS'.gsub(/[[:alpha:]!]/) { |s| text.red(s) } + ____ _______________________ + / \ | A W G | + / O O \ | N I O N ! | + | | | S S R I ! | + \ \__/ / __| I K ! | + \____/ \________________________| + EOS + + if windows_ansi? + move_up = proc { |n| "\e[#{n}F" } + else + move_up = proc { |n| "\e[#{n}A\e[0G" } end - command(/!s\/(.*?)\/(.*?)/, "") do |source, dest| - eval_string.gsub!(/#{source}/) { dest } - run "show-input" - end + output.puts "\n" * 6 + output.puts picture.lines.map(&:chomp).reverse.join(move_up[1]) + output.puts "\n" * 6 + output.puts "** ENV['TERM'] is #{ENV['TERM']} **\n\n" - command "get-naked", "" do - text = %{ --- -We dont have to take our clothes off to have a good time. -We could dance & party all night And drink some cherry wine. --- Jermaine Stewart } - output.puts text - text - end - - command "east-coker", "" do - text = %{ --- -Now the light falls -Across the open field, leaving the deep lane -Shuttered with branches, dark in the afternoon, -Where you lean against a bank while a van passes, -And the deep lane insists on the direction -Into the village, in the electric heat -Hypnotised. In a warm haze the sultry light -Is absorbed, not refracted, by grey stone. -The dahlias sleep in the empty silence. -Wait for the early owl. --- T.S Eliot - } - output.puts text - text - end - - command "cohen-poem", "" do - text = %{ --- -When this American woman, -whose thighs are bound in casual red cloth, -comes thundering past my sitting place -like a forest-burning Mongol tribe, -the city is ravished -and brittle buildings of a hundred years -splash into the street; -and my eyes are burnt -for the embroidered Chinese girls, -already old, -and so small between the thin pines -on these enormous landscapes, -that if you turn your head -they are lost for hours. - -- Leonard Cohen - } - output.puts text - text -end - - command "test-ansi", "" do - prev_color = Pry.color - Pry.color = true - - picture = unindent <<-'EOS'.gsub(/[[:alpha:]!]/) { |s| text.red(s) } - ____ _______________________ - / \ | A W G | - / O O \ | N I O N ! | - | | | S S R I ! | - \ \__/ / __| I K ! | - \____/ \________________________| - EOS - - if windows_ansi? - move_up = proc { |n| "\e[#{n}F" } - else - move_up = proc { |n| "\e[#{n}A\e[0G" } - end - - output.puts "\n" * 6 - output.puts picture.lines.map(&:chomp).reverse.join(move_up[1]) - output.puts "\n" * 6 - output.puts "** ENV['TERM'] is #{ENV['TERM']} **\n\n" - - Pry.color = prev_color - end + Pry.color = prev_color end end end diff --git a/lib/pry/default_commands/edit.rb b/lib/pry/default_commands/edit.rb new file mode 100644 index 00000000..c9187228 --- /dev/null +++ b/lib/pry/default_commands/edit.rb @@ -0,0 +1,137 @@ +class Pry + Pry::Commands.create_command "edit" do + description "Invoke the default editor on a file." + + banner <<-BANNER + Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]|--in N] + + Open a text editor. When no FILE is given, edits the pry input buffer. + Ensure Pry.config.editor is set to your editor of choice. + + e.g: `edit sample.rb` + e.g: `edit sample.rb --line 105` + e.g: `edit --ex` + + https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command + BANNER + + def options(opt) + opt.on :e, :ex, "Open the file that raised the most recent exception (_ex_.file)", :optional_argument => true, :as => Integer + opt.on :i, :in, "Open a temporary file containing the Nth input expression. N may be a range.", :optional_argument => true, :as => Range, :default => -1..-1 + opt.on :t, :temp, "Open an empty temporary file" + opt.on :l, :line, "Jump to this line in the opened file", :argument => true, :as => Integer + opt.on :n, :"no-reload", "Don't automatically reload the edited code" + opt.on :c, :"current", "Open the current __FILE__ and at __LINE__ (as returned by `whereami`)." + opt.on :r, :reload, "Reload the edited code immediately (default for ruby files)" + end + + def process + if [opts.present?(:ex), opts.present?(:temp), opts.present?(:in), !args.empty?].count(true) > 1 + raise CommandError, "Only one of --ex, --temp, --in and FILE may be specified." + end + + if !opts.present?(:ex) && !opts.present?(:current) && args.empty? + # edit of local code, eval'd within pry. + process_local_edit + else + # edit of remote code, eval'd at top-level + process_remote_edit + end + end + + def process_i + case opts[:i] + when Range + (_pry_.input_array[opts[:i]] || []).join + when Fixnum + _pry_.input_array[opts[:i]] || "" + else + return output.puts "Not a valid range: #{opts[:i]}" + end + end + + def process_local_edit + content = case + when opts.present?(:temp) + "" + when opts.present?(:in) + process_i + when eval_string.strip != "" + eval_string + else + _pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || "" + end + + line = content.lines.count + + temp_file do |f| + f.puts(content) + f.flush + reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload + f.close(false) + invoke_editor(f.path, line, reload) + if reload + silence_warnings do + eval_string.replace(File.read(f.path)) + end + end + end + end + + def process_remote_edit + if opts.present?(:ex) + if _pry_.last_exception.nil? + raise CommandError, "No exception found." + end + + ex = _pry_.last_exception + bt_index = opts[:ex].to_i + + ex_file, ex_line = ex.bt_source_location_for(bt_index) + if ex_file && RbxPath.is_core_path?(ex_file) + file_name = RbxPath.convert_path_to_full(ex_file) + else + file_name = ex_file + end + + line = ex_line + + if file_name.nil? + raise CommandError, "Exception has no associated file." + end + + if Pry.eval_path == file_name + raise CommandError, "Cannot edit exceptions raised in REPL." + end + elsif opts.present?(:current) + file_name = target.eval("__FILE__") + line = target.eval("__LINE__") + else + + # break up into file:line + file_name = File.expand_path(args.first) + line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1 + end + + if not_a_real_file?(file_name) + raise CommandError, "#{file_name} is not a valid file name, cannot edit!" + end + + line = opts[:l].to_i if opts.present?(:line) + + reload = opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:'no-reload')) && !Pry.config.disable_auto_reload + + # Sanitize blanks. + sanitized_file_name = Shellwords.escape(file_name) + + invoke_editor(sanitized_file_name, line, reload) + set_file_and_dir_locals(sanitized_file_name) + + if reload + silence_warnings do + TOPLEVEL_BINDING.eval(File.read(file_name), file_name) + end + end + end + end +end diff --git a/lib/pry/default_commands/edit_method.rb b/lib/pry/default_commands/edit_method.rb new file mode 100644 index 00000000..eeb42253 --- /dev/null +++ b/lib/pry/default_commands/edit_method.rb @@ -0,0 +1,179 @@ +class Pry + Pry::Commands.create_command "edit-method" do + description "Edit the source code for a method." + + banner <<-BANNER + Usage: edit-method [OPTIONS] [METH] + + Edit the method METH in an editor. + Ensure Pry.config.editor is set to your editor of choice. + + e.g: `edit-method hello_method` + e.g: `edit-method Pry#rep` + e.g: `edit-method` + + https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_method + BANNER + + command_options :shellwords => false + + def options(opt) + method_options(opt) + opt.on :n, "no-reload", "Do not automatically reload the method's file after editing." + opt.on "no-jump", "Do not fast forward editor to first line of method." + opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch." + end + + def process + if !Pry.config.editor + raise CommandError, "No editor set!\nEnsure that #{text.bold("Pry.config.editor")} is set to your editor of choice." + end + + begin + @method = method_object + rescue MethodNotFound => err + end + + if opts.present?(:patch) || (@method && @method.dynamically_defined?) + if err + raise err # can't patch a non-method + end + + process_patch + else + if err && !File.exist?(target.eval('__FILE__')) + raise err # can't edit a non-file + end + + process_file + end + end + + def process_patch + lines = @method.source.lines.to_a + + lines[0] = definition_line_for_owner(lines[0]) + + temp_file do |f| + f.puts lines + f.flush + f.close(false) + invoke_editor(f.path, 0, true) + + source = wrap_for_nesting(wrap_for_owner(File.read(f.path))) + + if @method.alias? + with_method_transaction(original_name, @method.owner) do + Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING) + Pry.binding_for(@method.owner).eval("alias #{@method.name} #{original_name}") + end + else + Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING) + end + end + end + + def process_file + file, line = extract_file_and_line + + reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload + invoke_editor(file, opts["no-jump"] ? 0 : line, reload) + silence_warnings do + load file if reload + end + end + + protected + def extract_file_and_line + if @method + if @method.source_type == :c + raise CommandError, "Can't edit a C method." + else + [@method.source_file, @method.source_line] + end + else + [target.eval('__FILE__'), target.eval('__LINE__')] + end + end + + # Run some code ensuring that at the end target#meth_name will not have changed. + # + # When we're redefining aliased methods we will overwrite the method at the + # unaliased name (so that super continues to work). By wrapping that code in a + # transation we make that not happen, which means that alias_method_chains, etc. + # continue to work. + # + # @param [String] meth_name The method name before aliasing + # @param [Module] target The owner of the method + def with_method_transaction(meth_name, target) + target = Pry.binding_for(target) + temp_name = "__pry_#{meth_name}__" + + target.eval("alias #{temp_name} #{meth_name}") + yield + target.eval("alias #{meth_name} #{temp_name}") + ensure + target.eval("undef #{temp_name}") rescue nil + end + + # The original name of the method, if it's not present raise an error telling + # the user why we don't work. + # + def original_name + @method.original_name or raise CommandError, "Pry can only patch methods created with the `def` keyword." + end + + # Update the definition line so that it can be eval'd directly on the Method's + # owner instead of from the original context. + # + # In particular this takes `def self.foo` and turns it into `def foo` so that we + # don't end up creating the method on the singleton class of the singleton class + # by accident. + # + # This is necessarily done by String manipulation because we can't find out what + # syntax is needed for the argument list by ruby-level introspection. + # + # @param String The original definition line. e.g. def self.foo(bar, baz=1) + # @return String The new definition line. e.g. def foo(bar, baz=1) + def definition_line_for_owner(line) + if line =~ /^def (?:.*?\.)?#{Regexp.escape(original_name)}(?=[\(\s;]|$)/ + "def #{original_name}#{$'}" + else + raise CommandError, "Could not find original `def #{original_name}` line to patch." + end + end + + # Update the source code so that when it has the right owner when eval'd. + # + # This (combined with definition_line_for_owner) is backup for the case that + # wrap_for_nesting fails, to ensure that the method will stil be defined in + # the correct place. + # + # @param [String] source The source to wrap + # @return [String] + def wrap_for_owner(source) + Thread.current[:__pry_owner__] = @method.owner + source = "Thread.current[:__pry_owner__].class_eval do\n#{source}\nend" + end + + # Update the new source code to have the correct Module.nesting. + # + # This method uses syntactic analysis of the original source file to determine + # the new nesting, so that we can tell the difference between: + # + # class A; def self.b; end; end + # class << A; def b; end; end + # + # The resulting code should be evaluated in the TOPLEVEL_BINDING. + # + # @param [String] source The source to wrap. + # @return [String] + def wrap_for_nesting(source) + nesting = Pry::Code.from_file(@method.source_file).nesting_at(@method.source_line) + + (nesting + [source] + nesting.map{ "end" } + [""]).join("\n") + rescue Pry::Indent::UnparseableNestingError => e + source + end + end +end diff --git a/lib/pry/default_commands/editing.rb b/lib/pry/default_commands/editing.rb deleted file mode 100644 index 9c3e86b3..00000000 --- a/lib/pry/default_commands/editing.rb +++ /dev/null @@ -1,463 +0,0 @@ -require 'tempfile' -require 'shellwords' -require 'pry/default_commands/hist' - -class Pry - module DefaultCommands - - Editing = Pry::CommandSet.new do - import Hist - - create_command "!", "Clear the input buffer. Useful if the parsing process goes wrong and you get stuck in the read loop.", :use_prefix => false do - def process - output.puts "Input buffer cleared!" - eval_string.replace("") - end - end - - create_command "show-input", "Show the contents of the input buffer for the current multi-line expression." do - def process - output.puts Code.new(eval_string).with_line_numbers - end - end - - create_command "edit" do - description "Invoke the default editor on a file." - - banner <<-BANNER - Usage: edit [--no-reload|--reload] [--line LINE] [--temp|--ex|FILE[:LINE]|--in N] - - Open a text editor. When no FILE is given, edits the pry input buffer. - Ensure Pry.config.editor is set to your editor of choice. - - e.g: `edit sample.rb` - e.g: `edit sample.rb --line 105` - e.g: `edit --ex` - - https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_command - BANNER - - def options(opt) - opt.on :e, :ex, "Open the file that raised the most recent exception (_ex_.file)", :optional_argument => true, :as => Integer - opt.on :i, :in, "Open a temporary file containing the Nth input expression. N may be a range.", :optional_argument => true, :as => Range, :default => -1..-1 - opt.on :t, :temp, "Open an empty temporary file" - opt.on :l, :line, "Jump to this line in the opened file", :argument => true, :as => Integer - opt.on :n, :"no-reload", "Don't automatically reload the edited code" - opt.on :c, :"current", "Open the current __FILE__ and at __LINE__ (as returned by `whereami`)." - opt.on :r, :reload, "Reload the edited code immediately (default for ruby files)" - end - - def process - if [opts.present?(:ex), opts.present?(:temp), opts.present?(:in), !args.empty?].count(true) > 1 - raise CommandError, "Only one of --ex, --temp, --in and FILE may be specified." - end - - if !opts.present?(:ex) && !opts.present?(:current) && args.empty? - # edit of local code, eval'd within pry. - process_local_edit - else - # edit of remote code, eval'd at top-level - process_remote_edit - end - end - - def process_i - case opts[:i] - when Range - (_pry_.input_array[opts[:i]] || []).join - when Fixnum - _pry_.input_array[opts[:i]] || "" - else - return output.puts "Not a valid range: #{opts[:i]}" - end - end - - def process_local_edit - content = case - when opts.present?(:temp) - "" - when opts.present?(:in) - process_i - when eval_string.strip != "" - eval_string - else - _pry_.input_array.reverse_each.find{ |x| x && x.strip != "" } || "" - end - - line = content.lines.count - - temp_file do |f| - f.puts(content) - f.flush - reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload - f.close(false) - invoke_editor(f.path, line, reload) - if reload - silence_warnings do - eval_string.replace(File.read(f.path)) - end - end - end - end - - def process_remote_edit - if opts.present?(:ex) - if _pry_.last_exception.nil? - raise CommandError, "No exception found." - end - - ex = _pry_.last_exception - bt_index = opts[:ex].to_i - - ex_file, ex_line = ex.bt_source_location_for(bt_index) - if ex_file && RbxPath.is_core_path?(ex_file) - file_name = RbxPath.convert_path_to_full(ex_file) - else - file_name = ex_file - end - - line = ex_line - - if file_name.nil? - raise CommandError, "Exception has no associated file." - end - - if Pry.eval_path == file_name - raise CommandError, "Cannot edit exceptions raised in REPL." - end - elsif opts.present?(:current) - file_name = target.eval("__FILE__") - line = target.eval("__LINE__") - else - - # break up into file:line - file_name = File.expand_path(args.first) - line = file_name.sub!(/:(\d+)$/, "") ? $1.to_i : 1 - end - - if not_a_real_file?(file_name) - raise CommandError, "#{file_name} is not a valid file name, cannot edit!" - end - - line = opts[:l].to_i if opts.present?(:line) - - reload = opts.present?(:reload) || ((opts.present?(:ex) || file_name.end_with?(".rb")) && !opts.present?(:'no-reload')) && !Pry.config.disable_auto_reload - - # Sanitize blanks. - sanitized_file_name = Shellwords.escape(file_name) - - invoke_editor(sanitized_file_name, line, reload) - set_file_and_dir_locals(sanitized_file_name) - - if reload - silence_warnings do - TOPLEVEL_BINDING.eval(File.read(file_name), file_name) - end - end - end - end - - create_command "edit-method" do - description "Edit the source code for a method." - - banner <<-BANNER - Usage: edit-method [OPTIONS] [METH] - - Edit the method METH in an editor. - Ensure Pry.config.editor is set to your editor of choice. - - e.g: `edit-method hello_method` - e.g: `edit-method Pry#rep` - e.g: `edit-method` - - https://github.com/pry/pry/wiki/Editor-integration#wiki-Edit_method - BANNER - - command_options :shellwords => false - - def options(opt) - method_options(opt) - opt.on :n, "no-reload", "Do not automatically reload the method's file after editing." - opt.on "no-jump", "Do not fast forward editor to first line of method." - opt.on :p, :patch, "Instead of editing the method's file, try to edit in a tempfile and apply as a monkey patch." - end - - def process - if !Pry.config.editor - raise CommandError, "No editor set!\nEnsure that #{text.bold("Pry.config.editor")} is set to your editor of choice." - end - - begin - @method = method_object - rescue MethodNotFound => err - end - - if opts.present?(:patch) || (@method && @method.dynamically_defined?) - if err - raise err # can't patch a non-method - end - - process_patch - else - if err && !File.exist?(target.eval('__FILE__')) - raise err # can't edit a non-file - end - - process_file - end - end - - def process_patch - lines = @method.source.lines.to_a - - lines[0] = definition_line_for_owner(lines[0]) - - temp_file do |f| - f.puts lines - f.flush - f.close(false) - invoke_editor(f.path, 0, true) - - source = wrap_for_nesting(wrap_for_owner(File.read(f.path))) - - if @method.alias? - with_method_transaction(original_name, @method.owner) do - Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING) - Pry.binding_for(@method.owner).eval("alias #{@method.name} #{original_name}") - end - else - Pry.new(:input => StringIO.new(source)).rep(TOPLEVEL_BINDING) - end - end - end - - def process_file - file, line = extract_file_and_line - - reload = !opts.present?(:'no-reload') && !Pry.config.disable_auto_reload - invoke_editor(file, opts["no-jump"] ? 0 : line, reload) - silence_warnings do - load file if reload - end - end - - protected - def extract_file_and_line - if @method - if @method.source_type == :c - raise CommandError, "Can't edit a C method." - else - [@method.source_file, @method.source_line] - end - else - [target.eval('__FILE__'), target.eval('__LINE__')] - end - end - - # Run some code ensuring that at the end target#meth_name will not have changed. - # - # When we're redefining aliased methods we will overwrite the method at the - # unaliased name (so that super continues to work). By wrapping that code in a - # transation we make that not happen, which means that alias_method_chains, etc. - # continue to work. - # - # @param [String] meth_name The method name before aliasing - # @param [Module] target The owner of the method - def with_method_transaction(meth_name, target) - target = Pry.binding_for(target) - temp_name = "__pry_#{meth_name}__" - - target.eval("alias #{temp_name} #{meth_name}") - yield - target.eval("alias #{meth_name} #{temp_name}") - ensure - target.eval("undef #{temp_name}") rescue nil - end - - # The original name of the method, if it's not present raise an error telling - # the user why we don't work. - # - def original_name - @method.original_name or raise CommandError, "Pry can only patch methods created with the `def` keyword." - end - - # Update the definition line so that it can be eval'd directly on the Method's - # owner instead of from the original context. - # - # In particular this takes `def self.foo` and turns it into `def foo` so that we - # don't end up creating the method on the singleton class of the singleton class - # by accident. - # - # This is necessarily done by String manipulation because we can't find out what - # syntax is needed for the argument list by ruby-level introspection. - # - # @param String The original definition line. e.g. def self.foo(bar, baz=1) - # @return String The new definition line. e.g. def foo(bar, baz=1) - def definition_line_for_owner(line) - if line =~ /^def (?:.*?\.)?#{Regexp.escape(original_name)}(?=[\(\s;]|$)/ - "def #{original_name}#{$'}" - else - raise CommandError, "Could not find original `def #{original_name}` line to patch." - end - end - - # Update the source code so that when it has the right owner when eval'd. - # - # This (combined with definition_line_for_owner) is backup for the case that - # wrap_for_nesting fails, to ensure that the method will stil be defined in - # the correct place. - # - # @param [String] source The source to wrap - # @return [String] - def wrap_for_owner(source) - Thread.current[:__pry_owner__] = @method.owner - source = "Thread.current[:__pry_owner__].class_eval do\n#{source}\nend" - end - - # Update the new source code to have the correct Module.nesting. - # - # This method uses syntactic analysis of the original source file to determine - # the new nesting, so that we can tell the difference between: - # - # class A; def self.b; end; end - # class << A; def b; end; end - # - # The resulting code should be evaluated in the TOPLEVEL_BINDING. - # - # @param [String] source The source to wrap. - # @return [String] - def wrap_for_nesting(source) - nesting = Pry::Code.from_file(@method.source_file).nesting_at(@method.source_line) - - (nesting + [source] + nesting.map{ "end" } + [""]).join("\n") - rescue Pry::Indent::UnparseableNestingError => e - source - end - end - - create_command(/amend-line(?: (-?\d+)(?:\.\.(-?\d+))?)?/) do - description "Amend a line of input in multi-line mode." - command_options :interpolate => false, :listing => "amend-line" - - banner <<-'BANNER' - Amend a line of input in multi-line mode. `amend-line N`, where the N in `amend-line N` represents line to replace. - - Can also specify a range of lines using `amend-line N..M` syntax. Passing '!' as replacement content deletes the line(s) instead. - e.g amend-line 1 puts 'hello world! # replace line 1' - e.g amend-line 1..4 ! # delete lines 1..4 - e.g amend-line 3 >puts 'goodbye' # insert before line 3 - e.g amend-line puts 'hello again' # no line number modifies immediately preceding line - BANNER - - def process - start_line_number, end_line_number, replacement_line = *args - - if eval_string.empty? - raise CommandError, "No input to amend." - end - - replacement_line = "" if !replacement_line - input_array = eval_string.each_line.to_a - - end_line_number = start_line_number.to_i if !end_line_number - line_range = start_line_number ? (one_index_number(start_line_number.to_i)..one_index_number(end_line_number.to_i)) : input_array.size - 1 - - # delete selected lines if replacement line is '!' - if arg_string == "!" - input_array.slice!(line_range) - elsif arg_string.start_with?(">") - insert_slot = Array(line_range).first - input_array.insert(insert_slot, arg_string[1..-1] + "\n") - else - input_array[line_range] = arg_string + "\n" - end - eval_string.replace input_array.join - run "show-input" - end - end - - create_command "play" do - include Helpers::DocumentationHelpers - - description "Play back a string variable or a method or a file as input." - - banner <<-BANNER - Usage: play [OPTIONS] [--help] - - The play command enables you to replay code from files and methods as - if they were entered directly in the Pry REPL. Default action (no - options) is to play the provided string variable - - e.g: `play -i 20 --lines 1..3` - e.g: `play -m Pry#repl --lines 1..-1` - e.g: `play -f Rakefile --lines 5` - - https://github.com/pry/pry/wiki/User-Input#wiki-Play - BANNER - - attr_accessor :content - - def setup - self.content = "" - end - - def options(opt) - opt.on :m, :method, "Play a method's source.", :argument => true do |meth_name| - meth = get_method_or_raise(meth_name, target, {}) - self.content << meth.source - end - opt.on :d, :doc, "Play a method's documentation.", :argument => true do |meth_name| - meth = get_method_or_raise(meth_name, target, {}) - text.no_color do - self.content << process_comment_markup(meth.doc) - end - end - opt.on :c, :command, "Play a command's source.", :argument => true do |command_name| - command = find_command(command_name) - block = Pry::Method.new(command.block) - self.content << block.source - end - opt.on :f, :file, "Play a file.", :argument => true do |file| - self.content << File.read(File.expand_path(file)) - end - opt.on :l, :lines, "Only play a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1 - opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range. Note this can only replay pure Ruby code, not Pry commands.", :optional_argument => true, - :as => Range, :default => -5..-1 do |range| - input_expressions = _pry_.input_array[range] || [] - Array(input_expressions).each { |v| self.content << v } - end - opt.on :o, "open", 'When used with the -m switch, it plays the entire method except the last line, leaving the method definition "open". `amend-line` can then be used to modify the method.' - end - - def process - perform_play - run "show-input" unless Pry::Code.complete_expression?(eval_string) - end - - def process_non_opt - args.each do |arg| - begin - self.content << target.eval(arg) - rescue Pry::RescuableException - raise CommandError, "Problem when evaling #{arg}." - end - end - end - - def perform_play - process_non_opt - - if opts.present?(:lines) - self.content = restrict_to_lines(self.content, opts[:l]) - end - - if opts.present?(:open) - self.content = restrict_to_lines(self.content, 1..-2) - end - - eval_string << self.content - end - end - end - end -end diff --git a/lib/pry/default_commands/exit.rb b/lib/pry/default_commands/exit.rb new file mode 100644 index 00000000..15416081 --- /dev/null +++ b/lib/pry/default_commands/exit.rb @@ -0,0 +1,41 @@ +class Pry + Pry::Commands.create_command "exit" do + description "Pop the previous binding (does NOT exit program). Aliases: quit" + + banner <<-BANNER + Usage: exit [OPTIONS] [--help] + Aliases: quit + + It can be useful to exit a context with a user-provided value. For + instance an exit value can be used to determine program flow. + + e.g: `exit "pry this"` + e.g: `exit` + + https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value + BANNER + + command_options( + :keep_retval => true + ) + + def process + if _pry_.binding_stack.one? + _pry_.run_command "exit-all #{arg_string}" + else + # otherwise just pop a binding and return user supplied value + process_pop_and_return + end + end + + def process_pop_and_return + popped_object = _pry_.binding_stack.pop.eval('self') + + # return a user-specified value if given otherwise return the object + return target.eval(arg_string) unless arg_string.empty? + popped_object + end + end + + Pry::Commands.alias_command "quit", "exit" +end diff --git a/lib/pry/default_commands/exit_all.rb b/lib/pry/default_commands/exit_all.rb new file mode 100644 index 00000000..972281cd --- /dev/null +++ b/lib/pry/default_commands/exit_all.rb @@ -0,0 +1,14 @@ +class Pry + Pry::Commands.command "exit-all", "End the current Pry session (popping all bindings) and returning to caller. Accepts optional return value. Aliases: !!@" do + # calculate user-given value + exit_value = target.eval(arg_string) + + # clear the binding stack + _pry_.binding_stack.clear + + # break out of the repl loop + throw(:breakout, exit_value) + end + + Pry::Commands.alias_command "!!@", "exit-all" +end diff --git a/lib/pry/default_commands/exit_program.rb b/lib/pry/default_commands/exit_program.rb new file mode 100644 index 00000000..561b059b --- /dev/null +++ b/lib/pry/default_commands/exit_program.rb @@ -0,0 +1,9 @@ +class Pry + Pry::Commands.command "exit-program", "End the current program. Aliases: quit-program, !!!" do + Pry.save_history if Pry.config.history.should_save + Kernel.exit target.eval(arg_string).to_i + end + + Pry::Commands.alias_command "quit-program", "exit-program" + Pry::Commands.alias_command "!!!", "exit-program" +end diff --git a/lib/pry/default_commands/find_method.rb b/lib/pry/default_commands/find_method.rb index f8c383a2..3fb85a3c 100644 --- a/lib/pry/default_commands/find_method.rb +++ b/lib/pry/default_commands/find_method.rb @@ -1,168 +1,163 @@ class Pry - module DefaultCommands - FindMethod = Pry::CommandSet.new do + Pry::Commands.create_command "find-method" do + extend Pry::Helpers::BaseHelpers - create_command "find-method" do - extend Helpers::BaseHelpers + group "Context" - group "Context" + options :requires_gem => "ruby18_source_location" if mri_18? + options :shellwords => false - options :requires_gem => "ruby18_source_location" if mri_18? - options :shellwords => false + description "Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]" - description "Recursively search for a method within a Class/Module or the current namespace. find-method [-n | -c] METHOD [NAMESPACE]" + banner <<-BANNER + Usage: find-method [-n | -c] METHOD [NAMESPACE] - banner <<-BANNER - Usage: find-method [-n | -c] METHOD [NAMESPACE] + Recursively search for a method within a Class/Module or the current namespace. + Use the `-n` switch (the default) to search for methods whose name matches the given regex. + Use the `-c` switch to search for methods that contain the given code. - Recursively search for a method within a Class/Module or the current namespace. - Use the `-n` switch (the default) to search for methods whose name matches the given regex. - Use the `-c` switch to search for methods that contain the given code. + e.g find-method re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc. + e.g find-method -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace. + BANNER - e.g find-method re Pry # find all methods whose name match /re/ inside the Pry namespace. Matches Pry#repl, etc. - e.g find-method -c 'output.puts' Pry # find all methods that contain the code: output.puts inside the Pry namepsace. - BANNER + def setup + require 'ruby18_source_location' if mri_18? + end - def setup - require 'ruby18_source_location' if mri_18? + def options(opti) + opti.on :n, :name, "Search for a method by name" + opti.on :c, :content, "Search for a method based on content in Regex form" + end + + def process + return if args.size < 1 + pattern = ::Regexp.new args[0] + if args[1] + klass = target.eval(args[1]) + if !klass.is_a?(Module) + klass = klass.class end + else + klass = (target_self.is_a?(Module)) ? target_self : target_self.class + end - def options(opti) - opti.on :n, :name, "Search for a method by name" - opti.on :c, :content, "Search for a method based on content in Regex form" + matches = if opts.content? + content_search(pattern, klass) + else + name_search(pattern, klass) + end + + if matches.empty? + output.puts text.bold("No Methods Matched") + else + print_matches(matches, pattern) + end + + end + + private + + # pretty-print a list of matching methods. + # + # @param Array[Method] + def print_matches(matches, pattern) + grouped = matches.group_by(&:owner) + order = grouped.keys.sort_by{ |x| x.name || x.to_s } + + order.each do |klass| + output.puts text.bold(klass.name) + grouped[klass].each do |method| + header = method.name_with_owner + + extra = if opts.content? + header += ": " + colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}")) + else + "" + end + + output.puts header + extra end + end + end - def process - return if args.size < 1 - pattern = ::Regexp.new args[0] - if args[1] - klass = target.eval(args[1]) - if !klass.is_a?(Module) - klass = klass.class - end - else - klass = (target_self.is_a?(Module)) ? target_self : target_self.class - end + # Run the given block against every constant in the provided namespace. + # + # @param Module The namespace in which to start the search. + # @param Hash[Module,Boolean] The namespaces we've already visited (private) + # @yieldparam klazz Each class/module in the namespace. + # + def recurse_namespace(klass, done={}, &block) + return if !(Module === klass) || done[klass] - matches = if opts.content? - content_search(pattern, klass) - else - name_search(pattern, klass) - end + done[klass] = true - if matches.empty? - output.puts text.bold("No Methods Matched") - else - print_matches(matches, pattern) - end + yield klass + klass.constants.each do |name| + next if klass.autoload?(name) + begin + const = klass.const_get(name) + rescue RescuableException + # constant loading is an inexact science at the best of times, + # this often happens when a constant was .autoload? but someone + # tried to load it. It's now not .autoload? but will still raise + # a NameError when you access it. + else + recurse_namespace(const, done, &block) end + end + end - private + # Gather all the methods in a namespace that pass the given block. + # + # @param Module The namespace in which to search. + # @yieldparam Method The method to test + # @yieldreturn Boolean + # @return Array[Method] + # + def search_all_methods(namespace) + done = Hash.new{ |h,k| h[k] = {} } + matches = [] - # pretty-print a list of matching methods. - # - # @param Array[Method] - def print_matches(matches, pattern) - grouped = matches.group_by(&:owner) - order = grouped.keys.sort_by{ |x| x.name || x.to_s } + recurse_namespace(namespace) do |klass| + (Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).each do |method| + next if done[method.owner][method.name] + done[method.owner][method.name] = true - order.each do |klass| - output.puts text.bold(klass.name) - grouped[klass].each do |method| - header = method.name_with_owner - - extra = if opts.content? - header += ": " - colorize_code((method.source.split(/\n/).select {|x| x =~ pattern }).join("\n#{' ' * header.length}")) - else - "" - end - - output.puts header + extra - end - end + matches << method if yield method end + end - # Run the given block against every constant in the provided namespace. - # - # @param Module The namespace in which to start the search. - # @param Hash[Module,Boolean] The namespaces we've already visited (private) - # @yieldparam klazz Each class/module in the namespace. - # - def recurse_namespace(klass, done={}, &block) - return if !(Module === klass) || done[klass] + matches + end - done[klass] = true + # Search for all methods with a name that matches the given regex + # within a namespace. + # + # @param Regex The regex to search for + # @param Module The namespace to search + # @return Array[Method] + # + def name_search(regex, namespace) + search_all_methods(namespace) do |meth| + meth.name =~ regex + end + end - yield klass - - klass.constants.each do |name| - next if klass.autoload?(name) - begin - const = klass.const_get(name) - rescue RescuableException - # constant loading is an inexact science at the best of times, - # this often happens when a constant was .autoload? but someone - # tried to load it. It's now not .autoload? but will still raise - # a NameError when you access it. - else - recurse_namespace(const, done, &block) - end - end - end - - # Gather all the methods in a namespace that pass the given block. - # - # @param Module The namespace in which to search. - # @yieldparam Method The method to test - # @yieldreturn Boolean - # @return Array[Method] - # - def search_all_methods(namespace) - done = Hash.new{ |h,k| h[k] = {} } - matches = [] - - recurse_namespace(namespace) do |klass| - (Pry::Method.all_from_class(klass) + Pry::Method.all_from_obj(klass)).each do |method| - next if done[method.owner][method.name] - done[method.owner][method.name] = true - - matches << method if yield method - end - end - - matches - end - - # Search for all methods with a name that matches the given regex - # within a namespace. - # - # @param Regex The regex to search for - # @param Module The namespace to search - # @return Array[Method] - # - def name_search(regex, namespace) - search_all_methods(namespace) do |meth| - meth.name =~ regex - end - end - - # Search for all methods who's implementation matches the given regex - # within a namespace. - # - # @param Regex The regex to search for - # @param Module The namespace to search - # @return Array[Method] - # - def content_search(regex, namespace) - search_all_methods(namespace) do |meth| - begin - meth.source =~ regex - rescue RescuableException - false - end - end + # Search for all methods who's implementation matches the given regex + # within a namespace. + # + # @param Regex The regex to search for + # @param Module The namespace to search + # @return Array[Method] + # + def content_search(regex, namespace) + search_all_methods(namespace) do |meth| + begin + meth.source =~ regex + rescue RescuableException + false end end end diff --git a/lib/pry/default_commands/gem_cd.rb b/lib/pry/default_commands/gem_cd.rb new file mode 100644 index 00000000..7cfb4fc0 --- /dev/null +++ b/lib/pry/default_commands/gem_cd.rb @@ -0,0 +1,20 @@ +class Pry + Pry::Commands.create_command "gem-cd", "Change working directory to specified gem's directory.", :argument_required => true do |gem| + banner <<-BANNER + Usage: gem-cd GEM_NAME + + Change the current working directory to that in which the given gem is installed. + BANNER + + def process(gem) + specs = Gem::Specification.respond_to?(:each) ? Gem::Specification.find_all_by_name(gem) : Gem.source_index.find_name(gem) + spec = specs.sort { |a,b| Gem::Version.new(b.version) <=> Gem::Version.new(a.version) }.first + if spec + Dir.chdir(spec.full_gem_path) + output.puts(Dir.pwd) + else + raise CommandError, "Gem `#{gem}` not found." + end + end + end +end diff --git a/lib/pry/default_commands/gem_install.rb b/lib/pry/default_commands/gem_install.rb new file mode 100644 index 00000000..54a7e6cb --- /dev/null +++ b/lib/pry/default_commands/gem_install.rb @@ -0,0 +1,28 @@ +class Pry + Pry::Commands.create_command "gem-install", "Install a gem and refresh the gem cache.", :argument_required => true do |gem| + banner <<-BANNER + Usage: gem-install GEM_NAME + + Installs the given gem and refreshes the gem cache so that you can immediately 'require GEM_FILE' + BANNER + + def setup + require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller + end + + def process(gem) + begin + destination = File.writable?(Gem.dir) ? Gem.dir : Gem.user_dir + installer = Gem::DependencyInstaller.new :install_dir => destination + installer.install gem + rescue Errno::EACCES + raise CommandError, "Insufficient permissions to install `#{text.green gem}`." + rescue Gem::GemNotFoundException + raise CommandError, "Gem `#{text.green gem}` not found." + else + Gem.refresh + output.puts "Gem `#{text.green gem}` installed." + end + end + end +end diff --git a/lib/pry/default_commands/gem_list.rb b/lib/pry/default_commands/gem_list.rb new file mode 100644 index 00000000..6e88f2cd --- /dev/null +++ b/lib/pry/default_commands/gem_list.rb @@ -0,0 +1,31 @@ +class Pry + Pry::Commands.create_command "gem-list", "List and search installed gems." do |pattern| + banner <<-BANNER + Usage: gem-list [REGEX] + + List all installed gems, when a regex is provided, limit the output to those that + match the regex. + BANNER + + def process(pattern=nil) + pattern = Regexp.compile(pattern || '') + gems = if Gem::Specification.respond_to?(:each) + Gem::Specification.select{|spec| spec.name =~ pattern }.group_by(&:name) + else + Gem.source_index.gems.values.group_by(&:name).select { |gemname, specs| gemname =~ pattern } + end + + gems.each do |gem, specs| + specs.sort! do |a,b| + Gem::Version.new(b.version) <=> Gem::Version.new(a.version) + end + + versions = specs.each_with_index.map do |spec, index| + index == 0 ? text.bright_green(spec.version.to_s) : text.green(spec.version.to_s) + end + + output.puts "#{text.default gem} (#{versions.join ', '})" + end + end + end +end diff --git a/lib/pry/default_commands/gems.rb b/lib/pry/default_commands/gems.rb deleted file mode 100644 index 4e8e0920..00000000 --- a/lib/pry/default_commands/gems.rb +++ /dev/null @@ -1,84 +0,0 @@ -class Pry - module DefaultCommands - - Gems = Pry::CommandSet.new do - - create_command "gem-install", "Install a gem and refresh the gem cache.", :argument_required => true do |gem| - - banner <<-BANNER - Usage: gem-install GEM_NAME - - Installs the given gem and refreshes the gem cache so that you can immediately 'require GEM_FILE' - BANNER - - def setup - require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller - end - - def process(gem) - begin - destination = File.writable?(Gem.dir) ? Gem.dir : Gem.user_dir - installer = Gem::DependencyInstaller.new :install_dir => destination - installer.install gem - rescue Errno::EACCES - raise CommandError, "Insufficient permissions to install `#{text.green gem}`." - rescue Gem::GemNotFoundException - raise CommandError, "Gem `#{text.green gem}` not found." - else - Gem.refresh - output.puts "Gem `#{text.green gem}` installed." - end - end - end - - create_command "gem-cd", "Change working directory to specified gem's directory.", :argument_required => true do |gem| - banner <<-BANNER - Usage: gem-cd GEM_NAME - - Change the current working directory to that in which the given gem is installed. - BANNER - - def process(gem) - specs = Gem::Specification.respond_to?(:each) ? Gem::Specification.find_all_by_name(gem) : Gem.source_index.find_name(gem) - spec = specs.sort { |a,b| Gem::Version.new(b.version) <=> Gem::Version.new(a.version) }.first - if spec - Dir.chdir(spec.full_gem_path) - output.puts(Dir.pwd) - else - raise CommandError, "Gem `#{gem}` not found." - end - end - end - - create_command "gem-list", "List and search installed gems." do |pattern| - banner <<-BANNER - Usage: gem-list [REGEX] - - List all installed gems, when a regex is provided, limit the output to those that - match the regex. - BANNER - - def process(pattern=nil) - pattern = Regexp.compile(pattern || '') - gems = if Gem::Specification.respond_to?(:each) - Gem::Specification.select{|spec| spec.name =~ pattern }.group_by(&:name) - else - Gem.source_index.gems.values.group_by(&:name).select { |gemname, specs| gemname =~ pattern } - end - - gems.each do |gem, specs| - specs.sort! do |a,b| - Gem::Version.new(b.version) <=> Gem::Version.new(a.version) - end - - versions = specs.each_with_index.map do |spec, index| - index == 0 ? text.bright_green(spec.version.to_s) : text.green(spec.version.to_s) - end - - output.puts "#{text.default gem} (#{versions.join ', '})" - end - end - end - end - end -end diff --git a/lib/pry/default_commands/gist.rb b/lib/pry/default_commands/gist.rb index 61e4220a..073b9bdf 100644 --- a/lib/pry/default_commands/gist.rb +++ b/lib/pry/default_commands/gist.rb @@ -1,187 +1,183 @@ class Pry - module DefaultCommands - Gist = Pry::CommandSet.new do - create_command "gist", "Gist a method or expression history to GitHub.", :requires_gem => "jist" do - include Pry::Helpers::DocumentationHelpers + Pry::Commands.create_command "gist", "Gist a method or expression history to GitHub.", :requires_gem => "jist" do + include Pry::Helpers::DocumentationHelpers - banner <<-USAGE - Usage: gist [OPTIONS] [METH] - Gist method (doc or source) or input expression to GitHub. + banner <<-USAGE + Usage: gist [OPTIONS] [METH] + Gist method (doc or source) or input expression to GitHub. - If you'd like to permanently associate your gists with your GitHub account run `gist --login`. + If you'd like to permanently associate your gists with your GitHub account run `gist --login`. - e.g: gist -m my_method # gist the method my_method - e.g: gist -d my_method # gist the documentation for my_method - e.g: gist -i 1..10 # gist the input expressions from 1 to 10 - e.g: gist -k show-method # gist the command show-method - e.g: gist -c Pry # gist the Pry class - e.g: gist -m hello_world --lines 2..-2 # gist from lines 2 to the second-last of the hello_world method - e.g: gist -m my_method --clip # Copy my_method source to clipboard, do not gist it. - USAGE + e.g: gist -m my_method # gist the method my_method + e.g: gist -d my_method # gist the documentation for my_method + e.g: gist -i 1..10 # gist the input expressions from 1 to 10 + e.g: gist -k show-method # gist the command show-method + e.g: gist -c Pry # gist the Pry class + e.g: gist -m hello_world --lines 2..-2 # gist from lines 2 to the second-last of the hello_world method + e.g: gist -m my_method --clip # Copy my_method source to clipboard, do not gist it. + USAGE - command_options :shellwords => false + command_options :shellwords => false - attr_accessor :content - attr_accessor :filename + attr_accessor :content + attr_accessor :filename - def setup - require 'jist' - self.content = "" - self.filename = "a.rb" + def setup + require 'jist' + self.content = "" + self.filename = "a.rb" + end + + def options(opt) + opt.on :login, "Authenticate the jist gem with GitHub" + opt.on :m, :method, "Gist a method's source.", :argument => true do |meth_name| + meth = get_method_or_raise(meth_name, target, {}) + self.content << meth.source << "\n" + self.filename = meth.source_file + end + opt.on :d, :doc, "Gist a method's documentation.", :argument => true do |meth_name| + meth = get_method_or_raise(meth_name, target, {}) + text.no_color do + self.content << process_comment_markup(meth.doc) << "\n" + end + self.filename = meth.source_file + ".doc" + end + opt.on :k, :command, "Gist a command's source.", :argument => true do |command_name| + command = find_command(command_name) + block = Pry::Method.new(command.block) + self.content << block.source << "\n" + self.filename = block.source_file + end + opt.on :c, :class, "Gist a class or module's source.", :argument => true do |class_name| + mod = Pry::WrappedModule.from_str(class_name, target) + self.content << mod.source << "\n" + self.filename = mod.source_file + end + opt.on :var, "Gist a variable's content.", :argument => true do |variable_name| + begin + obj = target.eval(variable_name) + rescue Pry::RescuableException + raise CommandError, "Gist failed: Invalid variable name: #{variable_name}" end - def options(opt) - opt.on :login, "Authenticate the jist gem with GitHub" - opt.on :m, :method, "Gist a method's source.", :argument => true do |meth_name| - meth = get_method_or_raise(meth_name, target, {}) - self.content << meth.source << "\n" - self.filename = meth.source_file - end - opt.on :d, :doc, "Gist a method's documentation.", :argument => true do |meth_name| - meth = get_method_or_raise(meth_name, target, {}) - text.no_color do - self.content << process_comment_markup(meth.doc) << "\n" - end - self.filename = meth.source_file + ".doc" - end - opt.on :k, :command, "Gist a command's source.", :argument => true do |command_name| - command = find_command(command_name) - block = Pry::Method.new(command.block) - self.content << block.source << "\n" - self.filename = block.source_file - end - opt.on :c, :class, "Gist a class or module's source.", :argument => true do |class_name| - mod = Pry::WrappedModule.from_str(class_name, target) - self.content << mod.source << "\n" - self.filename = mod.source_file - end - opt.on :var, "Gist a variable's content.", :argument => true do |variable_name| - begin - obj = target.eval(variable_name) - rescue Pry::RescuableException - raise CommandError, "Gist failed: Invalid variable name: #{variable_name}" - end - - self.content << Pry.config.gist.inspecter.call(obj) << "\n" - end - opt.on :hist, "Gist a range of Readline history lines.", :optional_argument => true, :as => Range, :default => -20..-1 do |range| - h = Pry.history.to_a - self.content << h[one_index_range(convert_to_range(range))].join("\n") << "\n" - end - - opt.on :f, :file, "Gist a file.", :argument => true do |file| - self.content << File.read(File.expand_path(file)) << "\n" - self.filename = file - end - opt.on :o, :out, "Gist entries from Pry's output result history. Takes an index or range.", :optional_argument => true, - :as => Range, :default => -1 do |range| - range = convert_to_range(range) - - range.each do |v| - self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v]) - end - - self.content << "\n" - end - opt.on :clip, "Copy the selected content to clipboard instead, do NOT gist it.", :default => false - opt.on :p, :public, "Create a public gist (default: false)", :default => false - opt.on :l, :lines, "Only gist a subset of lines from the gistable content.", :optional_argument => true, :as => Range, :default => 1..-1 - opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional_argument => true, - :as => Range, :default => -1 do |range| - range = convert_to_range(range) - input_expressions = _pry_.input_array[range] || [] - Array(input_expressions).each_with_index do |code, index| - corrected_index = index + range.first - if code && code != "" - self.content << code - if code !~ /;\Z/ - self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}" - end - end - end - end - end - - def process - return Jist.login! if opts.present?(:login) - - if self.content =~ /\A\s*\z/ - raise CommandError, "Found no code to gist." - end - - if opts.present?(:clip) - perform_clipboard - else - perform_gist - end - end - - # copy content to clipboard instead (only used with --clip flag) - def perform_clipboard - copy(self.content) - output.puts "Copied content to clipboard!" - end - - def perform_gist - if opts.present?(:lines) - self.content = restrict_to_lines(content, opts[:l]) - end - - response = Jist.gist(content, :filename => File.basename(filename), - :public => !!opts[:p]) - - if response - copy(response['html_url']) - output.puts "Gist created at #{response['html_url']} and added to clipboard." - end - end - - def convert_to_range(n) - if !n.is_a?(Range) - (n..n) - else - n - end - end - - def comment_expression_result_for_gist(result) - content = "" - result.lines.each_with_index do |line, index| - if index == 0 - content << "# => #{line}" - else - content << "# #{line}" - end - end - content - end - - # Copy a string to the clipboard. - # - # @param [String] content - # - # @copyright Copyright (c) 2008 Chris Wanstrath (MIT) - # @see https://github.com/defunkt/gist/blob/master/lib/gist.rb#L178 - def copy(content) - cmd = case true - when system("type pbcopy > /dev/null 2>&1") - :pbcopy - when system("type xclip > /dev/null 2>&1") - :xclip - when system("type putclip > /dev/null 2>&1") - :putclip - end - - if cmd - IO.popen(cmd.to_s, 'r+') { |clip| clip.print content } - end - - content - end + self.content << Pry.config.gist.inspecter.call(obj) << "\n" + end + opt.on :hist, "Gist a range of Readline history lines.", :optional_argument => true, :as => Range, :default => -20..-1 do |range| + h = Pry.history.to_a + self.content << h[one_index_range(convert_to_range(range))].join("\n") << "\n" end - alias_command "clipit", "gist --clip" - alias_command "jist", "gist" + opt.on :f, :file, "Gist a file.", :argument => true do |file| + self.content << File.read(File.expand_path(file)) << "\n" + self.filename = file + end + opt.on :o, :out, "Gist entries from Pry's output result history. Takes an index or range.", :optional_argument => true, + :as => Range, :default => -1 do |range| + range = convert_to_range(range) + + range.each do |v| + self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v]) + end + + self.content << "\n" + end + opt.on :clip, "Copy the selected content to clipboard instead, do NOT gist it.", :default => false + opt.on :p, :public, "Create a public gist (default: false)", :default => false + opt.on :l, :lines, "Only gist a subset of lines from the gistable content.", :optional_argument => true, :as => Range, :default => 1..-1 + opt.on :i, :in, "Gist entries from Pry's input expression history. Takes an index or range.", :optional_argument => true, + :as => Range, :default => -1 do |range| + range = convert_to_range(range) + input_expressions = _pry_.input_array[range] || [] + Array(input_expressions).each_with_index do |code, index| + corrected_index = index + range.first + if code && code != "" + self.content << code + if code !~ /;\Z/ + self.content << "#{comment_expression_result_for_gist(Pry.config.gist.inspecter.call(_pry_.output_array[corrected_index]))}" + end + end + end + end + end + + def process + return Jist.login! if opts.present?(:login) + + if self.content =~ /\A\s*\z/ + raise CommandError, "Found no code to gist." + end + + if opts.present?(:clip) + perform_clipboard + else + perform_gist + end + end + + # copy content to clipboard instead (only used with --clip flag) + def perform_clipboard + copy(self.content) + output.puts "Copied content to clipboard!" + end + + def perform_gist + if opts.present?(:lines) + self.content = restrict_to_lines(content, opts[:l]) + end + + response = Jist.gist(content, :filename => File.basename(filename), + :public => !!opts[:p]) + + if response + copy(response['html_url']) + output.puts "Gist created at #{response['html_url']} and added to clipboard." + end + end + + def convert_to_range(n) + if !n.is_a?(Range) + (n..n) + else + n + end + end + + def comment_expression_result_for_gist(result) + content = "" + result.lines.each_with_index do |line, index| + if index == 0 + content << "# => #{line}" + else + content << "# #{line}" + end + end + content + end + + # Copy a string to the clipboard. + # + # @param [String] content + # + # @copyright Copyright (c) 2008 Chris Wanstrath (MIT) + # @see https://github.com/defunkt/gist/blob/master/lib/gist.rb#L178 + def copy(content) + cmd = case true + when system("type pbcopy > /dev/null 2>&1") + :pbcopy + when system("type xclip > /dev/null 2>&1") + :xclip + when system("type putclip > /dev/null 2>&1") + :putclip + end + + if cmd + IO.popen(cmd.to_s, 'r+') { |clip| clip.print content } + end + + content end end + + Pry::Commands.alias_command "clipit", "gist --clip" + Pry::Commands.alias_command "jist", "gist" end diff --git a/lib/pry/default_commands/help.rb b/lib/pry/default_commands/help.rb index e104ede6..97314b27 100644 --- a/lib/pry/default_commands/help.rb +++ b/lib/pry/default_commands/help.rb @@ -1,127 +1,123 @@ class Pry - module DefaultCommands - Help = Pry::CommandSet.new do - create_command "help" do |cmd| - description "Show a list of commands. Type `help ` for information about ." + Pry::Commands.create_command "help" do |cmd| + description "Show a list of commands. Type `help ` for information about ." - banner <<-BANNER - Usage: help [ COMMAND ] + banner <<-BANNER + Usage: help [ COMMAND ] - With no arguments, help lists all the available commands in the current - command-set along with their description. + With no arguments, help lists all the available commands in the current + command-set along with their description. - When given a command name as an argument, shows the help for that command. - BANNER + When given a command name as an argument, shows the help for that command. + BANNER - # We only want to show commands that have descriptions, so that the - # easter eggs don't show up. - def visible_commands - visible = {} - commands.each do |key, command| - visible[key] = command if command.description && !command.description.empty? - end - visible + # We only want to show commands that have descriptions, so that the + # easter eggs don't show up. + def visible_commands + visible = {} + commands.each do |key, command| + visible[key] = command if command.description && !command.description.empty? + end + visible + end + + # Get a hash of available commands grouped by the "group" name. + def command_groups + visible_commands.values.group_by(&:group) + end + + def process + if args.empty? + display_index(command_groups) + else + display_search(args.first) + end + end + + # Display the index view, with headings and short descriptions per command. + # + # @param Hash[String => Array[Commands]] + def display_index(groups) + help_text = [] + + groups.keys.sort_by(&method(:group_sort_key)).each do |key| + commands = groups[key].sort_by{ |command| command.options[:listing].to_s } + + unless commands.empty? + help_text << "#{text.bold(key)}\n" + commands.map do |command| + " #{command.options[:listing].to_s.ljust(18)} #{command.description}" + end.join("\n") + end + end + + stagger_output(help_text.join("\n\n")) + end + + # Display help for an individual command or group. + # + # @param String The string to search for. + def display_search(search) + if command = command_set.find_command_for_help(search) + display_command(command) + else + groups = search_hash(search, command_groups) + + if groups.size > 0 + display_index(groups) + return end - # Get a hash of available commands grouped by the "group" name. - def command_groups - visible_commands.values.group_by(&:group) - end + filtered = search_hash(search, visible_commands) + raise CommandError, "No help found for '#{args.first}'" if filtered.empty? - def process - if args.empty? - display_index(command_groups) - else - display_search(args.first) - end - end - - # Display the index view, with headings and short descriptions per command. - # - # @param Hash[String => Array[Commands]] - def display_index(groups) - help_text = [] - - groups.keys.sort_by(&method(:group_sort_key)).each do |key| - commands = groups[key].sort_by{ |command| command.options[:listing].to_s } - - unless commands.empty? - help_text << "#{text.bold(key)}\n" + commands.map do |command| - " #{command.options[:listing].to_s.ljust(18)} #{command.description}" - end.join("\n") - end - end - - stagger_output(help_text.join("\n\n")) - end - - # Display help for an individual command or group. - # - # @param String The string to search for. - def display_search(search) - if command = command_set.find_command_for_help(search) - display_command(command) - else - groups = search_hash(search, command_groups) - - if groups.size > 0 - display_index(groups) - return - end - - filtered = search_hash(search, visible_commands) - raise CommandError, "No help found for '#{args.first}'" if filtered.empty? - - if filtered.size == 1 - display_command(filtered.values.first) - else - display_index({"'#{search}' commands" => filtered.values}) - end - end - end - - # Display help for an individual command. - # - # @param [Pry::Command] - def display_command(command) - stagger_output command.new.help - end - - # Find a subset of a hash that matches the user's search term. - # - # If there's an exact match a Hash of one element will be returned, - # otherwise a sub-Hash with every key that matches the search will - # be returned. - # - # @param [String] the search term - # @param [Hash] the hash to search - def search_hash(search, hash) - matching = {} - - hash.each_pair do |key, value| - next unless key.is_a?(String) - if normalize(key) == normalize(search) - return {key => value} - elsif normalize(key).start_with?(normalize(search)) - matching[key] = value - end - end - - matching - end - - # Clean search terms to make it easier to search group names - # - # @param String - # @return String - def normalize(key) - key.downcase.gsub(/pry\W+/, '') - end - - def group_sort_key(group_name) - [%w(Help Context Editing Introspection Input_and_output Navigating_pry Gems Basic Commands).index(group_name.gsub(' ', '_')) || 99, group_name] + if filtered.size == 1 + display_command(filtered.values.first) + else + display_index({"'#{search}' commands" => filtered.values}) end end end + + # Display help for an individual command. + # + # @param [Pry::Command] + def display_command(command) + stagger_output command.new.help + end + + # Find a subset of a hash that matches the user's search term. + # + # If there's an exact match a Hash of one element will be returned, + # otherwise a sub-Hash with every key that matches the search will + # be returned. + # + # @param [String] the search term + # @param [Hash] the hash to search + def search_hash(search, hash) + matching = {} + + hash.each_pair do |key, value| + next unless key.is_a?(String) + if normalize(key) == normalize(search) + return {key => value} + elsif normalize(key).start_with?(normalize(search)) + matching[key] = value + end + end + + matching + end + + # Clean search terms to make it easier to search group names + # + # @param String + # @return String + def normalize(key) + key.downcase.gsub(/pry\W+/, '') + end + + def group_sort_key(group_name) + [%w(Help Context Editing Introspection Input_and_output Navigating_pry Gems Basic Commands).index(group_name.gsub(' ', '_')) || 99, group_name] + end end end diff --git a/lib/pry/default_commands/hist.rb b/lib/pry/default_commands/hist.rb index 1c8de625..9c3c5d48 100644 --- a/lib/pry/default_commands/hist.rb +++ b/lib/pry/default_commands/hist.rb @@ -1,120 +1,112 @@ class Pry - module DefaultCommands - Hist = Pry::CommandSet.new do + Pry::Commands.create_command "hist", "Show and replay Readline history. Aliases: history" do + group "Editing" - create_command "hist", "Show and replay Readline history. Aliases: history" do - group "Editing" - banner <<-USAGE - Usage: hist - hist --head N - hist --tail N - hist --show START..END - hist --grep PATTERN - hist --clear - hist --replay START..END - hist --save [START..END] FILE - USAGE + banner <<-USAGE + Usage: hist + hist --head N + hist --tail N + hist --show START..END + hist --grep PATTERN + hist --clear + hist --replay START..END + hist --save [START..END] FILE + USAGE - def options(opt) - opt.on :H, :head, "Display the first N items.", :optional_argument => true, :as => Integer - opt.on :T, :tail, "Display the last N items.", :optional_argument => true, :as => Integer - opt.on :s, :show, "Show the given range of lines.", :optional_argument => true, :as => Range - opt.on :G, :grep, "Show lines matching the given pattern.", :argument => true, :as => String - opt.on :c, :clear, "Clear the current session's history." - opt.on :r, :replay, "Replay a line or range of lines.", :argument => true, :as => Range - opt.on :save, "Save history to a file.", :argument => true, :as => Range + def options(opt) + opt.on :H, :head, "Display the first N items.", :optional_argument => true, :as => Integer + opt.on :T, :tail, "Display the last N items.", :optional_argument => true, :as => Integer + opt.on :s, :show, "Show the given range of lines.", :optional_argument => true, :as => Range + opt.on :G, :grep, "Show lines matching the given pattern.", :argument => true, :as => String + opt.on :c, :clear, "Clear the current session's history." + opt.on :r, :replay, "Replay a line or range of lines.", :argument => true, :as => Range + opt.on :save, "Save history to a file.", :argument => true, :as => Range - opt.on :e, :'exclude-pry', "Exclude Pry commands from the history." - opt.on :n, :'no-numbers', "Omit line numbers." - opt.on :f, :flood, "Do not use a pager to view text longer than one screen." - end + opt.on :e, :'exclude-pry', "Exclude Pry commands from the history." + opt.on :n, :'no-numbers', "Omit line numbers." + opt.on :f, :flood, "Do not use a pager to view text longer than one screen." + end - def process - @history = Pry::Code(Pry.history.to_a) + def process + @history = Pry::Code(Pry.history.to_a) - if opts.present?(:show) - @history = @history.between(opts[:show]) - end - - if opts.present?(:grep) - @history = @history.grep(opts[:grep]) - end - - @history = case - when opts.present?(:head) - @history.take_lines(1, opts[:head] || 10) - when opts.present?(:tail) - @history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10) - when opts.present?(:show) - @history.between(opts[:show]) - else - @history - end - - if opts.present?(:'exclude-pry') - @history = @history.select { |l, ln| !command_set.valid_command?(l) } - end - - if opts.present?(:save) - process_save - elsif opts.present?(:clear) - process_clear - elsif opts.present?(:replay) - process_replay - else - process_display - end - end - - def process_display - unless opts.present?(:'no-numbers') - @history = @history.with_line_numbers - end - - render_output(@history, opts) - end - - def process_save - case opts[:save] - when Range - @history = @history.between(opts[:save]) - - unless args.first - raise CommandError, "Must provide a file name." - end - - file_name = File.expand_path(args.first) - when String - file_name = File.expand_path(opts[:save]) - end - - output.puts "Saving history in #{file_name}..." - - File.open(file_name, 'w') { |f| f.write(@history.raw) } - - output.puts "History saved." - end - - def process_clear - Pry.history.clear - output.puts "History cleared." - end - - def process_replay - @history = @history.between(opts[:r]) - - _pry_.input_stack.push _pry_.input - _pry_.input = StringIO.new(@history.raw) - # eval_string << "#{@history.raw}\n" - # run "show-input" unless _pry_.complete_expression?(eval_string) - end + if opts.present?(:show) + @history = @history.between(opts[:show]) end - alias_command "history", "hist" + if opts.present?(:grep) + @history = @history.grep(opts[:grep]) + end + @history = case + when opts.present?(:head) + @history.take_lines(1, opts[:head] || 10) + when opts.present?(:tail) + @history.take_lines(-(opts[:tail] || 10), opts[:tail] || 10) + when opts.present?(:show) + @history.between(opts[:show]) + else + @history + end + if opts.present?(:'exclude-pry') + @history = @history.select { |l, ln| !command_set.valid_command?(l) } + end + if opts.present?(:save) + process_save + elsif opts.present?(:clear) + process_clear + elsif opts.present?(:replay) + process_replay + else + process_display + end + end + def process_display + unless opts.present?(:'no-numbers') + @history = @history.with_line_numbers + end + + render_output(@history, opts) + end + + def process_save + case opts[:save] + when Range + @history = @history.between(opts[:save]) + + unless args.first + raise CommandError, "Must provide a file name." + end + + file_name = File.expand_path(args.first) + when String + file_name = File.expand_path(opts[:save]) + end + + output.puts "Saving history in #{file_name}..." + + File.open(file_name, 'w') { |f| f.write(@history.raw) } + + output.puts "History saved." + end + + def process_clear + Pry.history.clear + output.puts "History cleared." + end + + def process_replay + @history = @history.between(opts[:r]) + + _pry_.input_stack.push _pry_.input + _pry_.input = StringIO.new(@history.raw) + # eval_string << "#{@history.raw}\n" + # run "show-input" unless _pry_.complete_expression?(eval_string) end end + + Pry::Commands.alias_command "history", "hist" end diff --git a/lib/pry/default_commands/import_set.rb b/lib/pry/default_commands/import_set.rb new file mode 100644 index 00000000..ab40d4db --- /dev/null +++ b/lib/pry/default_commands/import_set.rb @@ -0,0 +1,12 @@ +class Pry + Pry::Commands.create_command "import-set", "Import a command set" do + group "Commands" + + def process(command_set_name) + raise CommandError, "Provide a command set name" if command_set.nil? + + set = target.eval(arg_string) + _pry_.commands.import set + end + end +end diff --git a/lib/pry/default_commands/input_and_output.rb b/lib/pry/default_commands/input_and_output.rb deleted file mode 100644 index 32c59bd5..00000000 --- a/lib/pry/default_commands/input_and_output.rb +++ /dev/null @@ -1,306 +0,0 @@ -require 'tempfile' -require 'pry/default_commands/gist' - -class Pry - module DefaultCommands - - InputAndOutput = Pry::CommandSet.new do - import Gist - command(/\.(.*)/, "All text following a '.' is forwarded to the shell.", :listing => ".", :use_prefix => false, :takes_block => true) do |cmd| - if cmd =~ /^cd\s+(.+)/i - dest = $1 - begin - Dir.chdir File.expand_path(dest) - rescue Errno::ENOENT - raise CommandError, "No such directory: #{dest}" - end - else - pass_block(cmd) - - if command_block - command_block.call `#{cmd}` - else - Pry.config.system.call(output, cmd, _pry_) - end - end - end - - command "shell-mode", "Toggle shell mode. Bring in pwd prompt and file completion." do - case _pry_.prompt - when Pry::SHELL_PROMPT - _pry_.pop_prompt - _pry_.custom_completions = Pry::DEFAULT_CUSTOM_COMPLETIONS - else - _pry_.push_prompt Pry::SHELL_PROMPT - _pry_.custom_completions = Pry::FILE_COMPLETIONS - Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, - _pry_.instance_eval(&Pry::FILE_COMPLETIONS) - end - end - alias_command "file-mode", "shell-mode" - - create_command "save-file", "Export to a file using content from the REPL." do - banner <<-USAGE - Usage: save-file [OPTIONS] [FILE] - Save REPL content to a file. - e.g: save-file -m my_method -m my_method2 ./hello.rb - e.g: save-file -i 1..10 ./hello.rb --append - e.g: save-file -k show-method ./my_command.rb - e.g: save-file -f sample_file --lines 2..10 ./output_file.rb - USAGE - - attr_accessor :content - attr_accessor :file_name - - def setup - self.content = "" - end - - def convert_to_range(n) - if !n.is_a?(Range) - (n..n) - else - n - end - end - - def options(opt) - opt.on :m, :method, "Save a method's source.", :argument => true do |meth_name| - meth = get_method_or_raise(meth_name, target, {}) - self.content << meth.source - end - opt.on :c, :class, "Save a class's source.", :argument => true do |class_name| - mod = Pry::WrappedModule.from_str(class_name, target) - self.content << mod.source - end - opt.on :k, :command, "Save a command's source.", :argument => true do |command_name| - command = find_command(command_name) - block = Pry::Method.new(command.block) - self.content << block.source - end - opt.on :f, :file, "Save a file.", :argument => true do |file| - self.content << File.read(File.expand_path(file)) - end - opt.on :l, :lines, "Only save a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1 - opt.on :o, :out, "Save entries from Pry's output result history. Takes an index or range.", :optional_argument => true, - :as => Range, :default => -5..-1 do |range| - range = convert_to_range(range) - - range.each do |v| - self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v]) - end - - self.content << "\n" - end - opt.on :i, :in, "Save entries from Pry's input expression history. Takes an index or range.", :optional_argument => true, - :as => Range, :default => -5..-1 do |range| - input_expressions = _pry_.input_array[range] || [] - Array(input_expressions).each { |v| self.content << v } - end - opt.on :a, :append, "Append to the given file instead of overwriting it." - end - - def process - if args.empty? - raise CommandError, "Must specify a file name." - end - - self.file_name = File.expand_path(args.first) - - save_file - end - - def save_file - if self.content.empty? - raise CommandError, "Found no code to save." - end - - File.open(file_name, mode) do |f| - if opts.present?(:lines) - f.puts restrict_to_lines(content, opts[:l]) - else - f.puts content - end - end - end - - def mode - if opts.present?(:append) - "a" - else - "w" - end - end - end - - create_command "cat", "Show code from a file, Pry's input buffer, or the last exception." do - banner <<-USAGE - Usage: cat FILE - cat --ex [STACK_INDEX] - cat --in [INPUT_INDEX_OR_RANGE] - - cat is capable of showing part or all of a source file, the context of the - last exception, or an expression from Pry's input history. - - cat --ex defaults to showing the lines surrounding the location of the last - exception. Invoking it more than once travels up the exception's backtrace, - and providing a number shows the context of the given index of the backtrace. - USAGE - - def options(opt) - opt.on :ex, "Show the context of the last exception.", :optional_argument => true, :as => Integer - opt.on :i, :in, "Show one or more entries from Pry's expression history.", :optional_argument => true, :as => Range, :default => -5..-1 - - opt.on :s, :start, "Starting line (defaults to the first line).", :optional_argument => true, :as => Integer - opt.on :e, :end, "Ending line (defaults to the last line).", :optional_argument => true, :as => Integer - opt.on :l, :'line-numbers', "Show line numbers." - opt.on :t, :type, "The file type for syntax highlighting (e.g., 'ruby' or 'python').", :argument => true, :as => Symbol - - opt.on :f, :flood, "Do not use a pager to view text longer than one screen." - end - - def process - handler = case - when opts.present?(:ex) - method :process_ex - when opts.present?(:in) - method :process_in - else - method :process_file - end - - output = handler.call do |code| - code.code_type = opts[:type] || :ruby - - code.between(opts[:start] || 1, opts[:end] || -1). - with_line_numbers(opts.present?(:'line-numbers') || opts.present?(:ex)) - end - - render_output(output, opts) - end - - def process_ex - window_size = Pry.config.default_window_size || 5 - ex = _pry_.last_exception - - raise CommandError, "No exception found." unless ex - - if opts[:ex].nil? - bt_index = ex.bt_index - ex.inc_bt_index - else - bt_index = opts[:ex] - ex.bt_index = bt_index - ex.inc_bt_index - end - - ex_file, ex_line = ex.bt_source_location_for(bt_index) - - raise CommandError, "The given backtrace level is out of bounds." unless ex_file - - if RbxPath.is_core_path?(ex_file) - ex_file = RbxPath.convert_path_to_full(ex_file) - end - - set_file_and_dir_locals(ex_file) - - start_line = ex_line - window_size - start_line = 1 if start_line < 1 - end_line = ex_line + window_size - - header = unindent <<-HEADER - #{text.bold 'Exception:'} #{ex.class}: #{ex.message} - -- - #{text.bold('From:')} #{ex_file} @ line #{ex_line} @ #{text.bold("level: #{bt_index}")} of backtrace (of #{ex.backtrace.size - 1}). - - HEADER - - code = yield(Pry::Code.from_file(ex_file). - between(start_line, end_line). - with_marker(ex_line)) - - "#{header}#{code}" - end - - def process_in - normalized_range = absolute_index_range(opts[:i], _pry_.input_array.length) - input_items = _pry_.input_array[normalized_range] || [] - - zipped_items = normalized_range.zip(input_items).reject { |_, s| s.nil? || s == "" } - - unless zipped_items.length > 0 - raise CommandError, "No expressions found." - end - - if zipped_items.length > 1 - contents = "" - zipped_items.each do |i, s| - contents << "#{text.bold(i.to_s)}:\n" - contents << yield(Pry::Code(s).with_indentation(2)).to_s - end - else - contents = yield(Pry::Code(zipped_items.first.last)) - end - - contents - end - - def process_file - file_name = args.shift - - unless file_name - raise CommandError, "Must provide a filename, --in, or --ex." - end - - file_name, line_num = file_name.split(':') - file_name = File.expand_path(file_name) - set_file_and_dir_locals(file_name) - - code = yield(Pry::Code.from_file(file_name)) - - code.code_type = opts[:type] || detect_code_type_from_file(file_name) - if line_num - code = code.around(line_num.to_i, - Pry.config.default_window_size || 7) - end - - code - end - - def detect_code_type_from_file(file_name) - name, ext = File.basename(file_name).split('.', 2) - - if ext - case ext - when "py" - :python - when "rb", "gemspec", "rakefile", "ru" - :ruby - when "js" - return :javascript - when "yml", "prytheme" - :yaml - when "groovy" - :groovy - when "c" - :c - when "cpp" - :cpp - when "java" - :java - else - :text - end - else - case name - when "Rakefile", "Gemfile" - :ruby - else - :text - end - end - end - end - end - end -end diff --git a/lib/pry/default_commands/install_command.rb b/lib/pry/default_commands/install_command.rb new file mode 100644 index 00000000..6ad4d878 --- /dev/null +++ b/lib/pry/default_commands/install_command.rb @@ -0,0 +1,47 @@ +class Pry + Pry::Commands.create_command "install-command", "Install a disabled command." do |name| + group 'Commands' + + banner <<-BANNER + Usage: install-command COMMAND + + Installs the gems necessary to run the given COMMAND. You will generally not + need to run this unless told to by an error message. + BANNER + + def process(name) + require 'rubygems/dependency_installer' unless defined? Gem::DependencyInstaller + command = find_command(name) + + if command_dependencies_met?(command.options) + output.puts "Dependencies for #{command.name} are met. Nothing to do." + return + end + + output.puts "Attempting to install `#{name}` command..." + gems_to_install = Array(command.options[:requires_gem]) + + gems_to_install.each do |g| + next if gem_installed?(g) + output.puts "Installing `#{g}` gem..." + + begin + Gem::DependencyInstaller.new.install(g) + rescue Gem::GemNotFoundException + raise CommandError, "Required Gem: `#{g}` not found. Aborting command installation." + end + end + + Gem.refresh + gems_to_install.each do |g| + begin + require g + rescue LoadError + raise CommandError, "Required Gem: `#{g}` installed but not found?!. Aborting command installation." + end + end + + output.puts "Installation of `#{name}` successful! Type `help #{name}` for information" + end + end +end diff --git a/lib/pry/default_commands/introspection.rb b/lib/pry/default_commands/introspection.rb deleted file mode 100644 index b9fb4c81..00000000 --- a/lib/pry/default_commands/introspection.rb +++ /dev/null @@ -1,488 +0,0 @@ -require 'tempfile' - -class Pry - module DefaultCommands - - # For show-doc and show-source - module ModuleIntrospectionHelpers - attr_accessor :module_object - - def module_object - if @module_object - @module_object - else - name = args.first - @module_object = WrappedModule.from_str(name, target) - if @module_object - sup = @module_object.ancestors.select do |anc| - anc.class == @module_object.wrapped.class - end[opts[:super]] - @module_object = sup ? Pry::WrappedModule(sup) : nil - end - end - end - - # @param [String] - # @param [Binding] target The binding context of the input. - # @return [Symbol] type of input - def input_type(input,target) - if input == "" - :blank - elsif target.eval("defined? #{input} ") =~ /variable|constant/ && - target.eval(input).respond_to?(:source_location) - :sourcable_object - elsif Pry::Method.from_str(input,target) - :method - elsif Pry::WrappedModule.from_str(input, target) - :module - else - :unknown - end - end - - def process(name) - input = args.join(" ") - type = input_type(input, target) - - code_or_doc = case type - when :blank - process_blank - when :sourcable_object - process_sourcable_object - when :method - process_method - when :module - process_module - else - command_error("method or module for '#{input}' could not be found or derived", false) - end - - render_output(code_or_doc, opts) - end - - def process_blank - if mod = extract_module_from_internal_binding - @module_object = mod - process_module - elsif meth = extract_method_from_binding - @method_object = meth - process_method - else - command_error("method or module for '' could not be derived", false) - end - end - - def extract_module_from_internal_binding - if args.empty? && internal_binding?(target) - mod = target_self.is_a?(Module) ? target_self : target_self.class - Pry::WrappedModule(mod) - end - end - - def extract_method_from_binding - Pry::Method.from_binding(target) - end - - def module_start_line(mod, candidate_rank=0) - if opts.present?(:'base-one') - 1 - else - mod.candidate(candidate_rank).line - end - end - - def use_line_numbers? - opts.present?(:b) || opts.present?(:l) - end - - def attempt - rank = 0 - begin - yield(rank) - rescue Pry::CommandError - raise if rank > (module_object.number_of_candidates - 1) - rank += 1 - retry - end - end - end - - Introspection = Pry::CommandSet.new do - - create_command "show-doc", "Show the documentation for a method or class. Aliases: \?", :shellwords => false do - include ModuleIntrospectionHelpers - include Helpers::DocumentationHelpers - extend Helpers::BaseHelpers - - banner <<-BANNER - Usage: show-doc [OPTIONS] [METH] - Aliases: ? - - Show the documentation for a method or class. Tries instance methods first and then methods by default. - e.g show-doc hello_method # docs for hello_method - e.g show-doc Pry # docs for Pry class - e.g show-doc Pry -a # docs for all definitions of Pry class (all monkey patches) - BANNER - - options :requires_gem => "ruby18_source_location" if mri_18? - - def setup - require 'ruby18_source_location' if mri_18? - end - - def options(opt) - method_options(opt) - opt.on :l, "line-numbers", "Show line numbers." - opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)." - opt.on :f, :flood, "Do not use a pager to view text longer than one screen." - opt.on :a, :all, "Show docs for all definitions and monkeypatches of the module/class" - end - - def process_sourcable_object - name = args.first - object = target.eval(name) - - file_name, line = object.source_location - - doc = Pry::Code.from_file(file_name).comment_describing(line) - doc = strip_leading_hash_and_whitespace_from_ruby_comments(doc) - - result = "" - result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" - result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{doc.lines.count}\n\n" - result << doc - result << "\n" - end - - def process_module - raise Pry::CommandError, "No documentation found." if module_object.nil? - if opts.present?(:all) - all_modules - else - normal_module - end - end - - def normal_module - doc = "" - if module_object.yard_docs? - file_name, line = module_object.yard_file, module_object.yard_line - doc << module_object.yard_doc - start_line = 1 - else - attempt do |rank| - file_name, line = module_object.candidate(rank).source_location - set_file_and_dir_locals(file_name) - doc << module_object.candidate(rank).doc - start_line = module_start_line(module_object, rank) - end - end - - doc = Code.new(doc, start_line, :text). - with_line_numbers(use_line_numbers?).to_s - - doc.insert(0, "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line ? line : "N/A"}:\n\n") - end - - def all_modules - doc = "" - doc << "Found #{module_object.number_of_candidates} candidates for `#{module_object.name}` definition:\n" - module_object.number_of_candidates.times do |v| - candidate = module_object.candidate(v) - begin - doc << "\nCandidate #{v+1}/#{module_object.number_of_candidates}: #{candidate.file} @ #{candidate.line}:\n\n" - doc << candidate.doc - rescue Pry::RescuableException - doc << "No documentation found.\n" - next - end - end - doc - end - - def process_method - raise Pry::CommandError, "No documentation found." if method_object.doc.nil? || method_object.doc.empty? - - doc = process_comment_markup(method_object.doc) - output.puts make_header(method_object, doc) - output.puts "#{text.bold("Owner:")} #{method_object.owner || "N/A"}" - output.puts "#{text.bold("Visibility:")} #{method_object.visibility}" - output.puts "#{text.bold("Signature:")} #{method_object.signature}" - output.puts - - if use_line_numbers? - doc = Code.new(doc, start_line, :text). - with_line_numbers(true).to_s - end - - doc - end - - def module_start_line(mod, candidate=0) - if opts.present?(:'base-one') - 1 - else - if mod.candidate(candidate).line - mod.candidate(candidate).line - mod.candidate(candidate).doc.lines.count - else - 1 - end - end - end - - def start_line - if opts.present?(:'base-one') - 1 - else - (method_object.source_line - method_object.doc.lines.count) || 1 - end - end - end - - alias_command "?", "show-doc" - - create_command "stat", "View method information and set _file_ and _dir_ locals.", :shellwords => false do - banner <<-BANNER - Usage: stat [OPTIONS] [METH] - Show method information for method METH and set _file_ and _dir_ locals. - e.g: stat hello_method - BANNER - - def options(opt) - method_options(opt) - end - - def process - meth = method_object - output.puts unindent <<-EOS - Method Information: - -- - Name: #{meth.name} - Owner: #{meth.owner ? meth.owner : "Unknown"} - Visibility: #{meth.visibility} - Type: #{meth.is_a?(::Method) ? "Bound" : "Unbound"} - Arity: #{meth.arity} - Method Signature: #{meth.signature} - Source Location: #{meth.source_location ? meth.source_location.join(":") : "Not found."} - EOS - end - end - - create_command "show-source" do - include ModuleIntrospectionHelpers - extend Helpers::BaseHelpers - - description "Show the source for a method or class. Aliases: $, show-method" - - banner <<-BANNER - Usage: show-source [OPTIONS] [METH|CLASS] - Aliases: $, show-method - - Show the source for a method or class. Tries instance methods first and then methods by default. - - e.g: `show-source hello_method` - e.g: `show-source -m hello_method` - e.g: `show-source Pry#rep` # source for Pry#rep method - e.g: `show-source Pry` # source for Pry class - e.g: `show-source Pry -a` # source for all Pry class definitions (all monkey patches) - e.g: `show-source Pry --super # source for superclass of Pry (Object class) - - https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method - BANNER - - options :shellwords => false - options :requires_gem => "ruby18_source_location" if mri_18? - - def setup - require 'ruby18_source_location' if mri_18? - end - - def options(opt) - method_options(opt) - opt.on :l, "line-numbers", "Show line numbers." - opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)." - opt.on :f, :flood, "Do not use a pager to view text longer than one screen." - opt.on :a, :all, "Show source for all definitions and monkeypatches of the module/class" - end - - def process_sourcable_object - name = args.first - object = target.eval(name) - - file_name, line = object.source_location - - source = Pry::Code.from_file(file_name).expression_at(line) - code = Pry::Code.new(source).with_line_numbers(use_line_numbers?).to_s - - result = "" - result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" - result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" - result << code - result << "\n" - end - - def process_method - raise CommandError, "Could not find method source" unless method_object.source - - code = "" - code << make_header(method_object) - code << "#{text.bold("Owner:")} #{method_object.owner || "N/A"}\n" - code << "#{text.bold("Visibility:")} #{method_object.visibility}\n" - code << "\n" - - code << Code.from_method(method_object, start_line). - with_line_numbers(use_line_numbers?).to_s - end - - def process_module - raise Pry::CommandError, "No documentation found." if module_object.nil? - if opts.present?(:all) - all_modules - else - normal_module - end - end - - def normal_module - file_name = line = code = nil - attempt do |rank| - file_name, line = module_object.candidate(rank).source_location - set_file_and_dir_locals(file_name) - code = Code.from_module(module_object, module_start_line(module_object, rank), rank). - with_line_numbers(use_line_numbers?).to_s - end - - result = "" - result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" - result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" - result << code - end - - def all_modules - mod = module_object - - result = "" - result << "Found #{mod.number_of_candidates} candidates for `#{mod.name}` definition:\n" - mod.number_of_candidates.times do |v| - candidate = mod.candidate(v) - begin - result << "\nCandidate #{v+1}/#{mod.number_of_candidates}: #{candidate.file} @ line #{candidate.line}:\n" - code = Code.from_module(mod, module_start_line(mod, v), v). - with_line_numbers(use_line_numbers?).to_s - result << "Number of lines: #{code.lines.count}\n\n" - result << code - rescue Pry::RescuableException - result << "\nNo code found.\n" - next - end - end - result - end - - def use_line_numbers? - opts.present?(:b) || opts.present?(:l) - end - - def start_line - if opts.present?(:'base-one') - 1 - else - method_object.source_line || 1 - end - end - end - - alias_command "show-method", "show-source" - alias_command "$", "show-source" - - command "show-command", "Show the source for CMD." do |*args| - target = target() - - opts = Slop.parse!(args) do |opt| - opt.banner unindent <<-USAGE - Usage: show-command [OPTIONS] [CMD] - Show the source for command CMD. - e.g: show-command show-method - USAGE - - opt.on :l, "line-numbers", "Show line numbers." - opt.on :f, :flood, "Do not use a pager to view text longer than one screen." - opt.on :h, :help, "This message." do - output.puts opt.help - end - end - - next if opts.present?(:help) - - command_name = args.shift - if !command_name - raise CommandError, "You must provide a command name." - end - - if find_command(command_name) - block = Pry::Method.new(find_command(command_name).block) - - next unless block.source - set_file_and_dir_locals(block.source_file) - - output.puts make_header(block) - output.puts - - code = Code.from_method(block).with_line_numbers(opts.present?(:'line-numbers')).to_s - - render_output(code, opts) - else - raise CommandError, "No such command: #{command_name}." - end - end - - create_command "ri", "View ri documentation. e.g `ri Array#each`" do - banner <<-BANNER - Usage: ri [spec] - e.g. ri Array#each - - Relies on the rdoc gem being installed. See also: show-doc. - BANNER - - def process(spec) - # Lazily load RI - require 'rdoc/ri/driver' - - unless defined? RDoc::RI::PryDriver - - # Subclass RI so that it formats its output nicely, and uses `lesspipe`. - subclass = Class.new(RDoc::RI::Driver) # the hard way. - - subclass.class_eval do - def page - Pry::Helpers::BaseHelpers.lesspipe {|less| yield less} - end - - def formatter(io) - if @formatter_klass then - @formatter_klass.new - else - RDoc::Markup::ToAnsi.new - end - end - end - - RDoc::RI.const_set :PryDriver, subclass # hook it up! - end - - # Spin-up an RI insance. - ri = RDoc::RI::PryDriver.new :use_stdout => true, :interactive => false - - begin - ri.display_names [spec] # Get the documentation (finally!) - rescue RDoc::RI::Driver::NotFoundError => e - output.puts "error: '#{e.name}' not found" - end - end - - end - - end - end -end - diff --git a/lib/pry/default_commands/jump_to.rb b/lib/pry/default_commands/jump_to.rb new file mode 100644 index 00000000..052c2df1 --- /dev/null +++ b/lib/pry/default_commands/jump_to.rb @@ -0,0 +1,17 @@ +class Pry + Pry::Commands.command "jump-to", "Jump to a binding further up the stack, popping all bindings below." do |break_level| + break_level = break_level.to_i + nesting_level = _pry_.binding_stack.size - 1 + + case break_level + when nesting_level + output.puts "Already at nesting level #{nesting_level}" + when (0...nesting_level) + _pry_.binding_stack.slice!(break_level + 1, _pry_.binding_stack.size) + + else + max_nest_level = nesting_level - 1 + output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}." + end + end +end diff --git a/lib/pry/default_commands/ls.rb b/lib/pry/default_commands/ls.rb index cf01c6f2..8e84fafd 100644 --- a/lib/pry/default_commands/ls.rb +++ b/lib/pry/default_commands/ls.rb @@ -1,272 +1,266 @@ class Pry - module DefaultCommands + Pry::Commands.create_command "ls","Show the list of vars and methods in the current scope.", + :shellwords => false, :interpolate => false do - Ls = Pry::CommandSet.new do + group "Context" - create_command "ls","Show the list of vars and methods in the current scope.", - :shellwords => false, :interpolate => false do + def options(opt) + opt.banner unindent <<-USAGE + Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object] + ls [-g] [-l] - group "Context" + ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object. - def options(opt) - opt.banner unindent <<-USAGE - Usage: ls [-m|-M|-p|-pM] [-q|-v] [-c|-i] [Object] - ls [-g] [-l] + The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator. - ls shows you which methods, constants and variables are accessible to Pry. By default it shows you the local variables defined in the current shell, and any public methods or instance variables defined on the current object. + Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class. + USAGE - The colours used are configurable using Pry.config.ls.*_color, and the separator is Pry.config.ls.separator. + opt.on :m, "methods", "Show public methods defined on the Object (default)" + opt.on :M, "instance-methods", "Show methods defined in a Module or Class" - Pry.config.ls.ceiling is used to hide methods defined higher up in the inheritance chain, this is by default set to [Object, Module, Class] so that methods defined on all Objects are omitted. The -v flag can be used to ignore this setting and show all methods, while the -q can be used to set the ceiling much lower and show only methods defined on the object or its direct class. - USAGE + opt.on :p, "ppp", "Show public, protected (in yellow) and private (in green) methods" + opt.on :q, "quiet", "Show only methods defined on object.singleton_class and object.class" + opt.on :v, "verbose", "Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling)" - opt.on :m, "methods", "Show public methods defined on the Object (default)" - opt.on :M, "instance-methods", "Show methods defined in a Module or Class" + opt.on :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)" + opt.on :l, "locals", "Show locals, including those provided by Pry (in red)" - opt.on :p, "ppp", "Show public, protected (in yellow) and private (in green) methods" - opt.on :q, "quiet", "Show only methods defined on object.singleton_class and object.class" - opt.on :v, "verbose", "Show methods and constants on all super-classes (ignores Pry.config.ls.ceiling)" + opt.on :c, "constants", "Show constants, highlighting classes (in blue), and exceptions (in purple).\n" + + " " * 32 + "Constants that are pending autoload? are also shown (in yellow)." - opt.on :g, "globals", "Show global variables, including those builtin to Ruby (in cyan)" - opt.on :l, "locals", "Show locals, including those provided by Pry (in red)" + opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)" - opt.on :c, "constants", "Show constants, highlighting classes (in blue), and exceptions (in purple).\n" + - " " * 32 + "Constants that are pending autoload? are also shown (in yellow)." + opt.on :G, "grep", "Filter output by regular expression", :argument => true + if jruby? + opt.on :J, "all-java", "Show all the aliases for methods from java (default is to show only prettiest)" + end + end - opt.on :i, "ivars", "Show instance variables (in blue) and class variables (in bright blue)" + def process + obj = args.empty? ? target_self : target.eval(args.join(" ")) - opt.on :G, "grep", "Filter output by regular expression", :argument => true - if jruby? - opt.on :J, "all-java", "Show all the aliases for methods from java (default is to show only prettiest)" - end + # exclude -q, -v and --grep because they don't specify what the user wants to see. + has_opts = (opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || + opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) || + opts.present?(:ivars)) + + show_methods = opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || !has_opts + show_self_methods = (!has_opts && Module === obj) + show_constants = opts.present?(:constants) || (!has_opts && Module === obj) + show_ivars = opts.present?(:ivars) || !has_opts + show_locals = opts.present?(:locals) || (!has_opts && args.empty?) + + grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }] + + raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty? + raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty? + raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose) + raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:'instance-methods') && !(Module === obj) + raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj) + + + if opts.present?(:globals) + output_section("global variables", grep[format_globals(target.eval("global_variables"))]) + end + + if show_constants + mod = Module === obj ? obj : Object + constants = mod.constants + constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose) + output_section("constants", grep[format_constants(mod, constants)]) + end + + if show_methods + # methods is a hash {Module/Class => [Pry::Methods]} + methods = all_methods(obj).group_by(&:owner) + + # reverse the resolution order so that the most useful information appears right by the prompt + resolution_order(obj).take_while(&below_ceiling(obj)).reverse.each do |klass| + methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex }) + output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here + end + end + + if show_self_methods + methods = all_methods(obj, true).select{ |m| m.owner == obj && m.name =~ grep_regex } + output_section "#{Pry::WrappedModule.new(obj).method_prefix}methods", format_methods(methods) + end + + if show_ivars + klass = (Module === obj ? obj : obj.class) + ivars = Pry::Method.safe_send(obj, :instance_variables) + kvars = Pry::Method.safe_send(klass, :class_variables) + output_section("instance variables", format_variables(:instance_var, ivars)) + output_section("class variables", format_variables(:class_var, kvars)) + end + + if show_locals + output_section("locals", format_locals(grep[target.eval("local_variables")])) + end + end + + private + + # http://ruby.runpaint.org/globals, and running "puts global_variables.inspect". + BUILTIN_GLOBALS = %w($" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\ + $: $; $< $= $> $0 $ARGV $CONSOLE $DEBUG $DEFAULT_INPUT $DEFAULT_OUTPUT + $FIELD_SEPARATOR $FILENAME $FS $IGNORECASE $INPUT_LINE_NUMBER + $INPUT_RECORD_SEPARATOR $KCODE $LOADED_FEATURES $LOAD_PATH $NR $OFS + $ORS $OUTPUT_FIELD_SEPARATOR $OUTPUT_RECORD_SEPARATOR $PID $PROCESS_ID + $PROGRAM_NAME $RS $VERBOSE $deferr $defout $stderr $stdin $stdout) + + # $SAFE and $? are thread-local, the exception stuff only works in a rescue clause, + # everything else is basically a local variable with a $ in its name. + PSEUDO_GLOBALS = %w($! $' $& $` $@ $? $+ $_ $~ $1 $2 $3 $4 $5 $6 $7 $8 $9 + $CHILD_STATUS $SAFE $ERROR_INFO $ERROR_POSITION $LAST_MATCH_INFO + $LAST_PAREN_MATCH $LAST_READ_LINE $MATCH $POSTMATCH $PREMATCH) + + # Get all the methods that we'll want to output + def all_methods(obj, instance_methods=false) + methods = if instance_methods || opts.present?(:'instance-methods') + Pry::Method.all_from_class(obj) + else + Pry::Method.all_from_obj(obj) + end + + if jruby? && !opts.present?(:J) + methods = trim_jruby_aliases(methods) + end + + methods.select{ |method| opts.present?(:ppp) || method.visibility == :public } + end + + # JRuby creates lots of aliases for methods imported from java in an attempt to + # make life easier for ruby programmers. + # (e.g. getFooBar becomes get_foo_bar and foo_bar, and maybe foo_bar? if it + # returns a Boolean). + # The full transformations are in the assignAliases method of: + # https://github.com/jruby/jruby/blob/master/src/org/jruby/javasupport/JavaClass.java + # + # This has the unfortunate side-effect of making the output of ls even more + # incredibly verbose than it normally would be for these objects; and so we filter + # out all but the nicest of these aliases here. + # + # TODO: This is a little bit vague, better heuristics could be used. + # JRuby also has a lot of scala-specific logic, which we don't copy. + # + def trim_jruby_aliases(methods) + grouped = methods.group_by do |m| + m.name.sub(/\A(is|get|set)(?=[A-Z_])/, '').gsub(/[_?=]/, '').downcase + end + + grouped.map do |key, values| + values = values.sort_by do |m| + rubbishness(m.name) end - def process - obj = args.empty? ? target_self : target.eval(args.join(" ")) - - # exclude -q, -v and --grep because they don't specify what the user wants to see. - has_opts = (opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || - opts.present?(:globals) || opts.present?(:locals) || opts.present?(:constants) || - opts.present?(:ivars)) - - show_methods = opts.present?(:methods) || opts.present?(:'instance-methods') || opts.present?(:ppp) || !has_opts - show_self_methods = (!has_opts && Module === obj) - show_constants = opts.present?(:constants) || (!has_opts && Module === obj) - show_ivars = opts.present?(:ivars) || !has_opts - show_locals = opts.present?(:locals) || (!has_opts && args.empty?) - - grep_regex, grep = [Regexp.new(opts[:G] || "."), lambda{ |x| x.grep(grep_regex) }] - - raise Pry::CommandError, "-l does not make sense with a specified Object" if opts.present?(:locals) && !args.empty? - raise Pry::CommandError, "-g does not make sense with a specified Object" if opts.present?(:globals) && !args.empty? - raise Pry::CommandError, "-q does not make sense with -v" if opts.present?(:quiet) && opts.present?(:verbose) - raise Pry::CommandError, "-M only makes sense with a Module or a Class" if opts.present?(:'instance-methods') && !(Module === obj) - raise Pry::CommandError, "-c only makes sense with a Module or a Class" if opts.present?(:constants) && !args.empty? && !(Module === obj) - - - if opts.present?(:globals) - output_section("global variables", grep[format_globals(target.eval("global_variables"))]) - end - - if show_constants - mod = Module === obj ? obj : Object - constants = mod.constants - constants -= (mod.ancestors - [mod]).map(&:constants).flatten unless opts.present?(:verbose) - output_section("constants", grep[format_constants(mod, constants)]) - end - - if show_methods - # methods is a hash {Module/Class => [Pry::Methods]} - methods = all_methods(obj).group_by(&:owner) - - # reverse the resolution order so that the most useful information appears right by the prompt - resolution_order(obj).take_while(&below_ceiling(obj)).reverse.each do |klass| - methods_here = format_methods((methods[klass] || []).select{ |m| m.name =~ grep_regex }) - output_section "#{Pry::WrappedModule.new(klass).method_prefix}methods", methods_here - end - end - - if show_self_methods - methods = all_methods(obj, true).select{ |m| m.owner == obj && m.name =~ grep_regex } - output_section "#{Pry::WrappedModule.new(obj).method_prefix}methods", format_methods(methods) - end - - if show_ivars - klass = (Module === obj ? obj : obj.class) - ivars = Pry::Method.safe_send(obj, :instance_variables) - kvars = Pry::Method.safe_send(klass, :class_variables) - output_section("instance variables", format_variables(:instance_var, ivars)) - output_section("class variables", format_variables(:class_var, kvars)) - end - - if show_locals - output_section("locals", format_locals(grep[target.eval("local_variables")])) - end + found = [] + values.select do |x| + (!found.any?{ |y| x == y }) && found << x end + end.flatten(1) + end - private - - # http://ruby.runpaint.org/globals, and running "puts global_variables.inspect". - BUILTIN_GLOBALS = %w($" $$ $* $, $-0 $-F $-I $-K $-W $-a $-d $-i $-l $-p $-v $-w $. $/ $\\ - $: $; $< $= $> $0 $ARGV $CONSOLE $DEBUG $DEFAULT_INPUT $DEFAULT_OUTPUT - $FIELD_SEPARATOR $FILENAME $FS $IGNORECASE $INPUT_LINE_NUMBER - $INPUT_RECORD_SEPARATOR $KCODE $LOADED_FEATURES $LOAD_PATH $NR $OFS - $ORS $OUTPUT_FIELD_SEPARATOR $OUTPUT_RECORD_SEPARATOR $PID $PROCESS_ID - $PROGRAM_NAME $RS $VERBOSE $deferr $defout $stderr $stdin $stdout) - - # $SAFE and $? are thread-local, the exception stuff only works in a rescue clause, - # everything else is basically a local variable with a $ in its name. - PSEUDO_GLOBALS = %w($! $' $& $` $@ $? $+ $_ $~ $1 $2 $3 $4 $5 $6 $7 $8 $9 - $CHILD_STATUS $SAFE $ERROR_INFO $ERROR_POSITION $LAST_MATCH_INFO - $LAST_PAREN_MATCH $LAST_READ_LINE $MATCH $POSTMATCH $PREMATCH) - - # Get all the methods that we'll want to output - def all_methods(obj, instance_methods=false) - methods = if instance_methods || opts.present?(:'instance-methods') - Pry::Method.all_from_class(obj) - else - Pry::Method.all_from_obj(obj) - end - - if jruby? && !opts.present?(:J) - methods = trim_jruby_aliases(methods) - end - - methods.select{ |method| opts.present?(:ppp) || method.visibility == :public } + # When removing jruby aliases, we want to keep the alias that is "least rubbish" + # according to this metric. + def rubbishness(name) + name.each_char.map{ |x| + case x + when /[A-Z]/ + 1 + when '?', '=', '!' + -2 + else + 0 end + }.inject(&:+) + (name.size / 100.0) + end - # JRuby creates lots of aliases for methods imported from java in an attempt to - # make life easier for ruby programmers. - # (e.g. getFooBar becomes get_foo_bar and foo_bar, and maybe foo_bar? if it - # returns a Boolean). - # The full transformations are in the assignAliases method of: - # https://github.com/jruby/jruby/blob/master/src/org/jruby/javasupport/JavaClass.java - # - # This has the unfortunate side-effect of making the output of ls even more - # incredibly verbose than it normally would be for these objects; and so we filter - # out all but the nicest of these aliases here. - # - # TODO: This is a little bit vague, better heuristics could be used. - # JRuby also has a lot of scala-specific logic, which we don't copy. - # - def trim_jruby_aliases(methods) - grouped = methods.group_by do |m| - m.name.sub(/\A(is|get|set)(?=[A-Z_])/, '').gsub(/[_?=]/, '').downcase - end + def resolution_order(obj) + opts.present?(:'instance-methods') ? Pry::Method.instance_resolution_order(obj) : Pry::Method.resolution_order(obj) + end - grouped.map do |key, values| - values = values.sort_by do |m| - rubbishness(m.name) - end + # Get a lambda that can be used with .take_while to prevent over-eager + # traversal of the Object's ancestry graph. + def below_ceiling(obj) + ceiling = if opts.present?(:quiet) + [opts.present?(:'instance-methods') ? obj.ancestors[1] : obj.class.ancestors[1]] + Pry.config.ls.ceiling + elsif opts.present?(:verbose) + [] + else + Pry.config.ls.ceiling.dup + end - found = [] - values.select do |x| - (!found.any?{ |y| x == y }) && found << x - end - end.flatten(1) - end + lambda { |klass| !ceiling.include?(klass) } + end - # When removing jruby aliases, we want to keep the alias that is "least rubbish" - # according to this metric. - def rubbishness(name) - name.each_char.map{ |x| - case x - when /[A-Z]/ - 1 - when '?', '=', '!' - -2 - else - 0 - end - }.inject(&:+) + (name.size / 100.0) - end - - def resolution_order(obj) - opts.present?(:'instance-methods') ? Pry::Method.instance_resolution_order(obj) : Pry::Method.resolution_order(obj) - end - - # Get a lambda that can be used with .take_while to prevent over-eager - # traversal of the Object's ancestry graph. - def below_ceiling(obj) - ceiling = if opts.present?(:quiet) - [opts.present?(:'instance-methods') ? obj.ancestors[1] : obj.class.ancestors[1]] + Pry.config.ls.ceiling - elsif opts.present?(:verbose) - [] - else - Pry.config.ls.ceiling.dup - end - - lambda { |klass| !ceiling.include?(klass) } - end - - # Format and colourise a list of methods. - def format_methods(methods) - methods.sort_by(&:name).map do |method| - if method.name == 'method_missing' - color(:method_missing, 'method_missing') - elsif method.visibility == :private - color(:private_method, method.name) - elsif method.visibility == :protected - color(:protected_method, method.name) - else - color(:public_method, method.name) - end - end - end - - def format_variables(type, vars) - vars.sort_by(&:downcase).map{ |var| color(type, var) } - end - - def format_constants(mod, constants) - constants.sort_by(&:downcase).map do |name| - if const = (!mod.autoload?(name) && (mod.const_get(name) || true) rescue nil) - if (const < Exception rescue false) - color(:exception_constant, name) - elsif (Module === mod.const_get(name) rescue false) - color(:class_constant, name) - else - color(:constant, name) - end - else - color(:unloaded_constant, name) - end - end - end - - def format_globals(globals) - globals.sort_by(&:downcase).map do |name| - if PSEUDO_GLOBALS.include?(name) - color(:pseudo_global, name) - elsif BUILTIN_GLOBALS.include?(name) - color(:builtin_global, name) - else - color(:global_var, name) - end - end - end - - def format_locals(locals) - locals.sort_by(&:downcase).map do |name| - if _pry_.sticky_locals.include?(name.to_sym) - color(:pry_var, name) - else - color(:local_var, name) - end - end - end - - # Add a new section to the output. Outputs nothing if the section would be empty. - def output_section(heading, body) - return if body.compact.empty? - output.puts "#{text.bold(color(:heading, heading))}: #{body.compact.join(Pry.config.ls.separator)}" - end - - # Color output based on config.ls.*_color - def color(type, str) - text.send(Pry.config.ls.send(:"#{type}_color"), str) + # Format and colourise a list of methods. + def format_methods(methods) + methods.sort_by(&:name).map do |method| + if method.name == 'method_missing' + color(:method_missing, 'method_missing') + elsif method.visibility == :private + color(:private_method, method.name) + elsif method.visibility == :protected + color(:protected_method, method.name) + else + color(:public_method, method.name) end end end + + def format_variables(type, vars) + vars.sort_by(&:downcase).map{ |var| color(type, var) } + end + + def format_constants(mod, constants) + constants.sort_by(&:downcase).map do |name| + if const = (!mod.autoload?(name) && (mod.const_get(name) || true) rescue nil) + if (const < Exception rescue false) + color(:exception_constant, name) + elsif (Module === mod.const_get(name) rescue false) + color(:class_constant, name) + else + color(:constant, name) + end + else + color(:unloaded_constant, name) + end + end + end + + def format_globals(globals) + globals.sort_by(&:downcase).map do |name| + if PSEUDO_GLOBALS.include?(name) + color(:pseudo_global, name) + elsif BUILTIN_GLOBALS.include?(name) + color(:builtin_global, name) + else + color(:global_var, name) + end + end + end + + def format_locals(locals) + locals.sort_by(&:downcase).map do |name| + if _pry_.sticky_locals.include?(name.to_sym) + color(:pry_var, name) + else + color(:local_var, name) + end + end + end + + # Add a new section to the output. Outputs nothing if the section would be empty. + def output_section(heading, body) + return if body.compact.empty? + output.puts "#{text.bold(color(:heading, heading))}: #{body.compact.join(Pry.config.ls.separator)}" + end + + # Color output based on config.ls.*_color + def color(type, str) + text.send(Pry.config.ls.send(:"#{type}_color"), str) + end end end diff --git a/lib/pry/default_commands/misc.rb b/lib/pry/default_commands/misc.rb deleted file mode 100644 index 0944f32b..00000000 --- a/lib/pry/default_commands/misc.rb +++ /dev/null @@ -1,38 +0,0 @@ -class Pry - module DefaultCommands - Misc = Pry::CommandSet.new do - - command "toggle-color", "Toggle syntax highlighting." do - Pry.color = !Pry.color - output.puts "Syntax highlighting #{Pry.color ? "on" : "off"}" - end - - command "simple-prompt", "Toggle the simple prompt." do - case _pry_.prompt - when Pry::SIMPLE_PROMPT - _pry_.pop_prompt - else - _pry_.push_prompt Pry::SIMPLE_PROMPT - end - end - - command "pry-version", "Show Pry version." do - output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}." - end - - command "reload-method", "Reload the source file that contains the specified method" do |meth_name| - meth = get_method_or_raise(meth_name, target, {}, :omit_help) - - if meth.source_type == :c - raise CommandError, "Can't reload a C method." - elsif meth.dynamically_defined? - raise CommandError, "Can't reload an eval method." - else - file_name = meth.source_file - load file_name - output.puts "Reloaded #{file_name}." - end - end - end - end -end diff --git a/lib/pry/default_commands/navigating_pry.rb b/lib/pry/default_commands/navigating_pry.rb deleted file mode 100644 index c3d18ef3..00000000 --- a/lib/pry/default_commands/navigating_pry.rb +++ /dev/null @@ -1,110 +0,0 @@ -class Pry - module DefaultCommands - - NavigatingPry = Pry::CommandSet.new do - command "switch-to", "Start a new sub-session on a binding in the current stack (numbered by nesting)." do |selection| - selection = selection.to_i - - if selection < 0 || selection > _pry_.binding_stack.size - 1 - raise CommandError, "Invalid binding index #{selection} - use `nesting` command to view valid indices." - else - Pry.start(_pry_.binding_stack[selection]) - end - end - - command "nesting", "Show nesting information." do - output.puts "Nesting status:" - output.puts "--" - _pry_.binding_stack.each_with_index do |obj, level| - if level == 0 - output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))} (Pry top level)" - else - output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))}" - end - end - end - - command "jump-to", "Jump to a binding further up the stack, popping all bindings below." do |break_level| - break_level = break_level.to_i - nesting_level = _pry_.binding_stack.size - 1 - - case break_level - when nesting_level - output.puts "Already at nesting level #{nesting_level}" - when (0...nesting_level) - _pry_.binding_stack.slice!(break_level + 1, _pry_.binding_stack.size) - - else - max_nest_level = nesting_level - 1 - output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}." - end - end - - command "exit-all", "End the current Pry session (popping all bindings) and returning to caller. Accepts optional return value. Aliases: !!@" do - # calculate user-given value - exit_value = target.eval(arg_string) - - # clear the binding stack - _pry_.binding_stack.clear - - # break out of the repl loop - throw(:breakout, exit_value) - end - - alias_command "!!@", "exit-all" - - create_command "exit" do - description "Pop the previous binding (does NOT exit program). Aliases: quit" - - banner <<-BANNER - Usage: exit [OPTIONS] [--help] - Aliases: quit - - It can be useful to exit a context with a user-provided value. For - instance an exit value can be used to determine program flow. - - e.g: `exit "pry this"` - e.g: `exit` - - https://github.com/pry/pry/wiki/State-navigation#wiki-Exit_with_value - BANNER - - command_options( - :keep_retval => true - ) - - def process - if _pry_.binding_stack.one? - _pry_.run_command "exit-all #{arg_string}" - else - # otherwise just pop a binding and return user supplied value - process_pop_and_return - end - end - - def process_pop_and_return - popped_object = _pry_.binding_stack.pop.eval('self') - - # return a user-specified value if given otherwise return the object - return target.eval(arg_string) unless arg_string.empty? - popped_object - end - end - - alias_command "quit", "exit" - - command "exit-program", "End the current program. Aliases: quit-program, !!!" do - Pry.save_history if Pry.config.history.should_save - Kernel.exit target.eval(arg_string).to_i - end - - alias_command "quit-program", "exit-program" - alias_command "!!!", "exit-program" - - command "!pry", "Start a Pry session on current self; this even works mid multi-line expression." do - target.pry - end - - end - end -end diff --git a/lib/pry/default_commands/nesting.rb b/lib/pry/default_commands/nesting.rb new file mode 100644 index 00000000..d7cb144f --- /dev/null +++ b/lib/pry/default_commands/nesting.rb @@ -0,0 +1,13 @@ +class Pry + Pry::Commands.command "nesting", "Show nesting information." do + output.puts "Nesting status:" + output.puts "--" + _pry_.binding_stack.each_with_index do |obj, level| + if level == 0 + output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))} (Pry top level)" + else + output.puts "#{level}. #{Pry.view_clip(obj.eval('self'))}" + end + end + end +end diff --git a/lib/pry/default_commands/play.rb b/lib/pry/default_commands/play.rb new file mode 100644 index 00000000..ee02eb11 --- /dev/null +++ b/lib/pry/default_commands/play.rb @@ -0,0 +1,84 @@ +class Pry + Pry::Commands.create_command "play" do + include Pry::Helpers::DocumentationHelpers + + description "Play back a string variable or a method or a file as input." + + banner <<-BANNER + Usage: play [OPTIONS] [--help] + + The play command enables you to replay code from files and methods as + if they were entered directly in the Pry REPL. Default action (no + options) is to play the provided string variable + + e.g: `play -i 20 --lines 1..3` + e.g: `play -m Pry#repl --lines 1..-1` + e.g: `play -f Rakefile --lines 5` + + https://github.com/pry/pry/wiki/User-Input#wiki-Play + BANNER + + attr_accessor :content + + def setup + self.content = "" + end + + def options(opt) + opt.on :m, :method, "Play a method's source.", :argument => true do |meth_name| + meth = get_method_or_raise(meth_name, target, {}) + self.content << meth.source + end + opt.on :d, :doc, "Play a method's documentation.", :argument => true do |meth_name| + meth = get_method_or_raise(meth_name, target, {}) + text.no_color do + self.content << process_comment_markup(meth.doc) + end + end + opt.on :c, :command, "Play a command's source.", :argument => true do |command_name| + command = find_command(command_name) + block = Pry::Method.new(command.block) + self.content << block.source + end + opt.on :f, :file, "Play a file.", :argument => true do |file| + self.content << File.read(File.expand_path(file)) + end + opt.on :l, :lines, "Only play a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1 + opt.on :i, :in, "Play entries from Pry's input expression history. Takes an index or range. Note this can only replay pure Ruby code, not Pry commands.", :optional_argument => true, + :as => Range, :default => -5..-1 do |range| + input_expressions = _pry_.input_array[range] || [] + Array(input_expressions).each { |v| self.content << v } + end + opt.on :o, "open", 'When used with the -m switch, it plays the entire method except the last line, leaving the method definition "open". `amend-line` can then be used to modify the method.' + end + + def process + perform_play + run "show-input" unless Pry::Code.complete_expression?(eval_string) + end + + def process_non_opt + args.each do |arg| + begin + self.content << target.eval(arg) + rescue Pry::RescuableException + raise CommandError, "Problem when evaling #{arg}." + end + end + end + + def perform_play + process_non_opt + + if opts.present?(:lines) + self.content = restrict_to_lines(self.content, opts[:l]) + end + + if opts.present?(:open) + self.content = restrict_to_lines(self.content, 1..-2) + end + + eval_string << self.content + end + end +end diff --git a/lib/pry/default_commands/pry_backtrace.rb b/lib/pry/default_commands/pry_backtrace.rb new file mode 100644 index 00000000..447a310c --- /dev/null +++ b/lib/pry/default_commands/pry_backtrace.rb @@ -0,0 +1,21 @@ +class Pry + Pry::Commands.create_command "pry-backtrace", "Show the backtrace for the Pry session." do + banner <<-BANNER + Usage: pry-backtrace [OPTIONS] [--help] + + Show the backtrace for the position in the code where Pry was started. This can be used to + infer the behavior of the program immediately before it entered Pry, just like the backtrace + property of an exception. + + (NOTE: if you are looking for the backtrace of the most recent exception raised, + just type: `_ex_.backtrace` instead, see https://github.com/pry/pry/wiki/Special-Locals) + + e.g: pry-backtrace + BANNER + + def process + output.puts "\n#{text.bold('Backtrace:')}\n--\n" + stagger_output _pry_.backtrace.join("\n") + end + end +end diff --git a/lib/pry/default_commands/pry_version.rb b/lib/pry/default_commands/pry_version.rb new file mode 100644 index 00000000..35cf6500 --- /dev/null +++ b/lib/pry/default_commands/pry_version.rb @@ -0,0 +1,5 @@ +class Pry + Pry::Commands.command "pry-version", "Show Pry version." do + output.puts "Pry version: #{Pry::VERSION} on Ruby #{RUBY_VERSION}." + end +end diff --git a/lib/pry/default_commands/raise_up.rb b/lib/pry/default_commands/raise_up.rb new file mode 100644 index 00000000..922f6b61 --- /dev/null +++ b/lib/pry/default_commands/raise_up.rb @@ -0,0 +1,23 @@ +class Pry + # N.B. using a regular expresion here so that "raise-up 'foo'" does the right thing. + Pry::Commands.create_command(/raise-up(!?\b.*)/, :listing => 'raise-up') do + description "Raise an exception out of the current pry instance." + banner <<-BANNER + Raise up, like exit, allows you to quit pry. Instead of returning a value however, it raises an exception. + If you don't provide the exception to be raised, it will use the most recent exception (in pry _ex_). + + e.g. `raise-up "get-me-out-of-here"` is equivalent to: + `raise "get-me-out-of-here" + raise-up` + + When called as raise-up! (with an exclamation mark), this command raises the exception through + any nested prys you have created by "cd"ing into objects. + BANNER + + def process + return stagger_output help if captures[0] =~ /(-h|--help)\b/ + # Handle 'raise-up', 'raise-up "foo"', 'raise-up RuntimeError, 'farble' in a rubyesque manner + target.eval("_pry_.raise_up#{captures[0]}") + end + end +end diff --git a/lib/pry/default_commands/reload_method.rb b/lib/pry/default_commands/reload_method.rb new file mode 100644 index 00000000..07821ad4 --- /dev/null +++ b/lib/pry/default_commands/reload_method.rb @@ -0,0 +1,15 @@ +class Pry + Pry::Commands.command "reload-method", "Reload the source file that contains the specified method" do |meth_name| + meth = get_method_or_raise(meth_name, target, {}, :omit_help) + + if meth.source_type == :c + raise CommandError, "Can't reload a C method." + elsif meth.dynamically_defined? + raise CommandError, "Can't reload an eval method." + else + file_name = meth.source_file + load file_name + output.puts "Reloaded #{file_name}." + end + end +end diff --git a/lib/pry/default_commands/reset.rb b/lib/pry/default_commands/reset.rb new file mode 100644 index 00000000..7fe481c8 --- /dev/null +++ b/lib/pry/default_commands/reset.rb @@ -0,0 +1,6 @@ +class Pry + Pry::Commands.command "reset", "Reset the REPL to a clean state." do + output.puts "Pry reset." + exec "pry" + end +end diff --git a/lib/pry/default_commands/ri.rb b/lib/pry/default_commands/ri.rb new file mode 100644 index 00000000..e689e09f --- /dev/null +++ b/lib/pry/default_commands/ri.rb @@ -0,0 +1,46 @@ +class Pry + Pry::Commands.create_command "ri", "View ri documentation. e.g `ri Array#each`" do + banner <<-BANNER + Usage: ri [spec] + e.g. ri Array#each + + Relies on the rdoc gem being installed. See also: show-doc. + BANNER + + def process(spec) + # Lazily load RI + require 'rdoc/ri/driver' + + unless defined? RDoc::RI::PryDriver + + # Subclass RI so that it formats its output nicely, and uses `lesspipe`. + subclass = Class.new(RDoc::RI::Driver) # the hard way. + + subclass.class_eval do + def page + Pry::Helpers::BaseHelpers.lesspipe {|less| yield less} + end + + def formatter(io) + if @formatter_klass then + @formatter_klass.new + else + RDoc::Markup::ToAnsi.new + end + end + end + + RDoc::RI.const_set :PryDriver, subclass # hook it up! + end + + # Spin-up an RI insance. + ri = RDoc::RI::PryDriver.new :use_stdout => true, :interactive => false + + begin + ri.display_names [spec] # Get the documentation (finally!) + rescue RDoc::RI::Driver::NotFoundError => e + output.puts "error: '#{e.name}' not found" + end + end + end +end diff --git a/lib/pry/default_commands/save_file.rb b/lib/pry/default_commands/save_file.rb new file mode 100644 index 00000000..f0f33baf --- /dev/null +++ b/lib/pry/default_commands/save_file.rb @@ -0,0 +1,95 @@ +class Pry + Pry::Commands.create_command "save-file", "Export to a file using content from the REPL." do + banner <<-USAGE + Usage: save-file [OPTIONS] [FILE] + Save REPL content to a file. + e.g: save-file -m my_method -m my_method2 ./hello.rb + e.g: save-file -i 1..10 ./hello.rb --append + e.g: save-file -k show-method ./my_command.rb + e.g: save-file -f sample_file --lines 2..10 ./output_file.rb + USAGE + + attr_accessor :content + attr_accessor :file_name + + def setup + self.content = "" + end + + def convert_to_range(n) + if !n.is_a?(Range) + (n..n) + else + n + end + end + + def options(opt) + opt.on :m, :method, "Save a method's source.", :argument => true do |meth_name| + meth = get_method_or_raise(meth_name, target, {}) + self.content << meth.source + end + opt.on :c, :class, "Save a class's source.", :argument => true do |class_name| + mod = Pry::WrappedModule.from_str(class_name, target) + self.content << mod.source + end + opt.on :k, :command, "Save a command's source.", :argument => true do |command_name| + command = find_command(command_name) + block = Pry::Method.new(command.block) + self.content << block.source + end + opt.on :f, :file, "Save a file.", :argument => true do |file| + self.content << File.read(File.expand_path(file)) + end + opt.on :l, :lines, "Only save a subset of lines.", :optional_argument => true, :as => Range, :default => 1..-1 + opt.on :o, :out, "Save entries from Pry's output result history. Takes an index or range.", :optional_argument => true, + :as => Range, :default => -5..-1 do |range| + range = convert_to_range(range) + + range.each do |v| + self.content << Pry.config.gist.inspecter.call(_pry_.output_array[v]) + end + + self.content << "\n" + end + opt.on :i, :in, "Save entries from Pry's input expression history. Takes an index or range.", :optional_argument => true, + :as => Range, :default => -5..-1 do |range| + input_expressions = _pry_.input_array[range] || [] + Array(input_expressions).each { |v| self.content << v } + end + opt.on :a, :append, "Append to the given file instead of overwriting it." + end + + def process + if args.empty? + raise CommandError, "Must specify a file name." + end + + self.file_name = File.expand_path(args.first) + + save_file + end + + def save_file + if self.content.empty? + raise CommandError, "Found no code to save." + end + + File.open(file_name, mode) do |f| + if opts.present?(:lines) + f.puts restrict_to_lines(content, opts[:l]) + else + f.puts content + end + end + end + + def mode + if opts.present?(:append) + "a" + else + "w" + end + end + end +end diff --git a/lib/pry/default_commands/shell_command.rb b/lib/pry/default_commands/shell_command.rb new file mode 100644 index 00000000..2977ff71 --- /dev/null +++ b/lib/pry/default_commands/shell_command.rb @@ -0,0 +1,20 @@ +class Pry + Pry::Commands.command(/\.(.*)/, "All text following a '.' is forwarded to the shell.", :listing => ".", :use_prefix => false, :takes_block => true) do |cmd| + if cmd =~ /^cd\s+(.+)/i + dest = $1 + begin + Dir.chdir File.expand_path(dest) + rescue Errno::ENOENT + raise CommandError, "No such directory: #{dest}" + end + else + pass_block(cmd) + + if command_block + command_block.call `#{cmd}` + else + Pry.config.system.call(output, cmd, _pry_) + end + end + end +end diff --git a/lib/pry/default_commands/shell_mode.rb b/lib/pry/default_commands/shell_mode.rb new file mode 100644 index 00000000..ae923592 --- /dev/null +++ b/lib/pry/default_commands/shell_mode.rb @@ -0,0 +1,16 @@ +class Pry + Pry::Commands.command "shell-mode", "Toggle shell mode. Bring in pwd prompt and file completion." do + case _pry_.prompt + when Pry::SHELL_PROMPT + _pry_.pop_prompt + _pry_.custom_completions = Pry::DEFAULT_CUSTOM_COMPLETIONS + else + _pry_.push_prompt Pry::SHELL_PROMPT + _pry_.custom_completions = Pry::FILE_COMPLETIONS + Readline.completion_proc = Pry::InputCompleter.build_completion_proc target, + _pry_.instance_eval(&Pry::FILE_COMPLETIONS) + end + end + + Pry::Commands.alias_command "file-mode", "shell-mode" +end diff --git a/lib/pry/default_commands/show_command.rb b/lib/pry/default_commands/show_command.rb new file mode 100644 index 00000000..e0bdecb1 --- /dev/null +++ b/lib/pry/default_commands/show_command.rb @@ -0,0 +1,42 @@ +class Pry + Pry::Commands.command "show-command", "Show the source for CMD." do |*args| + target = target() + + opts = Slop.parse!(args) do |opt| + opt.banner unindent <<-USAGE + Usage: show-command [OPTIONS] [CMD] + Show the source for command CMD. + e.g: show-command show-method + USAGE + + opt.on :l, "line-numbers", "Show line numbers." + opt.on :f, :flood, "Do not use a pager to view text longer than one screen." + opt.on :h, :help, "This message." do + output.puts opt.help + end + end + + next if opts.present?(:help) + + command_name = args.shift + if !command_name + raise CommandError, "You must provide a command name." + end + + if find_command(command_name) + block = Pry::Method.new(find_command(command_name).block) + + next unless block.source + set_file_and_dir_locals(block.source_file) + + output.puts make_header(block) + output.puts + + code = Pry::Code.from_method(block).with_line_numbers(opts.present?(:'line-numbers')).to_s + + render_output(code, opts) + else + raise CommandError, "No such command: #{command_name}." + end + end +end diff --git a/lib/pry/default_commands/show_doc.rb b/lib/pry/default_commands/show_doc.rb new file mode 100644 index 00000000..27db66e1 --- /dev/null +++ b/lib/pry/default_commands/show_doc.rb @@ -0,0 +1,133 @@ +class Pry + Pry::Commands.create_command "show-doc", "Show the documentation for a method or class. Aliases: \?", :shellwords => false do + include Pry::Helpers::ModuleIntrospectionHelpers + include Pry::Helpers::DocumentationHelpers + extend Pry::Helpers::BaseHelpers + + banner <<-BANNER + Usage: show-doc [OPTIONS] [METH] + Aliases: ? + + Show the documentation for a method or class. Tries instance methods first and then methods by default. + e.g show-doc hello_method # docs for hello_method + e.g show-doc Pry # docs for Pry class + e.g show-doc Pry -a # docs for all definitions of Pry class (all monkey patches) + BANNER + + options :requires_gem => "ruby18_source_location" if mri_18? + + def setup + require 'ruby18_source_location' if mri_18? + end + + def options(opt) + method_options(opt) + opt.on :l, "line-numbers", "Show line numbers." + opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)." + opt.on :f, :flood, "Do not use a pager to view text longer than one screen." + opt.on :a, :all, "Show docs for all definitions and monkeypatches of the module/class" + end + + def process_sourcable_object + name = args.first + object = target.eval(name) + + file_name, line = object.source_location + + doc = Pry::Code.from_file(file_name).comment_describing(line) + doc = strip_leading_hash_and_whitespace_from_ruby_comments(doc) + + result = "" + result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" + result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{doc.lines.count}\n\n" + result << doc + result << "\n" + end + + def process_module + raise Pry::CommandError, "No documentation found." if module_object.nil? + if opts.present?(:all) + all_modules + else + normal_module + end + end + + def normal_module + doc = "" + if module_object.yard_docs? + file_name, line = module_object.yard_file, module_object.yard_line + doc << module_object.yard_doc + start_line = 1 + else + attempt do |rank| + file_name, line = module_object.candidate(rank).source_location + set_file_and_dir_locals(file_name) + doc << module_object.candidate(rank).doc + start_line = module_start_line(module_object, rank) + end + end + + doc = Pry::Code.new(doc, start_line, :text). + with_line_numbers(use_line_numbers?).to_s + + doc.insert(0, "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line ? line : "N/A"}:\n\n") + end + + def all_modules + doc = "" + doc << "Found #{module_object.number_of_candidates} candidates for `#{module_object.name}` definition:\n" + module_object.number_of_candidates.times do |v| + candidate = module_object.candidate(v) + begin + doc << "\nCandidate #{v+1}/#{module_object.number_of_candidates}: #{candidate.file} @ #{candidate.line}:\n\n" + doc << candidate.doc + rescue Pry::RescuableException + doc << "No documentation found.\n" + next + end + end + doc + end + + def process_method + raise Pry::CommandError, "No documentation found." if method_object.doc.nil? || method_object.doc.empty? + + doc = process_comment_markup(method_object.doc) + output.puts make_header(method_object, doc) + output.puts "#{text.bold("Owner:")} #{method_object.owner || "N/A"}" + output.puts "#{text.bold("Visibility:")} #{method_object.visibility}" + output.puts "#{text.bold("Signature:")} #{method_object.signature}" + output.puts + + if use_line_numbers? + doc = Pry::Code.new(doc, start_line, :text). + with_line_numbers(true).to_s + end + + doc + end + + def module_start_line(mod, candidate=0) + if opts.present?(:'base-one') + 1 + else + if mod.candidate(candidate).line + mod.candidate(candidate).line - mod.candidate(candidate).doc.lines.count + else + 1 + end + end + end + + def start_line + if opts.present?(:'base-one') + 1 + else + (method_object.source_line - method_object.doc.lines.count) || 1 + end + end + end + + Pry::Commands.alias_command "?", "show-doc" +end diff --git a/lib/pry/default_commands/show_input.rb b/lib/pry/default_commands/show_input.rb new file mode 100644 index 00000000..5e6ba0f7 --- /dev/null +++ b/lib/pry/default_commands/show_input.rb @@ -0,0 +1,7 @@ +class Pry + Pry::Commands.create_command "show-input", "Show the contents of the input buffer for the current multi-line expression." do + def process + output.puts Code.new(eval_string).with_line_numbers + end + end +end diff --git a/lib/pry/default_commands/show_source.rb b/lib/pry/default_commands/show_source.rb new file mode 100644 index 00000000..e38cfca3 --- /dev/null +++ b/lib/pry/default_commands/show_source.rb @@ -0,0 +1,128 @@ +class Pry + Pry::Commands.create_command "show-source" do + include Pry::Helpers::ModuleIntrospectionHelpers + extend Pry::Helpers::BaseHelpers + + description "Show the source for a method or class. Aliases: $, show-method" + + banner <<-BANNER + Usage: show-source [OPTIONS] [METH|CLASS] + Aliases: $, show-method + + Show the source for a method or class. Tries instance methods first and then methods by default. + + e.g: `show-source hello_method` + e.g: `show-source -m hello_method` + e.g: `show-source Pry#rep` # source for Pry#rep method + e.g: `show-source Pry` # source for Pry class + e.g: `show-source Pry -a` # source for all Pry class definitions (all monkey patches) + e.g: `show-source Pry --super # source for superclass of Pry (Object class) + + https://github.com/pry/pry/wiki/Source-browsing#wiki-Show_method + BANNER + + options :shellwords => false + options :requires_gem => "ruby18_source_location" if mri_18? + + def setup + require 'ruby18_source_location' if mri_18? + end + + def options(opt) + method_options(opt) + opt.on :l, "line-numbers", "Show line numbers." + opt.on :b, "base-one", "Show line numbers but start numbering at 1 (useful for `amend-line` and `play` commands)." + opt.on :f, :flood, "Do not use a pager to view text longer than one screen." + opt.on :a, :all, "Show source for all definitions and monkeypatches of the module/class" + end + + def process_sourcable_object + name = args.first + object = target.eval(name) + + file_name, line = object.source_location + + source = Pry::Code.from_file(file_name).expression_at(line) + code = Pry::Code.new(source).with_line_numbers(use_line_numbers?).to_s + + result = "" + result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" + result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" + result << code + result << "\n" + end + + def process_method + raise CommandError, "Could not find method source" unless method_object.source + + code = "" + code << make_header(method_object) + code << "#{text.bold("Owner:")} #{method_object.owner || "N/A"}\n" + code << "#{text.bold("Visibility:")} #{method_object.visibility}\n" + code << "\n" + + code << Code.from_method(method_object, start_line). + with_line_numbers(use_line_numbers?).to_s + end + + def process_module + raise Pry::CommandError, "No documentation found." if module_object.nil? + if opts.present?(:all) + all_modules + else + normal_module + end + end + + def normal_module + file_name = line = code = nil + attempt do |rank| + file_name, line = module_object.candidate(rank).source_location + set_file_and_dir_locals(file_name) + code = Code.from_module(module_object, module_start_line(module_object, rank), rank). + with_line_numbers(use_line_numbers?).to_s + end + + result = "" + result << "\n#{Pry::Helpers::Text.bold('From:')} #{file_name} @ line #{line}:\n" + result << "#{Pry::Helpers::Text.bold('Number of lines:')} #{code.lines.count}\n\n" + result << code + end + + def all_modules + mod = module_object + + result = "" + result << "Found #{mod.number_of_candidates} candidates for `#{mod.name}` definition:\n" + mod.number_of_candidates.times do |v| + candidate = mod.candidate(v) + begin + result << "\nCandidate #{v+1}/#{mod.number_of_candidates}: #{candidate.file} @ line #{candidate.line}:\n" + code = Code.from_module(mod, module_start_line(mod, v), v). + with_line_numbers(use_line_numbers?).to_s + result << "Number of lines: #{code.lines.count}\n\n" + result << code + rescue Pry::RescuableException + result << "\nNo code found.\n" + next + end + end + result + end + + def use_line_numbers? + opts.present?(:b) || opts.present?(:l) + end + + def start_line + if opts.present?(:'base-one') + 1 + else + method_object.source_line || 1 + end + end + end + + Pry::Commands.alias_command "show-method", "show-source" + Pry::Commands.alias_command "$", "show-source" +end diff --git a/lib/pry/default_commands/simple_prompt.rb b/lib/pry/default_commands/simple_prompt.rb new file mode 100644 index 00000000..7c8163d7 --- /dev/null +++ b/lib/pry/default_commands/simple_prompt.rb @@ -0,0 +1,10 @@ +class Pry + Pry::Commands.command "simple-prompt", "Toggle the simple prompt." do + case _pry_.prompt + when Pry::SIMPLE_PROMPT + _pry_.pop_prompt + else + _pry_.push_prompt Pry::SIMPLE_PROMPT + end + end +end diff --git a/lib/pry/default_commands/stat.rb b/lib/pry/default_commands/stat.rb new file mode 100644 index 00000000..8870cb0c --- /dev/null +++ b/lib/pry/default_commands/stat.rb @@ -0,0 +1,28 @@ +class Pry + Pry::Commands.create_command "stat", "View method information and set _file_ and _dir_ locals.", :shellwords => false do + banner <<-BANNER + Usage: stat [OPTIONS] [METH] + Show method information for method METH and set _file_ and _dir_ locals. + e.g: stat hello_method + BANNER + + def options(opt) + method_options(opt) + end + + def process + meth = method_object + output.puts unindent <<-EOS + Method Information: + -- + Name: #{meth.name} + Owner: #{meth.owner ? meth.owner : "Unknown"} + Visibility: #{meth.visibility} + Type: #{meth.is_a?(::Method) ? "Bound" : "Unbound"} + Arity: #{meth.arity} + Method Signature: #{meth.signature} + Source Location: #{meth.source_location ? meth.source_location.join(":") : "Not found."} + EOS + end + end +end diff --git a/lib/pry/default_commands/switch_to.rb b/lib/pry/default_commands/switch_to.rb new file mode 100644 index 00000000..da426d73 --- /dev/null +++ b/lib/pry/default_commands/switch_to.rb @@ -0,0 +1,11 @@ +class Pry + Pry::Commands.command "switch-to", "Start a new sub-session on a binding in the current stack (numbered by nesting)." do |selection| + selection = selection.to_i + + if selection < 0 || selection > _pry_.binding_stack.size - 1 + raise CommandError, "Invalid binding index #{selection} - use `nesting` command to view valid indices." + else + Pry.start(_pry_.binding_stack[selection]) + end + end +end diff --git a/lib/pry/default_commands/toggle_color.rb b/lib/pry/default_commands/toggle_color.rb new file mode 100644 index 00000000..b1e5750c --- /dev/null +++ b/lib/pry/default_commands/toggle_color.rb @@ -0,0 +1,6 @@ +class Pry + Pry::Commands.command "toggle-color", "Toggle syntax highlighting." do + Pry.color = !Pry.color + output.puts "Syntax highlighting #{Pry.color ? "on" : "off"}" + end +end diff --git a/lib/pry/default_commands/whereami.rb b/lib/pry/default_commands/whereami.rb index 0ad28ac0..121e642b 100644 --- a/lib/pry/default_commands/whereami.rb +++ b/lib/pry/default_commands/whereami.rb @@ -1,91 +1,86 @@ - class Pry - module DefaultCommands - Whereami = Pry::CommandSet.new do - create_command "whereami" do - description "Show code surrounding the current context." - group 'Context' - banner <<-BANNER - Usage: whereami [-q] [N] + Pry::Commands.create_command "whereami" do + description "Show code surrounding the current context." + group 'Context' + banner <<-BANNER + Usage: whereami [-q] [N] - Describe the current location. If you use `binding.pry` inside a - method then whereami will print out the source for that method. + Describe the current location. If you use `binding.pry` inside a + method then whereami will print out the source for that method. - If a number is passed, then N lines before and after the current line - will be shown instead of the method itself. + If a number is passed, then N lines before and after the current line + will be shown instead of the method itself. - The `-q` flag can be used to suppress error messages in the case that - there's no code to show. This is used by pry in the default - before_session hook to show you when you arrive at a `binding.pry`. + The `-q` flag can be used to suppress error messages in the case that + there's no code to show. This is used by pry in the default + before_session hook to show you when you arrive at a `binding.pry`. - When pry was started on an Object and there is no associated method, - whereami will instead output a brief description of the current - object. - BANNER + When pry was started on an Object and there is no associated method, + whereami will instead output a brief description of the current + object. + BANNER - def setup - @method = Pry::Method.from_binding(target) - @file = target.eval('__FILE__') - @line = target.eval('__LINE__') + def setup + @method = Pry::Method.from_binding(target) + @file = target.eval('__FILE__') + @line = target.eval('__LINE__') + end + + def options(opt) + opt.on :q, :quiet, "Don't display anything in case of an error" + end + + def code + @code ||= if show_method? + Pry::Code.from_method(@method) + else + Pry::Code.from_file(@file).around(@line, window_size) + end + end + + def location + "#{@file} @ line #{show_method? ? @method.source_line : @line} #{@method && @method.name_with_owner}" + end + + def process + if opts.quiet? && (internal_binding?(target) || !code?) + return + elsif internal_binding?(target) + if target_self == TOPLEVEL_BINDING.eval("self") + output.puts "At the top level." + else + output.puts "Inside #{Pry.view_clip(target_self)}." end + return + end - def options(opt) - opt.on :q, :quiet, "Don't display anything in case of an error" - end + set_file_and_dir_locals(@file) - def code - @code ||= if show_method? - Pry::Code.from_method(@method) - else - Pry::Code.from_file(@file).around(@line, window_size) - end - end + output.puts "\n#{text.bold('From:')} #{location}:\n\n" + output.puts code.with_line_numbers.with_marker(@line) + output.puts + end - def location - "#{@file} @ line #{show_method? ? @method.source_line : @line} #{@method && @method.name_with_owner}" - end + private - def process - if opts.quiet? && (internal_binding?(target) || !code?) - return - elsif internal_binding?(target) - if target_self == TOPLEVEL_BINDING.eval("self") - output.puts "At the top level." - else - output.puts "Inside #{Pry.view_clip(target_self)}." - end - return - end + def show_method? + args.empty? && @method && @method.source? && @method.source_range.count < 20 && + # These checks are needed in case of an eval with a binding and file/line + # numbers set to outside the function. As in rails' use of ERB. + @method.source_file == @file && @method.source_range.include?(@line) + end - set_file_and_dir_locals(@file) + def code? + !!code + rescue MethodSource::SourceNotFoundError + false + end - output.puts "\n#{text.bold('From:')} #{location}:\n\n" - output.puts code.with_line_numbers.with_marker(@line) - output.puts - end - - private - - def show_method? - args.empty? && @method && @method.source? && @method.source_range.count < 20 && - # These checks are needed in case of an eval with a binding and file/line - # numbers set to outside the function. As in rails' use of ERB. - @method.source_file == @file && @method.source_range.include?(@line) - end - - def code? - !!code - rescue MethodSource::SourceNotFoundError - false - end - - def window_size - if args.empty? - Pry.config.default_window_size - else - args.first.to_i - end - end + def window_size + if args.empty? + Pry.config.default_window_size + else + args.first.to_i end end end diff --git a/lib/pry/default_commands/wtf.rb b/lib/pry/default_commands/wtf.rb new file mode 100644 index 00000000..f56d34b8 --- /dev/null +++ b/lib/pry/default_commands/wtf.rb @@ -0,0 +1,36 @@ +class Pry + Pry::Commands.create_command(/wtf([?!]*)/, "Show the backtrace of the most recent exception") do + options :listing => 'wtf?' + + banner <<-BANNER + Show's a few lines of the backtrace of the most recent exception (also available + as _ex_.backtrace). + + If you want to see more lines, add more question marks or exclamation marks: + + e.g. + pry(main)> wtf? + pry(main)> wtf?!???!?!? + + To see the entire backtrace, pass the -v/--verbose flag: + + e.g. + pry(main)> wtf -v + BANNER + + def options(opt) + opt.on(:v, :verbose, "Show the full backtrace.") + end + + def process + raise Pry::CommandError, "No most-recent exception" unless _pry_.last_exception + + output.puts "#{text.bold('Exception:')} #{_pry_.last_exception.class}: #{_pry_.last_exception}\n--" + if opts.verbose? + output.puts Pry::Code.new(_pry_.last_exception.backtrace, 0, :text).with_line_numbers.to_s + else + output.puts Pry::Code.new(_pry_.last_exception.backtrace.first([captures[0].size, 0.5].max * 10), 0, :text).with_line_numbers.to_s + end + end + end +end diff --git a/lib/pry/helpers.rb b/lib/pry/helpers.rb index 1dc539ae..14503e36 100644 --- a/lib/pry/helpers.rb +++ b/lib/pry/helpers.rb @@ -2,3 +2,4 @@ require "pry/helpers/base_helpers" require "pry/helpers/options_helpers" require "pry/helpers/command_helpers" require "pry/helpers/text" +require "pry/helpers/module_introspection_helpers" diff --git a/lib/pry/helpers/module_introspection_helpers.rb b/lib/pry/helpers/module_introspection_helpers.rb new file mode 100644 index 00000000..fbaf0395 --- /dev/null +++ b/lib/pry/helpers/module_introspection_helpers.rb @@ -0,0 +1,106 @@ +class Pry + module Helpers + module ModuleIntrospectionHelpers + attr_accessor :module_object + + def module_object + if @module_object + @module_object + else + name = args.first + @module_object = WrappedModule.from_str(name, target) + if @module_object + sup = @module_object.ancestors.select do |anc| + anc.class == @module_object.wrapped.class + end[opts[:super]] + @module_object = sup ? Pry::WrappedModule(sup) : nil + end + end + end + + # @param [String] + # @param [Binding] target The binding context of the input. + # @return [Symbol] type of input + def input_type(input,target) + if input == "" + :blank + elsif target.eval("defined? #{input} ") =~ /variable|constant/ && + target.eval(input).respond_to?(:source_location) + :sourcable_object + elsif Pry::Method.from_str(input,target) + :method + elsif Pry::WrappedModule.from_str(input, target) + :module + else + :unknown + end + end + + def process(name) + input = args.join(" ") + type = input_type(input, target) + + code_or_doc = case type + when :blank + process_blank + when :sourcable_object + process_sourcable_object + when :method + process_method + when :module + process_module + else + command_error("method or module for '#{input}' could not be found or derived", false) + end + + render_output(code_or_doc, opts) + end + + def process_blank + if mod = extract_module_from_internal_binding + @module_object = mod + process_module + elsif meth = extract_method_from_binding + @method_object = meth + process_method + else + command_error("method or module for '' could not be derived", false) + end + end + + def extract_module_from_internal_binding + if args.empty? && internal_binding?(target) + mod = target_self.is_a?(Module) ? target_self : target_self.class + Pry::WrappedModule(mod) + end + end + + def extract_method_from_binding + Pry::Method.from_binding(target) + end + + def module_start_line(mod, candidate_rank=0) + if opts.present?(:'base-one') + 1 + else + mod.candidate(candidate_rank).line + end + end + + def use_line_numbers? + opts.present?(:b) || opts.present?(:l) + end + + def attempt + rank = 0 + begin + yield(rank) + rescue Pry::CommandError + raise if rank > (module_object.number_of_candidates - 1) + rank += 1 + retry + end + end + end + end +end diff --git a/test/test_command.rb b/test/test_command.rb index bde244de..9ecc053b 100644 --- a/test/test_command.rb +++ b/test/test_command.rb @@ -4,7 +4,7 @@ describe "Pry::Command" do before do @set = Pry::CommandSet.new - @set.import Pry::DefaultCommands::Help + @set.import Pry::Commands end describe 'call_safely' do diff --git a/test/test_command_integration.rb b/test/test_command_integration.rb index 098f95f0..07b38850 100644 --- a/test/test_command_integration.rb +++ b/test/test_command_integration.rb @@ -546,7 +546,9 @@ describe "commands" do end it 'should change description of a command using desc' do - klass = Pry::CommandSet.new do; import Pry::DefaultCommands::Help; end + klass = Pry::CommandSet.new do + import Pry::Commands + end orig = klass.commands["help"].description klass.instance_eval do desc "help", "blah" diff --git a/test/test_command_set.rb b/test/test_command_set.rb index b6b33fad..f0cdf439 100644 --- a/test/test_command_set.rb +++ b/test/test_command_set.rb @@ -2,7 +2,10 @@ require 'helper' describe Pry::CommandSet do before do - @set = Pry::CommandSet.new{ import Pry::DefaultCommands::Help } + @set = Pry::CommandSet.new do + import Pry::Commands + end + @ctx = { :target => binding, :command_set => @set diff --git a/test/test_default_commands/test_help.rb b/test/test_default_commands/test_help.rb index 2fb7b73f..7527d8f4 100644 --- a/test/test_default_commands/test_help.rb +++ b/test/test_default_commands/test_help.rb @@ -4,8 +4,7 @@ describe "'help' command" do before do @oldset = Pry.config.commands @set = Pry.config.commands = Pry::CommandSet.new do - import Pry::DefaultCommands::Help - import Pry::DefaultCommands::Ls + import Pry::Commands end end