Skip to content

Instantly share code, notes, and snippets.

@ptdecker
Last active October 17, 2024 18:56
Show Gist options
  • Save ptdecker/d80df0a5220178d2e7a0be3e8b563600 to your computer and use it in GitHub Desktop.
Save ptdecker/d80df0a5220178d2e7a0be3e8b563600 to your computer and use it in GitHub Desktop.
TOML Crate Demonstration
//! TOML Value Manipulation Demonstration
//!
//! This code is to supplement this excellent article:
//!
//! [Rust: Read, Write, and Modify .toml Files — Another way to handle User Set Environment Variables](https://medium.com/@itsuki.enjoy/rust-read-write-and-modify-toml-files-another-way-to-handle-user-set-environment-variables-27e1baf1a65f)
//!
//! This demo code assumes you have added additional entries to your ~/.cargo/config.toml file. Although, I used
//! `foo` and `bar` instead of the `password` and `username` that the article uses.
//!
//! Put this code into `main.rs`. And, here is the corresponding `Cargo.toml` needed for this demo.
//!
//! ```toml
//! [package]
//! name = "toml_demo"
//! version = "0.1.0"
//! edition = "2021"
//!
//! [dependencies]
//! toml = "0.8.19"
//! ```
//!
use std::{fs::{OpenOptions, File}, io::{Write, Read}, path::{Path, PathBuf}, str::FromStr};
use toml::{Table, Value};
/// Type alias for errors. Can evolved as the need for more sophisticated error handling progresses.
/// See Jeremy Chone's excellent YouTube tutorial:
///
/// [Rust Error Handling - Best Practices](https://www.youtube.com/watch?v=j-VQCYP7wyw)
type Error = Box<dyn std::error::Error + Send + Sync>;
/// Checks for the existence of a TOML config file and create it if it doesn't exist
fn init_toml(name: &str) -> Result<(), Error> {
let cargo_config_path = PathBuf::from_str(&format!("{}/{}", env!("CARGO_HOME"), name))?;
if !Path::new(&cargo_config_path).exists() {
println!("config.toml does not exist, creating it!");
let _ = File::create(cargo_config_path)?;
}
Ok(())
}
/// Reads a TOML file as a toml::Table
fn read_toml(name: &str) -> Result<Table, Error> {
let mut file = File::open(&format!("{}/{}", env!("CARGO_HOME"), name))?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents.parse::<Table>()?)
}
/// Reads a specific attribute value from a section of a TOML file and return it as a toml::Value
fn read_toml_value(name: &str, section: &str, attribute: &str) -> Result<Value, Error> {
read_toml(name)?
.get(section)
.ok_or_else(|| format!("Environment section {section} was not found").into())
.and_then(|env| {
env.as_table()
.ok_or_else(|| format!("Environment section {section} is not a table").into())
})
.and_then(|table| {
table
.get(attribute)
.ok_or_else(|| {
format!("Attribute {attribute} is missing from section {section}").into()
})
.cloned()
})
}
/// Write a value to a specific attribute in a specified section of a TOML file. If the section does
/// not already exist, it will be inserted into the TOML. If the value already exists in the section,
/// it will be updated. If it does not exist, it will be inserted. All other values in the section
/// are preserved.
fn write_toml_value(
name: &str,
section: &str,
attribute: &str,
value: &Value,
) -> Result<(), Error> {
let mut main_table = read_toml(name)?;
if let Value::Table(ref mut section) = main_table
.entry(section)
.or_insert_with(|| Value::Table(Table::new()))
{
section.entry(attribute).or_insert(value.clone());
}
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(&format!("{}/{}", env!("CARGO_HOME"), name))?
.write_all(&main_table.to_string().as_bytes())?;
Ok(())
}
fn main() -> Result<(), Error> {
// Demo reading from config.toml
println!("{}", read_toml("config.toml")?);
// Demo reading a specific value from config.toml
println!(
"foo: {}, bar: {}",
read_toml_value("config.toml", "env", "foo")?,
read_toml_value("config.toml", "env", "bar")?
);
// Demo creating a new TOML file if it doesn't exist
init_toml("config2.toml")?;
// Demo writing to the new TOML file
write_toml_value("config2.toml", "env", "baz", &"fizz".into())?;
// Demo reading from second demo TOML file
println!("{}", read_toml("config2.toml")?);
// Demo reading a specific value from second demo TOML file
println!("baz: {}", read_toml_value("config2.toml", "env", "baz")?,);
// Demo writing another value to the new TOML file
write_toml_value("config2.toml", "env", "bar", &"buzz".into())?;
// Demo reading from second demo TOML file
println!("{}", read_toml("config2.toml")?);
// Demo reading a specific value from second demo TOML file
println!("bar: {}", read_toml_value("config2.toml", "env", "bar")?,);
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment