Skip to content

Commit

Permalink
Refactor Config
Browse files Browse the repository at this point in the history
Fixes pry#1843 (Rework the Pry config)

There are a few breaking changes. They are mostly minor, so I decided not to
indroduce deprecations because it will considerable slow things down.

Key changes:

* `Pry.lazy` was replaced with `Pry::Configuration::LazyValue`

  The config accepts three values now `LazyValue`, `MemoizedValue` and simply
  `Value`. The main difference is that:
    - `Value` is any value, including procs (so that an option returns a raw
      proc)
    - `LazyValue` is a proc that is call on every invocation of an option
    - `MemoizedValue` is a value that is called only once (and then the option
      always returns the return value of the )

* `Pry.config.history` was a meta-option that held suboptions. However, the new
  config doesn't permit that (unless you know what you do)

  Instead, we introduce a few options. For example:
    - `Pry.config.history.histignore` becomes `Pry.config.history_ignorelist`
    - `Pry.config.history.file` becomes `Pry.config.history_file`
    - and so on

  This was done so we can simplify configuration merging. Inlining option makes
  configuration implementation simpler, without losing much. The rule is that
  you want to keep your options under your prefix (if you are a
  plugin). Therefore, say, `Pry.config.pry_rescue.*` should be
  `Pry.config.pry_rescue_*` if you need merging.

The rest should behave in a similar fashion (and I rely heavily on our test
suite to claim so).
  • Loading branch information
