summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKyrylo Silin <silin@kyrylo.org>2018-11-03 21:58:16 +0800
committerKyrylo Silin <silin@kyrylo.org>2018-11-03 22:08:52 +0800
commit82d683b146094dec1ac4dc09f56208df2e1dde7e (patch)
tree0927e5b3267667d19ee95b262aed68e489f4a154
parent98c5e95064524a603dbaef03c94c97a6b665b9c2 (diff)
downloadpry-prompt-api.tar.gz
prompt: add basic API for adding promptsprompt-api
Fixes #1836 (Add an API for adding new prompts)
-rw-r--r--lib/pry/commands/change_prompt.rb8
-rw-r--r--lib/pry/commands/list_prompts.rb7
-rw-r--r--lib/pry/commands/shell_mode.rb10
-rw-r--r--lib/pry/config/default.rb2
-rw-r--r--lib/pry/prompt.rb196
-rw-r--r--spec/prompt_spec.rb37
6 files changed, 154 insertions, 106 deletions
diff --git a/lib/pry/commands/change_prompt.rb b/lib/pry/commands/change_prompt.rb
index 13d36cbb..f20b741a 100644
--- a/lib/pry/commands/change_prompt.rb
+++ b/lib/pry/commands/change_prompt.rb
@@ -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
diff --git a/lib/pry/commands/list_prompts.rb b/lib/pry/commands/list_prompts.rb
index c257de7f..9fe2cad3 100644
--- a/lib/pry/commands/list_prompts.rb
+++ b/lib/pry/commands/list_prompts.rb
@@ -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) "
diff --git a/lib/pry/commands/shell_mode.rb b/lib/pry/commands/shell_mode.rb
index 0e5fd0bd..429da7ad 100644
--- a/lib/pry/commands/shell_mode.rb
+++ b/lib/pry/commands/shell_mode.rb
@@ -9,13 +9,11 @@ class Pry
BANNER
def process
- case _pry_.prompt
- when Pry::Prompt::SHELL
- _pry_.pop_prompt
- _pry_.custom_completions = _pry_.config.file_completions
+ if state.disabled ^= true
+ 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
diff --git a/lib/pry/config/default.rb b/lib/pry/config/default.rb
index 0821f576..fa351f7c 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
+ Pry::Prompt[:default][:value]
},
prompt_safe_contexts: proc {
Pry::Prompt::SAFE_CONTEXTS
diff --git a/lib/pry/prompt.rb b/lib/pry/prompt.rb
index 072214b0..0479f0d1 100644
--- a/lib/pry/prompt.rb
+++ b/lib/pry/prompt.rb
@@ -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
- 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
+ # 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 [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
+ # @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
- # @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
- )
+ # 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
+
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
-
- # Simple prompt doesn't display target or nesting level.
- # @return [Array<Proc>]
- SIMPLE = [proc { '>> ' }, proc { ' | ' }].freeze
-
- # @return [Array<Proc>]
- NO_PROMPT = Array.new(2) { proc { '' } }.freeze
-
- # @return [Array<Proc>]
- SHELL = [shell('$'), shell('*')].freeze
-
- # 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
+ 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
- # @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
- },
+ add(:simple, "A simple '>>'.", ['>> ', ' | ']) do |_, _, _, sep|
+ sep
+ end
- "simple" => {
- value: SIMPLE,
- description: "A simple '>>'.".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
- "nav" => {
- value: NAV,
- description: "A prompt that displays the binding stack as a path and\n" \
- "includes information about _in_ and _out_.".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
- "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
diff --git a/spec/prompt_spec.rb b/spec/prompt_spec.rb
index d376e5ec..f026c667 100644
--- a/spec/prompt_spec.rb
+++ b/spec/prompt_spec.rb
@@ -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