···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
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
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-(* Check if an Eio exception indicates a missing file/directory *)
240240-let is_not_found_error = function
241241- | Eio.Io (Eio.Fs.E (Eio.Fs.Not_found _), _) -> true
242242- | Eio.Io (Eio.Fs.E (Eio.Fs.Permission_denied _), _) -> true
243243- | _ -> false
244244-245245-(* File search following XDG specification *)
246246-let find_file_in_dirs dirs filename =
247247- let rec search_dirs = function
248248- | [] -> None
249249- | dir :: remaining_dirs -> (
250250- let file_path = Eio.Path.(dir / filename) in
251251- try
252252- (* Try to check if file exists and is readable *)
253253- let _ = Eio.Path.stat ~follow:true file_path in
254254- Some file_path
255255- with exn when is_not_found_error exn ->
256256- (* File is inaccessible (non-existent, permissions, etc.)
257257- Skip and continue with next directory per XDG spec *)
258258- search_dirs remaining_dirs)
259259- in
260260- search_dirs dirs
261261-262262-let find_config_file t filename =
263263- (* Search user config dir first, then system config dirs *)
264264- find_file_in_dirs (t.config_dir :: t.config_dirs) filename
265265-266266-let find_data_file t filename =
267267- (* Search user data dir first, then system data dirs *)
268268- find_file_in_dirs (t.data_dir :: t.data_dirs) filename
269269-270270-let pp ?(brief = false) ?(sources = false) ppf t =
271271- let pp_source ppf = function
272272- | Default -> Fmt.(styled `Faint string) ppf "default"
273273- | Env var -> Fmt.pf ppf "%a" Fmt.(styled `Yellow string) ("env(" ^ var ^ ")")
274274- | Cmdline -> Fmt.(styled `Blue string) ppf "cmdline"
275275- in
276276- let pp_path_with_source ppf path source =
277277- if sources then
278278- Fmt.pf ppf "%a %a"
279279- Fmt.(styled `Green Eio.Path.pp)
280280- path
281281- Fmt.(styled `Faint (brackets pp_source))
282282- source
283283- else Fmt.(styled `Green Eio.Path.pp) ppf path
284284- in
285285- let pp_path_opt_with_source ppf path_opt source =
286286- match path_opt with
287287- | None ->
288288- if sources then
289289- Fmt.pf ppf "%a %a"
290290- Fmt.(styled `Red string)
291291- "<none>"
292292- Fmt.(styled `Faint (brackets pp_source))
293293- source
294294- else Fmt.(styled `Red string) ppf "<none>"
295295- | Some path -> pp_path_with_source ppf path source
296296- in
297297- let pp_paths ppf paths =
298298- Fmt.(list ~sep:(any ";@ ") (styled `Green Eio.Path.pp)) ppf paths
299299- in
300300- if brief then
301301- Fmt.pf ppf "%a config=%a data=%a>"
302302- Fmt.(styled `Cyan string)
303303- ("<xdg:" ^ t.app_name)
304304- (fun ppf (path, source) -> pp_path_with_source ppf path source)
305305- (t.config_dir, t.config_dir_source)
306306- (fun ppf (path, source) -> pp_path_with_source ppf path source)
307307- (t.data_dir, t.data_dir_source)
308308- else (
309309- Fmt.pf ppf "@[<v>%a@,"
310310- Fmt.(styled `Bold string)
311311- ("XDG directories for '" ^ t.app_name ^ "':");
312312- Fmt.pf ppf "@[<v 2>%a@," Fmt.(styled `Bold string) "User directories:";
313313- Fmt.pf ppf "%a %a@,"
314314- Fmt.(styled `Cyan string)
315315- "config:"
316316- (fun ppf (path, source) -> pp_path_with_source ppf path source)
317317- (t.config_dir, t.config_dir_source);
318318- Fmt.pf ppf "%a %a@,"
319319- Fmt.(styled `Cyan string)
320320- "data:"
321321- (fun ppf (path, source) -> pp_path_with_source ppf path source)
322322- (t.data_dir, t.data_dir_source);
323323- Fmt.pf ppf "%a %a@,"
324324- Fmt.(styled `Cyan string)
325325- "cache:"
326326- (fun ppf (path, source) -> pp_path_with_source ppf path source)
327327- (t.cache_dir, t.cache_dir_source);
328328- Fmt.pf ppf "%a %a@,"
329329- Fmt.(styled `Cyan string)
330330- "state:"
331331- (fun ppf (path, source) -> pp_path_with_source ppf path source)
332332- (t.state_dir, t.state_dir_source);
333333- Fmt.pf ppf "%a %a@]@,"
334334- Fmt.(styled `Cyan string)
335335- "runtime:"
336336- (fun ppf (path_opt, source) ->
337337- pp_path_opt_with_source ppf path_opt source)
338338- (t.runtime_dir, t.runtime_dir_source);
339339- Fmt.pf ppf "@[<v 2>%a@," Fmt.(styled `Bold string) "System directories:";
340340- Fmt.pf ppf "%a [@[<hov>%a@]]@,"
341341- Fmt.(styled `Cyan string)
342342- "config_dirs:" pp_paths t.config_dirs;
343343- Fmt.pf ppf "%a [@[<hov>%a@]]@]@]"
344344- Fmt.(styled `Cyan string)
345345- "data_dirs:" pp_paths t.data_dirs)
346346-347347-module Cmd = struct
348348- type xdg_t = t
349349- type 'a with_source = { value : 'a option; source : source }
350350-351351- type t = {
352352- config_dir : string with_source;
353353- data_dir : string with_source;
354354- cache_dir : string with_source;
355355- state_dir : string with_source;
356356- runtime_dir : string with_source;
357357- }
358358-359359- type dir = [ `Config | `Cache | `Data | `State | `Runtime ]
360360-361361- let term app_name fs ?(dirs = [ `Config; `Data; `Cache; `State; `Runtime ]) ()
362362- =
363363- let open Cmdliner in
364364- let app_upper = String.uppercase_ascii app_name in
365365- let show_paths =
366366- let doc = "Show only the resolved directory paths without formatting" in
367367- Arg.(value & flag & info [ "show-paths" ] ~doc)
368368- in
369369- let has_dir d = List.mem d dirs in
370370- let make_dir_arg ~enabled name env_suffix xdg_var default_path =
371371- if not enabled then
372372- (* Return a term that always gives the environment-only result *)
373373- Term.(
374374- const (fun () ->
375375- let app_env = app_upper ^ "_" ^ env_suffix in
376376- match Sys.getenv_opt app_env with
377377- | Some v when v <> "" -> { value = Some v; source = Env app_env }
378378- | Some _ | None -> (
379379- match Sys.getenv_opt xdg_var with
380380- | Some v -> { value = Some v; source = Env xdg_var }
381381- | None -> { value = None; source = Default }))
382382- $ const ())
383383- else
384384- let app_env = app_upper ^ "_" ^ env_suffix in
385385- let doc =
386386- match default_path with
387387- | Some path ->
388388- Printf.sprintf
389389- "Override %s directory. Can also be set with %s or %s. \
390390- Default: %s"
391391- name app_env xdg_var path
392392- | None ->
393393- Printf.sprintf
394394- "Override %s directory. Can also be set with %s or %s. No \
395395- default value."
396396- name app_env xdg_var
397397- in
398398- let arg =
399399- Arg.(
400400- value
401401- & opt (some string) None
402402- & info [ name ^ "-dir" ] ~docv:"DIR" ~doc)
403403- in
404404- Term.(
405405- const (fun cmdline_val ->
406406- match cmdline_val with
407407- | Some v -> { value = Some v; source = Cmdline }
408408- | None -> (
409409- match Sys.getenv_opt app_env with
410410- | Some v when v <> "" ->
411411- { value = Some v; source = Env app_env }
412412- | Some _ | None -> (
413413- match Sys.getenv_opt xdg_var with
414414- | Some v -> { value = Some v; source = Env xdg_var }
415415- | None -> { value = None; source = Default })))
416416- $ arg)
417417- in
418418- let home_prefix = "\\$HOME" in
419419- let config_dir =
420420- make_dir_arg ~enabled:(has_dir `Config) "config" "CONFIG_DIR"
421421- "XDG_CONFIG_HOME"
422422- (Some (home_prefix ^ "/.config/" ^ app_name))
423423- in
424424- let data_dir =
425425- make_dir_arg ~enabled:(has_dir `Data) "data" "DATA_DIR" "XDG_DATA_HOME"
426426- (Some (home_prefix ^ "/.local/share/" ^ app_name))
427427- in
428428- let cache_dir =
429429- make_dir_arg ~enabled:(has_dir `Cache) "cache" "CACHE_DIR"
430430- "XDG_CACHE_HOME"
431431- (Some (home_prefix ^ "/.cache/" ^ app_name))
432432- in
433433- let state_dir =
434434- make_dir_arg ~enabled:(has_dir `State) "state" "STATE_DIR"
435435- "XDG_STATE_HOME"
436436- (Some (home_prefix ^ "/.local/state/" ^ app_name))
437437- in
438438- let runtime_dir =
439439- make_dir_arg ~enabled:(has_dir `Runtime) "runtime" "RUNTIME_DIR"
440440- "XDG_RUNTIME_DIR" None
441441- in
442442- Term.(
443443- const
444444- (fun
445445- show_paths_flag
446446- config_dir_ws
447447- data_dir_ws
448448- cache_dir_ws
449449- state_dir_ws
450450- runtime_dir_ws
451451- ->
452452- let config =
453453- {
454454- config_dir = config_dir_ws;
455455- data_dir = data_dir_ws;
456456- cache_dir = cache_dir_ws;
457457- state_dir = state_dir_ws;
458458- runtime_dir = runtime_dir_ws;
459459- }
460460- in
461461- let home_path = get_home_dir fs in
462462- (* First validate all standard XDG environment variables *)
463463- validate_standard_xdg_vars ();
464464- let xdg_ctx = Xdg.create ~env:Sys.getenv_opt () in
465465- (* Helper to resolve directory from config with source tracking *)
466466- let resolve_from_config config_ws xdg_getter =
467467- match config_ws.value with
468468- | Some dir -> (resolve_path fs home_path dir, config_ws.source)
469469- | None ->
470470- let xdg_base = xdg_getter xdg_ctx in
471471- let base_path = resolve_path fs home_path xdg_base in
472472- (Eio.Path.(base_path / app_name), config_ws.source)
473473- in
474474- (* User directories *)
475475- let config_dir, config_dir_source =
476476- resolve_from_config config.config_dir Xdg.config_dir
477477- in
478478- let data_dir, data_dir_source =
479479- resolve_from_config config.data_dir Xdg.data_dir
480480- in
481481- let cache_dir, cache_dir_source =
482482- resolve_from_config config.cache_dir Xdg.cache_dir
483483- in
484484- let state_dir, state_dir_source =
485485- resolve_from_config config.state_dir Xdg.state_dir
486486- in
487487- (* Runtime directory *)
488488- let runtime_dir, runtime_dir_source =
489489- match config.runtime_dir.value with
490490- | Some dir ->
491491- (Some (resolve_path fs home_path dir), config.runtime_dir.source)
492492- | None ->
493493- ( Option.map
494494- (fun base ->
495495- let base_path = resolve_path fs home_path base in
496496- Eio.Path.(base_path / app_name))
497497- (Xdg.runtime_dir xdg_ctx),
498498- config.runtime_dir.source )
499499- in
500500- (* System directories - reuse shared helper *)
501501- let config_dirs =
502502- resolve_system_dirs fs home_path app_name "CONFIG_DIRS"
503503- "XDG_CONFIG_DIRS" [ "/etc/xdg" ]
504504- in
505505- let data_dirs =
506506- resolve_system_dirs fs home_path app_name "DATA_DIRS"
507507- "XDG_DATA_DIRS"
508508- [ "/usr/local/share"; "/usr/share" ]
509509- in
510510- ensure_dir config_dir;
511511- ensure_dir data_dir;
512512- ensure_dir cache_dir;
513513- ensure_dir state_dir;
514514- Option.iter (ensure_runtime_dir fs) runtime_dir;
515515- let xdg =
516516- {
517517- app_name;
518518- config_dir;
519519- config_dir_source;
520520- data_dir;
521521- data_dir_source;
522522- cache_dir;
523523- cache_dir_source;
524524- state_dir;
525525- state_dir_source;
526526- runtime_dir;
527527- runtime_dir_source;
528528- config_dirs;
529529- data_dirs;
530530- }
531531- in
532532- (* Handle --show-paths option *)
533533- if show_paths_flag then (
534534- let print_path name path =
535535- match path with
536536- | None -> Printf.printf "%s: <none>\n" name
537537- | Some p -> Printf.printf "%s: %s\n" name (Eio.Path.native_exn p)
538538- in
539539- let print_paths name paths =
540540- match paths with
541541- | [] -> Printf.printf "%s: []\n" name
542542- | paths ->
543543- let paths_str =
544544- String.concat ":" (List.map Eio.Path.native_exn paths)
545545- in
546546- Printf.printf "%s: %s\n" name paths_str
547547- in
548548- print_path "config_dir" (Some config_dir);
549549- print_path "data_dir" (Some data_dir);
550550- print_path "cache_dir" (Some cache_dir);
551551- print_path "state_dir" (Some state_dir);
552552- print_path "runtime_dir" runtime_dir;
553553- print_paths "config_dirs" config_dirs;
554554- print_paths "data_dirs" data_dirs;
555555- Stdlib.exit 0);
556556- (xdg, config))
557557- $ show_paths $ config_dir $ data_dir $ cache_dir $ state_dir $ runtime_dir)
558558-559559- let cache_term app_name =
560560- let open Cmdliner in
561561- let app_upper = String.uppercase_ascii app_name in
562562- let app_env = app_upper ^ "_CACHE_DIR" in
563563- let xdg_var = "XDG_CACHE_HOME" in
564564- let home = Sys.getenv "HOME" in
565565- let default_path = home ^ "/.cache/" ^ app_name in
566566-567567- let doc =
568568- Printf.sprintf
569569- "Override cache directory. Can also be set with %s or %s. Default: %s"
570570- app_env xdg_var default_path
571571- in
572572-573573- let arg =
574574- Arg.(
575575- value & opt string default_path
576576- & info [ "cache-dir"; "c" ] ~docv:"DIR" ~doc)
577577- in
578578-579579- Term.(
580580- const (fun cmdline_val ->
581581- (* Check command line first *)
582582- if cmdline_val <> default_path then cmdline_val
583583- else
584584- (* Then check app-specific env var *)
585585- match Sys.getenv_opt app_env with
586586- | Some v when v <> "" -> v
587587- | _ -> (
588588- (* Then check XDG env var *)
589589- match Sys.getenv_opt xdg_var with
590590- | Some v when v <> "" -> v ^ "/" ^ app_name
591591- | _ -> default_path))
592592- $ arg)
593593-594594- let env_docs app_name =
595595- let app_upper = String.uppercase_ascii app_name in
596596- Printf.sprintf
597597- {|
598598-Configuration Precedence (follows standard Unix conventions):
599599- 1. Command-line flags (e.g., --config-dir) - highest priority
600600- 2. Application-specific environment variable (e.g., %s_CONFIG_DIR)
601601- 3. XDG standard environment variable (e.g., XDG_CONFIG_HOME)
602602- 4. Default path (e.g., ~/.config/%s) - lowest priority
603603-604604- This allows per-application overrides without affecting other XDG-compliant programs.
605605- For example, setting %s_CONFIG_DIR only changes the config directory for %s,
606606- while XDG_CONFIG_HOME affects all XDG-compliant applications.
607607-608608-Application-specific variables:
609609- %s_CONFIG_DIR Override config directory for %s only
610610- %s_DATA_DIR Override data directory for %s only
611611- %s_CACHE_DIR Override cache directory for %s only
612612- %s_STATE_DIR Override state directory for %s only
613613- %s_RUNTIME_DIR Override runtime directory for %s only
614614-615615-XDG standard variables (shared by all XDG applications):
616616- XDG_CONFIG_HOME User configuration directory (default: ~/.config/%s)
617617- XDG_DATA_HOME User data directory (default: ~/.local/share/%s)
618618- XDG_CACHE_HOME User cache directory (default: ~/.cache/%s)
619619- XDG_STATE_HOME User state directory (default: ~/.local/state/%s)
620620- XDG_RUNTIME_DIR User runtime directory (no default)
621621- XDG_CONFIG_DIRS System configuration directories (default: /etc/xdg/%s)
622622- XDG_DATA_DIRS System data directories (default: /usr/local/share/%s:/usr/share/%s)
623623-|}
624624- app_upper app_name app_upper app_name app_upper app_name app_upper
625625- app_name app_upper app_name app_upper app_name app_upper app_name app_name
626626- app_name app_name app_name app_name app_name app_name
627627-628628- let pp ppf config =
629629- let pp_source ppf = function
630630- | Default -> Fmt.(styled `Faint string) ppf "default"
631631- | Env var ->
632632- Fmt.pf ppf "%a" Fmt.(styled `Yellow string) ("env(" ^ var ^ ")")
633633- | Cmdline -> Fmt.(styled `Blue string) ppf "cmdline"
634634- in
635635- let pp_with_source name ppf ws =
636636- match ws.value with
637637- | None when ws.source = Default -> ()
638638- | None ->
639639- Fmt.pf ppf "@,%a %a %a"
640640- Fmt.(styled `Cyan string)
641641- (name ^ ":")
642642- Fmt.(styled `Red string)
643643- "<unset>"
644644- Fmt.(styled `Faint (brackets pp_source))
645645- ws.source
646646- | Some value ->
647647- Fmt.pf ppf "@,%a %a %a"
648648- Fmt.(styled `Cyan string)
649649- (name ^ ":")
650650- Fmt.(styled `Green string)
651651- value
652652- Fmt.(styled `Faint (brackets pp_source))
653653- ws.source
654654- in
655655- Fmt.pf ppf "@[<v>%a%a%a%a%a%a@]"
656656- Fmt.(styled `Bold string)
657657- "XDG config:"
658658- (pp_with_source "config_dir")
659659- config.config_dir
660660- (pp_with_source "data_dir")
661661- config.data_dir
662662- (pp_with_source "cache_dir")
663663- config.cache_dir
664664- (pp_with_source "state_dir")
665665- config.state_dir
666666- (pp_with_source "runtime_dir")
667667- config.runtime_dir
668668-end
-441
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