Commit initially

This commit is contained in:
Robert Prehn 2024-02-22 18:24:26 -06:00
commit 73dd9cc152
No known key found for this signature in database
15 changed files with 260 additions and 0 deletions

4
.formatter.exs Normal file
View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
# The directory Mix will write compiled artifacts to.
/_build/
# 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").
kindling-*.tar
# Temporary files, for example, from tests.
/tmp/

21
README.md Normal file
View file

@ -0,0 +1,21 @@
# Kindling
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `kindling` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:kindling, "~> 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/kindling>.

18
lib/kindling.ex Normal file
View file

@ -0,0 +1,18 @@
defmodule Kindling do
@moduledoc """
Documentation for `Kindling`.
"""
@doc """
Hello world.
## Examples
iex> Kindling.hello()
:world
"""
def hello do
:world
end
end

11
lib/kindling/schema.ex Normal file
View file

@ -0,0 +1,11 @@
defmodule Kindling.Schema do
alias Kindling.Version
def schema_object(version) do
filename = Path.join(Version.version_dir(version), "fhir.schema.json")
filename
|> File.read!()
|> Jason.decode!()
end
end

View file

@ -0,0 +1,32 @@
defmodule Kindling.Schema.Resource do
@empty_properties %{const: [], has_one: [], has_many: [], enum: [], value: []}
def properties(df) do
df["properties"]
end
def grouped_properties(df) do
properties =
(df["properties"] || [])
|> Enum.map(fn {key, value} -> {key, value, property_type(value)} end)
|> Enum.group_by(fn {_key, _value, type} ->
type
end)
Map.merge(@empty_properties, properties)
end
def required(df) do
Map.take(df["properties"], df["required"])
end
def required?(df, key) do
key in df["required"]
end
def property_type(%{"const" => _}), do: :const
def property_type(%{"$ref" => _}), do: :has_one
def property_type(%{"items" => _}), do: :has_many
def property_type(%{"enum" => _}), do: :enum
def property_type(_), do: :value
end

View file

@ -0,0 +1,19 @@
defmodule Kindling.SchemaDownloader do
def download_version(version) do
version_dir = "#{Mix.Project.build_path(Mix.Project.config())}/fhir/#{version}"
filename = "#{version_dir}/fhir.schema.json.zip"
File.mkdir_p!(version_dir)
"http://hl7.org/fhir/#{version}/fhir.schema.json.zip"
|> Req.get!()
|> then(&File.write!(filename, &1.body))
case :zip.unzip(to_charlist(filename), cwd: to_charlist(version_dir)) do
{:ok, _filelist} ->
:ok
other ->
other
end
end
end

21
lib/kindling/template.eex Normal file
View file

@ -0,0 +1,21 @@
defmodule <%= @namespace %>.<%= @version %>.<%= @resource_name %> do
use Ecto.Schema
schema "<%= Recase.to_snake(@resource_name) %>" do
<%= if @properties.const != [] do %># Constants<% end %>
<%= for {name, df, _} <- @properties.const do %>field :<%= Recase.to_snake(name) %>, :string, virtual: true, default: "<%= df["const"] %>"
<% end %>
<%= if @properties.value != [] do %># Fields<% end %>
<%= for {name, df, _} <- @properties.value do %>field :<%= Recase.to_snake(name) %>, :<%= fhir_type_to_ecto(df) %>
<% end %>
<%= if @properties.enum != [] do %># Enum<% end %>
<%= for {name, df, _} <- @properties.enum do %>field :<%= Recase.to_snake(name) %>, Ecto.Enum, values: <%= inspect(df["enum"] |> Enum.map(&Recase.to_snake(&1)) |> Enum.map(&String.to_atom/1)) %>
<% end %>
<%= if @properties.has_one != [] do %># Has One<% end %>
<%= for {name, df, _} <- @properties.has_one do %>has_one :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["$ref"]) %>
<% end %>
<%= if @properties.has_many != [] do %># Has Many<% end %>
<%= for {name, df, _} <- @properties.has_many do %>has_many :<%= Recase.to_snake(name) %>, <%= @namespace %>.<%= @version %>.<%= ref_to_class_name(df["items"]["$ref"]) %>
<% end %>
end
end

