File: open_hash.rb

package info (click to toggle)
ruby-hashery 2.1.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 404 kB
  • sloc: ruby: 2,997; makefile: 7
file content (145 lines) | stat: -rw-r--r-- 3,584 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
require 'hashery/crud_hash'

module Hashery

  # OpenHash is a Hash, but also supports open properties much like
  # OpenStruct.
  #
  # Only names that are name methods of Hash can be used as open slots.
  # To open a slot for a name that would otherwise be a method, the 
  # method needs to be made private. The `#open!` method can be used
  # to handle this.
  #
  # Examples
  #
  #     o = OpenHash.new
  #     o.open!(:send)
  #     o.send = 4
  #
  class OpenHash < CRUDHash

    alias :object_class :class

    #FILTER  = /(^__|^\W|^instance_|^object_|^to_)/
    #methods = Hash.instance_methods(true).select{ |m| m !~ FILTER }
    #methods = methods - [:each, :inspect, :send]  # :class, :as]
    #private *methods

    #
    # Initialize new OpenHash instance.
    #
    # TODO: Maybe `safe` should be the first argument?
    #
    def initialize(default=nil, safe=false, &block)
      @safe = safe
      super(*[default].compact, &block)
    end

    #
    # If safe is set to true, then public methods cannot be overriden
    # by hash keys.
    #
    attr_accessor :safe

    #
    # Alias to original store method.
    #
    #alias :store! :store

    #
    # Index `value` to `key`. Unless safe mode, will also open up the 
    # key if it is not already open.
    #
    # key   - Index key to associate with value.
    # value - Value to be associate with key.
    #
    # Returns +value+.
    #
    def store(key, value)
      #open!(key)
      super(key, value)
    end

    #
    # Open up a slot that that would normally be a Hash method.
    #
    # The only methods that can't be opened are ones starting with `__`.
    #
    # methods - [Array<String,Symbol>] method names
    #
    # Returns Array of slot names that were opened.
    #
    def open!(*methods)
      # Only select string and symbols, any other type of key is allowed,
      # it just won't be accessible via dynamic methods.
      methods = methods.select{ |x| String === x || Symbol === x }
      if methods.any?{ |m| m.to_s.start_with?('__') }
        raise ArgumentError, "cannot open shadow methods"
      end
      # only public methods need be made protected
      methods = methods.map{ |x| x.to_sym }
      methods = methods & public_methods(true).map{ |x| x.to_sym }
      if @safe
        raise ArgumentError, "cannot set public method" unless methods.empty?
      else
        (class << self; self; end).class_eval{ protected *methods }
      end
      methods
    end

    # @deprecated
    alias :omit! :open!

    #
    # Is a slot open?
    #
    # method - [String,Symbol] method name
    #
    # Returns `true` or `false`.
    #
    def open?(method)
      methods = public_methods(true).map{ |m| m.to_sym }
      ! methods.include?(method.to_sym)
    end

    #
    # Make specific Hash methods available for use that have previously opened.
    #
    # methods - [Array<String,Symbol>] method names
    #
    # Returns +methods+.
    #
    def close!(*methods)
      (class << self; self; end).class_eval{ public *methods }
      methods
    end

    #
    #
    #
    def method_missing(s,*a, &b)
      type = s.to_s[-1,1]
      name = s.to_s.sub(/[!?=]$/, '')
      key  = name.to_sym

      case type
      when '='
        store(key, a.first)
      when '?'
        key?(key)
      when '!'
        # call an underlying private method
        # TODO: limit this to omitted methods (from included) ?
        __send__(name, *a, &b)
      else
        #if key?(key)
          retrieve(key)
        #else
        #  super(s,*a,&b)
        #end
      end
    end

  end

end