diff --git a/lib/pry.rb b/lib/pry.rb index 3b4bcfe15..fbbaa2abd 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -24,6 +24,7 @@ require 'pry/exception_handler' require 'pry/system_command_handler' require 'pry/control_d_handler' +require 'pry/command_state' Pry::Commands = Pry::CommandSet.new unless defined?(Pry::Commands) diff --git a/lib/pry/command.rb b/lib/pry/command.rb index 81cded015..fa6d2e519 100644 --- a/lib/pry/command.rb +++ b/lib/pry/command.rb @@ -196,6 +196,10 @@ def group(name = nil) end end end + + def state + Pry::CommandState.default.state_for(match) + end end # Properties of one execution of a command (passed by {Pry#run_command} as a hash of @@ -305,7 +309,7 @@ def target_self # state.my_state = "my state" # this will not conflict with any # # `state.my_state` used in another command. def state - pry_instance.command_state[match] ||= Pry::Config.from_hash({}) + self.class.state end # Revaluate the string (str) and perform interpolation. diff --git a/lib/pry/command_state.rb b/lib/pry/command_state.rb new file mode 100644 index 000000000..263d14d87 --- /dev/null +++ b/lib/pry/command_state.rb @@ -0,0 +1,29 @@ +require 'ostruct' + +class Pry + # CommandState is a data structure to hold per-command state. + # + # Pry commands can store arbitrary state here. This state persists between + # subsequent command invocations. All state saved here is unique to the + # command. + # + # @since ?.?.? + # @api private + class CommandState + def self.default + @default ||= new + end + + def initialize + @command_state = {} + end + + def state_for(command_name) + @command_state[command_name] ||= OpenStruct.new + end + + def reset(command_name) + @command_state[command_name] = OpenStruct.new + end + end +end diff --git a/lib/pry/control_d_handler.rb b/lib/pry/control_d_handler.rb index d02e2110f..1021c767d 100644 --- a/lib/pry/control_d_handler.rb +++ b/lib/pry/control_d_handler.rb @@ -16,8 +16,8 @@ def self.default(eval_string, pry_instance) else # Otherwise, saves current binding stack as old stack and pops last # binding out of binding stack (the old stack still has that binding). - pry_instance.command_state['cd'] ||= Pry::Config.from_hash({}) - pry_instance.command_state['cd'].old_stack = pry_instance.binding_stack.dup + cd_state = Pry::CommandState.default.state_for('cd') + cd_state.old_stack = pry_instance.binding_stack.dup pry_instance.binding_stack.pop end end diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 9269ded7d..e1309ce9c 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -34,7 +34,6 @@ class Pry attr_accessor :last_dir attr_reader :last_exception - attr_reader :command_state attr_reader :exit_value # @since v0.12.0 @@ -72,7 +71,6 @@ class Pry def initialize(options = {}) @binding_stack = [] @indent = Pry::Indent.new - @command_state = {} @eval_string = "" @backtrace = options.delete(:backtrace) || caller target = options.delete(:target) diff --git a/spec/command_spec.rb b/spec/command_spec.rb index f56d92dbb..b01cff079 100644 --- a/spec/command_spec.rb +++ b/spec/command_spec.rb @@ -416,6 +416,12 @@ def self.name end end + describe ".state" do + it "returns a command state" do + expect(described_class.state).to be_an(OpenStruct) + end + end + describe "#run" do let(:command_set) do set = Pry::CommandSet.new @@ -475,12 +481,12 @@ def process; end subject { Class.new(described_class).new(pry_instance: Pry.new) } it "returns a state hash" do - expect(subject.state).to be_a(Pry::Config) + expect(subject.state).to be_an(OpenStruct) end it "remembers the state" do - subject.state[:foo] = :bar - expect(subject.state[:foo]).to eq(:bar) + subject.state.foo = :bar + expect(subject.state.foo).to eq(:bar) end end diff --git a/spec/command_state_spec.rb b/spec/command_state_spec.rb new file mode 100644 index 000000000..10097b2b2 --- /dev/null +++ b/spec/command_state_spec.rb @@ -0,0 +1,47 @@ +RSpec.describe Pry::CommandState do + describe ".default" do + it "returns the default command state" do + expect(described_class.default).to be_a(described_class) + end + + context "when called multiple times" do + it "returns the same command state" do + first_state = described_class.default + second_state = described_class.default + expect(first_state).to eql(second_state) + end + end + end + + describe "#state_for" do + it "returns a state for the matching command" do + subject.state_for('command').foobar = 1 + expect(subject.state_for('command').foobar).to eq(1) + end + + it "returns new state for new command" do + expect(subject.state_for('command')) + .not_to equal(subject.state_for('other-command')) + end + + it "memoizes state for the same command" do + expect(subject.state_for('command')).to equal(subject.state_for('command')) + end + end + + describe "#reset" do + it "resets the command state for the given command" do + subject.state_for('command').foobar = 1 + subject.reset('command') + expect(subject.state_for('command').foobar).to be_nil + end + + it "doesn't reset command state for other commands" do + subject.state_for('command').foobar = 1 + subject.state_for('other-command').foobar = 1 + subject.reset('command') + + expect(subject.state_for('other-command').foobar).to eq(1) + end + end +end diff --git a/spec/commands/cd_spec.rb b/spec/commands/cd_spec.rb index d2120a6e4..38830ace5 100644 --- a/spec/commands/cd_spec.rb +++ b/spec/commands/cd_spec.rb @@ -16,20 +16,16 @@ def binding_stack end def command_state - pry.command_state["cd"] + pry.commands['cd'].state end def old_stack - pry.command_state['cd'].old_stack.dup + pry.commands['cd'].state.old_stack.dup end end end - describe 'state' do - it 'should not to be set up in fresh instance' do - expect(@t.command_state).to equal nil - end - end + after { Pry::CommandState.default.reset('cd') } describe 'old stack toggling with `cd -`' do describe 'in fresh pry instance' do diff --git a/spec/commands/shell_command_spec.rb b/spec/commands/shell_command_spec.rb index 4417f0ee0..62526b3c2 100644 --- a/spec/commands/shell_command_spec.rb +++ b/spec/commands/shell_command_spec.rb @@ -5,11 +5,13 @@ @t = pry_tester(@o) do def command_state - pry.command_state[Pry::Command::ShellCommand.match] + Pry::CommandState.default.state_for(Pry::Command::ShellCommand.match) end end end + after { Pry::CommandState.default.reset(Pry::Command::ShellCommand.match) } + describe ".cd" do before do allow(Dir).to receive(:chdir) diff --git a/spec/control_d_handler_spec.rb b/spec/control_d_handler_spec.rb index 2c5a45aea..5e21ccbee 100644 --- a/spec/control_d_handler_spec.rb +++ b/spec/control_d_handler_spec.rb @@ -37,7 +37,7 @@ it "saves a dup of the current binding stack in the 'cd' command" do described_class.default(eval_string, pry_instance) - cd_state = pry_instance.command_state['cd'] + cd_state = pry_instance.commands['cd'].state expect(cd_state.old_stack).to eq([binding1, binding2]) end