pry 0.10.4 → 0.11.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -18
  3. data/LICENSE +1 -1
  4. data/README.md +28 -26
  5. data/bin/pry +3 -7
  6. data/lib/pry.rb +3 -2
  7. data/lib/pry/basic_object.rb +6 -0
  8. data/lib/pry/cli.rb +39 -34
  9. data/lib/pry/code.rb +6 -1
  10. data/lib/pry/code/code_file.rb +8 -2
  11. data/lib/pry/code_object.rb +23 -0
  12. data/lib/pry/color_printer.rb +11 -8
  13. data/lib/pry/command.rb +40 -16
  14. data/lib/pry/command_set.rb +9 -2
  15. data/lib/pry/commands/cat/exception_formatter.rb +11 -10
  16. data/lib/pry/commands/cat/file_formatter.rb +7 -3
  17. data/lib/pry/commands/code_collector.rb +16 -14
  18. data/lib/pry/commands/easter_eggs.rb +9 -9
  19. data/lib/pry/commands/edit.rb +6 -2
  20. data/lib/pry/commands/edit/file_and_line_locator.rb +1 -1
  21. data/lib/pry/commands/find_method.rb +1 -1
  22. data/lib/pry/commands/gem_open.rb +1 -1
  23. data/lib/pry/commands/gem_readme.rb +25 -0
  24. data/lib/pry/commands/gem_search.rb +40 -0
  25. data/lib/pry/commands/hist.rb +2 -2
  26. data/lib/pry/commands/jump_to.rb +7 -7
  27. data/lib/pry/commands/ls/formatter.rb +1 -0
  28. data/lib/pry/commands/ls/jruby_hacks.rb +2 -2
  29. data/lib/pry/commands/ls/self_methods.rb +2 -0
  30. data/lib/pry/commands/play.rb +2 -2
  31. data/lib/pry/commands/reload_code.rb +2 -2
  32. data/lib/pry/commands/ri.rb +4 -0
  33. data/lib/pry/commands/shell_command.rb +34 -8
  34. data/lib/pry/commands/show_info.rb +10 -2
  35. data/lib/pry/commands/watch_expression/expression.rb +1 -1
  36. data/lib/pry/commands/whereami.rb +6 -6
  37. data/lib/pry/config.rb +3 -16
  38. data/lib/pry/config/behavior.rb +139 -49
  39. data/lib/pry/config/default.rb +21 -33
  40. data/lib/pry/config/lazy.rb +25 -0
  41. data/lib/pry/editor.rb +1 -1
  42. data/lib/pry/exceptions.rb +1 -1
  43. data/lib/pry/helpers/base_helpers.rb +6 -10
  44. data/lib/pry/helpers/documentation_helpers.rb +1 -0
  45. data/lib/pry/helpers/options_helpers.rb +1 -1
  46. data/lib/pry/helpers/text.rb +69 -76
  47. data/lib/pry/history.rb +22 -1
  48. data/lib/pry/history_array.rb +1 -1
  49. data/lib/pry/hooks.rb +48 -107
  50. data/lib/pry/indent.rb +6 -2
  51. data/lib/pry/input_completer.rb +118 -118
  52. data/lib/pry/method.rb +13 -13
  53. data/lib/pry/method/disowned.rb +1 -0
  54. data/lib/pry/method/patcher.rb +0 -3
  55. data/lib/pry/output.rb +37 -38
  56. data/lib/pry/pager.rb +11 -8
  57. data/lib/pry/plugins.rb +20 -5
  58. data/lib/pry/pry_class.rb +29 -3
  59. data/lib/pry/pry_instance.rb +8 -6
  60. data/lib/pry/repl.rb +37 -5
  61. data/lib/pry/repl_file_loader.rb +1 -1
  62. data/lib/pry/rubygem.rb +3 -1
  63. data/lib/pry/slop.rb +661 -0
  64. data/lib/pry/slop/LICENSE +20 -0
  65. data/lib/pry/slop/commands.rb +196 -0
  66. data/lib/pry/slop/option.rb +208 -0
  67. data/lib/pry/terminal.rb +16 -5
  68. data/lib/pry/test/helper.rb +11 -2
  69. data/lib/pry/version.rb +1 -1
  70. data/lib/pry/wrapped_module.rb +5 -5
  71. data/lib/pry/{module_candidate.rb → wrapped_module/candidate.rb} +2 -4
  72. metadata +14 -20
