├── .gitignore ├── .rspec ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.MIT ├── README.md ├── Rakefile ├── lib ├── pry-timetravel.rb ├── pry-timetravel │ └── commands.rb └── pry │ └── timetravel.rb ├── pry-timetravel.gemspec └── spec ├── commands_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | .rbx 4 | .yardoc 5 | doc 6 | tags 7 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | --format d 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: bundler 3 | rvm: 4 | - 1.9.3 5 | - 2.0.0 6 | - 2.1.0 7 | - rbx 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.4 released 2017-01-02 2 | * Add auto-snapshot mode via `snap -a` 3 | * Include `snap next` and `snap step` aliases by default 4 | 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gemspec 3 | -------------------------------------------------------------------------------- /LICENSE.MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Brock Wilcox 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pry-timetravel 2 | 3 | Time travel! For your REPL! 4 | 5 | > x = 5 6 | 5 7 | > snap 8 | > x = 10 9 | 10 10 | > back 11 | > x 12 | 5 13 | 14 | This is an attempt to package the proof of concept. API WILL CHANGE! 15 | 16 | ## How it works 17 | 18 | The 'snap' command causes a fork, and the child is sent a SIGSTOP to freeze the 19 | process. The parent continues on. Later when you do 'back' the saved child is 20 | sent SIGCONT and the current process is sent a SIGSTOP. Ultimately you can have 21 | a whole pool of frozen snapshots and can resume any of them. 22 | 23 | In theory copy-on-write semantics makes this a tollerable thing to do :) 24 | 25 | There are a few more details, but that is the important bit. 26 | 27 | WARNING: Time travel may cause zombies. 28 | 29 | ## KNOWN ISSUES 30 | 31 | ### General 32 | 33 | * If you close a connection, like to a DB, it is closed for all snapshots 34 | 35 | ### Redis fork detection 36 | 37 | Redis checks to see if you forked and yells at you about needing to reconnect. 38 | Reasonably so, as it is a crazy idea to use the same connection in forked 39 | children! But we are doing crazy things. In 3.1.0 the gem will auto-reconnect, 40 | or you can pass inherit_socket to the connection to stop that. Or you can do 41 | this to bypass safety measures: 42 | 43 | class Redis 44 | class Client 45 | def ensure_connected 46 | yield 47 | end 48 | end 49 | end 50 | 51 | ## Similar Technology 52 | 53 | * [Time Travel Python Debugger](https://github.com/TomOnTime/timetravelpdb) 54 | * [Elm's Time Traveling Debugger](http://debug.elm-lang.org/) 55 | * [OCaml Time Travel](http://caml.inria.fr/pub/docs/manual-ocaml-4.00/manual030.html#htoc195) 56 | * [Chronon (java - proprietary)](http://chrononsystems.com/) 57 | 58 | ## Meta 59 | 60 | Released under the MIT license, see LICENSE.MIT for details. License is 61 | negotiable. Contributions and bug-reports are welcome! 62 | 63 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rspec/core/rake_task' 2 | 3 | task :default => :test 4 | task :spec => :test 5 | 6 | RSpec::Core::RakeTask.new(:test) 7 | 8 | task :build do 9 | sh 'gem build *.gemspec' 10 | end 11 | 12 | task :install => :build do 13 | sh 'gem install *.gem' 14 | end 15 | -------------------------------------------------------------------------------- /lib/pry-timetravel.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'pry' 3 | require 'json' 4 | 5 | require_relative 'pry-timetravel/commands' 6 | 7 | # = Timetravel! 8 | # 9 | # This is a pry plugin that keeps a pool of fork()ed process checkpoints, so 10 | # that you can jump "back in time" to a previous place in your execution. 11 | # 12 | # == Forking Model 13 | # 14 | # When you create a snapshot, fork() is executed. The parent (original) process 15 | # is suspended, and the new child process picks up where that left off. 16 | class PryTimetravel 17 | class << self 18 | 19 | def dlog(msg) 20 | if ENV["TIMETRAVEL_DEBUG"] 21 | File.open("meta.log", 'a') do |file| 22 | file.puts("#{Time.now} [#{$$}] #{msg}") 23 | end 24 | end 25 | end 26 | 27 | # TODO: Should probably delay calling this until first snapshot 28 | at_exit do 29 | PryTimetravel.dlog "at_exit" 30 | if $root_parent && $$ != $root_parent 31 | PryTimetravel.dlog "Sending SIGUSR1 up to #{$root_parent}" 32 | Process.kill 'SIGUSR1', $root_parent 33 | end 34 | end 35 | 36 | def enter_suspended_animation 37 | dlog("Suspend: Installing SIGCONT trap") 38 | old_sigcont_handler = Signal.trap('CONT') do 39 | dlog("Got a SIGCONT") 40 | end 41 | 42 | dlog("Suspend: Installing SIGEXIT trap") 43 | old_sigexit_handler = Signal.trap('EXIT') do 44 | dlog("got EXIT") 45 | Kernel.exit! true 46 | end 47 | 48 | dlog("Suspend: Stopping myself") 49 | Process.kill 'SIGSTOP', $$ 50 | 51 | dlog("Resume: Back from SIGSTOP! Loading snapshot tree") 52 | load_global_timetravel_state 53 | 54 | dlog("Resume: Returning to old SIGCONT") 55 | Signal.trap('CONT', old_sigcont_handler || "DEFAULT") 56 | 57 | dlog("Resume: Returning to old SIGEXIT") 58 | Signal.trap('EXIT', old_sigexit_handler || "DEFAULT") 59 | end 60 | 61 | # The root parent is a sort of overseer of the whole tree, and something 62 | # that is still alive and waiting for any signals from the outside world. 63 | # 64 | # Once any time travel starts, the parent forks and waits for its one and only direct child. All the other action happens in that child and its own children. 65 | # 66 | # If you USR1 this root parent, it will try to clean up the entire tree. 67 | # 68 | # It ignores INT, so that if you ^C the process it won't die so quickly. 69 | def start_root_parent 70 | $root_parent = $$ 71 | child_pid = fork 72 | if child_pid 73 | Signal.trap('INT') do 74 | dlog("Root-parent: Got INT, ignoring") 75 | end 76 | Signal.trap('USR1') do 77 | dlog("Root-parent: Got USR1, exiting") 78 | cleanup_global_timetravel_state 79 | Kernel.exit! true 80 | end 81 | dlog "Root parent: Waiting on child pid #{child_pid}" 82 | Process.waitpid child_pid 83 | dlog "Root parent: Exiting after wait" 84 | cleanup_global_timetravel_state 85 | FileUtils.rm("/tmp/timetravel_#{$root_parent}.json") 86 | Kernel.exit! true 87 | end 88 | end 89 | 90 | def auto_snapshot(target) 91 | if !@do_trace 92 | @trace = TracePoint.new(:line) do |tp| 93 | # puts "trace status: #{$in_trace}" 94 | if !$in_trace 95 | $in_trace = true 96 | # puts "path: #{File.expand_path(tp.path)}" 97 | # puts "snapshotting trace" 98 | # if ! (tp.defined_class.to_s =~ /Pry/) 99 | # if tp.path =~ /
/ 100 | # if tp.path =~ /
/ 101 | # p [tp.path, tp.lineno, tp.event, tp.defined_class, Dir.pwd] # , tp.raised_exception] 102 | # end 103 | # if tp.path.include?(Dir.pwd) || tp.path =~ /
/ 104 | if File.expand_path(tp.path).include?(Dir.pwd) 105 | p [tp.path, tp.lineno, tp.event, tp.defined_class, Dir.pwd] # , tp.raised_exception] 106 | # p caller 107 | # if tp.path =~ /
/ 108 | # # p tp 109 | # p [tp.path, tp.lineno, tp.event] # , tp.raised_exception] 110 | PryTimetravel.snapshot( 111 | tp.binding, 112 | # now_do: -> { run(args.join(" ")) unless args.empty? }, 113 | # on_return_do: -> { run('whereami') } 114 | ) 115 | # end 116 | end 117 | $in_trace = false 118 | end 119 | end 120 | @trace.enable 121 | @do_trace = true 122 | else 123 | @trace.disable 124 | @do_trace = false 125 | end 126 | end 127 | 128 | def update_current_snapshot_info(target, parent_pid = nil) 129 | my_pid = $$.to_s 130 | @snap_tree ||= {} 131 | @snap_tree[my_pid] ||= {} 132 | @snap_tree[my_pid]["file"] = target.eval('__FILE__') 133 | @snap_tree[my_pid]["line"] = target.eval('__LINE__') 134 | @snap_tree[my_pid]["time"] = Time.now.to_f 135 | @snap_tree[my_pid]["id"] = @id 136 | @snap_tree[my_pid]["previous"] = parent_pid if parent_pid 137 | end 138 | 139 | def snapshot(target, opts = {}) 140 | opts[:now_do] ||= -> {} 141 | opts[:on_return_do] ||= -> {} 142 | 143 | # We need a root-parent to keep the shell happy 144 | if ! $root_parent 145 | start_root_parent 146 | end 147 | 148 | @id ||= 0 149 | @timetravel_root ||= $$ 150 | update_current_snapshot_info(target) 151 | @id += 1 152 | 153 | parent_pid = $$ 154 | child_pid = fork 155 | 156 | if child_pid 157 | 158 | dlog("Snapshot: I am parent #{parent_pid}. I have a child pid #{child_pid}. Now suspending.") 159 | enter_suspended_animation 160 | 161 | dlog("Snapshot: Back from suspended animation. Running on_return_do.") 162 | # Perform operation now that we've come back 163 | opts[:on_return_do].() 164 | 165 | else 166 | 167 | child_pid = $$ 168 | dlog("Snapshot: I am child #{child_pid}. I have a parent pid #{parent_pid}") 169 | 170 | update_current_snapshot_info(target, parent_pid) 171 | 172 | # Perform immediate operation 173 | dlog("Snapshot: Running now_do.") 174 | opts[:now_do].() 175 | 176 | end 177 | end 178 | 179 | def snapshot_list(target, indent = "", node = @timetravel_root.to_s, my_indent = nil) 180 | if node == "" 181 | return "No snapshots" 182 | end 183 | return unless node && node != "" 184 | 185 | # Freshen the current snapshot so it looks right 186 | update_current_snapshot_info(target) 187 | 188 | code_line = IO.readlines(@snap_tree[node]["file"])[@snap_tree[node]["line"].to_i - 1].chomp 189 | 190 | out = "#{my_indent || indent}#{node} (#{@snap_tree[node]["id"]}) #{@snap_tree[node]["file"]} #{@snap_tree[node]["line"]} #{ node == $$.to_s ? '***' : ''} #{code_line}\n" 191 | children = @snap_tree.keys.select { |n| @snap_tree[n]["previous"] == node.to_i } 192 | if children.length == 1 193 | out += snapshot_list(target, indent, children[0]) 194 | else 195 | children.each { |n| 196 | out += snapshot_list(target, indent + " ", n, indent + "> ") 197 | } 198 | end 199 | # @snap_tree.keys.select { |n| 200 | # @snap_tree[n]["previous"] == node.to_i 201 | # }.each do |n| 202 | # out += snapshot_list(target, indent + " ", n) 203 | # end 204 | out 205 | end 206 | 207 | def restore_snapshot(target, target_pid = nil) 208 | dlog("Restore: Trying to restore,"); 209 | 210 | if target_pid.nil? && @snap_tree && ! @snap_tree[$$.to_s].nil? 211 | target_pid = @snap_tree[$$.to_s]["previous"] 212 | else 213 | target_pid = target_pid 214 | end 215 | 216 | if target_pid && @snap_tree[target_pid.to_s] 217 | dlog("Restore: I found a target pid #{target_pid}! TIME TRAVEL TIME") 218 | 219 | # Update our current information of our current running snapshot 220 | update_current_snapshot_info(target) 221 | 222 | save_global_timetravel_state 223 | 224 | # Bring our target back to life 225 | Process.kill 'SIGCONT', target_pid 226 | 227 | # Go to sleeeeeeeppppp 228 | enter_suspended_animation 229 | else 230 | dlog("Restore: I was unable to time travel. Maybe it is a myth."); 231 | puts "No previous snapshot found." 232 | end 233 | end 234 | 235 | def restore_root_snapshot 236 | restore_snapshot(@timetravel_root) if @timetravel_root 237 | end 238 | 239 | def global_timetravel_state_filename 240 | "/tmp/timetravel_#{$root_parent}.json" 241 | end 242 | 243 | def save_global_timetravel_state 244 | File.open(global_timetravel_state_filename, 'w') do |f| 245 | global_state = { 246 | snap_tree: @snap_tree, 247 | do_trace: @do_trace 248 | } 249 | f.puts global_state.to_json 250 | end 251 | end 252 | 253 | def load_global_timetravel_state 254 | global_state = JSON.parse(File.read(global_timetravel_state_filename)) 255 | @snap_tree = global_state["snap_tree"] 256 | @do_trace = global_state["do_trace"] 257 | dlog("Loaded: " + @snap_tree.to_json) 258 | @id = (@snap_tree.values.map{|snap| snap['id']}.max || 0) + 1 259 | end 260 | 261 | def cleanup_global_timetravel_state 262 | FileUtils.rm(global_timetravel_state_filename) 263 | end 264 | 265 | end 266 | end 267 | 268 | -------------------------------------------------------------------------------- /lib/pry-timetravel/commands.rb: -------------------------------------------------------------------------------- 1 | 2 | Pry::Commands.create_command "snap", "Create a snapshot that you can later return to" do 3 | match 'snap' 4 | group 'Timetravel' 5 | banner <<-'BANNER' 6 | Usage: snap [cmd] 7 | snap -l|--list List existing snapshots 8 | snap -a|--auto Automatically take new snapshots 9 | 10 | This will add a snapshot which you can return to later. 11 | 12 | If you provide [cmd] then that command will also be run -- nice for "snap next" in conjunction with pry-byebug. 13 | BANNER 14 | 15 | def options(opt) 16 | # opt.on :d, :delete=, 17 | # "Delete the snapshot with the given index. If no index is given delete them all", 18 | # :optional_argument => true, :as => Integer 19 | opt.on :l, :list, 20 | "Show a list of existing snapshots" 21 | opt.on :a, :auto, "Automatically take snapshots!" 22 | end 23 | def process 24 | if opts.l? 25 | output.puts PryTimetravel.snapshot_list(target) 26 | elsif opts.a? 27 | output.puts PryTimetravel.auto_snapshot(target) 28 | else 29 | PryTimetravel.snapshot( 30 | target, 31 | now_do: -> { run(args.join(" ")) unless args.empty? }, 32 | on_return_do: -> { run('whereami') } 33 | ) 34 | end 35 | end 36 | end 37 | 38 | Pry::Commands.create_command "back", "Go back to the most recent snapshot" do 39 | match 'back' 40 | group 'Timetravel' 41 | banner <<-'BANNER' 42 | Usage: back [count] 43 | back --pid pid 44 | back --home 45 | 46 | Go back to a previous snapshot. 47 | BANNER 48 | 49 | def options(opt) 50 | opt.on :p, :pid=, "Jump (back) to a specific snapshot identified by [pid]", 51 | :as => Integer 52 | opt.on :home, "Jump to the end of the original execution sequence" 53 | end 54 | def process 55 | if opts.home? 56 | PryTimetravel.restore_root_snapshot(target) 57 | else 58 | target_pid = args.first ? args.first.to_i : opts[:p] 59 | PryTimetravel.restore_snapshot(target, target_pid) 60 | end 61 | end 62 | end 63 | 64 | Pry::Commands.alias_command 'n', 'snap next' 65 | Pry::Commands.alias_command 's', 'snap step' 66 | Pry::Commands.alias_command 'p', 'back' 67 | -------------------------------------------------------------------------------- /lib/pry/timetravel.rb: -------------------------------------------------------------------------------- 1 | #Bundler shenanigans 2 | require 'pry-timetravel' 3 | -------------------------------------------------------------------------------- /pry-timetravel.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'pry-timetravel' 3 | s.version = '0.0.4' 4 | s.summary = 'Timetravel' 5 | s.description = 'Allows you to timetravel!' 6 | s.homepage = 'https://github.com/awwaiid/pry-timetravel' 7 | s.email = ['awwaiid@thelackthereof.org'] 8 | s.authors = ['Brock Wilcox'] 9 | s.files = `git ls-files`.split("\n") 10 | s.require_paths = ['lib'] 11 | 12 | s.add_dependency 'pry' 13 | 14 | s.add_development_dependency 'rake' 15 | s.add_development_dependency 'rspec' 16 | s.add_development_dependency 'pry-byebug' 17 | end 18 | -------------------------------------------------------------------------------- /spec/commands_spec.rb: -------------------------------------------------------------------------------- 1 | require './spec/spec_helper' 2 | require 'pty' 3 | require 'expect' 4 | 5 | pry_timetravel_cmd = "TERM=dumb pry -f --no-color --no-pager --no-history --noprompt -s timetravel" 6 | 7 | describe "pry-timetravel" do 8 | it "starts with no snapshots" do 9 | PTY.spawn(pry_timetravel_cmd) do |reader, writer| 10 | writer.puts("snap --list") 11 | found = reader.expect(/No snapshots/,1) 12 | expect(found).to be_truthy 13 | end 14 | end 15 | 16 | it "Exits with 0 snapshots cleanly" do 17 | saved_pid = nil 18 | PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid| 19 | saved_pid = cmd_pid 20 | writer.puts("snap --list") 21 | found = reader.expect(/No snapshots/,1) 22 | expect(found).to be_truthy 23 | writer.puts("exit") 24 | sleep 0.1 # Give time to exit? 25 | end 26 | 27 | pid_list = `ps h -o pid,ppid -s #{saved_pid}`.split(/\n/) 28 | expect(pid_list.count).to be == 0 29 | end 30 | 31 | it "creates one snapshot" do 32 | PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid| 33 | writer.puts("snap") 34 | writer.puts("snap --list") 35 | 36 | all1, pid1 = reader.expect(/^(\d+) \(0\)
1/,1) 37 | all2, pid2 = reader.expect(/^ (\d+) \(1\)
1 \*\*\*/,1) 38 | 39 | expect(pid1.to_i).to be > 0 40 | expect(pid2.to_i).to be > 0 41 | 42 | pid_list = `ps h -o pid,ppid,sess -s #{cmd_pid}`.split(/\n/) 43 | # 0: shell 44 | # 1: root 45 | # 2: base snapshot 46 | # 3: current running branch 47 | expect(pid_list.count).to be == 4 48 | end 49 | end 50 | 51 | it "creates second snapshot" do 52 | PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid| 53 | writer.puts("snap") 54 | writer.puts("snap") 55 | writer.puts("snap --list") 56 | 57 | all1, pid1 = reader.expect(/^(\d+) \(0\)
1/,1) 58 | all2, pid2 = reader.expect(/^ (\d+) \(1\)
1/,1) 59 | all3, pid3 = reader.expect(/^ (\d+) \(2\)
1 \*\*\*/,1) 60 | 61 | expect(pid1.to_i).to be > 0 62 | expect(pid2.to_i).to be > 0 63 | expect(pid3.to_i).to be > 0 64 | 65 | pid_list = `ps h -o pid,ppid -s #{cmd_pid}`.split(/\n/) 66 | # 0: shell 67 | # 1: root 68 | # 2: base snapshot 69 | # 2: second snapshot 70 | # 3: current running branch 71 | expect(pid_list.count).to be == 5 72 | end 73 | end 74 | 75 | it "Can time-travel to before a var existed" do 76 | PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid| 77 | writer.puts("snap") 78 | writer.puts("x = 7") 79 | reader.expect(/^=> 7/,1) 80 | writer.puts("x") 81 | reader.expect(/^=> 7/,1) 82 | writer.puts("back") 83 | reader.expect(/^At the top level\./,1) 84 | writer.puts("x") 85 | result = reader.expect(/^NameError: undefined local variable or method `x' for main:Object/,1) 86 | expect(result).to be_truthy 87 | end 88 | end 89 | 90 | it "Can time-travel to a previous var value" do 91 | PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid| 92 | writer.puts("x = 7") 93 | expect(reader.expect(/^=> 7/,1)).to be_truthy 94 | writer.puts("snap") 95 | writer.puts("x") 96 | expect(reader.expect(/^=> 7/,1)).to be_truthy 97 | writer.puts("snap") 98 | writer.puts("x = 7") 99 | reader.expect(/^=> 13/,1) 100 | writer.puts("back") 101 | reader.expect(/^At the top level\./,1) 102 | writer.puts("x") 103 | result = reader.expect(/^=> 7/,1) 104 | expect(result).to be_truthy 105 | end 106 | end 107 | 108 | it "Can auto-checkpoint" do 109 | PTY.spawn(pry_timetravel_cmd) do |reader, writer, cmd_pid| 110 | writer.puts("snap -a") 111 | writer.puts("x = 7") 112 | expect(reader.expect(/^=> 7/,1)).to be_truthy 113 | writer.puts("x") 114 | expect(reader.expect(/^=> 7/,1)).to be_truthy 115 | writer.puts("x = 13") 116 | expect(reader.expect(/^=> 13/,1)).to be_truthy 117 | writer.puts("back") 118 | expect(reader.expect(/^At the top level\./,1)).to be_truthy 119 | writer.puts("x") 120 | result = reader.expect(/^=> 7/,1) 121 | expect(result).to be_truthy 122 | end 123 | end 124 | 125 | end 126 | 127 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require './lib/pry-timetravel' 3 | 4 | #require 'pry/test/helper' 5 | 6 | # in case the tests call reset_defaults, ensure we reset them to 7 | # amended (test friendly) values 8 | # class << Pry 9 | # alias_method :orig_reset_defaults, :reset_defaults 10 | # def reset_defaults 11 | # orig_reset_defaults 12 | 13 | # Pry.config.color = false 14 | # Pry.config.pager = false 15 | # Pry.config.should_load_rc = false 16 | # Pry.config.should_load_local_rc= false 17 | # Pry.config.should_load_plugins = false 18 | # Pry.config.history.should_load = false 19 | # Pry.config.history.should_save = false 20 | # Pry.config.correct_indent = false 21 | # Pry.config.hooks = Pry::Hooks.new 22 | # Pry.config.collision_warning = false 23 | # end 24 | # end 25 | # Pry.reset_defaults 26 | --------------------------------------------------------------------------------