defmodule Skogsra do @moduledoc """ This module defines the macros needed to use `Skogsra` e.g: ```elixir defmodule MyApp.Settings do use Skogsra @envdoc "My hostname" app_env :my_hostname, :myapp, :hostname, default: "localhost" end ``` """ alias Skogsra.Core alias Skogsra.Docs alias Skogsra.Env @doc """ For now is just equivalent to use `import Skogsra`. """ defmacro __using__(_) do quote do import Skogsra end end @doc """ Creates a function to retrieve specific environment/application variables values. The function created is named `function_name` and will get the value associated with an application called `app_name` and one or several `parameters` keys. Optionally, receives a list of `options`. Available options: Option | Type | Default | Description :------------ | :------------------- | :------------ | :---------- `default` | `any` | `nil` | Sets the Default value for the variable. `type` | `Skogsra.Env.type()` | `:binary` | Sets the explicit type for the variable. `os_env` | `binary` | autogenerated | Overrides automatically generated OS environment variable name. `skip_system` | `boolean` | `false` | Whether it should skip looking for a value in the OS or not. `skip_config` | `boolean` | `false` | Whether it should skip looking for a value in the application config or not. `required` | `boolean` | `false` | Whether the variable is required or not. `cached` | `boolean` | `true` | Whether the variable should be cached or not. `namespace` | `module` | `nil` | Overrides any namespace. e.g: For the following declaration: ``` app_env :db_password, :myapp, [:mydb, :password], default: "password", ``` will generate: - `db_password/0` and `db_password/1` for getting the variable's value without or with namespace respectively. It returns `:ok` and `:error` tuples. - `db_password!/0` and `db_password!/1` for getting the variable's value without or with namespace respectively. It fails on error. - `reload_db_password/0` and `reload_db_password/1` for reloading the variable's value in the cache without or with namespace respectively. - `put_db_password/1` and `put_db_password/2` for settings a new value for the variable directly to the cache without or with namespace respectively. A call to `db_password/0` will try to get a value: 1. From the OS environment variable `$MYAPP_MYDB_PASSWORD` (can be overriden by the option `os_env`). 2. From the configuration file e.g: ``` config :myapp, mydb: [password: "some password"] ``` 3. From the default value if it exists (In this case, it would return `"password"`). A call to `db_password/1` with namespace `Test` will try to get a value: 1. From the OS environment variable `$TEST_MYAPP_MYDB_PASSWORD`. 2. From the configuration file e.g: ``` config :myapp, Test, mydb: [password: "some test password"] ``` 3. From the OS environment variable `$MYAPP_MYDB_PASSWORDT`. 4. From the configuraton file e.g: ``` config :myapp, mydb: [password: "some password"] ``` 5. From the default value if it exists. In our example, `"password"`. """ # credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity defmacro app_env(function_name, app_name, keys, options \\ []) do definition = String.to_atom("__#{function_name}__") bang! = String.to_atom("#{function_name}!") reload = String.to_atom("reload_#{function_name}") put = String.to_atom("put_#{function_name}") quote do @doc false @spec unquote(definition)() :: Env.t() @spec unquote(definition)(namespace :: Env.namespace()) :: Env.t() def unquote(definition)(namespace \\ nil) do app_name = unquote(app_name) keys = unquote(keys) options = unquote(options) Env.new(namespace, app_name, keys, options) end # Function to get the variable's value. Errors when is required and does # not exist. @doc Docs.gen_full_docs( __MODULE__, unquote(function_name), unquote(app_name), unquote(keys), unquote(options), Module.get_attribute(__MODULE__, :envdoc) ) @spec unquote(function_name)() :: {:ok, term()} | {:error, term()} @spec unquote(function_name)(Env.namespace()) :: {:ok, term()} | {:error, term()} def unquote(function_name)(namespace \\ nil) def unquote(function_name)(namespace) do env = unquote(definition)(namespace) Core.get_env(env) end # Function to get the variable's value. Fails when is required and does # not exist, @doc Docs.gen_short_docs( __MODULE__, unquote(function_name), Module.get_attribute(__MODULE__, :envdoc) ) @spec unquote(bang!)() :: term() | no_return() @spec unquote(bang!)(Env.namespace()) :: term() | no_return() def unquote(bang!)(namespace \\ nil) def unquote(bang!)(namespace) do env = unquote(definition)(namespace) Core.get_env!(env) end # Reloads the variable. @doc Docs.gen_reload_docs(__MODULE__, unquote(function_name)) @spec unquote(reload)() :: {:ok, term()} | {:error, term()} @spec unquote(reload)(Env.namespace()) :: {:ok, term()} | {:error, term()} def unquote(reload)(namespace \\ nil) def unquote(reload)(namespace) do env = unquote(definition)(namespace) Core.reload_env(env) end # Puts a new value to a variable. @doc Docs.gen_put_docs(__MODULE__, unquote(function_name)) @spec unquote(put)(term()) :: :ok | {:error, term()} @spec unquote(put)(term(), Env.namespace()) :: :ok | {:error, term()} def unquote(put)(value, namespace \\ nil) def unquote(put)(value, namespace) do env = unquote(definition)(namespace) Core.put_env(env, value) end end end end