#cli #search #utility #xdg


Easily find config files and directories for your CLI application

1 unstable release

0.1.2 Nov 6, 2022
0.1.1 Nov 6, 2022
0.1.0 Nov 6, 2022

#228 in Configuration


222 lines

Config finder

Automatically search for <dirs>/.config/my-app for your application, including local-only files.

See [ConfigDirs] for the entry point.

The problem

Following the "show the door before showing the key" adage, following is an explanation of why this crate exists.

Our imaginary CLI application, let's name it "Pear", transparently connects directories from our local machine to a remote server, keeping them in sync.

It has a global config, applied to all connections, but also needs directory-specific ones, for example dir-perso points to server-perso and dir-work points to server-work. The company uses a default configuration to point to the work server and developers are expected to use their own credentials to connect.

Because "Pear" is a nice app following good CLI practices, it puts repository-scoped config in repo/.config/pear/... files.

So the structure is as follow:

|- .config/ (or where $XDG_CONFIG_HOME is pointing to)
|  +- pear/
|     +- config.kdl
|- perso/project-A
|  |- .config/
|  |  +- pear/
|  |     +- config.kdl
|  |- Cargo.toml
|  +- src/..
+- work/
   |- .config/
   |  +- pear/
   |     |- config.kdl
   |     +- config.local.kdl
   |- Cargo.toml
   +- src/..

How do we find all the config files nicely ?

There is $XDG_CONFIG_HOME, $HOME/.config if the former is unset, .config dirs in various repos, config.local.kdl vs config.local, it's hard to find everything and not forget one.

The solution

Obviously, you're on the documentation for a config-finder crate, so it's the (one) solution.

Here's an example for the work config:

# fn main() { wrapped(); }
# fn wrapped() -> Option<()> {
use std::path::Path;

use config_finder::ConfigDirs;

std::env::set_var("XDG_CONFIG_HOME", "/configs/user-1");

let mut cd = ConfigDirs::empty();
let mut files = cd.add_path("~/work") // `.config` is automatically added
                  .add_platform_config_dir() // `.config` is not added for this
                  // Takes a reference to the original `ConfigDirs` so you can create
                  // multiple iterators to search for multiple files or directories
                  .search("pear", "config", "kdl");

let with_local = files.next()?;
assert_eq!(with_local.path(), Path::new("~/work/.config/pear/config.kdl"));
assert_eq!(with_local.local_path(), Path::new("~/work/.config/pear/config.local.kdl"));

let with_local = files.next()?;
assert_eq!(with_local.path(), Path::new("/configs/user-1/pear/config.kdl"));
assert_eq!(with_local.local_path(), Path::new("/configs/user-1/pear/config.local.kdl"));

assert_eq!(files.next(), None);
# Some(()) }

Further details

What your app does next with the local and normal form of the config files and directories is entirely up to you. You can merge local and normal config, entirely ignore the normal one if a local one is present, only accept non-local configs, it's your choice.

Absolutely no checking is done in the given paths since this library cannot know about the shape of your system nor the requirements of your application.

Canonicalization of paths is not done either since it requires filesystem access. If you want to use only canonicalized paths, wrap the types exposed by this library.


Current MSRV is Rust 1.56.0. I don't expect this to change much over time, but if need be, it will be done in a minor version (at least) and will not move further than 3 versions back (e.g., if current Rust version is 1.65, it will not jump to later than 1.62).