#!/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 -> fw::iptables function module::to_namespace() { echo "${1//\//:}"; } # Build fully qualified function name # e.g. module::fn "firewall/iptables" "on_load" -> fw::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" # Wildcard: load all submodules in a directory if [[ "$name" == *"/*" ]]; then _load_module_dir "${name%/*}" return $? fi 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 } function _load_module_dir() { local dir="${1:-}" local module_dir module_dir="$(ctx::modules)/${dir}" if [[ ! -d "$module_dir" ]]; then log::error "Module directory not found: ${dir} (${module_dir})" return 1 fi for file in "${module_dir}"/*.module.sh; do [[ -f "$file" ]] || continue local subname="${dir}/$(basename "${file%.module.sh}")" load_module "$subname" done }