diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/README.md b/README.md index 175fd8f..297048f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,9 @@ The package can be installed by adding `expostal` to your list of dependencies i ```elixir def deps do - [{:expostal, "~> 0.2.0"}] + [ + {:expostal, "~> 0.3.0"} + ] end ``` @@ -27,17 +29,22 @@ Depends on [system-wide installation of libpostal](https://github.com/openvenues ## Usage -Parsing an address: +Parsing an address: ``` iex> Expostal.parse_address("615 Rene Levesque Ouest, Montreal, QC, Canada") +%Expostal.Address{ + city: "montreal", + country: "canada", + house_number: "615", + road: "rene levesque ouest", + state: "qc" +} -%{city: "montreal", country: "canada", house_number: "615", - road: "rene levesque ouest", state: "qc"} ``` -Expanding an address: +Expanding an address: ``` iex> Expostal.expand_address("781 Franklin Ave Crown Hts Brooklyn NY") @@ -46,6 +53,14 @@ iex> Expostal.expand_address("781 Franklin Ave Crown Hts Brooklyn NY") "781 franklin avenue crown heights brooklyn ny"] ``` +Classifying language: +Returns a tuple with probability of the most probable language for a given address and a language list + +``` +iex> Expostal.classify_language("agricola pl.") +{0.508300861587544, ["en", "fr", "es", "de"]} + +``` ## Documentation View the docs on [https://hexdocs.pm/expostal](https://hexdocs.pm/expostal), or diff --git a/config/config.exs b/config/config.exs index c3267b3..72a62eb 100644 --- a/config/config.exs +++ b/config/config.exs @@ -1,6 +1,6 @@ # This file is responsible for configuring your application # and its dependencies with the aid of the Mix.Config module. -use Mix.Config +# use Mix.Config # This configuration is loaded before any dependency and is restricted # to this project. If another project depends on this project, this diff --git a/lib/address.ex b/lib/address.ex new file mode 100644 index 0000000..6ed01d5 --- /dev/null +++ b/lib/address.ex @@ -0,0 +1,52 @@ +defmodule Expostal.Address do + @shortdoc "Struct for holding the results returned by libpostal." + @moduledoc """ + Struct for holding the results returned by libpostal. + From the upstream README.md: + https://github.com/openvenues/libpostal + + * house: venue name e.g. "Brooklyn Academy of Music", and building names e.g. "Empire State Building" + * category: for category queries like "restaurants", etc. + * near: phrases like "in", "near", etc. used after a category phrase to help with parsing queries like "restaurants in Brooklyn" + * house_number: usually refers to the external (street-facing) building number. In some countries this may be a compount, hyphenated number which also includes an apartment number, or a block number (a la Japan), but libpostal will just call it the house_number for simplicity. + * road: street name(s) + * unit: an apartment, unit, office, lot, or other secondary unit designator + * level: expressions indicating a floor number e.g. "3rd Floor", "Ground Floor", etc. + * staircase: numbered/lettered staircase + * entrance: numbered/lettered entrance + * po_box: post office box: typically found in non-physical (mail-only) addresses + * postcode: postal codes used for mail sorting + * suburb: usually an unofficial neighborhood name like "Harlem", "South Bronx", or "Crown Heights" + * city_district: these are usually boroughs or districts within a city that serve some official purpose e.g. "Brooklyn" or "Hackney" or "Bratislava IV" + * city: any human settlement including cities, towns, villages, hamlets, localities, etc. + * island: named islands e.g. "Maui" + * state_district: usually a second-level administrative division or county. + * state: a first-level administrative division. Scotland, Northern Ireland, Wales, and England in the UK are mapped to "state" as well (convention used in OSM, GeoPlanet, etc.) + * country_region: informal subdivision of a country without any political status + * country: sovereign nations and their dependent territories, anything with an ISO-3166 code. + * world_region: currently only used for appending “West Indies” after the country name, a pattern frequently used in the English-speaking Caribbean e.g. “Jamaica, West Indies” + """ + + defstruct [ + :house, + :category, + :near, + :house_number, + :road, + :unit, + :level, + :staircase, + :entrance, + :po_box, + :postcode, + :suburb, + :city_district, + :city, + :island, + :state_district, + :state, + :country_region, + :country, + :world_region + ] +end diff --git a/lib/expostal.ex b/lib/expostal.ex index f06c386..f97070f 100644 --- a/lib/expostal.ex +++ b/lib/expostal.ex @@ -3,19 +3,19 @@ defmodule Expostal do Address parsing and expansion module for Openvenue's Libpostal, which does parses addresses. """ - @compile { :autoload, false } - @on_load { :init, 0 } + @compile {:autoload, false} + @on_load {:init, 0} - app = Mix.Project.config[:app] + app = Mix.Project.config()[:app] - defp init do - path = :filename.join(:code.priv_dir(unquote(app)), 'expostal') + def init do + path = :filename.join(:code.priv_dir(unquote(app)), ~c"expostal") :ok = :erlang.load_nif(path, 0) end @doc """ - Loads the large dataset from disk for libpostal and prepares it for future calls. + Loads the large dataset from disk for libpostal and prepares it for future calls. If you do not run this explicitly, then it will be run by `parse_address/1` or `expand_address/1` on their first run. This is a very slow process (it can take 10s of seconds), so if you value the responsiveness of your application, you can spawn a secondary thread to run this bootstrap @@ -38,18 +38,28 @@ defmodule Expostal do end @doc """ + Parse given address into a map of address components ## Examples iex> Expostal.parse_address("615 Rene Levesque Ouest, Montreal, QC, Canada") - %{city: "montreal", country: "canada", house_number: "615", - road: "rene levesque ouest", state: "qc"} + %Expostal.Address{ + city: "montreal", + country: "canada", + house_number: "615", + road: "rene levesque ouest", + state: "qc" + } """ - @spec parse_address(address :: String.t) :: map - def parse_address(address) - def parse_address(_) do + @spec parse_address(address :: String.t()) :: map + def parse_address(address) do + parsed = _parse_address(address) + struct(Expostal.Address, parsed) + end + + defp _parse_address(_address) do case :erlang.phash2(1, 1) do 0 -> raise "Nif not loaded" 1 -> %{} @@ -57,20 +67,54 @@ defmodule Expostal do end @doc """ + Expand given address into a list of expansions ## Examples + iex> Expostal.expand_address("781 Franklin Ave Crown Hts Brooklyn NY") - ["781 franklin avenue crown heights brooklyn new york", - "781 franklin avenue crown heights brooklyn ny"] + ["781 franklin avenue crown heights brooklyn ny", + "781 franklin avenue crown heights brooklyn new york"] """ - @spec expand_address(address :: String.t) :: [String.t] - def expand_address(address) do + @spec expand_address(address :: String.t()) :: [String.t()] + def expand_address(address), do: _expand_address(address) + + defp _expand_address(_address) do case :erlang.phash2(1, 1) do 0 -> raise "Nif not loaded" - 1 -> [address] + 1 -> [] end end + @doc """ + + Returns a tuple with probability of the most probable language + for a given address and a language list + + ## Examples + + iex> Expostal.classify_language("agricola pl.") + {0.508300861587544, ["en", "fr", "es", "de"]} + + """ + @spec classify_language(address :: String.t()) :: + {float, [String.t()]} | {:error, :argument_error} + def classify_language(address) when is_binary(address) do + try do + _classify_language(address) + rescue + _ -> + {:error, :argument_error} + end + end + + defp _classify_language(""), do: {:error, :argument_error} + + defp _classify_language(_address) do + case :erlang.phash2(1, 1) do + 0 -> raise "Nif not loaded" + 1 -> {0.0, []} + end + end end diff --git a/mix.exs b/mix.exs index 62fe983..c8f5494 100644 --- a/mix.exs +++ b/mix.exs @@ -1,14 +1,15 @@ defmodule Mix.Tasks.Compile.Libpostal do def run(_) do - if match? {:win32, _}, :os.type do + if match?({:win32, _}, :os.type()) do # libpostal does not support Windows unfortunately. IO.warn("Windows is not supported.") exit(1) else File.mkdir_p("priv") {result, _error_code} = System.cmd("make", ["priv/expostal.so"], stderr_to_stdout: true) - IO.binwrite result + IO.binwrite(result) end + :ok end end @@ -17,20 +18,21 @@ defmodule Expostal.Mixfile do use Mix.Project def project do - [app: :expostal, - version: "0.2.0", - elixir: "~> 1.4", - build_embedded: Mix.env == :prod, - start_permanent: Mix.env == :prod, - compilers: [:libpostal, :elixir, :app], - docs: [main: "readme", - extras: ["README.md"]], - deps: deps(), - description: description(), - package: package(), - name: "Expostal", - source_url: "https://github.com/SweetIQ/expostal", - homepage_url: "https://github.com/SweetIQ/expostal"] + [ + app: :expostal, + version: "0.3.0", + elixir: "~> 1.13", + build_embedded: Mix.env() == :prod, + start_permanent: Mix.env() == :prod, + compilers: [:libpostal, :elixir, :app], + docs: [main: "readme", extras: ["README.md"]], + deps: deps(), + description: description(), + package: package(), + name: "Expostal", + source_url: "https://github.com/SweetIQ/expostal", + homepage_url: "https://github.com/SweetIQ/expostal" + ] end # Configuration for the OTP application @@ -52,8 +54,8 @@ defmodule Expostal.Mixfile do # Type "mix help deps" for more examples and options defp deps do [ - {:ex_doc, "~> 0.14", only: :dev, runtime: false}, - {:dialyxir, "~> 0.5", only: :dev, runtime: false} + {:ex_doc, "~> 0.30", only: :dev, runtime: false}, + {:dialyxir, "~> 1.3", only: :dev, runtime: false} ] end @@ -63,6 +65,7 @@ defmodule Expostal.Mixfile do Expostal parses street address and expand address acroymes with high accuracy. """ end + defp package do # These are the default files included in the package [ diff --git a/mix.lock b/mix.lock index 12866d2..48da661 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,10 @@ -%{"dialyxir": {:hex, :dialyxir, "0.5.0", "5bc543f9c28ecd51b99cc1a685a3c2a1a93216990347f259406a910cf048d1d7", [:mix], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.2", "f718159d6b65068e8daeef709ccddae5f7fdc770707d82e7d126f584cd925b74", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.16.1", "b4b8a23602b4ce0e9a5a960a81260d1f7b29635b9652c67e95b0c2f7ccee5e81", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, - "libpostal": {:git, "https://github.com/openvenues/libpostal.git", "8dd84b71bad4c70150e60c7bda8071b9bd8902f8", []}} +%{ + "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.30.3", "bfca4d340e3b95f2eb26e72e4890da83e2b3a5c5b0e52607333bf5017284b063", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "fbc8702046c1d25edf79de376297e608ac78cdc3a29f075484773ad1718918b6"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, +} diff --git a/src/expostal.c b/src/expostal.c index 9cfd83b..8d2a256 100644 --- a/src/expostal.c +++ b/src/expostal.c @@ -1,32 +1,30 @@ -#include #include +#include +#include #include #include -#include -#define HAS_DIRTY_SCHEDULER (ERL_NIF_MAJOR_VERSION > 2 || (ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 10)) +#define HAS_DIRTY_SCHEDULER \ + (ERL_NIF_MAJOR_VERSION > 2 || \ + (ERL_NIF_MAJOR_VERSION == 2 && ERL_NIF_MINOR_VERSION >= 10)) -static pthread_mutex_t* expostal_lock; +static pthread_mutex_t *expostal_lock; static int is_setup = 0; // make sure that the libpostal library is indeed already setup int do_bootstrap() { - if (is_setup == 0) - { - if (!libpostal_setup()) - { + if (is_setup == 0) { + if (!libpostal_setup()) { fprintf(stderr, "Error loading libpostal\r\n"); return 1; } - if (!libpostal_setup_parser()) - { + if (!libpostal_setup_parser()) { fprintf(stderr, "Error loading libpostal parser\r\n"); return 1; } - if (!libpostal_setup_language_classifier()) - { + if (!libpostal_setup_language_classifier()) { fprintf(stderr, "Error loading language classifier\r\n"); return 1; } @@ -35,50 +33,50 @@ int do_bootstrap() { return 0; } -static ERL_NIF_TERM -bootstrap(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { +static ERL_NIF_TERM bootstrap(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { // make sure nothing else is trying to bootstrap while we run pthread_mutex_lock(expostal_lock); int retval = do_bootstrap(); pthread_mutex_unlock(expostal_lock); - if (retval == 0) - { + if (retval == 0) { return enif_make_atom(env, "ok"); - } - else - { + } else { return enif_make_atom(env, "error"); } } -static ERL_NIF_TERM -expand_address(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { - // libpostal is not threadsafe (see https://github.com/openvenues/libpostal/issues/34) +static ERL_NIF_TERM _expand_address(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { + // libpostal is not threadsafe (see + // https://github.com/openvenues/libpostal/issues/34) pthread_mutex_lock(expostal_lock); do_bootstrap(); libpostal_normalize_options_t options = libpostal_get_default_options(); ErlNifBinary address_bin; - if (!enif_inspect_iolist_as_binary(env, argv[0], &address_bin)) - { + if (!enif_inspect_iolist_as_binary(env, argv[0], &address_bin)) { + pthread_mutex_unlock(expostal_lock); return enif_make_badarg(env); } - char *address = strndup((char*) address_bin.data, address_bin.size); + char *address = strndup((char *)address_bin.data, address_bin.size); size_t num_expansions = 0; - char **expansions = libpostal_expand_address(address, options, &num_expansions); + char **expansions = + libpostal_expand_address(address, options, &num_expansions); ERL_NIF_TERM *expansion_terms = malloc(sizeof(ERL_NIF_TERM) * num_expansions); unsigned long i; - for (i = 0; i < num_expansions; i++) - { + for (i = 0; i < num_expansions; i++) { char *expansion = expansions[i]; ERL_NIF_TERM expansion_term; - unsigned char *expansion_term_bin = enif_make_new_binary(env, strlen(expansion), &expansion_term); - strncpy((char *)expansion_term_bin, expansion, strlen(expansion)); + unsigned char *expansion_term_bin = + enif_make_new_binary(env, strlen(expansion), &expansion_term); + strcpy((char *)expansion_term_bin, expansion); expansion_terms[i] = expansion_term; } - ERL_NIF_TERM expansions_list_term = enif_make_list_from_array(env, expansion_terms, num_expansions); + ERL_NIF_TERM expansions_list_term = + enif_make_list_from_array(env, expansion_terms, num_expansions); free(expansion_terms); free(address); @@ -87,38 +85,37 @@ expand_address(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { return expansions_list_term; } -static ERL_NIF_TERM -parse_address(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{ +static ERL_NIF_TERM _parse_address(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { pthread_mutex_lock(expostal_lock); do_bootstrap(); - libpostal_address_parser_options_t options = libpostal_get_address_parser_default_options(); + libpostal_address_parser_options_t options = + libpostal_get_address_parser_default_options(); ERL_NIF_TERM components = enif_make_new_map(env); ErlNifBinary address_bin; - if (!enif_inspect_iolist_as_binary(env, argv[0], &address_bin)) - { + if (!enif_inspect_iolist_as_binary(env, argv[0], &address_bin)) { + pthread_mutex_unlock(expostal_lock); return enif_make_badarg(env); } - char *address = strndup((char*) address_bin.data, address_bin.size); - libpostal_address_parser_response_t *response = libpostal_parse_address(address, options); + char *address = strndup((char *)address_bin.data, address_bin.size); + libpostal_address_parser_response_t *response = + libpostal_parse_address(address, options); const char *component, *label; size_t i; - for (i = 0; i < response->num_components; i++) - { + for (i = 0; i < response->num_components; i++) { component = response->components[i]; label = response->labels[i]; ERL_NIF_TERM component_term; - unsigned char *component_term_bin = enif_make_new_binary(env, strlen(component), &component_term); - strncpy((char *)component_term_bin, component, strlen(component)); + unsigned char *component_term_bin = + enif_make_new_binary(env, strlen(component), &component_term); + strcpy((char *)component_term_bin, component); - enif_make_map_put(env, components, - enif_make_atom(env, label), - component_term, - &components); + enif_make_map_put(env, components, enif_make_atom(env, label), + component_term, &components); } enif_release_binary(&address_bin); @@ -129,45 +126,85 @@ parse_address(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) return components; } +static ERL_NIF_TERM _classify_language(ErlNifEnv *env, int argc, + const ERL_NIF_TERM argv[]) { + pthread_mutex_lock(expostal_lock); + do_bootstrap(); + + ErlNifBinary address_bin; + if (!enif_inspect_iolist_as_binary(env, argv[0], &address_bin)) { + pthread_mutex_unlock(expostal_lock); + return enif_make_badarg(env); + } + + char *address = strndup((char *)address_bin.data, address_bin.size); + libpostal_language_classifier_response_t *response = + libpostal_classify_language(address); + + free(address); + if (response == NULL) { + pthread_mutex_unlock(expostal_lock); + return enif_make_badarg(env); + } + + const char *language; + + size_t i; + ERL_NIF_TERM *language_terms = + malloc(sizeof(ERL_NIF_TERM) * response->num_languages); + for (i = 0; i < response->num_languages; i++) { + language = response->languages[i]; + ERL_NIF_TERM language_term; + unsigned char *language_term_bin = + enif_make_new_binary(env, strlen(language), &language_term); + strcpy((char *)language_term_bin, language); + language_terms[i] = language_term; + } + ERL_NIF_TERM probs_term = enif_make_double(env, *response->probs); + + ERL_NIF_TERM language_list_term = + enif_make_list_from_array(env, language_terms, response->num_languages); + + ERL_NIF_TERM response_tuple_term = + enif_make_tuple2(env, probs_term, language_list_term); + + free(language_terms); + libpostal_language_classifier_response_destroy(response); + + pthread_mutex_unlock(expostal_lock); + return response_tuple_term; +} + // map our C functions to their Elixir equivalents #if HAS_DIRTY_SCHEDULER static ErlNifFunc funcs[] = { {"bootstrap", 0, bootstrap, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"parse_address", 1, parse_address, ERL_NIF_DIRTY_JOB_IO_BOUND}, - {"expand_address", 1, expand_address, ERL_NIF_DIRTY_JOB_IO_BOUND} -}; + {"_classify_language", 1, _classify_language, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"_parse_address", 1, _parse_address, ERL_NIF_DIRTY_JOB_IO_BOUND}, + {"_expand_address", 1, _expand_address, ERL_NIF_DIRTY_JOB_IO_BOUND}}; #else -static ErlNifFunc funcs[] = { - {"bootstrap", 0, bootstrap}, - {"parse_address", 1, parse_address}, - {"expand_address", 1, expand_address} -}; +static ErlNifFunc funcs[] = {{"bootstrap", 0, bootstrap}, + {"_classify_language", 1, _classify_language}, + {"_parse_address", 1, _parse_address}, + {"_expand_address", 1, _expand_address}}; #endif -static int -load(ErlNifEnv *env, void **priv, ERL_NIF_TERM info) -{ +static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM info) { expostal_lock = malloc(sizeof(pthread_mutex_t)); pthread_mutex_init(expostal_lock, NULL); return 0; } -static int -reload(ErlNifEnv *env, void **priv, ERL_NIF_TERM info) -{ - return 0; -} +static int reload(ErlNifEnv *env, void **priv, ERL_NIF_TERM info) { return 0; } -static int -upgrade(ErlNifEnv *env, void **priv, void **old_priv, ERL_NIF_TERM info) -{ +static int upgrade(ErlNifEnv *env, void **priv, void **old_priv, + ERL_NIF_TERM info) { return load(env, priv, info); } -static void -unload(ErlNifEnv *env, void *priv) -{ +static void unload(ErlNifEnv *env, void *priv) { libpostal_teardown(); + libpostal_teardown_language_classifier(); libpostal_teardown_parser(); enif_free(priv); } diff --git a/test/expostal_test.exs b/test/expostal_test.exs index 8da5605..3c3b164 100644 --- a/test/expostal_test.exs +++ b/test/expostal_test.exs @@ -1,47 +1,123 @@ defmodule ExpostalTest do use ExUnit.Case doctest Expostal + test "parse address" do address = "615 Rene Levesque Ouest, Montreal, QC, Canada" parsed = Expostal.parse_address(address) - assert parsed == %{ - country: "canada", - city: "montreal", - house_number: "615", - road: "rene levesque ouest", - state: "qc" - } + + assert parsed == %Expostal.Address{ + city: "montreal", + country: "canada", + house_number: "615", + road: "rene levesque ouest", + state: "qc", + category: nil, + city_district: nil, + country_region: nil, + entrance: nil, + house: nil, + island: nil, + level: nil, + near: nil, + po_box: nil, + postcode: nil, + staircase: nil, + state_district: nil, + suburb: nil, + unit: nil, + world_region: nil + } address = "92 rue de l'Église, QC" parsed = Expostal.parse_address(address) - assert parsed == %{ - house_number: "92", - road: "rue de l'église", - state: "qc" - } + + assert parsed == + %Expostal.Address{ + house_number: "92", + road: "rue de l'église", + state: "qc", + category: nil, + city: nil, + city_district: nil, + country: nil, + country_region: nil, + entrance: nil, + house: nil, + island: nil, + level: nil, + near: nil, + po_box: nil, + postcode: nil, + staircase: nil, + state_district: nil, + suburb: nil, + unit: nil, + world_region: nil + } address = "天津市红桥区一号路一百号" parsed = Expostal.parse_address(address) - assert parsed == %{ - city: "天津市红桥区", - road: "一号路", - house_number: "一百号", - } + + assert parsed == %Expostal.Address{ + city: "天津市红桥区", + house_number: "一百号", + road: "一号路", + category: nil, + city_district: nil, + country: nil, + country_region: nil, + entrance: nil, + house: nil, + island: nil, + level: nil, + near: nil, + po_box: nil, + postcode: nil, + staircase: nil, + state: nil, + state_district: nil, + suburb: nil, + unit: nil, + world_region: nil + } end describe "Expand" do test "test address expansion" do assert address_in_expansion( - "781 Franklin Ave Crown Hts Brooklyn NY", - "781 franklin avenue crown heights brooklyn new york" - ) - assert address_in_expansion("781 Franklin Ave Crown Hts Brooklyn NY", "781 franklin avenue crown heights brooklyn new york") + "781 Franklin Ave Crown Hts Brooklyn NY", + "781 franklin avenue crown heights brooklyn new york" + ) - assert address_in_expansion("Friedrichstraße 128, Berlin, Germany", "friedrich straße 128 berlin germany") + assert address_in_expansion( + "781 Franklin Ave Crown Hts Brooklyn NY", + "781 franklin avenue crown heights brooklyn new york" + ) + + assert address_in_expansion( + "Friedrichstraße 128, Berlin, Germany", + "friedrich strasse 128 berlin germany" + ) assert address_in_expansion("MAPLE ST.", "maple street") assert address_in_expansion("MAPLE ST.", "maple street") + end + end + + describe "Classify" do + test "test address expansion" do + assert Expostal.classify_language("") == {:error, :argument_error} + + assert Expostal.classify_language("781 Franklin Ave Crown Hts Brooklyn NY") == + {0.9999999997485425, ["en"]} + + assert Expostal.classify_language("Friedrichstraße 128, Berlin, Germany") == + {0.9999970742636454, ["de"]} + assert Expostal.classify_language("天津市红桥区一号路一百号") == {0.9241848948924529, ["ja", "zh"]} + assert Expostal.classify_language("MAPLE ST.") == {0.9996832089814511, ["en"]} + assert Expostal.classify_language("92 rue de l'Église, QC") == {0.9999999997818882, ["fr"]} end end