Class: Insta::Redaction::Selector

Inherits:
Object
  • Object
show all
Defined in:
lib/insta/redaction/selector.rb,
sig/insta/redaction/selector.rbs

Defined Under Namespace

Classes: Segment

Constant Summary collapse

SEGMENT_TYPES =

: Array

Returns:

  • (Array[Symbol])
[:key, :index, :wildcard, :deep_wildcard, :full_range].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(selector_string) ⇒ Selector

: (String) -> void

Parameters:

  • (String)


13
14
15
# File 'lib/insta/redaction/selector.rb', line 13

def initialize(selector_string)
  @segments = parse(selector_string)
end

Instance Attribute Details

#segmentsArray[Segment] (readonly)

: Array

Returns:



10
11
12
# File 'lib/insta/redaction/selector.rb', line 10

def segments
  @segments
end

Instance Method Details

#deep_wildcard_match?(path, deep_index) ⇒ Boolean

: (Array[{ type: Symbol, value: untyped }], Integer) -> bool

Parameters:

  • (Array[{ type: Symbol, value: untyped }])
  • (Integer)

Returns:

  • (Boolean)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/insta/redaction/selector.rb', line 142

def deep_wildcard_match?(path, deep_index)
  before = segments[0...deep_index] || [] #: Array[Segment]
  after = segments[(deep_index + 1)..] || [] #: Array[Segment]

  return false if path.length < before.length + after.length

  before.each_with_index do |segment, index|
    return false unless segment_matches?(segment, path[index])
  end

  return true if after.empty?

  after_length = after.length
  path_length = path.length

  after.each_with_index do |segment, index|
    path_index = path_length - after_length + index
    return false unless segment_matches?(segment, path[path_index])
  end

  true
end

#exact_match?(path, expected_segments) ⇒ Boolean

: (Array[{ type: Symbol, value: untyped }], Array) -> bool

Parameters:

  • (Array[{ type: Symbol, value: untyped }])
  • (Array[Segment])

Returns:

  • (Boolean)


166
167
168
169
170
171
172
173
174
# File 'lib/insta/redaction/selector.rb', line 166

def exact_match?(path, expected_segments)
  return false unless path.length == expected_segments.length

  expected_segments.each_with_index do |segment, index|
    return false unless segment_matches?(segment, path[index])
  end

  true
end

#matches?(path) ⇒ Boolean

: (Array[{ type: Symbol, value: untyped }]) -> bool

Parameters:

  • (Array[{ type: Symbol, value: untyped }])

Returns:

  • (Boolean)


18
19
20
21
22
23
24
25
26
# File 'lib/insta/redaction/selector.rb', line 18

def matches?(path)
  deep_index = segments.index { |segment| segment.type == :deep_wildcard }

  if deep_index
    deep_wildcard_match?(path, deep_index)
  else
    exact_match?(path, segments)
  end
end

#parse(input) ⇒ Array[Segment]

: (String) -> Array

Parameters:

  • (String)

Returns:



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/insta/redaction/selector.rb', line 31

def parse(input)
  raise ArgumentError, "Selector must not be empty" if input.empty?
  raise ArgumentError, "Selector must start with '.' or '['" unless input.start_with?(".", "[")

  result = [] #: Array[Segment]
  position = 0

  while position < input.length
    case input[position]
    when "."
      position = parse_dot_segment(input, position + 1, result)
    when "["
      position = parse_bracket_segment(input, position + 1, result)
    else
      raise ArgumentError, "Unexpected character '#{input[position]}' at position #{position}"
    end
  end

  result
end

#parse_bracket_segment(input, position, result) ⇒ Integer

: (String, Integer, Array) -> Integer

Parameters:

  • (String)
  • (Integer)
  • (Array[Segment])

Returns:

  • (Integer)


93
94
95
96
97
98
99
100
101
102
103
# File 'lib/insta/redaction/selector.rb', line 93

