1
0
Fork 0
mirror of https://github.com/pry/pry.git synced 2022-11-09 12:35:05 -05:00

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.
This commit is contained in:
Kyrylo Silin 2018-11-11 04:20:45 +08:00
parent 62d8ddc616
commit bc26e405ea
10 changed files with 232 additions and 164 deletions

View file

@ -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'

View file

@ -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|

View file

@ -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."

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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<Proc>] the array of procs that hold
# `[wait_proc, incomplete_proc]`
attr_reader :prompt_procs
# @param [String] name
# @param [String] description
# @param [Array<Proc>] 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(
"[%<in_count>s] %<name>s(%<context>s)%<nesting>s%<separator>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)
) { '' }

View file

@ -58,7 +58,7 @@ class Pry
# The object to use for commands.
# @option options [Hash] :hooks
# The defined hook Procs.
# @option options [Array<Proc>] :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<Proc>] 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<Proc>] new_prompt
# @return [Array<Proc>] 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<Proc>] 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

View file

@ -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(#<Object>):1> ')
expect(proc.call(Object.new, 1, pry, '>')).to eq('[1] b(#<Object>):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(#<Object>):1> ')
expect(proc.call(Object.new, 1, config._pry_)).to eq('[1] 102(#<Object>):1> ')
end
end

View file

@ -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-#{name}>" }
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