2018-11-19 21:01:13 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2018-06-06 20:14:10 -04:00
|
|
|
module Gitlab
|
|
|
|
module Search
|
|
|
|
class Query < SimpleDelegator
|
2018-12-02 16:47:33 -05:00
|
|
|
include EncodingHelper
|
|
|
|
|
2021-02-01 04:09:28 -05:00
|
|
|
QUOTES_REGEXP = %r{\A"|"\Z}.freeze
|
|
|
|
TOKEN_WITH_QUOTES_REGEXP = %r{\s(?=(?:[^"]|"[^"]*")*$)}.freeze
|
|
|
|
|
2018-06-06 20:14:10 -04:00
|
|
|
def initialize(query, filter_opts = {}, &block)
|
|
|
|
@raw_query = query.dup
|
|
|
|
@filters = []
|
|
|
|
@filter_options = { default_parser: :downcase.to_proc }.merge(filter_opts)
|
|
|
|
|
|
|
|
self.instance_eval(&block) if block_given?
|
|
|
|
|
|
|
|
@query = Gitlab::Search::ParsedQuery.new(*extract_filters)
|
|
|
|
# set the ParsedQuery as our default delegator thanks to SimpleDelegator
|
|
|
|
super(@query)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def filter(name, **attributes)
|
2020-08-17 14:10:01 -04:00
|
|
|
filter = {
|
|
|
|
parser: @filter_options[:default_parser],
|
|
|
|
name: name
|
|
|
|
}.merge(attributes)
|
2018-06-06 20:14:10 -04:00
|
|
|
|
|
|
|
@filters << filter
|
|
|
|
end
|
|
|
|
|
|
|
|
def filter_options(**options)
|
|
|
|
@filter_options.merge!(options)
|
|
|
|
end
|
|
|
|
|
|
|
|
def extract_filters
|
|
|
|
fragments = []
|
|
|
|
|
2021-02-01 04:09:28 -05:00
|
|
|
query_tokens = parse_raw_query
|
2018-06-06 20:14:10 -04:00
|
|
|
filters = @filters.each_with_object([]) do |filter, parsed_filters|
|
2021-02-01 04:09:28 -05:00
|
|
|
match = query_tokens.find { |part| part =~ /\A-?#{filter[:name]}:/ }
|
|
|
|
|
2018-06-06 20:14:10 -04:00
|
|
|
next unless match
|
|
|
|
|
2021-12-14 07:13:33 -05:00
|
|
|
input = match.split(':')[1..].join
|
2018-06-06 20:14:10 -04:00
|
|
|
next if input.empty?
|
|
|
|
|
2020-08-17 14:10:01 -04:00
|
|
|
filter[:negated] = match.start_with?("-")
|
2021-02-01 04:09:28 -05:00
|
|
|
filter[:value] = parse_filter(filter, input.gsub(QUOTES_REGEXP, ''))
|
2018-06-06 20:14:10 -04:00
|
|
|
filter[:regex_value] = Regexp.escape(filter[:value]).gsub('\*', '.*?')
|
|
|
|
fragments << match
|
|
|
|
|
|
|
|
parsed_filters << filter
|
|
|
|
end
|
|
|
|
|
2021-02-01 04:09:28 -05:00
|
|
|
query = (query_tokens - fragments).join(' ')
|
2020-12-16 01:10:11 -05:00
|
|
|
query = '*' if query.empty?
|
2018-06-06 20:14:10 -04:00
|
|
|
|
|
|
|
[query, filters]
|
|
|
|
end
|
|
|
|
|
|
|
|
def parse_filter(filter, input)
|
2018-12-02 16:47:33 -05:00
|
|
|
result = filter[:parser].call(input)
|
|
|
|
|
|
|
|
@filter_options[:encode_binary] ? encode_binary(result) : result
|
2018-06-06 20:14:10 -04:00
|
|
|
end
|
2021-02-01 04:09:28 -05:00
|
|
|
|
|
|
|
def parse_raw_query
|
|
|
|
# Positive lookahead for any non-quote char or even number of quotes
|
|
|
|
# for example '"search term" path:"foo bar.txt"' would break into
|
|
|
|
# ["search term", "path:\"foo bar.txt\""]
|
|
|
|
@raw_query.split(TOKEN_WITH_QUOTES_REGEXP).reject(&:empty?)
|
|
|
|
end
|
2018-06-06 20:14:10 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|