Packages
phoenix_kit
1.7.119
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit/install/igniter_config.ex
defmodule PhoenixKit.Install.IgniterConfig do
@moduledoc """
Helper functions for working with Igniter to read and modify parent project configuration.
This module provides utilities to read, update, and merge configuration values in the
parent Phoenix application's config files using Igniter's configuration system.
All functions are designed to work with Igniter's project modification capabilities
and provide safe operations that handle missing configs and merge strategies.
## Examples
iex> igniter = Igniter.new()
iex> {igniter, value} = PhoenixKit.Install.IgniterConfig.read_config(igniter, :my_app, [:key])
{igniter, nil}
iex> igniter = PhoenixKit.Install.IgniterConfig.update_config(igniter, :my_app, [:key], :value)
# Returns updated igniter with the new config
"""
alias Igniter.Code.Common
alias Igniter.Project.Config
alias Sourceror.Zipper
@doc """
Reads a configuration value from the parent project's config files.
## Parameters
- `igniter` - The Igniter project struct
- `app_name` - The application name (atom)
- `key_path` - List of atoms representing the configuration key path
- `config_file` - Config file name (default: "config.exs")
## Returns
`{igniter, {:ok, value}}` if the config exists
`{igniter, {:error, reason}}` if the config doesn't exist or can't be read
## Examples
iex> igniter = Igniter.new()
iex> {igniter, result} = PhoenixKit.Install.IgniterConfig.read_config(igniter, :my_app, [:my_key])
{igniter, {:ok, :my_value}}
"""
@spec read_config(Igniter.t(), atom(), list(atom()), String.t()) ::
{Igniter.t(), {:ok, any()} | {:error, String.t()}}
def read_config(igniter, app_name, key_path, config_file \\ "config.exs") do
igniter
|> Config.configure(
config_file,
app_name,
key_path,
nil,
updater: fn zipper ->
case extract_current_value(zipper) do
{:ok, value} ->
# Store the value in process dictionary for retrieval
Process.put({__MODULE__, :read_value}, value)
{:ok, zipper}
:error ->
Process.put({__MODULE__, :read_value}, :not_found)
{:ok, zipper}
end
end
)
|> then(fn igniter ->
value = Process.get({__MODULE__, :read_value}, :not_found)
Process.delete({__MODULE__, :read_value})
case value do
:not_found -> {igniter, {:error, "Configuration not found"}}
value -> {igniter, {:ok, value}}
end
end)
end
@doc """
Updates or creates a configuration value in the parent project's config files.
## Parameters
- `igniter` - The Igniter project struct
- `app_name` - The application name (atom)
- `key_path` - List of atoms representing the configuration key path
- `value` - The new value to set
- `config_file` - Config file name (default: "config.exs")
- `opts` - Options:
- `:merge_lists` - If true and both existing and new values are lists, merges them (default: false)
## Returns
The updated `igniter` struct
## Examples
iex> igniter = Igniter.new()
iex> igniter = PhoenixKit.Install.IgniterConfig.update_config(igniter, :my_app, [:my_key], :new_value)
# Returns igniter with the updated config
"""
@spec update_config(Igniter.t(), atom(), list(atom()), any(), String.t(), keyword()) ::
Igniter.t()
def update_config(igniter, app_name, key_path, value, config_file \\ "config.exs", opts \\ []) do
merge_lists = Keyword.get(opts, :merge_lists, false)
igniter
|> Config.configure(
config_file,
app_name,
key_path,
value,
updater: fn zipper ->
if merge_lists and should_merge_lists?(zipper, value) do
case extract_current_value(zipper) do
{:ok, existing_list} when is_list(existing_list) and is_list(value) ->
merged = Enum.uniq(existing_list ++ value)
{:ok, Common.replace_code(zipper, merged)}
_ ->
{:ok, Common.replace_code(zipper, value)}
end
else
{:ok, Common.replace_code(zipper, value)}
end
end
)
end
@doc """
Merges a new value into an existing list configuration.
If the config doesn't exist or is not a list, it will be created with the new value.
## Parameters
- `igniter` - The Igniter project struct
- `app_name` - The application name (atom)
- `key_path` - List of atoms representing the configuration key path
- `items` - List of items to add to the existing configuration
- `config_file` - Config file name (default: "config.exs")
- `opts` - Options:
- `:merge_strategy` - How to merge when items are maps with `:title` keys:
- `:prepend` - Add new items at the beginning (default)
- `:append` - Add new items at the end
- `:replace` - Replace existing items with matching titles
## Returns
The updated `igniter` struct
## Examples
iex> igniter = Igniter.new()
iex> items = [%{title: "New Item", value: 1}]
iex> igniter = PhoenixKit.Install.IgniterConfig.merge_into_list_config(
...> igniter, :my_app, [:my_list], items
...> )
"""
@spec merge_into_list_config(
Igniter.t(),
atom(),
list(atom()),
list(),
String.t(),
keyword()
) :: Igniter.t()
def merge_into_list_config(
igniter,
app_name,
key_path,
items,
config_file \\ "config.exs",
opts \\ []
)
def merge_into_list_config(igniter, app_name, key_path, items, config_file, opts)
when is_list(items) do
merge_strategy = Keyword.get(opts, :merge_strategy, :prepend)
igniter
|> Config.configure(
config_file,
app_name,
key_path,
items,
updater: fn zipper ->
case extract_current_value(zipper) do
{:ok, existing_list} when is_list(existing_list) ->
updated_list =
case merge_strategy do
:replace ->
replace_by_title(existing_list, items)
:append ->
existing_list ++ items
:prepend ->
items ++ existing_list
end
{:ok, Common.replace_code(zipper, updated_list)}
_ ->
# Config doesn't exist or is not a list
{:ok, Common.replace_code(zipper, items)}
end
end
)
end
@doc """
Reads a PhoenixKit-specific configuration value.
Shortcut for reading config values from the :phoenix_kit application.
## Parameters
- `igniter` - The Igniter project struct
- `key_path` - List of atoms representing the configuration key path
- `config_file` - Config file name (default: "config.exs")
## Returns
`{igniter, {:ok, value}}` if the config exists
`{igniter, {:error, reason}}` if the config doesn't exist or can't be read
"""
@spec read_phoenix_kit_config(Igniter.t(), list(atom()), String.t()) ::
{Igniter.t(), {:ok, any()} | {:error, String.t()}}
def read_phoenix_kit_config(igniter, key_path, config_file \\ "config.exs") do
read_config(igniter, :phoenix_kit, key_path, config_file)
end
@doc """
Updates a PhoenixKit-specific configuration value.
Shortcut for updating config values in the :phoenix_kit application.
## Parameters
- `igniter` - The Igniter project struct
- `key_path` - List of atoms representing the configuration key path
- `value` - The new value to set
- `config_file` - Config file name (default: "config.exs")
- `opts` - Options passed through to `update_config/5`
## Returns
The updated `igniter` struct
"""
@spec update_phoenix_kit_config(Igniter.t(), list(atom()), any(), String.t(), keyword()) ::
Igniter.t()
def update_phoenix_kit_config(igniter, key_path, value, config_file \\ "config.exs", opts \\ []) do
update_config(igniter, :phoenix_kit, key_path, value, config_file, opts)
end
@doc """
Adds an item to an admin dashboard category in PhoenixKit configuration.
Shortcut for `add_to_category/7` with PhoenixKit-specific defaults.
## Parameters
- `igniter` - The Igniter project struct
- `category_title` - The title of the category to add to
- `item` - The item to add to the category's subsections
- `category_opts` - Options for creating a new category if needed
## Returns
The updated `igniter` struct
## Examples
iex> igniter = Igniter.new()
iex> new_page = %{title: "Reports", url: "/admin/reports", icon: "hero-chart-bar"}
iex> igniter = PhoenixKit.Install.IgniterConfig.add_to_admin_category(
...> igniter, "Analytics", new_page
...> )
"""
@spec add_to_admin_category(Igniter.t(), String.t(), map(), keyword()) :: Igniter.t()
def add_to_admin_category(igniter, category_title, item, category_opts \\ []) do
add_to_category(
igniter,
:phoenix_kit,
[:admin_dashboard_categories],
category_title,
item,
category_opts
)
end
@doc """
Adds a tab to a user dashboard category, creating the category if it doesn't exist.
This function is similar to `add_to_admin_category/4` but for user dashboard tabs.
It uses `:tabs` instead of `:subsections` to match the user dashboard category format.
## Parameters
- `igniter` - The Igniter project struct
- `category_title` - The title of the category (e.g., "Farm Management")
- `tab` - A map with tab properties: `:title`, `:url`, `:icon`, `:description`
- `category_opts` - Options for creating a new category:
- `:icon` - Icon for the new category (default: "hero-folder")
## Examples
iex> igniter = Igniter.new()
iex> new_tab = %{title: "History", url: "/dashboard/history", icon: "hero-chart-bar"}
iex> igniter = PhoenixKit.Install.IgniterConfig.add_to_user_dashboard_category(
...> igniter, "Farm Management", new_tab, icon: "hero-cube"
...> )
"""
@spec add_to_user_dashboard_category(Igniter.t(), String.t(), map(), keyword()) :: Igniter.t()
def add_to_user_dashboard_category(igniter, category_title, tab, category_opts \\ []) do
add_to_category_with_tabs(
igniter,
:phoenix_kit,
[:user_dashboard_categories],
category_title,
tab,
category_opts
)
end
@doc """
Adds a tab to a category within a list configuration (using :tabs key).
Similar to `add_to_category/7` but uses `:tabs` instead of `:subsections`.
Designed for user dashboard categories.
"""
@spec add_to_category_with_tabs(
Igniter.t(),
atom(),
list(atom()),
String.t(),
map(),
keyword(),
String.t()
) :: Igniter.t()
def add_to_category_with_tabs(
igniter,
app_name,
key_path,
category_title,
tab,
category_opts \\ [],
config_file \\ "config.exs"
) do
default_icon = Keyword.get(category_opts, :icon, "hero-folder")
igniter
|> Config.configure(
config_file,
app_name,
key_path,
# Default value if config doesn't exist
[
%{
title: category_title,
icon: default_icon,
tabs: [tab]
}
],
updater: fn zipper ->
case extract_current_value(zipper) do
{:ok, existing_categories} when is_list(existing_categories) ->
# Find if category already exists
case Enum.find_index(existing_categories, &(&1.title == category_title)) do
nil ->
# Category doesn't exist, create new one
new_category = %{
title: category_title,
icon: default_icon,
tabs: [tab]
}
updated_categories = existing_categories ++ [new_category]
{:ok, Common.replace_code(zipper, updated_categories)}
category_index ->
# Category exists, add to its tabs
updated_categories =
existing_categories
|> List.update_at(category_index, fn category_config ->
existing_tabs = Map.get(category_config, :tabs, [])
updated_tabs = existing_tabs ++ [tab]
%{category_config | tabs: updated_tabs}
end)
{:ok, Common.replace_code(zipper, updated_categories)}
end
_ ->
# Config doesn't exist or is not a list, create new
new_category = %{
title: category_title,
icon: default_icon,
tabs: [tab]
}
{:ok, Common.replace_code(zipper, [new_category])}
end
end
)
end
@doc """
Adds an item to a category within a list configuration, creating the category if needed.
This function is designed for configurations that contain a list of categories,
each with a `:title` key and `:subsections` list. It will find an existing category
by title and add the new item to its subsections, or create a new category if it doesn't exist.
## Parameters
- `igniter` - The Igniter project struct
- `app_name` - The application name (atom)
- `key_path` - List of atoms representing the configuration key path
- `category_title` - The title of the category to add to
- `item` - The item to add to the category's subsections
- `category_opts` - Options for creating a new category if needed:
- `:icon` - Icon for the new category (default: "hero-folder")
- `config_file` - Config file name (default: "config.exs")
## Returns
The updated `igniter` struct
## Examples
iex> igniter = Igniter.new()
iex> new_page = %{title: "Reports", url: "/admin/reports"}
iex> igniter = PhoenixKit.Install.IgniterConfig.add_to_category(
...> igniter, :my_app, [:admin_categories], "Analytics", new_page
...> )
"""
@spec add_to_category(
Igniter.t(),
atom(),
list(atom()),
String.t(),
map(),
keyword(),
String.t()
) :: Igniter.t()
def add_to_category(
igniter,
app_name,
key_path,
category_title,
item,
category_opts \\ [],
config_file \\ "config.exs"
) do
default_icon = Keyword.get(category_opts, :icon, "hero-folder")
igniter
|> Config.configure(
config_file,
app_name,
key_path,
# Default value if config doesn't exist
[
%{
title: category_title,
icon: default_icon,
subsections: [item]
}
],
updater: fn zipper ->
case extract_current_value(zipper) do
{:ok, existing_categories} when is_list(existing_categories) ->
# Find if category already exists
case Enum.find_index(existing_categories, &(&1.title == category_title)) do
nil ->
# Category doesn't exist, create new one
new_category = %{
title: category_title,
icon: default_icon,
subsections: [item]
}
updated_categories = existing_categories ++ [new_category]
{:ok, Common.replace_code(zipper, updated_categories)}
category_index ->
# Category exists, add to its subsections
updated_categories =
existing_categories
|> List.update_at(category_index, fn category_config ->
existing_subsections = Map.get(category_config, :subsections, [])
updated_subsections = existing_subsections ++ [item]
%{category_config | subsections: updated_subsections}
end)
{:ok, Common.replace_code(zipper, updated_categories)}
end
_ ->
# Config doesn't exist or is not a list, create new
new_category = %{
title: category_title,
icon: default_icon,
subsections: [item]
}
{:ok, Common.replace_code(zipper, [new_category])}
end
end
)
end
@doc """
Checks if a configuration value exists.
## Parameters
- `igniter` - The Igniter project struct
- `app_name` - The application name (atom)
- `key_path` - List of atoms representing the configuration key path
- `config_file` - Config file name (default: "config.exs")
## Returns
`{igniter, true}` if the config exists
`{igniter, false}` if the config doesn't exist
"""
@spec config_exists?(Igniter.t(), atom(), list(atom()), String.t()) :: {Igniter.t(), boolean()}
def config_exists?(igniter, app_name, key_path, config_file \\ "config.exs") do
{igniter, result} = read_config(igniter, app_name, key_path, config_file)
case result do
{:ok, _value} -> {igniter, true}
{:error, _reason} -> {igniter, false}
end
end
# Private helpers
# Extracts the current value from a zipper
defp extract_current_value(zipper) do
current_node = Zipper.node(zipper)
case Code.eval_quoted(current_node) do
{value, _binding} -> {:ok, value}
end
rescue
_ -> :error
end
# Checks if we should merge lists based on existing and new values
defp should_merge_lists?(zipper, new_value) do
case extract_current_value(zipper) do
{:ok, existing_value} when is_list(existing_value) and is_list(new_value) ->
true
_ ->
false
end
end
# Replaces items in a list by matching on :title key
defp replace_by_title(existing_list, new_items) do
existing_titles = MapSet.new(existing_list, fn item -> Map.get(item, :title) end)
{to_replace, _to_add} =
Enum.split_with(new_items, fn item ->
Map.get(item, :title) in existing_titles
end)
# Remove existing items that will be replaced
replacement_titles = MapSet.new(to_replace, fn item -> Map.get(item, :title) end)
filtered_existing =
Enum.reject(existing_list, fn item ->
Map.get(item, :title) in replacement_titles
end)
# Add new items (both replacements and additions)
filtered_existing ++ new_items
end
end