@@ -7,7 +7,7 @@ class Pry
7
7
  end
8
8
 
9
9
  def from_code_object(code_object, filename_argument)
10
- if File.exists?(code_object.source_file.to_s)
10
+ if File.exist?(code_object.source_file.to_s)
11
11
  [code_object.source_file, code_object.source_line]
12
12
  else
13
13
  raise CommandError, "Cannot find a file for #{filename_argument}!"
@@ -20,7 +20,7 @@ class Pry
20
20
  find-method re Pry
21
21
 
22
22
  # Find all methods that contain the code:
23
- # output.puts inside the Pry namepsace.
23
+ # output.puts inside the Pry namespace.
24
24
  find-method -c 'output.puts' Pry
25
25
  BANNER
26
26
 
@@ -16,7 +16,7 @@ class Pry
16
16
 
17
17
  def process(gem)
18
18
  Dir.chdir(Rubygem.spec(gem).full_gem_path) do
19
- Pry::Editor.invoke_editor(".", 0, false)
19
+ Pry::Editor.new(_pry_).invoke_editor(".", 0, false)
20
20
  end
21
21
  end
22
22
 
@@ -0,0 +1,25 @@
1
+ class Pry::Command::GemReadme < Pry::ClassCommand
2
+ match 'gem-readme'
3
+ description 'Show the readme bundled with a rubygem'
4
+ group 'Gems'
5
+ command_options argument_required: true
6
+ banner <<-BANNER
7
+ gem-readme gem
8
+ Show the readme bundled with a rubygem
9
+ BANNER
10
+
11
+ def process(name)
12
+ spec = Gem::Specification.find_by_name(name)
13
+ glob = File.join(spec.full_gem_path, 'README*')
14
+ readme = Dir[glob][0]
15
+ if File.exist?(readme.to_s)
16
+ _pry_.pager.page File.read(readme)
17
+ else
18
+ raise Pry::CommandError, "Gem '#{name}' doesn't appear to have a README"
19
+ end
20
+ rescue Gem::LoadError
21
+ raise Pry::CommandError, "Gem '#{name}' wasn't found. Are you sure it is installed?"
22
+ end
23
+
24
+ Pry::Commands.add_command(self)
25
+ end
@@ -0,0 +1,40 @@
1
+ class Pry::Command::GemSearch < Pry::ClassCommand
2
+ match 'gem-search'
3
+ description 'Search for a gem with the rubygems.org JSON API'
4
+ group 'Gems'
5
+ command_options argument_required: true
6
+ banner <<-BANNER
7
+ gem-search [options] gem
8
+ Search for a gem with the rubygems.org HTTP API
9
+ BANNER
10
+
11
+ API_ENDPOINT = 'https://rubygems.org/api/v1/search.json'
12
+
13
+ def setup
14
+ require 'json' unless defined?(JSON)
15
+ require 'net/http' unless defined?(Net::HTTP)
16
+ end
17
+
18
+ def options(opt)
19
+ opt.on :l, :limit, 'Limit the number of results (max: 30)',
20
+ default: 10,
21
+ as: Integer,
22
+ argument: true
23
+ end
24
+
25
+ def process(str)
26
+ uri = URI.parse(API_ENDPOINT)
27
+ uri.query = URI.encode_www_form(query: str)
28
+ gems = JSON.load Net::HTTP.get(uri)
29
+ _pry_.pager.page list_as_string(gems, opts[:limit])
30
+ end
31
+
32
+ private
33
+ def list_as_string(gems, limit = 10)
34
+ gems[0..limit-1].map do |gem|
35
+ name, version, info = gem.values_at 'name', 'version', 'info'
36
+ "#{text.bold(name)} #{text.bold('v'+version)} \n#{info}\n\n"
37
+ end.join
38
+ end
39
+ Pry::Commands.add_command(self)
40
+ end
@@ -170,8 +170,8 @@ class Pry
170
170
  else
