From 503e6bb63f80822ec0bb95a1a2af6077110eefe0 Mon Sep 17 00:00:00 2001 From: John Mair Date: Fri, 11 Jan 2013 20:38:12 +0100 Subject: [PATCH] edit command: add --method option * --method doesn't accept any parameters, it only edits the 'current' method associated with the binding. It's equivalent to 'edit-method' (with no args). * This option is necessary as 'edit' by itself edits the last expression. * Note that --method doesn't just edit the current method, if no method exists (i.e binding.pry was not put inside a method context) then it edits the current OBJECT instead. TODO: * explicitly restrict --method to *just* methods and not allow it to fall back to objects? * allow --method to take a parameter that must be a method object? e.g --method Pry#repl works but --method Pry would fail ? --- lib/pry/commands/edit.rb | 58 +++++++++++++++-------------- spec/commands/edit_spec.rb | 75 +++++++++++++++++++++++++++++++++++++- 2 files changed, 105 insertions(+), 28 deletions(-) diff --git a/lib/pry/commands/edit.rb b/lib/pry/commands/edit.rb index cd18b418..d3dce6d3 100644 --- a/lib/pry/commands/edit.rb +++ b/lib/pry/commands/edit.rb @@ -1,11 +1,4 @@ class Pry - # Uses the following state variables: - # - dynamical_ex_file [Array] - # Utilised in `edit --ex --patch` operations. Contains the source code - # of a monkey patched file, in which an exception was raised. We store - # the entire source code because an exception may happen anywhere in the - # code and there is no way to predict that. So we simply superimpose - # everything (admittedly, doing extra job). class Command::Edit < Pry::ClassCommand require 'pry/commands/edit/method_patcher' require 'pry/commands/edit/exception_patcher' @@ -24,6 +17,7 @@ class Pry edit sample.rb edit sample.rb --line 105 edit MyClass#my_method + edit --method edit -p MyClass#my_method edit YourClass edit --ex` @@ -43,11 +37,12 @@ class Pry 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)" opt.on :p, :patch, "Instead of editing the object's file, try to edit in a tempfile and apply as a monkey patch" + opt.on :m, :method, "Explicitly edit the _current_ method (when inside a method context)." end def process if bad_option_combination? - raise CommandError, "Only one of --ex, --temp, --in and FILE may be specified." + raise CommandError, "Only one of --ex, --temp, --in, --method and FILE may be specified." end if repl_edit? @@ -62,6 +57,11 @@ class Pry end end + def repl_edit? + !opts.present?(:ex) && !opts.present?(:current) && !opts.present?(:method) && + filename_argument.empty? + end + def repl_edit content = Pry::Editor.edit_tempfile_with_content(initial_temp_file_content, initial_temp_file_content.lines.count) @@ -72,10 +72,13 @@ class Pry end end + def runtime_patch? + opts.present?(:patch) || pry_method?(code_object) + end + def apply_runtime_patch if patch_exception? - ex_file_and_line = FileAndLineLocator.from_exception(_pry_.last_exception, opts[:ex].to_i) - ExceptionPatcher.new(_pry_, state, ex_file_and_line).perform_patch + ExceptionPatcher.new(_pry_, state, file_and_line_for_current_exception).perform_patch else if code_object.is_a?(Pry::Method) MethodPatcher.new(_pry_, code_object).perform_patch @@ -86,20 +89,24 @@ class Pry end def ensure_file_name_is_valid(file_name) - raise CommandError, "Cannot find a valid file for #{args.first}" if !file_name + raise CommandError, "Cannot find a valid file for #{filename_argument}" if !file_name raise CommandError, "#{file_name} is not a valid file name, cannot edit!" if not_a_real_file?(file_name) end + def file_and_line_for_current_exception + FileAndLineLocator.from_exception(_pry_.last_exception, opts[:ex].to_i) + end + def file_and_line file_name, line = if opts.present?(:current) FileAndLineLocator.from_binding(target) elsif opts.present?(:ex) - FileAndLineLocator.from_exception(_pry_.last_exception, opts[:ex].to_i) + file_and_line_for_current_exception elsif code_object - FileAndLineLocator.from_code_object(code_object, args.first) + FileAndLineLocator.from_code_object(code_object, filename_argument) else # when file and line are passed as a single arg, e.g my_file.rb:30 - FileAndLineLocator.from_filename_argument(args.first) + FileAndLineLocator.from_filename_argument(filename_argument) end [file_name, opts.present?(:line) ? opts[:l].to_i : line] @@ -107,6 +114,7 @@ class Pry def file_edit file_name, line = file_and_line + ensure_file_name_is_valid(file_name) # Sanitize blanks. @@ -122,22 +130,18 @@ class Pry end end + def filename_argument + args.first || "" + end + def code_object - @code_object ||= args.first && !probably_a_file?(args.first) && - Pry::CodeObject.lookup(args.first, target, _pry_) + @code_object ||= !probably_a_file?(filename_argument) && + Pry::CodeObject.lookup(filename_argument, _pry_) end - def repl_edit? - !opts.present?(:ex) && !opts.present?(:current) && args.empty? - end - - def runtime_patch? - opts.present?(:patch) || dynamically_defined_method? - end - - def dynamically_defined_method? + def pry_method?(code_object) code_object.is_a?(Pry::Method) && - code_object.dynamically_defined? + code_object.pry_method? end def patch_exception? @@ -146,7 +150,7 @@ class Pry def bad_option_combination? [opts.present?(:ex), opts.present?(:temp), - opts.present?(:in), !args.empty?].count(true) > 1 + opts.present?(:in), opts.present?(:method), !filename_argument.empty?].count(true) > 1 end def input_expression diff --git a/spec/commands/edit_spec.rb b/spec/commands/edit_spec.rb index 24ca3e9d..c8688d1b 100644 --- a/spec/commands/edit_spec.rb +++ b/spec/commands/edit_spec.rb @@ -307,7 +307,7 @@ describe "edit" do proc { pry_eval 'edit ruby.rb -i' }.should.raise(Pry::CommandError). - message.should =~ /Only one of --ex, --temp, --in and FILE/ + message.should =~ /Only one of --ex, --temp, --in, --method and FILE/ end it "should not work with nonsense" do @@ -623,4 +623,77 @@ describe "edit" do end end end + + describe "--method flag" do + before do + @t = pry_tester + class BinkyWink + eval %{ + def tits_macgee + binding + end + } + + def tots_macgee + :jeremy_jones + binding + end + end + end + + after do + Object.remove_const(:BinkyWink) + end + + it 'should edit method context' do + old_editor = Pry.editor + Pry.editor = lambda do |file, line| + [file, line].should == BinkyWink.instance_method(:tots_macgee).source_location + nil + end + + t = pry_tester(BinkyWink.new.tots_macgee) + t.process_command "edit -m -n" + Pry.editor = old_editor + end + + it 'errors when cannot find method context' do + old_editor = Pry.editor + Pry.editor = lambda do |file, line| + [file, line].should == BinkyWink.instance_method(:tits_macgee).source_location + nil + end + + t = pry_tester(BinkyWink.new.tits_macgee) + lambda { t.process_command "edit -m -n" }.should. + raise(Pry::CommandError).message.should.match(/Cannot find a file for/) + Pry.editor = old_editor + end + + it 'errors when a filename arg is passed with --method' do + lambda { @t.process_command "edit -m Pry#repl" }.should. + raise(Pry::CommandError).message.should.match(/Only one of/) + end + end + + describe "pretty error messages" do + before do + @t = pry_tester + class TrinkyDink + eval %{ + def claudia_linklater + end + } + end + end + + after do + Object.remove_const(:TrinkyDink) + end + + it 'should display a nice error message when cannot open a file' do + lambda { @t.process_command "edit TrinkyDink#claudia_linklater" }.should. + raise(Pry::CommandError).message.should.match(/Cannot find a file for/) + end + end end