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
- EMPTY =
new(Util::EMPTY_ARY)
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.
-
#ancestor_of?(other_ptr) ⇒ Boolean
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
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.
-
#resolve_against(document) ⇒ Ptr
Resolves each token of this pointer in
document, in particular resolving strings indicating array indices to integers. -
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first
nof our tokens. - #to_s ⇒ Object
-
#uri ⇒ 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.
108 109 110 111 112 113 |
# File 'lib/jsi/ptr.rb', line 108 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.
115 116 117 |
# File 'lib/jsi/ptr.rb', line 115 def tokens @tokens end |
Class Method Details
.[](*tokens) ⇒ JSI::Ptr
instantiates a pointer from the given tokens.
JSI::Ptr[]
instantiates a root pointer.
JSI::Ptr['a', 'b']
JSI::Ptr['a']['b']
are both ways to instantiate a pointer with tokens ['a', 'b']. the latter example chains the class .[] method with the instance #[] method.
49 50 51 |
# File 'lib/jsi/ptr.rb', line 49 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 32 33 |
# File 'lib/jsi/ptr.rb', line 25 def self.ary_ptr(ary_ptr) if ary_ptr.is_a?(Ptr) ary_ptr elsif ary_ptr == Util::EMPTY_ARY EMPTY 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 JSI::URI before passing to this method, e.g.:
JSI::Ptr.from_fragment(JSI::URI["#/foo"].fragment)
=> JSI::Ptr["foo"]
73 74 75 |
# File 'lib/jsi/ptr.rb', line 73 def self.from_fragment(fragment) from_pointer(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"]
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/jsi/ptr.rb', line 88 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.
210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/jsi/ptr.rb', line 210 def +(ptr) if ptr.is_a?(Ptr) return(ptr) if tokens.empty? 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
239 240 241 |
# File 'lib/jsi/ptr.rb', line 239 def [](token) Ptr.new(tokens.dup.push(token).freeze) end |
#ancestor_of?(other_ptr) ⇒ Boolean
whether this pointer is an ancestor of other_ptr, a descendent pointer.
ancestor_of? is inclusive; a pointer is an ancestor of itself.
192 193 194 |
# File 'lib/jsi/ptr.rb', line 192 def ancestor_of?(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
170 171 172 |
# File 'lib/jsi/ptr.rb', line 170 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.
124 125 126 127 128 129 130 |
# File 'lib/jsi/ptr.rb', line 124 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
158 159 160 |
# File 'lib/jsi/ptr.rb', line 158 def fragment URI.escape(pointer).freeze end |
#inspect ⇒ String
a string representation of this pointer
283 284 285 |
# File 'lib/jsi/ptr.rb', line 283 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.
293 294 295 |
# File 'lib/jsi/ptr.rb', line 293 def jsi_fingerprint {class: Ptr, tokens: tokens}.freeze 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.
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 |
# File 'lib/jsi/ptr.rb', line 254 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 = tokens.size == 1 ? EMPTY : 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.equal?(document_child) 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 : fail(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
181 182 183 184 185 186 |
# File 'lib/jsi/ptr.rb', line 181 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
152 153 154 |
# File 'lib/jsi/ptr.rb', line 152 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
199 200 201 202 203 204 205 |
# File 'lib/jsi/ptr.rb', line 199 def relative_to(ancestor_ptr) return self if ancestor_ptr.empty? unless ancestor_ptr.ancestor_of?(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 |
#resolve_against(document) ⇒ Ptr
Resolves each token of this pointer in document, in particular resolving strings indicating
array indices to integers.
136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/jsi/ptr.rb', line 136 def resolve_against(document) return(self) if tokens.empty? node = document resolved_tokens = nil tokens.each_with_index do |token, i| resolved_token, node = node_subscript_token_child(node, token) next if resolved_token.equal?(token) resolved_tokens ||= tokens.dup resolved_tokens[i] = resolved_token end return(self) if !resolved_tokens Ptr.new(resolved_tokens.freeze) end |
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first n of our tokens
226 227 228 229 230 231 232 233 |
# File 'lib/jsi/ptr.rb', line 226 def take(n) return(EMPTY) if n == 0 return(self) if n == tokens.size unless n.is_a?(Integer) && n >= 0 && n <= tokens.size raise(ArgumentError, "n not in range (0..#{tokens.size}): #{n.inspect}") end Ptr.new(tokens.take(n).freeze) end |
#to_s ⇒ Object
287 288 289 |
# File 'lib/jsi/ptr.rb', line 287 def to_s inspect end |
#uri ⇒ URI
a URI consisting of a fragment containing this pointer's fragment string representation
164 165 166 |
# File 'lib/jsi/ptr.rb', line 164 def uri URI.new(fragment: fragment).freeze end |