lib/xkeys.rb in xkeys-0.0.2 vs lib/xkeys.rb in xkeys-1.0.1

- old
+ new

@@ -1,25 +1,26 @@ # XKeys - Extended keys to facilitate fetching and storing in nested # hash and array structures with Perl-ish auto-vivification. # # Synopsis: # root = {}.extend XKeys::Hash -# root[:my, :list, nil] = 'value 1' -# root[:my, :list, nil] = 'value 2' +# root[:my, :list, :[]] = 'value 1' +# root[:my, :list, :[]] = 'value 2' # root[:sparse, 10] = 'value 3' # # => { :my => { :list => [ 'value 1', 'value 2' ] }, # # :sparse => { 10 => 'value 3' } } # root[:missing] # => nil # root[:missing, :else => false] # => false -# root[:missing, {}] # => raises KeyError +# root[:missing, :raise => true] # => raises KeyError # # root = [].extend XKeys::Auto -# root[1, nil] = 'value 1' +# root[1, :[]] = 'value 1' # root[1, 3] = 'value 2' # # => [ nil, [ 'value 1', nil, nil, 'value 2' ] ] # root[0, 1] # => [ nil ] (slice of length 1 at 0) -# root[nil, 1, 0] # => 'value 1' +# root[1, 0, {}] # => 'value 1' +# root[1, 4, {}] # => nil # # @author Brian Katzung <briank@kappacs.com>, Kappa Computer Solutions, LLC # @copyright 2013 Brian Katzung and Kappa Computer Solutions, LLC # @license MIT License @@ -29,25 +30,39 @@ module XKeys::Get # Perform an extended fetch using successive keys to traverse a tree # of nested hashes and/or arrays. # - # xfetch([nil,] key1, ..., keyN [, :else => default_value]) + # xfetch(key1, ..., keyN [, option_hash]) # - # An optional leading nil key is ignored (see []). If the specified - # keys do not exist, the default value is returned (if provided) or - # the standard exception (e.g. KeyError or IndexError) is raised. + # Options: + # + # :else => default value + # The default value to return if the specified keys do not exist. + # The :raise option takes precedence. + # + # :raise => true + # Raise a KeyError or IndexError if the specified keys do not + # exist. This is the default behavior for xfetch in the absence + # of an :else option. + # + # :raise => *parameters + # Like :raise => true but does raise *parameters instead, e.g. + # :raise => RuntimeError or :raise => [RuntimeError, 'SNAFU'] def xfetch (*args) if args[-1].is_a?(Hash) then options, last = args[-1], -2 else options, last = {}, -1 end - first = (args[0] == nil) ? 1 : 0 - args[first..last].inject(self) do |node, key| + args[0..last].inject(self) do |node, key| begin node.fetch key rescue KeyError, IndexError - return options[:else] if options.has_key? :else - raise + if options[:raise] and options[:raise] != true + raise *options[:raise] + elsif options[:raise] or !options.has_key? :else + raise + else return options[:else] + end end end end # Perform an extended get using successive keys to traverse a tree of @@ -55,51 +70,74 @@ # # [key] returns the hash or array element (or range-based array slice) # as normal. # # array[int1, int2] returns a length-based array slice as normal. - # Prepend a nil key and/or append an option hash to force nested index - # behavior for two integer array indexes: array[nil, index1, index2]. + # Append an option hash to force nested index behavior for two + # integer array indexes: array[index1, index2, {}]. # - # [[nil,] key1, ..., keyN[, option_hash]] traverses a tree of nested - # hashes and/or arrays using xfetch. The optional leading nil key is - # always ignored. In the absence of an option hash, the default is - # :else => nil. + # [key1, ..., keyN[, option_hash]] traverses a tree of nested + # hashes and/or arrays using xfetch. + # + # Option :else => nil is used if no :else option is supplied. + # See xfetch for option details. def [] (*args) - if args.count == 1 || (self.is_a?(Array) && args.count == 2 && - args[0].is_a?(Integer) && args[1].is_a?(Integer)) + if args.count == 1 or (self.is_a?(Array) and args.count == 2 and + args[0].is_a?(Integer) and args[1].is_a?(Integer)) # [key] or array[start, length] super *args - elsif args[-1].is_a?(Hash) then xfetch(*args) - else xfetch(*args, :else => nil) + else + def_opts = { :else => nil } # Default options + if args[-1].is_a? Hash + options, last = def_opts.merge(args[-1]), -2 + else options, last = def_opts, -1 + end + xfetch *args[0..last], options end end end # "Private" module for XKeys::Set_* common code module XKeys::Set_ - # Common code for XKeys::Set_Hash and XKeys::Set_Auto + # Common code for XKeys::Set_Hash and XKeys::Set_Auto. This method + # returns true if it is handling the set, or false if super should + # handle the set. + # + # _xset(key1, ..., keyN[, options_hash], value) { |key, options| block } + # + # The block should return true to auto-vivify an array or false to + # auto-vivify a hash. + # + # Options: + # + # :[] => false + # Disable :[] auto-indexing def _xset (*args) - if args.count == 2 - if self.is_a?(Array) && args[0] == nil - self << args[1] # array[nil] = value - else return false # [key] = value ==> super *args + if args[-2].is_a?(Hash) then options, last = args[-2], -3 + else options, last = {}, -2 + end + if args.count + last == 0 + if self.is_a?(Array) && args[0] == :[] + self << args[-1] # array[:[]] = value + else return false # [key] = value ==> super end else - # root[key1, ..., keyN] = value - (node, key) = args[1..-2].inject([self, args[0]]) do |node, key| - if yield key + # root[key1, ..., keyN[, option_hash]] = value + (node, key) = args[1..last].inject([self, args[0]]) do |node, key| + if yield key, options node[0][node[1]] ||= [] - [node[0][node[1]], key || node[0][node[1]].size] + [node[0][node[1]], (key != :[]) ? key : + node[0][node[1]].size] else node[0][node[1]] ||= {} [node[0][node[1]], key] end end - if yield key then node[key || node.size] = args[-1] + if yield key, options + node[(key != :[])? key : node.size] = args[-1] else node[key] = args[-1] end end true end @@ -109,33 +147,36 @@ # Extended set ([]=) with hash keys module XKeys::Set_Hash include XKeys::Set_ # Auto-vivify nested hash trees using extended hash key/array index - # assignment syntax. Nil keys create nested arrays as needed. Other + # assignment syntax. :[] keys create nested arrays as needed. Other # keys, including integer keys, create nested hashes as needed. # - # root[key1, ..., keyN] = value + # root[key1, ..., keyN[, options_hash]] = value def []= (*args) - super *args unless _xset(*args) { |key| key == nil } + super args[0], args[-1] unless _xset(*args) do |key, opts| + key == :[] and opts[:[]] != false + end args[-1] end end # Extended set ([]=) with automatic selection of hash keys or array indexes module XKeys::Set_Auto include XKeys::Set_ # Auto-vivify nested hash and/or array trees using extended hash - # key/array index assignment syntax. Nil keys and integer keys + # key/array index assignment syntax. :[] keys and integer keys # create nested arrays as needed. Other keys create nested hashes # as needed. # - # root[key1, ..., keyN] = value + # root[key1, ..., keyN[, options_hash]] = value def []= (*args) - super *args unless - _xset(*args) { |key| key == nil || key.is_a?(Integer) } + super args[0], args[-1] unless _xset(*args) do |key, opts| + (key == :[] and opts[:[]] != false) or key.is_a?(Integer) + end args[-1] end end