diff --git a/Cargo.lock b/Cargo.lock index 2ece897..3755bf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -875,18 +875,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "confy" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b1f4c00870f07dc34adcac82bb6a72cc5aabca8536ba1797e01df51d2ce9a0" -dependencies = [ - "directories", - "serde", - "thiserror", - "toml", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -1078,15 +1066,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "5.0.1" @@ -2975,6 +2954,17 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "smawk" version = "0.3.2" @@ -3056,7 +3046,6 @@ dependencies = [ "aws-smithy-types", "chrono", "clap", - "confy", "dirs", "humansize", "image", @@ -3068,13 +3057,16 @@ dependencies = [ "ratatui-image", "rstest", "serde", + "smart-default", "syntect", "textwrap", "tokio", + "toml", "tracing", "tracing-log", "tracing-subscriber", "tui-input", + "umbra", ] [[package]] @@ -3405,6 +3397,17 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "umbra" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ac88228f0bed8eb7d4cd07bb7ff186226f35ea5dd015675162fad47da74eae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-bidi" version = "0.3.15" diff --git a/Cargo.toml b/Cargo.toml index c761cae..6cb47a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ aws-sdk-s3 = "1.57.0" aws-smithy-types = "1.2.4" chrono = "0.4.38" clap = { version = "4.5.20", features = ["derive"] } -confy = "0.6.1" dirs = "5.0.1" humansize = "2.1.3" image = "0.25.4" @@ -34,15 +33,18 @@ open = "5.3.0" ratatui = { version = "0.28.1", features = ["unstable-widget-ref"] } ratatui-image = "2.0.1" serde = { version = "1.0.210", features = ["derive"] } +smart-default = "0.7.1" syntect = { version = "5.2.0", default-features = false, features = [ "default-fancy", ] } textwrap = "0.16.1" tokio = { version = "1.40.0", features = ["full"] } +toml = "0.8.19" tracing = "0.1.40" tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.18", features = ["chrono"] } tui-input = "0.10.1" +umbra = "0.1.0" [dev-dependencies] rstest = "0.23.0" diff --git a/README.md b/README.md index 84e974b..07e3c78 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,9 @@ Detailed operations on each view can be displayed by pressing `?` key. Config is loaded from `$STU_ROOT_DIR/config.toml`. - If `STU_ROOT_DIR` environment variable is not set, `~/.stu` is used by default. -- If the file does not exist, it will be created automatically at startup. -- If no value is set, the default value will be set. + - If the `STU_ROOT_DIR` directory does not exist, it will be created automatically. +- If the config file does not exist, the default values will be used for all items. +- If the config file exists but some items are not set, the default values will be used for those unset items. #### Config file format diff --git a/src/config.rs b/src/config.rs index 60d7d15..046c0d1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,9 @@ use std::{env, path::PathBuf}; use anyhow::Context; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; +use smart_default::SmartDefault; +use umbra::optional; const STU_ROOT_DIR_ENV_VAR: &str = "STU_ROOT_DIR"; @@ -14,90 +16,53 @@ const PREVIEW_THEME_DIR: &str = "preview_theme"; const PREVIEW_SYNTAX_DIR: &str = "preview_syntax"; const CACHE_FILE_NAME: &str = "cache.txt"; -#[derive(Serialize, Deserialize, Debug, Clone)] +#[optional(derives = ["Deserialize"])] +#[derive(Debug, Clone, SmartDefault)] pub struct Config { - #[serde(default = "default_download_dir")] + #[default(_code = "default_download_dir()")] pub download_dir: String, - #[serde(default = "default_default_region")] + #[default = "us-east-1"] pub default_region: String, - #[serde(default)] + #[nested] pub ui: UiConfig, - #[serde(default)] + #[nested] pub preview: PreviewConfig, } -#[derive(Serialize, Deserialize, Debug, Default, Clone)] +#[optional(derives = ["Deserialize"])] +#[derive(Debug, Clone, SmartDefault)] pub struct UiConfig { - #[serde(default)] + #[nested] pub object_list: UiObjectListConfig, - #[serde(default)] + #[nested] pub object_detail: UiObjectDetailConfig, } -#[derive(Serialize, Deserialize, Debug, Clone)] +#[optional(derives = ["Deserialize"])] +#[derive(Debug, Clone, SmartDefault)] pub struct UiObjectListConfig { - #[serde(default = "default_ui_object_list_date_format")] + #[default = "%Y-%m-%d %H:%M:%S"] pub date_format: String, - #[serde(default = "default_ui_object_list_date_width")] + #[default = 19] // // "2021-01-01 12:34:56".len() pub date_width: usize, } -impl Default for UiObjectListConfig { - fn default() -> Self { - Self { - date_format: default_ui_object_list_date_format(), - date_width: default_ui_object_list_date_width(), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] +#[optional(derives = ["Deserialize"])] +#[derive(Debug, Clone, SmartDefault)] pub struct UiObjectDetailConfig { - #[serde(default = "default_ui_object_detail_date_format")] + #[default = "%Y-%m-%d %H:%M:%S"] pub date_format: String, } -impl Default for UiObjectDetailConfig { - fn default() -> Self { - Self { - date_format: default_ui_object_detail_date_format(), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] +#[optional(derives = ["Deserialize"])] +#[derive(Debug, Clone, SmartDefault)] pub struct PreviewConfig { - #[serde(default)] pub highlight: bool, - #[serde(default = "default_preview_highlight_theme")] + #[default = "base16-ocean.dark"] pub highlight_theme: String, - #[serde(default)] pub image: bool, } -impl Default for PreviewConfig { - fn default() -> Self { - Self { - highlight: false, - highlight_theme: default_preview_highlight_theme(), - image: false, - } - } -} - -impl Default for Config { - fn default() -> Self { - let download_dir = default_download_dir(); - let default_region = default_default_region(); - Self { - download_dir, - default_region, - ui: UiConfig::default(), - preview: PreviewConfig::default(), - } - } -} - fn default_download_dir() -> String { match Config::get_app_base_dir() { Ok(dir) => { @@ -108,31 +73,20 @@ fn default_download_dir() -> String { } } -fn default_default_region() -> String { - "us-east-1".to_string() -} - -fn default_ui_object_list_date_format() -> String { - "%Y-%m-%d %H:%M:%S".to_string() -} - -fn default_ui_object_list_date_width() -> usize { - 19 // "2021-01-01 12:34:56".len() -} - -fn default_ui_object_detail_date_format() -> String { - "%Y-%m-%d %H:%M:%S".to_string() -} - -fn default_preview_highlight_theme() -> String { - "base16-ocean.dark".to_string() -} - impl Config { pub fn load() -> anyhow::Result { let dir = Config::get_app_base_dir()?; + if !dir.exists() { + std::fs::create_dir_all(&dir)?; + } let path = dir.join(CONFIG_FILE_NAME); - confy::load_path(path).context("Failed to load config file") + if path.exists() { + let content = std::fs::read_to_string(path)?; + let config: OptionalConfig = toml::from_str(&content)?; + Ok(config.into()) + } else { + Ok(Config::default()) + } } pub fn download_file_path(&self, name: &str) -> PathBuf {