Class: JSI::Ptr

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Util::FingerprintHash

#==, #hash

Constructor Details

#initialize(tokens) ⇒ Ptr

initializes a JSI::Ptr from the given tokens.

Parameters:

  • tokens (Array<Object>)

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

#tokensObject (readonly) Also known as: reference_tokens

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

instantiates a pointer from the given tokens.

JSI::Ptr[]

instantes 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.

Parameters:

  • tokens

    any number of tokens

Returns:


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

Parameters:

  • ary_ptr (#to_ary, JSI::Ptr)

    an array of tokens, or a pointer

Returns:


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"]

Parameters:

  • fragment (String)

    a fragment containing a pointer

Returns:

Raises:


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"]

Parameters:

  • pointer_string (String)

    a pointer string

Returns:

Raises:


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.

Parameters:

Returns:


179
180
181
182
183
184
185
186
187
188
# File 'lib/jsi/ptr.rb', line 179

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 JSI::Ptr or Array of tokens; got: #{ptr.inspect}")
  end
  Ptr.new(self.tokens + ptr_tokens)
end

#[](token) ⇒ JSI::Ptr

appends the given token to this pointer's tokens and returns the result

Parameters:

  • token (Object)

Returns:

  • (JSI::Ptr)

    pointer to a child node of this pointer with the given token


205
206
207
# File 'lib/jsi/ptr.rb', line 205

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 child pointer. contains? is inclusive; a pointer does contain itself.

Returns:

  • (Boolean)

162
163
164
# File 'lib/jsi/ptr.rb', line 162

def contains?(other_ptr)
  self.tokens == other_ptr.tokens[0...self.tokens.size]
end

#empty?Boolean Also known as: root?

whether this pointer is empty, i.e. it has no tokens

Returns:

  • (Boolean)

140
141
142
# File 'lib/jsi/ptr.rb', line 140

def empty?
  tokens.empty?
end

#evaluate(document, *a) ⇒ Object

takes a root json document and evaluates this pointer through the document, returning the value pointed to by this pointer.

Parameters:

  • document (#to_ary, #to_hash)

    the document against which we will evaluate this pointer

  • a

    arguments are passed to each invocation of #[]

Returns:

  • (Object)

    the content of the document pointed to by this pointer

Raises:


112
113
114
115
116
117
118
# File 'lib/jsi/ptr.rb', line 112

def evaluate(document, *a)
  res = tokens.inject(document) do |value, token|
    _, child = node_subscript_token_child(value, token, *a)
    child
  end
  res
end

#fragmentString

the fragment string representation of this pointer

Returns:

  • (String)

128
129
130
# File 'lib/jsi/ptr.rb', line 128

def fragment
  Addressable::URI.escape(pointer)
end

#inspectString Also known as: to_s

a string representation of this pointer

Returns:

  • (String)

249
250
251
# File 'lib/jsi/ptr.rb', line 249

def inspect
  "#{self.class.name}[#{tokens.map(&:inspect).join(", ")}]"
end

#jsi_fingerprintObject

pointers are equal if the tokens are equal


256
257
258
# File 'lib/jsi/ptr.rb', line 256

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.

Parameters:

  • document (Object)

    the document to apply this pointer to

Yields:

  • (Object)

    the content this pointer applies to in the given document the block must result in the new content which will be placed in the modified document copy.

Returns:

  • (Object)

    a copy of the given document, with the content this pointer applies to replaced by the result of the block


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
245
# File 'lib/jsi/ptr.rb', line 220

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?
    Typelike.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

#parentJSI::Ptr

pointer to the parent of where this pointer points

Returns:

Raises:


151
152
153
154
155
156
157
# File 'lib/jsi/ptr.rb', line 151

def parent
  if root?
    raise(Ptr::Error, "cannot access parent of root pointer: #{pretty_inspect.chomp}")
  else
    Ptr.new(tokens[0...-1])
  end
end

#pointerString

the pointer string representation of this pointer

Returns:

  • (String)

122
123
124
# File 'lib/jsi/ptr.rb', line 122

def pointer
  tokens.map { |t| '/' + t.to_s.gsub('~', '~0').gsub('/', '~1') }.join('')
end

#ptr_relative_to(ancestor_ptr) ⇒ JSI::Ptr

part of this pointer relative to the given ancestor_ptr

Returns:

Raises:

  • (JSI::Ptr::Error)

    if the given ancestor_ptr is not an ancestor of this pointer


169
170
171
172
173
174
# File 'lib/jsi/ptr.rb', line 169

def ptr_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

Parameters:

  • n (Integer)

Returns:

Raises:

  • (ArgumentError)

    if n is not between 0 and the size of our tokens


194
195
196
197
198
199
# File 'lib/jsi/ptr.rb', line 194

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

#uriAddressable::URI

a URI consisting of a fragment containing this pointer's fragment string representation

Returns:

  • (Addressable::URI)

134
135
136
# File 'lib/jsi/ptr.rb', line 134

def uri
  Addressable::URI.new(fragment: fragment)
end