defmodule QuickBEAM.JS.Exports do
@moduledoc false
@type export_map :: String.t() | [export_map()] | %{String.t() => export_map()} | nil
@spec parse(map()) :: %{String.t() => String.t() | map()} | nil
def parse(%{"exports" => exports}) when is_binary(exports), do: %{"." => exports}
def parse(%{"exports" => exports}) when is_map(exports) do
if subpath_exports?(exports), do: exports, else: %{"." => exports}
end
def parse(_), do: nil
@spec resolve(map(), String.t(), [String.t()]) :: {:ok, String.t()} | :error
def resolve(export_map, subpath, conditions \\ ["default"]) do
export_map
|> candidates(subpath, conditions)
|> Enum.find_value(:error, &{:ok, &1})
end
defp subpath_exports?(map) do
Enum.any?(Map.keys(map), &String.starts_with?(&1, "."))
end
defp candidates(export_map, subpath, conditions) when is_map(export_map) do
exact = export_map |> Map.get(subpath) |> target_candidates(conditions)
wildcard =
Enum.flat_map(export_map, fn {pattern, target} ->
case wildcard_replacement(pattern, subpath) do
nil -> []
replacement -> replace_wildcards(target_candidates(target, conditions), replacement)
end
end)
exact ++ wildcard
end
defp candidates(_, _, _), do: []
defp target_candidates(nil, _conditions), do: []
defp target_candidates(path, _conditions) when is_binary(path), do: [path]
defp target_candidates(list, conditions) when is_list(list) do
Enum.flat_map(list, &target_candidates(&1, conditions))
end
defp target_candidates(target, conditions) when is_map(target) do
Enum.flat_map(conditions, fn condition ->
target |> Map.get(condition) |> target_candidates(conditions)
end)
end
defp target_candidates(_, _), do: []
defp replace_wildcards(paths, replacement) do
Enum.map(paths, &String.replace(&1, "*", replacement))
end
defp wildcard_replacement(pattern, subpath) do
case String.split(pattern, "*", parts: 2) do
[prefix, suffix] ->
if String.starts_with?(subpath, prefix) and String.ends_with?(subpath, suffix) do
subpath
|> String.trim_leading(prefix)
|> trim_suffix(suffix)
end
_ ->
nil
end
end
defp trim_suffix(value, ""), do: value
defp trim_suffix(value, suffix), do: String.trim_trailing(value, suffix)
end