My own corner of monopam
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

ocaml-xdg: fork dune's internal xdg, merge with previous nox-xdge

New package [nox-xdg], replacing the prior split between dune's
internal [xdg] library (META description: "[Internal] XDG base
directories specification implementation") and the standalone
[nox-xdge] Eio wrapper.

The fork was driven by two needs:

- dune's [xdg] is marked [Internal] in its META; dune doesn't promise
to keep it stable across releases. Building credential-handling
CLIs ([gdocs], [gsheets], [gslides], [gauth.Local_store]) on top of
a build-tool internal is a stability risk.
- The split layout had [nox-xdge] (Eio wrapper) depending on dune's
[xdg] (pure spec) by their shared library name [xdg], blocking any
attempt to give the wrapper a cleaner name.

Merging into one package solves both. The package provides two
libraries from one [ocaml-xdg/] source tree:

- [nox-xdg] (module [Xdg]) -- pure spec, no Eio dep.
lib/xdg.ml + xdg_stubs.c
- [nox-xdg.eio] (module [Xdg_eio]) -- Eio backend on top of the
resolver. lib/xdg_eio.ml

The pure spec is a Unix+Windows-faithful fork of dune's
[otherlibs/xdg/xdg.ml] and [xdg_stubs.c]. Original code by Jane
Street (MIT); LICENSE.md keeps both copyright lines. The C symbol
[dune_xdg__get_known_folder_path] is renamed to
[nox_xdg__get_known_folder_path] so a downstream binary that links
both this package and dune's internal xdg doesn't hit duplicate
symbols.

One small divergence from upstream: the [make] helper in xdg.ml
defers calling the C stub until the env-var override misses, instead
of computing it eagerly. Identical behaviour on Windows; lets the
test suite exercise the [~win32:true] code path from a non-Windows
host as long as every XDG_*_HOME is set. Documented inline.

Tests:

- [test/test_xdg.ml] (22 cases) -- spec-anchored: every default,
every env-var override, relative/empty/missing values per §4,
HOME interpretation, snapshot semantics at create-time, and the
full Windows extension.
- [test/eio/test_xdg_eio.ml] (5 cases) -- carried over from the old
xdge package, now driving the [Xdg_eio] module.
- [test/eio/cram/] -- the old xdge cram tests, against the renamed
module.

The previous [xdge/] subtree is removed; its [nox-xdge.opam] is
gone. Consumer migration (gauth, gdocs, gsheets, gslides, requests,
cookie, agent, atp, slack, linkedin, oci, uniboot, monopam) lands
in the next commit.

Also fixes an unrelated build break in [monopam-info/lib/index]: the
[Dune.Package.Library.codec] API was removed from [nox-dune] in
favour of the higher-level [Dune.Package.libraries] decoder; switch
to the new entry point and drop the now-unused [nox-sexp] dep.

+833 -353
+1 -1
dune-project
··· 74 74 uunf 75 75 uutf 76 76 wire 77 - xdg 77 + nox-xdg 78 78 zarith 79 79 ))
+1 -1
llms.txt
··· 218 218 - [osrelease](osrelease/README.md): Detect operating system, distro and version information 219 219 - [prune](prune/README.md): Find unused exports in OCaml interface files 220 220 - [uniboot](uniboot/README.md): Minimal bootable disk image builder 221 - - [nox-xdge](xdge/README.md): XDG Base Directory Specification support for Eio 221 + - [nox-xdg](ocaml-xdg/README.md): XDG Base Directory specification: pure resolution + Eio backend (fork of dune's xdg + previous nox-xdge) 222 222 223 223 ## Optional 224 224
+1 -1
monopam-info/lib/index/dune
··· 1 1 (library 2 2 (name monopam_info_index) 3 3 (public_name monopam-info.index) 4 - (libraries eio fpath nox-sexp nox-dune)) 4 + (libraries eio fpath nox-dune))
+4 -12
monopam-info/lib/index/monopam_info_index.ml
··· 46 46 else None) 47 47 entries 48 48 49 - (* Stream stanzas through Library.codec while harvesting library names — 50 - needed for the lib_to_pkg reverse index. Returning the names lets us 51 - register both indexes in one pass. *) 49 + (* Harvest library names for the lib_to_pkg reverse index. Uses the 50 + high-level [Dune.Package.libraries] decoder which already skips the 51 + [(lang ...)] header and any non-library stanzas. *) 52 52 let library_names_in_dune_package content = 53 - match Sexp.Value.parse_string_many content with 54 - | Error _ -> [] 55 - | Ok stanzas -> 56 - List.filter_map 57 - (fun s -> 58 - match Sexp.Codec.decode_value Dune.Package.Library.codec s with 59 - | Ok (lib : Dune.Package.Library.t) -> Some lib.name 60 - | Error _ -> None) 61 - stanzas 53 + Dune.Package.libraries content |> List.map Dune.Package.name 62 54 63 55 let pkg_index t pkg = 64 56 match Hashtbl.find_opt t.pkg_libs pkg with
+22
ocaml-xdg/LICENSE.md
··· 1 + The MIT License 2 + 3 + Copyright (c) 2016 Jane Street Group, LLC <opensource@janestreet.com> 4 + Copyright (c) 2026 Thomas Gazagnaire <thomas@gazagnaire.org> 5 + 6 + Permission is hereby granted, free of charge, to any person obtaining a copy 7 + of this software and associated documentation files (the "Software"), to deal 8 + in the Software without restriction, including without limitation the rights 9 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 + copies of the Software, and to permit persons to whom the Software is 11 + furnished to do so, subject to the following conditions: 12 + 13 + The above copyright notice and this permission notice shall be included in all 14 + copies or substantial portions of the Software. 15 + 16 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 + SOFTWARE.
+92
ocaml-xdg/README.md
··· 1 + # nox-xdg 2 + 3 + XDG Base Directory specification: pure path resolution. 4 + 5 + `nox-xdg` is a fork of [dune's internal `xdg` library][upstream] 6 + (META description: *"[Internal] XDG base directories specification 7 + implementation"*). Forked rather than depended on so this package 8 + has no upstream dep on a build-tool internal — dune doesn't promise 9 + to keep its `xdg` library stable across releases. 10 + 11 + The OCaml symbol exported by the C stub is renamed from 12 + `dune_xdg__get_known_folder_path` to `nox_xdg__get_known_folder_path` 13 + to avoid duplicate-symbol errors at link time when a downstream 14 + binary pulls in both this package and dune's internal xdg library. 15 + 16 + [upstream]: https://github.com/ocaml/dune/tree/main/otherlibs/xdg 17 + 18 + ## What it does 19 + 20 + Computes the standard XDG base directory paths from the 21 + [XDG Base Directory Specification][spec] on Unix, and the 22 + corresponding [Windows known folders][knownfolders] on Windows. 23 + Pure: no filesystem effects, no Eio dependency. 24 + 25 + [spec]: https://specifications.freedesktop.org/basedir-spec/latest/ 26 + [knownfolders]: https://learn.microsoft.com/en-us/windows/win32/shell/known-folders 27 + 28 + For an Eio-aware wrapper that creates directories, validates 29 + permissions, and threads paths through Eio capabilities, see 30 + [`nox-xdge`][xdge]. 31 + 32 + [xdge]: https://tangled.org/gazagnaire.org/ocaml-xdge 33 + 34 + ## Installation 35 + 36 + <!-- $MDX skip --> 37 + ```sh 38 + $ opam install nox-xdg 39 + ``` 40 + 41 + ## Usage 42 + 43 + ```ocaml 44 + let dirs = Xdg.create ~env:Sys.getenv_opt () 45 + 46 + let config_path = 47 + Filename.concat (Xdg.config_dir dirs) "myapp/settings.json" 48 + 49 + let cache_path = 50 + Filename.concat (Xdg.cache_dir dirs) "myapp/thumbnails" 51 + ``` 52 + 53 + The defaults follow the spec: 54 + 55 + | Function | Env override | Unix default | 56 + |---|---|---| 57 + | `Xdg.config_dir` | `$XDG_CONFIG_HOME` | `$HOME/.config` | 58 + | `Xdg.data_dir` | `$XDG_DATA_HOME` | `$HOME/.local/share` | 59 + | `Xdg.cache_dir` | `$XDG_CACHE_HOME` | `$HOME/.cache` | 60 + | `Xdg.state_dir` | `$XDG_STATE_HOME` | `$HOME/.local/state` | 61 + | `Xdg.runtime_dir` | `$XDG_RUNTIME_DIR` | *(none — returns `None`)* | 62 + 63 + Per spec §4, env var values must be absolute; relative or empty 64 + values are ignored and the default is used instead. 65 + 66 + ### Testing 67 + 68 + Pass a closure for `~env` to inject a deterministic environment: 69 + 70 + ```ocaml 71 + let test_env = 72 + let bindings = 73 + [ "HOME", "/home/test"; "XDG_CONFIG_HOME", "/etc/xdg" ] 74 + in 75 + fun name -> List.assoc_opt name bindings 76 + 77 + let dirs = Xdg.create ~env:test_env () 78 + let _ = assert (Xdg.config_dir dirs = "/etc/xdg") 79 + ``` 80 + 81 + ### Windows 82 + 83 + On Windows, defaults come from `SHGetKnownFolderPath` 84 + (`FOLDERID_LocalAppData` for config/data/state, `FOLDERID_InternetCache` 85 + for cache) instead of the Unix `$HOME/.config` paths. Override with 86 + `~win32:bool` if you need to force a particular platform behaviour 87 + in tests. 88 + 89 + ## Licence 90 + 91 + MIT. Original code by Jane Street; this fork by Thomas Gazagnaire. 92 + See [`LICENSE.md`](LICENSE.md).
+7
ocaml-xdg/dune
··· 1 + (env 2 + (dev 3 + (flags :standard %{dune-warnings}))) 4 + 5 + (mdx 6 + (files README.md) 7 + (libraries nox-xdg))
+48
ocaml-xdg/dune-project
··· 1 + (lang dune 3.21) 2 + (using mdx 0.4) 3 + 4 + (name nox-xdg) 5 + 6 + (generate_opam_files true) 7 + 8 + (source (tangled gazagnaire.org/ocaml-xdg)) 9 + (license MIT) 10 + (authors 11 + "Jane Street Group, LLC <opensource@janestreet.com>" 12 + "Thomas Gazagnaire <thomas@gazagnaire.org>") 13 + (maintainers "Thomas Gazagnaire <thomas@gazagnaire.org>") 14 + 15 + (package 16 + (name nox-xdg) 17 + (synopsis "XDG Base Directory specification: pure resolution + Eio backend") 18 + (tags (org:blacksun freedesktop xdg windows eio)) 19 + (description 20 + "Fork/merge of two upstream packages: 21 + 22 + - dune's internal [otherlibs/xdg] -- pure XDG Base Directory 23 + specification resolution ($XDG_CONFIG_HOME, $XDG_DATA_HOME, 24 + $XDG_CACHE_HOME, $XDG_STATE_HOME, $XDG_RUNTIME_DIR), with full 25 + parity (Unix + Windows known-folder fallbacks). 26 + - the previous [nox-xdge] package -- an Eio-aware wrapper that 27 + creates directories, validates permissions, and threads paths 28 + through Eio capabilities. 29 + 30 + Provides two libraries: 31 + 32 + - [nox-xdg] (module [Xdg]) -- the pure resolver, no Eio dep. 33 + - [nox-xdg.eio] (module [Xdge]) -- the Eio backend on top of the 34 + resolver. 35 + 36 + Forked rather than depended on so this package has no upstream dep 37 + on dune's internal xdg library, which dune doesn't promise to keep 38 + stable across releases.") 39 + (depends 40 + (ocaml (>= 5.1)) 41 + (dune (>= 3.21)) 42 + (eio (>= 1.1)) 43 + (cmdliner (>= 1.2.0)) 44 + fmt 45 + (eio_main :with-test) 46 + (alcotest :with-test) 47 + (odoc :with-doc) 48 + (mdx :with-test)))
+17
ocaml-xdg/lib/dune
··· 1 + (library 2 + (public_name nox-xdg) 3 + (name xdg) 4 + (modules xdg) 5 + (foreign_stubs 6 + (language c) 7 + (names xdg_stubs))) 8 + 9 + (library 10 + (public_name nox-xdg.eio) 11 + (name xdg_eio) 12 + (modules xdg_eio) 13 + (libraries nox-xdg eio unix cmdliner fmt)) 14 + 15 + (mdx 16 + (files xdg_eio.mli) 17 + (libraries nox-xdg.eio fmt eio eio.unix eio_main))
+94
ocaml-xdg/lib/xdg.ml
··· 1 + (* Forked from dune's otherlibs/xdg/xdg.ml. 2 + Copyright (c) 2016 Jane Street Group, LLC <opensource@janestreet.com> 3 + MIT licensed; see LICENSE.md. 4 + 5 + The C stub symbol [dune_xdg__get_known_folder_path] is renamed to 6 + [nox_xdg__get_known_folder_path] to avoid duplicate-symbol errors at 7 + link time when a downstream binary pulls in both this package and 8 + dune's internal xdg library. *) 9 + 10 + type t = { 11 + env : string -> string option; 12 + win32 : bool; 13 + home_dir : string; 14 + mutable cache_dir : string; 15 + mutable config_dir : string; 16 + mutable data_dir : string; 17 + mutable state_dir : string; 18 + mutable runtime_dir : string option; 19 + } 20 + 21 + let ( / ) = Filename.concat 22 + 23 + (* The two Windows known folders we use as XDG defaults on win32. The 24 + integer ordinals are passed to the C stub, which dispatches to the 25 + corresponding [FOLDERID_*]: 26 + - 0 -> [FOLDERID_InternetCache] (cache_dir) 27 + - 1 -> [FOLDERID_LocalAppData] (config_dir / data_dir / state_dir) *) 28 + type known_folder = InternetCache | LocalAppData 29 + 30 + external get_known_folder_path : known_folder -> string option 31 + = "nox_xdg__get_known_folder_path" 32 + 33 + (* Resolve a single XDG path: prefer the env var if set to an absolute 34 + path, otherwise fall back to either the Windows known-folder path 35 + (when [t.win32]) or the Unix default (e.g. ~/.config). The XDG spec 36 + requires the env var to hold an absolute path; relative values are 37 + ignored. 38 + 39 + Difference from upstream: we only invoke the C stub when the default 40 + is actually needed. Upstream computes [default] eagerly even when the 41 + env var override is going to win, which makes the stub raise on 42 + non-Windows hosts even for callers that never depend on the default. 43 + Functionally equivalent on Windows; lets [~win32:true] be exercised 44 + from a non-Windows test suite as long as every XDG_*_HOME is set. *) 45 + let make t env_var unix_default win32_folder = 46 + match t.env env_var with 47 + | Some s when not (Filename.is_relative s) -> s 48 + | _ -> 49 + if t.win32 then 50 + match get_known_folder_path win32_folder with None -> "" | Some s -> s 51 + else unix_default 52 + 53 + let cache_dir t = make t "XDG_CACHE_HOME" (t.home_dir / ".cache") InternetCache 54 + 55 + let config_dir t = 56 + make t "XDG_CONFIG_HOME" (t.home_dir / ".config") LocalAppData 57 + 58 + let data_dir t = 59 + make t "XDG_DATA_HOME" (t.home_dir / ".local" / "share") LocalAppData 60 + 61 + let state_dir t = 62 + make t "XDG_STATE_HOME" (t.home_dir / ".local" / "state") LocalAppData 63 + 64 + let create ?win32 ~env () = 65 + let win32 = match win32 with None -> Sys.win32 | Some s -> s in 66 + let home_dir = 67 + let var = if win32 then "USERPROFILE" else "HOME" in 68 + match env var with None -> "" | Some s -> s 69 + in 70 + let t = 71 + { 72 + env; 73 + win32; 74 + home_dir; 75 + cache_dir = ""; 76 + config_dir = ""; 77 + data_dir = ""; 78 + state_dir = ""; 79 + runtime_dir = None; 80 + } 81 + in 82 + t.cache_dir <- cache_dir t; 83 + t.config_dir <- config_dir t; 84 + t.data_dir <- data_dir t; 85 + t.state_dir <- state_dir t; 86 + t.runtime_dir <- env "XDG_RUNTIME_DIR"; 87 + t 88 + 89 + let home_dir t = t.home_dir 90 + let config_dir t = t.config_dir 91 + let data_dir t = t.data_dir 92 + let cache_dir t = t.cache_dir 93 + let state_dir t = t.state_dir 94 + let runtime_dir t = t.runtime_dir
+69
ocaml-xdg/lib/xdg.mli
··· 1 + (** XDG Base Directory specification: pure resolution. 2 + 3 + Forked from dune's internal [xdg] library 4 + ({{:https://github.com/ocaml/dune/tree/main/otherlibs/xdg} [otherlibs/xdg]}; 5 + META description: 6 + ["[Internal] XDG base directories specification implementation"]). Forked 7 + rather than depended on so this package has no upstream dep on a build-tool 8 + internal -- dune doesn't promise to keep its [xdg] library stable across 9 + releases. 10 + 11 + Computes the standard XDG base directory paths from the 12 + {{:https://specifications.freedesktop.org/basedir-spec/latest/} XDG Base 13 + Directory Specification} on Unix, and the corresponding 14 + {{:https://learn.microsoft.com/en-us/windows/win32/shell/known-folders} 15 + known folders} on Windows. 16 + 17 + Pure: no filesystem effects, no Eio dependency. Use [nox-xdge] for the 18 + Eio-aware wrapper that creates directories, validates permissions, and 19 + threads paths through Eio capabilities. 20 + 21 + Original code by Jane Street; this is an MIT-licensed fork. *) 22 + 23 + type t 24 + (** A resolved set of XDG base directories, computed once from an environment 25 + snapshot. *) 26 + 27 + val create : ?win32:bool -> env:(string -> string option) -> unit -> t 28 + (** [create ?win32 ~env ()] resolves the base directories from the given 29 + environment lookup ([Sys.getenv_opt] in production, a closure in tests for 30 + determinism). 31 + 32 + [win32] defaults to {!Sys.win32}. When [true], path defaults are taken from 33 + Windows known folders (via the [SHGetKnownFolderPath] shell API) rather than 34 + the Unix [$HOME/.config] / [$HOME/.cache] / etc. Environment variables 35 + ([XDG_CONFIG_HOME] etc.) still override the defaults on either platform when 36 + set to an absolute path. 37 + 38 + The home directory is read from [$USERPROFILE] on Windows and [$HOME] on 39 + Unix; if unset, it defaults to the empty string. *) 40 + 41 + val home_dir : t -> string 42 + (** [home_dir t] is [$USERPROFILE] (Windows) or [$HOME] (Unix), or [""] if 43 + unset. *) 44 + 45 + val config_dir : t -> string 46 + (** [config_dir t] is [$XDG_CONFIG_HOME] if set to an absolute path, the Windows 47 + [LocalAppData] folder if [~win32:true], otherwise [home_dir t / ".config"]. 48 + *) 49 + 50 + val data_dir : t -> string 51 + (** [data_dir t] is [$XDG_DATA_HOME] if set to an absolute path, the Windows 52 + [LocalAppData] folder if [~win32:true], otherwise 53 + [home_dir t / ".local/share"]. *) 54 + 55 + val cache_dir : t -> string 56 + (** [cache_dir t] is [$XDG_CACHE_HOME] if set to an absolute path, the Windows 57 + [InternetCache] folder if [~win32:true], otherwise [home_dir t / ".cache"]. 58 + *) 59 + 60 + val state_dir : t -> string 61 + (** [state_dir t] is [$XDG_STATE_HOME] if set to an absolute path, the Windows 62 + [LocalAppData] folder if [~win32:true], otherwise 63 + [home_dir t / ".local/state"]. *) 64 + 65 + val runtime_dir : t -> string option 66 + (** [runtime_dir t] is [$XDG_RUNTIME_DIR] if set, [None] otherwise. The XDG spec 67 + deliberately has no default for this one -- a missing value means "no 68 + runtime directory available", which the caller must handle. There is no 69 + Windows fallback. *)
+80
ocaml-xdg/lib/xdg_stubs.c
··· 1 + /* 2 + * XDG known-folder lookup on Windows. 3 + * 4 + * Forked from dune's otherlibs/xdg/xdg_stubs.c. The original symbol 5 + * (dune_xdg__get_known_folder_path) is renamed to nox_xdg__... to 6 + * avoid duplicate-symbol errors at link time when a downstream binary 7 + * pulls in both this package and dune's internal xdg library. 8 + */ 9 + 10 + #include <caml/alloc.h> 11 + #include <caml/fail.h> 12 + #include <caml/memory.h> 13 + #include <caml/mlvalues.h> 14 + 15 + #ifdef _WIN32 16 + 17 + /* Windows Vista (or later) functions enabled, for SHGetKnownFolderPath. */ 18 + #undef _WIN32_WINNT 19 + #define _WIN32_WINNT 0x0600 20 + 21 + #include <knownfolders.h> 22 + #include <shlobj.h> 23 + #include <windows.h> 24 + 25 + value nox_xdg__get_known_folder_path(value v_known_folder) { 26 + CAMLparam1(v_known_folder); 27 + CAMLlocal2(v_res, v_path); 28 + WCHAR *wcp = NULL; 29 + HRESULT res; 30 + int wlen, len; 31 + const KNOWNFOLDERID *rfid; 32 + 33 + v_res = Val_int(0); 34 + 35 + switch (Int_val(v_known_folder)) { 36 + case 0: 37 + rfid = &FOLDERID_InternetCache; 38 + break; 39 + case 1: 40 + rfid = &FOLDERID_LocalAppData; 41 + break; 42 + default: 43 + caml_invalid_argument("get_known_folder_path"); 44 + break; 45 + } 46 + 47 + res = SHGetKnownFolderPath(rfid, 0, NULL, &wcp); 48 + 49 + if (res != S_OK) 50 + goto done; 51 + 52 + wlen = wcslen(wcp); 53 + len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wcp, wlen, NULL, 0, 54 + NULL, NULL); 55 + 56 + if (!len) 57 + goto done; 58 + 59 + v_path = caml_alloc_string(len); 60 + 61 + if (!WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wcp, wlen, 62 + (char *)String_val(v_path), len, NULL, NULL)) 63 + goto done; 64 + 65 + v_res = caml_alloc_small(1, 0); 66 + Field(v_res, 0) = v_path; 67 + 68 + done: 69 + CoTaskMemFree(wcp); 70 + CAMLreturn(v_res); 71 + } 72 + 73 + #else /* _WIN32 */ 74 + 75 + value nox_xdg__get_known_folder_path(value v_unit) { 76 + (void)v_unit; 77 + caml_invalid_argument("get_known_folder_path: not implemented"); 78 + } 79 + 80 + #endif /* _WIN32 */
+61
ocaml-xdg/nox-xdg.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "XDG Base Directory specification: pure resolution + Eio backend" 4 + description: """ 5 + Fork/merge of two upstream packages: 6 + 7 + - dune's internal [otherlibs/xdg] -- pure XDG Base Directory 8 + specification resolution ($XDG_CONFIG_HOME, $XDG_DATA_HOME, 9 + $XDG_CACHE_HOME, $XDG_STATE_HOME, $XDG_RUNTIME_DIR), with full 10 + parity (Unix + Windows known-folder fallbacks). 11 + - the previous [nox-xdge] package -- an Eio-aware wrapper that 12 + creates directories, validates permissions, and threads paths 13 + through Eio capabilities. 14 + 15 + Provides two libraries: 16 + 17 + - [nox-xdg] (module [Xdg]) -- the pure resolver, no Eio dep. 18 + - [nox-xdg.eio] (module [Xdge]) -- the Eio backend on top of the 19 + resolver. 20 + 21 + Forked rather than depended on so this package has no upstream dep 22 + on dune's internal xdg library, which dune doesn't promise to keep 23 + stable across releases.""" 24 + maintainer: ["Thomas Gazagnaire <thomas@gazagnaire.org>"] 25 + authors: [ 26 + "Jane Street Group, LLC <opensource@janestreet.com>" 27 + "Thomas Gazagnaire <thomas@gazagnaire.org>" 28 + ] 29 + license: "MIT" 30 + tags: ["org:blacksun" "freedesktop" "xdg" "windows" "eio"] 31 + homepage: "https://tangled.org/gazagnaire.org/ocaml-xdg" 32 + bug-reports: "https://tangled.org/gazagnaire.org/ocaml-xdg/issues" 33 + depends: [ 34 + "ocaml" {>= "5.1"} 35 + "dune" {>= "3.21" & >= "3.21"} 36 + "eio" {>= "1.1"} 37 + "cmdliner" {>= "1.2.0"} 38 + "fmt" 39 + "eio_main" {with-test} 40 + "alcotest" {with-test} 41 + "odoc" {with-doc} 42 + "mdx" {with-test} 43 + ] 44 + build: [ 45 + ["dune" "subst"] {dev} 46 + [ 47 + "dune" 48 + "build" 49 + "-p" 50 + name 51 + "-j" 52 + jobs 53 + "@install" 54 + "@runtest" {with-test} 55 + "@doc" {with-doc} 56 + ] 57 + ] 58 + dev-repo: "git+https://tangled.org/gazagnaire.org/ocaml-xdg" 59 + x-maintenance-intent: ["(latest)"] 60 + x-quality-build: "2026-04-30" 61 + x-quality-test: "2026-04-30"
+2
ocaml-xdg/nox-xdg.opam.template
··· 1 + x-quality-build: "2026-04-30" 2 + x-quality-test: "2026-04-30"
+3
ocaml-xdg/test/dune
··· 1 + (test 2 + (name test) 3 + (libraries nox-xdg alcotest))
+3
ocaml-xdg/test/eio/cram/helpers/dune
··· 1 + (executable 2 + (name xdg_example) 3 + (libraries nox-xdg.eio eio_main cmdliner fmt))
+3
ocaml-xdg/test/eio/dune
··· 1 + (test 2 + (name test) 3 + (libraries nox-xdg.eio alcotest eio_main))
+1
ocaml-xdg/test/eio/test.ml
··· 1 + let () = Alcotest.run "nox-xdg.eio" [ Test_xdg_eio.suite ]
+1
ocaml-xdg/test/test.ml
··· 1 + let () = Alcotest.run "nox-xdg" [ Test_xdg.suite ]
+297
ocaml-xdg/test/test_xdg.ml
··· 1 + (* Tests for the XDG Base Directory specification implementation. 2 + 3 + Spec: 4 + https://specifications.freedesktop.org/basedir-spec/latest/ 5 + 6 + Spec rules exercised: 7 + 8 + §3 Environment variables: 9 + - $XDG_DATA_HOME defines the base directory relative to which user- 10 + specific data files should be stored. If empty or not set, default 11 + to $HOME/.local/share. 12 + - $XDG_CONFIG_HOME ... default to $HOME/.config. 13 + - $XDG_STATE_HOME ... default to $HOME/.local/state. 14 + - $XDG_CACHE_HOME ... default to $HOME/.cache. 15 + - $XDG_RUNTIME_DIR is required from environment; no default. 16 + 17 + §4 Referencing: 18 + - All paths set in the environment variables MUST be absolute. If an 19 + implementation encounters a relative path in any of these 20 + variables it should consider the path invalid and ignore it. 21 + 22 + Plus the Windows extension (inherited from dune's upstream): 23 + - With ~win32:true, defaults come from SHGetKnownFolderPath 24 + (LocalAppData / InternetCache) instead of $HOME/.config etc. 25 + - $USERPROFILE replaces $HOME for the Windows home directory. 26 + - Env var overrides still apply on Windows. *) 27 + 28 + (* Build a closure that mimics Sys.getenv_opt for a fixed env. *) 29 + let env_of bindings name = List.assoc_opt name bindings 30 + let create ?win32 env_bindings = Xdg.create ?win32 ~env:(env_of env_bindings) () 31 + 32 + (* -- §3 Defaults: env vars unset, all paths derived from $HOME -- *) 33 + 34 + let unix_defaults_when_env_unset () = 35 + let t = create [ ("HOME", "/home/alice") ] in 36 + Alcotest.(check string) "HOME" "/home/alice" (Xdg.home_dir t); 37 + Alcotest.(check string) 38 + "config_dir default" "/home/alice/.config" (Xdg.config_dir t); 39 + Alcotest.(check string) 40 + "data_dir default" "/home/alice/.local/share" (Xdg.data_dir t); 41 + Alcotest.(check string) 42 + "cache_dir default" "/home/alice/.cache" (Xdg.cache_dir t); 43 + Alcotest.(check string) 44 + "state_dir default" "/home/alice/.local/state" (Xdg.state_dir t) 45 + 46 + let runtime_dir_no_default () = 47 + (* §3: $XDG_RUNTIME_DIR has no default. *) 48 + let t = create [ ("HOME", "/home/alice") ] in 49 + Alcotest.(check (option string)) 50 + "no runtime dir when unset" None (Xdg.runtime_dir t) 51 + 52 + let runtime_dir_when_set () = 53 + let t = 54 + create [ ("HOME", "/home/alice"); ("XDG_RUNTIME_DIR", "/run/user/1000") ] 55 + in 56 + Alcotest.(check (option string)) 57 + "runtime dir from env" (Some "/run/user/1000") (Xdg.runtime_dir t) 58 + 59 + (* -- §3 / §4 Env var overrides when absolute -- *) 60 + 61 + let xdg_config_home_overrides () = 62 + let t = create [ ("HOME", "/home/alice"); ("XDG_CONFIG_HOME", "/etc/xdg") ] in 63 + Alcotest.(check string) "config_dir from env" "/etc/xdg" (Xdg.config_dir t) 64 + 65 + let xdg_data_home_overrides () = 66 + let t = 67 + create [ ("HOME", "/home/alice"); ("XDG_DATA_HOME", "/var/lib/data") ] 68 + in 69 + Alcotest.(check string) "data_dir from env" "/var/lib/data" (Xdg.data_dir t) 70 + 71 + let xdg_cache_home_overrides () = 72 + let t = 73 + create [ ("HOME", "/home/alice"); ("XDG_CACHE_HOME", "/var/cache") ] 74 + in 75 + Alcotest.(check string) "cache_dir from env" "/var/cache" (Xdg.cache_dir t) 76 + 77 + let xdg_state_home_overrides () = 78 + let t = 79 + create [ ("HOME", "/home/alice"); ("XDG_STATE_HOME", "/var/lib/state") ] 80 + in 81 + Alcotest.(check string) 82 + "state_dir from env" "/var/lib/state" (Xdg.state_dir t) 83 + 84 + (* -- §4 Relative env vars are ignored -- *) 85 + 86 + let relative_xdg_config_home_ignored () = 87 + let t = 88 + create [ ("HOME", "/home/alice"); ("XDG_CONFIG_HOME", "relative/config") ] 89 + in 90 + Alcotest.(check string) 91 + "relative env var ignored, default used" "/home/alice/.config" 92 + (Xdg.config_dir t) 93 + 94 + let relative_xdg_data_home_ignored () = 95 + let t = create [ ("HOME", "/home/alice"); ("XDG_DATA_HOME", "../data") ] in 96 + Alcotest.(check string) 97 + "relative env var ignored" "/home/alice/.local/share" (Xdg.data_dir t) 98 + 99 + let relative_xdg_cache_home_ignored () = 100 + let t = create [ ("HOME", "/home/alice"); ("XDG_CACHE_HOME", "cache") ] in 101 + Alcotest.(check string) 102 + "relative env var ignored" "/home/alice/.cache" (Xdg.cache_dir t) 103 + 104 + let relative_xdg_state_home_ignored () = 105 + let t = create [ ("HOME", "/home/alice"); ("XDG_STATE_HOME", "state/dir") ] in 106 + Alcotest.(check string) 107 + "relative env var ignored" "/home/alice/.local/state" (Xdg.state_dir t) 108 + 109 + (* Per spec wording: "If empty or not set, default ...". We treat empty as 110 + unset by relying on env returning None for missing -- but if the env 111 + provides Some "", we follow upstream and use the empty value as a 112 + relative path (which fails the absolute check, falling back to default). *) 113 + let empty_xdg_config_home_falls_back () = 114 + let t = create [ ("HOME", "/home/alice"); ("XDG_CONFIG_HOME", "") ] in 115 + Alcotest.(check string) 116 + "empty env var ignored as non-absolute" "/home/alice/.config" 117 + (Xdg.config_dir t) 118 + 119 + (* -- HOME handling -- *) 120 + 121 + let home_unset_yields_empty () = 122 + let t = create [] in 123 + Alcotest.(check string) "HOME unset -> empty" "" (Xdg.home_dir t); 124 + (* Defaults still computed but with empty HOME they end up as relative 125 + ".config" etc. The spec doesn't define this corner; we match 126 + upstream. *) 127 + Alcotest.(check string) 128 + "config_dir relative when HOME unset" ".config" (Xdg.config_dir t) 129 + 130 + let home_explicit_used_when_no_xdg_vars () = 131 + let t = create [ ("HOME", "/u/bob") ] in 132 + Alcotest.(check string) "HOME used" "/u/bob/.config" (Xdg.config_dir t) 133 + 134 + (* -- Independence: each env var is independent -- *) 135 + 136 + let only_one_xdg_var_set () = 137 + let t = 138 + create [ ("HOME", "/home/alice"); ("XDG_CACHE_HOME", "/var/cache") ] 139 + in 140 + Alcotest.(check string) "cache_dir overridden" "/var/cache" (Xdg.cache_dir t); 141 + Alcotest.(check string) 142 + "config_dir default" "/home/alice/.config" (Xdg.config_dir t); 143 + Alcotest.(check string) 144 + "data_dir default" "/home/alice/.local/share" (Xdg.data_dir t); 145 + Alcotest.(check string) 146 + "state_dir default" "/home/alice/.local/state" (Xdg.state_dir t) 147 + 148 + let all_xdg_vars_set () = 149 + let t = 150 + create 151 + [ 152 + ("HOME", "/h"); 153 + ("XDG_CONFIG_HOME", "/a"); 154 + ("XDG_DATA_HOME", "/b"); 155 + ("XDG_CACHE_HOME", "/c"); 156 + ("XDG_STATE_HOME", "/d"); 157 + ("XDG_RUNTIME_DIR", "/e"); 158 + ] 159 + in 160 + Alcotest.(check string) "config" "/a" (Xdg.config_dir t); 161 + Alcotest.(check string) "data" "/b" (Xdg.data_dir t); 162 + Alcotest.(check string) "cache" "/c" (Xdg.cache_dir t); 163 + Alcotest.(check string) "state" "/d" (Xdg.state_dir t); 164 + Alcotest.(check (option string)) "runtime" (Some "/e") (Xdg.runtime_dir t) 165 + 166 + (* -- Resolution snapshot at create time -- *) 167 + 168 + let resolution_at_create_time () = 169 + (* Once create returns, mutations to the closure's environment must not 170 + affect resolved paths. *) 171 + let env = ref [ ("HOME", "/home/alice") ] in 172 + let t = Xdg.create ~env:(fun n -> List.assoc_opt n !env) () in 173 + let original = Xdg.config_dir t in 174 + env := [ ("HOME", "/home/bob") ]; 175 + Alcotest.(check string) 176 + "config_dir frozen at create" original (Xdg.config_dir t) 177 + 178 + (* -- Windows extension -- *) 179 + 180 + (* Build a Windows-style env where every XDG path is set to an absolute 181 + value. This shields all the win32-mode tests from the 182 + SHGetKnownFolderPath C stub, which raises [Invalid_argument] on 183 + non-Windows hosts. The dedicated default-fallback test below is the 184 + only one that hits the stub. *) 185 + (* Use Unix-style absolute paths (leading [/]) -- those are recognized as 186 + absolute by both Unix and Windows OCaml runtimes, which lets the same 187 + fixture run on both platforms. *) 188 + let win32_env_with_all_xdg ?(extra = []) base = 189 + let xdg = 190 + [ 191 + ("XDG_CONFIG_HOME", "/D/config"); 192 + ("XDG_DATA_HOME", "/D/data"); 193 + ("XDG_CACHE_HOME", "/D/cache"); 194 + ("XDG_STATE_HOME", "/D/state"); 195 + ] 196 + in 197 + base @ xdg @ extra 198 + 199 + let win32_uses_userprofile_for_home () = 200 + let t = 201 + create ~win32:true 202 + (win32_env_with_all_xdg 203 + [ ("USERPROFILE", "C:\\Users\\Alice"); ("HOME", "/home/alice") ]) 204 + in 205 + Alcotest.(check string) 206 + "USERPROFILE wins on win32" "C:\\Users\\Alice" (Xdg.home_dir t) 207 + 208 + let win32_env_override_still_works () = 209 + let t = 210 + create ~win32:true 211 + (win32_env_with_all_xdg [ ("USERPROFILE", "C:\\Users\\Alice") ]) 212 + in 213 + Alcotest.(check string) "env override on win32" "/D/config" (Xdg.config_dir t) 214 + 215 + let win32_runtime_dir_from_env () = 216 + let t = 217 + create ~win32:true 218 + (win32_env_with_all_xdg 219 + ~extra:[ ("XDG_RUNTIME_DIR", "/runtime") ] 220 + [ ("USERPROFILE", "C:\\Users\\Alice") ]) 221 + in 222 + Alcotest.(check (option string)) 223 + "runtime dir from env on win32" (Some "/runtime") (Xdg.runtime_dir t) 224 + 225 + let win32_runtime_dir_unset () = 226 + let t = 227 + create ~win32:true 228 + (win32_env_with_all_xdg [ ("USERPROFILE", "C:\\Users\\Alice") ]) 229 + in 230 + Alcotest.(check (option string)) 231 + "no runtime dir on win32 when unset" None (Xdg.runtime_dir t) 232 + 233 + (* The win32 default-path test (env unset, win32=true, expect known-folder 234 + path) requires the SHGetKnownFolderPath C stub to actually return a 235 + real path -- which only happens on Windows. On other platforms the 236 + stub raises [Invalid_argument] and that bubbles out of [create]. *) 237 + let win32_default_path_behaviour () = 238 + let create_unset () = 239 + create ~win32:true [ ("USERPROFILE", "C:\\Users\\Alice") ] 240 + in 241 + if Sys.win32 then 242 + let t = create_unset () in 243 + Alcotest.(check bool) 244 + "win32 config_dir non-empty" true 245 + (String.length (Xdg.config_dir t) > 0) 246 + else 247 + try 248 + let _ = create_unset () in 249 + Alcotest.fail "expected Invalid_argument on non-Windows" 250 + with Invalid_argument _ -> () 251 + 252 + let suite = 253 + ( "xdg", 254 + [ 255 + Alcotest.test_case "unix defaults when env unset" `Quick 256 + unix_defaults_when_env_unset; 257 + Alcotest.test_case "runtime dir has no default" `Quick 258 + runtime_dir_no_default; 259 + Alcotest.test_case "runtime dir from env" `Quick runtime_dir_when_set; 260 + Alcotest.test_case "XDG_CONFIG_HOME overrides" `Quick 261 + xdg_config_home_overrides; 262 + Alcotest.test_case "XDG_DATA_HOME overrides" `Quick 263 + xdg_data_home_overrides; 264 + Alcotest.test_case "XDG_CACHE_HOME overrides" `Quick 265 + xdg_cache_home_overrides; 266 + Alcotest.test_case "XDG_STATE_HOME overrides" `Quick 267 + xdg_state_home_overrides; 268 + Alcotest.test_case "relative XDG_CONFIG_HOME ignored" `Quick 269 + relative_xdg_config_home_ignored; 270 + Alcotest.test_case "relative XDG_DATA_HOME ignored" `Quick 271 + relative_xdg_data_home_ignored; 272 + Alcotest.test_case "relative XDG_CACHE_HOME ignored" `Quick 273 + relative_xdg_cache_home_ignored; 274 + Alcotest.test_case "relative XDG_STATE_HOME ignored" `Quick 275 + relative_xdg_state_home_ignored; 276 + Alcotest.test_case "empty XDG_CONFIG_HOME falls back" `Quick 277 + empty_xdg_config_home_falls_back; 278 + Alcotest.test_case "HOME unset yields empty home_dir" `Quick 279 + home_unset_yields_empty; 280 + Alcotest.test_case "HOME used when no XDG vars" `Quick 281 + home_explicit_used_when_no_xdg_vars; 282 + Alcotest.test_case "single XDG var only affects its slot" `Quick 283 + only_one_xdg_var_set; 284 + Alcotest.test_case "all XDG vars set" `Quick all_xdg_vars_set; 285 + Alcotest.test_case "resolution frozen at create time" `Quick 286 + resolution_at_create_time; 287 + Alcotest.test_case "win32 uses USERPROFILE for home" `Quick 288 + win32_uses_userprofile_for_home; 289 + Alcotest.test_case "win32 env override still works" `Quick 290 + win32_env_override_still_works; 291 + Alcotest.test_case "win32 runtime dir from env" `Quick 292 + win32_runtime_dir_from_env; 293 + Alcotest.test_case "win32 runtime dir unset -> None" `Quick 294 + win32_runtime_dir_unset; 295 + Alcotest.test_case "win32 default path behaviour" `Quick 296 + win32_default_path_behaviour; 297 + ] )
+1
ocaml-xdg/test/test_xdg.mli
··· 1 + val suite : string * unit Alcotest.test_case list
+4 -4
sources.toml
··· 95 95 upstream = "git+https://tangled.org/anil.recoil.org/ocaml-cookeio" 96 96 reason = "Fork" 97 97 98 - [xdge] 99 - source = "git+https://tangled.org/gazagnaire.org/xdge" 100 - upstream = "git+https://tangled.org/anil.recoil.org/xdge" 101 - reason = "Collaborative development" 98 + [ocaml-xdg] 99 + source = "git+https://tangled.org/gazagnaire.org/ocaml-xdg" 100 + upstream = "git+https://github.com/ocaml/dune" 101 + reason = "Fork of dune's internal otherlibs/xdg as a standalone package; merged with the previous nox-xdge Eio wrapper" 102 102 103 103 [ocaml-claude-skills] 104 104 source = "git+https://tangled.org/gazagnaire.org/ocaml-claude-skills"
-17
xdge/.gitignore
··· 1 - # OCaml build artifacts 2 - _build/ 3 - *.install 4 - *.merlin 5 - 6 - # Third-party sources (fetch locally with opam source) 7 - third_party/ 8 - 9 - # Editor and OS files 10 - .DS_Store 11 - *.swp 12 - *~ 13 - .vscode/ 14 - .idea/ 15 - 16 - # Opam local switch 17 - _opam/
-1
xdge/.ocamlformat
··· 1 - version = 0.29.0
-49
xdge/.tangled/workflows/build.yml
··· 1 - when: 2 - - event: ["push", "pull_request"] 3 - branch: ["main"] 4 - 5 - engine: nixery 6 - 7 - dependencies: 8 - nixpkgs: 9 - - shell 10 - - stdenv 11 - - findutils 12 - - binutils 13 - - libunwind 14 - - ncurses 15 - - opam 16 - - git 17 - - gawk 18 - - gnupatch 19 - - gnum4 20 - - gnumake 21 - - gnutar 22 - - gnused 23 - - gnugrep 24 - - diffutils 25 - - gzip 26 - - bzip2 27 - - gcc 28 - - ocaml 29 - 30 - steps: 31 - - name: opam 32 - command: | 33 - opam init --disable-sandboxing -any 34 - - name: switch 35 - command: | 36 - opam install . --confirm-level=unsafe-yes --deps-only 37 - - name: build 38 - command: | 39 - opam exec -- dune build 40 - - name: switch-test 41 - command: | 42 - opam install . --confirm-level=unsafe-yes --deps-only --with-test 43 - - name: test 44 - command: | 45 - opam exec -- dune runtest --verbose 46 - - name: doc 47 - command: | 48 - opam install -y odoc 49 - opam exec -- dune build @doc
-10
xdge/CHANGES.md
··· 1 - v1.1.0 (dev) 2 - ------------ 3 - 4 - - Remove dependency on `eio_main` for library (@avsm). 5 - Thanks to @Alizter for the workaround in https://github.com/ocaml/dune/issues/12821). 6 - 7 - v1.0.0 (2025-11-29) 8 - ------------------- 9 - 10 - - Initial public release (@avsm)
-18
xdge/LICENSE.md
··· 1 - (* 2 - * ISC License 3 - * 4 - * Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org> 5 - * 6 - * Permission to use, copy, modify, and distribute this software for any 7 - * purpose with or without fee is hereby granted, provided that the above 8 - * copyright notice and this permission notice appear in all copies. 9 - * 10 - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 - * 18 - *)
-110
xdge/README.md
··· 1 - # xdge - XDG Base Directory Specification for Eio 2 - 3 - This library implements the [XDG Base Directory 4 - Specification](https://specifications.freedesktop.org/basedir-spec/latest/) for 5 - OCaml applications using the [Eio](https://github.com/ocaml-multicore/eio) 6 - effects-based I/O library. 7 - 8 - ## What is XDG? 9 - 10 - The XDG Base Directory Specification defines standard locations for user-specific files on Unix-like systems, keeping home directories clean and organised: 11 - 12 - - Config (`~/.config/app`): User preferences and settings 13 - - Data (`~/.local/share/app`): Persistent application data 14 - - Cache (`~/.cache/app`): Non-essential cached data (safe to delete) 15 - - State (`~/.local/state/app`): Logs, history, and runtime state 16 - - Runtime (`$XDG_RUNTIME_DIR/app`): Sockets, pipes, and session-bound files 17 - 18 - The specification also defines system-wide search paths (`/etc/xdg`, 19 - `/usr/share`) and a precedence system using environment variables 20 - (`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, and so on). 21 - 22 - ## Why Eio? 23 - 24 - Eio uses a **capability-based** approach to I/O where filesystem access must be 25 - explicitly passed to functions. This design aligns naturally with XDG directory 26 - management. For example: 27 - 28 - ```ocaml 29 - (* Filesystem access is an explicit capability. *) 30 - let make ~env = Xdge.v env#fs "myapp" 31 - ``` 32 - 33 - The capability model provides the benefit that code that needs filesystem 34 - access must receive the `fs` capability, with no hidden global state or ambient 35 - authority. The `Eio.Path.t` type returned by xdge encapsulates both the 36 - filesystem capability and the path, preventing path traversal outside the 37 - granted capability. Applications can restrict filesystem access by passing a 38 - sandboxed `fs` capability, and xdge respects those boundaries. 39 - 40 - ## Usage 41 - 42 - ```ocaml 43 - let run () = 44 - Eio_main.run @@ fun env -> 45 - let xdg = Xdge.v env#fs "myapp" in 46 - let config = Xdge.config_dir xdg in 47 - let data = Xdge.data_dir xdg in 48 - Fmt.pr "config: %a@." Eio.Path.pp config; 49 - Fmt.pr "data: %a@." Eio.Path.pp data; 50 - match Xdge.config_file xdg "settings.json" with 51 - | Some path -> Fmt.pr "found settings.json at %a@." Eio.Path.pp path 52 - | None -> Fmt.pr "no settings.json on the XDG search path@." 53 - ``` 54 - 55 - On a default Linux install with no XDG overrides the first three lines 56 - print: 57 - 58 - ``` 59 - config: <fs>/home/alice/.config/myapp 60 - data: <fs>/home/alice/.local/share/myapp 61 - ``` 62 - 63 - `Xdge.config_file` walks `$XDG_CONFIG_HOME`, then each entry of 64 - `$XDG_CONFIG_DIRS` (defaulting to `/etc/xdg`), returning the first hit: 65 - 66 - ``` 67 - 1. /home/alice/.config/myapp/settings.json 68 - 2. /etc/xdg/myapp/settings.json 69 - ``` 70 - 71 - Equivalent paths on macOS follow the same precedence rules with 72 - `$HOME/Library/Application Support/myapp` as the user default. Override 73 - any layer by setting `XDG_CONFIG_HOME`, `XDG_CONFIG_DIRS`, etc. 74 - 75 - ## Cmdliner integration 76 - 77 - For CLI applications, xdge provides a Cmdliner term that exposes 78 - `--config-dir`, `--data-dir`, `--cache-dir`, `--state-dir`, and 79 - `--runtime-dir` flags, with precedence: 80 - 81 - ``` 82 - --config-dir (CLI) > MYAPP_CONFIG_DIR > XDG_CONFIG_HOME > default 83 - ``` 84 - 85 - ```ocaml 86 - let term ~env = Xdge.Cmd.term "myapp" env#fs () 87 - ``` 88 - 89 - ## Installation 90 - 91 - Install with opam: 92 - 93 - <!-- $MDX skip --> 94 - ```sh 95 - $ opam install nox-xdge 96 - ``` 97 - 98 - If opam cannot find the package, it may not yet be released in the public 99 - `opam-repository`. Add the overlay repository, then install it: 100 - 101 - <!-- $MDX skip --> 102 - ```sh 103 - $ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git 104 - $ opam update 105 - $ opam install nox-xdge 106 - ``` 107 - 108 - ## Licence 109 - 110 - ISC
-12
xdge/dune
··· 1 - (env 2 - (dev 3 - (flags :standard %{dune-warnings}))) 4 - 5 - (alias 6 - (name default) 7 - (deps 8 - (alias_rec lib/all))) 9 - 10 - (mdx 11 - (files README.md) 12 - (libraries nox-xdge eio_main eio eio.core eio.unix cmdliner fmt))
-35
xdge/dune-project
··· 1 - (lang dune 3.21) 2 - (using mdx 0.4) 3 - 4 - (name nox-xdge) 5 - 6 - (generate_opam_files true) 7 - 8 - (license ISC) 9 - (authors "Anil Madhavapeddy") 10 - (source (tangled anil.recoil.org/xdge)) 11 - (homepage "https://tangled.org/anil.recoil.org/xdge") 12 - (maintainers "Anil Madhavapeddy <anil@recoil.org>") 13 - (bug_reports "https://tangled.org/anil.recoil.org/xdge/issues") 14 - (maintenance_intent "(latest)") 15 - 16 - (package 17 - (name nox-xdge) 18 - (synopsis "XDG Base Directory Specification support for Eio") 19 - (tags (org:blacksun eio system)) 20 - (description 21 - "This library implements the XDG Base Directory Specification \ 22 - with Eio capabilities to provide safe access to configuration, \ 23 - data, cache, state, and runtime directories. The library exposes \ 24 - Cmdliner terms that allow for proper environment variable overrides \ 25 - and command-line flags.") 26 - (depends 27 - (ocaml (>= 5.1.0)) 28 - (eio (>= 1.1)) 29 - (cmdliner (>= 1.2.0)) 30 - fmt 31 - xdg 32 - (eio_main :with-test) 33 - (odoc :with-doc) 34 - (mdx :with-test) 35 - (alcotest (and :with-test (>= 1.7.0)))))
-8
xdge/lib/dune
··· 1 - (library 2 - (public_name nox-xdge) 3 - (name xdge) 4 - (libraries eio xdg unix cmdliner fmt)) 5 - 6 - (mdx 7 - (files xdge.mli) 8 - (libraries nox-xdge fmt eio eio.unix eio_main))
xdge/lib/xdge.ml ocaml-xdg/lib/xdg_eio.ml
+3 -3
xdge/lib/xdge.mli ocaml-xdg/lib/xdg_eio.mli
··· 44 44 45 45 This library is used by: 46 46 47 - - [Requests] - HTTP client that uses Xdge for cookie persistence paths 47 + - [Requests] - HTTP client that uses Xdg_eio for cookie persistence paths 48 48 - [Cookie_jar] - Cookie jar with XDG-compliant storage *) 49 49 50 50 type t ··· 87 87 let run () = 88 88 Eio_main.run @@ fun env -> 89 89 let fs = Eio.Stdenv.fs env in 90 - let xdg = Xdge.v fs "myapp" in 91 - let config = Xdge.config_dir xdg in 90 + let xdg = Xdg_eio.v fs "myapp" in 91 + let config = Xdg_eio.config_dir xdg in 92 92 Fmt.pr "config dir: %a@." Eio.Path.pp config 93 93 ]} 94 94
-33
xdge/nox-xdge.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "XDG Base Directory Specification support for Eio" 4 - description: 5 - "This library implements the XDG Base Directory Specification with Eio capabilities to provide safe access to configuration, data, cache, state, and runtime directories. The library exposes Cmdliner terms that allow for proper environment variable overrides and command-line flags." 6 - maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 - authors: ["Anil Madhavapeddy"] 8 - license: "ISC" 9 - tags: ["org:blacksun" "eio" "system"] 10 - homepage: "https://tangled.org/anil.recoil.org/xdge" 11 - bug-reports: "https://tangled.org/anil.recoil.org/xdge/issues" 12 - depends: [ 13 - "dune" {>= "3.21"} 14 - "ocaml" {>= "5.1.0"} 15 - "eio" {>= "1.1"} 16 - "cmdliner" {>= "1.2.0"} 17 - "fmt" 18 - "xdg" 19 - "eio_main" {with-test} 20 - "odoc" {with-doc} 21 - "mdx" {with-test} 22 - "alcotest" {with-test & >= "1.7.0"} 23 - ] 24 - dev-repo: "git+https://tangled.org/anil.recoil.org/xdge" 25 - x-maintenance-intent: ["(latest)"] 26 - build: [ 27 - [ "dune" "subst" ] {dev} 28 - [ "dune" "build" "-p" name "-j" jobs "@install" ] 29 - [ "dune" "build" "-p" name "-j" jobs "runtest" ] {with-test & opam-version >= "2.2"} 30 - [ "dune" "build" "-p" name "-j" jobs "@doc" ] {with-doc} 31 - ] 32 - x-quality-build: "2026-04-15" 33 - x-quality-test: "2026-04-15"
-8
xdge/nox-xdge.opam.template
··· 1 - build: [ 2 - [ "dune" "subst" ] {dev} 3 - [ "dune" "build" "-p" name "-j" jobs "@install" ] 4 - [ "dune" "build" "-p" name "-j" jobs "runtest" ] {with-test & opam-version >= "2.2"} 5 - [ "dune" "build" "-p" name "-j" jobs "@doc" ] {with-doc} 6 - ] 7 - x-quality-build: "2026-04-15" 8 - x-quality-test: "2026-04-15"
xdge/test/cram/dune ocaml-xdg/test/eio/cram/dune
xdge/test/cram/helpers.sh ocaml-xdg/test/eio/cram/helpers.sh
-3
xdge/test/cram/helpers/dune
··· 1 - (executable 2 - (name xdg_example) 3 - (libraries nox-xdge eio_main cmdliner fmt))
+5 -5
xdge/test/cram/helpers/xdg_example.ml ocaml-xdg/test/eio/cram/helpers/xdg_example.ml
··· 6 6 let run (xdg, cfg) = 7 7 Fmt.pr "%a@.%a@.@.%a@.%a@." 8 8 Fmt.(styled `Bold string) 9 - "=== Cmdliner Config ===" Xdge.Cmd.pp cfg 9 + "=== Cmdliner Config ===" Xdg_eio.Cmd.pp cfg 10 10 Fmt.(styled `Bold string) 11 11 "=== XDG Directories ===" 12 - (Xdge.pp ~brief:false ~sources:true) 12 + (Xdg_eio.pp ~brief:false ~sources:true) 13 13 xdg 14 14 15 15 open Cmdliner ··· 24 24 [ 25 25 `S Manpage.s_description; 26 26 `P 27 - "This example shows how to use the Xdge library with Cmdliner to \ 27 + "This example shows how to use the Xdg_eio library with Cmdliner to \ 28 28 handle XDG Base Directory Specification paths with command-line and \ 29 29 environment variable overrides."; 30 30 `S Manpage.s_environment; 31 - `P (Xdge.Cmd.env_docs app_name); 31 + `P (Xdg_eio.Cmd.env_docs app_name); 32 32 ] 33 33 in 34 34 let info = Cmdliner.Cmd.info "xdg_example" ~version:"1.0" ~doc ~man in 35 35 Eio_main.run @@ fun env -> 36 - let create_xdg_term = Xdge.Cmd.term app_name env#fs () in 36 + let create_xdg_term = Xdg_eio.Cmd.term app_name env#fs () in 37 37 let main_term = Term.(const run $ create_xdg_term) in 38 38 let cmd = Cmdliner.Cmd.v info main_term in 39 39 exit @@ Cmdliner.Cmd.eval cmd
xdge/test/cram/helpers/xdg_example.mli ocaml-xdg/test/eio/cram/helpers/xdg_example.mli
xdge/test/cram/xdg.t/run.t ocaml-xdg/test/eio/cram/xdg.t/run.t
-3
xdge/test/dune
··· 1 - (test 2 - (name test) 3 - (libraries nox-xdge eio eio_main alcotest))
-6
xdge/test/test.ml
··· 1 - (*--------------------------------------------------------------------------- 2 - Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved. 3 - SPDX-License-Identifier: ISC 4 - ---------------------------------------------------------------------------*) 5 - 6 - let () = Alcotest.run "xdge" [ Test_xdge.suite ]
+12 -12
xdge/test/test_xdge.ml ocaml-xdg/test/eio/test_xdg_eio.ml
··· 5 5 6 6 let test_v () = 7 7 Eio_main.run @@ fun env -> 8 - let xdg = Xdge.v env#fs "test_app" in 9 - let name = Xdge.app_name xdg in 8 + let xdg = Xdg_eio.v env#fs "test_app" in 9 + let name = Xdg_eio.app_name xdg in 10 10 Alcotest.(check string) "app_name" "test_app" name 11 11 12 12 let test_dirs_exist () = 13 13 Eio_main.run @@ fun env -> 14 - let xdg = Xdge.v env#fs "test_dirs" in 15 - let config = Xdge.config_dir xdg in 16 - let data = Xdge.data_dir xdg in 17 - let cache = Xdge.cache_dir xdg in 18 - let state = Xdge.state_dir xdg in 14 + let xdg = Xdg_eio.v env#fs "test_dirs" in 15 + let config = Xdg_eio.config_dir xdg in 16 + let data = Xdg_eio.data_dir xdg in 17 + let cache = Xdg_eio.cache_dir xdg in 18 + let state = Xdg_eio.state_dir xdg in 19 19 let stat_ok p = 20 20 try 21 21 let _ = Eio.Path.stat ~follow:true p in ··· 29 29 30 30 let test_config_file_not_found () = 31 31 Eio_main.run @@ fun env -> 32 - let xdg = Xdge.v env#fs "test_search" in 33 - let result = Xdge.config_file xdg "nonexistent.conf" in 32 + let xdg = Xdg_eio.v env#fs "test_search" in 33 + let result = Xdg_eio.config_file xdg "nonexistent.conf" in 34 34 Alcotest.(check (option string)) 35 35 "not found" None 36 36 (Option.map Eio.Path.native_exn result) 37 37 38 38 let test_data_file_not_found () = 39 39 Eio_main.run @@ fun env -> 40 - let xdg = Xdge.v env#fs "test_search" in 41 - let result = Xdge.data_file xdg "nonexistent.dat" in 40 + let xdg = Xdg_eio.v env#fs "test_search" in 41 + let result = Xdg_eio.data_file xdg "nonexistent.dat" in 42 42 Alcotest.(check (option string)) 43 43 "not found" None 44 44 (Option.map Eio.Path.native_exn result) 45 45 46 46 let test_home_dir () = 47 47 Eio_main.run @@ fun env -> 48 - let home = Xdge.home_dir env#fs in 48 + let home = Xdg_eio.home_dir env#fs in 49 49 let path_str = Eio.Path.native_exn home in 50 50 Alcotest.(check bool) 51 51 "home_dir is absolute" true
+1 -1
xdge/test/test_xdge.mli ocaml-xdg/test/eio/test_xdg_eio.mli
··· 4 4 ---------------------------------------------------------------------------*) 5 5 6 6 val suite : string * unit Alcotest.test_case list 7 - (** [suite] is the Alcotest test suite for {!Xdge}. *) 7 + (** [suite] is the Alcotest test suite for {!Xdg_eio}. *)