From e5556a2be8627ec3fe594c738f10422d5a1f5d43 Mon Sep 17 00:00:00 2001 From: Kyrylo Silin Date: Mon, 25 Mar 2019 02:40:05 +0200 Subject: [PATCH] Refactor Config Fixes #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). --- CHANGELOG.md | 5 + lib/pry.rb | 7 +- lib/pry/commands/ls/formatter.rb | 2 +- lib/pry/config.rb | 255 ++++++++++++++++--- lib/pry/config/attributable.rb | 20 ++ lib/pry/config/behavior.rb | 377 ----------------------------- lib/pry/config/convenience.rb | 28 --- lib/pry/config/lazy.rb | 27 --- lib/pry/config/lazy_value.rb | 27 +++ lib/pry/config/memoized_value.rb | 28 +++ lib/pry/config/value.rb | 22 ++ lib/pry/history.rb | 12 +- lib/pry/prompt.rb | 7 - lib/pry/pry_class.rb | 50 +--- lib/pry/pry_instance.rb | 14 +- lib/pry/testable.rb | 29 +-- spec/command_spec.rb | 2 +- spec/commands/edit_spec.rb | 14 +- spec/commands/hist_spec.rb | 8 +- spec/commands/show_doc_spec.rb | 12 +- spec/config/attributable_spec.rb | 27 +++ spec/config/behavior_spec.rb | 21 -- spec/config/lazy_value_spec.rb | 9 + spec/config/memoized_value_spec.rb | 9 + spec/config/value_spec.rb | 37 +++ spec/config_spec.rb | 345 ++++++++------------------ spec/history_spec.rb | 18 +- spec/prompt_spec.rb | 2 +- spec/pry_defaults_spec.rb | 4 +- spec/pry_output_spec.rb | 2 +- spec/pry_repl_spec.rb | 2 +- 31 files changed, 571 insertions(+), 851 deletions(-) create mode 100644 lib/pry/config/attributable.rb delete mode 100644 lib/pry/config/behavior.rb delete mode 100644 lib/pry/config/convenience.rb delete mode 100644 lib/pry/config/lazy.rb create mode 100644 lib/pry/config/lazy_value.rb create mode 100644 lib/pry/config/memoized_value.rb create mode 100644 lib/pry/config/value.rb create mode 100644 spec/config/attributable_spec.rb delete mode 100644 spec/config/behavior_spec.rb create mode 100644 spec/config/lazy_value_spec.rb create mode 100644 spec/config/memoized_value_spec.rb create mode 100644 spec/config/value_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index e7c238acb..d259460cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/lib/pry.rb b/lib/pry.rb index fbbaa2abd..623562c19 100644 --- a/lib/pry.rb +++ b/lib/pry.rb @@ -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' diff --git a/lib/pry/commands/ls/formatter.rb b/lib/pry/commands/ls/formatter.rb index 147e86ec2..8b0ae6b69 100644 --- a/lib/pry/commands/ls/formatter.rb +++ b/lib/pry/commands/ls/formatter.rb @@ -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. diff --git a/lib/pry/config.rb b/lib/pry/config.rb index 194535ccb..dea97447f 100644 --- a/lib/pry/config.rb +++ b/lib/pry/config.rb @@ -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] 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] + attribute :history_ignorelist + + # @return [Array] 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), @@ -42,11 +185,11 @@ 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, @@ -54,29 +197,72 @@ def self.defaults 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" \ @@ -85,6 +271,5 @@ def self.lazy_readline(defaults) ) raise end - private_class_method :lazy_readline end end diff --git a/lib/pry/config/attributable.rb b/lib/pry/config/attributable.rb new file mode 100644 index 000000000..4ff4e7cb0 --- /dev/null +++ b/lib/pry/config/attributable.rb @@ -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 diff --git a/lib/pry/config/behavior.rb b/lib/pry/config/behavior.rb deleted file mode 100644 index 1fb51f995..000000000 --- a/lib/pry/config/behavior.rb +++ /dev/null @@ -1,377 +0,0 @@ -class Pry - class Config < Pry::BasicObject - # - # {Pry::Config::Behavior} is a module who can be included by classes who - # wish to behave similar to an OpenStruct object: - # - # ```ruby - # class Store - # include Pry::Config::Behavior - # end - # store = Store.from_hash(number: 300) - # store.number # => 300 - # store[:number] # => 300 - # store['number'] # => 300 - # ``` - # - # Classes who include {Pry::Config::Behavior} can be linked to each other - # to provide a default in case a key does not exist locally: - # - # ```ruby - # class Store - # include Pry::Config::Behavior - # end - # store = Store.from_hash({}, Store.from_hash(greeting: 'hello')) - # store.greeting # => 'hello' - # ``` - # - # When an object is read from a default like in the example above, a copy - # of the object is created to avoid a mutation changing its value: - # - # ```ruby - # default = Store.from_hash(greeting: 'hello') - # store = Store.from_hash({}, default) - # store.greeting # => 'hello' - # default.greeting.sub! 'hello', 'goodbye' - # store.greeting # => 'hello' - # ``` - # - module Behavior - ASSIGNMENT = "=".freeze - - NODUP = [ - TrueClass, FalseClass, NilClass, Symbol, Numeric, Module, Proc, Method, - Pry::Prompt, Pry::Config::Lazy - ].freeze - - INSPECT_REGEXP = /#{Regexp.escape "default=#<"}/.freeze - ReservedKeyError = Class.new(RuntimeError) - - # - # The instance methods of this module are available as singleton methods - # on classes who include {Pry::Config::Behavior}. The methods can be used - # to initialize a {Pry::Config::Behavior} object from a Hash object. - # - # @example - # class Store - # include Pry::Config::Behavior - # end - # obj1 = Store.assign(foo: 1, bar: 2) - # obj2 = Store.from_hash(foo: 1, bar: 2) - # [obj1.class, obj2.class] # => [Store, Store] - # - module Builder - # - # @example - # c = Pry::Config.assign(foo: {bar: {baz: 42}}) - # c.class # => Pry::Config - # c.foo.class # => Hash - # - # @param - # (see #from_hash) - # - # @return [Pry::Config::Behavior] - # An instance of an object that has included Pry::Config::Behavior. - # `attributes` is not visited using recursion. - # - def assign(attributes, default = nil) - new(default).tap do |behavior| - behavior.merge!(attributes) - end - end - - # - # @example - # c = Pry::Config.from_hash(foo: {bar: {baz: 42}}) - # c.foo.bar.class # => Pry::Config - # c.foo.bar.baz # => 42 - # - # @param [Hash] attributes - # - # @param [Pry::Config::Behavior, nil] default - # A default, or nil for none. - # - # @return [Pry::Config::Behavior] - # An instance of an object that has included Pry::Config::Behavior. - # `attributes` is visited using recursion. - # - def from_hash(attributes, default = nil) - new(default).tap do |config| - attributes.each do |key, value| - config[key] = if value.is_a?(Hash) - from_hash(value) - elsif value.is_a?(Array) - value.map { |v| v.is_a?(Hash) ? from_hash(v) : v } - else - value - end - end - end - end - end - - def self.included(klass) - klass.extend(Builder) - end - - # - # @example - # class Store - # include Pry::Config::Behavior - # end - # c = Store.new(Pry.config) - # c.input # => Readline - # - # @param [Pry::Config::Behavior, nil] default - # A default to query when a key is not found in self, or nil for none. - # - # - def initialize(default = Pry.config) - @default = default - @lookup = {} - @reserved_keys = methods.map(&:to_s).freeze - end - - # - # @return [Pry::Config::Behavior, nil] - # The object queried when a key is not found in self. - # - def default - @default - end - - # - # @param [#to_s] key - # - # @return [Object, BasicObject] - # An object - # - def [](key) - key = key.to_s - obj = key?(key) ? @lookup[key] : (@default && @default[key]) - obj.is_a?(Pry::Config::Lazy) ? obj.call : obj - end - - # - # Assigns a key/value pair. - # - # @param [#to_s] key - # - # @param [Object, BasicObject] value - # - # @raise [Pry::Config::ReservedKeyError] - # When `key` is a reserved key. - # - def []=(key, value) - key = key.to_s - if @reserved_keys.include?(key) - raise ReservedKeyError, - "It is not possible to use '#{key}' as a key name, please " \ - "choose a different key name." - end - - __push(key, value) - end - - # - # Removes `key` from self and allows the next lookup for `key` to - # traverse back to {#default}. - # - # @example - # pry_instance.config.prompt_name = 'foo' - # pry_instance.config.forget(:prompt_name) - # pry_instance.config.prompt_name # => 'pry' - # - # @param [#to_s] key - # - # @return [void] - # - def forget(key) - key = key.to_s - __remove(key) - default.forget(key) if default && default != last_default - end - - # - # @example - # c = Pry::Config.from_hash(foo: 1) - # c.merge!(bar: 2) - # c.merge!(Pry::Config.from_hash(baz: 3)) - # - # @param [Hash, #to_h, #to_hash] other - # An object to merge into self. - # - # @return [void] - # - def merge!(other) - other = __try_convert_to_hash(other) - raise TypeError, "unable to convert argument into a Hash" unless other - - other.each do |key, value| - self[key] = value - end - end - - # - # @example - # Pry::Config.from_hash(foo: 1) == {'foo' => 1} # => true - # Pry::Config.from_hash(foo: 1) == Pry::Config.from_hash(foo: 1) # => true - # - # @param [Hash, #to_h, #to_hash] other - # Compares `other` against self. - # - # @return [Boolean] - # True if self and `other` are considered `eql?`, otherwise false. - # - def ==(other) - return false unless other - - @lookup == __try_convert_to_hash(other) - end - alias eql? == - - # - # @example - # c = Pry::Config.from_hash(foo: 1) - # c.key?(:foo) # => true - # c.key?('foo') # => true - # - # @param [#to_s] key - # - # @return [Boolean] - # True if `key` is stored in self, otherwise false. - # - def key?(key) - key = key.to_s - @lookup.key?(key) - end - - # - # Clears the contents of self. - # - # @return [void] - # - def clear - @lookup.clear - true - end - - # - # @return [Array] - # An array of keys being stored in self. - # - def keys - @lookup.keys - end - - # - # Eagerly loads keys into self directly from {#last_default}. - # - # @example - # [1] pry(main)> pry_instance.config.keys.size - # => 13 - # [2] pry(main)> pry_instance.config.eager_load!; - # [warning] Pry.config.exception_whitelist is deprecated, - # please use Pry.config.unrescued_exceptions instead. - # [3] pry(main)> pry_instance.config.keys.size - # => 40 - # - # @return [Array, nil] - # An array of keys inserted into self, or nil if {#last_default} is nil. - # - def eager_load! - return unless last_default - - last_default.keys.each { |key| self[key] = public_send(key) } - end - - # - # @example - # # pry_instance.config -> Pry.config -> Pry::Config.defaults - # [1] pry(main)> pry_instance.config.last_default - # - # @return [Pry::Config::Behaviour] - # The last linked default, or nil if there is none. - # - def last_default - last = @default - last = last.default while last && last.default - last - end - - # - # @return [Hash] - # A duplicate copy of the Hash used by self. - # - def to_hash - @lookup.dup - end - alias to_h to_hash - - def inspect - key_str = keys.map { |key| "'#{key}'" }.join(",") - "#<#{__clip_inspect(self)} keys=[#{key_str}] default=#{@default.inspect}>" - end - - def pretty_print(queue) - queue.text(inspect[1..-1].gsub(INSPECT_REGEXP, "default=<")) - end - - # rubocop:disable Style/MethodMissingSuper - def method_missing(method_name, *args, &block) - name = method_name.to_s - if name[-1] == ASSIGNMENT - short_name = name[0..-2] - self[short_name] = args[0] - elsif key?(name) - self[name] - elsif @default.respond_to?(method_name) - value = @default.public_send(method_name, *args, &block) - self[name] = __dup(value) - end - end - # rubocop:enable Style/MethodMissingSuper - - def respond_to_missing?(key, include_all = false) - key = key.to_s.chomp(ASSIGNMENT) - key?(key) || @default.respond_to?(key) || super(key, include_all) - end - - private - - def __clip_inspect(obj) - format("#{obj.class}:0x%x", id: obj.object_id) - end - - def __try_convert_to_hash(obj) - if obj.is_a?(Hash) - obj - elsif obj.respond_to?(:to_h) - obj.to_h - elsif obj.respond_to?(:to_hash) - obj.to_hash - end - end - - def __dup(value) - if NODUP.any? { |klass| value.is_a?(klass) } - value - else - value.dup - end - end - - def __push(key, value) - unless singleton_class.method_defined? key - define_singleton_method(key) { self[key] } - define_singleton_method("#{key}=") { |val| @lookup[key] = val } - end - @lookup[key] = value - end - - def __remove(key) - @lookup.delete(key) - end - end - end -end diff --git a/lib/pry/config/convenience.rb b/lib/pry/config/convenience.rb deleted file mode 100644 index 5560b82f0..000000000 --- a/lib/pry/config/convenience.rb +++ /dev/null @@ -1,28 +0,0 @@ -class Pry - class Config < Pry::BasicObject - module Convenience - SHORTCUTS = [ - :input, - :output, - :commands, - :print, - :exception_handler, - :hooks, - :color, - :pager, - :editor, - :memory_size, - :extra_sticky_locals - ].freeze - - def config_shortcut(*names) - names.each do |name| - reader = name - setter = "#{name}=" - define_method(reader) { config.public_send(name) } - define_method(setter) { |value| config.public_send(setter, value) } - end - end - end - end -end diff --git a/lib/pry/config/lazy.rb b/lib/pry/config/lazy.rb deleted file mode 100644 index 9072a5e54..000000000 --- a/lib/pry/config/lazy.rb +++ /dev/null @@ -1,27 +0,0 @@ -class Pry - class Config < Pry::BasicObject - # The primary purpose for instances of this class is to be used as a - # configuration value that is computed upon each access, see {Pry.lazy} - # for more examples. - # - # @example - # num = 19 - # pry_instance.config.foo = Pry::Config::Lazy.new(&proc { num += 1 }) - # pry_instance.config.foo # => 20 - # pry_instance.config.foo # => 21 - # pry_instance.config.foo # => 22 - # - # @api private - # @since v0.12.0 - class Lazy - def initialize(&block) - @block = block - end - - # @return [Object] - def call - @block.call - end - end - end -end diff --git a/lib/pry/config/lazy_value.rb b/lib/pry/config/lazy_value.rb new file mode 100644 index 000000000..8c2330ac0 --- /dev/null +++ b/lib/pry/config/lazy_value.rb @@ -0,0 +1,27 @@ +class Pry + class Config + # LazyValue is a Proc (block) wrapper. It is meant to be used as a + # configuration value. Subsequent `#call` calls always evaluate the given + # block. + # + # @example + # num = 19 + # value = Pry::Config::LazyValue.new { num += 1 } + # value.foo # => 20 + # value.foo # => 21 + # value.foo # => 22 + # + # @api private + # @since ?.?.? + # @see Pry::Config::MemoizedValue + class LazyValue + def initialize(&block) + @block = block + end + + def call + @block.call + end + end + end +end diff --git a/lib/pry/config/memoized_value.rb b/lib/pry/config/memoized_value.rb new file mode 100644 index 000000000..a8f61d158 --- /dev/null +++ b/lib/pry/config/memoized_value.rb @@ -0,0 +1,28 @@ +class Pry + class Config + # MemoizedValue is a Proc (block) wrapper. It is meant to be used as a + # configuration value. Subsequent `#call` calls return the same memoized + # result. + # + # @example + # num = 19 + # value = Pry::Config::MemoizedValue.new { num += 1 } + # value.call # => 20 + # value.call # => 20 + # value.call # => 20 + # + # @api private + # @since ?.?.? + # @see Pry::Config::LazyValue + class MemoizedValue + def initialize(&block) + @block = block + @call = nil + end + + def call + @call ||= @block.call + end + end + end +end diff --git a/lib/pry/config/value.rb b/lib/pry/config/value.rb new file mode 100644 index 000000000..3afec3866 --- /dev/null +++ b/lib/pry/config/value.rb @@ -0,0 +1,22 @@ +class Pry + class Config + # Value holds a value for the given attribute and decides how it should + # be read. Procs get called, other values are returned as is. + # + # @since ?.?.? + # @api private + class Value + def initialize(value) + @value = value + end + + def call + unless [Config::MemoizedValue, Config::LazyValue].include?(@value.class) + return @value + end + + @value.call + end + end + end +end diff --git a/lib/pry/history.rb b/lib/pry/history.rb index ad1c590d2..17451bd16 100644 --- a/lib/pry/history.rb +++ b/lib/pry/history.rb @@ -55,7 +55,7 @@ def push(line) return line if line == last_line @history << line - @saver.call(line) if !should_ignore?(line) && Pry.config.history.should_save + @saver.call(line) if !should_ignore?(line) && Pry.config.history_save line end @@ -95,17 +95,17 @@ def filter(history) private # Check if the line match any option in the histignore - # [Pry.config.history.histignore] + # [Pry.config.history_ignorelist] # @return [Boolean] a boolean that notifies if the line was found in the # histignore array. def should_ignore?(line) - hist_ignore = Pry.config.history.histignore + hist_ignore = Pry.config.history_ignorelist return false if hist_ignore.nil? || hist_ignore.empty? hist_ignore.any? { |p| line.to_s.match(p) } end - # The default loader. Yields lines from `Pry.history.config.file`. + # The default loader. Yields lines from `Pry.config.history_file`. def read_from_file path = history_file_path @@ -114,7 +114,7 @@ def read_from_file warn "Unable to read history file: #{error.message}" end - # The default saver. Appends the given line to `Pry.history.config.file`. + # The default saver. Appends the given line to `Pry.config.history_file`. def save_to_file(line) history_file.puts line if history_file end @@ -137,7 +137,7 @@ def history_file end def history_file_path - File.expand_path(@file_path || Pry.config.history.file) + File.expand_path(@file_path || Pry.config.history_file) end def invalid_readline_line?(line) diff --git a/lib/pry/prompt.rb b/lib/pry/prompt.rb index 1792f59fa..906a5f01b 100644 --- a/lib/pry/prompt.rb +++ b/lib/pry/prompt.rb @@ -34,13 +34,6 @@ class Pry # @since v0.11.0 # @api public class Prompt - # @return [String] - DEFAULT_NAME = 'pry'.freeze - - # @return [Array] the list of objects that are known to have a - # 1-line #inspect output suitable for prompt - SAFE_CONTEXTS = [String, Numeric, Symbol, nil, true, false].freeze - # A Hash that holds all prompts. The keys of the Hash are prompt # names, the values are Hash instances of the format {:description, :value}. @prompts = {} diff --git a/lib/pry/pry_class.rb b/lib/pry/pry_class.rb index a58e8bab9..c00c6ae11 100644 --- a/lib/pry/pry_class.rb +++ b/lib/pry/pry_class.rb @@ -30,27 +30,13 @@ class << self def_delegators :@plugin_manager, :plugins, :load_plugins, :locate_plugins - extend Pry::Config::Convenience - config_shortcut(*Pry::Config.shortcuts) - - def prompt=(value) - config.prompt = value - end - - def prompt - config.prompt - end - - def history - return @history if @history - - @history = - if defined?(Pry.config.input::HISTORY) - History.new(history: Pry.config.input::HISTORY) - else - History.new - end - end + def_delegators( + :@config, :input, :input=, :output, :output=, :commands, + :commands=, :print, :print=, :exception_handler, :exception_handler=, + :hooks, :hooks=, :color, :color=, :pager, :pager=, :editor, :editor=, + :memory_size, :memory_size=, :extra_sticky_locals, :extra_sticky_locals=, + :prompt, :prompt=, :history, :history= + ) # # @example @@ -162,7 +148,7 @@ def self.final_session_setup @session_finalized = true load_plugins if Pry.config.should_load_plugins load_requires if Pry.config.should_load_requires - load_history if Pry.config.history.should_load + load_history if Pry.config.history_load load_traps if Pry.config.should_trap_interrupts load_win32console if Helpers::Platform.windows? && !Helpers::Platform.windows_ansi? end @@ -342,7 +328,7 @@ def self.reset_defaults @initial_session = true @session_finalized = nil - self.config = Pry::Config.new Pry::Config.defaults + self.config = Pry::Config.new self.cli = false self.current_line = 1 self.line_buffer = [""] @@ -401,24 +387,6 @@ def self.critical_section ensure Thread.current[:pry_critical_section] -= 1 end - - # Wraps a block in a named block called `Pry::Config::Lazy`. This is used for - # dynamic config values, which are calculated every time - # {Pry::Config::Lazy#call} is called. - # - # @example - # # pryrc - # Pry.config.prompt_name = Pry.lazy { rand(100) } - # - # # Session - # [1] 96(main)> - # [2] 19(main)> - # [3] 80(main)> - # - # @return [#call] - def self.lazy(&block) - Pry::Config::Lazy.new(&block) - end end Pry.init diff --git a/lib/pry/pry_instance.rb b/lib/pry/pry_instance.rb index 03840142d..a6f87a7fe 100644 --- a/lib/pry/pry_instance.rb +++ b/lib/pry/pry_instance.rb @@ -25,6 +25,8 @@ # rubocop:disable Metrics/ClassLength class Pry + extend Pry::Forwardable + attr_accessor :binding_stack attr_accessor :custom_completions attr_accessor :eval_string @@ -45,8 +47,13 @@ class Pry attr_reader :config - extend Pry::Config::Convenience - config_shortcut(*Pry::Config.shortcuts) + def_delegators( + :@config, :input, :input=, :output, :output=, :commands, + :commands=, :print, :print=, :exception_handler, :exception_handler=, + :hooks, :hooks=, :color, :color=, :pager, :pager=, :editor, :editor=, + :memory_size, :memory_size=, :extra_sticky_locals, :extra_sticky_locals= + ) + EMPTY_COMPLETIONS = [].freeze # Create a new {Pry} instance. @@ -75,8 +82,7 @@ def initialize(options = {}) @eval_string = "" @backtrace = options.delete(:backtrace) || caller target = options.delete(:target) - @config = Pry::Config.new - config.merge!(options) + @config = self.class.config.merge(options) push_prompt(config.prompt) @input_ring = Pry::Ring.new(config.memory_size) @output_ring = Pry::Ring.new(config.memory_size) diff --git a/lib/pry/testable.rb b/lib/pry/testable.rb index 1a16ec26a..5dbb9269a 100644 --- a/lib/pry/testable.rb +++ b/lib/pry/testable.rb @@ -34,20 +34,6 @@ def self.included(mod) end end - TEST_DEFAULTS = { - color: false, - pager: false, - should_load_rc: false, - should_load_local_rc: false, - correct_indent: false, - collison_warning: false, - history: { - should_load: false, - should_save: false - } - }.freeze - private_constant :TEST_DEFAULTS - # # Sets various configuration options that make Pry optimal for a test # environment, see source code for complete details. @@ -55,8 +41,17 @@ def self.included(mod) # @return [void] # def self.set_testenv_variables - Pry.config = Pry::Config.from_hash TEST_DEFAULTS, Pry::Config.defaults - Pry.config.hooks = Pry::Hooks.new + Pry.config = Pry::Config.new.merge( + color: false, + pager: false, + should_load_rc: false, + should_load_local_rc: false, + correct_indent: false, + collision_warning: false, + history_save: false, + history_load: false, + hooks: Pry::Hooks.new + ) end # @@ -65,7 +60,7 @@ def self.set_testenv_variables # @return [void] # def self.unset_testenv_variables - Pry.config = Pry::Config.from_hash({}, Pry::Config.defaults) + Pry.config = Pry::Config.new end end end diff --git a/spec/command_spec.rb b/spec/command_spec.rb index b01cff079..56ee27b30 100644 --- a/spec/command_spec.rb +++ b/spec/command_spec.rb @@ -480,7 +480,7 @@ def process; end subject { Class.new(described_class).new(pry_instance: Pry.new) } - it "returns a state hash" do + it "returns a state object" do expect(subject.state).to be_an(OpenStruct) end diff --git a/spec/commands/edit_spec.rb b/spec/commands/edit_spec.rb index 114906f6f..cc7410538 100644 --- a/spec/commands/edit_spec.rb +++ b/spec/commands/edit_spec.rb @@ -195,7 +195,7 @@ def last_exception end it "should reload the file" do - Pry.config.editor = lambda { |file, _line| + @t.pry.config.editor = lambda { |file, _line| File.open(file, 'w') { |f| f << "FOO = 'BAR'" } nil } @@ -244,7 +244,7 @@ def last_exception describe "with --patch" do # Original source code must be untouched. it "should apply changes only in memory (monkey patching)" do - Pry.config.editor = lambda { |file, _line| + @t.pry.config.editor = lambda { |file, _line| File.open(file, 'w') { |f| f << "FOO3 = 'PIYO'" } @patched_def = File.open(file, 'r').read nil @@ -265,7 +265,7 @@ def last_exception describe "with --ex NUM" do before do - Pry.config.editor = proc do |file, line| + @t.pry.config.editor = proc do |file, line| @__ex_file__ = file @__ex_line__ = line nil @@ -341,7 +341,7 @@ def last_exception end it "should evaluate the expression" do - Pry.config.editor = lambda { |file, _line| + @t.pry.config.editor = lambda { |file, _line| File.open(file, 'w') { |f| f << "'FOO'\n" } nil } @@ -350,7 +350,7 @@ def last_exception end it "should ignore -n for tempfiles" do - Pry.config.editor = lambda { |file, _line| + @t.pry.config.editor = lambda { |file, _line| File.open(file, 'w') { |f| f << "'FOO'\n" } nil } @@ -359,7 +359,7 @@ def last_exception end it "should not evaluate a file with -n" do - Pry.config.editor = lambda { |file, _line| + @t.pry.config.editor = lambda { |file, _line| File.open(file, 'w') { |f| f << "'FOO'\n" } nil } @@ -374,7 +374,7 @@ def last_exception it "should write the evaluated command to history" do quote = 'history repeats itself, first as tradegy...' - Pry.config.editor = lambda { |file, _line| + @t.pry.config.editor = lambda { |file, _line| File.open(file, 'w') do |f| f << quote end diff --git a/spec/commands/hist_spec.rb b/spec/commands/hist_spec.rb index 29315cba8..558b4f998 100644 --- a/spec/commands/hist_spec.rb +++ b/spec/commands/hist_spec.rb @@ -176,13 +176,13 @@ def next_input describe "sessions" do before do - @old_file = Pry.config.history.file - Pry.config.history.file = File.expand_path('spec/fixtures/pry_history') + @old_file = Pry.config.history_file + Pry.config.history_file = File.expand_path('spec/fixtures/pry_history') @hist.load end after do - Pry.config.history.file = @old_file + Pry.config.history_file = @old_file end it "displays history only for current session" do @@ -202,7 +202,7 @@ def next_input end it "should not display histignore words in history" do - Pry.config.history.histignore = [ + Pry.config.history_ignorelist = [ "well", "hello", "beautiful", diff --git a/spec/commands/show_doc_spec.rb b/spec/commands/show_doc_spec.rb index 82e6b2084..e0bba4dae 100644 --- a/spec/commands/show_doc_spec.rb +++ b/spec/commands/show_doc_spec.rb @@ -178,14 +178,10 @@ def initialize(foo); end def decolumnize(output); end end - begin - t = pry_tester(binding) - Pry.config.color = true - expect(t.eval("show-doc _c#decolumnize")).to match(/ls -l \$HOME/) - expect(t.eval("show-doc _c#decolumnize")).not_to match(/`ls -l \$HOME`/) - ensure - Pry.config.color = false - end + t = pry_tester(binding) + t.pry.config.color = true + expect(t.eval("show-doc _c#decolumnize")).to match(/ls -l \$HOME/) + expect(t.eval("show-doc _c#decolumnize")).not_to match(/`ls -l \$HOME`/) end end diff --git a/spec/config/attributable_spec.rb b/spec/config/attributable_spec.rb new file mode 100644 index 000000000..c0c2b167f --- /dev/null +++ b/spec/config/attributable_spec.rb @@ -0,0 +1,27 @@ +RSpec.describe Pry::Config::Attributable do + subject { klass.new } + + describe "#attribute" do + let(:klass) do + Class.new do + extend Pry::Config::Attributable + attribute :foo + end + end + + it "creates a reader attribute for the given name" do + expect(klass.instance_method(:foo)).to be_a(UnboundMethod) + end + + it "creates a writer attribute for the given name" do + expect(klass.instance_method(:foo=)).to be_a(UnboundMethod) + end + + context "and when the attribute is invoked" do + it "sends the 'call' message to the value" do + expect_any_instance_of(Pry::Config::Value).to receive(:call) + subject.foo + end + end + end +end diff --git a/spec/config/behavior_spec.rb b/spec/config/behavior_spec.rb deleted file mode 100644 index 9a789a162..000000000 --- a/spec/config/behavior_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -RSpec.describe Pry::Config::Behavior do - let(:behavior) do - Class.new do - include Pry::Config::Behavior - end - end - - describe "#last_default" do - it "returns the last default" do - last = behavior.from_hash({}, nil) - middle = behavior.from_hash({}, last) - expect(behavior.from_hash({}, middle).last_default).to be(last) - end - end - - describe "#eager_load!" do - it "returns nil when the default is nil" do - expect(behavior.from_hash({}, nil).eager_load!).to be(nil) - end - end -end diff --git a/spec/config/lazy_value_spec.rb b/spec/config/lazy_value_spec.rb new file mode 100644 index 000000000..dc2aaa1ee --- /dev/null +++ b/spec/config/lazy_value_spec.rb @@ -0,0 +1,9 @@ +RSpec.describe Pry::Config::LazyValue do + describe "#call" do + subject { described_class.new { rand } } + + it "doesn't memoize the result of call" do + expect(subject.call).not_to eq(subject.call) + end + end +end diff --git a/spec/config/memoized_value_spec.rb b/spec/config/memoized_value_spec.rb new file mode 100644 index 000000000..1601bff25 --- /dev/null +++ b/spec/config/memoized_value_spec.rb @@ -0,0 +1,9 @@ +RSpec.describe Pry::Config::MemoizedValue do + describe "#call" do + subject { described_class.new { rand } } + + it "memoizes the result of call" do + expect(subject.call).to eq(subject.call) + end + end +end diff --git a/spec/config/value_spec.rb b/spec/config/value_spec.rb new file mode 100644 index 000000000..efb8eca4b --- /dev/null +++ b/spec/config/value_spec.rb @@ -0,0 +1,37 @@ +RSpec.describe Pry::Config::Value do + describe "#call" do + context "when given value is a MemoizedValue" do + subject { described_class.new(Pry::Config::MemoizedValue.new { 123 }) } + + it "calls the MemoizedLazy object" do + expect(subject.call).to eq(123) + end + end + + context "when given value is a LazyValue" do + subject { described_class.new(Pry::Config::LazyValue.new { 123 }) } + + it "calls the LazyValue object" do + expect(subject.call).to eq(123) + end + end + + context "when given value is a Proc" do + let(:callable) { proc {} } + + subject { described_class.new(callable) } + + it "returns the value as is" do + expect(subject.call).to eq(callable) + end + end + + context "when given value is a non-callable object" do + subject { described_class.new('test') } + + it "returns the value as is" do + expect(subject.call).to eq('test') + end + end + end +end diff --git a/spec/config_spec.rb b/spec/config_spec.rb index a4e09613e..1cd9cdb2e 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -1,289 +1,134 @@ RSpec.describe Pry::Config do - describe ".from_hash" do - it "returns an object without a default" do - local = described_class.from_hash({}) - expect(local.default).to eq(nil) - end - - it "returns an object with a default" do - default = described_class.new(nil) - local = described_class.from_hash({}, default) - expect(local.default).to eq(local) - end + specify { expect(subject.input).to respond_to(:readline) } + specify { expect(subject.output).to be_an(IO) } + specify { expect(subject.commands).to be_a(Pry::CommandSet) } + specify { expect(subject.prompt_name).to be_a(String) } + specify { expect(subject.prompt).to be_a(Pry::Prompt) } + specify { expect(subject.prompt_safe_contexts).to be_an(Array) } + specify { expect(subject.print).to be_a(Method) } + specify { expect(subject.quiet).to be(true).or be(false) } + specify { expect(subject.exception_handler).to be_a(Method) } + specify { expect(subject.unrescued_exceptions).to be_an(Array) } + specify { expect(subject.hooks).to be_a(Pry::Hooks) } + specify { expect(subject.pager).to be(true).or be(false) } + specify { expect(subject.system).to be_a(Method) } + specify { expect(subject.color).to be(true).or be(false) } + specify { expect(subject.default_window_size).to be_a(Numeric) } + specify { expect(subject.editor).to be_a(String) } + specify { expect(subject.should_load_rc).to be(true).or be(false) } + specify { expect(subject.should_load_local_rc).to be(true).or be(false) } + specify { expect(subject.should_trap_interrupts).to be(true).or be(false) } + specify { expect(subject.disable_auto_reload).to be(true).or be(false) } + specify { expect(subject.command_prefix).to be_a(String) } + specify { expect(subject.auto_indent).to be(true).or be(false) } + specify { expect(subject.correct_indent).to be(true).or be(false) } + specify { expect(subject.collision_warning).to be(true).or be(false) } + specify { expect(subject.output_prefix).to be_a(String) } + specify { expect(subject.requires).to be_an(Array) } + specify { expect(subject.should_load_requires).to be(true).or be(false) } + specify { expect(subject.should_load_plugins).to be(true).or be(false) } + specify { expect(subject.windows_console_warning).to be(true).or be(false) } + specify { expect(subject.control_d_handler).to be_a(Method) } + specify { expect(subject.memory_size).to be_a(Numeric) } + specify { expect(subject.extra_sticky_locals).to be_a(Hash) } + specify { expect(subject.command_completions).to be_a(Proc) } + specify { expect(subject.file_completions).to be_a(Proc) } + specify { expect(subject.ls).to be_an(OpenStruct) } + specify { expect(subject.completer).to eq(Pry::InputCompleter) } + specify { expect(subject.history).to be_a(Pry::History) } + specify { expect(subject.history_save).to eq(true).or be(false) } + specify { expect(subject.history_load).to eq(true).or be(false) } + specify { expect(subject.history_file).to be_a(String) } + specify { expect(subject.exec_string).to be_a(String) } - it "recursively walks a Hash" do - h = { 'foo1' => { 'foo2' => { 'foo3' => 'foobar' } } } - default = described_class.from_hash(h) - expect(default.foo1).to be_instance_of(described_class) - expect(default.foo1.foo2).to be_instance_of(described_class) - end + describe "#merge!" do + it "merges given hash with the config instance" do + subject.merge!(output_prefix: '~> ', exec_string: '!') - it "recursively walks an Array" do - c = described_class.from_hash(ary: [{ number: 2 }, Object]) - expect(c.ary[0].number).to eq(2) - expect(c.ary[1]).to eq(Object) + expect(subject.output_prefix).to eq('~> ') + expect(subject.exec_string).to eq('!') end - end - describe "bug #1552" do - specify( - "a local key has precendence over its default when the stored value is false" - ) do - local = described_class.from_hash({}, described_class.from_hash('color' => true)) - local.color = false - expect(local.color).to eq(false) + it "returns self" do + config = subject.merge!(output_prefix: '~> ') + expect(subject).to eql(config) end - end - describe "bug #1277" do - specify "a local key has precendence over an inherited method of the same name" do - local = described_class.from_hash(output: 'foobar') - local.extend( - Module.new do - def output - 'broken' - end - end - ) - expect(local.output).to eq('foobar') - end - end + context "when an undefined option is given" do + it "adds the option to the config" do + subject.merge!(new_option: 1, other_option: 2) - describe "reserved keys" do - it "raises ReservedKeyError on assignment of a reserved key" do - local = described_class.new - local.instance_variable_get(:@reserved_keys).each do |key| - expect { local[key] = 1 }.to raise_error(described_class::ReservedKeyError) + expect(subject.new_option).to eq(1) + expect(subject.other_option).to eq(2) end end end - describe "traversal to parent" do - it "traverses back to the parent when a local key is not found" do - local = described_class.new described_class.from_hash(foo: 1) - expect(local.foo).to eq(1) + describe "#merge" do + it "returns a new config object" do + expect(subject).not_to equal(subject.merge(new_option: 1, other_option: 2)) end - it "stores a local key and prevents traversal to the parent" do - local = described_class.new described_class.from_hash(foo: 1) - local.foo = 2 - expect(local.foo).to eq(2) - end + it "doesn't mutate the original config" do + subject.merge(new_option: 1, other_option: 2) - it "traverses through a chain of parents" do - root = described_class.from_hash(foo: 21) - local1 = described_class.new(root) - local2 = described_class.new(local1) - local3 = described_class.new(local2) - expect(local3.foo).to eq(21) - end - - it "stores a local copy of the parents hooks upon accessing them" do - parent = described_class.from_hash(hooks: "parent_hooks") - local = described_class.new parent - local.hooks.gsub! 'parent', 'local' - expect(local.hooks).to eq 'local_hooks' - expect(parent.hooks).to eq('parent_hooks') + expect(subject).not_to respond_to(:new_option) + expect(subject).not_to respond_to(:other_option) end end - describe "#respond_to_missing?" do - before do - @config = described_class.new(nil) + describe "#method_missing" do + context "when invoked method ends with =" do + it "assigns a new custom option" do + subject.foo = 1 + expect(subject.foo).to eq(1) + end end - it "returns a Method object for a dynamic key" do - @config["key"] = 1 - method_obj = @config.method(:key) - expect(method_obj.name).to eq :key - expect(method_obj.call).to eq(1) + context "when invoked method is not an option" do + it "raises NoMethodError" do + expect { subject.foo }.to raise_error(NoMethodError) + end end - it "returns a Method object for a setter on a parent" do - config = described_class.from_hash({}, described_class.from_hash(foo: 1)) - expect(config.method(:foo=)).to be_an_instance_of(Method) + context "when invoked method is a LazyValue" do + it "defines a callable attribute" do + subject.foo = Pry::Config::LazyValue.new { 1 } + expect(subject.foo).to eq(1) + end end end describe "#respond_to?" do - before do - @config = described_class.new(nil) - end - - it "returns true for a local key" do - @config.zzfoo = 1 - expect(@config.respond_to?(:zzfoo)).to eq(true) - end - - it "returns false for an unknown key" do - expect(@config.respond_to?(:blahblah)).to eq(false) - end - end - - describe "#default" do - it "returns nil" do - local = described_class.new(nil) - expect(local.default).to eq(nil) - end - - it "returns the default" do - default = described_class.new(nil) - local = described_class.new(default) - expect(local.default).to eq(default) - end - end - - describe "#keys" do - it "returns an array of local keys" do - root = described_class.from_hash({ zoo: "boo" }, nil) - local = described_class.from_hash({ foo: "bar" }, root) - expect(local.keys).to eq(["foo"]) - end - end - - describe "#==" do - it "compares equality through the underlying lookup table" do - local1 = described_class.new(nil) - local2 = described_class.new(nil) - local1.foo = "hi" - local2.foo = "hi" - expect(local1).to eq(local2) - end - - it "compares equality against an object who does not implement #to_hash" do - local1 = described_class.new(nil) - expect(local1).not_to eq(Object.new) - end - - it "returns false when compared against nil" do - # rubocop:disable Style/NilComparison - expect(described_class.new(nil) == nil).to eq(false) - # rubocop:enable Style/NilComparison - end - end - - describe '#forget' do - it 'restores a key to its default value' do - last_default = described_class.from_hash(a: 'c') - middle_default = described_class.from_hash({ a: 'b' }, last_default) - c = described_class.from_hash({ a: 'a' }, middle_default) - c.forget(:a) - expect(c.a).to eq('c') - end - end - - describe "#to_hash" do - it "provides a copy of local key & value pairs as a Hash" do - local = described_class.new described_class.from_hash(bar: true) - local.foo = "21" - expect(local.to_hash).to eq("foo" => "21") - end - - it "returns a duplicate of the lookup table" do - local = described_class.new(nil) - local.to_hash["foo"] = 42 - expect(local.foo).not_to eq(42) - end - end - - describe "#merge!" do - before do - @config = described_class.new(nil) - end - - it "merges an object who returns a Hash through #to_hash" do - obj = Class.new do - def to_hash - { epoch: 1 } - end - end.new - @config.merge!(obj) - expect(@config.epoch).to eq(1) - end - - it "merges an object who returns a Hash through #to_h" do - obj = Class.new do - def to_h - { epoch: 2 } - end - end.new - @config.merge!(obj) - expect(@config.epoch).to eq(2) - end - - it "merges a Hash" do - @config[:epoch] = 420 - expect(@config.epoch).to eq(420) - end - - it "raises a TypeError for objects who can't become a Hash" do - expect { @config.merge!(Object.new) }.to raise_error TypeError - end - end - - describe "#clear" do - before do - @local = described_class.new(nil) + context "when checking an undefined option" do + it "returns false" do + expect(subject.respond_to?(:foo)).to be(false) + end end - it "returns true" do - expect(@local.clear).to eq(true) - end + context "when checking a defined option" do + before { subject.foo = 1 } - it "clears local assignments" do - @local.foo = 1 - @local.clear - expect(@local.to_hash).to eq({}) - end - end + it "returns true for the reader" do + expect(subject.respond_to?(:foo)).to be(true) + end - describe "#[]=" do - it "stores keys as strings" do - local = described_class.from_hash({}) - local[:zoo] = "hello" - expect(local.to_hash).to eq("zoo" => "hello") + it "returns true for the writer" do + expect(subject.respond_to?(:foo=)).to be(true) + end end end describe "#[]" do - it "traverses back to a default" do - default = described_class.from_hash(k: 1) - local = described_class.new(default) - expect(local['k']).to eq(1) - end - - it "traverses back to a default (2 deep)" do - default1 = described_class.from_hash(k: 1) - default2 = described_class.from_hash({}, default1) - local = described_class.new(default2) - expect(local['k']).to eq(1) - end - - it "traverses back to a default that doesn't exist, and returns nil" do - local = described_class.from_hash({}, nil) - expect(local['output']).to eq(nil) + it "reads the config value" do + expect_any_instance_of(Pry::Config::Value).to receive(:call) + subject[:foo] = 1 + subject[:foo] end - context "when returning a Pry::Config::Lazy object" do - it "invokes #call on it" do - c = described_class.from_hash foo: Pry.lazy { 10 } - expect(c['foo']).to eq(10) - end - - it "invokes #call upon each access" do - c = described_class.from_hash foo: Pry.lazy { 'foo' } - expect(c['foo']).to_not equal(c['foo']) - end - end - end - - describe "#eager_load!" do - it "eagerly loads keys from the last default into self" do - last_default = described_class.from_hash(foo: 1, bar: 2, baz: 3) - c = described_class.from_hash({}, last_default) - expect(c.keys.size).to eq(0) - c.eager_load! - expect(c.keys.size).to eq(3) + it "returns the config value" do + subject[:foo] = 1 + expect(subject[:foo]).to eq(1) end end end diff --git a/spec/history_spec.rb b/spec/history_spec.rb index f69fea0ca..0e3f4b6ee 100644 --- a/spec/history_spec.rb +++ b/spec/history_spec.rb @@ -68,15 +68,15 @@ describe "#clear" do before do - @old_file = Pry.config.history.file + @old_file = Pry.config.history_file @hist_file_path = File.expand_path('spec/fixtures/pry_history') - Pry.config.history.file = @hist_file_path + Pry.config.history_file = @hist_file_path Pry.history.clear Pry.load_history end after do - Pry.config.history.file = @old_file + Pry.config.history_file = @old_file end it "clears this session's history" do @@ -130,12 +130,12 @@ before do @histfile = Tempfile.new(%w[pryhistory txt]) @history = Pry::History.new(file_path: @histfile.path) - Pry.config.history.should_save = true + Pry.config.history_save = true end after do @histfile.close(true) - Pry.config.history.should_save = false + Pry.config.history_save = false end it "saves lines to a file as they are written" do @@ -152,7 +152,7 @@ end it "should not write histignore words to the history file" do - Pry.config.history.histignore = ["ls", /hist*/, 'exit'] + Pry.config.history_ignorelist = ["ls", /hist*/, 'exit'] @history.push "ls" @history.push "hist" @history.push "kakaroto" @@ -164,8 +164,8 @@ end describe "expanding the history file path" do - before { Pry.config.history.should_save = true } - after { Pry.config.history.should_save = false } + before { Pry.config.history_save = true } + after { Pry.config.history_save = false } it "recognizes ~ (#1262)" do # This is a pretty dumb way of testing this, but at least it shouldn't @@ -193,7 +193,7 @@ end it "handles #{error_class} failure to write history" do - Pry.config.history.should_save = true + Pry.config.history_save = true expect(File).to receive(:open).with(file_path, "a", 0o600).and_raise(error_class) expect(history).to receive(:warn).with(/Unable to write history file:/) expect { history.push("anything") }.to_not raise_error diff --git a/spec/prompt_spec.rb b/spec/prompt_spec.rb index 9e57045f8..58616cd54 100644 --- a/spec/prompt_spec.rb +++ b/spec/prompt_spec.rb @@ -90,7 +90,7 @@ it "computes prompt name dynamically" do proc = described_class[:default].wait_proc - pry.config.prompt_name = Pry.lazy { enum.next } + pry.config.prompt_name = Pry::Config::LazyValue.new { enum.next } expect(proc.call(Object.new, 1, pry, '>')).to eq('[1] a(#):1> ') expect(proc.call(Object.new, 1, pry, '>')).to eq('[1] b(#):1> ') end diff --git a/spec/pry_defaults_spec.rb b/spec/pry_defaults_spec.rb index 133d9b02d..24fa54c51 100644 --- a/spec/pry_defaults_spec.rb +++ b/spec/pry_defaults_spec.rb @@ -373,7 +373,7 @@ def m.name binding, input: InputTester.new("1", "exit-all"), output: @str_output, - hooks: Pry::Config.defaults.hooks + hooks: Pry::Config.new.hooks ) expect(@str_output.string).to match(/[w]hereami by default/) @@ -384,7 +384,7 @@ def m.name input: InputTester.new('exit-all'), output: @str_output, quiet: true, - hooks: Pry::Config.defaults.hooks + hooks: Pry::Config.new.hooks ) expect(@str_output.string).to eq "" diff --git a/spec/pry_output_spec.rb b/spec/pry_output_spec.rb index 2facc4f87..8f905d023 100644 --- a/spec/pry_output_spec.rb +++ b/spec/pry_output_spec.rb @@ -1,6 +1,6 @@ describe Pry do describe "output failsafe" do - after { Pry.config.print = Pry::Config.defaults.print } + after { Pry.config.print = Pry::Config.new.print } it "should catch serialization exceptions" do Pry.config.print = proc { raise "catch-22" } diff --git a/spec/pry_repl_spec.rb b/spec/pry_repl_spec.rb index 5430a0a79..f407240a8 100644 --- a/spec/pry_repl_spec.rb +++ b/spec/pry_repl_spec.rb @@ -45,7 +45,7 @@ it "shouldn't break if we start a nested instance" do ReplTester.start do - input 'Pry.start(10, pry_instance.config)' + input 'Pry.start(10)' output '' prompt(/10.*> $/)