#!/usr/bin/env bash # ============================================ # Module Registry # ============================================ declare -A _LOADED_MODULES=() readonly _MODULE_AUTO_LOAD_HOOK="on_load" # ============================================ # Helpers # ============================================ function module::loaded() { [[ -n "${_LOADED_MODULES["$1"]:-}" ]]; } # Convert path-style name to namespace # e.g. firewall/iptables -> firewall::iptables function module::to_namespace() { echo "${1//\//:}"; } # Build fully qualified function name # e.g. module::fn "firewall/iptables" "on_load" -> firewall::iptables::on_load function module::fn() { local namespace namespace=$(module::to_namespace "$1") echo "${namespace}::${2}" } function module::has_function() { declare -F "$(module::fn "$1" "$2")" >/dev/null 2>&1; } function module::is_auto_load() { declare -F "$(module::fn "$1" on_load)" >/dev/null 2>&1; } # ============================================ # Loader # ============================================ function load_module() { local name="$1" module::loaded "$name" && return 0 local path path="$(ctx::modules)/${name}.module.sh" if [[ ! -f "$path" ]]; then log::error "Module not found: ${name} (${path})" return 1 fi source "$path" _LOADED_MODULES["$name"]=1 core::call_if_exists "$(module::fn "$name" on_load)" return 0 }