config: simplify structure

* Replace `require_relative` with `require`
  The project tries to use the `require` form everywhere where possible, which
  is the common form
* `require` from `pry.rb`
  Spreaded `require` statements where we require internal classes is confusing
* Fixed namespace definition for Config classes
  https://github.com/rubocop-hq/ruby-style-guide#namespace-definition recommends
  to use explicit nesting
This commit is contained in:
Kyrylo Silin 2018-10-28 17:45:29 +08:00
parent 7608ff1523
commit 4b6fc303bb
7 changed files with 475 additions and 455 deletions

View File

@ -113,6 +113,12 @@ require 'pry/command_set'
require 'pry/commands'
require 'pry/plugins'
require 'pry/core_extensions'
require 'pry/basic_object'
require 'pry/config/behavior'
require 'pry/config/memoization'
require 'pry/config/default'
require 'pry/config/convenience'
require 'pry/config'
require 'pry/pry_class'
require 'pry/pry_instance'
require 'pry/cli'

View File

@ -1,11 +1,9 @@
require_relative 'basic_object'
class Pry::Config < Pry::BasicObject
require_relative 'config/behavior'
require_relative 'config/memoization'
require_relative 'config/default'
require_relative 'config/convenience'
include Pry::Config::Behavior
def self.shortcuts
Convenience::SHORTCUTS
class Pry
class Config < Pry::BasicObject
include Behavior
def self.shortcuts
Convenience::SHORTCUTS
end
end
end

View File

