config-rs icon indicating copy to clipboard operation
config-rs copied to clipboard

How to write the config back to the config file?

Open MTRNord opened this issue 7 years ago • 19 comments

Hi I use set_default() to set default values. How can I write these back to the config file?

MTRNord avatar May 23 '17 16:05 MTRNord

This is not a feature I'm looking at implementing in the near future but I'm definitely open to the idea. With the merging it'd require a bit of thinking on the implementation but it should be do-able.

mehcode avatar May 30 '17 23:05 mehcode

Looking back at this, I'm thinking we could implement something that would work for enough use cases to be useful.

I looked around and it seems there are two common use cases for serializing configuration:

  • Generating a "default" configuration file.
  • Reading a file, editing it, and saving those edits.

The first is significantly easier than the second. We could offer something like (to hopefully tighten up your code, here):

config.set_default("ip", "0.0.0.0");
config.set_default("db.ip", "127.0.0.1");
config.set_default("username", "root");
config.set_default("password", "");

// Write defaults to file if the file doesn't exist
if let Ok(mut f) = OpenOptions::new().write(true).create_new(true).open(&config_dir) {
    config.serialize_defaults_to(FileFormat::Json, f)?;
}

Would the above work for you @MTRNord ?


I could further envision a .serialize method that serializes the current configuration stack. It'd be quite useful for debugging the library too.

This is probably enough to solve the second use case for some people but it's really not good enough. I'd love to hear some thoughts on API design if someone is interested in exploring this space. Below is a rough spike on what could possibly work (if you wrap the child configurations in RwLock).

let mut default_config = Config::new();
default_config.merge(File::with_name("default.json"));

let mut user_config = Config::new();
user_config.merge(File::with_name("user.json"));

let mut config = Config::new();
config.merge(default_config); 
config.merge(user_config);

user_config.set("update", 2); // updates user config which updates the config
user_config.serialize( ... ); // serialize just user.json plus changes

Another issue with the second use case is preserving file format. The main issue here is format libraries just don't seem to be able to do this at this time.

mehcode avatar Aug 09 '17 08:08 mehcode

It wouldn't entirely solve this, but adding #[derive(Serialize)] to config::Value would make it easier for users to roll their own solutions for writing default config back out to a file.

wbthomason avatar Jan 20 '18 06:01 wbthomason

Another question to consider is whether writing back would preserve formatting like indentation, line breaks and comments or whether it would just rewrite a new file with the given values

eberkund avatar Feb 24 '18 20:02 eberkund

Take a look at https://crates.io/crates/trope

psox avatar Aug 21 '18 21:08 psox

There are two main use cases at play:

  • Write back changes to a loaded config file. Think Atom/VS Code. App loads from multiple sources and writes back only dynamic ( config.set ) changes to a single file.

  • Write the entire config tree. Would be useful in trope and for debugging.

I'm planning on supporting both of these cases soon.

mehcode avatar Oct 05 '18 14:10 mehcode

~Lost 2 hours trying to figure this out and what am I doing wrong~, maybe mention in the docs that this is not possible?

kunicmarko20 avatar Jan 17 '19 22:01 kunicmarko20

Apologies for the time lost. What specific use case were you wanting to do?

mehcode avatar Jan 17 '19 23:01 mehcode

No problem, now that I think about it, it sounded a bit rude, it's a great crate and I am using it, just expected since you can read from a file and update values you would be able to write to a file.

kunicmarko20 avatar Jan 17 '19 23:01 kunicmarko20

I'm also interested in this. I currently have a single config object that is loaded from multiple files (user config and an application generated config - these are non-overlapping Json files). It would be really cool if the library had a way to track/define which config keys were loaded from each file and could save updates to the respective files.

That said, this would be trivial to refactor into separate config objects - and it probably makes sense to do so. In that use case, very simple save-all functionality would be sufficient.

GeorgeHahn avatar Jan 18 '19 22:01 GeorgeHahn

A dirty and easy solution I wrote as a test:

use std::fs::File;
use std::path::Path;
use std::io::Write;
use std::fs::OpenOptions;
use config::*;

    pub fn save(&mut self) {
        let path = Path::new("Config.toml");

        if let Ok(mut file) = OpenOptions::new().write(true).open(path) {
            file.set_len(0).unwrap();
            for (key, value) in self.config.collect().unwrap() {
                file.write_all(
                    format!(
                        "{}=\"{}\"\n",
                        key,
                        value.into_str().unwrap()
                    ).as_bytes()
                ).unwrap();
            }
        }
    }

I created a wrapper struct around the config and added this method. I know this is a bad solution for a library, but in case someone comes searching for it.

kunicmarko20 avatar Jan 19 '19 00:01 kunicmarko20

Any update ?

patrickelectric avatar May 28 '20 20:05 patrickelectric

Bumping, as this I believe should be available as a feature in the crate.

Blakeinstein avatar Jul 10 '21 22:07 Blakeinstein

A method I used, only catch is that is strips out comments from the file. Just had to import the toml crate toml = "0.5.9"

impl Settings {
    pub fn new() -> Result<Self, ConfigError> {
        let mut path = env::current_dir().unwrap();
        path.push(SETTINGS_FILE);
        
        let s = Config::builder()
            .add_source(File::with_name(SETTINGS_FILE))
            .build()?;
        s.try_deserialize()
    }
    
    pub fn save(&self) {
        let mut path = env::current_dir().unwrap();
        path.push(SETTINGS_FILE);

        let toml_string = toml::to_string(&self).expect("Could not encode settings.toml file!");
        fs::write(path, toml_string).expect("Could not write to settings.toml file!");
    }
}

joffreybesos avatar Oct 30 '22 14:10 joffreybesos

Actually kinda too bad this feature is missing from config :( Anyway, keep up the good work around here ! This crate is awesome

Quessou avatar Jul 17 '23 19:07 Quessou

@mehcode Hey, writing back to the file still not possible, right?

uwejan avatar Nov 15 '23 16:11 uwejan

They do not maintain this package, I do :smile: No, writing back is still not possible, sorry.

matthiasbeyer avatar Nov 15 '23 18:11 matthiasbeyer

Oh ok my bad. This would make a hufe diffrence though. I see you are planning on a redesign of the crate, i think having this feature is crutial at some point. Thank you.

uwejan avatar Nov 15 '23 18:11 uwejan

Yes, the progress on this effort is rather stalled though. The more contributors, the better, you know? While at it, have a look at the "figment" crate. Not sure whether it does writing, but might be worth a look!

matthiasbeyer avatar Nov 15 '23 18:11 matthiasbeyer