XDG library path support for OCaml via Eio capabilities
linux macos ocaml xdg

Compare changes

Choose any two refs to compare.

+17 -1
.gitignore
···
-
_build
+
# OCaml build artifacts
+
_build/
+
*.install
+
*.merlin
+
+
# Third-party sources (fetch locally with opam source)
+
third_party/
+
+
# Editor and OS files
+
.DS_Store
+
*.swp
+
*~
+
.vscode/
+
.idea/
+
+
# Opam local switch
+
_opam/
+6 -1
.tangled/workflows/build.yml
···
- event: ["push", "pull_request"]
branch: ["main"]
+
engine: nixery
+
dependencies:
nixpkgs:
- shell
···
opam install . --confirm-level=unsafe-yes --deps-only
- name: build
command: |
-
opam exec -- dune build --verbose
+
opam exec -- dune build
+
- name: switch-test
+
command: |
+
opam install . --confirm-level=unsafe-yes --deps-only --with-test
- name: test
command: |
opam exec -- dune runtest --verbose
+10
CHANGES.md
···
+
v1.1.0 (dev)
+
------------
+
+
- Remove dependency on `eio_main` for library (@avsm).
+
Thanks to @Alizter for the workaround in https://github.com/ocaml/dune/issues/12821).
+
+
v1.0.0 (2025-11-29)
+
-------------------
+
+
- Initial public release (@avsm)
+75
README.md
···
+
# xdge - XDG Base Directory Specification for Eio
+
+
This library implements the [XDG Base Directory
+
Specification](https://specifications.freedesktop.org/basedir-spec/latest/) for
+
OCaml applications using the [Eio](https://github.com/ocaml-multicore/eio)
+
effects-based I/O library.
+
+
## What is XDG?
+
+
The XDG Base Directory Specification defines standard locations for user-specific files on Unix-like systems, keeping home directories clean and organized:
+
+
- Config (`~/.config/app`): User preferences and settings
+
- Data (`~/.local/share/app`): Persistent application data
+
- Cache (`~/.cache/app`): Non-essential cached data (safe to delete)
+
- State (`~/.local/state/app`): Logs, history, and runtime state
+
- Runtime (`$XDG_RUNTIME_DIR/app`): Sockets, pipes, and session-bound files
+
+
The specification also defines system-wide search paths (`/etc/xdg`,
+
`/usr/share`) and a precedence system using environment variables
+
(`XDG_CONFIG_HOME`, `XDG_DATA_HOME`, and so on).
+
+
## Why Eio?
+
+
Eio uses a **capability-based** approach to I/O where filesystem access must be
+
explicitly passed to functions. This design aligns naturally with XDG directory
+
management. For example:
+
+
```ocaml
+
(* Filesystem access is an explicit capability *)
+
let xdg = Xdge.create env#fs "myapp"
+
```
+
+
The capability model provides the benefit that code that needs filesystem
+
access must receive the `fs` capability, with no hidden global state or ambient
+
authority. The `Eio.Path.t` type returned by xdge encapsulates both the
+
filesystem capability and the path, preventing path traversal outside the
+
granted capability. Applications can restrict filesystem access by passing a
+
sandboxed `fs` capability, and xdge respects those boundaries.
+
+
## Usage
+
+
```ocaml
+
Eio_main.run @@ fun env ->
+
let xdg = Xdge.create env#fs "myapp" in
+
+
(* Access XDG directories as Eio paths *)
+
let config = Xdge.config_dir xdg in
+
let data = Xdge.data_dir xdg in
+
+
(* Search for files following XDG precedence *)
+
match Xdge.find_config_file xdg "settings.json" with
+
| Some path -> (* use path *)
+
| None -> (* use defaults *)
+
```
+
+
For CLI applications, xdge provides Cmdliner terms that handle environment
+
variable precedence and command-line overrides:
+
+
```ocaml
+
let () =
+
Eio_main.run @@ fun env ->
+
let term = Xdge.Cmd.term "myapp" env#fs () in
+
(* Generates --config-dir, --data-dir, etc. flags *)
+
(* Respects MYAPP_CONFIG_DIR > XDG_CONFIG_HOME > default *)
+
```
+
+
## Installation
+
+
```
+
opam install xdge
+
```
+
+
## License
+
+
ISC
+4
dune
···
+
(alias
+
(name default)
+
(deps
+
(alias_rec lib/all)))
+4 -3
dune-project
···
(authors "Anil Madhavapeddy")
(homepage "https://tangled.sh/@anil.recoil.org/xdge")
(maintainers "Anil Madhavapeddy <anil@recoil.org>")
-
(bug_reports "https://tangled.sh/@anil.recoil.org/xgde/issues")
+
(bug_reports "https://tangled.sh/@anil.recoil.org/xdge/issues")
(maintenance_intent "(latest)")
(package
···
(ocaml (>= 5.1.0))
(eio (>= 1.1))
(cmdliner (>= 1.2.0))
-
(fmt (>= 0.11.0))
+
fmt
+
xdg
+
(eio_main :with-test)
(odoc :with-doc)
-
(eio_main :with-test)
(alcotest (and :with-test (>= 1.7.0)))))
+1 -1
lib/dune
···
(library
(public_name xdge)
(name xdge)
-
(libraries eio eio_main xdg cmdliner fmt))
+
(libraries eio xdg unix cmdliner fmt))
+5
lib/xdge.ml
···
+
(*---------------------------------------------------------------------------
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
+
type source = Default | Env of string | Cmdline
type t = {
+5
lib/xdge.mli
···
+
(*---------------------------------------------------------------------------
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
+
(** XDG Base Directory Specification support with Eio capabilities
This library provides an OCaml implementation of the XDG Base Directory
+5
test/test_paths.ml
···
+
(*---------------------------------------------------------------------------
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
+
let test_path_validation () =
Printf.printf "Testing XDG path validation...\n";
(* Test absolute path validation for environment variables *)
+4
test/test_paths.mli
···
+
(*---------------------------------------------------------------------------
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
+5
test/xdg_example.ml
···
+
(*---------------------------------------------------------------------------
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
+
let run (xdg, cfg) =
Fmt.pr "%a@.%a@.@.%a@.%a@."
Fmt.(styled `Bold string)
+4
test/xdg_example.mli
···
+
(*---------------------------------------------------------------------------
+
Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
+
SPDX-License-Identifier: ISC
+
---------------------------------------------------------------------------*)
+8 -15
xdge.opam
···
authors: ["Anil Madhavapeddy"]
license: "ISC"
homepage: "https://tangled.sh/@anil.recoil.org/xdge"
-
bug-reports: "https://tangled.sh/@anil.recoil.org/xgde/issues"
+
bug-reports: "https://tangled.sh/@anil.recoil.org/xdge/issues"
depends: [
"dune" {>= "3.20"}
"ocaml" {>= "5.1.0"}
"eio" {>= "1.1"}
"cmdliner" {>= "1.2.0"}
"fmt" {>= "0.11.0"}
-
"odoc" {with-doc}
+
"xdg"
"eio_main" {with-test}
+
"odoc" {with-doc}
"alcotest" {with-test & >= "1.7.0"}
]
+
x-maintenance-intent: ["(latest)"]
build: [
-
["dune" "subst"] {dev}
-
[
-
"dune"
-
"build"
-
"-p"
-
name
-
"-j"
-
jobs
-
"@install"
-
"@runtest" {with-test}
-
"@doc" {with-doc}
-
]
+
[ "dune" "subst" ] {dev}
+
[ "dune" "build" "-p" name "-j" jobs "@install" ]
+
[ "dune" "build" "-p" name "-j" jobs "runtest" ] {with-test & opam-version >= "2.2"}
+
[ "dune" "build" "-p" name "-j" jobs "@doc" ] {with-doc}
]
-
x-maintenance-intent: ["(latest)"]
+6
xdge.opam.template
···
+
build: [
+
[ "dune" "subst" ] {dev}
+
[ "dune" "build" "-p" name "-j" jobs "@install" ]
+
[ "dune" "build" "-p" name "-j" jobs "runtest" ] {with-test & opam-version >= "2.2"}
+
[ "dune" "build" "-p" name "-j" jobs "@doc" ] {with-doc}
+
]