171
171
  Pry.history.to_a.last(Pry.history.session_line_count)
172
172
  end
173
- # The last value in history will be the 'hist' command itself.
174
- Pry::Code(h[0..-2])
173
+
174
+ Pry::Code(Pry.history.filter(h[0..-2]))
175
175
  end
176
176
  end
177
177
 
@@ -9,18 +9,18 @@ class Pry
9
9
  BANNER
10
10
 
11
11
  def process(break_level)
12
- break_level = break_level.to_i
13
- nesting_level = _pry_.binding_stack.size - 1
12
+ break_level = break_level.to_i
13
+ nesting_level = _pry_.binding_stack.size - 1
14
+ max_nest_level = nesting_level - 1
14
15
 
15
16
  case break_level
16
17
  when nesting_level
17
18
  output.puts "Already at nesting level #{nesting_level}"
18
- when (0...nesting_level)
19
- _pry_.binding_stack.slice!(break_level + 1, _pry_.binding_stack.size)
20
-
19
+ when 0..max_nest_level
20
+ _pry_.binding_stack = _pry_.binding_stack[0..break_level]
21
21
  else
22
- max_nest_level = nesting_level - 1
23
- output.puts "Invalid nest level. Must be between 0 and #{max_nest_level}. Got #{break_level}."
22
+ output.puts "Invalid nest level. Must be between 0 and " \
23
+ "#{max_nest_level}. Got #{break_level}."
24
24
  end
25
25
  end
26
26
  end
@@ -7,6 +7,7 @@ class Pry
7
7
  def initialize(_pry_)
8
8
  @_pry_ = _pry_
9
9
  @target = _pry_.current_context
10
+ @default_switch = nil
10
11
  end
11
12
 
12
13
  def write_out
@@ -19,7 +19,7 @@ module Pry::Command::Ls::JRubyHacks
19
19
  m.name.sub(/\A(is|get|set)(?=[A-Z_])/, '').gsub(/[_?=]/, '').downcase
20
20
  end
21
21
 
22
- grouped.map do |key, values|
22
+ grouped.flat_map do |key, values|
23
23
  values = values.sort_by do |m|
24
24
  rubbishness(m.name)
25
25
  end
@@ -28,7 +28,7 @@ module Pry::Command::Ls::JRubyHacks
28
28
  values.select do |x|
29
29
  (!found.any? { |y| x == y }) && found << x
30
30
  end
31
- end.flatten(1)
31
+ end
32
32
  end
33
33
 
34
34
  # When removing jruby aliases, we want to keep the alias that is
@@ -11,6 +11,8 @@ class Pry
11
11
  super(_pry_)
12
12
  @interrogatee = interrogatee
13
13
  @no_user_opts = no_user_opts
14
+ @ppp_switch = opts[:ppp]
15
+ @jruby_switch = opts['all-java']
14
16
  end
15
17
 
16
18
  def output_self
@@ -12,7 +12,7 @@ class Pry
12
12
 
13
13
  play --lines 149..153 # assumes current context
14
14
  play -i 20 --lines 1..3 # assumes lines of the input expression at 20
15
- play -o 4 # the output of of an expression at 4
15
+ play -o 4 # the output of an expression at 4
16
16
  play Pry#repl -l 1..-1 # play the contents of Pry#repl method
17
17
  play -e 2 # play from specified line until end of valid expression
18
18
  play hello.rb # play a file
@@ -91,7 +91,7 @@ class Pry
91
91
  end
92
92
 
93
93
  def file_content
94
- if default_file && File.exists?(default_file)
94
+ if default_file && File.exist?(default_file)
95
95
  @cc.restrict_to_lines(File.read(default_file), @cc.line_range)
