diff --git a/Cargo.lock b/Cargo.lock index c1fd4e0..1823791 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,7 @@ dependencies = [ "anyhow", "avt", "clap 3.2.23", + "dirs 6.0.0", "env_logger", "fontdb", "fontdue", @@ -43,6 +44,7 @@ dependencies = [ "serde_json", "shellexpand", "tiny-skia", + "toml", "usvg", ] @@ -263,7 +265,7 @@ dependencies = [ "bitflags 1.3.2", "clap_derive", "clap_lex 0.2.4", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim", "termcolor", @@ -431,7 +433,16 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dece029acd3353e3a58ac2e3eb3c8d6c35827a892edc6cc4138ef9c33df46ecd" dependencies = [ - "dirs-sys", + "dirs-sys 0.4.0", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", ] [[package]] @@ -441,10 +452,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04414300db88f70d74c5ff54e50f9e1d1737d9a5b90f53fcf2e95ca2a9ab554b" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.3", "windows-sys 0.45.0", ] +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.59.0", +] + [[package]] name = "dunce" version = "1.0.3" @@ -470,6 +493,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.0" @@ -729,6 +758,12 @@ dependencies = [ "ahash 0.8.3", ] +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + [[package]] name = "heck" version = "0.4.1" @@ -918,6 +953,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", +] + [[package]] name = "io-lifetimes" version = "1.0.9" @@ -984,6 +1029,15 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.3.0" @@ -1141,6 +1195,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_str_bytes" version = "6.5.0" @@ -1255,7 +1315,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 1.0.40", "tokio", "tracing", ] @@ -1272,7 +1332,7 @@ dependencies = [ "rustc-hash", "rustls", "slab", - "thiserror", + "thiserror 1.0.40", "tinyvec", "tracing", ] @@ -1368,7 +1428,18 @@ checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall", - "thiserror", + "thiserror 1.0.40", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom", + "libredox", + "thiserror 2.0.18", ] [[package]] @@ -1657,7 +1728,7 @@ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] @@ -1671,6 +1742,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1689,7 +1769,7 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" dependencies = [ - "dirs", + "dirs 5.0.0", ] [[package]] @@ -1797,9 +1877,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", @@ -1836,7 +1916,16 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.40", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", ] [[package]] @@ -1847,7 +1936,18 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", ] [[package]] @@ -1940,6 +2040,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower-service" version = "0.3.2" @@ -2138,7 +2279,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-shared", ] @@ -2172,7 +2313,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2483,6 +2624,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + [[package]] name = "xmlparser" version = "0.13.5" @@ -2513,7 +2663,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.87", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ee96f50..4b6ea0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,3 +29,5 @@ serde_json = "1.0.81" shellexpand = "3.1.0" tiny-skia = "0.11.4" usvg = "0.45.1" +toml = "0.8" +dirs = "6" diff --git a/src/lib.rs b/src/lib.rs index 4b74472..7509c64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -63,32 +63,58 @@ impl Default for Config { } } -#[derive(Clone, ArgEnum, Default)] +#[derive(serde::Deserialize, Clone, ArgEnum, Default)] pub enum Renderer { #[default] Resvg, Fontdue, } -#[derive(Clone, Debug, ArgEnum, Default)] +#[derive(serde::Deserialize, Clone, Debug, ArgEnum, Default)] pub enum Theme { + #[serde(rename = "asciinema")] Asciinema, + #[default] + #[serde(rename = "dracula")] Dracula, + + #[serde(rename = "github-dark")] GithubDark, + + #[serde(rename = "github-light")] GithubLight, + + #[serde(rename = "kanagawa")] Kanagawa, + + #[serde(rename = "kanagawa-dragon")] KanagawaDragon, + + #[serde(rename = "kanagawa-light")] KanagawaLight, + + #[serde(rename = "monokai")] Monokai, + + #[serde(rename = "nord")] Nord, + + #[serde(rename = "solarized-dark")] SolarizedDark, + + #[serde(rename = "solarized-light")] SolarizedLight, + + #[serde(rename = "gruvbox-dark")] GruvboxDark, #[clap(skip)] + #[serde(rename = "custom")] Custom(String), + #[clap(skip)] + #[serde(skip_deserializing)] Embedded(theme::Theme), } diff --git a/src/main.rs b/src/main.rs index e225ac3..1c50f71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use clap::{ArgAction, ArgEnum, Parser}; +use clap::{ArgAction, ArgEnum, CommandFactory, FromArgMatches, Parser}; use reqwest::header; use std::io; use std::{fs::File, io::BufReader, iter}; @@ -53,6 +53,13 @@ struct Cli { /// GIF path/filename output_filename: String, + /// Path to TOML config file + #[clap( + long, + help = "Path to TOML config file (default: search for agg.toml in config directory)" + )] + config: Option, + /// Select frame rendering backend #[clap(long, arg_enum, default_value_t = agg::Renderer::default())] renderer: agg::Renderer, @@ -114,6 +121,87 @@ struct Cli { quiet: bool, } +#[derive(serde::Deserialize, Default)] +#[serde(deny_unknown_fields, rename_all = "kebab-case")] +struct TomlConfig { + renderer: Option, + font_family: Option, + font_size: Option, + font_dirs: Option>, + line_height: Option, + theme: Option, + speed: Option, + no_loop: Option, + idle_time_limit: Option, + fps_cap: Option, + last_frame_duration: Option, + cols: Option, + rows: Option, + show_progress_bar: Option, +} + +fn apply_toml_config(cfg: &mut agg::Config, tml: TomlConfig, matches: clap::ArgMatches) { + let is_explicit = + move |id: &str| matches.value_source(id) == Some(clap::parser::ValueSource::CommandLine); + + macro_rules! apply { + (opt, $field:ident) => { + if !is_explicit(stringify!($field)) { + if let Some(v) = tml.$field { + cfg.$field = Some(v); + } + } + }; + + (opt, $field:ident, $id:expr) => { + if !is_explicit($id) { + if let Some(v) = tml.$field { + cfg.$field = Some(v); + } + } + }; + + (not, $field:ident, $id:expr) => { + if !is_explicit($id) { + if let Some(v) = tml.$field { + cfg.$field = !v; + } + } + }; + + ($field:ident) => { + if !is_explicit(stringify!($field)) { + if let Some(v) = tml.$field { + cfg.$field = v; + } + } + }; + + ($field:ident, $id:expr) => { + if !is_explicit($id) { + if let Some(v) = tml.$field { + cfg.$field = v; + } + } + }; + } + + apply!(renderer); + apply!(font_family, "font-family"); + apply!(font_size, "font-size"); + apply!(font_dirs, "font-dir"); + apply!(line_height, "line-height"); + apply!(opt, theme); + apply!(speed); + apply!(no_loop, "no-loop"); + apply!(opt, idle_time_limit, "idle-time-limit"); + apply!(fps_cap, "fps-cap"); + apply!(last_frame_duration, "last-frame-duration"); + apply!(opt, cols); + apply!(opt, rows); + apply!(not, show_progress_bar, "quiet"); +} + fn download(url: &str) -> Result { let client = reqwest::blocking::Client::builder() .user_agent(USER_AGENT) @@ -159,7 +247,8 @@ fn reader(path: &str) -> Result> { } fn main() -> Result<()> { - let cli = Cli::parse(); + let matches = Cli::command().get_matches(); + let cli = Cli::from_arg_matches(&matches).unwrap(); let log_level = match cli.verbose { 0 => "error", @@ -172,7 +261,7 @@ fn main() -> Result<()> { .format_timestamp(None) .init(); - let config = agg::Config { + let mut config = agg::Config { cols: cli.cols, font_dirs: cli.font_dir, font_family: cli.font_family, @@ -189,6 +278,23 @@ fn main() -> Result<()> { show_progress_bar: !cli.quiet, }; + let config_path = cli.config.clone().or_else(|| { + dirs::config_dir().and_then(|config_dir| { + let path = config_dir.join("agg.toml"); + if path.is_file() { + Some(path.to_string_lossy().to_string()) + } else { + None + } + }) + }); + + if let Some(path) = config_path { + let contents = std::fs::read_to_string(path)?; + let tml: TomlConfig = toml::from_str(&contents)?; + apply_toml_config(&mut config, tml, matches); + } + let input = BufReader::new(reader(&cli.input_filename_or_url)?); let mut output = File::create(&cli.output_filename)?;