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.
-
#ancestor_of?(other_ptr) ⇒ Boolean
whether this pointer is an ancestor of
other_ptr
, a descendent pointer. - #contains?(other_ptr) ⇒ Boolean deprecated Deprecated.
-
#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.
-
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first
n
of our tokens. - #to_s ⇒ Object
-
#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.
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
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 Addressable::URI
before passing to this method, e.g.:
JSI::Ptr.from_fragment(Addressable::URI.parse("#/foo").fragment)
=> JSI::Ptr["foo"]
73 74 75 |
# File 'lib/jsi/ptr.rb', line 73 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"]
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.
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/jsi/ptr.rb', line 197 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
223 224 225 |
# File 'lib/jsi/ptr.rb', line 223 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.
174 175 176 |
# File 'lib/jsi/ptr.rb', line 174 def ancestor_of?(other_ptr) tokens == other_ptr.tokens[0...tokens.size] end |
#contains?(other_ptr) ⇒ Boolean
179 180 181 |
# File 'lib/jsi/ptr.rb', line 179 def contains?(other_ptr) ancestor_of?(other_ptr) end |
#empty? ⇒ Boolean Also known as: root?
whether this pointer is empty, i.e. it has no tokens
152 153 154 |
# File 'lib/jsi/ptr.rb', line 152 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
140 141 142 |
# File 'lib/jsi/ptr.rb', line 140 def fragment Addressable::URI.escape(pointer).freeze end |
#inspect ⇒ String
a string representation of this pointer
267 268 269 |
# File 'lib/jsi/ptr.rb', line 267 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.
277 278 279 |
# File 'lib/jsi/ptr.rb', line 277 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.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/jsi/ptr.rb', line 238 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.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
163 164 165 166 167 168 |
# File 'lib/jsi/ptr.rb', line 163 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
134 135 136 |
# File 'lib/jsi/ptr.rb', line 134 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
186 187 188 189 190 191 192 |
# File 'lib/jsi/ptr.rb', line 186 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 |
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first n
of our tokens
212 213 214 215 216 217 |
# File 'lib/jsi/ptr.rb', line 212 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 |
#to_s ⇒ Object
271 272 273 |
# File 'lib/jsi/ptr.rb', line 271 def to_s inspect end |
#uri ⇒ Addressable::URI
a URI consisting of a fragment containing this pointer's fragment string representation
146 147 148 |
# File 'lib/jsi/ptr.rb', line 146 def uri Addressable::URI.new(fragment: fragment).freeze end |