96
96
  else
97
97
  raise CommandError, "File does not exist! File was: #{default_file.inspect}"
@@ -31,7 +31,7 @@ class Pry
31
31
  end
32
32
 
33
33
  def reload_current_file
34
- if !File.exists?(current_file)
34
+ if !File.exist?(current_file)
35
35
  raise CommandError, "Current file: #{current_file} cannot be found on disk!"
36
36
  end
37
37
 
@@ -49,7 +49,7 @@ class Pry
49
49
  def check_for_reloadability(code_object, identifier)
50
50
  if !code_object || !code_object.source_file
51
51
  raise CommandError, "Cannot locate #{identifier}!"
52
- elsif !File.exists?(code_object.source_file)
52
+ elsif !File.exist?(code_object.source_file)
53
53
  raise CommandError,
54
54
  "Cannot reload #{identifier} as it has no associated file on disk. " \
55
55
  "File found was: #{code_object.source_file}"
@@ -14,6 +14,10 @@ class Pry
14
14
  BANNER
15
15
 
16
16
  def process(spec)
17
+ unless spec
18
+ return output.puts "Please provide a class, module, or method name (e.g: ri Array#push)"
19
+ end
20
+
17
21
  # Lazily load RI
18
22
  require 'rdoc/ri/driver'
19
23
 
@@ -4,7 +4,7 @@ class Pry
4
4
  group 'Input and Output'
5
5
  description "All text following a '.' is forwarded to the shell."
6
6
  command_options :listing => '.<shell command>', :use_prefix => false,
7
- :takes_block => true
7
+ :takes_block => true
8
8
 
9
9
  banner <<-'BANNER'
10
10
  Usage: .COMMAND_NAME
@@ -30,18 +30,44 @@ class Pry
30
30
 
31
31
  private
32
32
 
33
- def parse_destination(dest)
34
- return "~" if dest.empty?
35
- return dest unless dest == "-"
36
- state.old_pwd || raise(CommandError, "No prior directory available")
37
- end
33
+ def parse_destination(dest)
34
+ return "~" if dest.empty?
35
+ return dest unless dest == "-"
36
+ state.old_pwd || raise(CommandError, "No prior directory available")
37
+ end
38
38
 
39
- def process_cd(dest)
39
+ def process_cd(dest)
40
+ begin
40
41
  state.old_pwd = Dir.pwd
41
- Dir.chdir File.expand_path(dest)
42
+ Dir.chdir(File.expand_path(path_from_cd_path(dest) || dest))
42
43
  rescue Errno::ENOENT
43
44
  raise CommandError, "No such directory: #{dest}"
44
45
  end