@ -1,250 +1,255 @@
module Pry::Config::Behavior
ASSIGNMENT = "=".freeze
NODUP = [TrueClass, FalseClass, NilClass, Symbol, Numeric, Module, Proc].freeze
INSPECT_REGEXP = /#{Regexp.escape "default=#<"}/
ReservedKeyError = Class.new(RuntimeError)
class Pry
class Config < Pry::BasicObject
module Behavior
ASSIGNMENT = "=".freeze
NODUP = [TrueClass, FalseClass, NilClass, Symbol, Numeric, Module, Proc].freeze
INSPECT_REGEXP = /#{Regexp.escape "default=#<"}/
ReservedKeyError = Class.new(RuntimeError)
module Builder
#
# Returns a new Behavior, non-recursively (unlike {#from_hash}).
#
# @param
# (see #from_hash)
#
# @return
# (see #from_hash)
#
def assign(attributes, default = nil)
new(default).tap do |behavior|
behavior.merge!(attributes)
end
end
module Builder
#
# Returns a new Behavior, non-recursively (unlike {#from_hash}).
#
# @param
# (see #from_hash)
#
# @return
# (see #from_hash)
#
def assign(attributes, default = nil)
new(default).tap do |behavior|
behavior.merge!(attributes)
end
end
#
# Returns a new Behavior, recursively walking attributes.
#
# @param [Hash] attributes
# a hash to initialize an instance of self with.
#
# @param [Pry::Config, nil] default
# a default, or nil for none.
#
# @return [Pry::Config]
# returns an instance of self.
#
def from_hash(attributes, default = nil)
new(default).tap do |config|
attributes.each do |key,value|
config[key] = Hash === value ? from_hash(value, nil) : value
#
# Returns a new Behavior, recursively walking attributes.
#
# @param [Hash] attributes
# a hash to initialize an instance of self with.
#
# @param [Pry::Config, nil] default
# a default, or nil for none.
#
# @return [Pry::Config]
# returns an instance of self.
#
def from_hash(attributes, default = nil)
new(default).tap do |config|
attributes.each do |key,value|
config[key] = Hash === value ? from_hash(value, nil) : value
end
end
end
end
def self.included(klass)
klass.extend(Builder)
end
def initialize(default = Pry.config)
@default = default
@lookup = {}
@reserved_keys = methods.map(&:to_s).freeze
end
#
# @return [Pry::Config::Behavior]
# returns the default used incase a key isn't found in self.
#
def default
@default
end
#
# @param [String] key
# a key (as a String)
#
# @return [Object, BasicObject]
# returns an object from self or one of its defaults.
#
def [](key)
key = key.to_s
key?(key) ? @lookup[key] : (@default and @default[key])
end
#
# Add a key and value pair to self.
#
# @param [String] key
# a key (as a String).
#
# @param [Object,BasicObject] value
# a value.
#
# @raise [Pry::Config::ReservedKeyError]
# when 'key' is a reserved key name.
#
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 a key from self.
#
# @param [String] key
# a key (as a String)
#
# @return [void]
#
def forget(key)
key = key.to_s
__remove(key)
end
#
# @param [Hash, #to_h, #to_hash] other
# a hash 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
#
# @param [Hash, #to_h, #to_hash] other
# a hash to compare against the lookup table of self.
#
def ==(other)
@lookup == __try_convert_to_hash(other)
end
alias_method :eql?, :==
#
# @param [String] key
# a key (as a String)
#
# @return [Boolean]
# returns true when "key" is a member of self.
#
def key?(key)
key = key.to_s
@lookup.key?(key)
end
#
# Clear the lookup table of self.
#
# @return [void]
#
def clear
@lookup.clear
true
end
#
# @return [Array<String>]
# returns an array of keys in self.
#
def keys
@lookup.keys
end
def eager_load!
default = @default
while default
default.memoized_methods.each {|method| self[key] = default.public_send(key)} if default.respond_to?(:memoized_methods)
default = @default.default
end
end
def last_default
last = @default
last = last.default while last and last.default
last
end
#
# @return [Hash]
# returns a duplicate copy of the lookup table used by self.
#
def to_hash
@lookup.dup
end
alias_method :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(q)
q.text inspect[1..-1].gsub(INSPECT_REGEXP, "default=<")
end
def method_missing(name, *args, &block)
key = name.to_s
if key[-1] == ASSIGNMENT
short_key = key[0..-2]
self[short_key] = args[0]
elsif key?(key)
self[key]
elsif @default.respond_to?(name)
value = @default.public_send(name, *args, &block)
self[key] = __dup(value)
else
nil
end
end
def respond_to_missing?(key, include_all=false)
key = key.to_s.chomp(ASSIGNMENT)
key?(key) or @default.respond_to?(key) or super(key, include_all)
end
private
def __clip_inspect(obj)
"#{obj.class}:0x%x" % obj.object_id
end
def __try_convert_to_hash(obj)
if Hash === obj
obj
elsif obj.respond_to?(:to_h)
obj.to_h
elsif obj.respond_to?(:to_hash)
obj.to_hash
else
nil
end
end
def __dup(value)
if NODUP.any? { |klass| klass === value }
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
def self.included(klass)
klass.extend(Builder)
end
def initialize(default = Pry.config)
@default = default
@lookup = {}
@reserved_keys = methods.map(&:to_s).freeze
end
#
# @return [Pry::Config::Behavior]
# returns the default used incase a key isn't found in self.
#
def default
@default
end
#
# @param [String] key
# a key (as a String)
#
# @return [Object, BasicObject]
# returns an object from self or one of its defaults.
#
def [](key)
key = key.to_s
key?(key) ? @lookup[key] : (@default and @default[key])
end
#
# Add a key and value pair to self.
#
# @param [String] key
# a key (as a String).
#
# @param [Object,BasicObject] value
# a value.
#
# @raise [Pry::Config::ReservedKeyError]
# when 'key' is a reserved key name.
#
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 a key from self.
#
# @param [String] key
# a key (as a String)
#
# @return [void]
#
def forget(key)
key = key.to_s
__remove(key)
end
#
# @param [Hash, #to_h, #to_hash] other
# a hash 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
#
# @param [Hash, #to_h, #to_hash] other
# a hash to compare against the lookup table of self.
#
def ==(other)
@lookup == __try_convert_to_hash(other)
end
alias_method :eql?, :==
#
# @param [String] key
# a key (as a String)
#
# @return [Boolean]
# returns true when "key" is a member of self.
#
def key?(key)
key = key.to_s
@lookup.key?(key)
end
#
# Clear the lookup table of self.
#
# @return [void]
#
def clear
@lookup.clear
true
end
#
# @return [Array<String>]
# returns an array of keys in self.
#
def keys
@lookup.keys
end
def eager_load!
default = @default
while default
default.memoized_methods.each {|method| self[key] = default.public_send(key)} if default.respond_to?(:memoized_methods)
default = @default.default
end
end
def last_default
last = @default
last = last.default while last and last.default
last
end
#
# @return [Hash]
# returns a duplicate copy of the lookup table used by self.
#
def to_hash
@lookup.dup
end
alias_method :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(q)
q.text inspect[1..-1].gsub(INSPECT_REGEXP, "default=<")
end
def method_missing(name, *args, &block)
key = name.to_s
if key[-1] == ASSIGNMENT
short_key = key[0..-2]
self[short_key] = args[0]
elsif key?(key)
self[key]
elsif @default.respond_to?(name)
value = @default.public_send(name, *args, &block)
self[key] = __dup(value)
else
nil
end
end
def respond_to_missing?(key, include_all=false)
key = key.to_s.chomp(ASSIGNMENT)
key?(key) or @default.respond_to?(key) or super(key, include_all)
end
private
def __clip_inspect(obj)
"#{obj.class}:0x%x" % obj.object_id
end
def __try_convert_to_hash(obj)
if Hash === obj
obj
elsif obj.respond_to?(:to_h)
obj.to_h
elsif obj.respond_to?(:to_hash)
obj.to_hash
else
nil
end
end
def __dup(value)
if NODUP.any? { |klass| klass === value }
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

