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.
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(())
}