diff options
| author | Robert <r-obert@users.noreply.github.com> | 2017-09-01 01:23:17 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-09-01 01:23:17 +0100 |
| commit | 17e21dcd6a40b783cae7e2fdbe0b68389d5d9df7 (patch) | |
| tree | 66c0f5fb8c26c63753c73c70e11f6a94b376ce54 | |
| parent | 9bc74de9a7434f6361627c8975ac3b53161eb167 (diff) | |
| parent | 3cc7f056df3ceae9fbf705f29d48c2f89591eaad (diff) | |
| download | pry-include-text-module.tar.gz | |
Merge branch 'master' into include-text-moduleinclude-text-module
| -rw-r--r-- | .travis.yml | 4 | ||||
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | Gemfile | 2 | ||||
| -rw-r--r-- | lib/pry/commands/alias_prompt.rb | 33 | ||||
| -rw-r--r-- | lib/pry/commands/change_prompt.rb | 23 | ||||
| -rw-r--r-- | lib/pry/commands/list_prompts.rb | 28 | ||||
| -rw-r--r-- | lib/pry/config/default.rb | 2 | ||||
| -rw-r--r-- | lib/pry/prompt.rb | 208 | ||||
| -rw-r--r-- | lib/pry/pry_instance.rb | 8 | ||||
| -rw-r--r-- | lib/pry/wrapped_module/candidate.rb | 10 | ||||
| -rw-r--r-- | spec/prompt_spec.rb | 84 | ||||
| -rw-r--r-- | spec/wrapped_module_spec.rb | 13 |
12 files changed, 369 insertions, 54 deletions
diff --git a/.travis.yml b/.travis.yml index aaba3066..67ff931b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,10 @@ rvm: - - 1.9.2 - 1.9.3 - 2.0.0 - 2.1 - 2.2 - 2.3.1 - - 2.4 + - 2.4.1 - ruby-head - rbx-2 - jruby @@ -21,6 +20,7 @@ matrix: - rvm: ruby-head - rvm: jruby-head - rvm: rbx-2 + - rvm: 2.4.1 # because of https://bugs.ruby-lang.org/issues/13537 notifications: irc: "irc.freenode.org#pry" diff --git a/CHANGELOG.md b/CHANGELOG.md index 427c3fd0..fd8ac564 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,16 @@ ### HEAD * Include Pry::Helpers::Text into Pry::Command, and deprecate `Pry::Command#text`. ([#1629](https://github.com/pry/pry/pull/1629)) +* Add 'alias-prompt' command. + +* Add `Pry::Prompt.[]`, `Pry::Prompt.add_prompt()`, `Pry::Prompt.alias_prompt()` and + `Pry::Prompt.remove_prompt()`, plus more, for integrating custom prompts with Pry + ([#1628](https://github.com/pry/pry/pull/1628)). See Pry::Prompt for complete details. + * Add text helpers for background colors ([#1624](https://github.com/pry/pry/pull/1624)) * Fix string literal methods completion. ([#1590](https://github.com/pry/pry/pull/1590)) +* Make sure Pry::WrappedModule::Candidate#source_location returns non-nil value when `.name` has + been redefined on a Class/Module ([#1623](https://github.com/pry/pry/pull/1623)) * Add alias 'whereami[?!]+' for 'whereami' command. ([#1597](https://github.com/pry/pry/pull/1597)) * Improve Ruby 2.4 support ([#1611](https://github.com/pry/pry/pull/1611)): * Deprecated constants are hidden from `ls` output by default, use the `-d` switch to see them. @@ -2,11 +2,9 @@ source 'https://rubygems.org' gemspec gem 'rake', '~> 10.0' -# For Guard group :development do gem 'gist' gem 'yard' - gem 'rb-inotify', :require => false gem 'rb-fsevent', :require => false end diff --git a/lib/pry/commands/alias_prompt.rb b/lib/pry/commands/alias_prompt.rb new file mode 100644 index 00000000..a5f7a5ab --- /dev/null +++ b/lib/pry/commands/alias_prompt.rb @@ -0,0 +1,33 @@ +class Pry + class Command::AliasPrompt < Pry::ClassCommand + match "alias-prompt" + group 'Input and Output' + description "Create an alternative alias for a prompt" + banner <<-BANNER + alias-prompt PROMPT_NAME ALIAS_PROMPT + + Create an alternative alias for a prompt that can be seen from list-prompts and + used by the change-prompt commands. + BANNER + + command_options argument_required: true + + def process(prompt_name, alias_name) + if not args_ok?([prompt_name, alias_name]) + return output.puts help + end + if Pry::Prompt[prompt_name] + Pry::Prompt.alias_prompt prompt_name, alias_name + output.puts "Alias '#{alias_name}' created" + else + raise Pry::CommandError, "prompt #{prompt_name} cannot be aliased because it doesn't exist." + end + end + + private + def args_ok?(args) + args.size == 2 and args.all?{|s| String === s} + end + Pry::Commands.add_command(self) + end +end diff --git a/lib/pry/commands/change_prompt.rb b/lib/pry/commands/change_prompt.rb index 13d36cbb..859825a2 100644 --- a/lib/pry/commands/change_prompt.rb +++ b/lib/pry/commands/change_prompt.rb @@ -11,16 +11,29 @@ class Pry::Command::ChangePrompt < Pry::ClassCommand BANNER def process(prompt) - if prompt_map.key?(prompt) - _pry_.prompt = prompt_map[prompt][:value] + prompts = Pry::Prompt.all_by_name(prompt) + if prompts.size == 1 + _pry_.prompt = prompts[0] + elsif prompts.size > 1 + multiple_choice(prompts) else raise Pry::CommandError, "'#{prompt}' isn't a known prompt!" end end -private - def prompt_map - Pry::Prompt::MAP + private + def multiple_choice(prompts) + _pry_.pager.page "Multiple aliases found, please choose:\n" + prompts.each.with_index(1) do |prompt, i| + _pry_.pager.page "#{i}) #{prompt.name} => #{prompt.alias_for}" + end + output.write "Choice: " + reply = (_pry_.input.respond_to?(:gets) ? _pry_.input.gets : _pry_.input.readline).strip + if reply =~ /^[1-9][0-9]*$/ and reply.to_i <= prompts.size + _pry_.prompt = prompts[reply.to_i-1] + else + multiple_choice(prompts) + end end Pry::Commands.add_command(self) end diff --git a/lib/pry/commands/list_prompts.rb b/lib/pry/commands/list_prompts.rb index 436f719b..b8c2b29b 100644 --- a/lib/pry/commands/list_prompts.rb +++ b/lib/pry/commands/list_prompts.rb @@ -10,26 +10,28 @@ class Pry::Command::ListPrompts < Pry::ClassCommand BANNER def process - output.puts heading("Available prompts") + "\n" - prompt_map.each do |name, prompt| - output.write "Name: #{text.bold(name)}" - output.puts selected_prompt?(prompt) ? selected_text : "" - output.puts prompt[:description] + output.puts heading("Available prompts") + "\n\n" + all_prompts.each do |prompt| + next if prompt.alias? + aliases = Pry::Prompt.aliases_for(prompt.name) + output.write "Name: #{text.bold(prompt.name)}" + output.puts selected_prompt?([prompt].concat(aliases)) ? text.green(" [active]") : "" + output.puts "Aliases: #{aliases.map {|s| text.bold(s.name) }.join(',')}" if aliases.any? + output.puts prompt.description output.puts end end -private - def prompt_map - Pry::Prompt::MAP - end + private - def selected_text - text.red " (selected) " + def all_prompts + Pry::Prompt.all_prompts end - def selected_prompt?(prompt) - _pry_.prompt == prompt[:value] + def selected_prompt?(prompts) + prompts.any? do |prompt| + _pry_.prompt == prompt or _pry_.prompt == prompt.proc_array + end end Pry::Commands.add_command(self) end diff --git a/lib/pry/config/default.rb b/lib/pry/config/default.rb index e4ae3fb6..1a653520 100644 --- a/lib/pry/config/default.rb +++ b/lib/pry/config/default.rb @@ -16,7 +16,7 @@ class Pry::Config::Default Pry::DEFAULT_PROMPT_NAME }, prompt: proc { - Pry::DEFAULT_PROMPT + Pry::Prompt['default'] }, prompt_safe_objects: proc { Pry::DEFAULT_PROMPT_SAFE_OBJECTS diff --git a/lib/pry/prompt.rb b/lib/pry/prompt.rb index 62f0bc1f..ea71b346 100644 --- a/lib/pry/prompt.rb +++ b/lib/pry/prompt.rb @@ -1,26 +1,184 @@ -class Pry::Prompt - MAP = { - "default" => { - value: Pry::DEFAULT_PROMPT, - 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." - }, - - "simple" => { - value: Pry::SIMPLE_PROMPT, - description: "A simple '>>'." - }, - - "nav" => { - value: Pry::NAV_PROMPT, - description: "A prompt that displays the binding stack as a path and\n" \ - "includes information about _in_ and _out_." - }, - - "none" => { - value: Pry::NO_PROMPT, - description: "Wave goodbye to the Pry prompt." - } - } +require 'set' +module Pry::Prompt + extend self + PROMPT_MAP = {} + private_constant :PROMPT_MAP + AliasError = Class.new(RuntimeError) + PromptInfo = Struct.new(:name, :description, :proc_array, :alias_for) do + # + # @return [Boolean] + # Returns true if the prompt is an alias of another prompt. + # + def alias? + alias_for != nil + end + + def <=>(other) + name == other.alias_for ? 1 : 0 + end + + def to_a + proc_array + end + + def eql?(other) + return false if not Pry::Prompt::PromptInfo === other + # Aliases are eql? + [:proc_array].all? {|m| public_send(m) == other.public_send(m) } + end + end + + # + # @return [Array<PromptInfo>] + # Returns an Array of {PromptInfo} objects. + # + def all_prompts + PROMPT_MAP.values.map{|s| s.to_a}.flatten + end + + # + # @param [String] prompt + # The name of a prompt. + # + # @return [Array<PromptInfo>] + # Returns an array of aliases for _prompt_, as {PromptInfo} objects. + # + def aliases_for(prompt) + all_prompts.select{|prompt_info| prompt_info.alias_for == prompt.to_s} + end + + # + # @return [Array<PromptInfo>] + # Returns an array of all prompt aliases, as {PromptInfo} objects. + # + def aliased_prompts + all_prompts.select(&:alias?) + end + + # + # @param [Array<Proc,Proc>] proc_array + # An array in the form of [proc{}, proc{}] + # + # @return [PromptInfo] + # Returns the first {PromptInfo} object who holds value eql? to `proc_array`. + # + def first_matching_proc_array(proc_array) + all_prompts.find do |prompt| + prompt.proc_array == proc_array and not prompt.alias? + end + end + + # + # Integrate a custom prompt with Pry. + # The prompt will be visible in the output of the "list-prompts" command, and + # it can be used with the "change-prompt"command. + # + # @param [String] name + # The name of the prompt. + # + # @param [String] description + # A short description of the prompt. + # + # @param [Array<Proc,Proc>] value + # A prompt in the form of [proc {}, proc {}]. + # + def add_prompt(name, description, value) + PROMPT_MAP[name.to_s] = SortedSet.new [PromptInfo.new(name.to_s, description.to_s, value, nil)] + end + + # + # @example + # + # # .pryrc + # Pry.configure do |config| + # config.prompt = Pry::Prompt['simple'].proc_array + # end + # + # @return [PromptInfo] + # Returns a prompt in the form of a PromptInfo object. + # + def [](name) + all_prompts.find {|prompt| prompt.name == name.to_s } + end + + # + # @param [String] name + # The name of a prompt. + # + # @return [Array<PromptInfo>] + # An array of {PromptInfo} objects. + # + def all_by_name(name) + name = name.to_s + all_prompts.select{|prompt| prompt.name == name} + end + + # + # Remove a prompt from Pry. + # It will no longer be visible in the output of "list-prompts" or usable with the + # "change-prompt" command. + # + # @note + # Aliases are also removed. + # + # @param [String] name + # The name of a prompt. + # + # @return [Object, nil] + # Returns truthy if a prompt was deleted, otherwise nil. + # + def remove_prompt(name) + prompt = self[name.to_s] + PROMPT_MAP.delete name.to_s if prompt + end + + # + # Provide alternative name for a prompt, which can be used from the list-prompts + # and change-prompt commands. + # + # @param [String] prompt_name + # The name of the prompt to alias. + # + # @param [String] aliased_prompt + # The name of the aliased prompt. + # + def alias_prompt(prompt_name, aliased_prompt) + prompt_name = prompt_name.to_s + prompt = self[prompt_name] + if not prompt + raise AliasError, "prompt '#{prompt}' cannot be aliased because it doesn't exist" + elsif prompt.alias? + prompt_name = prompt.alias_for + end + PROMPT_MAP[prompt_name].add PromptInfo.new *[aliased_prompt.to_s, prompt.description, + prompt.proc_array, prompt_name] + end + + # + # @param [String] alias_name + # Name of a prompt alias. + # + # @return [Integer] + # Returns the number of removed aliases. + # + def remove_alias(alias_name) + alias_name = alias_name.to_s + all_prompts.count do |prompt| + PROMPT_MAP[prompt.alias_for].delete(prompt) if prompt.alias? and prompt.name == alias_name + end + end + + add_prompt "default", + "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.", + Pry::DEFAULT_PROMPT + + add_prompt "nav", + "A prompt that displays the binding stack as a path and\n" \ + "includes information about _in_ and _out_.", + Pry::NAV_PROMPT + + add_prompt "simple", "A simple '>>'.", Pry::SIMPLE_PROMPT + add_prompt "none", "Wave goodbye to the Pry prompt.", Pry::NO_PROMPT end diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 0ee7b790..4ffe4c58 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -88,14 +88,16 @@ class Pry # self.prompt = Pry::SIMPLE_PROMPT # self.prompt # => Pry::SIMPLE_PROMPT # - # @return [Array<Proc>] Current prompt. + # @return [Pry::Prompt::PromptInfo, Array<Proc, Proc>] Current prompt. def prompt - prompt_stack.last + proc_array = prompt_stack.last + Pry::Prompt.first_matching_proc_array(proc_array) or proc_array end def prompt=(new_prompt) + new_prompt = new_prompt.to_a if prompt_stack.empty? - push_prompt new_prompt + push_prompt(new_prompt) else prompt_stack[-1] = new_prompt end diff --git a/lib/pry/wrapped_module/candidate.rb b/lib/pry/wrapped_module/candidate.rb index 67ef1925..3481b2d0 100644 --- a/lib/pry/wrapped_module/candidate.rb +++ b/lib/pry/wrapped_module/candidate.rb @@ -97,9 +97,13 @@ class Pry def class_regexes mod_type_string = wrapped.class.to_s.downcase - [/^\s*#{mod_type_string}\s+(?:(?:\w*)::)*?#{wrapped.name.split(/::/).last}/, - /^\s*(::)?#{wrapped.name.split(/::/).last}\s*?=\s*?#{wrapped.class}/, - /^\s*(::)?#{wrapped.name.split(/::/).last}\.(class|instance)_eval/] + + # workaround for things that redefine Module.name - yes, they're out there. + wrapped_name = Pry::Helpers::BaseHelpers.safe_send wrapped, :name + + [/^\s*#{mod_type_string}\s+(?:(?:\w*)::)*?#{wrapped_name.split(/::/).last}/, + /^\s*(::)?#{wrapped_name.split(/::/).last}\s*?=\s*?#{wrapped.class}/, + /^\s*(::)?#{wrapped_name.split(/::/).last}\.(class|instance)_eval/] end # This method is used by `Candidate#source_location` as a diff --git a/spec/prompt_spec.rb b/spec/prompt_spec.rb index fdd087f0..55692703 100644 --- a/spec/prompt_spec.rb +++ b/spec/prompt_spec.rb @@ -1,5 +1,89 @@ require_relative 'helper' +RSpec.describe Pry::Prompt do + let(:prompt_value) do + [proc{},proc{}] + end + + let(:prompt_desc) do + "$my custom prompt description$" + end + + before do + described_class.add_prompt "prompt-name", prompt_desc, prompt_value + end + + after do + described_class.remove_prompt "prompt-name" + end + + describe ".add_prompt" do + specify "adds new Prompt" do + expect(described_class['prompt-name']).to be_instance_of(Pry::Prompt::PromptInfo) + end + + specify "prompt appears in list-prompts" do + expect(pry_eval("list-prompts")).to include("prompt-name") + expect(pry_eval("list-prompts")).to include(prompt_desc) + end + + specify "prompt is changed to via change-prompt" do + expect(pry_eval("change-prompt prompt-name", "_pry_.prompt")).to eq(described_class['prompt-name']) + end + end + + describe ".remove_prompt" do + specify "removes a prompt" do + described_class.remove_prompt 'prompt-name' + expect(described_class['prompt-name']).to eq(nil) + end + + specify "prompt disappears from list-prompts" do + described_class.remove_prompt 'prompt-name' + expect(pry_eval("list-prompts")).to_not include("prompt-name") + end + + specify "does not remove aliases" do + described_class.alias_prompt "prompt-name", "prompt-alias" + described_class.remove_prompt 'prompt-alias' + expect(described_class['prompt-alias']).to_not be_nil + end + end + + describe ".alias_prompt" do + before do + described_class.alias_prompt "prompt-name", "prompt-alias" + end + + specify "creates alias" do + expect(described_class.aliases_for("prompt-name")).to eq([described_class['prompt-alias']]) + end + + specify "alias appears in list-prompts" do + expect(pry_eval("list-prompts")).to include("Aliases: prompt-alias") + end + + specify "alias is changed to via change-prompt" do + expect(pry_eval("change-prompt prompt-alias", "_pry_.prompt")).to eq(described_class['prompt-name']) + end + end + + describe ".remove_alias" do + before do + described_class.alias_prompt "prompt-name", "prompt-alias" + end + + specify "returns number of removed aliases" do + expect(described_class.remove_alias("prompt-alias")).to eq(1) + end + + specify "removes alias from Pry::Prompt" do + expect(described_class.remove_alias("prompt-alias")).to eq(1) + expect(described_class['prompt-alias']).to eq(nil) + end + end +end + describe "Prompts" do describe "one-parameter prompt proc" do it 'should get full config object' do diff --git a/spec/wrapped_module_spec.rb b/spec/wrapped_module_spec.rb index b46d9cac..2482fd28 100644 --- a/spec/wrapped_module_spec.rb +++ b/spec/wrapped_module_spec.rb @@ -35,6 +35,19 @@ describe Pry::WrappedModule do end end end + + module ModuleNameOverride + def self.name; 'Not a good idea, but it happens in the wild.' end + end + end + + describe 'overidden Module#name' do + specify '#source_location does not return nil' do + c = Pry::WrappedModule(Host::ModuleNameOverride).candidate(0) + expect(c.wrapped).to eq(Host::ModuleNameOverride) + expect(c.nonblank_name).to eq(Host::ModuleNameOverride.name) + expect(c.source_location).not_to be_nil + end end describe "number_of_candidates" do |
