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 []