aboutsummaryrefslogtreecommitdiffstats
path: root/lib/configuration.ml
diff options
context:
space:
mode:
authorMarcos Magueta <maguetamarcos@gmail.com>2026-04-22 22:46:41 -0300
committerGitHub <noreply@github.com>2026-04-22 22:46:41 -0300
commit29e00769046cbc8cd020b2b7c47448d89f21ab39 (patch)
treedf6601daa94f7d588708ceca5a34fa02b75d791b /lib/configuration.ml
parentfdc63e730f91a4605ed4ff96a908e1f5e822d1bb (diff)
downloadSakura-29e00769046cbc8cd020b2b7c47448d89f21ab39.tar.gz
Sakura-29e00769046cbc8cd020b2b7c47448d89f21ab39.zip
Prototype for configuration
Diffstat (limited to 'lib/configuration.ml')
-rw-r--r--lib/configuration.ml84
1 files changed, 84 insertions, 0 deletions
diff --git a/lib/configuration.ml b/lib/configuration.ml
new file mode 100644
index 0000000..6229ce6
--- /dev/null
+++ b/lib/configuration.ml
@@ -0,0 +1,84 @@
+module type CONFIGURABLE = sig
+ type configuration
+
+ val parse : Sexplib.Sexp.t -> (configuration, string) result
+end
+
+type t = Sexplib.Sexp.t Utilities.StringMap.t
+(** Section name -> raw sexp subtree. *)
+
+let find_section (key : string) (config : t) : Sexplib.Sexp.t option =
+ Utilities.StringMap.find_opt key config
+
+let errorf fmt = Printf.ksprintf (fun s -> Error s) fmt
+
+(** Check that [key] is expected and not duplicate, then add it. *)
+let insert_section ~expected acc key body =
+ let ( let* ) = Result.bind in
+ let* () =
+ if Utilities.StringSet.mem key expected then Ok ()
+ else errorf "Unknown configuration section: %s" key
+ in
+ let* () =
+ if Utilities.StringMap.mem key acc then
+ errorf "Duplicate configuration section: %s" key
+ else Ok ()
+ in
+ match body with
+ | [ subtree ] -> Ok (Utilities.StringMap.add key subtree acc)
+ | [] -> errorf "Configuration section %s is empty (expected a tagged value)" key
+ | _ ->
+ errorf "Configuration section %s has multiple values (expected exactly one)"
+ key
+
+(** [(server ...)] sexp -> section map. Rejects unknown/duplicate keys. *)
+let parse_server ~expected_keys (sexp : Sexplib.Sexp.t) : (t, string) result =
+ let open Sexplib.Sexp in
+ match sexp with
+ | List (Atom "server" :: sections) ->
+ let expected =
+ List.fold_right Utilities.StringSet.add expected_keys
+ Utilities.StringSet.empty
+ in
+ let rec go acc = function
+ | [] -> Ok acc
+ | List (Atom key :: body) :: rest ->
+ Result.bind (insert_section ~expected acc key body) (fun acc ->
+ go acc rest)
+ | bad :: _ ->
+ errorf "Malformed configuration section: %s" (to_string bad)
+ in
+ go Utilities.StringMap.empty sections
+ | _ -> Error "Configuration must be a (server ...) s-expression"
+
+(** Read a file from disk and run [parse_server] on it. *)
+let load ~expected_keys (path : string) : (t, string) result =
+ match Sexplib.Sexp.load_sexp path with
+ | sexp -> parse_server ~expected_keys sexp
+ | exception Sys_error msg ->
+ errorf "Failed to load configuration file %s: %s" path msg
+ | exception Failure msg ->
+ errorf "Failed to parse configuration file %s: %s" path msg
+
+(** [(tag field1 ...)] -> [(tag, List [field1; ...])]. *)
+let extract_tagged_section (sexp : Sexplib.Sexp.t) :
+ (string * Sexplib.Sexp.t, string) result =
+ let open Sexplib.Sexp in
+ match sexp with
+ | List (Atom tag :: body) -> Ok (tag, List body)
+ | _ -> errorf "Expected (tag ...) but got: %s" (to_string sexp)
+
+(** Look up a section by name, extract its tag, and check the tag is allowed. *)
+let require_section ~(name : string) ~(valid_tags : string list) (config : t) :
+ (string * Sexplib.Sexp.t, string) result =
+ let ( let* ) = Result.bind in
+ let* sexp =
+ find_section name config
+ |> Option.to_result
+ ~none:(Printf.sprintf "Missing (%s ...) section in configuration" name)
+ in
+ let* tag, body = extract_tagged_section sexp in
+ let* () =
+ if List.mem tag valid_tags then Ok () else errorf "Unknown %s: %s" name tag
+ in
+ Ok (tag, body)