1require 'yaml'
2require 'dbm'
3
4module YAML
5
6# YAML + DBM = YDBM
7#
8# YAML::DBM provides the same interface as ::DBM.
9#
10# However, while DBM only allows strings for both keys and values,
11# this library allows one to use most Ruby objects for values
12# by first converting them to YAML. Keys must be strings.
13#
14# Conversion to and from YAML is performed automatically.
15#
16# See the documentation for ::DBM and ::YAML for more information.
17class DBM < ::DBM
18    VERSION = "0.1"
19
20    # Return value associated with +key+ from database.
21    #
22    # Returns +nil+ if there is no such +key+.
23    def []( key )
24        fetch( key )
25    end
26
27    # :call-seq:
28    #   []=( key, value )
29    #
30    # Set +key+ to +value+ in database.
31    #
32    # +value+ will be converted to YAML before storage.
33    def []=( key, val )
34        store( key, val )
35    end
36
37    # :call-seq:
38    #   fetch( key, ifnone = nil )
39    #   fetch( key, &block )
40    #
41    # Return value associated with +key+.
42    #
43    # If there is no value for +key+ and no block is given, returns +ifnone+.
44    #
45    # Otherwise, calls block passing in the given +key+.
46    def fetch( keystr, ifnone = nil )
47        begin
48            val = super( keystr )
49            return YAML.load( val ) if String === val
50        rescue IndexError
51        end
52        if block_given?
53            yield keystr
54        else
55            ifnone
56        end
57    end
58
59    # Deprecated, used YAML::DBM#key instead.
60    # ----
61    # Note:
62    # YAML::DBM#index makes warning from internal of ::DBM#index.
63    # It says 'DBM#index is deprecated; use DBM#key', but DBM#key
64    # behaves not same as DBM#index.
65    #
66    def index( keystr )
67        super( keystr.to_yaml )
68    end
69
70    def key( keystr )
71        invert[keystr]
72    end
73
74    # Returns an array containing the values associated with the given keys.
75    def values_at( *keys )
76        keys.collect { |k| fetch( k ) }
77    end
78
79    # Deletes value from database associated with +key+.
80    #
81    # Returns value or +nil+.
82    def delete( key )
83        v = super( key )
84        if String === v
85            v = YAML.load( v )
86        end
87        v
88    end
89
90    # Calls the given block once for each +key+, +value+ pair in the database.
91    # Deletes all entries for which the block returns true.
92    #
93    # Returns +self+.
94    def delete_if # :yields: [key, value]
95        del_keys = keys.dup
96        del_keys.delete_if { |k| yield( k, fetch( k ) ) == false }
97        del_keys.each { |k| delete( k ) }
98        self
99    end
100
101    # Converts the contents of the database to an in-memory Hash, then calls
102    # Hash#reject with the specified code block, returning a new Hash.
103    def reject
104        hsh = self.to_hash
105        hsh.reject { |k,v| yield k, v }
106    end
107
108    # Calls the given block once for each +key+, +value+ pair in the database.
109    #
110    # Returns +self+.
111    def each_pair # :yields: [key, value]
112        keys.each { |k| yield k, fetch( k ) }
113        self
114    end
115
116    # Calls the given block for each value in database.
117    #
118    # Returns +self+.
119    def each_value # :yields: value
120        super { |v| yield YAML.load( v ) }
121        self
122    end
123
124    # Returns an array of values from the database.
125    def values
126        super.collect { |v| YAML.load( v ) }
127    end
128
129    # Returns true if specified value is found in the database.
130    def has_value?( val )
131        each_value { |v| return true if v == val }
132        return false
133    end
134
135    # Returns a Hash (not a DBM database) created by using each value in the
136    # database as a key, with the corresponding key as its value.
137    #
138    # Note that all values in the hash will be Strings, but the keys will be
139    # actual objects.
140    def invert
141        h = {}
142        keys.each { |k| h[ self.fetch( k ) ] = k }
143        h
144    end
145
146    # Replaces the contents of the database with the contents of the specified
147    # object. Takes any object which implements the each_pair method, including
148    # Hash and DBM objects.
149    def replace( hsh )
150        clear
151        update( hsh )
152    end
153
154    # Removes a [key, value] pair from the database, and returns it.
155    # If the database is empty, returns +nil+.
156    #
157    # The order in which values are removed/returned is not guaranteed.
158    def shift
159        a = super
160        a[1] = YAML.load( a[1] ) if a
161        a
162    end
163
164    # :call-seq:
165    #   select( &block )
166    #   select( *keys )
167    #
168    # If a block is provided, returns a new array containing [key, value] pairs
169    # for which the block returns true.
170    #
171    # Otherwise, same as #values_at
172    def select( *keys )
173        if block_given?
174            self.keys.collect { |k| v = self[k]; [k, v] if yield k, v }.compact
175        else
176            values_at( *keys )
177        end
178    end
179
180    # :call-seq:
181    #   store( key, value )
182    #
183    #Stores +value+ in database with +key+ as the index. +value+ is converted
184    #to YAML before being stored.
185    #
186    #Returns +value+
187    def store( key, val )
188        super( key, val.to_yaml )
189        val
190    end
191
192    # Updates the database with multiple values from the specified object.
193    # Takes any object which implements the each_pair method, including
194    # Hash and DBM objects.
195    #
196    # Returns +self+.
197    def update( hsh )
198        hsh.each_pair do |k,v|
199            self.store( k, v )
200        end
201        self
202    end
203
204    # Converts the contents of the database to an array of [key, value] arrays,
205    # and returns it.
206    def to_a
207        a = []
208        keys.each { |k| a.push [ k, self.fetch( k ) ] }
209        a
210    end
211
212
213    # Converts the contents of the database to an in-memory Hash object, and
214    # returns it.
215    def to_hash
216        h = {}
217        keys.each { |k| h[ k ] = self.fetch( k ) }
218        h
219    end
220
221    alias :each :each_pair
222end
223
224end
225