kyrylo committed May 1, 2019
1 parent 03afca9 commit e5556a2
Show file tree
Hide file tree
Showing 31 changed files with 571 additions and 851 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
* Added ability to forward ARGV to a Pry session via `--` (or `-`) when
launching Pry from shell
([#1902](https://github.com/pry/pry/commit/5cd65d3c0eb053f6edcdf571eea5d0cd990626ed))
* Added `Pry::Config::LazyValue` & `Pry::Config::MemoizedValue`, which allow
storing callable procs in the config
([#2024](https://github.com/pry/pry/pull/2024))

#### API changes

Expand Down Expand Up @@ -65,6 +68,8 @@
([#2001](https://github.com/pry/pry/pull/2001))
* Deleted `Pry::BlockCommand#opts` (use `#context` instead)
([#2003](https://github.com/pry/pry/pull/2003))
* Deleted `Pry.lazy` (use `Pry::Config::LazyValue` instead)
([#2024](https://github.com/pry/pry/pull/2024))

#### Bug fixes

Expand Down
7 changes: 4 additions & 3 deletions lib/pry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@
require 'pry/commands/ls/methods_helper'
require 'pry/commands/ls'

require 'pry/config/convenience'
require 'pry/config/lazy'
require 'pry/config/behavior'
require 'pry/config/attributable'
require 'pry/config/value'
require 'pry/config/memoized_value'
require 'pry/config/lazy_value'
require 'pry/config'

require 'pry/pry_class'
Expand Down
2 changes: 1 addition & 1 deletion lib/pry/commands/ls/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def write_out
private

def color(type, str)
Pry::Helpers::Text.send pry_instance.config.ls["#{type}_color"], str
Pry::Helpers::Text.send pry_instance.config.ls.send("#{type}_color"), str
end

# Add a new section to the output.
Expand Down
255 changes: 220 additions & 35 deletions lib/pry/config.rb
Original file line number Diff line number Diff line change
@@ -1,37 +1,180 @@
require 'ostruct'

class Pry
# The Pry config.
# @api public
class Config < Pry::BasicObject
include Behavior

# @return [Pry::Config]
# An object who implements the default configuration for all
# Pry sessions.
def self.defaults
defaults = from_hash(
input: Pry.lazy { lazy_readline(defaults) },
# @api private
class Config
extend Attributable

# @return [IO, #readline] he object from which Pry retrieves its lines of
# input
attribute :input

# @return [IO, #puts] where Pry should output results provided by {input}
attribute :output

# @return [Pry::CommandSet]
attribute :commands

# @return [Proc] the printer for Ruby expressions (not commands)
attribute :print

# @return [Proc] the printer for exceptions
attribute :exception_handler

# @return [Array] Exception that Pry shouldn't rescue
attribute :unrescued_exceptions

# @deprecated
# @return [Array] Exception that Pry shouldn't rescue
attribute :exception_whitelist

# @return [Integer] The number of lines of context to show before and after
# exceptions
attribute :default_window_size

# @return [Pry::Hooks]
attribute :hooks

# @return [Pry::Prompt]
attribute :prompt

# @return [String] The display name that is part of the prompt
attribute :prompt_name

# @return [Array<Object>] the list of objects that are known to have a
# 1-line #inspect output suitable for prompt
attribute :prompt_safe_contexts

# If it is a String, then that String is used as the shell
# command to invoke the editor.
#
# If it responds to #call is callable then `file`, `line`, and `reloading`
# are passed to it. `reloading` indicates whether Pry will be reloading code
# after the shell command returns. All parameters are optional.
# @return [String, #call]
attribute :editor

# A string that must precede all commands. For example, if is is
# set to "%", the "cd" command must be invoked as "%cd").
# @return [String]
attribute :command_prefix

# @return [Boolean]
attribute :color

# @return [Boolean]
attribute :pager

# @return [Boolean] whether the global ~/.pryrc should be loaded
attribute :should_load_rc

# @return [Boolean] whether the local ./.pryrc should be loaded
attribute :should_load_local_rc

# @return [Boolean]
attribute :should_load_plugins

# @return [Boolean] whether to load files specified with the -r flag
attribute :should_load_requires

# @return [Boolean] whether to disable edit-method's auto-reloading behavior
attribute :disable_auto_reload

# Whether Pry should trap SIGINT and cause it to raise an Interrupt
# exception. This is only useful on JRuby, MRI does this for us.
# @return [Boolean]
attribute :should_trap_interrupts

# @return [Pry::History]
attribute :history

# @return [Boolean]
attribute :history_save

# @return [Boolean]
attribute :history_load

# @return [String]
attribute :history_file

# @return [Array<String,Regexp>]
attribute :history_ignorelist

# @return [Array<String>] Ruby files to be required
attribute :requires

# @return [Integer] how many input/output lines to keep in memory
attribute :memory_size

# @return [Proc]
attribute :control_d_handler

# @return [Proc] The proc that runs system commands
attribute :system

# @return [Boolean]
attribute :auto_indent

# @return [Boolean]
attribute :correct_indent

# @return [Boolean] whether or not display a warning when a command name
# collides with a method/local in the current context.
attribute :collision_warning

# @return [Hash{Symbol=>Proc}]
attribute :extra_sticky_locals

# @return [#build_completion_proc] a completer to use
attribute :completer

# @return [Boolean] suppresses whereami output on `binding.pry`
attribute :quiet

# @return [Boolean] displays a warning about experience improvement on
# Windows
attribute :windows_console_warning

# @return [Proc]
attribute :command_completions

# @return [Proc]
attribute :file_completions

# @return [Hash]
attribute :ls

# @return [String] a line of code to execute in context before the session
# starts
attribute :exec_string

# @return [String]
attribute :output_prefix

def initialize
merge!(
input: MemoizedValue.new { lazy_readline },
output: $stdout.tap { |out| out.sync = true },
commands: Pry::Commands,
prompt_name: Pry::Prompt::DEFAULT_NAME,
prompt_name: 'pry',
prompt: Pry::Prompt[:default],
prompt_safe_contexts: Pry::Prompt::SAFE_CONTEXTS,
prompt_safe_contexts: [String, Numeric, Symbol, nil, true, false],
print: Pry::ColorPrinter.method(:default),
quiet: false,
exception_handler: Pry::ExceptionHandler.method(:handle_exception),

unrescued_exceptions: [
::SystemExit, ::SignalException, Pry::TooSafeException
],

exception_whitelist: Pry.lazy do
defaults.output.puts(
exception_whitelist: MemoizedValue.new do
output.puts(
'[warning] Pry.config.exception_whitelist is deprecated, ' \
'please use Pry.config.unrescued_exceptions instead.'
)
[::SystemExit, ::SignalException, Pry::TooSafeException]
unrescued_exceptions
end,

# The default hooks - display messages when beginning and ending Pry
# sessions.
hooks: Pry::Hooks.default,
pager: true,
system: Pry::SystemCommandHandler.method(:default),
Expand All @@ -42,41 +185,84 @@ def self.defaults
should_load_local_rc: true,
should_trap_interrupts: Pry::Helpers::Platform.jruby?,
disable_auto_reload: false,
command_prefix: "",
command_prefix: '',
auto_indent: Pry::Helpers::BaseHelpers.use_ansi_codes?,
correct_indent: true,
collision_warning: false,
output_prefix: "=> ",
output_prefix: '=> ',
requires: [],
should_load_requires: true,
should_load_plugins: true,
windows_console_warning: true,
control_d_handler: Pry::ControlDHandler.method(:default),
memory_size: 100,
extra_sticky_locals: {},
command_completions: proc { defaults.commands.keys },
file_completions: proc { Dir["."] },
ls: Pry::Config.from_hash(Pry::Command::Ls::DEFAULT_OPTIONS),
command_completions: proc { commands.keys },
file_completions: proc { Dir['.'] },
ls: OpenStruct.new(Pry::Command::Ls::DEFAULT_OPTIONS),
completer: Pry::InputCompleter,
history: {
should_save: true,
should_load: true,
file: Pry::History.default_file
},
exec_string: ""
history_save: true,
history_load: true,
history_file: Pry::History.default_file,
history_ignorelist: [],
history: MemoizedValue.new do
if defined?(input::HISTORY)
Pry::History.new(history: input::HISTORY)
else
Pry::History.new
end
end,
exec_string: ''
)

@custom_attrs = {}
end

def merge!(config_hash)
config_hash.each_pair { |attr, value| __send__("#{attr}=", value) }
self
end

def merge(config_hash)
dup.merge!(config_hash)
end

def []=(attr, value)
@custom_attrs[attr.to_s] = Config::Value.new(value)
end

def self.shortcuts
Convenience::SHORTCUTS
def [](attr)
@custom_attrs[attr.to_s].call
end

# @api private
def self.lazy_readline(defaults)
def method_missing(method_name, *args, &block)
name = method_name.to_s

if name.end_with?('=')
self[name[0..-2]] = args.first
elsif @custom_attrs.key?(name)
self[name]
else
super
end
end

def respond_to_missing?(method_name, include_all = false)
@custom_attrs.key?(method_name.to_s.tr('=', '')) || super
end

def initialize_dup(other)
super
@custom_attrs = @custom_attrs.dup
end

private

def lazy_readline
require 'readline'
::Readline
rescue LoadError
defaults.output.puts(
output.puts(
"Sorry, you can't use Pry without Readline or a compatible library. \n" \
"Possible solutions: \n" \
" * Rebuild Ruby with Readline support using `--with-readline` \n" \
Expand All @@ -85,6 +271,5 @@ def self.lazy_readline(defaults)
)
raise
end
private_class_method :lazy_readline
end
end
20 changes: 20 additions & 0 deletions lib/pry/config/attributable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Pry
class Config
# Attributable provides the ability to create "attribute"
# accessors. Attribute accessors create a standard "attr_writer" and a
# customised "attr_reader". This reader is Proc-aware (lazy).
#
# @since ?.?.?
# @api private
module Attributable
def attribute(attr_name)
define_method(attr_name) do
value = Config::Value.new(instance_variable_get("@#{attr_name}"))
value.call
end

attr_writer(attr_name)
end
end
end
end
Loading

0 comments on commit e5556a2

Please sign in to comment.