; VariableEngine.ahk — Variables des prompts : automatiques et manuelles (architecture §10). class VariableEngine { static AutoNames := ["selection", "clipboard", "date", "datetime", "time", "app", "cursor"] static IsAuto(name) { return HasInArray(VariableEngine.AutoNames, StrLower(name)) } ; Toutes les variables {{...}} d'un corps, dans l'ordre d'apparition, dédupliquées. static FindVars(body) { found := [] seen := Map() pos := 1 while (m := RegExMatch(body, "\{\{\s*([A-Za-z0-9_]+)\s*\}\}", &match, pos)) { name := StrLower(match[1]) if !seen.Has(name) { seen[name] := true found.Push(name) } pos := m + match.Len[0] } return found } ; Variables manuelles requises : celles du corps qui ne sont pas automatiques. ; Retourne [ Map {name, label, type, choices, default, required, multiline, declared} ]. static ManualVars(prompt) { declared := Map() for v in prompt.Variables() { if v.Has("name") declared[StrLower(String(v["name"]))] := v } out := [] for name in VariableEngine.FindVars(prompt.Body) { if VariableEngine.IsAuto(name) continue if declared.Has(name) { d := declared[name] out.Push(Map( "name", name, "label", d.Has("label") && d["label"] != "" ? String(d["label"]) : name, "type", d.Has("type") ? String(d["type"]) : "text", "choices", d.Has("choices") && (d["choices"] is Array) ? d["choices"] : [], "default", d.Has("default") ? String(d["default"]) : "", "required", d.Has("required") ? Truthy(d["required"]) : 0, "multiline", d.Has("multiline") ? Truthy(d["multiline"]) : 0, "declared", 1)) } else { out.Push(Map("name", name, "label", name, "type", "text", "choices", [], "default", "", "required", 0, "multiline", 0, "declared", 0)) } } return out } ; Valeurs automatiques à partir du contexte d'invocation (figées à l'invocation, §10.3). static AutoValues(ctx) { vals := Map() vals["selection"] := ctx.Has("selection") ? ctx["selection"] : "" vals["clipboard"] := ctx.Has("clipboardText") ? ctx["clipboardText"] : "" vals["date"] := FormatTime(, "yyyy-MM-dd") vals["datetime"] := FormatTime(, "yyyy-MM-dd HH:mm") vals["time"] := FormatTime(, "HH:mm") vals["app"] := ctx.Has("process") ? ctx["process"] : "" return vals } ; Résolution (§10.5). Retourne Map { text, unresolved: [], cursorFromEnd: Integer }. static Resolve(body, manualValues, ctx) { auto := VariableEngine.AutoValues(ctx) text := body unresolved := [] ; Remplacement variable par variable (toutes occurrences). for name in VariableEngine.FindVars(body) { token := "" if (name = "cursor") continue if auto.Has(name) value := auto[name] else if manualValues.Has(name) value := String(manualValues[name]) else { unresolved.Push(name) continue } text := RegExReplace(text, "\{\{\s*" name "\s*\}\}", VariableEngine.EscapeReplacement(value)) } ; Marqueur de curseur : on retient la distance à la fin, puis on retire le marqueur. cursorFromEnd := 0 if RegExMatch(text, "\{\{\s*cursor\s*\}\}", &cm) { cursorFromEnd := StrLen(text) - (cm.Pos[0] + cm.Len[0] - 1) text := StrReplace(text, cm[0], "") } return Map("text", text, "unresolved", unresolved, "cursorFromEnd", cursorFromEnd) } ; Dans RegExReplace, $ a un sens spécial : on le neutralise pour insérer le texte tel quel. static EscapeReplacement(value) { return StrReplace(value, "$", "$$") } ; Enveloppe du profil (§12.2) : préambule + corps + postambule. static ApplyProfile(text, profile) { out := text pre := profile.Has("preambule") ? profile["preambule"] : "" post := profile.Has("postambule") ? profile["postambule"] : "" if (pre != "") out := pre "`n`n" out if (post != "") out := out "`n`n" post return out } } ; Vrai pour 1, "1", "true" (insensible à la casse) — valeurs lues depuis l'en-tête. Truthy(v) { if (v = 1) return 1 if (v is String) { lv := StrLower(v) if (lv = "true" || lv = "1" || lv = "oui") return 1 } return 0 }