46
+ end
47
+
48
+ def cd_path_env
49
+ ENV['CDPATH']
50
+ end
51
+
52
+ def cd_path_exists?
53
+ cd_path_env && cd_path_env.length.nonzero?
54
+ end
55
+
56
+ def path_from_cd_path(dest)
57
+ return if !(dest && cd_path_exists?) || special_case_path?(dest)
58
+
59
+ cd_path_env.split(File::PATH_SEPARATOR).each do |path|
60
+ if File.directory?(path) && path.split(File::SEPARATOR).last == dest
61
+ return path
62
+ end
63
+ end
64
+
65
+ return nil
66
+ end
67
+
68
+ def special_case_path?(dest)
69
+ ['.', '..', '-'].include?(dest) || dest =~ /\A[#{File::PATH_SEPARATOR}~]/
70
+ end
45
71
  end
46
72
 
47
73
  Pry::Commands.add_command(Pry::Command::ShellCommand)
@@ -22,7 +22,15 @@ class Pry
22
22
  raise CommandError, no_definition_message if !code_object
23
23
  @original_code_object = code_object
24
24
 
25
- if show_all_modules?(code_object)
25
+ if !obj_name && code_object.c_module? && !opts[:all]
26
+ result = "Warning: You're inside an object, whose class is defined by means\n" +
27
+ " of the C Ruby API. Pry cannot display the information for\n" +
28
+ " this class."
29
+ if code_object.candidates.any?
30
+ result += "\n However, you can view monkey-patches applied to this class.\n" +
31
+ " Just execute the same command with the '--all' switch."
32
+ end
33
+ elsif show_all_modules?(code_object)
26
34
  # show all monkey patches for a module
27
35
 
28
36
  result = content_and_headers_for_all_module_candidates(code_object)
@@ -85,7 +93,7 @@ class Pry
85
93
  end
86
94
 
87
95
  def no_definition_message
88
- "Couldn't locate a definition for #{obj_name}!"
96
+ "Couldn't locate a definition for #{obj_name}"
89
97
  end
90
98
 
91
99
  # Generate a header (meta-data information) for all the code
@@ -10,7 +10,7 @@ class Pry
10
10
  end
11
11
 
12
12
  def eval!
13
- @previous_value = @value
13
+ @previous_value = value
14
14
  @value = Pry::ColorPrinter.pp(target_eval(target, source), "")
15
15
  end
16
16
 
@@ -159,12 +159,12 @@ class Pry
159
159
  end
160
160
 
161
161
  def class_code
162
- return @class_code if @class_code
163
-
164
- mod = @method ? Pry::WrappedModule(@method.owner) : target_class
165
-
166
- idx = mod.candidates.find_index { |v| expand_path(v.source_file) == @file }
167
- @class_code = idx && Pry::Code.from_module(mod, idx)
162
+ @class_code ||=
163
+ begin
164
+ mod = @method ? Pry::WrappedModule(@method.owner) : target_class
165
+ idx = mod.candidates.find_index { |v| expand_path(v.source_file) == @file }
166
+ idx && Pry::Code.from_module(mod, idx)
167
+ end
168
168
  end
169
169
 
170
170
  def valid_method?
@@ -1,24 +1,11 @@
1
- class Pry::Config
1
+ require_relative 'basic_object'
2
+ class Pry::Config < Pry::BasicObject
2
3
  require_relative 'config/behavior'
4
+ require_relative 'config/lazy'
3
5
  require_relative 'config/default'
4
6
  require_relative 'config/convenience'
5
7
  include Pry::Config::Behavior
6
-
7
8
  def self.shortcuts
8
9
  Convenience::SHORTCUTS
9
10
  end
10
-
11
- #
12
- # FIXME
13
- # @param [Pry::Hooks] hooks
14
- #
15
- def hooks=(hooks)
16
- if hooks.is_a?(Hash)
17
- warn "Hash-based hooks are now deprecated! Use a `Pry::Hooks` object " \
18
- "instead! http://rubydoc.info/github/pry/pry/master/Pry/Hooks"
19
- self["hooks"] = Pry::Hooks.from_hash(hooks)
20
- else
21
- self["hooks"] = hooks
22
- end
23
- end
24
11
  end
@@ -2,130 +2,200 @@ module Pry::Config::Behavior
2
2
  ASSIGNMENT = "=".freeze
3
3
  NODUP = [TrueClass, FalseClass, NilClass, Symbol, Numeric, Module, Proc].freeze
4
4
  INSPECT_REGEXP = /#{Regexp.escape "default=#<"}/
5
+ ReservedKeyError = Class.new(RuntimeError)
5
6
 
6
7
  module Builder
7
- def from_hash(hash, default = nil)
8
+ #
9
+ # @param [Hash] attributes
10
+ # a hash to initialize an instance of self with.
11
+ #
12
+ # @param [Pry::Config, nil] default
13
+ # a default, or nil for none.
14
+ #
15
+ # @return [Pry::Config]
16
+ # returns an instance of self.
17
+ #
18
+ def from_hash(attributes, default = nil)
8
19
  new(default).tap do |config|
9
- config.merge!(hash)
20
+ attributes.each do |key,value|
21
+ config[key] = Hash === value ? from_hash(value, nil) : value
22
+ end
10
23
  end
11
24
  end
12
25
  end
13
26
 
14
27
  def self.included(klass)
15
- unless defined?(RESERVED_KEYS)
16
- const_set :RESERVED_KEYS, instance_methods(false).map(&:to_s).freeze
17
- end
18
28
  klass.extend(Builder)
19
29
  end
20
30
 
21
31
  def initialize(default = Pry.config)
22
32
  @default = default
23
33
  @lookup = {}
34
+ @reserved_keys = methods.map(&:to_s).freeze
24
35
  end
25
36
 
26
37
  #
27
38
  # @return [Pry::Config::Behavior]
28
- # returns the default used if a matching value for a key isn't found in self
39
+ # returns the default used incase a key isn't found in self.
29
40
  #
30
41
  def default
31
42
  @default
32
43
  end
33
44
 
45
+ #
46
+ # @param [String] key
47
+ # a key (as a String)
48
+ #
49
+ # @return [Object, BasicObject]
50
+ # returns an object from self or one of its defaults.
51
+ #
34
52
  def [](key)
35
- @lookup[key.to_s]
53
+ key = key.to_s
54
+ @lookup[key] or (@default and @default[key])
36
55
  end
37
56
 
57
+ #
58
+ # Add a key and value pair to self.
59
+ #
60
+ # @param [String] key
61
+ # a key (as a String).
62
+ #
63
+ # @param [Object,BasicObject] value
64
+ # a value.
65
+ #
66
+ # @raise [Pry::Config::ReservedKeyError]
67
+ # when 'key' is a reserved key name.
68
+ #
38
69
  def []=(key, value)
39
70
  key = key.to_s
40
- if RESERVED_KEYS.include?(key)
41
- raise ArgumentError, "few things are reserved by pry, but using '#{key}' as a configuration key is."
71
+ if @reserved_keys.include?(key)
72
+ raise ReservedKeyError, "It is not possible to use '#{key}' as a key name, please choose a different key name."
42
73
  end
43
- @lookup[key] = value
74
+ __push(key,value)
44
75
  end
45
76
 
46
- def method_missing(name, *args, &block)
47
- key = name.to_s
48
- if key[-1] == ASSIGNMENT
49
- short_key = key[0..-2]
50
- self[short_key] = args[0]
51
- elsif key?(key)
52
- self[key]
53
- elsif @default.respond_to?(name)
54
- value = @default.public_send(name, *args, &block)
55
- # FIXME: refactor Pry::Hook so that it stores config on the config object,
56
- # so that we can use the normal strategy.
57
- self[key] = value = value.dup if key == 'hooks'
58
- value
59
- else
60
- nil
61
- end
77
+ #
78
+ # Removes a key from self.
79
+ #
80
+ # @param [String] key
81
+ # a key (as a String)
82
+ #
83
+ # @return [void]
84
+ #
85
+ def forget(key)
86
+ key = key.to_s
87
+ __remove(key)
62
88
  end
63
89
 
90
+ #
91
+ # @param [Hash, #to_h, #to_hash] other
92
+ # a hash to merge into self.
93
+ #
94
+ # @return [void]
95
+ #
64
96
  def merge!(other)
65
- other = try_convert_to_hash(other)
97
+ other = __try_convert_to_hash(other)
66
98
  raise TypeError, "unable to convert argument into a Hash" unless other
67
99
  other.each do |key, value|
68
100
  self[key] = value
69
101
  end
70
102
  end
71
103
 
104
+ #
105
+ # @param [Hash, #to_h, #to_hash] other
106
+ # a hash to compare against the lookup table of self.
107
+ #
72
108
  def ==(other)
73
- @lookup == try_convert_to_hash(other)
109
+ @lookup == __try_convert_to_hash(other)
74
110
  end
75
111
  alias_method :eql?, :==
76
112
 
77
- def respond_to_missing?(key, include_private=false)
78
- key?(key) or @default.respond_to?(key) or super(key, include_private)
79
- end
80
-
113
+ #
114
+ # @param [String] key
115
+ # a key (as a String)
116
+ #
117
+ # @return [Boolean]
118
+ # returns true when "key" is a member of self.
119
+ #
81
120
  def key?(key)
82
121
  key = key.to_s
83
122
  @lookup.key?(key)
84
123
  end
85
124
 
125
+ #
126
+ # Clear the lookup table of self.
127
+ #
128
+ # @return [void]
129
+ #
86
130
  def clear
87
131
  @lookup.clear
88
132
  true
89
133
  end
90
- alias_method :refresh, :clear
91
-
92
- def forget(key)
93
- @lookup.delete(key.to_s)
94
- end
95
134
 
135
+ #
136
+ # @return [Array<String>]
137
+ # returns an array of keys in self.
138
+ #
96
139
  def keys
97
140
  @lookup.keys
98
141
  end
99
142
 
143
+ def eager_load!
144
+ local_last_default = last_default
145
+ local_last_default.lazy_keys.each do |key|
146
+ self[key] = local_last_default.public_send(key)
147
+ end
148
+ end
149
+
150
+ def last_default
151
+ last = @default
152
+ last = last.default while last and last.default
153
+ last
154
+ end
155
+
156
+ #
157
+ # @return [Hash]
158
+ # returns a duplicate copy of the lookup table used by self.
159
+ #
100
160
  def to_hash
101
161
  @lookup.dup
102
162
  end
103
163
  alias_method :to_h, :to_hash
104
164
 
105
-
106
165
  def inspect
107
166
  key_str = keys.map { |key| "'#{key}'" }.join(",")
108
- "#<#{_clip_inspect(self)} local_keys=[#{key_str}] default=#{@default.inspect}>"
167
+ "#<#{__clip_inspect(self)} keys=[#{key_str}] default=#{@default.inspect}>"
109
168
  end
110
169
 
111
170
  def pretty_print(q)
112
171
  q.text inspect[1..-1].gsub(INSPECT_REGEXP, "default=<")
113
172
  end
114
173
 
115
- private
116
- def _clip_inspect(obj)
117
- "#{obj.class}:0x%x" % obj.object_id << 1
118
- end
119
-
120
- def _dup(value)
121
- if NODUP.any? { |klass| klass === value }
122
- value
174
+ def method_missing(name, *args, &block)
175
+ key = name.to_s
176
+ if key[-1] == ASSIGNMENT
177
+ short_key = key[0..-2]
178
+ self[short_key] = args[0]
179
+ elsif key?(key)
180
+ self[key]
181
+ elsif @default.respond_to?(name)
182
+ value = @default.public_send(name, *args, &block)
183
+ self[key] = __dup(value)
123
184
  else
124
- value.dup
185
+ nil
125
186
  end
126
187
  end
127
188
 
128
- def try_convert_to_hash(obj)
189
+ def respond_to_missing?(key, include_private=false)
190
+ key?(key) or @default.respond_to?(key) or super(key, include_private)
191
+ end
192
+
193
+ private
194
+ def __clip_inspect(obj)
195
+ "#{obj.class}:0x%x" % obj.object_id
196
+ end
197
+
198
+ def __try_convert_to_hash(obj)
129
199
  if Hash === obj
130
200
  obj
131
201
  elsif obj.respond_to?(:to_h)
@@ -136,4 +206,24 @@ private
136
206
  nil
137
207
  end
138
208
  end
209
+
210
+ def __dup(value)
211
+ if NODUP.any? { |klass| klass === value }
212
+ value
213
+ else
214
+ value.dup
215
+ end
216
+ end
217
+
218
+ def __push(key,value)
219
+ unless singleton_class.method_defined? key
220
+ define_singleton_method(key) { self[key] }
221
+ define_singleton_method("#{key}=") { |val| @lookup[key] = val }
222
+ end
223
+ @lookup[key] = value
224
+ end
225
+
226
+ def __remove(key)
227
+ @lookup.delete(key)
228
+ end
139
229
  end