diff options
author | Kyrylo Silin <silin@kyrylo.org> | 2018-11-03 21:58:16 +0800 |
---|---|---|
committer | Kyrylo Silin <silin@kyrylo.org> | 2018-11-03 22:08:52 +0800 |
commit | 82d683b146094dec1ac4dc09f56208df2e1dde7e (patch) | |
tree | 0927e5b3267667d19ee95b262aed68e489f4a154 | |
parent | 98c5e95064524a603dbaef03c94c97a6b665b9c2 (diff) | |
download | pry-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.rb | 8 | ||||
-rw-r--r-- | lib/pry/commands/list_prompts.rb | 7 | ||||
-rw-r--r-- | lib/pry/commands/shell_mode.rb | 10 | ||||
-rw-r--r-- | lib/pry/config/default.rb | 2 | ||||
-rw-r--r-- | lib/pry/prompt.rb | 196 | ||||
-rw-r--r-- | spec/prompt_spec.rb | 37 |
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 |