View File

@ -1,25 +1,28 @@
module Pry::Config::Convenience
SHORTCUTS = [
:input,
:output,
:commands,
:print,
:exception_handler,
:hooks,
:color,
:pager,
:editor,
:memory_size,
:extra_sticky_locals
]
class Pry
class Config < Pry::BasicObject
module Convenience
SHORTCUTS = [
:input,
:output,
:commands,
:print,
:exception_handler,
:hooks,
:color,
:pager,
:editor,
:memory_size,
:extra_sticky_locals
]
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) }
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

View File

@ -1,149 +1,154 @@
class Pry::Config::Default
include Pry::Config::Behavior
include Pry::Config::Memoization
class Pry
class Config < Pry::BasicObject
class Default
include Config::Behavior
include Config::Memoization
def_memoized({
input: proc {
lazy_readline
},
output: proc {
$stdout.tap { |out| out.sync = true }
},
commands: proc {
Pry::Commands
},
prompt_name: proc {
Pry::Prompt::DEFAULT_NAME
},
prompt: proc {
Pry::Prompt::DEFAULT
},
prompt_safe_contexts: proc {
Pry::Prompt::SAFE_CONTEXTS
},
print: proc {
Pry::DEFAULT_PRINT
},
quiet: proc {
false
},
exception_handler: proc {
Pry::DEFAULT_EXCEPTION_HANDLER
},
exception_whitelist: proc {
Pry::DEFAULT_EXCEPTION_WHITELIST
},
hooks: proc {
Pry::DEFAULT_HOOKS
},
pager: proc {
true
},
system: proc {
Pry::DEFAULT_SYSTEM
},
color: proc {
Pry::Helpers::BaseHelpers.use_ansi_codes?
},
default_window_size: proc {
5
},
editor: proc {
Pry.default_editor_for_platform
}, # TODO: Pry::Platform.editor
should_load_rc: proc {
true
},
should_load_local_rc: proc {
true
},
should_trap_interrupts: proc {
Pry::Platform.jruby?
},
disable_auto_reload: proc {
false
},
command_prefix: proc {
""
},
auto_indent: proc {
Pry::Helpers::BaseHelpers.use_ansi_codes?
},
correct_indent: proc {
true
},
collision_warning: proc {
false
},
output_prefix: proc {
"=> "
},
requires: proc {
[]
},
should_load_requires: proc {
true
},
should_load_plugins: proc {
true
},
windows_console_warning: proc {
true
},
control_d_handler: proc {
Pry::DEFAULT_CONTROL_D_HANDLER
},
memory_size: proc {
100
},
extra_sticky_locals: proc {
{}
},
command_completions: proc {
proc { commands.keys }
},
file_completions: proc {
proc { Dir["."] }
},
ls: proc {
Pry::Config.from_hash(Pry::Command::Ls::DEFAULT_OPTIONS)
},
completer: proc {
require "pry/input_completer"
Pry::InputCompleter
},
gist: proc {
Pry::Config.from_hash({inspecter: proc(&:pretty_inspect)}, nil)
},
history: proc {
Pry::Config.from_hash({should_save: true, should_load: true}, nil).tap do |history|
history.file = File.expand_path("~/.pry_history") rescue nil
if history.file.nil?
self.should_load_rc = false
history.should_save = false
history.should_load = false
end
def_memoized({
input: proc {
lazy_readline
},
output: proc {
$stdout.tap { |out| out.sync = true }
},
commands: proc {
Pry::Commands
},
prompt_name: proc {
Pry::Prompt::DEFAULT_NAME
},
prompt: proc {
Pry::Prompt::DEFAULT
},
prompt_safe_contexts: proc {
Pry::Prompt::SAFE_CONTEXTS
},
print: proc {
Pry::DEFAULT_PRINT
},
quiet: proc {
false
},
exception_handler: proc {
Pry::DEFAULT_EXCEPTION_HANDLER
},
exception_whitelist: proc {
Pry::DEFAULT_EXCEPTION_WHITELIST
},
hooks: proc {
Pry::DEFAULT_HOOKS
},
pager: proc {
true
},
system: proc {
Pry::DEFAULT_SYSTEM
},
color: proc {
Pry::Helpers::BaseHelpers.use_ansi_codes?
},
default_window_size: proc {
5
},
editor: proc {
Pry.default_editor_for_platform
}, # TODO: Pry::Platform.editor
should_load_rc: proc {
true
},
should_load_local_rc: proc {
true
},
should_trap_interrupts: proc {
Pry::Platform.jruby?
},
disable_auto_reload: proc {
false
},
command_prefix: proc {
""
},
auto_indent: proc {
Pry::Helpers::BaseHelpers.use_ansi_codes?
},
correct_indent: proc {
true
},
collision_warning: proc {
false
},
output_prefix: proc {
"=> "
},
requires: proc {
[]
},
should_load_requires: proc {
true
},
should_load_plugins: proc {
true
},
windows_console_warning: proc {
true
},
control_d_handler: proc {
Pry::DEFAULT_CONTROL_D_HANDLER
},
memory_size: proc {
100
},
extra_sticky_locals: proc {
{}
},
command_completions: proc {
proc { commands.keys }
},
file_completions: proc {
proc { Dir["."] }
},
ls: proc {
Pry::Config.from_hash(Pry::Command::Ls::DEFAULT_OPTIONS)
},
completer: proc {
require "pry/input_completer"
Pry::InputCompleter
},
gist: proc {
Pry::Config.from_hash({inspecter: proc(&:pretty_inspect)}, nil)
},
history: proc {
Pry::Config.from_hash({should_save: true, should_load: true}, nil).tap do |history|
history.file = File.expand_path("~/.pry_history") rescue nil
if history.file.nil?
self.should_load_rc = false
history.should_save = false
history.should_load = false
end
end
},
exec_string: proc {
""
}
})
def initialize
super(nil)
end
},
exec_string: proc {
""
}
})
def initialize
super(nil)
end
private
private
def lazy_readline
require 'readline'
Readline
rescue LoadError
warn "Sorry, you can't use Pry without Readline or a compatible library."
warn "Possible solutions:"
warn " * Rebuild Ruby with Readline support using `--with-readline`"
warn " * Use the rb-readline gem, which is a pure-Ruby port of Readline"
warn " * Use the pry-coolline gem, a pure-ruby alternative to Readline"
raise
def lazy_readline
require 'readline'
Readline
rescue LoadError
warn "Sorry, you can't use Pry without Readline or a compatible library."
warn "Possible solutions:"
warn " * Rebuild Ruby with Readline support using `--with-readline`"
warn " * Use the rb-readline gem, which is a pure-Ruby port of Readline"
warn " * Use the pry-coolline gem, a pure-ruby alternative to Readline"
raise
end
end
end
end

