I found myself needing to use placeholders in my configuration file, but config doesn't support them.
I found an open ticket about it and a draft PR, so I decided to write a small library (config-shellexpand) that implements it by combining the file sources from config with shellexpand.
config.toml
value = ${NUMBER_FROM_ENV}
[section]
name = "${NAME_FROM_ENV}"
main.rs
use config_shellexpand::TemplatedFile;
use config::Config;
let config: Config = Config::builder()
.add_source(TemplatedFile::with_name(path))
.build();
When loading, the contents of the files are read into memory, then expanded with shellexpand, and finally loaded using config's FileFormat, like non-expanded files.
You can optionally provide a Context (with_name_and_context) that is passed on to shellexpand for variable lookups if you want to source them from somewhere other than the environment (the tests use this a lot).
It also works with strings if you provide the file format (just like it works in config).
The project is minutes older than the original post here, and if I understand the comment about "combining the sources" plus CLAUDE.toml right, it's a project where AI copied the source code of two projects together.
Why did you do that instead of contributing upstream?
It's quite obvious that you didn't read the actual code
It is not a copy of any source code (it depends on both config-rs and shellexpand).
The project wraps FileSource from config-rs and applies shell expansion from shellexpand before handing the contents over to config-rs' FileFormat to extract the value map.
I didn't contribute it back to config-rs because I needed the functionality yesterday and I'm not sure they want it. I've offered to merge it behind a feature if they're interested.
Use of Claude doesn't imply slop or copying. Most of the code is hand-written, but I have a Claude subscription, so I'd be dumb not to use it when it's appropriate.
I don't know where you get your timings from, but the implementation, testing and documentation took me about 3 hours. So it definitely pre-dates the post by more than minutes.
I rechecked them, and I'm guessing I was at a boundary of "5h ago" here and on codeberg was actually separated by 2h, that's def on me, sorry.
The reason I am overly careful about such announcements is that we have around 2 per day that are slop and that unambiguously tick the "code is a few hours old and gets an announcement" box. There's a reason I commented and didn't take any actions, I saw you were an actual human doing work.
Yes, it should solve that, however, you don't set the placeholders in the code - shellexpand fetches them from the environment automatically - with_name is called with the name of the configuration file (it mimics File::with_name from config-rs).
let config: Config = Config::builder()
.add_source(TemplatedFile::with_name("path/to/config.json"))
.build();