def parse_bracket_segment(input, position, result)
  if position < input.length && input[position] == "]"
    result << Segment.new(type: :full_range, value: nil)

    position + 1
  elsif position < input.length && input[position] == '"'
    parse_quoted_key_segment(input, position + 1, result)
  else
    parse_index_segment(input, position, result)
  end
end

#parse_dot_segment(input, position, result) ⇒ Integer

: (String, Integer, Array) -> Integer

Parameters:

  • (String)
  • (Integer)
  • (Array[Segment])

Returns:

  • (Integer)


53
54
55
56
57
58
59
# File 'lib/insta/redaction/selector.rb', line 53

def parse_dot_segment(input, position, result)
  if position < input.length && input[position] == "*"
    parse_wildcard_segment(input, position + 1, result)
  else
    parse_key_segment(input, position, result)
  end
end

#parse_index_segment(input, position, result) ⇒ Integer

: (String, Integer, Array) -> Integer

Parameters:

  • (String)
  • (Integer)
  • (Array[Segment])

Returns:

  • (Integer)


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/insta/redaction/selector.rb', line 125

def parse_index_segment(input, position, result)
  digits = +""

  while position < input.length && input[position] =~ /\d/
    digits << input[position].to_s
    position += 1
  end

  raise ArgumentError, "Expected number or ']' in bracket" if digits.empty?
  raise ArgumentError, "Expected ']' after index" if position >= input.length || input[position] != "]"

  result << Segment.new(type: :index, value: digits.to_i)

  position + 1
end

#parse_key_segment(input, position, result) ⇒ Integer

: (String, Integer, Array) -> Integer

Parameters:

  • (String)
  • (Integer)
  • (Array[Segment])

Returns:

  • (Integer)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/insta/redaction/selector.rb', line 77

def parse_key_segment(input, position, result)
  key = +""

  while position < input.length && input[position] != "." && input[position] != "["
    key << input[position].to_s
    position += 1
  end

  raise ArgumentError, "Empty key in selector at position #{position}" if key.empty?

  result << Segment.new(type: :key, value: key)

  position
end

#parse_quoted_key_segment(input, position, result) ⇒ Integer

: (String, Integer, Array) -> Integer

Parameters:

  • (String)
  • (Integer)
  • (Array[Segment])

Returns:

  • (Integer)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/insta/redaction/selector.rb', line 106

def parse_quoted_key_segment(input, position, result)
  key = +""

  while position < input.length && input[position] != '"'
    key << input[position].to_s
    position += 1
  end

  raise ArgumentError, "Unterminated quoted key in selector" if position >= input.length

  position += 1
  raise ArgumentError, "Expected ']' after quoted key" if position >= input.length || input[position] != "]"

  result << Segment.new(type: :key, value: key)

  position + 1
end

#parse_wildcard_segment(input, position, result) ⇒ Integer

: (String, Integer, Array) -> Integer

Parameters:

  • (String)
  • (Integer)
  • (Array[Segment])

Returns:

  • (Integer)


62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/insta/redaction/selector.rb', line 62

def parse_wildcard_segment(input, position, result)
  if position < input.length && input[position] == "*"
    raise ArgumentError, "Only one deep wildcard (**) allowed per selector" if result.any? { |segment| segment.type == :deep_wildcard }

    result << Segment.new(type: :deep_wildcard, value: nil)

    position + 1
  else
    result << Segment.new(type: :wildcard, value: nil)

    position
  end
end

#segment_matches?(segment, path_entry) ⇒ Boolean

: (Segment, { type: Symbol, value: untyped }) -> bool

Parameters:

  • (Segment)
  • ({ type: Symbol, value: untyped })

Returns:

  • (Boolean)


177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/insta/redaction/selector.rb', line 177

def segment_matches?(segment, path_entry)
  case segment.type
  when :key
    path_entry[:type] == :key && path_entry[:value].to_s == segment.value
  when :index
    path_entry[:type] == :index && path_entry[:value] == segment.value
  when :wildcard
    path_entry[:type] == :key
  when :full_range
    path_entry[:type] == :index
  else
    false
  end
end