Current section
Files
Jump to
Current section
Files
lib/cmdc/plugin/registry.ex
defmodule CMDC.Plugin.Registry do
@moduledoc """
Plugin 注册表。
管理已注册的 Plugin 集合,按 `priority/0` 自动排序(数值小的先执行),
同一模块只保留一份(去重)。
## 使用示例
registry = CMDC.Plugin.Registry.new()
{:ok, registry} = CMDC.Plugin.Registry.register(registry, {MyPlugin, []})
{:ok, registry} = CMDC.Plugin.Registry.register(registry, {SecurityPlugin, max_paths: 10})
plugins = CMDC.Plugin.Registry.entries(registry)
"""
alias CMDC.Plugin
require Logger
# ==========================================================================
# 类型定义
# ==========================================================================
@type plugin_spec :: module() | {module(), keyword()}
@type entry :: {module(), Plugin.plugin_state()}
@type t :: %__MODULE__{
entries: [entry()]
}
defstruct entries: []
# ==========================================================================
# Public API
# ==========================================================================
@doc "创建空的 Registry。"
@spec new() :: t()
def new, do: %__MODULE__{}
@doc """
从 plugin 规格列表构建 Registry,完成初始化、去重、排序。
`specs` 支持两种格式:
- `{ModuleName, keyword_opts}` — 带自定义选项
- `ModuleName` — 使用空 opts 初始化
初始化失败的 plugin 会被跳过并记录警告日志。
"""
@spec from_specs([plugin_spec()]) :: t()
def from_specs(specs) when is_list(specs) do
Enum.reduce(specs, new(), fn spec, registry ->
case register(registry, normalize_spec(spec)) do
{:ok, registry} -> registry
{:error, _reason} -> registry
end
end)
end
@doc """
注册一个 Plugin。
调用 `module.init(opts)` 初始化状态,若同一模块已注册则覆盖旧状态,
注册后自动重新排序。
"""
@spec register(t(), {module(), keyword()}) :: {:ok, t()} | {:error, term()}
def register(%__MODULE__{} = registry, {module, opts})
when is_atom(module) and is_list(opts) do
case module.init(opts) do
{:ok, plugin_state} ->
{:ok, upsert_and_sort(registry, module, plugin_state)}
{:error, reason} ->
Logger.warning("[Plugin.Registry] #{inspect(module)} init failed: #{inspect(reason)}")
{:error, reason}
end
rescue
e ->
Logger.warning("[Plugin.Registry] #{inspect(e)} while registering plugin")
{:error, e}
end
@doc "注销一个 Plugin(按模块名移除)。"
@spec unregister(t(), module()) :: t()
def unregister(%__MODULE__{} = registry, module) when is_atom(module) do
%{registry | entries: Enum.reject(registry.entries, fn {mod, _} -> mod == module end)}
end
@doc "返回已注册的 plugin 模块列表。"
@spec modules(t()) :: [module()]
def modules(%__MODULE__{entries: entries}) do
Enum.map(entries, fn {mod, _} -> mod end)
end
@doc "返回已排序的 `[{module, plugin_state}]` 列表,供 Pipeline.run/3 使用。"
@spec entries(t()) :: [entry()]
def entries(%__MODULE__{entries: entries}), do: entries
@doc "检查模块是否已注册。"
@spec registered?(t(), module()) :: boolean()
def registered?(%__MODULE__{entries: entries}, module) do
Enum.any?(entries, fn {mod, _} -> mod == module end)
end
@doc "返回已注册 plugin 的数量。"
@spec count(t()) :: non_neg_integer()
def count(%__MODULE__{entries: entries}), do: length(entries)
# ==========================================================================
# 私有辅助
# ==========================================================================
defp normalize_spec({module, opts}) when is_atom(module) and is_list(opts), do: {module, opts}
defp normalize_spec(module) when is_atom(module), do: {module, []}
defp upsert_and_sort(registry, module, plugin_state) do
filtered = Enum.reject(registry.entries, fn {mod, _} -> mod == module end)
new_entries = [{module, plugin_state} | filtered]
sorted = Enum.sort_by(new_entries, fn {mod, _} -> mod.priority() end)
%{registry | entries: sorted}
end
end