Advertisement
Guest User

Untitled

a guest
Sep 30th, 2016
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.55 KB | None | 0 0
  1. # XKeys - Extended keys to facilitate fetching and storing in nested
  2. # hash- and array-like structures with Perl-ish auto-vivification.
  3. #
  4. # Synopsis:
  5. # root = {}.extend XKeys::Hash
  6. # root[:my, :list, :[]] = 'value 1'
  7. # root[:my, :list, :[]] = 'value 2'
  8. # root[:sparse, 10] = 'value 3'
  9. # # => { :my => { :list => [ 'value 1', 'value 2' ] },
  10. # # :sparse => { 10 => 'value 3' } }
  11. # root[:missing] # => nil
  12. # root[:missing, :else => false] # => false
  13. # root[:missing, :raise => true] # => raises KeyError
  14. #
  15. # root = [].extend XKeys::Auto
  16. # root[1, :[]] = 'value 1'
  17. # root[1, 3] = 'value 2'
  18. # # => [ nil, [ 'value 1', nil, nil, 'value 2' ] ]
  19. # root[0, 1] # => [ nil ] (slice of length 1 at 0)
  20. # root[1, 0, {}] # => 'value 1'
  21. # root[1, 4, {}] # => nil
  22. #
  23. # As of version 2.0.0, any underlying type implementing #[], #[]= (if
  24. # setting), #fetch, and #push (if using push mode) is supported. (See the
  25. # Array documentation.)
  26. #
  27. # As of version 2.1.0, #[] is used if #fetch is not supported. Missing-key
  28. # detection (still) depends on KeyError or IndexError being raised.
  29. #
  30. # Version 2.2.0 2014-05-07
  31. #
  32. # @author Brian Katzung <briank@kappacs.com>, Kappa Computer Solutions, LLC
  33. # @copyright 2013-2014 Brian Katzung and Kappa Computer Solutions, LLC
  34. # @license MIT License
  35.  
  36. module XKeys; end
  37.  
  38. # Extended fetch and get ([])
  39. module XKeys::Get
  40.  
  41. # Perform an extended fetch using successive keys to traverse a tree
  42. # of nested hash- and/or array-like objects.
  43. #
  44. # xfetch(key1, ..., keyN [, option_hash])
  45. #
  46. # Options:
  47. #
  48. # :else => default value
  49. # The default value to return if any of the keys do not exist
  50. # (when an underlying #fetch generates a KeyError or IndexError).
  51. # The :raise option takes precedence.
  52. #
  53. # :raise => true
  54. # Re-raise the original KeyError or IndexError if any of the keys
  55. # do not exist. This is the default behavior for xfetch in the
  56. # absence of an :else option.
  57. #
  58. # :raise => *parameters
  59. # Like :raise => true but does raise *parameters instead, e.g.
  60. # :raise => RuntimeError or :raise => [RuntimeError, 'SNAFU']
  61. def xfetch (*args)
  62. if args[-1].is_a?(Hash) then options, last = args[-1], -2
  63. else options, last = {}, -1
  64. end
  65.  
  66. args[0..last].inject(self) do |node, key|
  67. begin node.respond_to?(:fetch) ? node.fetch(key) : node[key]
  68. rescue KeyError, IndexError
  69. if options[:raise] && options[:raise] != true
  70. raise *options[:raise]
  71. elsif options[:raise] || !options.has_key?(:else)
  72. raise
  73. else return options[:else]
  74. end
  75. end
  76. end
  77. end
  78.  
  79. # Perform an extended get using successive keys to traverse a tree of
  80. # nested hashes and/or arrays.
  81. #
  82. # [key] or [range] returns the normal hash or array element (or
  83. # range-based array slice).
  84. #
  85. # [int1, int2] for arrays (or other objects responding to the #slice
  86. # method) returns the object's normal two-parameter (e.g. start + length
  87. # slice) index value.
  88. #
  89. # [key1, ..., keyN[, option_hash]] traverses a tree of nested
  90. # hash- and/or array-like objects using xfetch.
  91. #
  92. # Option :else => nil is used if no :else option is supplied.
  93. # See xfetch for option details.
  94. def [] (*args)
  95. if args.count == 1 || (respond_to?(:slice) && args.count == 2 &&
  96. args[0].is_a?(Integer) && args[1].is_a?(Integer))
  97. # [key] or array[start, length]
  98. super *args
  99. else
  100. def_opts = { :else => nil } # Default options
  101. if args[-1].is_a? Hash
  102. options, last = def_opts.merge(args[-1]), -2
  103. else options, last = def_opts, -1
  104. end
  105. xfetch *args[0..last], options
  106. end
  107. end
  108.  
  109. end
  110.  
  111. # "Private" module for XKeys::Set_* common code
  112. module XKeys::Set_
  113.  
  114. # Common code for XKeys::Set_Hash and XKeys::Set_Auto. This method
  115. # returns true if it is handling the set, or false if the caller
  116. # should super to handle the set.
  117. #
  118. # _xkeys_set(key1, ..., keyN[, option_hash], value) { |key, options| block }
  119. #
  120. # If the root of the tree responds to the #xkeys_new method, it will
  121. # be called as follows whenever a new node needs to be created:
  122. #
  123. # xkeys_new(key2, info_hash, option_hash)
  124. #
  125. # where info_hash contains
  126. #
  127. # :node => The current node
  128. # :key1 => The key in the current node, or :[]
  129. # :block => The block passed to _xkeys_set
  130. #
  131. # The returned new node will be assigned to node[key1] (or pushed onto
  132. # the end of the array) and should be appropriate to accept key2.
  133. #
  134. # Otherwise, the block should return true for array-like keys or false
  135. # for hash-like keys. An array or hash node will be added accordingly.
  136. #
  137. # If a key is :[], the current node responds to the #push method, and
  138. # push mode has not been disabled (see below), a new node will be
  139. # pushed onto the end of the current node.
  140. #
  141. # If the root of the tree responds to the #xkeys_on_final method, it
  142. # will be called as follows before the (final leaf) assignment:
  143. #
  144. # xkeys_on_final(leaf_node, key, value, option_hash)
  145. #
  146. # Options:
  147. #
  148. # :[] => false
  149. # Disable :[] push mode
  150. def _xkeys_set (*args, &block)
  151. if args[-2].is_a?(Hash) then options, last = args[-2], -3
  152. else options, last = {}, -2
  153. end
  154.  
  155. push_mode = options[:[]] != false
  156.  
  157. if args.count + last == 0
  158. xkeys_on_final self, args[0], args[-1], options if
  159. respond_to? :xkeys_on_final
  160. if args[0] == :[] && push_mode && respond_to?(:push)
  161. push args[-1] # array[:[]] = value
  162. true # done--don't caller-super
  163. else false # use caller-super to do it
  164. end
  165. else
  166. # root[key1, ..., keyN[, option_hash]] = value
  167. (node, key) = args[1..last].inject([self, args[0]]) do |nk1, k2|
  168. if nk1[1] == :[] && push_mode && nk1[0].respond_to?(:push)
  169. # Push a new node onto an array-like node
  170. node = _xkeys_new(k2, { :node => nk1[0],
  171. :key1 => nk1[1], :block => block }, options)
  172. nk1[0].push node
  173. [node, k2]
  174. elsif nk1[0][nk1[1]].nil?
  175. # Auto-vivify the specified key/index
  176. node = _xkeys_new(k2, { :node => nk1[0],
  177. :key1 => nk1[1], :block => block }, options)
  178. nk1[0][nk1[1]] = node
  179. [node, k2]
  180. else
  181. # Traverse an existing node
  182. [nk1[0][nk1[1]], k2]
  183. end
  184. end
  185.  
  186. # Assign (or push) according to the final key.
  187. xkeys_on_final node, args[0], args[-1], options if
  188. respond_to? :xkeys_on_final
  189. if key == :[] && push_mode && node.respond_to?(:push)
  190. node.push args[-1]
  191. else
  192. node[key] = args[-1]
  193. end
  194. true # done--don't caller-super
  195. end
  196. end
  197.  
  198. # Return a new node for node[key1] suitable to hold key2.
  199. # Either key1 or key2 (or both) may be :[].
  200. def _xkeys_new (key2, info, options)
  201. if respond_to? :xkeys_new
  202. # Note: #xkeys_new is responsible for cloning extensions
  203. # as desired or needed.
  204. xkeys_new key2, info, options
  205. else
  206. node = info[:block].call(key2, options) ? [] : {}
  207.  
  208. # Clone XKeys extensions from the root node
  209. node.extend XKeys::Get if is_a? XKeys::Get
  210. node.extend XKeys::Set_Auto if is_a? XKeys::Set_Auto
  211. node.extend XKeys::Set_Hash if is_a? XKeys::Set_Hash
  212.  
  213. node
  214. end
  215. end
  216.  
  217. end
  218.  
  219. # Extended set ([]=) with hash keys
  220. module XKeys::Set_Hash
  221. include XKeys::Set_
  222.  
  223. # Auto-vivify nested hash trees using extended hash key/array index
  224. # assignment syntax. :[] keys create nested arrays as needed. Other
  225. # keys, including integer keys, create nested hashes as needed.
  226. #
  227. # See XKeys::Set_ for additional information.
  228. #
  229. # root[key1, ..., keyN[, option_hash]] = value
  230. def []= (*args)
  231. super args[0], args[-1] unless _xkeys_set(*args) do |key, options|
  232. key == :[] && options[:[]] != false
  233. end
  234. args[-1]
  235. end
  236.  
  237. end
  238.  
  239. # Extended set ([]=) with automatic selection of hash keys or array indexes
  240. module XKeys::Set_Auto
  241. include XKeys::Set_
  242.  
  243. # Auto-vivify nested hash and/or array trees using extended hash
  244. # key/array index assignment syntax. :[] keys and integer keys
  245. # create nested arrays as needed. Other keys create nested hashes
  246. # as needed.
  247. #
  248. # See XKeys::Set_ for additional information.
  249. #
  250. # root[key1, ..., keyN[, option_hash]] = value
  251. def []= (*args)
  252. super args[0], args[-1] unless _xkeys_set(*args) do |key, options|
  253. (key == :[] && options[:[]] != false) || key.is_a?(Integer)
  254. end
  255. args[-1]
  256. end
  257.  
  258. end
  259.  
  260. # Combined interfaces
  261.  
  262. # XKeys::Hash combines XKeys::Get and XKeys::Set_Hash
  263. module XKeys::Hash; include XKeys::Get; include XKeys::Set_Hash; end
  264.  
  265. # XKeys::Auto combines XKeys::Get and XKeys::Set_Auto
  266. module XKeys::Auto; include XKeys::Get; include XKeys::Set_Auto; end
  267.  
  268. # END
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement