My agentic slop goes here. Not intended for anyone else!
1type source = 2 | Default 3 | Env of string 4 | Cmdline 5 6type t = 7 { app_name : string 8 ; config_dir : Eio.Fs.dir_ty Eio.Path.t 9 ; config_dir_source : source 10 ; data_dir : Eio.Fs.dir_ty Eio.Path.t 11 ; data_dir_source : source 12 ; cache_dir : Eio.Fs.dir_ty Eio.Path.t 13 ; cache_dir_source : source 14 ; state_dir : Eio.Fs.dir_ty Eio.Path.t 15 ; state_dir_source : source 16 ; runtime_dir : Eio.Fs.dir_ty Eio.Path.t option 17 ; runtime_dir_source : source 18 ; config_dirs : Eio.Fs.dir_ty Eio.Path.t list 19 ; data_dirs : Eio.Fs.dir_ty Eio.Path.t list 20 } 21 22type dir = [ 23 | `Config 24 | `Cache 25 | `Data 26 | `State 27 | `Runtime 28] 29 30let ensure_dir ?(perm = 0o755) path = Eio.Path.mkdirs ~exists_ok:true ~perm path 31 32let validate_runtime_base_dir base_path = 33 (* Validate the base XDG_RUNTIME_DIR has correct permissions per spec *) 34 try 35 let path_str = Eio.Path.native_exn base_path in 36 let stat = Eio.Path.stat ~follow:true base_path in 37 let current_perm = stat.perm land 0o777 in 38 if current_perm <> 0o700 39 then 40 failwith 41 (Printf.sprintf 42 "XDG_RUNTIME_DIR base directory %s has incorrect permissions: %o (must be \ 43 0700)" 44 path_str 45 current_perm); 46 (* Check ownership - directory should be owned by current user *) 47 let uid = Unix.getuid () in 48 if stat.uid <> Int64.of_int uid 49 then 50 failwith 51 (Printf.sprintf 52 "XDG_RUNTIME_DIR base directory %s not owned by current user (uid %d, owner \ 53 %Ld)" 54 path_str 55 uid 56 stat.uid) 57 (* TODO: Check that directory is on local filesystem (not networked). 58 This would require filesystem type detection which is OS-specific. *) 59 with 60 | exn -> 61 failwith 62 (Printf.sprintf "Cannot validate XDG_RUNTIME_DIR: %s" (Printexc.to_string exn)) 63;; 64 65let ensure_runtime_dir _fs app_runtime_path = 66 (* Base directory validation is done in resolve_runtime_dir, 67 so we just create the app subdirectory *) 68 ensure_dir app_runtime_path 69;; 70 71let get_home_dir fs = 72 let home_str = 73 match Sys.getenv_opt "HOME" with 74 | Some home -> home 75 | None -> 76 (match Sys.os_type with 77 | "Win32" | "Cygwin" -> 78 (match Sys.getenv_opt "USERPROFILE" with 79 | Some profile -> profile 80 | None -> failwith "Cannot determine home directory") 81 | _ -> 82 (try Unix.((getpwuid (getuid ())).pw_dir) with 83 | _ -> failwith "Cannot determine home directory")) 84 in 85 Eio.Path.(fs / home_str) 86;; 87 88let make_env_var_name app_name suffix = String.uppercase_ascii app_name ^ "_" ^ suffix 89 90exception Invalid_xdg_path of string 91 92let validate_absolute_path context path = 93 if Filename.is_relative path 94 then 95 raise 96 (Invalid_xdg_path 97 (Printf.sprintf "%s must be an absolute path, got: %s" context path)) 98;; 99 100let resolve_path fs home_path base_path = 101 if Filename.is_relative base_path 102 then Eio.Path.(home_path / base_path) 103 else Eio.Path.(fs / base_path) 104;; 105 106(* Helper to resolve system directories (config_dirs or data_dirs) *) 107let resolve_system_dirs fs home_path app_name override_suffix xdg_var default_paths = 108 let override_var = make_env_var_name app_name override_suffix in 109 match Sys.getenv_opt override_var with 110 | Some dirs when dirs <> "" -> 111 String.split_on_char ':' dirs 112 |> List.filter (fun s -> s <> "") 113 |> List.filter_map (fun path -> 114 try 115 validate_absolute_path override_var path; 116 Some Eio.Path.(resolve_path fs home_path path / app_name) 117 with 118 | Invalid_xdg_path _ -> None) 119 | Some _ | None -> 120 (match Sys.getenv_opt xdg_var with 121 | Some dirs when dirs <> "" -> 122 String.split_on_char ':' dirs 123 |> List.filter (fun s -> s <> "") 124 |> List.filter_map (fun path -> 125 try 126 validate_absolute_path xdg_var path; 127 Some Eio.Path.(resolve_path fs home_path path / app_name) 128 with 129 | Invalid_xdg_path _ -> None) 130 | Some _ | None -> 131 List.map 132 (fun path -> Eio.Path.(resolve_path fs home_path path / app_name)) 133 default_paths) 134;; 135 136(* Helper to resolve a user directory with override precedence *) 137let resolve_user_dir fs home_path app_name xdg_ctx xdg_getter override_suffix = 138 let override_var = make_env_var_name app_name override_suffix in 139 match Sys.getenv_opt override_var with 140 | Some dir when dir <> "" -> 141 validate_absolute_path override_var dir; 142 Eio.Path.(fs / dir / app_name), Env override_var 143 | Some _ | None -> 144 let xdg_base = xdg_getter xdg_ctx in 145 let base_path = resolve_path fs home_path xdg_base in 146 Eio.Path.(base_path / app_name), Default 147;; 148 149(* Helper to resolve runtime directory (special case since it can be None) *) 150let resolve_runtime_dir fs home_path app_name xdg_ctx = 151 let override_var = make_env_var_name app_name "RUNTIME_DIR" in 152 match Sys.getenv_opt override_var with 153 | Some dir when dir <> "" -> 154 validate_absolute_path override_var dir; 155 (* Validate the base runtime directory has correct permissions *) 156 let base_runtime_dir = resolve_path fs home_path dir in 157 validate_runtime_base_dir base_runtime_dir; 158 Some Eio.Path.(base_runtime_dir / app_name), Env override_var 159 | Some _ | None -> 160 ( (match Xdg.runtime_dir xdg_ctx with 161 | Some base -> 162 (* Validate the base runtime directory has correct permissions *) 163 let base_runtime_dir = resolve_path fs home_path base in 164 validate_runtime_base_dir base_runtime_dir; 165 Some Eio.Path.(base_runtime_dir / app_name) 166 | None -> None) 167 , Default ) 168;; 169 170let validate_standard_xdg_vars () = 171 (* Validate standard XDG environment variables for absolute paths *) 172 let xdg_vars = 173 [ "XDG_CONFIG_HOME" 174 ; "XDG_DATA_HOME" 175 ; "XDG_CACHE_HOME" 176 ; "XDG_STATE_HOME" 177 ; "XDG_RUNTIME_DIR" 178 ; "XDG_CONFIG_DIRS" 179 ; "XDG_DATA_DIRS" 180 ] 181 in 182 List.iter 183 (fun var -> 184 match Sys.getenv_opt var with 185 | Some value when value <> "" -> 186 if String.contains value ':' 187 then 188 (* Colon-separated list - validate each part *) 189 String.split_on_char ':' value 190 |> List.filter (fun s -> s <> "") 191 |> List.iter (fun path -> validate_absolute_path var path) 192 else 193 (* Single path *) 194 validate_absolute_path var value 195 | _ -> ()) 196 xdg_vars 197;; 198 199let create fs app_name = 200 let fs = fs in 201 let home_path = get_home_dir fs in 202 (* First validate all standard XDG environment variables *) 203 validate_standard_xdg_vars (); 204 let xdg_ctx = Xdg.create ~env:Sys.getenv_opt () in 205 (* User directories *) 206 let config_dir, config_dir_source = 207 resolve_user_dir fs home_path app_name xdg_ctx Xdg.config_dir "CONFIG_DIR" 208 in 209 let data_dir, data_dir_source = 210 resolve_user_dir fs home_path app_name xdg_ctx Xdg.data_dir "DATA_DIR" 211 in 212 let cache_dir, cache_dir_source = 213 resolve_user_dir fs home_path app_name xdg_ctx Xdg.cache_dir "CACHE_DIR" 214 in 215 let state_dir, state_dir_source = 216 resolve_user_dir fs home_path app_name xdg_ctx Xdg.state_dir "STATE_DIR" 217 in 218 (* Runtime directory *) 219 let runtime_dir, runtime_dir_source = 220 resolve_runtime_dir fs home_path app_name xdg_ctx 221 in 222 (* System directories *) 223 let config_dirs = 224 resolve_system_dirs 225 fs 226 home_path 227 app_name 228 "CONFIG_DIRS" 229 "XDG_CONFIG_DIRS" 230 [ "/etc/xdg" ] 231 in 232 let data_dirs = 233 resolve_system_dirs 234 fs 235 home_path 236 app_name 237 "DATA_DIRS" 238 "XDG_DATA_DIRS" 239 [ "/usr/local/share"; "/usr/share" ] 240 in 241 ensure_dir config_dir; 242 ensure_dir data_dir; 243 ensure_dir cache_dir; 244 ensure_dir state_dir; 245 Option.iter (ensure_runtime_dir fs) runtime_dir; 246 { app_name 247 ; config_dir 248 ; config_dir_source 249 ; data_dir 250 ; data_dir_source 251 ; cache_dir 252 ; cache_dir_source 253 ; state_dir 254 ; state_dir_source 255 ; runtime_dir 256 ; runtime_dir_source 257 ; config_dirs 258 ; data_dirs 259 } 260;; 261 262let app_name t = t.app_name 263let config_dir t = t.config_dir 264let data_dir t = t.data_dir 265let cache_dir t = t.cache_dir 266let state_dir t = t.state_dir 267let runtime_dir t = t.runtime_dir 268let config_dirs t = t.config_dirs 269let data_dirs t = t.data_dirs 270 271(* File search following XDG specification *) 272let find_file_in_dirs dirs filename = 273 let rec search_dirs = function 274 | [] -> None 275 | dir :: remaining_dirs -> 276 let file_path = Eio.Path.(dir / filename) in 277 (try 278 (* Try to check if file exists and is readable *) 279 let _ = Eio.Path.stat ~follow:true file_path in 280 Some file_path 281 with 282 | _ -> 283 (* File is inaccessible (non-existent, permissions, etc.) 284 Skip and continue with next directory per XDG spec *) 285 search_dirs remaining_dirs) 286 in 287 search_dirs dirs 288;; 289 290let find_config_file t filename = 291 (* Search user config dir first, then system config dirs *) 292 find_file_in_dirs (t.config_dir :: t.config_dirs) filename 293;; 294 295let find_data_file t filename = 296 (* Search user data dir first, then system data dirs *) 297 find_file_in_dirs (t.data_dir :: t.data_dirs) filename 298;; 299 300let pp ?(brief = false) ?(sources = false) ppf t = 301 let pp_source ppf = function 302 | Default -> Fmt.(styled `Faint string) ppf "default" 303 | Env var -> Fmt.pf ppf "%a" Fmt.(styled `Yellow string) ("env(" ^ var ^ ")") 304 | Cmdline -> Fmt.(styled `Blue string) ppf "cmdline" 305 in 306 let pp_path_with_source ppf path source = 307 if sources 308 then 309 Fmt.pf 310 ppf 311 "%a %a" 312 Fmt.(styled `Green Eio.Path.pp) 313 path 314 Fmt.(styled `Faint (brackets pp_source)) 315 source 316 else Fmt.(styled `Green Eio.Path.pp) ppf path 317 in 318 let pp_path_opt_with_source ppf path_opt source = 319 match path_opt with 320 | None -> 321 if sources 322 then 323 Fmt.pf 324 ppf 325 "%a %a" 326 Fmt.(styled `Red string) 327 "<none>" 328 Fmt.(styled `Faint (brackets pp_source)) 329 source 330 else Fmt.(styled `Red string) ppf "<none>" 331 | Some path -> pp_path_with_source ppf path source 332 in 333 let pp_paths ppf paths = 334 Fmt.(list ~sep:(any ";@ ") (styled `Green Eio.Path.pp)) ppf paths 335 in 336 if brief 337 then 338 Fmt.pf 339 ppf 340 "%a config=%a data=%a>" 341 Fmt.(styled `Cyan string) 342 ("<xdg:" ^ t.app_name) 343 (fun ppf (path, source) -> pp_path_with_source ppf path source) 344 (t.config_dir, t.config_dir_source) 345 (fun ppf (path, source) -> pp_path_with_source ppf path source) 346 (t.data_dir, t.data_dir_source) 347 else ( 348 Fmt.pf 349 ppf 350 "@[<v>%a@," 351 Fmt.(styled `Bold string) 352 ("XDG directories for '" ^ t.app_name ^ "':"); 353 Fmt.pf ppf "@[<v 2>%a@," Fmt.(styled `Bold string) "User directories:"; 354 Fmt.pf 355 ppf 356 "%a %a@," 357 Fmt.(styled `Cyan string) 358 "config:" 359 (fun ppf (path, source) -> pp_path_with_source ppf path source) 360 (t.config_dir, t.config_dir_source); 361 Fmt.pf 362 ppf 363 "%a %a@," 364 Fmt.(styled `Cyan string) 365 "data:" 366 (fun ppf (path, source) -> pp_path_with_source ppf path source) 367 (t.data_dir, t.data_dir_source); 368 Fmt.pf 369 ppf 370 "%a %a@," 371 Fmt.(styled `Cyan string) 372 "cache:" 373 (fun ppf (path, source) -> pp_path_with_source ppf path source) 374 (t.cache_dir, t.cache_dir_source); 375 Fmt.pf 376 ppf 377 "%a %a@," 378 Fmt.(styled `Cyan string) 379 "state:" 380 (fun ppf (path, source) -> pp_path_with_source ppf path source) 381 (t.state_dir, t.state_dir_source); 382 Fmt.pf 383 ppf 384 "%a %a@]@," 385 Fmt.(styled `Cyan string) 386 "runtime:" 387 (fun ppf (path_opt, source) -> pp_path_opt_with_source ppf path_opt source) 388 (t.runtime_dir, t.runtime_dir_source); 389 Fmt.pf ppf "@[<v 2>%a@," Fmt.(styled `Bold string) "System directories:"; 390 Fmt.pf 391 ppf 392 "%a [@[<hov>%a@]]@," 393 Fmt.(styled `Cyan string) 394 "config_dirs:" 395 pp_paths 396 t.config_dirs; 397 Fmt.pf 398 ppf 399 "%a [@[<hov>%a@]]@]@]" 400 Fmt.(styled `Cyan string) 401 "data_dirs:" 402 pp_paths 403 t.data_dirs) 404;; 405 406module Cmd = struct 407 type xdg_t = t 408 409 type 'a with_source = 410 { value : 'a option 411 ; source : source 412 } 413 414 type t = 415 { config_dir : string with_source 416 ; data_dir : string with_source 417 ; cache_dir : string with_source 418 ; state_dir : string with_source 419 ; runtime_dir : string with_source 420 } 421 422 let term app_name fs 423 ?(dirs=[`Config; `Data; `Cache; `State; `Runtime]) () = 424 let open Cmdliner in 425 let app_upper = String.uppercase_ascii app_name in 426 let show_paths = 427 let doc = "Show only the resolved directory paths without formatting" in 428 Arg.(value & flag & info [ "show-paths" ] ~doc) 429 in 430 let has_dir d = List.mem d dirs in 431 let make_dir_arg ~enabled name env_suffix xdg_var default_path = 432 if not enabled then 433 (* Return a term that always gives the environment-only result *) 434 Term.(const (fun () -> 435 let app_env = app_upper ^ "_" ^ env_suffix in 436 match Sys.getenv_opt app_env with 437 | Some v when v <> "" -> { value = Some v; source = Env app_env } 438 | Some _ | None -> 439 (match Sys.getenv_opt xdg_var with 440 | Some v -> { value = Some v; source = Env xdg_var } 441 | None -> { value = None; source = Default })) 442 $ const ()) 443 else 444 let app_env = app_upper ^ "_" ^ env_suffix in 445 let doc = 446 match default_path with 447 | Some path -> 448 Printf.sprintf 449 "Override %s directory. Can also be set with %s or %s. Default: %s" 450 name 451 app_env 452 xdg_var 453 path 454 | None -> 455 Printf.sprintf 456 "Override %s directory. Can also be set with %s or %s. No default value." 457 name 458 app_env 459 xdg_var 460 in 461 let arg = 462 Arg.(value & opt (some string) None & info [ name ^ "-dir" ] ~docv:"DIR" ~doc) 463 in 464 Term.( 465 const (fun cmdline_val -> 466 match cmdline_val with 467 | Some v -> { value = Some v; source = Cmdline } 468 | None -> 469 (match Sys.getenv_opt app_env with 470 | Some v when v <> "" -> { value = Some v; source = Env app_env } 471 | Some _ | None -> 472 (match Sys.getenv_opt xdg_var with 473 | Some v -> { value = Some v; source = Env xdg_var } 474 | None -> { value = None; source = Default }))) 475 $ arg) 476 in 477 let home_prefix = "\\$HOME" in 478 let config_dir = 479 make_dir_arg 480 ~enabled:(has_dir `Config) 481 "config" 482 "CONFIG_DIR" 483 "XDG_CONFIG_HOME" 484 (Some (home_prefix ^ "/.config/" ^ app_name)) 485 in 486 let data_dir = 487 make_dir_arg 488 ~enabled:(has_dir `Data) 489 "data" 490 "DATA_DIR" 491 "XDG_DATA_HOME" 492 (Some (home_prefix ^ "/.local/share/" ^ app_name)) 493 in 494 let cache_dir = 495 make_dir_arg 496 ~enabled:(has_dir `Cache) 497 "cache" 498 "CACHE_DIR" 499 "XDG_CACHE_HOME" 500 (Some (home_prefix ^ "/.cache/" ^ app_name)) 501 in 502 let state_dir = 503 make_dir_arg 504 ~enabled:(has_dir `State) 505 "state" 506 "STATE_DIR" 507 "XDG_STATE_HOME" 508 (Some (home_prefix ^ "/.local/state/" ^ app_name)) 509 in 510 let runtime_dir = make_dir_arg ~enabled:(has_dir `Runtime) "runtime" "RUNTIME_DIR" "XDG_RUNTIME_DIR" None in 511 Term.( 512 const 513 (fun 514 show_paths_flag 515 config_dir_ws 516 data_dir_ws 517 cache_dir_ws 518 state_dir_ws 519 runtime_dir_ws 520 -> 521 let config = 522 { config_dir = config_dir_ws 523 ; data_dir = data_dir_ws 524 ; cache_dir = cache_dir_ws 525 ; state_dir = state_dir_ws 526 ; runtime_dir = runtime_dir_ws 527 } 528 in 529 let home_path = get_home_dir fs in 530 (* First validate all standard XDG environment variables *) 531 validate_standard_xdg_vars (); 532 let xdg_ctx = Xdg.create ~env:Sys.getenv_opt () in 533 (* Helper to resolve directory from config with source tracking *) 534 let resolve_from_config config_ws xdg_getter = 535 match config_ws.value with 536 | Some dir -> resolve_path fs home_path dir, config_ws.source 537 | None -> 538 let xdg_base = xdg_getter xdg_ctx in 539 let base_path = resolve_path fs home_path xdg_base in 540 Eio.Path.(base_path / app_name), config_ws.source 541 in 542 (* User directories *) 543 let config_dir, config_dir_source = 544 resolve_from_config config.config_dir Xdg.config_dir 545 in 546 let data_dir, data_dir_source = 547 resolve_from_config config.data_dir Xdg.data_dir 548 in 549 let cache_dir, cache_dir_source = 550 resolve_from_config config.cache_dir Xdg.cache_dir 551 in 552 let state_dir, state_dir_source = 553 resolve_from_config config.state_dir Xdg.state_dir 554 in 555 (* Runtime directory *) 556 let runtime_dir, runtime_dir_source = 557 match config.runtime_dir.value with 558 | Some dir -> Some (resolve_path fs home_path dir), config.runtime_dir.source 559 | None -> 560 ( Option.map 561 (fun base -> 562 let base_path = resolve_path fs home_path base in 563 Eio.Path.(base_path / app_name)) 564 (Xdg.runtime_dir xdg_ctx) 565 , config.runtime_dir.source ) 566 in 567 (* System directories - reuse shared helper *) 568 let config_dirs = 569 resolve_system_dirs 570 fs 571 home_path 572 app_name 573 "CONFIG_DIRS" 574 "XDG_CONFIG_DIRS" 575 [ "/etc/xdg" ] 576 in 577 let data_dirs = 578 resolve_system_dirs 579 fs 580 home_path 581 app_name 582 "DATA_DIRS" 583 "XDG_DATA_DIRS" 584 [ "/usr/local/share"; "/usr/share" ] 585 in 586 ensure_dir config_dir; 587 ensure_dir data_dir; 588 ensure_dir cache_dir; 589 ensure_dir state_dir; 590 Option.iter (ensure_runtime_dir fs) runtime_dir; 591 let xdg = 592 { app_name 593 ; config_dir 594 ; config_dir_source 595 ; data_dir 596 ; data_dir_source 597 ; cache_dir 598 ; cache_dir_source 599 ; state_dir 600 ; state_dir_source 601 ; runtime_dir 602 ; runtime_dir_source 603 ; config_dirs 604 ; data_dirs 605 } 606 in 607 (* Handle --show-paths option *) 608 if show_paths_flag 609 then ( 610 let print_path name path = 611 match path with 612 | None -> Printf.printf "%s: <none>\n" name 613 | Some p -> Printf.printf "%s: %s\n" name (Eio.Path.native_exn p) 614 in 615 let print_paths name paths = 616 match paths with 617 | [] -> Printf.printf "%s: []\n" name 618 | paths -> 619 let paths_str = String.concat ":" (List.map Eio.Path.native_exn paths) in 620 Printf.printf "%s: %s\n" name paths_str 621 in 622 print_path "config_dir" (Some config_dir); 623 print_path "data_dir" (Some data_dir); 624 print_path "cache_dir" (Some cache_dir); 625 print_path "state_dir" (Some state_dir); 626 print_path "runtime_dir" runtime_dir; 627 print_paths "config_dirs" config_dirs; 628 print_paths "data_dirs" data_dirs; 629 Stdlib.exit 0); 630 xdg, config) 631 $ show_paths 632 $ config_dir 633 $ data_dir 634 $ cache_dir 635 $ state_dir 636 $ runtime_dir) 637 ;; 638 639 let cache_term app_name = 640 let open Cmdliner in 641 let app_upper = String.uppercase_ascii app_name in 642 let app_env = app_upper ^ "_CACHE_DIR" in 643 let xdg_var = "XDG_CACHE_HOME" in 644 let home = Sys.getenv "HOME" in 645 let default_path = home ^ "/.cache/" ^ app_name in 646 647 let doc = 648 Printf.sprintf 649 "Override cache directory. Can also be set with %s or %s. Default: %s" 650 app_env xdg_var default_path 651 in 652 653 let arg = Arg.(value & opt string default_path & info ["cache-dir"; "c"] ~docv:"DIR" ~doc) in 654 655 Term.(const (fun cmdline_val -> 656 (* Check command line first *) 657 if cmdline_val <> default_path then 658 cmdline_val 659 else 660 (* Then check app-specific env var *) 661 match Sys.getenv_opt app_env with 662 | Some v when v <> "" -> v 663 | _ -> 664 (* Then check XDG env var *) 665 match Sys.getenv_opt xdg_var with 666 | Some v when v <> "" -> v ^ "/" ^ app_name 667 | _ -> default_path 668 ) $ arg) 669 ;; 670 671 let env_docs app_name = 672 let app_upper = String.uppercase_ascii app_name in 673 Printf.sprintf 674 {| 675Configuration Precedence (follows standard Unix conventions): 676 1. Command-line flags (e.g., --config-dir) - highest priority 677 2. Application-specific environment variable (e.g., %s_CONFIG_DIR) 678 3. XDG standard environment variable (e.g., XDG_CONFIG_HOME) 679 4. Default path (e.g., ~/.config/%s) - lowest priority 680 681 This allows per-application overrides without affecting other XDG-compliant programs. 682 For example, setting %s_CONFIG_DIR only changes the config directory for %s, 683 while XDG_CONFIG_HOME affects all XDG-compliant applications. 684 685Application-specific variables: 686 %s_CONFIG_DIR Override config directory for %s only 687 %s_DATA_DIR Override data directory for %s only 688 %s_CACHE_DIR Override cache directory for %s only 689 %s_STATE_DIR Override state directory for %s only 690 %s_RUNTIME_DIR Override runtime directory for %s only 691 692XDG standard variables (shared by all XDG applications): 693 XDG_CONFIG_HOME User configuration directory (default: ~/.config/%s) 694 XDG_DATA_HOME User data directory (default: ~/.local/share/%s) 695 XDG_CACHE_HOME User cache directory (default: ~/.cache/%s) 696 XDG_STATE_HOME User state directory (default: ~/.local/state/%s) 697 XDG_RUNTIME_DIR User runtime directory (no default) 698 XDG_CONFIG_DIRS System configuration directories (default: /etc/xdg/%s) 699 XDG_DATA_DIRS System data directories (default: /usr/local/share/%s:/usr/share/%s) 700|} 701 app_upper 702 app_name 703 app_upper 704 app_name 705 app_upper 706 app_name 707 app_upper 708 app_name 709 app_upper 710 app_name 711 app_upper 712 app_name 713 app_upper 714 app_name 715 app_name 716 app_name 717 app_name 718 app_name 719 app_name 720 app_name 721 app_name 722 ;; 723 724 let pp ppf config = 725 let pp_source ppf = function 726 | Default -> Fmt.(styled `Faint string) ppf "default" 727 | Env var -> Fmt.pf ppf "%a" Fmt.(styled `Yellow string) ("env(" ^ var ^ ")") 728 | Cmdline -> Fmt.(styled `Blue string) ppf "cmdline" 729 in 730 let pp_with_source name ppf ws = 731 match ws.value with 732 | None when ws.source = Default -> () 733 | None -> 734 Fmt.pf 735 ppf 736 "@,%a %a %a" 737 Fmt.(styled `Cyan string) 738 (name ^ ":") 739 Fmt.(styled `Red string) 740 "<unset>" 741 Fmt.(styled `Faint (brackets pp_source)) 742 ws.source 743 | Some value -> 744 Fmt.pf 745 ppf 746 "@,%a %a %a" 747 Fmt.(styled `Cyan string) 748 (name ^ ":") 749 Fmt.(styled `Green string) 750 value 751 Fmt.(styled `Faint (brackets pp_source)) 752 ws.source 753 in 754 Fmt.pf 755 ppf 756 "@[<v>%a%a%a%a%a%a@]" 757 Fmt.(styled `Bold string) 758 "XDG config:" 759 (pp_with_source "config_dir") 760 config.config_dir 761 (pp_with_source "data_dir") 762 config.data_dir 763 (pp_with_source "cache_dir") 764 config.cache_dir 765 (pp_with_source "state_dir") 766 config.state_dir 767 (pp_with_source "runtime_dir") 768 config.runtime_dir 769 ;; 770end