From bc26e405ea1a06549ffc431350681064df4dc0c7 Mon Sep 17 00:00:00 2001 From: Kyrylo Silin Date: Sun, 11 Nov 2018 04:20:45 +0800 Subject: [PATCH] Simplify Prompt API Before this change when you set a prompt, you have to do the following: ```rb Pry.config.prompt = Pry::Prompt[:simple][:value] ``` The `[:value]` part was leaking implementation details and it proved to be an unnecessary step. With this change we can do the following: ```rb Pry.config.prompt = Pry::Prompt[:simple] ``` `[:value]` is omitted. I have also refactored some tests and removed irrelevant ones. The Array API for prompt is deprecated: `Pry.config.prompt = [proc {}, proc {}]` emits a warning now. --- lib/pry.rb | 2 +- lib/pry/cli.rb | 4 +- lib/pry/commands/change_prompt.rb | 8 +- lib/pry/commands/shell_mode.rb | 2 +- lib/pry/config/behavior.rb | 9 ++- lib/pry/config/default.rb | 2 +- lib/pry/prompt.rb | 100 +++++++++++++++++++++---- lib/pry/pry_instance.rb | 49 ++++++++---- spec/prompt_spec.rb | 120 ++++++++++++------------------ spec/pry_defaults_spec.rb | 100 ++++++++++++------------- 10 files changed, 232 insertions(+), 164 deletions(-) diff --git a/lib/pry.rb b/lib/pry.rb index afe1c12f..bd224ad6 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -110,6 +110,7 @@ require 'pry/commands' require 'pry/plugins' require 'pry/core_extensions' require 'pry/basic_object' +require "pry/prompt" require 'pry/config/behavior' require 'pry/config/memoization' require 'pry/config/default' @@ -125,7 +126,6 @@ require 'pry/editor' require 'pry/rubygem' require "pry/indent" require "pry/last_exception" -require "pry/prompt" require "pry/inspector" require 'pry/object_path' require 'pry/output' diff --git a/lib/pry/cli.rb b/lib/pry/cli.rb index a694ee7a..6fc021bc 100644 --- a/lib/pry/cli.rb +++ b/lib/pry/cli.rb @@ -180,11 +180,11 @@ Pry::CLI.add_options do end on "simple-prompt", "Enable simple prompt mode" do - Pry.config.prompt = Pry::Prompt[:simple][:value] + Pry.config.prompt = Pry::Prompt[:simple] end on "noprompt", "No prompt mode" do - Pry.config.prompt = Pry::Prompt[:none][:value] + Pry.config.prompt = Pry::Prompt[:none] end on :r, :require=, "`require` a Ruby script at startup" do |file| diff --git a/lib/pry/commands/change_prompt.rb b/lib/pry/commands/change_prompt.rb index 85696734..37d81da2 100644 --- a/lib/pry/commands/change_prompt.rb +++ b/lib/pry/commands/change_prompt.rb @@ -25,15 +25,15 @@ class Pry::Command::ChangePrompt < Pry::ClassCommand def list_prompts prompts = Pry::Prompt.all.map do |name, prompt| - "#{bold(name)}#{red(' (selected)') if _pry_.prompt == prompt[:value]}\n" + - prompt[:description] + "#{bold(name)}#{red(' (selected)') if _pry_.prompt == prompt}\n" + + prompt.description end output.puts(prompts.join("\n" * 2)) end def change_prompt(prompt) - if Pry::Prompt.all.key?(prompt) - _pry_.prompt = Pry::Prompt.all[prompt][:value] + if Pry::Prompt[prompt] + _pry_.prompt = Pry::Prompt[prompt] else raise Pry::CommandError, "'#{prompt}' isn't a known prompt. " \ "Run `change-prompt --list` to see the list of known prompts." diff --git a/lib/pry/commands/shell_mode.rb b/lib/pry/commands/shell_mode.rb index b6b42f84..8cb8fe52 100644 --- a/lib/pry/commands/shell_mode.rb +++ b/lib/pry/commands/shell_mode.rb @@ -13,7 +13,7 @@ class Pry if state.disabled state.prev_prompt = _pry_.prompt - _pry_.prompt = Pry::Prompt[:shell][:value] + _pry_.prompt = Pry::Prompt[:shell] else _pry_.prompt = state.prev_prompt end diff --git a/lib/pry/config/behavior.rb b/lib/pry/config/behavior.rb index 8bb68f05..27efb81a 100644 --- a/lib/pry/config/behavior.rb +++ b/lib/pry/config/behavior.rb @@ -1,8 +1,14 @@ class Pry class Config < Pry::BasicObject + # rubocop:disable Metrics/ModuleLength module Behavior ASSIGNMENT = "=".freeze - NODUP = [TrueClass, FalseClass, NilClass, Symbol, Numeric, Module, Proc].freeze + + NODUP = [ + TrueClass, FalseClass, NilClass, Symbol, Numeric, Module, Proc, + Pry::Prompt + ].freeze + INSPECT_REGEXP = /#{Regexp.escape "default=#<"}/ ReservedKeyError = Class.new(RuntimeError) @@ -251,5 +257,6 @@ class Pry @lookup.delete(key) end end + # rubocop:enable Metrics/ModuleLength end end diff --git a/lib/pry/config/default.rb b/lib/pry/config/default.rb index 928df4e8..f7a5cd6b 100644 --- a/lib/pry/config/default.rb +++ b/lib/pry/config/default.rb @@ -18,7 +18,7 @@ class Pry Pry::Prompt::DEFAULT_NAME }, prompt: proc { - Pry::Prompt[:default][:value] + Pry::Prompt[:default] }, prompt_safe_contexts: proc { Pry::Prompt::SAFE_CONTEXTS diff --git a/lib/pry/prompt.rb b/lib/pry/prompt.rb index 1e43841a..e4e46354 100644 --- a/lib/pry/prompt.rb +++ b/lib/pry/prompt.rb @@ -1,9 +1,9 @@ class Pry # Prompt represents the Pry prompt, which can be used with Readline-like # libraries. It defines a few default prompts (default prompt, simple prompt, - # etc) and also provides an API to add custom prompts. + # etc) and also provides an API for adding and implementing custom prompts. # - # @example + # @example Registering a new Pry prompt # Pry::Prompt.add( # :ipython, # 'IPython-like prompt', [':', '...:'] @@ -17,9 +17,23 @@ class Pry # # ...: end # # => :foo # # In [4]: + # + # @example Manually instantiating the Prompt class + # prompt_procs = [ + # proc { '#{rand(1)}>" }, + # proc { "#{('a'..'z').to_a.sample}*" } + # ] + # prompt = Pry::Prompt.new( + # :random, + # 'Random number or letter prompt.', + # prompt_procs + # ) + # prompt.wait_proc.call(...) #=> + # prompt.incomplete_proc.call(...) + # # @since v0.11.0 # @api public - module Prompt + class Prompt # @return [String] DEFAULT_NAME = 'pry'.freeze @@ -35,7 +49,7 @@ class Pry # Retrieves a prompt. # # @example - # Prompt[:my_prompt][:value] + # Prompt[:my_prompt] # # @param [Symbol] name The name of the prompt you want to access # @return [Hash{Symbol=>Object}] @@ -78,12 +92,13 @@ class Pry raise ArgumentError, "the '#{name}' prompt was already added" end - @prompts[name] = { - description: description, - value: separators.map do |sep| + @prompts[name] = new( + name, + description, + separators.map do |sep| proc { |context, nesting, _pry_| yield(context, nesting, _pry_, sep) } end - } + ) nil end @@ -97,10 +112,63 @@ class Pry end end - add 'default', - "The default Pry prompt. Includes information about the current expression \n" \ - "number, evaluation context, and nesting level, plus a reminder that you're \n" \ - 'using Pry.' do |context, nesting, _pry_, sep| + # @return [String] + attr_reader :name + + # @return [String] + attr_reader :description + + # @return [Array] the array of procs that hold + # `[wait_proc, incomplete_proc]` + attr_reader :prompt_procs + + # @param [String] name + # @param [String] description + # @param [Array] prompt_procs + def initialize(name, description, prompt_procs) + @name = name + @description = description + @prompt_procs = prompt_procs + end + + # @return [Proc] the proc which builds the wait prompt (`>`) + def wait_proc + @prompt_procs.first + end + + # @return [Proc] the proc which builds the prompt when in the middle of an + # expression such as open method, etc. (`*`) + def incomplete_proc + @prompt_procs.last + end + + # @deprecated Use a `Pry::Prompt` instance directly + def [](key) + key = key.to_s + loc = caller_locations(1..1).first + + if %w[name description].include?(key) + warn( + "#{loc.path}:#{loc.lineno}: warning: `Pry::Prompt[:#{@name}][:#{key}]` " \ + "is deprecated. Use `#{self.class}##{key}` instead" + ) + public_send(key) + elsif key.to_s == 'value' + warn( + "#{loc.path}:#{loc.lineno}: warning: `#{self.class}[:#{@name}][:value]` " \ + "is deprecated. Use `#{self.class}#prompt_procs` instead or an " \ + "instance of `#{self.class}` directly" + ) + @prompt_procs + end + end + + add( + :default, + "The default Pry prompt. Includes information about the current expression \n" \ + "number, evaluation context, and nesting level, plus a reminder that you're \n" \ + 'using Pry.' + ) do |context, nesting, _pry_, sep| format( "[%s] %s(%s)%s%s ", in_count: _pry_.input_ring.count, @@ -112,7 +180,7 @@ class Pry end add( - 'simple', + :simple, "A simple `>>`.", ['>> ', ' | '] ) do |_, _, _, sep| @@ -120,7 +188,7 @@ class Pry end add( - 'nav', + :nav, "A prompt that displays the binding stack as a path and includes information \n" \ "about #{Helpers::Text.bold('_in_')} and #{Helpers::Text.bold('_out_')}.", %w[> *] @@ -137,7 +205,7 @@ class Pry end add( - 'shell', + :shell, 'A prompt that displays `$PWD` as you change it.', %w[$ *] ) do |context, _nesting, _pry_, sep| @@ -151,7 +219,7 @@ class Pry end add( - 'none', + :none, 'Wave goodbye to the Pry prompt.', Array.new(2) ) { '' } diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 517df038..cd698ac7 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -58,7 +58,7 @@ class Pry # The object to use for commands. # @option options [Hash] :hooks # The defined hook Procs. - # @option options [Array] :prompt + # @option options [Pry::Prompt] :prompt # The array of Procs to use for prompts. # @option options [Proc] :print # The Proc to use for printing return values. @@ -85,14 +85,18 @@ class Pry @input_ring << nil push_initial_binding(target) exec_hook(:when_started, target, options, self) + @prompt_warn = false end # This is the prompt at the top of the prompt stack. - # @return [Array] the current prompt + # @return [Pry::Prompt] the current prompt def prompt prompt_stack.last end + # Sets the Pry prompt. + # @param [Pry::Prompt] new_prompt + # @return [void] def prompt=(new_prompt) if prompt_stack.empty? push_prompt new_prompt @@ -548,6 +552,22 @@ class Pry }) Pry.critical_section do + # If input buffer is empty, then use normal prompt. Otherwise use the wait + # prompt (indicating multi-line expression). + if prompt.is_a?(Pry::Prompt) + prompt_proc = eval_string.empty? ? prompt.wait_proc : prompt.incomplete_proc + return prompt_proc.call(c.object, c.nesting_level, c._pry_) + end + + unless @prompt_warn + @prompt_warn = true + output.warn( + "warning: setting prompt with help of " \ + "`Pry.config.prompt = [proc {}, proc {}]` is deprecated. " \ + "Use Pry::Prompt API instead" + ) + end + # If input buffer is empty then use normal prompt if eval_string.empty? generate_prompt(Array(prompt).first, c) @@ -575,27 +595,28 @@ class Pry # Pushes the current prompt onto a stack that it can be restored from later. # Use this if you wish to temporarily change the prompt. - # @param [Array] new_prompt - # @return [Array] new_prompt + # # @example - # new_prompt = [ proc { '>' }, proc { '>>' } ] - # push_prompt(new_prompt) # => new_prompt + # push_prompt(Pry::Prompt[:my_prompt]) + # + # @param [Pry::Prompt] new_prompt + # @return [Pry::Prompt] new_prompt def push_prompt(new_prompt) prompt_stack.push new_prompt end - # Pops the current prompt off of the prompt stack. - # If the prompt you are popping is the last prompt, it will not be popped. - # Use this to restore the previous prompt. - # @return [Array] Prompt being popped. + # Pops the current prompt off of the prompt stack. If the prompt you are + # popping is the last prompt, it will not be popped. Use this to restore the + # previous prompt. + # # @example - # prompt1 = [ proc { '>' }, proc { '>>' } ] - # prompt2 = [ proc { '$' }, proc { '>' } ] - # pry = Pry.new :prompt => prompt1 - # pry.push_prompt(prompt2) + # pry = Pry.new(prompt: Pry::Prompt[:my_prompt1]) + # pry.push_prompt(Pry::Prompt[:my_prompt2]) # pry.pop_prompt # => prompt2 # pry.pop_prompt # => prompt1 # pry.pop_prompt # => prompt1 + # + # @return [Pry::Prompt] the prompt being popped def pop_prompt prompt_stack.size > 1 ? prompt_stack.pop : prompt end diff --git a/spec/prompt_spec.rb b/spec/prompt_spec.rb index c339282f..80c9631b 100644 --- a/spec/prompt_spec.rb +++ b/spec/prompt_spec.rb @@ -23,7 +23,7 @@ describe Pry::Prompt do it "adds a new prompt" do described_class.add(:my_prompt) - expect(described_class[:my_prompt]).to be_a(Hash) + expect(described_class[:my_prompt]).to be_a(described_class) end it "raises error when separators.size != 2" do @@ -42,87 +42,59 @@ describe Pry::Prompt do end end - describe "one-parameter prompt proc" do - it 'should get full config object' do - config = nil - redirect_pry_io(InputTester.new("exit-all")) do - Pry.start(self, prompt: proc { |v| config = v }) - end - expect(config.is_a?(Pry::Config)).to eq true - end - - it 'should get full config object, when using a proc array' do - config1 = nil - redirect_pry_io(InputTester.new("exit-all")) do - Pry.start(self, prompt: [proc { |v| config1 = v }, proc { |v| _config2 = v }]) - end - expect(config1.is_a?(Pry::Config)).to eq true - end - - it 'should receive correct data in the config object' do - config = nil - redirect_pry_io(InputTester.new("def hello", "exit-all")) do - Pry.start(self, prompt: proc { |v| config = v }) - end - - expect(config.eval_string).to match(/def hello/) - expect(config.nesting_level).to eq 0 - expect(config.expr_number).to eq 1 - expect(config.cont).to eq true - expect(config._pry_.is_a?(Pry)).to eq true - expect(config.object).to eq self - end - - specify "object is Hash when current binding is a Hash" do - config = nil - h = {} - redirect_pry_io(InputTester.new("exit-all")) do - Pry.start(h, prompt: proc { |v| config = v }) - end - expect(config.object).to be(h) + describe "#name" do + it "returns name" do + prompt = described_class.new(:test, '', Array.new(2) { proc { '' } }) + expect(prompt.name).to eq(:test) end end - describe "BACKWARDS COMPATIBILITY: 3 parameter prompt proc" do - it 'should get 3 parameters' do - o = n = p = nil - redirect_pry_io(InputTester.new("exit-all")) do - Pry.start(:test, prompt: proc { |obj, nesting, _pry_| - o, n, p = obj, nesting, _pry_ }) - end - expect(o).to eq :test - expect(n).to eq 0 - expect(p.is_a?(Pry)).to eq true - end - - it 'should get 3 parameters, when using proc array' do - o1 = n1 = p1 = nil - redirect_pry_io(InputTester.new("exit-all")) do - Pry.start(:test, prompt: [proc { |obj, nesting, _pry_| - o1, n1, p1 = obj, nesting, _pry_ }, - proc { |obj, nesting, _pry_| - _o2, _n2, _p2 = obj, nesting, _pry_ }]) - end - expect(o1).to eq :test - expect(n1).to eq 0 - expect(p1.is_a?(Pry)).to eq true + describe "#description" do + it "returns description" do + prompt = described_class.new(:test, 'descr', Array.new(2) { proc { '' } }) + expect(prompt.description).to eq('descr') end end - it "can compute prompt name dynamically" do - config = nil - redirect_pry_io(InputTester.new("def hello", "exit-all")) do - Pry.start(self, prompt: proc { |v| config = v }) + describe "#prompt_procs" do + it "returns the proc array" do + prompt_procs = [proc { '>' }, proc { '*' }] + prompt = described_class.new(:test, 'descr', prompt_procs) + expect(prompt.prompt_procs).to eq(prompt_procs) + end + end + + describe "#wait_proc" do + it "returns the first proc" do + prompt_procs = [proc { '>' }, proc { '*' }] + prompt = described_class.new(:test, '', prompt_procs) + expect(prompt.wait_proc).to eq(prompt_procs.first) + end + end + + describe "#incomplete_proc" do + it "returns the second proc" do + prompt_procs = [proc { '>' }, proc { '*' }] + prompt = described_class.new(:test, '', prompt_procs) + expect(prompt.incomplete_proc).to eq(prompt_procs.last) + end + end + + describe "prompt invocation" do + let(:pry) { Pry.new } + + let(:enum) do + Enumerator.new do |y| + range = ('a'..'z').to_enum + loop { y << range.next } + end end - enum = Enumerator.new do |y| - count = 100 - loop { y << count += 1 } + it "computes prompt name dynamically" do + proc = described_class[:default].wait_proc + pry.config.prompt_name = Pry.lazy { enum.next } + expect(proc.call(Object.new, 1, pry, '>')).to eq('[1] a(#):1> ') + expect(proc.call(Object.new, 1, pry, '>')).to eq('[1] b(#):1> ') end - config._pry_.config.prompt_name = Pry.lazy { enum.next } - - proc = described_class[:default][:value].first - expect(proc.call(Object.new, 1, config._pry_)).to eq('[1] 101(#):1> ') - expect(proc.call(Object.new, 1, config._pry_)).to eq('[1] 102(#):1> ') end end diff --git a/spec/pry_defaults_spec.rb b/spec/pry_defaults_spec.rb index a0d79424..faf88745 100644 --- a/spec/pry_defaults_spec.rb +++ b/spec/pry_defaults_spec.rb @@ -141,95 +141,95 @@ describe "test Pry defaults" do end it 'should set the prompt default, and the default should be overridable (single prompt)' do - Pry.prompt = proc { "test prompt> " } - new_prompt = proc { "A" } + Pry.prompt = Pry::Prompt.new(:test, '', Array.new(2) { proc { '>' } }) + new_prompt = Pry::Prompt.new(:new_test, '', Array.new(2) { proc { 'A' } }) pry = Pry.new expect(pry.prompt).to eq Pry.prompt - expect(get_prompts(pry)).to eq ["test prompt> ", "test prompt> "] + expect(get_prompts(pry)).to eq(%w[> >]) pry = Pry.new(prompt: new_prompt) - expect(pry.prompt).to eq new_prompt - expect(get_prompts(pry)).to eq ["A", "A"] + expect(pry.prompt).to eq(new_prompt) + expect(get_prompts(pry)).to eq(%w[A A]) pry = Pry.new expect(pry.prompt).to eq Pry.prompt - expect(get_prompts(pry)).to eq ["test prompt> ", "test prompt> "] + expect(get_prompts(pry)).to eq(%w[> >]) end it 'should set the prompt default, and the default should be overridable (multi prompt)' do - Pry.prompt = [proc { "test prompt> " }, proc { "test prompt* " }] - new_prompt = [proc { "A" }, proc { "B" }] + Pry.prompt = Pry::Prompt.new(:test, '', [proc { '>' }, proc { '*' }]) + new_prompt = Pry::Prompt.new(:new_test, '', [proc { 'A' }, proc { 'B' }]) pry = Pry.new expect(pry.prompt).to eq Pry.prompt - expect(get_prompts(pry)).to eq ["test prompt> ", "test prompt* "] + expect(get_prompts(pry)).to eq(%w[> *]) pry = Pry.new(prompt: new_prompt) - expect(pry.prompt).to eq new_prompt - expect(get_prompts(pry)).to eq ["A", "B"] + expect(pry.prompt).to eq(new_prompt) + expect(get_prompts(pry)).to eq(%w[A B]) pry = Pry.new - expect(pry.prompt).to eq Pry.prompt - expect(get_prompts(pry)).to eq ["test prompt> ", "test prompt* "] + expect(pry.prompt).to eq(Pry.prompt) + expect(get_prompts(pry)).to eq(%w[> *]) end describe 'storing and restoring the prompt' do - before do - make = lambda do |name,i| - prompt = [ proc { "#{i}>" } , proc { "#{i + 1}>" } ] - (class << prompt; self; end).send(:define_method, :inspect) { "" } - prompt - end - @a , @b , @c = make[:a,0] , make[:b,1] , make[:c,2] - @pry = Pry.new prompt: @a - end + let(:prompt1) { Pry::Prompt.new(:test1, '', Array.new(2) { proc { '' } }) } + let(:prompt2) { Pry::Prompt.new(:test2, '', Array.new(2) { proc { '' } }) } + let(:prompt3) { Pry::Prompt.new(:test3, '', Array.new(2) { proc { '' } }) } + + let(:pry) { Pry.new(prompt: prompt1) } + it 'should have a prompt stack' do - @pry.push_prompt @b - @pry.push_prompt @c - expect(@pry.prompt).to eq @c - @pry.pop_prompt - expect(@pry.prompt).to eq @b - @pry.pop_prompt - expect(@pry.prompt).to eq @a + pry.push_prompt(prompt2) + pry.push_prompt(prompt3) + expect(pry.prompt).to eq(prompt3) + pry.pop_prompt + expect(pry.prompt).to match(prompt2) + pry.pop_prompt + expect(pry.prompt).to eq(prompt1) end - it 'should restore overridden prompts when returning from file-mode' do - pry = Pry.new(prompt: [ proc { 'P>' } ] * 2) - expect(pry.select_prompt).to eq "P>" + it 'should restore overridden prompts when returning from shell-mode' do + pry = Pry.new( + prompt: Pry::Prompt.new(:test, '', Array.new(2) { proc { 'P>' } }) + ) + expect(pry.select_prompt).to eq('P>') pry.process_command('shell-mode') expect(pry.select_prompt).to match(/\Apry .* \$ \z/) pry.process_command('shell-mode') - expect(pry.select_prompt).to eq "P>" + expect(pry.select_prompt).to eq('P>') end it '#pop_prompt should return the popped prompt' do - @pry.push_prompt @b - @pry.push_prompt @c - expect(@pry.pop_prompt).to eq @c - expect(@pry.pop_prompt).to eq @b + pry.push_prompt(prompt2) + pry.push_prompt(prompt3) + expect(pry.pop_prompt).to eq(prompt3) + expect(pry.pop_prompt).to eq(prompt2) end it 'should not pop the last prompt' do - @pry.push_prompt @b - expect(@pry.pop_prompt).to eq @b - expect(@pry.pop_prompt).to eq @a - expect(@pry.pop_prompt).to eq @a - expect(@pry.prompt).to eq @a + pry.push_prompt(prompt2) + expect(pry.pop_prompt).to eq(prompt2) + expect(pry.pop_prompt).to eq(prompt1) + expect(pry.pop_prompt).to eq(prompt1) + expect(pry.prompt).to eq(prompt1) end describe '#prompt= should replace the current prompt with the new prompt' do it 'when only one prompt on the stack' do - @pry.prompt = @b - expect(@pry.prompt).to eq @b - expect(@pry.pop_prompt).to eq @b - expect(@pry.pop_prompt).to eq @b + pry.prompt = prompt2 + expect(pry.prompt).to eq(prompt2) + expect(pry.pop_prompt).to eq(prompt2) + expect(pry.pop_prompt).to eq(prompt2) end + it 'when several prompts on the stack' do - @pry.push_prompt @b - @pry.prompt = @c - expect(@pry.pop_prompt).to eq @c - expect(@pry.pop_prompt).to eq @a + pry.push_prompt(prompt2) + pry.prompt = prompt3 + expect(pry.pop_prompt).to eq(prompt3) + expect(pry.pop_prompt).to eq(prompt1) end end end