My agentic slop goes here. Not intended for anyone else!
1type t = (string, string) Hashtbl.t 2 3let create pairs = 4 let config = Hashtbl.create (List.length pairs) in 5 List.iter (fun (k, v) -> Hashtbl.replace config k v) pairs; 6 config 7 8let from_file path = 9 try 10 let content = 11 let ic = open_in path in 12 let content = really_input_string ic (in_channel_length ic) in 13 close_in ic; 14 content in 15 16 match Toml.Parser.from_string content with 17 | `Error (msg, _) -> 18 Error (Zulip.Error.create ~code:(Other "toml_parse_error") ~msg:("Failed to parse TOML: " ^ msg) ()) 19 | `Ok toml -> 20 let config = Hashtbl.create 16 in 21 22 (* Helper to add a value to config by key *) 23 let add_value key_name value = 24 match value with 25 | Toml.Types.TString s -> Hashtbl.replace config key_name s 26 | Toml.Types.TInt i -> Hashtbl.replace config key_name (string_of_int i) 27 | Toml.Types.TFloat f -> Hashtbl.replace config key_name (string_of_float f) 28 | Toml.Types.TBool b -> Hashtbl.replace config key_name (string_of_bool b) 29 | _ -> () (* Skip non-primitive values *) in 30 31 (* Helper to extract all key-value pairs from a table *) 32 let add_table_values table = 33 Toml.Types.Table.iter (fun key value -> 34 let key_str = Toml.Types.Table.Key.to_string key in 35 add_value key_str value 36 ) table in 37 38 (* Add root level values *) 39 add_table_values toml; 40 41 (* Also check for [bot] section - values override root level *) 42 (match Toml.Lenses.(get toml (key "bot" |-- table)) with 43 | Some bot_table -> add_table_values bot_table 44 | None -> ()); 45 46 (* Also check for [features] section *) 47 (match Toml.Lenses.(get toml (key "features" |-- table)) with 48 | Some features_table -> add_table_values features_table 49 | None -> ()); 50 51 Ok config 52 with 53 | Sys_error msg -> 54 Error (Zulip.Error.create ~code:(Other "file_error") ~msg:("Cannot read config file: " ^ msg) ()) 55 | exn -> 56 Error (Zulip.Error.create ~code:(Other "parse_error") ~msg:("Error parsing config: " ^ Printexc.to_string exn) ()) 57 58let from_env ~prefix = 59 try 60 let config = Hashtbl.create 16 in 61 let env_vars = Array.to_list (Unix.environment ()) in 62 63 List.iter (fun env_var -> 64 match String.split_on_char '=' env_var with 65 | key :: value_parts when String.length key > String.length prefix && 66 String.sub key 0 (String.length prefix) = prefix -> 67 let config_key = String.sub key (String.length prefix) (String.length key - String.length prefix) in 68 let value = String.concat "=" value_parts in 69 Hashtbl.replace config config_key value 70 | _ -> () 71 ) env_vars; 72 73 Ok config 74 with 75 | exn -> 76 Error (Zulip.Error.create ~code:(Other "env_error") ~msg:("Error reading environment: " ^ Printexc.to_string exn) ()) 77 78let get t ~key = 79 Hashtbl.find_opt t key 80 81let get_required t ~key = 82 match Hashtbl.find_opt t key with 83 | Some value -> Ok value 84 | None -> Error (Zulip.Error.create ~code:(Other "config_missing") ~msg:("Required config key missing: " ^ key) ()) 85 86let has_key t ~key = 87 Hashtbl.mem t key 88 89let keys t = 90 Hashtbl.fold (fun k _ acc -> k :: acc) t []