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/.gitignore b/.gitignore index fc5d8e3..cd582a4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,29 @@ -# ---> Elixir -/_build -/cover -/deps -/doc -/.fetch -erl_crash.dump -*.ez -*.beam -/config/*.secret.exs -.elixir_ls/ +# The directory Mix will write compiled artifacts to. +/_build/ +/.elixir_ls/ +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +ex_datagovua-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +.DS_Store \ No newline at end of file diff --git a/.iex.exs b/.iex.exs new file mode 100644 index 0000000..7d8f701 --- /dev/null +++ b/.iex.exs @@ -0,0 +1,3 @@ +alias ExDataGovUA.Package +alias ExDataGovUA.Package.Meta +alias ExDataGovUA.Package.Meta.Fetcher diff --git a/README.md b/README.md index 00ad7f4..1875b85 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ -# ex_data.gov.ua +# ExDatagovua + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `ex_datagovua` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:ex_datagovua, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/ex_datagovua](https://hexdocs.pm/ex_datagovua). -data.gov.ua universal fetcher \ No newline at end of file diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..6779e4a --- /dev/null +++ b/config/config.exs @@ -0,0 +1,20 @@ +use Mix.Config + +config :ex_datagovua, + default_fetcher: ExDataGovUA.Fetchers.FinchFetcher, + default_decoder: ExDataGovUA.Decoders.JasonDecoder, + file_formats: [ + "json" + ], + container_formats: [ + "gz" + ], + packages: %{ + "cf37fc56-2eb5-4b6c-81d2-d790e3264111": %{ + # schedule: "0 0 1 * *", + # file: :all + # mode: :rewrite + } + } + +import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/dev.exs @@ -0,0 +1 @@ +import Config diff --git a/config/prod.exs b/config/prod.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/prod.exs @@ -0,0 +1 @@ +import Config diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..becde76 --- /dev/null +++ b/config/test.exs @@ -0,0 +1 @@ +import Config diff --git a/lib/ex_data_gov_u_a/application.ex b/lib/ex_data_gov_u_a/application.ex new file mode 100644 index 0000000..1879f64 --- /dev/null +++ b/lib/ex_data_gov_u_a/application.ex @@ -0,0 +1,18 @@ +defmodule ExDataGovUA.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + def start(_type, _args) do + children = [ + # {Finch, name: ExDataGovUA.Finch} + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: ExDataGovUA.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/lib/ex_data_gov_u_a/archivers/base.ex b/lib/ex_data_gov_u_a/archivers/base.ex new file mode 100644 index 0000000..ccee676 --- /dev/null +++ b/lib/ex_data_gov_u_a/archivers/base.ex @@ -0,0 +1,7 @@ +defmodule ExDataGovUA.Archivers.Base do + @type stream :: Stream.t() + + @callback deflate(stream) :: stream + + @callback inflate(stream) :: stream +end diff --git a/lib/ex_data_gov_u_a/archivers/gz.ex b/lib/ex_data_gov_u_a/archivers/gz.ex new file mode 100644 index 0000000..9135f1f --- /dev/null +++ b/lib/ex_data_gov_u_a/archivers/gz.ex @@ -0,0 +1,19 @@ +defmodule ExDataGovUA.Archivers.Gz do + @behaviour ExDataGovUA.Archivers.Base + + @type input :: Stream.t() + @type output :: Stream.t() + + @spec deflate(input) :: output + @doc """ + Pack data to archive + """ + def deflate(stream), do: stream |> StreamGzip.gzip + + + @spec inflate(input) :: output + @doc """ + Extract data from archive + """ + def inflate(stream), do: stream |> StreamGzip.gunzip +end diff --git a/lib/ex_data_gov_u_a/commons/http_stream.ex b/lib/ex_data_gov_u_a/commons/http_stream.ex new file mode 100644 index 0000000..6b68a0c --- /dev/null +++ b/lib/ex_data_gov_u_a/commons/http_stream.ex @@ -0,0 +1,93 @@ +defmodule ExDataGovUA.Commons.HttpStream do + def get(url,emit_end\\false) do + + Stream.resource( + fn -> start_fun(url) end, + + # next_fun (multi caluses) + fn + %HTTPoison.AsyncResponse{}=resp -> + handle_async_resp(resp,emit_end) + + #last accumulator when emitting :end + {:end, resp}-> + {:halt, resp} + end, + + fn %HTTPoison.AsyncResponse{id: id} -> + IO.puts("END_FUN") + :hackney.stop_async(id) + end + ) + end + + + defp start_fun(url) do + HTTPoison.get!(url,%{}, [stream_to: self(), async: :once]) + end + + + defp handle_async_resp(%HTTPoison.AsyncResponse{id: id}=resp,emit_end) do + receive do + %HTTPoison.AsyncStatus{id: ^id, code: code}-> + IO.inspect(code, label: "STATUS: ") + HTTPoison.stream_next(resp) + {[], resp} + %HTTPoison.AsyncHeaders{id: ^id, headers: headers}-> + IO.inspect(headers, label: "HEADERS: ") + HTTPoison.stream_next(resp) + {[], resp} + %HTTPoison.AsyncChunk{id: ^id, chunk: chunk}-> + HTTPoison.stream_next(resp) + # :erlang.garbage_collect() + {[chunk], resp} + %HTTPoison.AsyncEnd{id: ^id}-> + if emit_end do + {[:end], {:end, resp}} + else + {:halt, resp} + end + after + 5_000 -> raise "receive timeout" + end + end + + def lines(enum), do: lines(enum, :string_split) + + def lines(enum, :next_lines) do + enum + |> Stream.transform("",&next_lines/2) + end + + def lines(enum, :string_split) do + enum + |> Stream.transform("",fn + :end, acc -> + {[acc],""} + chunk, acc -> + [last_line | lines] = + String.split(acc <> chunk,"\n") + |> Enum.reverse() + {Enum.reverse(lines),last_line} + end) + end + + + defp next_lines(:end,prev), do: {[prev], ""} + defp next_lines(chunk,current_line) do + # :erlang.garbage_collect() + next_lines(chunk,current_line,[]) + end + + defp next_lines(<<"\n"::utf8, rest::binary>>,current_line,lines) do + next_lines(rest,"",[<> | lines]) + end + + defp next_lines(<>,current_line,lines) do + next_lines(rest,<>,lines) + end + + defp next_lines(<<>>,current_line,lines) do + {Enum.reverse(lines), current_line} + end +end diff --git a/lib/ex_data_gov_u_a/decoders/base.ex b/lib/ex_data_gov_u_a/decoders/base.ex new file mode 100644 index 0000000..66ef421 --- /dev/null +++ b/lib/ex_data_gov_u_a/decoders/base.ex @@ -0,0 +1,9 @@ +defmodule ExDataGovUA.Decoders.Base do + @type data :: map() + @type status :: Atom.t() + @type async_response :: Stream.t() + @type sync_response :: {status, data} + @type body :: String.t() | Stream.t() + + @callback decode(body) :: async_response | sync_response +end diff --git a/lib/ex_data_gov_u_a/decoders/jason_decoder.ex b/lib/ex_data_gov_u_a/decoders/jason_decoder.ex new file mode 100644 index 0000000..ac697b4 --- /dev/null +++ b/lib/ex_data_gov_u_a/decoders/jason_decoder.ex @@ -0,0 +1,8 @@ +defmodule ExDataGovUA.Decoders.JasonDecoder do + @behaviour ExDataGovUA.Decoders.Base + + @impl true + def decode(data) do + Jason.decode(data, [keys: :atoms]) + end +end diff --git a/lib/ex_data_gov_u_a/decoders/jaxon_decoder.ex b/lib/ex_data_gov_u_a/decoders/jaxon_decoder.ex new file mode 100644 index 0000000..c07c8ea --- /dev/null +++ b/lib/ex_data_gov_u_a/decoders/jaxon_decoder.ex @@ -0,0 +1,9 @@ +defmodule ExDataGovUA.Decoders.JaxonDecoder do + @behaviour ExDataGovUA.Decoders.Base + + @impl true + def decode(stream) do + stream + |> Jaxon.Stream.from_enumerable() + end +end diff --git a/lib/ex_data_gov_u_a/fetchers/base.ex b/lib/ex_data_gov_u_a/fetchers/base.ex new file mode 100644 index 0000000..971ed18 --- /dev/null +++ b/lib/ex_data_gov_u_a/fetchers/base.ex @@ -0,0 +1,8 @@ +defmodule ExDataGovUA.Fetchers.Base do + @type data :: map() + @type status :: Atom.t() + @type url :: String.t() + @type method :: Atom.t() + + @callback fetch(method, url) :: {status, data} +end diff --git a/lib/ex_data_gov_u_a/fetchers/finch_fetcher.ex b/lib/ex_data_gov_u_a/fetchers/finch_fetcher.ex new file mode 100644 index 0000000..9e386cf --- /dev/null +++ b/lib/ex_data_gov_u_a/fetchers/finch_fetcher.ex @@ -0,0 +1,19 @@ +defmodule ExDataGovUA.Fetchers.FinchFetcher do + @behaviour ExDataGovUA.Fetchers.Base + + @impl true + def fetch(method, url) do + start() + with request <- Finch.build(method, url), + {:ok, %Finch.Response{body: body}} <- Finch.request(request, MyFinchFetcher) do + {:ok, body} + end + end + + defp start() do + case Finch.start_link(name: MyFinchFetcher) do + {:ok, pid} -> pid + {:error, {:already_started, pid}} -> pid + end + end +end diff --git a/lib/ex_data_gov_u_a/package/base.ex b/lib/ex_data_gov_u_a/package/base.ex new file mode 100644 index 0000000..d1ae62a --- /dev/null +++ b/lib/ex_data_gov_u_a/package/base.ex @@ -0,0 +1,3 @@ +defmodule ExDataGovUA.Package.Base do + defstruct [:id, :meta] +end diff --git a/lib/ex_data_gov_u_a/package/meta/base.ex b/lib/ex_data_gov_u_a/package/meta/base.ex new file mode 100644 index 0000000..ddb5f8c --- /dev/null +++ b/lib/ex_data_gov_u_a/package/meta/base.ex @@ -0,0 +1,33 @@ +defmodule ExDataGovUA.Package.Meta.Base do + defstruct [ + :profile, + :name, + :contributors, + :created, + :title, + :keywords, + :version, + :licenses, + :homepage, + :id, + :resources, + :description + ] + + @type t :: %__MODULE__{ + profile: String.t(), + name: String.t(), + contributors: List.t(), + created: String.t(), + title: String.t(), + keywords: List.t(), + version: String.t(), + licenses: List.t(), + homepage: String.t(), + id: String.t(), + resources: List.t(), + description: String.t() + } + + use ExConstructor +end diff --git a/lib/ex_data_gov_u_a/package/meta/parser.ex b/lib/ex_data_gov_u_a/package/meta/parser.ex new file mode 100644 index 0000000..a7096a7 --- /dev/null +++ b/lib/ex_data_gov_u_a/package/meta/parser.ex @@ -0,0 +1,30 @@ +defmodule ExDataGovUA.Package.Meta.Parser do + @default_fetcher Application.get_env(:ex_datagovua, :default_fetcher) + @default_decoder Application.get_env(:ex_datagovua, :default_decoder) + + @type result :: %ExDataGovUA.Package.Meta.Base{} + @type id :: String.t() + @type url :: String.t() + @type fetcher :: Atom.t() + @type decoder :: Atom.t() + + @doc """ + Return meta information URL by data package ID + """ + @spec url(id) :: url + def url(id) do + "https://data.gov.ua/api/3/action/package_show?id=#{id}" + end + + @doc """ + Return meta information (ExDataGovUA.Package.Meta.Base) by data package ID + """ + @spec parse(id, fetcher, decoder) :: %ExDataGovUA.Package.Meta.Base{} + def parse(id, fetcher \\ @default_fetcher, decoder \\ @default_decoder) do + with {:ok, body} <- fetcher.fetch(:get, url(id)), + {:ok, map} <- decoder.decode(body), + result <- ExDataGovUA.Package.Meta.Base.new(map.result) do + result + end + end +end diff --git a/lib/ex_data_gov_u_a/resource/base.ex b/lib/ex_data_gov_u_a/resource/base.ex new file mode 100644 index 0000000..fac21f6 --- /dev/null +++ b/lib/ex_data_gov_u_a/resource/base.ex @@ -0,0 +1,11 @@ +defmodule ExDataGovUA.Resource.Base do + def download(url, _filename) do + url + |> ExDataGovUA.Streamers.HttpStreamer.get() + |> ExDataGovUA.Archivers.Gz.inflate() + |> ExDataGovUA.Decoders.JaxonDecoder.decode() + |> Enum.to_list() + # |> Stream.run() + # |> ExDataGovUA.Streamers.FileStreamer.export(filename) + end +end diff --git a/lib/ex_data_gov_u_a/streamers/file_streamer.ex b/lib/ex_data_gov_u_a/streamers/file_streamer.ex new file mode 100644 index 0000000..38f7b6b --- /dev/null +++ b/lib/ex_data_gov_u_a/streamers/file_streamer.ex @@ -0,0 +1,14 @@ +defmodule ExDataGovUA.Streamers.FileStreamer do + @chunk_size 2048 + @spec export(Stream.t(), String.t()) :: Atom.t() + def export(stream, filename) do + stream + |> Stream.into(File.stream!(filename, [], @chunk_size)) + end + + @spec import(String.t()) :: File.Stream.t() + def import(filename) do + filename + |> File.stream!([], @chunk_size) + end +end diff --git a/lib/ex_data_gov_u_a/streamers/http_streamer.ex b/lib/ex_data_gov_u_a/streamers/http_streamer.ex new file mode 100644 index 0000000..96502b5 --- /dev/null +++ b/lib/ex_data_gov_u_a/streamers/http_streamer.ex @@ -0,0 +1,8 @@ +defmodule ExDataGovUA.Streamers.HttpStreamer do + alias ExDataGovUA.Commons.HttpStream + + def get(url) do + url + |> HttpStream.get() + end +end diff --git a/lib/ex_datagovua.ex b/lib/ex_datagovua.ex new file mode 100644 index 0000000..8916239 --- /dev/null +++ b/lib/ex_datagovua.ex @@ -0,0 +1,18 @@ +defmodule ExDataGovUA do + @moduledoc """ + Documentation for `ExDatagovua`. + """ + + @doc """ + Hello world. + + ## Examples + + iex> ExDatagovua.hello() + :world + + """ + def hello do + :world + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..ce07d58 --- /dev/null +++ b/mix.exs @@ -0,0 +1,42 @@ +defmodule ExDataGovUA.MixProject do + use Mix.Project + + def project do + [ + app: :ex_datagovua, + version: "0.1.0", + elixir: "~> 1.12", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: applications(Mix.env()), + mod: {ExDataGovUA.Application, []} + ] + end + + defp applications(:dev), do: applications(:all) ++ ~w(remix)a + defp applications(_all), do: ~w(logger)a + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:jason, "~> 1.2.2"}, + {:jaxon, "~> 2.0.8"}, + {:finch, "~> 0.8.0"}, + {:httpoison, "~> 1.8.0"}, + {:stream_gzip, "~> 0.4.1"}, + {:exconstructor, "~> 1.2.2"}, + {:ex_doc, "~> 0.24.2", only: :dev, runtime: false}, + {:observer_cli, "~> 1.6.2", only: :dev, runtime: false}, + {:credo, "~> 1.5.6", only: :dev, runtime: false}, + {:dialyxir, "~> 1.1.0", only: :dev, runtime: false}, + # Remix for autorestart + {:remix, "~> 0.0.2", only: :dev} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..d375d83 --- /dev/null +++ b/mix.lock @@ -0,0 +1,36 @@ +%{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "castore": {:hex, :castore, "0.1.11", "c0665858e0e1c3e8c27178e73dffea699a5b28eb72239a3b2642d208e8594914", [:mix], [], "hexpm", "91b009ba61973b532b84f7c09ce441cba7aa15cb8b006cf06c6f4bba18220081"}, + "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"}, + "credo": {:hex, :credo, "1.5.6", "e04cc0fdc236fefbb578e0c04bd01a471081616e741d386909e527ac146016c6", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "4b52a3e558bd64e30de62a648518a5ea2b6e3e5d2b164ef5296244753fc7eb17"}, + "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.13", "0c98163e7d04a15feb62000e1a891489feb29f3d10cb57d4f845c405852bbef8", [:mix], [], "hexpm", "d602c26af3a0af43d2f2645613f65841657ad6efc9f0e361c3b6c06b578214ba"}, + "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.24.2", "e4c26603830c1a2286dae45f4412a4d1980e1e89dc779fcd0181ed1d5a05c8d9", [:mix], [{:earmark_parser, "~> 1.4.0", [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", "e134e1d9e821b8d9e4244687fb2ace58d479b67b282de5158333b0d57c6fb7da"}, + "exconstructor": {:hex, :exconstructor, "1.2.3", "1f40baf7669aef0ac66325eb02cb119e708d24b490ace05a27bc1b42e30113b2", [:mix], [], "hexpm", "c7f549596308e6ba66aa66a108ddd1048d5654c4f0aa49d55d15df3989554f34"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "finch": {:hex, :finch, "0.8.0", "9fe1b7b1613f4f0f43ac4be94462a0f3eb13264e5e9a624005da5670e452a1d1", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "07650fd74da3fb51984b9f62bf4db3ef720f3c06d914d716cceeb13a2bc2542d"}, + "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"}, + "httpoison": {:hex, :httpoison, "1.8.0", "6b85dea15820b7804ef607ff78406ab449dd78bed923a49c7160e1886e987a3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "28089eaa98cf90c66265b6b5ad87c59a3729bea2e74e9d08f9b51eb9729b3c3a"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, + "jaxon": {:hex, :jaxon, "2.0.8", "00951a79d354260e28d7e36f956c3de94818124768a4b22e0fc55559d1b3bfe7", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "74532853b1126609615ea98f0ceb5009e70465ca98027afbbd8ed314d887e82d"}, + "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mint": {:hex, :mint, "1.3.0", "396b3301102f7b775e103da5a20494b25753aed818d6d6f0ad222a3a018c3600", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "a9aac960562e43ca69a77e5176576abfa78b8398cec5543dd4fb4ab0131d5c1e"}, + "nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "nimble_pool": {:hex, :nimble_pool, "0.2.4", "1db8e9f8a53d967d595e0b32a17030cdb6c0dc4a451b8ac787bf601d3f7704c3", [:mix], [], "hexpm", "367e8071e137b787764e6a9992ccb57b276dc2282535f767a07d881951ebeac6"}, + "observer_cli": {:hex, :observer_cli, "1.6.2", "016588e9a966247401bcbf02976d468f1e6f06891dde44f873c9259c6496cca1", [:mix, :rebar3], [{:recon, "~>2.5.1", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "c23db9e4cca0e849adc42b0a099affb9e6267c5f23a871fc6f144348b249341f"}, + "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, + "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"}, + "remix": {:hex, :remix, "0.0.2", "f06115659d8ede8d725fae1708920ef73353a1b39efe6a232d2a38b1f2902109", [:mix], [], "hexpm", "5f5555646ed4fca83fab8620735150aa0bc408c5a17a70d28cfa7086bc6f497c"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, + "stream_gzip": {:hex, :stream_gzip, "0.4.1", "d5f611b3fa8f5c9d928db4c8446edb7e22bebdf38d9914b4017a6fff44887b26", [:mix], [], "hexpm", "343dee3cc30dc78562bb524e8ea802a13d6377fc6ef1c05ac4c9d9fb1f58044b"}, + "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, +} diff --git a/test/ex_datagovua_test.exs b/test/ex_datagovua_test.exs new file mode 100644 index 0000000..577e7d0 --- /dev/null +++ b/test/ex_datagovua_test.exs @@ -0,0 +1,8 @@ +defmodule ExDataGovUATest do + use ExUnit.Case + doctest ExDataGovUA + + test "greets the world" do + assert ExDataGovUA.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()