Skip to content

Commit

Permalink
Merge pull request #2020 from pry/command-global-state
Browse files Browse the repository at this point in the history
pry_instance: factor out command state to be global
  • Loading branch information
kyrylo committed Apr 29, 2019
2 parents ba18ef3 + aab6431 commit af1c066
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/pry.rb
Expand Up @@ -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)

Expand Down
6 changes: 5 additions & 1 deletion lib/pry/command.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
29 changes: 29 additions & 0 deletions 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
4 changes: 2 additions & 2 deletions lib/pry/control_d_handler.rb
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions lib/pry/pry_instance.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
12 changes: 9 additions & 3 deletions spec/command_spec.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
47 changes: 47 additions & 0 deletions 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
10 changes: 3 additions & 7 deletions spec/commands/cd_spec.rb
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion spec/commands/shell_command_spec.rb
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion spec/control_d_handler_spec.rb
Expand Up @@ -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

Expand Down

0 comments on commit af1c066

Please sign in to comment.