use std::{fs, path::PathBuf};
const DEFAULT_TAB_WIDTH: usize = 4;
const MAX_TAB_WIDTH: usize = 16;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Options {
pub wrap: bool,
pub tab_width: usize,
}
impl Default for Options {
fn default() -> Self {
Self {
wrap: true,
tab_width: DEFAULT_TAB_WIDTH,
}
}
}
impl std::fmt::Display for Options {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(formatter, "wrap {}", self.wrap)?;
writeln!(formatter, "tab-width {}", self.tab_width)
}
}
impl Options {
#[must_use]
pub fn load() -> Self {
let text = Self::path()
.and_then(|path| fs::read_to_string(path).ok())
.unwrap_or_default();
Self::parse(&text)
}
pub fn save(&self) -> std::io::Result<()> {
let Some(path) = Self::path() else {
return Err(std::io::Error::other("no config directory"));
};
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, self.to_string())
}
#[must_use]
pub fn path() -> Option<PathBuf> {
let base = std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|path| path.is_absolute())
.or_else(|| std::env::var_os("HOME").map(|home| PathBuf::from(home).join(".config")))?;
Some(base.join("idet").join("options.conf"))
}
#[must_use]
pub fn indent(&self) -> String {
" ".repeat(self.tab_width)
}
fn parse(text: &str) -> Self {
let mut options = Self::default();
for line in text.lines() {
let mut words = line.split_whitespace();
match (words.next(), words.next()) {
(Some("wrap"), Some(value)) => options.wrap = value == "true",
(Some("tab-width"), Some(value)) => {
if let Ok(width) = value.parse::<usize>() {
options.tab_width = width.clamp(1, MAX_TAB_WIDTH);
}
}
_ => {}
}
}
options
}
}
#[cfg(test)]
mod tests {
use super::Options;
#[test]
fn what_is_written_is_what_is_read_back() {
let options = Options {
wrap: false,
tab_width: 2,
};
assert_eq!(Options::parse(&options.to_string()), options);
}
#[test]
fn an_empty_or_broken_file_leaves_the_defaults_standing() {
assert_eq!(Options::parse(""), Options::default());
assert_eq!(
Options::parse("tab-width wide\nnonsense\n"),
Options::default()
);
}
#[test]
fn an_unusable_tab_width_is_pulled_into_range() {
assert_eq!(Options::parse("tab-width 0").tab_width, 1);
assert_eq!(Options::parse("tab-width 99").tab_width, 16);
}
}