mirror of
https://github.com/pry/pry.git
synced 2022-11-09 12:35:05 -05:00
prompt: add basic API for adding prompts
Fixes #1836 (Add an API for adding new prompts)
This commit is contained in:
parent
8588b36f62
commit
3da930f908
9 changed files with 170 additions and 124 deletions
|
@ -185,11 +185,11 @@ Pry::CLI.add_options do
|
|||
end
|
||||
|
||||
on "simple-prompt", "Enable simple prompt mode" do
|
||||
Pry.config.prompt = Pry::Prompt::SIMPLE
|
||||
Pry.config.prompt = Pry::Prompt[:simple][:value]
|
||||
end
|
||||
|
||||
on "noprompt", "No prompt mode" do
|
||||
Pry.config.prompt = Pry::Prompt::NO_PROMPT
|
||||
Pry.config.prompt = Pry::Prompt[:none][:value]
|
||||
end
|
||||
|
||||
on :r, :require=, "`require` a Ruby script at startup" do |file|
|
||||
|
|
|
@ -11,16 +11,12 @@ class Pry::Command::ChangePrompt < Pry::ClassCommand
|
|||
BANNER
|
||||
|
||||
def process(prompt)
|
||||
if prompt_map.key?(prompt)
|
||||
_pry_.prompt = prompt_map[prompt][:value]
|
||||
if Pry::Prompt.all.key?(prompt)
|
||||
_pry_.prompt = Pry::Prompt.all[prompt][:value]
|
||||
else
|
||||
raise Pry::CommandError, "'#{prompt}' isn't a known prompt!"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def prompt_map
|
||||
Pry::Prompt::MAP
|
||||
end
|
||||
Pry::Commands.add_command(self)
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ class Pry::Command::ListPrompts < Pry::ClassCommand
|
|||
|
||||
def process
|
||||
output.puts heading("Available prompts") + "\n"
|
||||
prompt_map.each do |name, prompt|
|
||||
Pry::Prompt.all.each do |name, prompt|
|
||||
output.write "Name: #{bold(name)}"
|
||||
output.puts selected_prompt?(prompt) ? selected_text : ""
|
||||
output.puts prompt[:description]
|
||||
|
@ -19,10 +19,7 @@ class Pry::Command::ListPrompts < Pry::ClassCommand
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
def prompt_map
|
||||
Pry::Prompt::MAP
|
||||
end
|
||||
private
|
||||
|
||||
def selected_text
|
||||
red " (selected) "
|
||||
|
|
|
@ -9,13 +9,13 @@ class Pry
|
|||
BANNER
|
||||
|
||||
def process
|
||||
case _pry_.prompt
|
||||
when Pry::Prompt::SHELL
|
||||
_pry_.pop_prompt
|
||||
_pry_.custom_completions = _pry_.config.file_completions
|
||||
state.disabled ^= true
|
||||
|
||||
if state.disabled
|
||||
state.prev_prompt = _pry_.prompt
|
||||
_pry_.prompt = Pry::Prompt[:shell][:value]
|
||||
else
|
||||
_pry_.push_prompt Pry::Prompt::SHELL
|
||||
_pry_.custom_completions = _pry_.config.command_completions
|
||||
_pry_.prompt = state.prev_prompt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,11 +9,13 @@ class Pry
|
|||
BANNER
|
||||
|
||||
def process
|
||||
case _pry_.prompt
|
||||
when Pry::Prompt::SIMPLE
|
||||
_pry_.pop_prompt
|
||||
state.disabled ^= true
|
||||
|
||||
if state.disabled
|
||||
state.prev_prompt = _pry_.prompt
|
||||
_pry_.prompt = Pry::Prompt[:simple][:value]
|
||||
else
|
||||
_pry_.push_prompt Pry::Prompt::SIMPLE
|
||||
_pry_.prompt = state.prev_prompt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,7 +18,7 @@ class Pry
|
|||
Pry::Prompt::DEFAULT_NAME
|
||||
},
|
||||
prompt: proc {
|
||||
Pry::Prompt::DEFAULT
|
||||
Pry::Prompt[:default][:value]
|
||||
},
|
||||
prompt_safe_contexts: proc {
|
||||
Pry::Prompt::SAFE_CONTEXTS
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
class Pry
|
||||
# Prompt represents the Pry prompt and holds necessary procs and constants to
|
||||
# be used with Readline-like libraries.
|
||||
# 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.
|
||||
#
|
||||
# @example
|
||||
# Pry::Prompt.add(
|
||||
# :ipython,
|
||||
# 'IPython-like prompt', [':', '...:']
|
||||
# ) do |_context, _nesting, _pry_, sep|
|
||||
# sep == ':' ? "In [#{_pry_.input_ring.count}]: " : ' ...: '
|
||||
# end
|
||||
#
|
||||
# # Produces:
|
||||
# # In [3]: def foo
|
||||
# # ...: puts 'foo'
|
||||
# # ...: end
|
||||
# # => :foo
|
||||
# # In [4]:
|
||||
# @since v0.11.0
|
||||
# @api private
|
||||
# @api public
|
||||
module Prompt
|
||||
# @return [String]
|
||||
DEFAULT_NAME = 'pry'.freeze
|
||||
|
@ -12,60 +27,62 @@ class Pry
|
|||
# 1-line #inspect output suitable for prompt
|
||||
SAFE_CONTEXTS = [String, Numeric, Symbol, nil, true, false].freeze
|
||||
|
||||
# @return [String]
|
||||
DEFAULT_TEMPLATE =
|
||||
"[%<in_count>s] %<name>s(%<context>s)%<nesting>s%<separator>s ".freeze
|
||||
|
||||
# @return [String]
|
||||
SHELL_TEMPLATE = "%<name>s %<context>s:%<pwd>s %<separator>s ".freeze
|
||||
|
||||
# @return [String]
|
||||
NAV_TEMPLATE = "[%<in_count>s] (%<name>s) %<tree>s: %<stack_size>s> ".freeze
|
||||
# A Hash that holds all prompts. The keys of the Hash are prompt
|
||||
# names, the values are Hash instances of the format {:description, :value}.
|
||||
@prompts = {}
|
||||
|
||||
class << self
|
||||
# Retrieves a prompt.
|
||||
#
|
||||
# @example
|
||||
# Prompt[:my_prompt][:value]
|
||||
#
|
||||
# @param [Symbol] prompt_name The name of the prompt you want to access
|
||||
# @return [Hash{Symbol=>Object}]
|
||||
# @since v0.12.0
|
||||
def [](prompt_name)
|
||||
all[prompt_name.to_s]
|
||||
end
|
||||
|
||||
# @return [Hash{Symbol=>Hash}] the duplicate of the internal prompts hash
|
||||
# @note Use this for read-only operations
|
||||
# @since v0.12.0
|
||||
def all
|
||||
@prompts.dup
|
||||
end
|
||||
|
||||
# Adds a new prompt to the prompt hash.
|
||||
#
|
||||
# @param [Symbol] prompt_name
|
||||
# @param [String] description
|
||||
# @param [Array<String>] separators The separators to differentiate
|
||||
# between prompt modes (default mode and class/method definition mode).
|
||||
# The Array *must* have a size of 2.
|
||||
# @yield [context, nesting, _pry_, sep]
|
||||
# @yieldparam context [Object] the context where Pry is currently in
|
||||
# @yieldparam nesting [Integer] whether the context is nested
|
||||
# @yieldparam _pry_ [Pry] the Pry instance
|
||||
# @yieldparam separator [String] separator string
|
||||
# @return [nil]
|
||||
# @raise [ArgumentError] if the size of `separators` is not 2
|
||||
# @since v0.12.0
|
||||
def add(prompt_name, description = '', separators = %w[> *])
|
||||
unless separators.size == 2
|
||||
raise ArgumentError, "separators size must be 2, given #{separators.size}"
|
||||
end
|
||||
|
||||
@prompts[prompt_name.to_s] = {
|
||||
description: description,
|
||||
value: separators.map do |sep|
|
||||
proc { |context, nesting, _pry_| yield(context, nesting, _pry_, sep) }
|
||||
end
|
||||
}
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# @return [Proc] the default prompt
|
||||
def default(separator)
|
||||
proc do |context, nesting, _pry_|
|
||||
format(
|
||||
DEFAULT_TEMPLATE,
|
||||
in_count: _pry_.input_ring.count,
|
||||
name: prompt_name(_pry_.config.prompt_name),
|
||||
context: Pry.view_clip(context),
|
||||
nesting: (nesting > 0 ? ":#{nesting}" : ''),
|
||||
separator: separator
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Proc] the shell prompt
|
||||
def shell(separator)
|
||||
proc do |context, _nesting, _pry_|
|
||||
format(
|
||||
SHELL_TEMPLATE,
|
||||
name: prompt_name(_pry_.config.prompt_name),
|
||||
context: Pry.view_clip(context),
|
||||
pwd: Dir.pwd,
|
||||
separator: separator
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Proc] the nav prompt
|
||||
def nav
|
||||
proc do |_context, _nesting, _pry_|
|
||||
tree = _pry_.binding_stack.map { |b| Pry.view_clip(b.eval('self')) }
|
||||
format(
|
||||
NAV_TEMPLATE,
|
||||
in_count: _pry_.input_ring.count,
|
||||
name: prompt_name(_pry_.config.prompt_name),
|
||||
tree: tree.join(' / '),
|
||||
stack_size: _pry_.binding_stack.size - 1
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def prompt_name(name)
|
||||
return name unless name.is_a?(Pry::Config::Lazy)
|
||||
|
||||
|
@ -73,48 +90,53 @@ class Pry
|
|||
end
|
||||
end
|
||||
|
||||
# The default Pry prompt, which includes the context and nesting level.
|
||||
# @return [Array<Proc>]
|
||||
DEFAULT = [default('>'), default('*')].freeze
|
||||
add(:default, <<DESC) do |context, nesting, _pry_, sep|
|
||||
The default Pry prompt. Includes information about the
|
||||
current expression number, evaluation context, and nesting
|
||||
level, plus a reminder that you're using Pry.
|
||||
DESC
|
||||
format(
|
||||
"[%<in_count>s] %<name>s(%<context>s)%<nesting>s%<separator>s ",
|
||||
in_count: _pry_.input_ring.count,
|
||||
name: prompt_name(_pry_.config.prompt_name),
|
||||
context: Pry.view_clip(context),
|
||||
nesting: (nesting > 0 ? ":#{nesting}" : ''),
|
||||
separator: sep
|
||||
)
|
||||
end
|
||||
|
||||
# Simple prompt doesn't display target or nesting level.
|
||||
# @return [Array<Proc>]
|
||||
SIMPLE = [proc { '>> ' }, proc { ' | ' }].freeze
|
||||
add(:simple, "A simple '>>'.", ['>> ', ' | ']) do |_, _, _, sep|
|
||||
sep
|
||||
end
|
||||
|
||||
# @return [Array<Proc>]
|
||||
NO_PROMPT = Array.new(2) { proc { '' } }.freeze
|
||||
add(:nav, <<DESC, %w[> *]) do |context, nesting, _pry_, sep|
|
||||
A prompt that displays the binding stack as a path and
|
||||
includes information about _in_ and _out_.
|
||||
DESC
|
||||
tree = _pry_.binding_stack.map { |b| Pry.view_clip(b.eval('self')) }
|
||||
format(
|
||||
"[%<in_count>s] (%<name>s) %<tree>s: %<stack_size>s%<separator>s ",
|
||||
in_count: _pry_.input_ring.count,
|
||||
name: prompt_name(_pry_.config.prompt_name),
|
||||
tree: tree.join(' / '),
|
||||
stack_size: _pry_.binding_stack.size - 1,
|
||||
separator: sep
|
||||
)
|
||||
end
|
||||
|
||||
# @return [Array<Proc>]
|
||||
SHELL = [shell('$'), shell('*')].freeze
|
||||
add(:shell, <<DESC, %w[$ *]) do |context, nesting, _pry_, sep|
|
||||
A prompt that displays the binding stack as a path and
|
||||
includes information about _in_ and _out_.
|
||||
DESC
|
||||
format(
|
||||
"%<name>s %<context>s:%<pwd>s %<separator>s ",
|
||||
name: prompt_name(_pry_.config.prompt_name),
|
||||
context: Pry.view_clip(context),
|
||||
pwd: Dir.pwd,
|
||||
separator: sep
|
||||
)
|
||||
end
|
||||
|
||||
# A prompt that includes the full object path as well as
|
||||
# input/output (_in_ and _out_) information. Good for navigation.
|
||||
NAV = Array.new(2) { nav }.freeze
|
||||
|
||||
# @return [Hash{String=>Hash}]
|
||||
MAP = {
|
||||
"default" => {
|
||||
value: DEFAULT,
|
||||
description: "The default Pry prompt. Includes information about the\n" \
|
||||
"current expression number, evaluation context, and nesting\n" \
|
||||
"level, plus a reminder that you're using Pry.".freeze
|
||||
},
|
||||
|
||||
"simple" => {
|
||||
value: SIMPLE,
|
||||
description: "A simple '>>'.".freeze
|
||||
},
|
||||
|
||||
"nav" => {
|
||||
value: NAV,
|
||||
description: "A prompt that displays the binding stack as a path and\n" \
|
||||
"includes information about _in_ and _out_.".freeze
|
||||
},
|
||||
|
||||
"none" => {
|
||||
value: NO_PROMPT,
|
||||
description: "Wave goodbye to the Pry prompt.".freeze
|
||||
}
|
||||
}.freeze
|
||||
add(:none, 'Wave goodbye to the Pry prompt.', Array.new(2)) { '' }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -117,14 +117,8 @@ class Pry
|
|||
@output_ring
|
||||
end
|
||||
|
||||
# The current prompt.
|
||||
# This is the prompt at the top of the prompt stack.
|
||||
#
|
||||
# @example
|
||||
# self.prompt = Pry::Prompt::SIMPLE
|
||||
# self.prompt # => Pry::Prompt::SIMPLE
|
||||
#
|
||||
# @return [Array<Proc>] Current prompt.
|
||||
# @return [Array<Proc>] the current prompt
|
||||
def prompt
|
||||
prompt_stack.last
|
||||
end
|
||||
|
|
|
@ -1,6 +1,41 @@
|
|||
require_relative 'helper'
|
||||
|
||||
describe Pry::Prompt do
|
||||
describe ".[]" do
|
||||
it "accesses prompts" do
|
||||
expect(subject[:default]).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe ".all" do
|
||||
it "returns a hash with prompts" do
|
||||
expect(subject.all).to be_a(Hash)
|
||||
end
|
||||
|
||||
it "returns a duplicate of original prompts" do
|
||||
subject.all[:foobar] = Object.new
|
||||
expect(subject[:foobar]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe ".add" do
|
||||
after { described_class.instance_variable_get(:@prompts).delete(:my_prompt) }
|
||||
|
||||
it "adds a new prompt" do
|
||||
subject.add(:my_prompt)
|
||||
expect(subject[:my_prompt]).to be_a(Hash)
|
||||
end
|
||||
|
||||
it "raises error when separators.size != 2" do
|
||||
expect { subject.add(:my_prompt, '', [1, 2, 3]) }
|
||||
.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "returns nil" do
|
||||
expect(subject.add(:my_prompt)).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "one-parameter prompt proc" do
|
||||
it 'should get full config object' do
|
||||
config = nil
|
||||
|
@ -80,7 +115,7 @@ describe Pry::Prompt do
|
|||
end
|
||||
config._pry_.config.prompt_name = Pry.lazy { enum.next }
|
||||
|
||||
proc = subject::DEFAULT.first
|
||||
proc = subject[: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
|
||||
|
|
Loading…
Add table
Reference in a new issue