Class: JSI::Ptr
- Inherits:
-
Object
- Object
- JSI::Ptr
- Includes:
- Util::FingerprintHash::Immutable
- Defined in:
- lib/jsi/ptr.rb
Overview
a representation to work with JSON Pointer, as described by RFC 6901 https://tools.ietf.org/html/rfc6901
a pointer is a sequence of tokens pointing to a node in a document.
Defined Under Namespace
Classes: Error, PointerSyntaxError, ResolutionError
Constant Summary collapse
Instance Attribute Summary collapse
-
#tokens ⇒ Object
readonly
Returns the value of attribute tokens.
Class Method Summary collapse
-
.[](*tokens) ⇒ JSI::Ptr
instantiates a pointer from the given tokens.
-
.ary_ptr(ary_ptr) ⇒ JSI::Ptr
instantiates a pointer or returns the given pointer.
-
.from_fragment(fragment) ⇒ JSI::Ptr
parse a URI-escaped fragment and instantiate as a JSI::Ptr.
-
.from_pointer(pointer_string) ⇒ JSI::Ptr
parse a pointer string and instantiate as a JSI::Ptr.
Instance Method Summary collapse
-
#+(ptr) ⇒ JSI::Ptr
a pointer with the tokens of this one plus the given
ptr
's. -
#[](token) ⇒ JSI::Ptr
appends the given token to this pointer's tokens and returns the result.
-
#contains?(other_ptr) ⇒ Boolean
whether this pointer contains the other_ptr - that is, whether this pointer is an ancestor of
other_ptr
, a descendent pointer. -
#empty? ⇒ Boolean
(also: #root?)
whether this pointer is empty, i.e.
-
#evaluate(document, *a, **kw) ⇒ Object
takes a root json document and evaluates this pointer through the document, returning the value pointed to by this pointer.
-
#fragment ⇒ String
the fragment string representation of this pointer.
-
#initialize(tokens) ⇒ Ptr
constructor
initializes a JSI::Ptr from the given tokens.
-
#inspect ⇒ String
(also: #to_s)
a string representation of this pointer.
- #jsi_fingerprint ⇒ Object private
-
#modified_document_copy(document) {|Object| ... } ⇒ Object
takes a document and a block.
-
#parent ⇒ JSI::Ptr
pointer to the parent of where this pointer points.
-
#pointer ⇒ String
the pointer string representation of this pointer.
-
#relative_to(ancestor_ptr) ⇒ JSI::Ptr
part of this pointer relative to the given ancestor_ptr.
-
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first
n
of our tokens. -
#uri ⇒ Addressable::URI
a URI consisting of a fragment containing this pointer's fragment string representation.
Constructor Details
#initialize(tokens) ⇒ Ptr
initializes a JSI::Ptr from the given tokens.
106 107 108 109 110 111 |
# File 'lib/jsi/ptr.rb', line 106 def initialize(tokens) unless tokens.respond_to?(:to_ary) raise(TypeError, "tokens must be an array. got: #{tokens.inspect}") end @tokens = Util.deep_to_frozen(tokens.to_ary, not_implemented: proc { |o| o }) end |
Instance Attribute Details
#tokens ⇒ Object (readonly)
Returns the value of attribute tokens.
113 114 115 |
# File 'lib/jsi/ptr.rb', line 113 def tokens @tokens end |
Class Method Details
.[](*tokens) ⇒ JSI::Ptr
47 48 49 |
# File 'lib/jsi/ptr.rb', line 47 def self.[](*tokens) tokens.empty? ? EMPTY : new(tokens.freeze) end |
.ary_ptr(ary_ptr) ⇒ JSI::Ptr
instantiates a pointer or returns the given pointer
25 26 27 28 29 30 31 |
# File 'lib/jsi/ptr.rb', line 25 def self.ary_ptr(ary_ptr) if ary_ptr.is_a?(Ptr) ary_ptr else new(ary_ptr) end end |
.from_fragment(fragment) ⇒ JSI::Ptr
parse a URI-escaped fragment and instantiate as a JSI::Ptr
JSI::Ptr.from_fragment('/foo/bar')
=> JSI::Ptr["foo", "bar"]
with URI escaping:
JSI::Ptr.from_fragment('/foo%20bar')
=> JSI::Ptr["foo bar"]
Note: A fragment does not include a leading '#'. The string "#/foo" is a URI containing the
fragment "/foo", which should be parsed by Addressable::URI
before passing to this method, e.g.:
JSI::Ptr.from_fragment(Addressable::URI.parse("#/foo").fragment)
=> JSI::Ptr["foo"]
71 72 73 |
# File 'lib/jsi/ptr.rb', line 71 def self.from_fragment(fragment) from_pointer(Addressable::URI.unescape(fragment)) end |
.from_pointer(pointer_string) ⇒ JSI::Ptr
parse a pointer string and instantiate as a JSI::Ptr
JSI::Ptr.from_pointer('/foo')
=> JSI::Ptr["foo"]
JSI::Ptr.from_pointer('/foo~0bar/baz~1qux')
=> JSI::Ptr["foo~bar", "baz/qux"]
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/jsi/ptr.rb', line 86 def self.from_pointer(pointer_string) pointer_string = pointer_string.to_str if pointer_string[0] == ?/ tokens = pointer_string.split('/', -1).map! do |piece| piece.gsub!('~1', '/') piece.gsub!('~0', '~') piece.freeze end tokens.shift new(tokens.freeze) elsif pointer_string.empty? EMPTY else raise(PointerSyntaxError, "Invalid pointer syntax in #{pointer_string.inspect}: pointer must begin with /") end end |
Instance Method Details
#+(ptr) ⇒ JSI::Ptr
a pointer with the tokens of this one plus the given ptr
's.
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/jsi/ptr.rb', line 188 def +(ptr) if ptr.is_a?(Ptr) ptr_tokens = ptr.tokens elsif ptr.respond_to?(:to_ary) ptr_tokens = ptr else raise(TypeError, "ptr must be a #{Ptr} or Array of tokens; got: #{ptr.inspect}") end ptr_tokens.empty? ? self : Ptr.new((tokens + ptr_tokens).freeze) end |
#[](token) ⇒ JSI::Ptr
appends the given token to this pointer's tokens and returns the result
214 215 216 |
# File 'lib/jsi/ptr.rb', line 214 def [](token) Ptr.new(tokens.dup.push(token).freeze) end |
#contains?(other_ptr) ⇒ Boolean
whether this pointer contains the other_ptr - that is, whether this pointer is an ancestor
of other_ptr
, a descendent pointer. contains?
is inclusive; a pointer does contain itself.
171 172 173 |
# File 'lib/jsi/ptr.rb', line 171 def contains?(other_ptr) tokens == other_ptr.tokens[0...tokens.size] end |
#empty? ⇒ Boolean Also known as: root?
whether this pointer is empty, i.e. it has no tokens
150 151 152 |
# File 'lib/jsi/ptr.rb', line 150 def empty? tokens.empty? end |
#evaluate(document, *a, **kw) ⇒ Object
takes a root json document and evaluates this pointer through the document, returning the value pointed to by this pointer.
122 123 124 125 126 127 128 |
# File 'lib/jsi/ptr.rb', line 122 def evaluate(document, *a, **kw) res = tokens.inject(document) do |value, token| _, child = node_subscript_token_child(value, token, *a, **kw) child end res end |
#fragment ⇒ String
the fragment string representation of this pointer
138 139 140 |
# File 'lib/jsi/ptr.rb', line 138 def fragment Addressable::URI.escape(pointer).freeze end |
#inspect ⇒ String Also known as: to_s
a string representation of this pointer
258 259 260 |
# File 'lib/jsi/ptr.rb', line 258 def inspect -"#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]" end |
#jsi_fingerprint ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
266 267 268 |
# File 'lib/jsi/ptr.rb', line 266 def jsi_fingerprint {class: Ptr, tokens: tokens} end |
#modified_document_copy(document) {|Object| ... } ⇒ Object
takes a document and a block. the block is yielded the content of the given document at this pointer's location. the block must result a modified copy of that content (and MUST NOT modify the object it is given). this modified copy of that content is incorporated into a modified copy of the given document, which is then returned. the structure and contents of the document outside the path pointed to by this pointer is not modified.
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/jsi/ptr.rb', line 229 def modified_document_copy(document, &block) # we need to preserve the rest of the document, but modify the content at our path. # # this is actually a bit tricky. we can't modify the original document, obviously. # we could do a deep copy, but that's expensive. instead, we make a copy of each array # or hash in the path above the node we point to. this node's content is modified by the # caller, and that is recursively merged up to the document root. if empty? Util.modified_copy(document, &block) else car = tokens[0] cdr = Ptr.new(tokens[1..-1].freeze) token, document_child = node_subscript_token_child(document, car) modified_document_child = cdr.modified_document_copy(document_child, &block) if modified_document_child.object_id == document_child.object_id document else modified_document = document.respond_to?(:[]=) ? document.dup : document.respond_to?(:to_hash) ? document.to_hash.dup : document.respond_to?(:to_ary) ? document.to_ary.dup : raise(Bug) # not possible; node_subscript_token_child would have raised modified_document[token] = modified_document_child modified_document end end end |
#parent ⇒ JSI::Ptr
pointer to the parent of where this pointer points
161 162 163 164 165 166 |
# File 'lib/jsi/ptr.rb', line 161 def parent if root? raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}") end tokens.size == 1 ? EMPTY : Ptr.new(tokens[0...-1].freeze) end |
#pointer ⇒ String
the pointer string representation of this pointer
132 133 134 |
# File 'lib/jsi/ptr.rb', line 132 def pointer tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('').freeze end |
#relative_to(ancestor_ptr) ⇒ JSI::Ptr
part of this pointer relative to the given ancestor_ptr
178 179 180 181 182 183 |
# File 'lib/jsi/ptr.rb', line 178 def relative_to(ancestor_ptr) unless ancestor_ptr.contains?(self) raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}") end ancestor_ptr.tokens.size == tokens.size ? EMPTY : Ptr.new(tokens[ancestor_ptr.tokens.size..-1].freeze) end |
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first n
of our tokens
203 204 205 206 207 208 |
# File 'lib/jsi/ptr.rb', line 203 def take(n) unless n.is_a?(Integer) && n >= 0 && n <= tokens.size raise(ArgumentError, "n not in range (0..#{tokens.size}): #{n.inspect}") end n == tokens.size ? self : Ptr.new(tokens.take(n).freeze) end |
#uri ⇒ Addressable::URI
a URI consisting of a fragment containing this pointer's fragment string representation
144 145 146 |
# File 'lib/jsi/ptr.rb', line 144 def uri Addressable::URI.new(fragment: fragment).freeze end |