diff options
Diffstat (limited to 'src/bahnhofname.gleam')
-rw-r--r-- | src/bahnhofname.gleam | 117 |
1 files changed, 111 insertions, 6 deletions
diff --git a/src/bahnhofname.gleam b/src/bahnhofname.gleam index 232f0f7..36ea035 100644 --- a/src/bahnhofname.gleam +++ b/src/bahnhofname.gleam @@ -4,14 +4,113 @@ import gleam/http.{Get} import gleam/bit_builder.{BitBuilder} import gleam/erlang/process 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, + ) + } + } +} + +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) + } + } +} + +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) + } + } +} + +fn unpercent(encoded: String) -> String { + let #([head], chunks) = + encoded + |> string.split(on: "%") + |> list.split(at: 1) + + let assert Ok(res) = + chunks + |> list.map(fn(str) { + case string.length(str) < 2 { + True -> bit_string.from_string(str) + False -> { + let assert Ok(codepoint) = + str + |> string.slice(at_index: 0, length: 2) + |> int.base_parse(16) + <<codepoint:8, string.drop_left(str, 2):utf8>> + } + } + }) + |> list.prepend(bit_string.from_string(head)) + |> bit_string.concat + |> bit_string.to_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), @@ -21,7 +120,7 @@ fn the_lookup( |> lazy_unwrap(fn() { io.println(query) map.get(stations, query) - |> lazy_unwrap(fn() { "unknown" }) + |> lazy_unwrap(fn() { guess_station(query, stations) }) }) } @@ -32,13 +131,14 @@ fn lookup_station( baseurl: String, ) -> Response(BitBuilder) { let #(code, text) = case request { - Request(method: Get, path: "/help", ..) -> #( + Request(method: Get, path: "/help", ..) + | Request(method: Get, path: "/", ..) -> #( 200, - "ds100 → Name: " <> baseurl <> "/FF\n" <> "Name → ds100: " <> baseurl <> "/Frankfurt Hbf", + "ds100 → Name: " <> baseurl <> "/NN\n" <> "Name → ds100: " <> baseurl <> "/Nürnberg Hbf", ) Request(method: Get, path: "/" <> path, ..) -> #( 200, - the_lookup(string.replace(path, each: "%20", with: " "), stations, ds100s), + the_lookup(unpercent(path), stations, ds100s), ) _ -> #(404, "intended usage is e.g. curl " <> baseurl <> "/FF") } @@ -49,6 +149,11 @@ fn lookup_station( "x-data-source", "https://data.deutschebahn.com/dataset/data-betriebsstellen.html", ) + |> response.prepend_header( + "x-sources-at", + "https://stuebinm.eu/git/bahnhof.name", + ) + |> response.prepend_header("content-type", "text/plain; charset=utf8") |> response.set_body(body) } @@ -89,8 +194,8 @@ pub fn main() { |> list.map(fn(a) { #(a.1, a.0) }) |> map.from_list - let assert Ok(_) = mist.run_service( - 1234, + mist.run_service( + 2345, fn(req) { lookup_station(req, stationmap, ds100map, baseurl) }, max_body_limit: 100, ) |