Skip to main content

hyprshell_config_lib/io/
save.rs

1use anyhow::{Context, bail};
2use ron::Options;
3use ron::extensions::Extensions;
4use ron::ser::PrettyConfig;
5use std::ffi::OsStr;
6use std::fs::{File, create_dir_all};
7use std::io::Write;
8use std::path::Path;
9use tracing::{debug, info, instrument};
10
11const CONFIG_EXPLANATION: &str = "Edit with `hyprshell config edit`";
12
13#[instrument(skip(config, config_file), level = "debug", name = "write_config", fields(config_file = %config_file.display()))]
14pub fn write_io_config(
15    config_file: &Path,
16    config: &crate::io::Config,
17    override_file: bool,
18) -> anyhow::Result<()> {
19    let config_file_display = config_file.display();
20    if config_file.exists() && !override_file {
21        bail!(
22            "Config file at {config_file_display} already exists, delete it before generating a new one or use -f to override"
23        );
24    }
25    if let Some(parent) = config_file.parent() {
26        create_dir_all(parent)
27            .with_context(|| format!("Failed to create config dir at ({})", parent.display()))?;
28    }
29    let str = match config_file.extension().and_then(OsStr::to_str) {
30        None | Some("ron") => Options::default()
31            .with_default_extension(Extensions::IMPLICIT_SOME)
32            .with_default_extension(Extensions::UNWRAP_NEWTYPES)
33            .with_default_extension(Extensions::UNWRAP_VARIANT_NEWTYPES)
34            .to_string_pretty(&config, PrettyConfig::default())
35            .with_context(|| format!("Failed to write RON config to ({config_file_display})")),
36        Some("json5" | "json") => {
37            serde_json::to_string_pretty(&config).context("Failed to generate JSON config")
38        }
39        Some("toml") => toml::to_string_pretty(&config).context("Failed to generate TOML config"),
40        Some(ext) => bail!(
41            "Invalid config file extension: {ext} (run with -vv and check `FEATURES: ` debug log to see enabled extensions)"
42        ),
43    }?;
44    #[allow(clippy::match_same_arms)]
45    let header = match config_file.extension().and_then(OsStr::to_str) {
46        None | Some("ron") => format!("// {CONFIG_EXPLANATION}"),
47        Some("json5") => format!("// {CONFIG_EXPLANATION}"),
48        Some("toml") => format!("# {CONFIG_EXPLANATION}"),
49        _ => String::new(),
50    };
51    let content = format!("{header}\n{str}");
52    let mut file = File::create(config_file)
53        .with_context(|| format!("Failed to create config file at ({config_file_display})"))?;
54    file.write_all(content.as_bytes())
55        .with_context(|| format!("Failed to write to config file at ({config_file_display})"))
56        .inspect_err(|_| {
57            info!("New config contents: {config:?}");
58        })?;
59
60    debug!("Config file written successfully at {config_file_display}");
61    Ok(())
62}
63
64pub fn write_config(
65    config_file: &Path,
66    config: &crate::Config,
67    override_file: bool,
68) -> anyhow::Result<()> {
69    let config = crate::io::Config::from(config.clone());
70    write_io_config(config_file, &config, override_file)
71}