From 02435be016024891b6a64c3c10275c64951cbfd0 Mon Sep 17 00:00:00 2001 From: Jonathan Allard Date: Fri, 26 Jun 2020 20:15:18 -0400 Subject: [PATCH 1/4] Refactor into Frames --- lib/pry-stack_explorer.rb | 1 + lib/pry-stack_explorer/commands.rb | 96 +++++------------------- lib/pry-stack_explorer/frame.rb | 115 +++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 78 deletions(-) create mode 100644 lib/pry-stack_explorer/frame.rb diff --git a/lib/pry-stack_explorer.rb b/lib/pry-stack_explorer.rb index 068ab6a..7bff43e 100755 --- a/lib/pry-stack_explorer.rb +++ b/lib/pry-stack_explorer.rb @@ -4,6 +4,7 @@ require "pry" unless defined?(::Pry) require "pry-stack_explorer/version" require "pry-stack_explorer/commands" +require "pry-stack_explorer/frame" require "pry-stack_explorer/frame_manager" require "pry-stack_explorer/when_started_hook" require "binding_of_caller" diff --git a/lib/pry-stack_explorer/commands.rb b/lib/pry-stack_explorer/commands.rb index d75b902..b487a71 100644 --- a/lib/pry-stack_explorer/commands.rb +++ b/lib/pry-stack_explorer/commands.rb @@ -20,69 +20,6 @@ def prior_context_exists? frame_managers.count > 1 || frame_manager.prior_binding end - # Return a description of the frame (binding). - # This is only useful for regular old bindings that have not been - # enhanced by `#of_caller`. - # @param [Binding] b The binding. - # @return [String] A description of the frame (binding). - def frame_description(b) - b_self = b.eval('self') - b_method = b.eval('__method__') - - if b_method && b_method != :__binding__ && b_method != :__binding_impl__ - b_method.to_s - elsif b_self.instance_of?(Module) - "" - elsif b_self.instance_of?(Class) - "" - else - "
" - end - end - - # Return a description of the passed binding object. Accepts an - # optional `verbose` parameter. - # @param [Binding] b The binding. - # @param [Boolean] verbose Whether to generate a verbose description. - # @return [String] The description of the binding. - def frame_info(b, verbose = false) - meth = b.eval('__method__') - b_self = b.eval('self') - meth_obj = Pry::Method.from_binding(b) if meth - - type = b.frame_type ? "[#{b.frame_type}]".ljust(9) : "" - desc = b.frame_description ? "#{b.frame_description}" : "#{frame_description(b)}" - sig = meth_obj ? "<#{signature_with_owner(meth_obj)}>" : "" - - self_clipped = "#{Pry.view_clip(b_self)}" - path = '@ ' + b.source_location.join(':') - - if !verbose - "#{type} #{desc} #{sig}" - else - "#{type} #{desc} #{sig}\n in #{self_clipped} #{path}" - end - end - - # @param [Pry::Method] meth_obj The method object. - # @return [String] Signature for the method object in Class#method format. - def signature_with_owner(meth_obj) - if !meth_obj.undefined? - args = meth_obj.parameters.inject([]) do |arr, (type, name)| - name ||= (type == :block ? 'block' : "arg#{arr.size + 1}") - arr << case type - when :req then name.to_s - when :opt then "#{name}=?" - when :rest then "*#{name}" - when :block then "&#{name}" - else '?' - end - end - "#{meth_obj.name_with_owner}(#{args.join(', ')})" - else - "#{meth_obj.name_with_owner}(UNKNOWN) (undefined method)" - end - end # Regexp.new(args[0]) def find_frame_by_regex(regex, up_or_down) @@ -225,7 +162,8 @@ def process new_frame_index = find_frame_by_regex(Regexp.new(args[0]), :up) frame_manager.change_frame_to new_frame_index else - output.puts "##{frame_manager.binding_index} #{frame_info(target, true)}" + frame = PryStackExplorer::Frame.make(target) + output.puts "##{frame_manager.binding_index} #{frame.info(verbose: true)}" end end end @@ -250,18 +188,6 @@ def options(opt) opt.on :a, :app, "Display application frames only", optional_argument: true end - def memoized_info(index, b, verbose) - frame_manager.user[:frame_info] ||= Hash.new { |h, k| h[k] = [] } - - if verbose - frame_manager.user[:frame_info][:v][index] ||= frame_info(b, verbose) - else - frame_manager.user[:frame_info][:normal][index] ||= frame_info(b, verbose) - end - end - - private :memoized_info - # @return [Array>] Return tuple of # base_frame_index and the array of frames. def selected_stack_frames @@ -320,11 +246,25 @@ def frames_with_indices end end + ARROW = "=>" + EMPTY = " " + # "=> #0 method_name " def make_stack_line(b, i, active) - arw = active ? "=>" : " " + arrow = active ? ARROW : EMPTY + frame_no = i.to_s.rjust(2) + frame_info = memoized_frame(i, b).info(verbose: opts[:v]) + + [ + arrow, + blue(bold frame_no) + ":", + frame_info, + ].join(" ") + end - "#{arw} ##{i} #{memoized_info(i, b, opts[:v])}" + def memoized_frame(index, b) + frame_manager.user[:frame_info] ||= {} + frame_manager.user[:frame_info][index] ||= PryStackExplorer::Frame.make(b) end def offset_frames diff --git a/lib/pry-stack_explorer/frame.rb b/lib/pry-stack_explorer/frame.rb new file mode 100644 index 0000000..b92a922 --- /dev/null +++ b/lib/pry-stack_explorer/frame.rb @@ -0,0 +1,115 @@ +module PryStackExplorer + class Frame + attr_reader :b + + def self.make(_binding) + new(_binding) + end + + def initialize(_binding) + @b = _binding + end + + # Return a description of the frame (binding). + # This is only useful for regular old bindings that have not been + # enhanced by `#of_caller`. + # @return [String] A description of the frame (binding). + def description + return b.frame_description if b.frame_description + + if is_method? + _method.to_s + elsif b.receiver.instance_of?(Module) + "" + elsif b.receiver.instance_of?(Class) + "" + else + "
" + end + end + + + # Produces a string describing the frame + # @param [Options] verbose: Whether to generate a verbose description. + # @return [String] The description of the binding. + def info(verbose: false) + return @info[!!verbose] if @info + + base = "" + base << faded(pretty_type.ljust(9)) + base << " #{description}" + + if sig + base << faded(" | ") + base << sig + end + + @info = { + false => base, + true => base + "\n in #{self_clipped} #{path}", + } + + @info[!!verbose] + end + + def pretty_type + type ? "[#{type}]" : "" + end + + def type + b.frame_type + end + + def _method + @_method ||= b.eval('__method__') + end + + def is_method? + _method && + _method != :__binding__ && + _method != :__binding_impl__ + end + + def self_clipped + Pry.view_clip(b.receiver) + end + + def path + '@ ' + b.source_location.join(':') + end + + def pry_method + Pry::Method.from_binding(b) if _method + end + + def sig + return unless pry_method + self.class.method_signature_with_owner(pry_method) + end + + # @param [Pry::Method] pry_method The method object. + # @return [String] Signature for the method object in Class#method format. + def self.method_signature_with_owner(pry_method) + if pry_method.undefined? + return "#{pry_method.name_with_owner}(UNKNOWN) (undefined method)" + end + + args = pry_method.parameters.inject([]) do |arr, (type, name)| + name ||= (type == :block ? 'block' : "arg#{arr.size + 1}") + arr << case type + when :req then name.to_s + when :opt then "#{name}=?" + when :rest then "*#{name}" + when :block then "&#{name}" + else '?' + end + end + "#{pry_method.name_with_owner}(#{args.join(', ')})" + end + + # Not in Pry yet + def faded(text) + "\e[2m#{text}\e[0m" + end + end +end From cb342632ad0368da0f612e552ad80fd3218f3655 Mon Sep 17 00:00:00 2001 From: Jonathan Allard Date: Fri, 26 Jun 2020 20:21:40 -0400 Subject: [PATCH 2/4] Add Frames for RSpec blocks --- lib/pry-stack_explorer/frame.rb | 8 +++- lib/pry-stack_explorer/frame/it_block.rb | 59 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 lib/pry-stack_explorer/frame/it_block.rb diff --git a/lib/pry-stack_explorer/frame.rb b/lib/pry-stack_explorer/frame.rb index b92a922..9592e67 100644 --- a/lib/pry-stack_explorer/frame.rb +++ b/lib/pry-stack_explorer/frame.rb @@ -1,9 +1,15 @@ +require_relative "frame/it_block" + module PryStackExplorer class Frame attr_reader :b def self.make(_binding) - new(_binding) + if defined?(RSpec::Core) && _binding.receiver.is_a?(RSpec::Core::ExampleGroup) + ItBlock.new(_binding) + else + new(_binding) + end end def initialize(_binding) diff --git a/lib/pry-stack_explorer/frame/it_block.rb b/lib/pry-stack_explorer/frame/it_block.rb new file mode 100644 index 0000000..3f9b7ca --- /dev/null +++ b/lib/pry-stack_explorer/frame/it_block.rb @@ -0,0 +1,59 @@ +module PryStackExplorer + class Frame + class ItBlock < self + def description + if is_method? + pry_method.name + else + it_description + end + end + + # it "does fun things" + def it_description + if metadata[:location] + "it " + metadata[:description]&.inspect + else + "it (anonymous)" + end + end + + def sig + super || metadata[:class_name] + end + + def type + if is_method? + super || "block" + else + "it" + end + end + + # Matches: + # # + # # + INSPECT_REGEXP = %r{ + \#< + (?.+?) + ( + \s" + (?.*?) + " + )? + ( + \s\( + (?.*?) + \) + )? + > + }x + + def metadata + @metadata ||= b.receiver.inspect + .match(INSPECT_REGEXP) + .named_captures.transform_keys(&:to_sym) + end + end + end +end From 6968f84b2e75be84b1bb0d5b9434e7f2455f4b02 Mon Sep 17 00:00:00 2001 From: Jonathan Allard Date: Mon, 29 Jun 2020 21:23:04 -0400 Subject: [PATCH 3/4] color wip --- lib/pry-stack_explorer/frame.rb | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/pry-stack_explorer/frame.rb b/lib/pry-stack_explorer/frame.rb index 9592e67..0e77aa6 100644 --- a/lib/pry-stack_explorer/frame.rb +++ b/lib/pry-stack_explorer/frame.rb @@ -34,6 +34,19 @@ def description end end + T = Pry::Helpers::Text + + COLOR_SCHEME = { + description: { + context: :default, + method: :green, + }, + signature: { + class: :default, + method: [:blue, :bold], + arguments: :blue, + } + } # Produces a string describing the frame # @param [Options] verbose: Whether to generate a verbose description. @@ -41,13 +54,29 @@ def description def info(verbose: false) return @info[!!verbose] if @info + deskription = description&.match(/((?:block(?:\s\(\d levels\))?)(?: in ))?(.*)/) + .to_a.then do |_, a, b| + [ + a, + T.green(b) + ].join("") + end + base = "" base << faded(pretty_type.ljust(9)) - base << " #{description}" + base << " " + base << (deskription) if sig base << faded(" | ") - base << sig + base << sig.match(/(.*?)([#\.].*?)(\(.*?\))/).to_a + .then do |_, a, b, c| + [ + (T.default a), + (T.blue T.bold b), + (T.blue c) + ].join("") + end end @info = { From a4bee8bfec6dbe9ae1161d0f6c4e6596911f5eba Mon Sep 17 00:00:00 2001 From: Jonathan Allard Date: Mon, 17 Aug 2020 18:23:08 -0400 Subject: [PATCH 4/4] wip --- lib/pry-stack_explorer/frame.rb | 122 +++++++++++++++++++++----------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/lib/pry-stack_explorer/frame.rb b/lib/pry-stack_explorer/frame.rb index 0e77aa6..3028b0a 100644 --- a/lib/pry-stack_explorer/frame.rb +++ b/lib/pry-stack_explorer/frame.rb @@ -20,7 +20,7 @@ def initialize(_binding) # This is only useful for regular old bindings that have not been # enhanced by `#of_caller`. # @return [String] A description of the frame (binding). - def description + def _description return b.frame_description if b.frame_description if is_method? @@ -34,7 +34,30 @@ def description end end - T = Pry::Helpers::Text + DESCRIPTION_PATTERN = %r{ + (? + (?: + block\s + (?:\(\d\ levels\)\ )? + ) + (?:in\ ) + )? + (?.*) + }x + + def description + return unless _description + _description.match(DESCRIPTION_PATTERN).named_captures + end + + module T + extend Pry::Helpers::Text + + # Not in Pry yet + def self.faded(text) + "\e[2m#{text}\e[0m" + end + end COLOR_SCHEME = { description: { @@ -42,53 +65,62 @@ def description method: :green, }, signature: { - class: :default, + module: :default, method: [:blue, :bold], arguments: :blue, } } + # faded(" | ") + PIPE = "\e[2m | \e[0m" + + def apply_color(string, color = nil, weight = nil) + return unless string + + string = T.public_send(weight, string) if weight + string = T.public_send(color, string) if color + + string + end + # Produces a string describing the frame # @param [Options] verbose: Whether to generate a verbose description. # @return [String] The description of the binding. def info(verbose: false) return @info[!!verbose] if @info - deskription = description&.match(/((?:block(?:\s\(\d levels\))?)(?: in ))?(.*)/) - .to_a.then do |_, a, b| - [ - a, - T.green(b) - ].join("") - end - - base = "" - base << faded(pretty_type.ljust(9)) - base << " " - base << (deskription) - - if sig - base << faded(" | ") - base << sig.match(/(.*?)([#\.].*?)(\(.*?\))/).to_a - .then do |_, a, b, c| - [ - (T.default a), - (T.blue T.bold b), - (T.blue c) - ].join("") - end - end - - @info = { - false => base, - true => base + "\n in #{self_clipped} #{path}", - } - + @info = _info @info[!!verbose] end - def pretty_type - type ? "[#{type}]" : "" + def _info + output = {} + output[:type] = T.faded(type.to_s.ljust(10)) + + output[:full_description] = [ + # description + [ + apply_color(description["context"], nil), + apply_color(description["method"], :green), + ].compact.join(""), + + # signature + [ + apply_color(signature['module'], nil), + apply_color(signature['method'], :blue, :bold), + apply_color(signature['arguments'], :faded), + ].compact.join("") + + ].compact.join(PIPE) + + base = output.values.join("") + + extra_info = " in #{self_clipped} #{path}" + + { + false => base, + true => base + "\n" + extra_info, + } end def type @@ -117,9 +149,18 @@ def pry_method Pry::Method.from_binding(b) if _method end - def sig - return unless pry_method - self.class.method_signature_with_owner(pry_method) + SIGNATURE_PATTERN = / + (?.*?) + (?[#\.].*?) + (?\(.*?\)) + /x + + def signature + return {} unless pry_method + string = self.class.method_signature_with_owner(pry_method) + + # Will match strings like `Module::Module#method(args)` + string.match(SIGNATURE_PATTERN).named_captures end # @param [Pry::Method] pry_method The method object. @@ -141,10 +182,5 @@ def self.method_signature_with_owner(pry_method) end "#{pry_method.name_with_owner}(#{args.join(', ')})" end - - # Not in Pry yet - def faded(text) - "\e[2m#{text}\e[0m" - end end end