README.md in dense-1.0.0 vs README.md in dense-1.1.0

- old
+ new

@@ -4,9 +4,322 @@ [![Build Status](https://secure.travis-ci.org/floraison/dense.svg)](http://travis-ci.org/floraison/dense) [![Gem Version](https://badge.fury.io/rb/dense.svg)](http://badge.fury.io/rb/dense) Fetching deep in a dense structure. A kind of bastard of [JSONPath](http://goessner.net/articles/JsonPath/). +## usage + +Let +```ruby + data = # taken from http://goessner.net/articles/JsonPath/ + { 'store' => { + 'book' => [ + { 'category' => 'reference', + 'author' => 'Nigel Rees', + 'title' => 'Sayings of the Century', + 'price' => 8.95 + }, + { 'category' => 'fiction', + 'author' => 'Evelyn Waugh', + 'title' => 'Sword of Honour', + 'price' => 12.99 + }, + { 'category' => 'fiction', + 'author' => 'Herman Melville', + 'title' => 'Moby Dick', + 'isbn' => '0-553-21311-3', + 'price' => 8.99 + }, + { 'category' => 'fiction', + 'author' => 'J. R. R. Tolkien', + 'title' => 'The Lord of the Rings', + 'isbn' => '0-395-19395-8', + 'price' => 22.99 + } + ], + 'bicycle' => { + 'color' => 'red', + 'price' => 19.95, + '7' => 'seven' + } + } + } +``` + +### paths + +```ruby +"store.book.1.title" # the title of the second book in the store +"store.book[1].title" # the title of the second book in the store +"store.book.1['french title']" # the french title of the 2nd book +"store.book.1[title,author]" # the title and the author of the 2nd book +"store.book[1,3].title" # the titles of the 2nd and 4th books +"store.book[1:8:2].title" # titles of books at offset 1, 3, 5, 7 +"store.book[::3].title" # titles of books at offset 0, 3, 6, 9, ... +"store.book[:3].title" # titles of books at offset 0, 1, 2, 3 +"store.*.price" # the price of everything directly in the store +"store..price" # the price of everything in the store +# ... +``` + +### `Dense.get(collection, path)` + +```ruby +Dense.get(data, 'store.book.1.title') + # => "Sword of Honour" + +Dense.get(data, 'store.book.*.title') + # => [ + # 'Sayings of the Century', + # 'Sword of Honour', + # 'Moby Dick', + # 'The Lord of the Rings' ] + +Dense.get(data, 'store.bicycle.7') + # => "seven" +``` + +When `Dense.get(collection, path)` doesn't find, it returns `nil`. + +As seen above `Dense.get` might return a single value or an array of values. A "single" path like `"store.book.1.title"` will return a single value, while a "multiple" path like `"store.book.*.title"` or `"store.book[1,2].title"` will return an array of values. + + +### `Dense.has_key?(collection, path)` + +```ruby +Dense.has_key?(data, 'store.book.1.title') + # => true +Dense.has_key?(data, 'store.book.1["social security number"]') + # => false +``` + + +### `Dense.fetch(collection, path)` + +`Dense.fetch` is modelled after `Hash.fetch`. + +```ruby +Dense.fetch(data, 'store.book.1.title') + # => 'Sword of Honour' + +Dense.fetch(data, 'store.book.*.title') + # => [ 'Sayings of the Century', 'Sword of Honour', 'Moby Dick', + # 'The Lord of the Rings' ] + +Dense.fetch(data, 'store.bicycle.7') + # => 'seven' + +Dense.fetch(data, 'store.bicycle[7]') + # => 'seven' +``` + +When it doesn't find, it raises an instance of `KeyError`: + +```ruby +Dense.fetch({}, 'a.0.b') + # raises + # KeyError: Found nothing at "a" ("0.b" remains) +``` + +It might instead raise an instance of `TypeError` if a non-integer key is requested of an array: + +```ruby +Dense.fetch({ 'a' => [] }, 'a.k.b') + # raises + # TypeError: No key "k" for Array at "a" +``` + +See KeyError and TypeError below for more details. + +`Dense.fetch(collection, path)` raises when it doesn't find, while `Dense.get(collection, path)` returns `nil`. + + +### `Dense.fetch(collection, path, default)` + +`Dense.fetch` is modelled after `Hash.fetch` so it features a `default` optional argument. + +If `fetch` doesn't find, it will return the provided default value. + +```ruby +Dense.fetch(data, 'store.book.1.title', -1) + # => "Sword of Honour" (found) +Dense.fetch(data, 'a.0.b', -1) + # => -1 +Dense.fetch(data, 'store.nada', 'x') + # => "x" +Dense.fetch(data, 'store.bicycle.seven', false) + # => false +``` + + +### `Dense.fetch(collection, path) { block }` + +`Dense.fetch` is modelled after `Hash.fetch` so it features a 'default' optional block. + +```ruby +Dense.fetch(data, 'store.book.1.title') do |coll, path| + "len:#{coll.length},path:#{path}" +end + # => "Sword of Honour" (found) + +Dense.fetch(@data, 'store.bicycle.otto') do |coll, path| + "len:#{coll.length},path:#{path}" +end + # => "len:18,path:store.bicycle.otto" (not found) + +not_found = lambda { |coll, path| "not found!" } + # +Dense.fetch(@data, 'store.bicycle.otto', not_found) + # => "not found!" +Dense.fetch(@data, 'store.bicycle.sept', not_found) + # => "not found!" +``` + + +### `Dense.set(collection, path, value)` + +Sets a value "deep" in a collection. Returns the value if successful. + +```ruby +c = {} +r = Dense.set(c, 'a', 1) +c # => { 'a' => 1 } +r # => 1 + +c = { 'h' => {} } +r = Dense.set(c, 'h.i', 1) +c # => { 'h' => { 'i' => 1 } } +r # => 1 + +c = { 'a' => [ 1, 2, 3 ] } +r = Dense.set(c, 'a.1', 1) +c # => { 'a' => [ 1, 1, 3 ] } +r # => 1 + +c = { 'h' => { 'a' => [ 1, 2, 3 ] } } +r = Dense.set(c, 'h.a.first', 'one') +c # => { 'h' => { 'a' => [ "one", 2, 3 ] } } +r # => 'one' + +c = { 'h' => { 'a' => [ 1, 2, 3 ] } } +r = Dense.set(c, 'h.a.last', 'three') +c # => { 'h' => { 'a' => [ 1, 2, 'three' ] } } +r # => 'three' + +c = { 'a' => [] } +Dense.set(c, 'a.b', 1) + # => TypeError: No key "b" for Array at "a" + + +c = { 'a' => {} } +r = Dense.set(c, 'a.1', 1) +c # => { 'a' => { '1' => 1 } } +r # => 1 + +c = {} +Dense.set(c, 'a.0', 1) + # => KeyError: Found nothing at "a" ("0" remains) +``` + +Setting at multiple places in one go is possible: +```ruby +c = { 'h' => {} } +Dense.set(c, 'h[k0,k1,k2]', 123) +c + # => { 'h' => { 'k0' => 123, 'k1' => 123, 'k2' => 123 } } +``` + + +### `Dense.insert(collection, path, value)` + +```ruby +c = { 'a' => [ 0, 1, 2, 3 ] } +r = Dense.insert(c, 'b', 1234) +c + # => { "a" => [ 0, 1, 2, 3 ], "b" => 1234 } + +c = { 'a' => [ 0, 1, 2, 3 ] } +r = Dense.insert(c, 'a.1', 'ONE') +c + # => { "a" => [ 0, "ONE", 1, 2, 3 ] } + +c = { 'a' => [ 0, 1, 2, 3 ], 'a1' => [ 0, 1 ] } +r = Dense.insert(c, '.1', 'ONE') +c + # => { "a" => [ 0, "ONE", 1, 2, 3 ], "a1" => [ 0, "ONE", 1 ] } +``` + + +### `Dense.unset(collection, path)` + +Removes an element deep in a collection. +```ruby +c = { 'a' => 1 } +r = Dense.unset(c, 'a') +c # => {} +r # => 1 + +c = { 'h' => { 'i' => 1 } } +r = Dense.unset(c, 'h.i') +c # => { 'h' => {} } +r # => 1 + +c = { 'a' => [ 1, 2, 3 ] } +r = Dense.unset(c, 'a.1') +c # => { 'a' => [ 1, 3 ] } +r # => 2 + +c = { 'h' => { 'a' => [ 1, 2, 3 ] } } +r = Dense.unset(c, 'h.a.first') +c # => { 'h' => { 'a' => [ 2, 3 ] } } +r # => 1 + +c = { 'h' => { 'a' => [ 1, 2, 3 ] } } +r = Dense.unset(c, 'h.a.last') +c # => { 'h' => { 'a' => [ 1, 2 ] } } +r # => 3 +``` + +It fails with a `KeyError` or a `TypeError` if it cannot unset. +```ruby +Dense.unset({}, 'a') + # => KeyError: Found nothing at "a" +Dense.unset([], 'a') + # => TypeError: No key "a" for Array at root +Dense.unset([], '1') + # => KeyError: Found nothing at "1" +``` + +Unsetting multiple values is OK: + +```ruby +c = { 'h' => { 'a' => [ 1, 2, 3, 4, 5 ] } } +r = Dense.unset(c, 'h.a[2,3]') +c + # => { 'h' => { 'a' => [ 1, 2, 5 ] } } +``` + +### KeyError and TypeError + +Dense might raise instances of `KeyError` and `TypeError`. Those instances have extra `#full_path` and `#miss` methods. + +```ruby +e = + begin + Dense.fetch({}, 'a.b') + rescue => err + err + end + # => #<KeyError: Found nothing at "a" ("b" remains)> +e.full_path + # => "a" +e.miss + # => [false, [], {}, "a", [ "b" ]] +``` + +The "miss" is an array `[ false, path-to-miss, collection-at-miss, key-at-miss, path-post-miss ]`. + + ## LICENSE MIT, see [LICENSE.txt](LICENSE.txt)