View File

@ -1,44 +1,48 @@
module Pry::Config::Memoization
MEMOIZED_METHODS = Hash.new {|h,k| h[k] = [] }
class Pry
class Config < Pry::BasicObject
module Memoization
MEMOIZED_METHODS = Hash.new {|h,k| h[k] = [] }
module ClassMethods
#
# Defines one or more methods who return a constant value after being
# called once.
#
# @example
# class Foo
# include Pry::Config::Memoization
# def_memoized({
# foo: proc {1+10},
# bar: proc{"aaa"<<"a"}
# })
# end
#
# @param [{String => Proc}] method_table
#
# @return [void]
#
def def_memoized(method_table)
method_table.each do |method_name, method|
define_method(method_name) do
method_table[method_name] = instance_eval(&method) if method_table[method_name].equal? method
method_table[method_name]
module ClassMethods
#
# Defines one or more methods who return a constant value after being
# called once.
#
# @example
# class Foo
# include Pry::Config::Memoization
# def_memoized({
# foo: proc {1+10},
# bar: proc{"aaa"<<"a"}
# })
# end
#
# @param [{String => Proc}] method_table
#
# @return [void]
#
def def_memoized(method_table)
method_table.each do |method_name, method|
define_method(method_name) do
method_table[method_name] = instance_eval(&method) if method_table[method_name].equal? method
method_table[method_name]
end
end
MEMOIZED_METHODS[self] |= method_table.keys
end
end
MEMOIZED_METHODS[self] |= method_table.keys
def self.included(mod)
mod.extend(ClassMethods)
end
#
# @return [Array<Symbol>]
# Returns the names of methods that have been defined by {ClassMethods#def_memoized}.
#
def memoized_methods
MEMOIZED_METHODS[self.class]
end
end
end
def self.included(mod)
mod.extend(ClassMethods)
end
#
# @return [Array<Symbol>]
# Returns the names of methods that have been defined by {ClassMethods#def_memoized}.
#
def memoized_methods
MEMOIZED_METHODS[self.class]
end
end

View File

@ -1,4 +1,3 @@
require 'pry/config'
class Pry
HOME_RC_FILE =