pry 0.10.4 → 0.11.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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