28
lib/kindling/templates.ex Normal file
View file

@ -0,0 +1,28 @@
defmodule Kindling.Templates do
require EEx
import Kindling.Templates.Functions
alias Kindling.Schema.Resource
EEx.function_from_file(:def, :render, "lib/kindling/template.eex", [:assigns])
def resource_code(namespace, version, resource_name, resource) do
assigns = %{
namespace: namespace,
version: version,
resource_name: resource_name,
properties: Resource.grouped_properties(resource)
}
assigns |> render() |> Code.format_string!(file: "#{resource_name}.ex")
end
def write_code(namespace, version, resource_name, resource) do
dir = Path.join(String.downcase(namespace), String.downcase(version))
file = Path.join(dir, "#{Recase.to_snake(resource_name)}.ex")
File.mkdir_p!(dir)
File.write!(file, resource_code(namespace, version, resource_name, resource))
end
end

View file

@ -0,0 +1,22 @@
defmodule Kindling.Templates.Functions do
@int_patterns [
"^[1-9][0-9]*$",
"^[0]|([1-9][0-9]*)$",
"^-?([0]|([1-9][0-9]*))$"
]
@decimal_patterns ["^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+-]?[0-9]+)?$"]
def fhir_type_to_ecto(%{"type" => "string"}), do: :string
def fhir_type_to_ecto(%{"type" => "number", "pattern" => pattern})
when pattern in @int_patterns,
do: :integer
def fhir_type_to_ecto(%{"type" => "number", "pattern" => pattern})
when pattern in @decimal_patterns,
do: :decimal
def ref_to_class_name("#/definitions/" <> name),
do: name |> String.replace("_", ".") |> Recase.to_pascal()
end

4
lib/kindling/version.ex Normal file
View file

@ -0,0 +1,4 @@
defmodule Kindling.Version do
def version_dir(version),
do: Path.join([Mix.Project.build_path(Mix.Project.config()), "fhir", version])
end

31
mix.exs Normal file
View file

@ -0,0 +1,31 @@
defmodule Kindling.MixProject do
use Mix.Project
def project do
[
app: :kindling,
version: "0.1.0",
elixir: "~> 1.15",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:jason, "~> 1.4"},
{:recase, "~> 0.7.0"},
{:req, "~> 0.4.11"}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

14
mix.lock Normal file
View file

@ -0,0 +1,14 @@
%{
"castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
"nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"},
"nimble_ownership": {:hex, :nimble_ownership, "0.2.1", "3e44c72ebe8dd213db4e13aff4090aaa331d158e72ce1891d02e0ffb05a1eb2d", [:mix], [], "hexpm", "bf38d2ef4fb990521a4ecf112843063c1f58a5c602484af4c7977324042badee"},
"nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"},
"recase": {:hex, :recase, "0.7.0", "3f2f719f0886c7a3b7fe469058ec539cb7bbe0023604ae3bce920e186305e5ae", [:mix], [], "hexpm", "36f5756a9f552f4a94b54a695870e32f4e72d5fad9c25e61bc4a3151c08a4e0c"},
"req": {:hex, :req, "0.4.11", "cb19f87d5251e7de30cfc67d1899696b290711092207c6b2e8fc2294f237fcdc", [:mix], [{:aws_signature, "~> 0.3.2", [hex: :aws_signature, repo: "hexpm", optional: true]}, {:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:nimble_ownership, "~> 0.2.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bbf4f2393c649fa4146a3b8470e2a7e8c9b23e4100a16c75f5e7d1d3d33144f3"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
}

8
test/kindling_test.exs Normal file
View file

@ -0,0 +1,8 @@
defmodule KindlingTest do
use ExUnit.Case
doctest Kindling
test "greets the world" do
assert Kindling.hello() == :world
end
end

1
test/test_helper.exs Normal file
View file

@ -0,0 +1 @@
ExUnit.start()