Search code examples
linuxrustdirectoryconfigcreate-directory

Create config directory in linux with Rust


Started learning rust yesterday so im a complete newbie. I wanted to make a command line program for linux as my first project. I want it so when you run the program it checks if the config directory ~/.config/my_program/config.toml exists. If it does it loads the settings from the toml file (which I will probably make another post for), but if it doesn't exist it instead creates it and puts the default settings in the toml.

Here is what I have so far:

use toml;
use std::io::prelude::*;
use std::path::Path;

fn main() {
    let config_path = "~/.config/my_program/config.toml";
    
    //checks if the director exists
    let config_exists = Path::new(config_path).exists();
    println!("{}", config_exists);

    if !config_exists {
        let default_config = "place_holder";
        match std::fs::create_dir_all("~/.config/my_program"){ //If it doesn't exist it creates the directory
            Err(error) => panic!("Couldn't create config directory: {}", error),
            Ok(file) => file,
        };
        let mut config = match std::fs::File::create(config_path){ //Creates the file itself
            Err(error) => panic!("couldn't create config file: {}", error),
            Ok(file) => file,
        };
        match config.write_all(default_config.as_bytes()){ //Writes the default config to the file
            Err(error) => panic!("Couldn't write defualt config to file: {}", error),
            Ok(_) => println!("Config file created at: {}\nEdit it in order to change some setings.", config_path),
        };

    } 
    
}

But nothing seems to work nicely with ~ referencing the home directory.

Basically what I need here is to get the user who is running the program and just pass in the full directory: "/home/user/.config/my_program/config.toml".

I have tried it with users::get_current_username().unwrap() but that returns an 0sString which can't easily be combined with normal &str and passed in as arguments.

So either learn how to turn an 0sString in to a &str or just try a completely new approach.


Solution

  • Here is a small example in order to illustrate the idea. It just deals with a simple text file (no specific format).

    Many of the involved system calls may fail (no such file, permission denied...) thus we have to propagate the errors with ?.

    PathBuf is dedicated to multiple path operations; it is very convenient here. We try to keep using OsString/OsStr as much as possible for path manipulations because nothing guaranties that the underlying file-system/OS conforms to the utf-8 String/str Rust uses internally.

    type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
    
    fn load_config(
        user_relative_path: &str
    ) -> Result<(std::ffi::OsString, String)> {
        let home_dir = std::env::var_os("HOME").ok_or("no home directory")?;
        let mut config_path = std::path::PathBuf::new();
        config_path.push(home_dir);
        config_path.push(user_relative_path);
        let config_content = if let Ok(content) = std::fs::read(&config_path) {
            String::from_utf8_lossy(&content).to_string()
        } else {
            "This is the default content\n".to_owned()
        };
        Ok((config_path.into_os_string(), config_content))
    }
    
    fn save_config(
        config_path: &std::ffi::OsStr,
        config_content: &str,
    ) -> Result<()> {
        let dir_name = std::path::Path::new(&config_path)
            .parent()
            .ok_or("incorrect directory")?;
        std::fs::create_dir_all(dir_name)?;
        std::fs::write(&config_path, config_content)?;
        Ok(())
    }
    
    fn main() -> Result<()> {
        let (config_path, mut config_content) =
            load_config("config_test_dir/config.txt")?;
    
        config_content += &format!(
            "{} chars before, but here is something new.\n",
            config_content.len()
        );
    
        save_config(&config_path, &config_content)?;
    
        println!("config file saved to {:?}", config_path);
        Ok(())
    }