80 lines
2 KiB
Elixir
80 lines
2 KiB
Elixir
defmodule Oban.Web.Search do
|
|
@moduledoc false
|
|
|
|
# Helpers for the search toolbar
|
|
|
|
@ignored_chars ~w(; / \ ` ' = * ! ? # $ & + ^ | ~ < > ( \) { } [ ])
|
|
|
|
# Suggestion tuning
|
|
@suggest_limit 10
|
|
@suggest_threshold 0.5
|
|
|
|
@doc """
|
|
Parse a string of qualifiers and values into structured search terms.
|
|
"""
|
|
def parse(terms, parser) when is_binary(terms) and is_function(parser, 1) do
|
|
terms
|
|
|> String.split(~r/\s+(?=([^\"]*\"[^\"]*\")*[^\"]*$)/, trim: true)
|
|
|> Map.new(fn term ->
|
|
term
|
|
|> String.replace(@ignored_chars, "")
|
|
|> parser.()
|
|
end)
|
|
end
|
|
|
|
@doc """
|
|
Append to the terms string without any duplication.
|
|
"""
|
|
def append(terms, choice, known)
|
|
when is_binary(terms) and is_binary(choice) and is_struct(known, MapSet) do
|
|
choice = if String.match?(choice, ~r/[\s,]/), do: ~s("#{choice}"), else: choice
|
|
|
|
cond do
|
|
MapSet.member?(known, choice) ->
|
|
choice
|
|
|
|
String.contains?(terms, ":") ->
|
|
[qualifier, _] = String.split(terms, ":", parts: 2)
|
|
|
|
"#{qualifier}:#{choice}"
|
|
|
|
true ->
|
|
terms
|
|
|> String.reverse()
|
|
|> String.split(["."], parts: 2)
|
|
|> List.last()
|
|
|> String.reverse()
|
|
|> Kernel.<>(".#{choice}")
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Restrict suggestions by similarity to a given fragment.
|
|
"""
|
|
def restrict_suggestions(suggestions, "") do
|
|
suggestions
|
|
|> Enum.sort()
|
|
|> Enum.take(@suggest_limit)
|
|
|> Enum.map(&{&1, "", ""})
|
|
end
|
|
|
|
def restrict_suggestions(suggestions, frag) do
|
|
suggestions
|
|
|> Enum.filter(&(similarity(&1, frag) >= @suggest_threshold))
|
|
|> Enum.sort_by(&{1.0 - similarity(&1, frag), &1}, :asc)
|
|
|> Enum.take(@suggest_limit)
|
|
|> Enum.map(&{&1, "", ""})
|
|
end
|
|
|
|
defp similarity(value, guess, boost \\ 0.5) do
|
|
value = String.downcase(value)
|
|
guess = String.downcase(guess)
|
|
distance = String.jaro_distance(value, guess)
|
|
|
|
if String.contains?(value, guess) do
|
|
min(distance + boost, 1.0)
|
|
else
|
|
distance
|
|
end
|
|
end
|
|
end
|