Class: JSI::Ptr
- Inherits:
-
Object
- Object
- JSI::Ptr
- Includes:
- Util::FingerprintHash
- 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
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
pointers are equal if the tokens are equal.
-
#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
(also: #ptr_relative_to)
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.
93 94 95 96 97 98 |
# File 'lib/jsi/ptr.rb', line 93 def initialize(tokens) unless tokens.respond_to?(:to_ary) raise(TypeError, "tokens must be an array. got: #{tokens.inspect}") end @tokens = tokens.to_ary.map(&:freeze).freeze end |
Instance Attribute Details
#tokens ⇒ Object (readonly)
Returns the value of attribute tokens.
100 101 102 |
# File 'lib/jsi/ptr.rb', line 100 def tokens @tokens end |
Class Method Details
.[](*tokens) ⇒ JSI::Ptr
44 45 46 |
# File 'lib/jsi/ptr.rb', line 44 def self.[](*tokens) new(tokens) end |
.ary_ptr(ary_ptr) ⇒ JSI::Ptr
instantiates a pointer or returns the given pointer
22 23 24 25 26 27 28 |
# File 'lib/jsi/ptr.rb', line 22 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"]
62 63 64 |
# File 'lib/jsi/ptr.rb', line 62 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"]
77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/jsi/ptr.rb', line 77 def self.from_pointer(pointer_string) tokens = pointer_string.split('/', -1).map! do |piece| piece.gsub('~1', '/').gsub('~0', '~') end if tokens[0] == '' new(tokens[1..-1]) elsif tokens.empty? new(tokens) 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.
178 179 180 181 182 183 184 185 186 187 |
# File 'lib/jsi/ptr.rb', line 178 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.new(tokens + ptr_tokens) end |
#[](token) ⇒ JSI::Ptr
appends the given token to this pointer's tokens and returns the result
204 205 206 |
# File 'lib/jsi/ptr.rb', line 204 def [](token) Ptr.new(tokens + [token]) 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.
158 159 160 |
# File 'lib/jsi/ptr.rb', line 158 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
137 138 139 |
# File 'lib/jsi/ptr.rb', line 137 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.
109 110 111 112 113 114 115 |
# File 'lib/jsi/ptr.rb', line 109 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
125 126 127 |
# File 'lib/jsi/ptr.rb', line 125 def fragment Addressable::URI.escape(pointer) end |
#inspect ⇒ String Also known as: to_s
a string representation of this pointer
248 249 250 |
# File 'lib/jsi/ptr.rb', line 248 def inspect "#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]" end |
#jsi_fingerprint ⇒ Object
pointers are equal if the tokens are equal
255 256 257 |
# File 'lib/jsi/ptr.rb', line 255 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.
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/jsi/ptr.rb', line 219 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]) 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
148 149 150 151 152 153 |
# File 'lib/jsi/ptr.rb', line 148 def parent if root? raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}") end Ptr.new(tokens[0...-1]) end |
#pointer ⇒ String
the pointer string representation of this pointer
119 120 121 |
# File 'lib/jsi/ptr.rb', line 119 def pointer tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('') end |
#relative_to(ancestor_ptr) ⇒ JSI::Ptr Also known as: ptr_relative_to
part of this pointer relative to the given ancestor_ptr
165 166 167 168 169 170 |
# File 'lib/jsi/ptr.rb', line 165 def relative_to(ancestor_ptr) unless ancestor_ptr.contains?(self) raise(Error, "ancestor_ptr #{ancestor_ptr.inspect} is not ancestor of #{inspect}") end Ptr.new(tokens[ancestor_ptr.tokens.size..-1]) end |
#take(n) ⇒ JSI::Ptr
a pointer consisting of the first n
of our tokens
193 194 195 196 197 198 |
# File 'lib/jsi/ptr.rb', line 193 def take(n) unless (0..tokens.size).include?(n) raise(ArgumentError, "n not in range (0..#{tokens.size}): #{n.inspect}") end Ptr.new(tokens.take(n)) end |
#uri ⇒ Addressable::URI
a URI consisting of a fragment containing this pointer's fragment string representation
131 132 133 |
# File 'lib/jsi/ptr.rb', line 131 def uri Addressable::URI.new(fragment: fragment) end |