Class: RoundhouseUi::FilterQuery::Pattern

Inherits:
Object
  • Object
show all
Defined in:
lib/roundhouse_ui/filter_query.rb

Overview

class=Roundhouse%, class=%oundhouse%, class=%Worker, class=A%B%C.

% only. NOT _: SQL's single-character wildcard would silently turn queue=default_low into a pattern matching defaultXlow, and every Ruby name in this console is underscore-dense. A wildcard nobody typed is the one thing that must never happen here, because the same predicate scopes a Delete.

An index walk rather than a Regexp. The pattern comes from a URL and is matched against every entry in a set that can hold fifty thousand of them, so a backtracking engine is both a performance and a ReDoS question. This is linear in the value and provably terminates.

Constant Summary collapse

MAX_WILDCARDS =
6

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value) ⇒ Pattern

Returns a new instance of Pattern.



82
83
84
85
86
87
88
# File 'lib/roundhouse_ui/filter_query.rb', line 82

def initialize(value)
  @source = value.to_s
  @segments = @source.split("%", -1)
  @lead = @segments.first
  @tail = @segments.last
  @mids = @segments[1..-2] || []
end

Instance Attribute Details

#sourceObject (readonly)

Returns the value of attribute source.



80
81
82
# File 'lib/roundhouse_ui/filter_query.rb', line 80

def source
  @source
end

Class Method Details

.for(value) ⇒ Object



76
77
78
# File 'lib/roundhouse_ui/filter_query.rb', line 76

def self.for(value)
  value.to_s.include?("%") ? new(value) : nil
end

Instance Method Details

#literalsObject

Literal characters the pattern insists on. Zero means it matches every possible value, which is not a filter — see the refusal in the parser.



92
# File 'lib/roundhouse_ui/filter_query.rb', line 92

def literals = @segments.sum(&:length)

#match?(value) ⇒ Boolean

Returns:

  • (Boolean)


96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/roundhouse_ui/filter_query.rb', line 96

def match?(value)
  v = value.to_s
  return false unless v.start_with?(@lead)
  # The tail must not reuse characters the lead already consumed, or `A%A`
  # would match "A" — one literal doing double duty.
  return false unless v.end_with?(@tail) && v.length >= @lead.length + @tail.length

  cursor = @lead.length
  stop = v.length - @tail.length
  @mids.each do |mid|
    next if mid.empty?

    at = v.index(mid, cursor)
    return false if at.nil? || at + mid.length > stop

    cursor = at + mid.length
  end
  true
end

#too_many?Boolean

Returns:

  • (Boolean)


94
# File 'lib/roundhouse_ui/filter_query.rb', line 94

def too_many? = wildcards > MAX_WILDCARDS

#wildcardsObject



93
# File 'lib/roundhouse_ui/filter_query.rb', line 93

def wildcards = @segments.length - 1