diff options
Diffstat (limited to 'src/bahnhofname.gleam')
-rw-r--r-- | src/bahnhofname.gleam | 209 |
1 files changed, 127 insertions, 82 deletions
diff --git a/src/bahnhofname.gleam b/src/bahnhofname.gleam index 36ea035..a783955 100644 --- a/src/bahnhofname.gleam +++ b/src/bahnhofname.gleam @@ -3,70 +3,62 @@ import gleam/http/request.{Request} import gleam/http.{Get} import gleam/bit_builder.{BitBuilder} import gleam/erlang/process +import gleam/erlang/atom import gleam/io import gleam/int import gleam/string import gleam/bit_string import gleam/list import gleam/map.{Map} -import gleam/result.{lazy_unwrap} import gleam/uri import gleam/hackney -import gleam/option.{None, Some} import mist -fn do_distlist( - b: String, - distlist: List(Int), - grapheme: String, - new_distlist: List(Int), - last_dist: Int, -) { - case #(b, distlist) { - #("", _) -> list.reverse(new_distlist) - #(_, [distlist_hd, distlist_snd, ..distlist_tl]) -> { - let assert Ok(b_hd) = string.first(b) - let b_tl = string.drop_left(b, up_to: 1) - let diff = case #(b_hd, grapheme) { - #(a, b) if a != b -> 1 - _ -> 0 - } - let minimum = - int.min(int.min(last_dist + 1, distlist_snd + 1), distlist_hd + diff) - do_distlist( - b_tl, - [distlist_snd, ..distlist_tl], - grapheme, - [minimum, ..new_distlist], - minimum, - ) - } - } -} +external type Index +external type Field -fn do_distance(a: String, b: String, distlist: List(Int), step: Int) { - case a { - "" -> result.unwrap(list.last(distlist), -1) - _ -> { - let assert Ok(src_hd) = string.first(a) - let src_tl = string.drop_left(a, up_to: 1) - let distlist = do_distlist(b, distlist, src_hd, [step], step) - do_distance(src_tl, b, distlist, step + 1) - } - } -} +external fn index_new(atom.Atom) -> Index = + "Elixir.Haystack.Index" "new" -fn levenshtein(a: String, b: String) -> Int { - case #(a, b) { - #(a, b) if a == b -> 0 - #("", b) -> string.length(b) - #(a, "") -> string.length(a) - #(a, b) -> { - let distlist = list.range(0, string.length(b)) - do_distance(a, b, distlist, 1) - } - } -} +external fn index_ref(Index, Field) -> Index = + "Elixir.Haystack.Index" "ref" + +external fn index_field(Index, Field) -> Index = + "Elixir.Haystack.Index" "field" + +external fn field_term(String) -> Field = + "Elixir.Haystack.Index.Field" "term" + +external fn field_new(String) -> Field = + "Elixir.Haystack.Index.Field" "new" + +external fn index_add(Index, List(a)) -> Index = + "Elixir.Haystack.Index" "add" + +external fn index_search(Index, String) -> List(Map(atom.Atom, String)) = + "Elixir.Haystack.Index" "search" + +pub external fn inspect(a) -> a = + "Elixir.IO" "inspect" + +external type Query +external type Clause +external type Expression +external fn query_new() -> Query = + "Elixir.Haystack.Query" "new" +external fn query_clause(Query, Clause) -> Query = + "Elixir.Haystack.Query" "clause" +external fn query_run(Query, Index) -> List(Map(atom.Atom, String)) = + "Elixir.Haystack.Query" "run" +external fn clause_new(atom.Atom) -> Clause = + "Elixir.Haystack.Query.Clause" "new" +external fn query_expressions(Clause, List(Expression)) -> Clause = + "Elixir.Haystack.Query.Clause" "expressions" +external fn query_expression_new(atom.Atom, List(#(atom.Atom, String))) -> Expression = + "Elixir.Haystack.Query.Expression" "new" + +external fn tokenize(String) -> List(Map(atom.Atom, String)) = + "Elixir.Haystack.Tokenizer" "tokenize" fn unpercent(encoded: String) -> String { let #([head], chunks) = @@ -94,34 +86,38 @@ fn unpercent(encoded: String) -> String { res } -fn guess_station(query: String, stations: Map(String, String)) -> String { - query - stations - |> map.keys - |> list.map(fn(a) { #(levenshtein(query, a), a) }) - |> list.fold( - from: #(string.length(query), query), - with: fn(a, b) { - case a.0 < b.0 { - True -> a - False -> b - } - }, - ) - |> fn(a: #(Int, String)) { a.1 } -} - fn the_lookup( query: String, stations: Map(String, String), ds100s: Map(String, String), -) -> String { - map.get(ds100s, query) - |> lazy_unwrap(fn() { - io.println(query) - map.get(stations, query) - |> lazy_unwrap(fn() { guess_station(query, stations) }) - }) + fuzzy: fn(String) -> List(String) +) -> #(Int, String) { + case map.get(ds100s, query) { + Ok(name) -> #(200, name) + _ -> { + io.println(query) + case map.get(stations, query) { + Ok(ds100) -> #(200, ds100) + _ -> { + let results = fuzzy(query) + |> list.filter_map(fn (res) { map.get(ds100s, string.uppercase(res)) }) + case results { + // results -> { + // let names = results + // |> list.map (fn (res) { + // map.get(ds100s, string.uppercase(res)) + // |> result.map(fn(a) { "/" <> a }) + // |> result.unwrap("/")}) + // #(200, string.join(names, "\n")) + // } + [res] -> #(302, res) + [res, ..] -> #(302, res) + _ -> #(404, "??") + } + } + } + } + } } fn lookup_station( @@ -129,6 +125,7 @@ fn lookup_station( stations: Map(String, String), ds100s: Map(String, String), baseurl: String, + fuzzy: fn (String) -> List(String) ) -> Response(BitBuilder) { let #(code, text) = case request { Request(method: Get, path: "/help", ..) @@ -136,10 +133,8 @@ fn lookup_station( 200, "ds100 → Name: " <> baseurl <> "/NN\n" <> "Name → ds100: " <> baseurl <> "/Nürnberg Hbf", ) - Request(method: Get, path: "/" <> path, ..) -> #( - 200, - the_lookup(unpercent(path), stations, ds100s), - ) + Request(method: Get, path: "/" <> path, ..) -> + the_lookup(unpercent(path), stations, ds100s, fuzzy) _ -> #(404, "intended usage is e.g. curl " <> baseurl <> "/FF") } let body = bit_builder.from_string(text) @@ -154,6 +149,10 @@ fn lookup_station( "https://stuebinm.eu/git/bahnhof.name", ) |> response.prepend_header("content-type", "text/plain; charset=utf8") + |> fn (a) { case code == 302 { + True -> response.prepend_header(a, "location", text) + _ -> a + } } |> response.set_body(body) } @@ -193,10 +192,56 @@ pub fn main() { stations |> list.map(fn(a) { #(a.1, a.0) }) |> map.from_list + let ref = atom.create_from_string("ref") + let index = index_new(atom.create_from_string("stations")) + |> index_ref(field_term("id")) + |> index_field(field_new("name")) + |> index_add(stations + |> list.map(fn(tuple) {case tuple { + #(name, ds100) + -> map.from_list([#("id", ds100), #("name", name)] + )}})) + + + let fuzzy = fn(searchterm: String) -> List(String) { + let query = query_new() + let match = atom.create_from_string("match") + let field = atom.create_from_string("field") + let term = atom.create_from_string("term") + let expressions = tokenize(inspect(searchterm)) + |> list.filter_map(fn (a) { map.get(a, atom.create_from_string("v")) }) + |> list.map(fn (token) { query_expression_new(match, [#(field, "name"), #(term, token)]) }) + let clause = query_expressions(clause_new(atom.create_from_string("all")), expressions) + let query = query_clause(query, clause) + + let matches = query_run(query, index) + |> list.filter_map(fn (a) { map.get(a, ref) }) + + inspect(matches) + case list.length(matches) > 5 { + True -> { + let query = query_new() + let clause = query_expressions( + clause_new(atom.create_from_string("all")), + [query_expression_new(match, [#(field, "name"), #(term, "hbf")]) , ..expressions] + ) + let query = query_clause(query, clause) + let narrow = query_run(query, index) + |> list.filter_map(fn (a) { map.get(a, ref) }) + case narrow { + [] -> matches + _ -> narrow + } + } + _ -> matches + } + } + + io.println("compiled index, starting server …") - mist.run_service( + let _ = mist.run_service( 2345, - fn(req) { lookup_station(req, stationmap, ds100map, baseurl) }, + fn(req) { lookup_station(req, stationmap, ds100map, baseurl, fuzzy) }, max_body_limit: 100, ) process.sleep_forever() |