···11+v1.1.0 (dev)
22+------------
33+44+- Remove dependency on `eio_main` for library (@avsm).
55+ Thanks to @Alizter for the workaround in https://github.com/ocaml/dune/issues/12821).
66+77+v1.0.0 (2025-11-29)
88+-------------------
99+1010+- Initial public release (@avsm)
+18
vendor/opam/xdge/LICENSE.md
···11+(*
22+ * ISC License
33+ *
44+ * Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>
55+ *
66+ * Permission to use, copy, modify, and distribute this software for any
77+ * purpose with or without fee is hereby granted, provided that the above
88+ * copyright notice and this permission notice appear in all copies.
99+ *
1010+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1111+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1212+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1313+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1414+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1515+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1616+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1717+ *
1818+ *)
+75
vendor/opam/xdge/README.md
···11+# xdge - XDG Base Directory Specification for Eio
22+33+This library implements the [XDG Base Directory
44+Specification](https://specifications.freedesktop.org/basedir-spec/latest/) for
55+OCaml applications using the [Eio](https://github.com/ocaml-multicore/eio)
66+effects-based I/O library.
77+88+## What is XDG?
99+1010+The XDG Base Directory Specification defines standard locations for user-specific files on Unix-like systems, keeping home directories clean and organized:
1111+1212+- Config (`~/.config/app`): User preferences and settings
1313+- Data (`~/.local/share/app`): Persistent application data
1414+- Cache (`~/.cache/app`): Non-essential cached data (safe to delete)
1515+- State (`~/.local/state/app`): Logs, history, and runtime state
1616+- Runtime (`$XDG_RUNTIME_DIR/app`): Sockets, pipes, and session-bound files
1717+1818+The specification also defines system-wide search paths (`/etc/xdg`,
1919+`/usr/share`) and a precedence system using environment variables
2020+(`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, and so on).
2121+2222+## Why Eio?
2323+2424+Eio uses a **capability-based** approach to I/O where filesystem access must be
2525+explicitly passed to functions. This design aligns naturally with XDG directory
2626+management. For example:
2727+2828+```ocaml
2929+(* Filesystem access is an explicit capability *)
3030+let xdg = Xdge.create env#fs "myapp"
3131+```
3232+3333+The capability model provides the benefit that code that needs filesystem
3434+access must receive the `fs` capability, with no hidden global state or ambient
3535+authority. The `Eio.Path.t` type returned by xdge encapsulates both the
3636+filesystem capability and the path, preventing path traversal outside the
3737+granted capability. Applications can restrict filesystem access by passing a
3838+sandboxed `fs` capability, and xdge respects those boundaries.
3939+4040+## Usage
4141+4242+```ocaml
4343+Eio_main.run @@ fun env ->
4444+ let xdg = Xdge.create env#fs "myapp" in
4545+4646+ (* Access XDG directories as Eio paths *)
4747+ let config = Xdge.config_dir xdg in
4848+ let data = Xdge.data_dir xdg in
4949+5050+ (* Search for files following XDG precedence *)
5151+ match Xdge.find_config_file xdg "settings.json" with
5252+ | Some path -> (* use path *)
5353+ | None -> (* use defaults *)
5454+```
5555+5656+For CLI applications, xdge provides Cmdliner terms that handle environment
5757+variable precedence and command-line overrides:
5858+5959+```ocaml
6060+let () =
6161+ Eio_main.run @@ fun env ->
6262+ let term = Xdge.Cmd.term "myapp" env#fs () in
6363+ (* Generates --config-dir, --data-dir, etc. flags *)
6464+ (* Respects MYAPP_CONFIG_DIR > XDG_CONFIG_HOME > default *)
6565+```
6666+6767+## Installation
6868+6969+```
7070+opam install xdge
7171+```
7272+7373+## License
7474+7575+ISC
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+type source = Default | Env of string | Cmdline
77+88+type t = {
99+ app_name : string;
1010+ config_dir : Eio.Fs.dir_ty Eio.Path.t;
1111+ config_dir_source : source;
1212+ data_dir : Eio.Fs.dir_ty Eio.Path.t;
1313+ data_dir_source : source;
1414+ cache_dir : Eio.Fs.dir_ty Eio.Path.t;
1515+ cache_dir_source : source;
1616+ state_dir : Eio.Fs.dir_ty Eio.Path.t;
1717+ state_dir_source : source;
1818+ runtime_dir : Eio.Fs.dir_ty Eio.Path.t option;
1919+ runtime_dir_source : source;
2020+ config_dirs : Eio.Fs.dir_ty Eio.Path.t list;
2121+ data_dirs : Eio.Fs.dir_ty Eio.Path.t list;
2222+}
2323+2424+let ensure_dir ?(perm = 0o755) path = Eio.Path.mkdirs ~exists_ok:true ~perm path
2525+2626+let validate_runtime_base_dir base_path =
2727+ (* Validate the base XDG_RUNTIME_DIR has correct permissions per spec *)
2828+ try
2929+ let path_str = Eio.Path.native_exn base_path in
3030+ let stat = Eio.Path.stat ~follow:true base_path in
3131+ let current_perm = stat.perm land 0o777 in
3232+ if current_perm <> 0o700 then
3333+ failwith
3434+ (Printf.sprintf
3535+ "XDG_RUNTIME_DIR base directory %s has incorrect permissions: %o \
3636+ (must be 0700)"
3737+ path_str current_perm);
3838+ (* Check ownership - directory should be owned by current user *)
3939+ let uid = Unix.getuid () in
4040+ if stat.uid <> Int64.of_int uid then
4141+ failwith
4242+ (Printf.sprintf
4343+ "XDG_RUNTIME_DIR base directory %s not owned by current user (uid \
4444+ %d, owner %Ld)"
4545+ path_str uid stat.uid)
4646+ (* TODO: Check that directory is on local filesystem (not networked).
4747+ This would require filesystem type detection which is OS-specific. *)
4848+ with exn ->
4949+ failwith
5050+ (Printf.sprintf "Cannot validate XDG_RUNTIME_DIR: %s"
5151+ (Printexc.to_string exn))
5252+5353+let ensure_runtime_dir _fs app_runtime_path =
5454+ (* Base directory validation is done in resolve_runtime_dir,
5555+ so we just create the app subdirectory *)
5656+ ensure_dir app_runtime_path
5757+5858+let get_home_dir fs =
5959+ let home_str =
6060+ match Sys.getenv_opt "HOME" with
6161+ | Some home -> home
6262+ | None -> (
6363+ match Sys.os_type with
6464+ | "Win32" | "Cygwin" -> (
6565+ match Sys.getenv_opt "USERPROFILE" with
6666+ | Some profile -> profile
6767+ | None -> failwith "Cannot determine home directory")
6868+ | _ -> (
6969+ try Unix.((getpwuid (getuid ())).pw_dir)
7070+ with _ -> failwith "Cannot determine home directory"))
7171+ in
7272+ Eio.Path.(fs / home_str)
7373+7474+let make_env_var_name app_name suffix =
7575+ String.uppercase_ascii app_name ^ "_" ^ suffix
7676+7777+exception Invalid_xdg_path of string
7878+7979+let validate_absolute_path context path =
8080+ if Filename.is_relative path then
8181+ raise
8282+ (Invalid_xdg_path
8383+ (Printf.sprintf "%s must be an absolute path, got: %s" context path))
8484+8585+let resolve_path fs home_path base_path =
8686+ if Filename.is_relative base_path then Eio.Path.(home_path / base_path)
8787+ else Eio.Path.(fs / base_path)
8888+8989+(* Helper to resolve system directories (config_dirs or data_dirs) *)
9090+let resolve_system_dirs fs home_path app_name override_suffix xdg_var
9191+ default_paths =
9292+ let override_var = make_env_var_name app_name override_suffix in
9393+ match Sys.getenv_opt override_var with
9494+ | Some dirs when dirs <> "" ->
9595+ String.split_on_char ':' dirs
9696+ |> List.filter (fun s -> s <> "")
9797+ |> List.filter_map (fun path ->
9898+ try
9999+ validate_absolute_path override_var path;
100100+ Some Eio.Path.(resolve_path fs home_path path / app_name)
101101+ with Invalid_xdg_path _ -> None)
102102+ | Some _ | None -> (
103103+ match Sys.getenv_opt xdg_var with
104104+ | Some dirs when dirs <> "" ->
105105+ String.split_on_char ':' dirs
106106+ |> List.filter (fun s -> s <> "")
107107+ |> List.filter_map (fun path ->
108108+ try
109109+ validate_absolute_path xdg_var path;
110110+ Some Eio.Path.(resolve_path fs home_path path / app_name)
111111+ with Invalid_xdg_path _ -> None)
112112+ | Some _ | None ->
113113+ List.map
114114+ (fun path -> Eio.Path.(resolve_path fs home_path path / app_name))
115115+ default_paths)
116116+117117+(* Helper to resolve a user directory with override precedence *)
118118+let resolve_user_dir fs home_path app_name xdg_ctx xdg_getter override_suffix =
119119+ let override_var = make_env_var_name app_name override_suffix in
120120+ match Sys.getenv_opt override_var with
121121+ | Some dir when dir <> "" ->
122122+ validate_absolute_path override_var dir;
123123+ (Eio.Path.(fs / dir / app_name), Env override_var)
124124+ | Some _ | None ->
125125+ let xdg_base = xdg_getter xdg_ctx in
126126+ let base_path = resolve_path fs home_path xdg_base in
127127+ (Eio.Path.(base_path / app_name), Default)
128128+129129+(* Helper to resolve runtime directory (special case since it can be None) *)
130130+let resolve_runtime_dir fs home_path app_name xdg_ctx =
131131+ let override_var = make_env_var_name app_name "RUNTIME_DIR" in
132132+ match Sys.getenv_opt override_var with
133133+ | Some dir when dir <> "" ->
134134+ validate_absolute_path override_var dir;
135135+ (* Validate the base runtime directory has correct permissions *)
136136+ let base_runtime_dir = resolve_path fs home_path dir in
137137+ validate_runtime_base_dir base_runtime_dir;
138138+ (Some Eio.Path.(base_runtime_dir / app_name), Env override_var)
139139+ | Some _ | None ->
140140+ ( (match Xdg.runtime_dir xdg_ctx with
141141+ | Some base ->
142142+ (* Validate the base runtime directory has correct permissions *)
143143+ let base_runtime_dir = resolve_path fs home_path base in
144144+ validate_runtime_base_dir base_runtime_dir;
145145+ Some Eio.Path.(base_runtime_dir / app_name)
146146+ | None -> None),
147147+ Default )
148148+149149+let validate_standard_xdg_vars () =
150150+ (* Validate standard XDG environment variables for absolute paths *)
151151+ let xdg_vars =
152152+ [
153153+ "XDG_CONFIG_HOME";
154154+ "XDG_DATA_HOME";
155155+ "XDG_CACHE_HOME";
156156+ "XDG_STATE_HOME";
157157+ "XDG_RUNTIME_DIR";
158158+ "XDG_CONFIG_DIRS";
159159+ "XDG_DATA_DIRS";
160160+ ]
161161+ in
162162+ List.iter
163163+ (fun var ->
164164+ match Sys.getenv_opt var with
165165+ | Some value when value <> "" ->
166166+ if String.contains value ':' then
167167+ (* Colon-separated list - validate each part *)
168168+ String.split_on_char ':' value
169169+ |> List.filter (fun s -> s <> "")
170170+ |> List.iter (fun path -> validate_absolute_path var path)
171171+ else
172172+ (* Single path *)
173173+ validate_absolute_path var value
174174+ | _ -> ())
175175+ xdg_vars
176176+177177+let create fs app_name =
178178+ let fs = fs in
179179+ let home_path = get_home_dir fs in
180180+ (* First validate all standard XDG environment variables *)
181181+ validate_standard_xdg_vars ();
182182+ let xdg_ctx = Xdg.create ~env:Sys.getenv_opt () in
183183+ (* User directories *)
184184+ let config_dir, config_dir_source =
185185+ resolve_user_dir fs home_path app_name xdg_ctx Xdg.config_dir "CONFIG_DIR"
186186+ in
187187+ let data_dir, data_dir_source =
188188+ resolve_user_dir fs home_path app_name xdg_ctx Xdg.data_dir "DATA_DIR"
189189+ in
190190+ let cache_dir, cache_dir_source =
191191+ resolve_user_dir fs home_path app_name xdg_ctx Xdg.cache_dir "CACHE_DIR"
192192+ in
193193+ let state_dir, state_dir_source =
194194+ resolve_user_dir fs home_path app_name xdg_ctx Xdg.state_dir "STATE_DIR"
195195+ in
196196+ (* Runtime directory *)
197197+ let runtime_dir, runtime_dir_source =
198198+ resolve_runtime_dir fs home_path app_name xdg_ctx
199199+ in
200200+ (* System directories *)
201201+ let config_dirs =
202202+ resolve_system_dirs fs home_path app_name "CONFIG_DIRS" "XDG_CONFIG_DIRS"
203203+ [ "/etc/xdg" ]
204204+ in
205205+ let data_dirs =
206206+ resolve_system_dirs fs home_path app_name "DATA_DIRS" "XDG_DATA_DIRS"
207207+ [ "/usr/local/share"; "/usr/share" ]
208208+ in
209209+ ensure_dir config_dir;
210210+ ensure_dir data_dir;
211211+ ensure_dir cache_dir;
212212+ ensure_dir state_dir;
213213+ Option.iter (ensure_runtime_dir fs) runtime_dir;
214214+ {
215215+ app_name;
216216+ config_dir;
217217+ config_dir_source;
218218+ data_dir;
219219+ data_dir_source;
220220+ cache_dir;
221221+ cache_dir_source;
222222+ state_dir;
223223+ state_dir_source;
224224+ runtime_dir;
225225+ runtime_dir_source;
226226+ config_dirs;
227227+ data_dirs;
228228+ }
229229+230230+let app_name t = t.app_name
231231+let config_dir t = t.config_dir
232232+let data_dir t = t.data_dir
233233+let cache_dir t = t.cache_dir
234234+let state_dir t = t.state_dir
235235+let runtime_dir t = t.runtime_dir
236236+let config_dirs t = t.config_dirs
237237+let data_dirs t = t.data_dirs
238238+239239+(* File search following XDG specification *)
240240+let find_file_in_dirs dirs filename =
241241+ let rec search_dirs = function
242242+ | [] -> None
243243+ | dir :: remaining_dirs -> (
244244+ let file_path = Eio.Path.(dir / filename) in
245245+ try
246246+ (* Try to check if file exists and is readable *)
247247+ let _ = Eio.Path.stat ~follow:true file_path in
248248+ Some file_path
249249+ with _ ->
250250+ (* File is inaccessible (non-existent, permissions, etc.)
251251+ Skip and continue with next directory per XDG spec *)
252252+ search_dirs remaining_dirs)
253253+ in
254254+ search_dirs dirs
255255+256256+let find_config_file t filename =
257257+ (* Search user config dir first, then system config dirs *)
258258+ find_file_in_dirs (t.config_dir :: t.config_dirs) filename
259259+260260+let find_data_file t filename =
261261+ (* Search user data dir first, then system data dirs *)
262262+ find_file_in_dirs (t.data_dir :: t.data_dirs) filename
263263+264264+let pp ?(brief = false) ?(sources = false) ppf t =
265265+ let pp_source ppf = function
266266+ | Default -> Fmt.(styled `Faint string) ppf "default"
267267+ | Env var -> Fmt.pf ppf "%a" Fmt.(styled `Yellow string) ("env(" ^ var ^ ")")
268268+ | Cmdline -> Fmt.(styled `Blue string) ppf "cmdline"
269269+ in
270270+ let pp_path_with_source ppf path source =
271271+ if sources then
272272+ Fmt.pf ppf "%a %a"
273273+ Fmt.(styled `Green Eio.Path.pp)
274274+ path
275275+ Fmt.(styled `Faint (brackets pp_source))
276276+ source
277277+ else Fmt.(styled `Green Eio.Path.pp) ppf path
278278+ in
279279+ let pp_path_opt_with_source ppf path_opt source =
280280+ match path_opt with
281281+ | None ->
282282+ if sources then
283283+ Fmt.pf ppf "%a %a"
284284+ Fmt.(styled `Red string)
285285+ "<none>"
286286+ Fmt.(styled `Faint (brackets pp_source))
287287+ source
288288+ else Fmt.(styled `Red string) ppf "<none>"
289289+ | Some path -> pp_path_with_source ppf path source
290290+ in
291291+ let pp_paths ppf paths =
292292+ Fmt.(list ~sep:(any ";@ ") (styled `Green Eio.Path.pp)) ppf paths
293293+ in
294294+ if brief then
295295+ Fmt.pf ppf "%a config=%a data=%a>"
296296+ Fmt.(styled `Cyan string)
297297+ ("<xdg:" ^ t.app_name)
298298+ (fun ppf (path, source) -> pp_path_with_source ppf path source)
299299+ (t.config_dir, t.config_dir_source)
300300+ (fun ppf (path, source) -> pp_path_with_source ppf path source)
301301+ (t.data_dir, t.data_dir_source)
302302+ else (
303303+ Fmt.pf ppf "@[<v>%a@,"
304304+ Fmt.(styled `Bold string)
305305+ ("XDG directories for '" ^ t.app_name ^ "':");
306306+ Fmt.pf ppf "@[<v 2>%a@," Fmt.(styled `Bold string) "User directories:";
307307+ Fmt.pf ppf "%a %a@,"
308308+ Fmt.(styled `Cyan string)
309309+ "config:"
310310+ (fun ppf (path, source) -> pp_path_with_source ppf path source)
311311+ (t.config_dir, t.config_dir_source);
312312+ Fmt.pf ppf "%a %a@,"
313313+ Fmt.(styled `Cyan string)
314314+ "data:"
315315+ (fun ppf (path, source) -> pp_path_with_source ppf path source)
316316+ (t.data_dir, t.data_dir_source);
317317+ Fmt.pf ppf "%a %a@,"
318318+ Fmt.(styled `Cyan string)
319319+ "cache:"
320320+ (fun ppf (path, source) -> pp_path_with_source ppf path source)
321321+ (t.cache_dir, t.cache_dir_source);
322322+ Fmt.pf ppf "%a %a@,"
323323+ Fmt.(styled `Cyan string)
324324+ "state:"
325325+ (fun ppf (path, source) -> pp_path_with_source ppf path source)
326326+ (t.state_dir, t.state_dir_source);
327327+ Fmt.pf ppf "%a %a@]@,"
328328+ Fmt.(styled `Cyan string)
329329+ "runtime:"
330330+ (fun ppf (path_opt, source) ->
331331+ pp_path_opt_with_source ppf path_opt source)
332332+ (t.runtime_dir, t.runtime_dir_source);
333333+ Fmt.pf ppf "@[<v 2>%a@," Fmt.(styled `Bold string) "System directories:";
334334+ Fmt.pf ppf "%a [@[<hov>%a@]]@,"
335335+ Fmt.(styled `Cyan string)
336336+ "config_dirs:" pp_paths t.config_dirs;
337337+ Fmt.pf ppf "%a [@[<hov>%a@]]@]@]"
338338+ Fmt.(styled `Cyan string)
339339+ "data_dirs:" pp_paths t.data_dirs)
340340+341341+module Cmd = struct
342342+ type xdg_t = t
343343+ type 'a with_source = { value : 'a option; source : source }
344344+345345+ type t = {
346346+ config_dir : string with_source;
347347+ data_dir : string with_source;
348348+ cache_dir : string with_source;
349349+ state_dir : string with_source;
350350+ runtime_dir : string with_source;
351351+ }
352352+353353+ type dir = [ `Config | `Cache | `Data | `State | `Runtime ]
354354+355355+ let term app_name fs ?(dirs = [ `Config; `Data; `Cache; `State; `Runtime ]) ()
356356+ =
357357+ let open Cmdliner in
358358+ let app_upper = String.uppercase_ascii app_name in
359359+ let show_paths =
360360+ let doc = "Show only the resolved directory paths without formatting" in
361361+ Arg.(value & flag & info [ "show-paths" ] ~doc)
362362+ in
363363+ let has_dir d = List.mem d dirs in
364364+ let make_dir_arg ~enabled name env_suffix xdg_var default_path =
365365+ if not enabled then
366366+ (* Return a term that always gives the environment-only result *)
367367+ Term.(
368368+ const (fun () ->
369369+ let app_env = app_upper ^ "_" ^ env_suffix in
370370+ match Sys.getenv_opt app_env with
371371+ | Some v when v <> "" -> { value = Some v; source = Env app_env }
372372+ | Some _ | None -> (
373373+ match Sys.getenv_opt xdg_var with
374374+ | Some v -> { value = Some v; source = Env xdg_var }
375375+ | None -> { value = None; source = Default }))
376376+ $ const ())
377377+ else
378378+ let app_env = app_upper ^ "_" ^ env_suffix in
379379+ let doc =
380380+ match default_path with
381381+ | Some path ->
382382+ Printf.sprintf
383383+ "Override %s directory. Can also be set with %s or %s. \
384384+ Default: %s"
385385+ name app_env xdg_var path
386386+ | None ->
387387+ Printf.sprintf
388388+ "Override %s directory. Can also be set with %s or %s. No \
389389+ default value."
390390+ name app_env xdg_var
391391+ in
392392+ let arg =
393393+ Arg.(
394394+ value
395395+ & opt (some string) None
396396+ & info [ name ^ "-dir" ] ~docv:"DIR" ~doc)
397397+ in
398398+ Term.(
399399+ const (fun cmdline_val ->
400400+ match cmdline_val with
401401+ | Some v -> { value = Some v; source = Cmdline }
402402+ | None -> (
403403+ match Sys.getenv_opt app_env with
404404+ | Some v when v <> "" ->
405405+ { value = Some v; source = Env app_env }
406406+ | Some _ | None -> (
407407+ match Sys.getenv_opt xdg_var with
408408+ | Some v -> { value = Some v; source = Env xdg_var }
409409+ | None -> { value = None; source = Default })))
410410+ $ arg)
411411+ in
412412+ let home_prefix = "\\$HOME" in
413413+ let config_dir =
414414+ make_dir_arg ~enabled:(has_dir `Config) "config" "CONFIG_DIR"
415415+ "XDG_CONFIG_HOME"
416416+ (Some (home_prefix ^ "/.config/" ^ app_name))
417417+ in
418418+ let data_dir =
419419+ make_dir_arg ~enabled:(has_dir `Data) "data" "DATA_DIR" "XDG_DATA_HOME"
420420+ (Some (home_prefix ^ "/.local/share/" ^ app_name))
421421+ in
422422+ let cache_dir =
423423+ make_dir_arg ~enabled:(has_dir `Cache) "cache" "CACHE_DIR"
424424+ "XDG_CACHE_HOME"
425425+ (Some (home_prefix ^ "/.cache/" ^ app_name))
426426+ in
427427+ let state_dir =
428428+ make_dir_arg ~enabled:(has_dir `State) "state" "STATE_DIR"
429429+ "XDG_STATE_HOME"
430430+ (Some (home_prefix ^ "/.local/state/" ^ app_name))
431431+ in
432432+ let runtime_dir =
433433+ make_dir_arg ~enabled:(has_dir `Runtime) "runtime" "RUNTIME_DIR"
434434+ "XDG_RUNTIME_DIR" None
435435+ in
436436+ Term.(
437437+ const
438438+ (fun
439439+ show_paths_flag
440440+ config_dir_ws
441441+ data_dir_ws
442442+ cache_dir_ws
443443+ state_dir_ws
444444+ runtime_dir_ws
445445+ ->
446446+ let config =
447447+ {
448448+ config_dir = config_dir_ws;
449449+ data_dir = data_dir_ws;
450450+ cache_dir = cache_dir_ws;
451451+ state_dir = state_dir_ws;
452452+ runtime_dir = runtime_dir_ws;
453453+ }
454454+ in
455455+ let home_path = get_home_dir fs in
456456+ (* First validate all standard XDG environment variables *)
457457+ validate_standard_xdg_vars ();
458458+ let xdg_ctx = Xdg.create ~env:Sys.getenv_opt () in
459459+ (* Helper to resolve directory from config with source tracking *)
460460+ let resolve_from_config config_ws xdg_getter =
461461+ match config_ws.value with
462462+ | Some dir -> (resolve_path fs home_path dir, config_ws.source)
463463+ | None ->
464464+ let xdg_base = xdg_getter xdg_ctx in
465465+ let base_path = resolve_path fs home_path xdg_base in
466466+ (Eio.Path.(base_path / app_name), config_ws.source)
467467+ in
468468+ (* User directories *)
469469+ let config_dir, config_dir_source =
470470+ resolve_from_config config.config_dir Xdg.config_dir
471471+ in
472472+ let data_dir, data_dir_source =
473473+ resolve_from_config config.data_dir Xdg.data_dir
474474+ in
475475+ let cache_dir, cache_dir_source =
476476+ resolve_from_config config.cache_dir Xdg.cache_dir
477477+ in
478478+ let state_dir, state_dir_source =
479479+ resolve_from_config config.state_dir Xdg.state_dir
480480+ in
481481+ (* Runtime directory *)
482482+ let runtime_dir, runtime_dir_source =
483483+ match config.runtime_dir.value with
484484+ | Some dir ->
485485+ (Some (resolve_path fs home_path dir), config.runtime_dir.source)
486486+ | None ->
487487+ ( Option.map
488488+ (fun base ->
489489+ let base_path = resolve_path fs home_path base in
490490+ Eio.Path.(base_path / app_name))
491491+ (Xdg.runtime_dir xdg_ctx),
492492+ config.runtime_dir.source )
493493+ in
494494+ (* System directories - reuse shared helper *)
495495+ let config_dirs =
496496+ resolve_system_dirs fs home_path app_name "CONFIG_DIRS"
497497+ "XDG_CONFIG_DIRS" [ "/etc/xdg" ]
498498+ in
499499+ let data_dirs =
500500+ resolve_system_dirs fs home_path app_name "DATA_DIRS"
501501+ "XDG_DATA_DIRS"
502502+ [ "/usr/local/share"; "/usr/share" ]
503503+ in
504504+ ensure_dir config_dir;
505505+ ensure_dir data_dir;
506506+ ensure_dir cache_dir;
507507+ ensure_dir state_dir;
508508+ Option.iter (ensure_runtime_dir fs) runtime_dir;
509509+ let xdg =
510510+ {
511511+ app_name;
512512+ config_dir;
513513+ config_dir_source;
514514+ data_dir;
515515+ data_dir_source;
516516+ cache_dir;
517517+ cache_dir_source;
518518+ state_dir;
519519+ state_dir_source;
520520+ runtime_dir;
521521+ runtime_dir_source;
522522+ config_dirs;
523523+ data_dirs;
524524+ }
525525+ in
526526+ (* Handle --show-paths option *)
527527+ if show_paths_flag then (
528528+ let print_path name path =
529529+ match path with
530530+ | None -> Printf.printf "%s: <none>\n" name
531531+ | Some p -> Printf.printf "%s: %s\n" name (Eio.Path.native_exn p)
532532+ in
533533+ let print_paths name paths =
534534+ match paths with
535535+ | [] -> Printf.printf "%s: []\n" name
536536+ | paths ->
537537+ let paths_str =
538538+ String.concat ":" (List.map Eio.Path.native_exn paths)
539539+ in
540540+ Printf.printf "%s: %s\n" name paths_str
541541+ in
542542+ print_path "config_dir" (Some config_dir);
543543+ print_path "data_dir" (Some data_dir);
544544+ print_path "cache_dir" (Some cache_dir);
545545+ print_path "state_dir" (Some state_dir);
546546+ print_path "runtime_dir" runtime_dir;
547547+ print_paths "config_dirs" config_dirs;
548548+ print_paths "data_dirs" data_dirs;
549549+ Stdlib.exit 0);
550550+ (xdg, config))
551551+ $ show_paths $ config_dir $ data_dir $ cache_dir $ state_dir $ runtime_dir)
552552+553553+ let cache_term app_name =
554554+ let open Cmdliner in
555555+ let app_upper = String.uppercase_ascii app_name in
556556+ let app_env = app_upper ^ "_CACHE_DIR" in
557557+ let xdg_var = "XDG_CACHE_HOME" in
558558+ let home = Sys.getenv "HOME" in
559559+ let default_path = home ^ "/.cache/" ^ app_name in
560560+561561+ let doc =
562562+ Printf.sprintf
563563+ "Override cache directory. Can also be set with %s or %s. Default: %s"
564564+ app_env xdg_var default_path
565565+ in
566566+567567+ let arg =
568568+ Arg.(
569569+ value & opt string default_path
570570+ & info [ "cache-dir"; "c" ] ~docv:"DIR" ~doc)
571571+ in
572572+573573+ Term.(
574574+ const (fun cmdline_val ->
575575+ (* Check command line first *)
576576+ if cmdline_val <> default_path then cmdline_val
577577+ else
578578+ (* Then check app-specific env var *)
579579+ match Sys.getenv_opt app_env with
580580+ | Some v when v <> "" -> v
581581+ | _ -> (
582582+ (* Then check XDG env var *)
583583+ match Sys.getenv_opt xdg_var with
584584+ | Some v when v <> "" -> v ^ "/" ^ app_name
585585+ | _ -> default_path))
586586+ $ arg)
587587+588588+ let env_docs app_name =
589589+ let app_upper = String.uppercase_ascii app_name in
590590+ Printf.sprintf
591591+ {|
592592+Configuration Precedence (follows standard Unix conventions):
593593+ 1. Command-line flags (e.g., --config-dir) - highest priority
594594+ 2. Application-specific environment variable (e.g., %s_CONFIG_DIR)
595595+ 3. XDG standard environment variable (e.g., XDG_CONFIG_HOME)
596596+ 4. Default path (e.g., ~/.config/%s) - lowest priority
597597+598598+ This allows per-application overrides without affecting other XDG-compliant programs.
599599+ For example, setting %s_CONFIG_DIR only changes the config directory for %s,
600600+ while XDG_CONFIG_HOME affects all XDG-compliant applications.
601601+602602+Application-specific variables:
603603+ %s_CONFIG_DIR Override config directory for %s only
604604+ %s_DATA_DIR Override data directory for %s only
605605+ %s_CACHE_DIR Override cache directory for %s only
606606+ %s_STATE_DIR Override state directory for %s only
607607+ %s_RUNTIME_DIR Override runtime directory for %s only
608608+609609+XDG standard variables (shared by all XDG applications):
610610+ XDG_CONFIG_HOME User configuration directory (default: ~/.config/%s)
611611+ XDG_DATA_HOME User data directory (default: ~/.local/share/%s)
612612+ XDG_CACHE_HOME User cache directory (default: ~/.cache/%s)
613613+ XDG_STATE_HOME User state directory (default: ~/.local/state/%s)
614614+ XDG_RUNTIME_DIR User runtime directory (no default)
615615+ XDG_CONFIG_DIRS System configuration directories (default: /etc/xdg/%s)
616616+ XDG_DATA_DIRS System data directories (default: /usr/local/share/%s:/usr/share/%s)
617617+|}
618618+ app_upper app_name app_upper app_name app_upper app_name app_upper
619619+ app_name app_upper app_name app_upper app_name app_upper app_name app_name
620620+ app_name app_name app_name app_name app_name app_name
621621+622622+ let pp ppf config =
623623+ let pp_source ppf = function
624624+ | Default -> Fmt.(styled `Faint string) ppf "default"
625625+ | Env var ->
626626+ Fmt.pf ppf "%a" Fmt.(styled `Yellow string) ("env(" ^ var ^ ")")
627627+ | Cmdline -> Fmt.(styled `Blue string) ppf "cmdline"
628628+ in
629629+ let pp_with_source name ppf ws =
630630+ match ws.value with
631631+ | None when ws.source = Default -> ()
632632+ | None ->
633633+ Fmt.pf ppf "@,%a %a %a"
634634+ Fmt.(styled `Cyan string)
635635+ (name ^ ":")
636636+ Fmt.(styled `Red string)
637637+ "<unset>"
638638+ Fmt.(styled `Faint (brackets pp_source))
639639+ ws.source
640640+ | Some value ->
641641+ Fmt.pf ppf "@,%a %a %a"
642642+ Fmt.(styled `Cyan string)
643643+ (name ^ ":")
644644+ Fmt.(styled `Green string)
645645+ value
646646+ Fmt.(styled `Faint (brackets pp_source))
647647+ ws.source
648648+ in
649649+ Fmt.pf ppf "@[<v>%a%a%a%a%a%a@]"
650650+ Fmt.(styled `Bold string)
651651+ "XDG config:"
652652+ (pp_with_source "config_dir")
653653+ config.config_dir
654654+ (pp_with_source "data_dir")
655655+ config.data_dir
656656+ (pp_with_source "cache_dir")
657657+ config.cache_dir
658658+ (pp_with_source "state_dir")
659659+ config.state_dir
660660+ (pp_with_source "runtime_dir")
661661+ config.runtime_dir
662662+end
+441
vendor/opam/xdge/lib/xdge.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** XDG Base Directory Specification support with Eio capabilities
77+88+ This library provides an OCaml implementation of the XDG Base Directory
99+ Specification with Eio filesystem integration. The XDG specification defines
1010+ standard locations for user-specific and system-wide application files,
1111+ helping to keep user home directories clean and organized.
1212+1313+ The specification is available at:
1414+ {{:https://specifications.freedesktop.org/basedir-spec/latest/} XDG Base
1515+ Directory Specification}
1616+1717+ {b Key Concepts:}
1818+1919+ The XDG specification defines several types of directories:
2020+ - {b User directories}: Store user-specific files (config, data, cache,
2121+ state, runtime)
2222+ - {b System directories}: Store system-wide files shared across users
2323+ - {b Precedence}: User directories take precedence over system directories
2424+ - {b Application isolation}: Each application gets its own subdirectory
2525+2626+ {b Environment Variable Precedence:}
2727+2828+ This library follows a three-level precedence system:
2929+ + Application-specific variables (e.g., [MYAPP_CONFIG_DIR]) - highest
3030+ priority
3131+ + XDG standard variables (e.g., [XDG_CONFIG_HOME])
3232+ + Default paths (e.g., [$HOME/.config]) - lowest priority
3333+3434+ This allows fine-grained control over directory locations without affecting
3535+ other XDG-compliant applications.
3636+3737+ {b Directory Creation:}
3838+3939+ All directories are automatically created with appropriate permissions
4040+ (0o755) when accessed, except for runtime directories which require stricter
4141+ permissions as per the specification.
4242+4343+ @see <https://specifications.freedesktop.org/basedir-spec/latest/>
4444+ XDG Base Directory Specification
4545+4646+ {2 Related Libraries}
4747+4848+ This library is used by:
4949+5050+ {ul
5151+ {- [Requests] - HTTP client that uses Xdge for cookie persistence paths}
5252+ {- [Cookeio_jar] - Cookie jar with XDG-compliant storage}} *)
5353+5454+type t
5555+(** The main XDG context type containing all directory paths for an application.
5656+5757+ A value of type [t] represents the complete XDG directory structure for a
5858+ specific application, including both user-specific and system-wide
5959+ directories. All paths are resolved at creation time and are absolute paths
6060+ within the Eio filesystem. *)
6161+6262+(** {1 Exceptions} *)
6363+6464+exception Invalid_xdg_path of string
6565+(** Exception raised when XDG environment variables contain invalid paths.
6666+6767+ The XDG specification requires all paths in environment variables to be
6868+ absolute. This exception is raised when a relative path is found. *)
6969+7070+(** {1 Construction} *)
7171+7272+val create : Eio.Fs.dir_ty Eio.Path.t -> string -> t
7373+(** [create fs app_name] creates an XDG context for the given application.
7474+7575+ This function initializes the complete XDG directory structure for your
7676+ application, resolving all paths according to the environment variables and
7777+ creating directories as needed.
7878+7979+ @param fs The Eio filesystem providing filesystem access
8080+ @param app_name The name of your application (used as subdirectory name)
8181+8282+ {b Path Resolution:}
8383+8484+ For each directory type, the following precedence is used:
8585+ + Application-specific environment variable (e.g., [MYAPP_CONFIG_DIR])
8686+ + XDG standard environment variable (e.g., [XDG_CONFIG_HOME])
8787+ + Default path as specified in the XDG specification
8888+8989+ {b Example:}
9090+ {[
9191+ let xdg = Xdge.create env#fs "myapp" in
9292+ let config = Xdge.config_dir xdg in
9393+ (* config is now <fs:$HOME/.config/myapp> or the overridden path *)
9494+ ]}
9595+9696+ All directories are created with permissions 0o755 if they don't exist,
9797+ except for runtime directories which are created with 0o700 permissions and
9898+ validated according to the XDG specification.
9999+100100+ @raise Invalid_xdg_path if any environment variable contains a relative path
101101+*)
102102+103103+(** {1 Accessors} *)
104104+105105+val app_name : t -> string
106106+(** [app_name t] returns the application name used when creating this XDG
107107+ context.
108108+109109+ This is the name that was passed to {!create} and is used as the
110110+ subdirectory name within each XDG base directory. *)
111111+112112+(** {1 Base Directories} *)
113113+114114+val config_dir : t -> Eio.Fs.dir_ty Eio.Path.t
115115+(** [config_dir t] returns the path to user-specific configuration files.
116116+117117+ {b Purpose:} Store user preferences, settings, and configuration files.
118118+ Configuration files should be human-readable when possible.
119119+120120+ {b Environment Variables:}
121121+ - [${APP_NAME}_CONFIG_DIR]: Application-specific override (highest priority)
122122+ - [XDG_CONFIG_HOME]: XDG standard variable
123123+ - Default: [$HOME/.config/{app_name}]
124124+125125+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
126126+ XDG_CONFIG_HOME specification *)
127127+128128+val data_dir : t -> Eio.Fs.dir_ty Eio.Path.t
129129+(** [data_dir t] returns the path to user-specific data files.
130130+131131+ {b Purpose:} Store persistent application data that should be preserved
132132+ across application restarts and system reboots. This data is typically not
133133+ modified by users directly.
134134+135135+ {b Environment Variables:}
136136+ - [${APP_NAME}_DATA_DIR]: Application-specific override (highest priority)
137137+ - [XDG_DATA_HOME]: XDG standard variable
138138+ - Default: [$HOME/.local/share/{app_name}]
139139+140140+ {b Example Files:}
141141+ - Application databases
142142+ - User-generated content (documents, projects)
143143+ - Downloaded resources
144144+ - Application plugins or extensions
145145+146146+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
147147+ XDG_DATA_HOME specification *)
148148+149149+val cache_dir : t -> Eio.Fs.dir_ty Eio.Path.t
150150+(** [cache_dir t] returns the path to user-specific cache files.
151151+152152+ {b Purpose:} Store non-essential cached data that can be regenerated if
153153+ deleted. The application should remain functional if this directory is
154154+ cleared, though performance may be temporarily impacted.
155155+156156+ {b Environment Variables:}
157157+ - [${APP_NAME}_CACHE_DIR]: Application-specific override (highest priority)
158158+ - [XDG_CACHE_HOME]: XDG standard variable
159159+ - Default: [$HOME/.cache/{app_name}]
160160+161161+ {b Example Files:}
162162+ - Downloaded thumbnails and previews
163163+ - Compiled bytecode or object files
164164+ - Network response caches
165165+ - Temporary computation results
166166+167167+ Users may clear cache directories to free disk space, so always check for
168168+ cache validity and be prepared to regenerate data.
169169+170170+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
171171+ XDG_CACHE_HOME specification *)
172172+173173+val state_dir : t -> Eio.Fs.dir_ty Eio.Path.t
174174+(** [state_dir t] returns the path to user-specific state files.
175175+176176+ {b Purpose:} Store persistent state data that should be preserved between
177177+ application restarts but is not important enough to be user data. This
178178+ includes application state that can be regenerated but would impact the user
179179+ experience if lost.
180180+181181+ {b Environment Variables:}
182182+ - [${APP_NAME}_STATE_DIR]: Application-specific override (highest priority)
183183+ - [XDG_STATE_HOME]: XDG standard variable
184184+ - Default: [$HOME/.local/state/{app_name}]
185185+186186+ {b Example Files:}
187187+ - Application history (recently used files, command history)
188188+ - Current application state (window positions, open tabs)
189189+ - Logs and journal files
190190+ - Undo/redo history
191191+192192+ {b Comparison with other directories:}
193193+ - Unlike cache: State should persist between reboots
194194+ - Unlike data: State can be regenerated (though inconvenient)
195195+ - Unlike config: State changes frequently during normal use
196196+197197+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
198198+ XDG_STATE_HOME specification *)
199199+200200+val runtime_dir : t -> Eio.Fs.dir_ty Eio.Path.t option
201201+(** [runtime_dir t] returns the path to user-specific runtime files.
202202+203203+ {b Purpose:} Store runtime files such as sockets, named pipes, and process
204204+ IDs. These files are only valid for the duration of the user's login
205205+ session.
206206+207207+ {b Environment Variables:}
208208+ - [${APP_NAME}_RUNTIME_DIR]: Application-specific override (highest
209209+ priority)
210210+ - [XDG_RUNTIME_DIR]: XDG standard variable
211211+ - Default: None (returns [None] if not set)
212212+213213+ {b Required Properties (per specification):}
214214+ - Owned by the user with access mode 0700
215215+ - Bound to the user login session lifetime
216216+ - Located on a local filesystem (not networked)
217217+ - Fully-featured by the OS (supporting proper locking, etc.)
218218+219219+ {b Example Files:}
220220+ - Unix domain sockets
221221+ - Named pipes (FIFOs)
222222+ - Lock files
223223+ - Small process communication files
224224+225225+ This may return [None] if no suitable runtime directory is available.
226226+ Applications should handle this gracefully, perhaps by falling back to
227227+ [/tmp] with appropriate security measures.
228228+229229+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
230230+ XDG_RUNTIME_DIR specification *)
231231+232232+(** {1 System Directories} *)
233233+234234+val config_dirs : t -> Eio.Fs.dir_ty Eio.Path.t list
235235+(** [config_dirs t] returns search paths for system-wide configuration files.
236236+237237+ {b Purpose:} Provide a search path for configuration files that are shared
238238+ between multiple users. Files in user-specific {!config_dir} take precedence
239239+ over these system directories.
240240+241241+ {b Environment Variables:}
242242+ - [${APP_NAME}_CONFIG_DIRS]: Application-specific override (highest
243243+ priority)
244244+ - [XDG_CONFIG_DIRS]: XDG standard variable (colon-separated list)
245245+ - Default: [[/etc/xdg/{app_name}]]
246246+247247+ {b Search Order:} Directories are ordered by preference, with earlier
248248+ entries taking precedence over later ones. When looking for a configuration
249249+ file, search {!config_dir} first, then each directory in this list.
250250+251251+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
252252+ XDG_CONFIG_DIRS specification *)
253253+254254+val data_dirs : t -> Eio.Fs.dir_ty Eio.Path.t list
255255+(** [data_dirs t] returns search paths for system-wide data files.
256256+257257+ {b Purpose:} Provide a search path for data files that are shared between
258258+ multiple users. Files in user-specific {!data_dir} take precedence over
259259+ these system directories.
260260+261261+ {b Environment Variables:}
262262+ - [${APP_NAME}_DATA_DIRS]: Application-specific override (highest priority)
263263+ - [XDG_DATA_DIRS]: XDG standard variable (colon-separated list)
264264+ - Default: [[/usr/local/share/{app_name}; /usr/share/{app_name}]]
265265+266266+ {b Search Order:} Directories are ordered by preference, with earlier
267267+ entries taking precedence over later ones. When looking for a data file,
268268+ search {!data_dir} first, then each directory in this list.
269269+270270+ {b Example Files:}
271271+ - Application icons and themes
272272+ - Desktop files
273273+ - Shared application resources
274274+ - Documentation files
275275+ - Default templates
276276+277277+ @see <https://specifications.freedesktop.org/basedir-spec/latest/#variables>
278278+ XDG_DATA_DIRS specification *)
279279+280280+(** {1 File Search} *)
281281+282282+val find_config_file : t -> string -> Eio.Fs.dir_ty Eio.Path.t option
283283+(** [find_config_file t filename] searches for a configuration file following
284284+ XDG precedence.
285285+286286+ This function searches for the given filename in the user configuration
287287+ directory first, then in system configuration directories in order of
288288+ preference. Files that are inaccessible (due to permissions, non-existence,
289289+ etc.) are silently skipped as per the XDG specification.
290290+291291+ @param t The XDG context
292292+ @param filename The name of the file to search for
293293+ @return [Some path] if found, [None] if not found in any directory
294294+295295+ {b Search Order:} 1. User config directory ({!config_dir}) 2. System config
296296+ directories ({!config_dirs}) in preference order *)
297297+298298+val find_data_file : t -> string -> Eio.Fs.dir_ty Eio.Path.t option
299299+(** [find_data_file t filename] searches for a data file following XDG
300300+ precedence.
301301+302302+ This function searches for the given filename in the user data directory
303303+ first, then in system data directories in order of preference. Files that
304304+ are inaccessible (due to permissions, non-existence, etc.) are silently
305305+ skipped as per the XDG specification.
306306+307307+ @param t The XDG context
308308+ @param filename The name of the file to search for
309309+ @return [Some path] if found, [None] if not found in any directory
310310+311311+ {b Search Order:} 1. User data directory ({!data_dir}) 2. System data
312312+ directories ({!data_dirs}) in preference order *)
313313+314314+(** {1 Pretty Printing} *)
315315+316316+val pp : ?brief:bool -> ?sources:bool -> Format.formatter -> t -> unit
317317+(** [pp ?brief ?sources ppf t] pretty prints the XDG directory configuration.
318318+319319+ @param brief If [true], prints a compact one-line summary (default: [false])
320320+ @param sources
321321+ If [true], shows the source of each directory value, indicating whether it
322322+ came from defaults, environment variables, or command line (default:
323323+ [false])
324324+ @param ppf The formatter to print to
325325+ @param t The XDG context to print
326326+327327+ {b Output formats:}
328328+ - Normal: Multi-line detailed view of all directories
329329+ - Brief: Single line showing app name and key directories
330330+ - With sources: Adds annotations showing where each path came from *)
331331+332332+(** {1 Cmdliner Integration} *)
333333+334334+module Cmd : sig
335335+ (** The type of the outer XDG context *)
336336+ type xdg_t = t
337337+ (** Cmdliner integration for XDG directory configuration.
338338+339339+ This module provides integration with the Cmdliner library, allowing XDG
340340+ directories to be configured via command-line arguments while respecting
341341+ the precedence of environment variables. *)
342342+343343+ type t
344344+ (** Type of XDG configuration gathered from command-line and environment.
345345+346346+ This contains all XDG directory paths along with their sources, as
347347+ determined by command-line arguments and environment variables. *)
348348+349349+ type dir =
350350+ [ `Config (** User configuration files *)
351351+ | `Cache (** User-specific cached data *)
352352+ | `Data (** User-specific application data *)
353353+ | `State (** User-specific state data (logs, history, etc.) *)
354354+ | `Runtime (** User-specific runtime files (sockets, pipes, etc.) *) ]
355355+ (** XDG directory types for specifying which directories an application needs.
356356+357357+ These allow applications to declare which XDG directories they use,
358358+ enabling runtime systems to only provide the requested directories. *)
359359+360360+ val term :
361361+ string ->
362362+ Eio.Fs.dir_ty Eio.Path.t ->
363363+ ?dirs:dir list ->
364364+ unit ->
365365+ (xdg_t * t) Cmdliner.Term.t
366366+ (** [term app_name fs ?dirs ()] creates a Cmdliner term for XDG directory
367367+ configuration.
368368+369369+ This function generates a Cmdliner term that handles XDG directory
370370+ configuration through both command-line flags and environment variables,
371371+ and directly returns the XDG context. Only command-line flags for the
372372+ requested directories are generated.
373373+374374+ @param app_name
375375+ The application name (used for environment variable prefixes)
376376+ @param fs The Eio filesystem to use for path resolution
377377+ @param dirs
378378+ List of directories to include flags for (default: all directories)
379379+380380+ {b Generated Command-line Flags:} Only the flags for requested directories
381381+ are generated:
382382+ - [--config-dir DIR]: Override configuration directory (if [`Config] in
383383+ dirs)
384384+ - [--data-dir DIR]: Override data directory (if [`Data] in dirs)
385385+ - [--cache-dir DIR]: Override cache directory (if [`Cache] in dirs)
386386+ - [--state-dir DIR]: Override state directory (if [`State] in dirs)
387387+ - [--runtime-dir DIR]: Override runtime directory (if [`Runtime] in dirs)
388388+389389+ {b Environment Variable Precedence:} For each directory type, the
390390+ following precedence applies:
391391+ + Command-line flag (e.g., [--config-dir]) - if enabled
392392+ + Application-specific variable (e.g., [MYAPP_CONFIG_DIR])
393393+ + XDG standard variable (e.g., [XDG_CONFIG_HOME])
394394+ + Default value *)
395395+396396+ val cache_term : string -> string Cmdliner.Term.t
397397+ (** [cache_term app_name] creates a Cmdliner term that provides just the cache
398398+ directory path as a string, respecting XDG precedence.
399399+400400+ This is a convenience function for applications that only need cache
401401+ directory configuration. It returns the resolved cache directory path
402402+ directly as a string, suitable for use in other Cmdliner terms.
403403+404404+ @param app_name
405405+ The application name (used for environment variable prefixes)
406406+407407+ {b Generated Command-line Flag:}
408408+ - [--cache-dir DIR]: Override cache directory
409409+410410+ {b Environment Variable Precedence:}
411411+ + Command-line flag ([--cache-dir])
412412+ + Application-specific variable (e.g., [MYAPP_CACHE_DIR])
413413+ + XDG standard variable ([XDG_CACHE_HOME])
414414+ + Default value ([$HOME/.cache/{app_name}]) *)
415415+416416+ val env_docs : string -> string
417417+ (** [env_docs app_name] generates documentation for environment variables.
418418+419419+ Returns a formatted string documenting all environment variables that
420420+ affect XDG directory configuration for the given application. This is
421421+ useful for generating man pages or help text.
422422+423423+ @param app_name The application name
424424+ @return A formatted documentation string
425425+426426+ {b Included Information:}
427427+ - Configuration precedence rules
428428+ - Application-specific environment variables
429429+ - XDG standard environment variables
430430+ - Default values for each directory type *)
431431+432432+ val pp : Format.formatter -> t -> unit
433433+ (** [pp ppf config] pretty prints a Cmdliner configuration.
434434+435435+ This function formats the configuration showing each directory path along
436436+ with its source, which is helpful for debugging configuration issues or
437437+ displaying the current configuration to users.
438438+439439+ @param ppf The formatter to print to
440440+ @param config The configuration to print *)
441441+end