#Requires AutoHotkey v2.0 #Include MOML2.ahk ; sample_gatekeeper.ahk — a worked MOML 2.0 gatekeeper for AutoHotkey v2 ; Mirrors the Python/PHP/JS reference pattern. ; ; Architecture: ; APP <-> Gatekeeper <-> MOML2.ahk ; The app never needs to call MOML directly. Reads are normalized here; ; writes are stringified here before MOML sees them. CONFIG_FILE := A_ScriptDir "\\sample_config.m2" ; ── Internal I/O — the only functions that touch MOML directly ───────────── GK_Load() { global CONFIG_FILE return MOML2_Load(CONFIG_FILE) } GK_Save(config) { global CONFIG_FILE MOML2_Dump(config, CONFIG_FILE) } ; ── Normalizers — app-facing type conversion ─────────────────────────────── GK_ToBool(v, fallback := false) { if (v = "") return fallback s := StrLower(Trim(v)) return (s = "true" || s = "1" || s = "yes") } GK_FromBool(v) { return v ? "true" : "false" } GK_ToInt(v, fallback := 0) { try return Integer(v) catch return fallback } GK_FromInt(v) { return v "" } GK_ToHex(v, fallback := "#FFFFFF") { if (v = "") return fallback return "#" v } GK_FromHex(v) { return LTrim(v, "#") } GK_ToPath(v) { return v } GK_FromPath(v) { return v } GK_ToKey(v) { return v } GK_FromKey(v) { return v } GK_ToList(v) { ; MOML 2.0 pads a one-element inline list as: item, "" ; Strip at most one trailing empty only when THIS accessor knows ; the setting is conceptually list-shaped. if !IsObject(v) { if (v = "") return [] return [v] } out := [] for item in v out.Push(item) if (out.Length >= 1 && out[out.Length] = "") out.Pop() return out } ; ── Schema-aware read ────────────────────────────────────────────────────── GK_Defaults() { return Map( "enabled", true, "max_items", 10, "theme_color", "#FFFFFF", "log_path", "", "dictate_keys", ["#h"] ) } GK_GetSettings() { defaults := GK_Defaults() result := Map() for k, v in defaults result[k] := v config := GK_Load() if !config.Has("settings") return result s := config["settings"] if !(s is MOMLDict) return result if s.Has("enabled") result["enabled"] := GK_ToBool(s["enabled"], defaults["enabled"]) if s.Has("max_items") result["max_items"] := GK_ToInt(s["max_items"], defaults["max_items"]) if s.Has("theme_color") result["theme_color"] := GK_ToHex(s["theme_color"], defaults["theme_color"]) if s.Has("log_path") result["log_path"] := GK_ToPath(s["log_path"]) if s.Has("dictate_keys") { keys := [] for item in GK_ToList(s["dictate_keys"]) keys.Push(GK_ToKey(item)) result["dictate_keys"] := keys } return result } ; ── Schema-aware write ───────────────────────────────────────────────────── GK_SaveSettings(vals) { ; vals is a Map keyed by setting name. This avoids positional-argument ; transposition bugs and makes schema validation explicit. if !(vals is Map) throw Error("GK_SaveSettings() expects a Map of setting names to values") config := GK_Load() if (config.Has("settings") && config["settings"] is MOMLDict) block := config["settings"] else block := MOMLDict() defaults := GK_Defaults() for key, val in vals { if (key = "enabled") { block[key] := GK_FromBool(val) } else if (key = "max_items") { block[key] := GK_FromInt(val) } else if (key = "theme_color") { block[key] := GK_FromHex(val) } else if (key = "log_path") { block[key] := GK_FromPath(val) } else if (key = "dictate_keys") { out := [] for item in val out.Push(GK_FromKey(item)) block[key] := out } else { known := "" for k, _ in defaults known .= (known = "" ? "" : ", ") k throw Error("GK_SaveSettings() received unknown setting '" key "'. Known settings: " known) } } config["settings"] := block GK_Save(config) } ; ── Demonstration ────────────────────────────────────────────────────────── if (A_LineFile = A_ScriptFullPath) { seed := "# MOML 2.0`n`n" . "settings {`n" . " enabled = true`n" . " max_items = 10`n" . " theme_color = 085041`n" . " log_path = C:\\Users\\Karim\\Documents`n" . " dictate_keys = #h, +#h`n" . "}`n" try FileDelete(CONFIG_FILE) FileAppend(seed, CONFIG_FILE, "UTF-8") settings := GK_GetSettings() if (settings["theme_color"] != "#085041") throw Error("theme_color leading zero did not survive") if (settings["log_path"] != "C:\\Users\\Karim\\Documents") throw Error("Windows path did not survive") if (settings["dictate_keys"].Length != 2 || settings["dictate_keys"][1] != "#h" || settings["dictate_keys"][2] != "+#h") throw Error("dictate_keys did not parse as expected") GK_SaveSettings(Map( "enabled", false, "max_items", 25, "theme_color", "#0f0f0f", "log_path", "D:\\Notes", "dictate_keys", ["#h"] )) reloaded := GK_GetSettings() if (reloaded["theme_color"] != "#0f0f0f") throw Error("theme_color round-trip failed") if (reloaded["max_items"] != 25) throw Error("max_items round-trip failed") if (reloaded["enabled"] != false) throw Error("enabled round-trip failed") if (reloaded["dictate_keys"].Length != 1 || reloaded["dictate_keys"][1] != "#h") throw Error("one-element dictate_keys unpadding failed") FileDelete(CONFIG_FILE) MsgBox("MOML 2.0 AHK sample gatekeeper: all assertions passed.") }