Current section
Files
Jump to
Current section
Files
lib/ccxt/extract/json_loader.ex
defmodule CCXT.Extract.JsonLoader do
@moduledoc """
Generates JSON loading boilerplate with `:persistent_term` caching.
Eliminates copy-pasted `@json_path`, `path/0`, `load/0`, `reload!/0`,
and `read_json!/0` across 5 loader modules.
## Usage
use CCXT.Extract.JsonLoader, file: "ccxt_parse_methods.json"
Generates:
- `@json_path` — compile-time resolved path to `priv/extractor/<file>`
- `path/0` — returns the JSON file path
- `load/0` — loads with `:persistent_term` caching
- `reload!/0` — bypasses cache, re-reads from disk
- `read_json!/0` (private) — reads and decodes JSON from disk
"""
defmacro __using__(opts) do
file = Keyword.fetch!(opts, :file)
quote do
# Inline priv path resolution to avoid compile-time dependency on CCXT.Priv
# (which would cause cascading recompilation of ~200 exchange modules)
@json_path (case :code.priv_dir(:ccxt_client) do
{:error, :bad_name} ->
[__DIR__, "..", "..", "priv", "extractor", unquote(file)]
|> Path.join()
|> Path.expand()
priv when is_list(priv) ->
Path.join(List.to_string(priv), "extractor/" <> unquote(file))
end)
@doc "Returns the JSON file path."
@spec path() :: String.t()
def path, do: @json_path
@doc """
Loads the JSON data with `:persistent_term` caching.
Call `reload!/0` to force re-read from disk.
"""
@spec load() :: map()
def load do
case :persistent_term.get({__MODULE__, :data}, :missing) do
:missing ->
data = read_json!()
:persistent_term.put({__MODULE__, :data}, data)
data
data ->
data
end
end
@doc "Reloads JSON from disk, bypassing cache."
@spec reload!() :: map()
def reload! do
data = read_json!()
:persistent_term.put({__MODULE__, :data}, data)
data
end
@doc false
# sobelow_skip ["Traversal.FileModule"]
# Reads and parses the JSON file from disk (path is a compile-time constant)
defp read_json! do
@json_path
|> File.read!()
|> Jason.decode!()
end
end
end
end