From a7bfb5b3c4e0acb2d2924f6a31be9e7ba9f45d75 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 09:42:43 +0200 Subject: [PATCH 01/14] chore: use more flexible types in get_config.rs --- src/get_config.rs | 48 +++++++++++++++++++++++++++++++---------------- src/main.rs | 6 +++--- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/get_config.rs b/src/get_config.rs index 7604238..52e98d3 100644 --- a/src/get_config.rs +++ b/src/get_config.rs @@ -1,4 +1,5 @@ use log::error; +use std::borrow::Cow; use toml::Value; /// Get a string value from the config @@ -13,25 +14,31 @@ use toml::Value; /// let host = get_config_str(&config, "server", "host", "127.0.0.1"); /// assert_eq!(host, "127.0.0.1"); /// ``` -pub fn get_config_str<'a>( +pub fn get_config_str<'a, D>( config: &'a Value, key: &str, second_key: &str, - default: &'a str, -) -> &'a str { + default: D, +) -> Cow<'a, str> +where + D: Into>, +{ match config.get(key).and_then(|k| k.get(second_key)) { - Some(v) => v.as_str().unwrap_or_else(|| { - panic!( - "Configuration error: Expected a string value for '{}.{}', but found a different type.", - key, second_key - ) - }), + Some(v) => v.as_str() + .map(Cow::from) + .unwrap_or_else(|| { + panic!( + "Configuration error: Expected a string value for '{}.{}', but found a different type.", + key, second_key + ) + }), None => { + let default_cow = default.into(); error!( "Configuration error: Unable to find the string value for '{}.{}'. Using default: '{}'.", - key, second_key, default + key, second_key, default_cow ); - default + default_cow } } } @@ -48,17 +55,26 @@ pub fn get_config_str<'a>( /// let port = get_config_int(&config, "server", "port", 8080); /// assert_eq!(port, 8080); /// ``` -pub fn get_config_int(config: &Value, key: &str, second_key: &str, default: i64) -> i64 { +pub fn get_config_int( + config: &Value, + key: impl AsRef, + second_key: impl AsRef, + default: impl Into, +) -> i64 { + let key_ref = key.as_ref(); + let second_key_ref = second_key.as_ref(); + let default_val = default.into(); + config - .get(key) - .and_then(|k| k.get(second_key)) + .get(key_ref) + .and_then(|k| k.get(second_key_ref)) .and_then(|v| v.as_integer()) .unwrap_or_else(|| { error!( "Configuration error: Unable to find the integer value for '{}.{}'. Using default: '{}'.", - key, second_key, default + key_ref, second_key_ref, default_val ); - default + default_val }) } diff --git a/src/main.rs b/src/main.rs index f7fd3f2..61376a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,12 +113,12 @@ fn main() -> Result<()> { let image = qrcode .render() .min_dimensions(size, size) - .dark_color(svg::Color(foreground)) - .light_color(svg::Color(background)) + .dark_color(svg::Color(&foreground)) + .light_color(svg::Color(&background)) .build(); info!("QR code rendered to image"); - save_image(&args.output, export_format, &image, size)?; + save_image(&args.output, &export_format, &image, size)?; info!("Image saved successfully to {:?}", args.output); Ok(()) From 45c6dc67374877a9be39be8633680f73b6e03b19 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 10:26:32 +0200 Subject: [PATCH 02/14] build: make github workflows test cross-platform --- .github/workflows/rust.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 31000a2..a291cd0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,13 +10,16 @@ env: CARGO_TERM_COLOR: always jobs: - build: - - runs-on: ubuntu-latest + build: + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose + From c65eaf7dc516184bba3005890970b0d046dfec3c Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 10:32:45 +0200 Subject: [PATCH 03/14] feat: enhance CLI with subcommands and configs This update introduces a modular CLI structure that allows users to generate QR codes, run Lua scripts, and save settings through subcommands. The configuration handling has been improved to support both file and stdin input, allowing users to use piping. Summary of changes: - Add support for reading configuration from stdin - Implement `async_save_image` for non-blocking image saving - Improve logging for better debugging and user feedback - Refactor CLI argument parsing to use subcommands - Enhance documentation for configuration functions - Remove deprecated test cases and clean up code --- src/get_config.rs | 25 ++--- src/image_ops.rs | 28 ++++-- src/main.rs | 244 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 210 insertions(+), 87 deletions(-) diff --git a/src/get_config.rs b/src/get_config.rs index 52e98d3..76bf560 100644 --- a/src/get_config.rs +++ b/src/get_config.rs @@ -2,9 +2,10 @@ use log::error; use std::borrow::Cow; use toml::Value; -/// Get a string value from the config -/// Returns the default if the value is not found -/// # Example: +/// Retrieve a string value from the configuration. +/// Returns the provided default if the value is not found. +/// +/// # Examples /// ```rust /// use ciphercanvas::get_config_str; /// let config = toml::from_str(r#" @@ -43,9 +44,10 @@ where } } -/// Get an integer value from the config -/// Returns the default if the value is not found -/// # Example: +/// Retrieve an integer value from the configuration. +/// Returns the provided default if the value is not found. +/// +/// # Examples /// ```rust /// use ciphercanvas::get_config_int; /// let config = toml::from_str(r#" @@ -172,15 +174,4 @@ mod tests { .unwrap(); get_config_str(&config, "server", "host", "localhost"); } - - #[test] - #[should_panic] - fn test_get_config_invalid_config() { - let _: toml::Value = toml::from_str( - r#" - i use neovim btw - "#, - ) - .unwrap(); - } } diff --git a/src/image_ops.rs b/src/image_ops.rs index 092bb0d..d6eadb2 100644 --- a/src/image_ops.rs +++ b/src/image_ops.rs @@ -9,12 +9,12 @@ use std::{ use tiny_skia::{Pixmap, Transform}; use usvg::{Options, Tree, fontdb}; +/// Load and render SVG content into a Pixmap of the specified size. fn load_svg(contents: &[u8], size: u32) -> Result { info!("Loading SVG content with size {}x{}", size, size); let options = Options::default(); let fontdb = fontdb::Database::new(); - let tree: Tree = Tree::from_data(contents, &options, &fontdb).with_context(|| { format!( "Failed to create SVG tree from data of size {}x{}", @@ -32,10 +32,13 @@ fn load_svg(contents: &[u8], size: u32) -> Result { Ok(pixmap) } -/// Save an image to a file -/// Fails when the format is not supported, or when saving fails -/// # Examples: -/// Save an SVG image +/// Save an image to a file. Supports both SVG and PNG output formats. +/// +/// When processing a PNG image, if the requested size is small (<256px), a warning is logged. +/// +/// # Usage Examples +/// +/// Save an SVG image: /// ```rust /// use ciphercanvas::save_image; /// let image = "..."; @@ -45,7 +48,7 @@ fn load_svg(contents: &[u8], size: u32) -> Result { /// save_image(&output, &format, &image, size).unwrap(); /// ``` /// -/// Save a PNG image +/// Save a PNG image: /// ```rust /// use ciphercanvas::save_image; /// let image = "..."; @@ -99,3 +102,16 @@ pub fn save_image(output: &PathBuf, format: &str, image: &str, size: u32) -> Res info!("Image saved successfully to {:?}", file_path); Ok(()) } + +/// An asynchronous wrapper for `save_image` for heavy I/O operations. +/// This function offloads blocking work to a separate thread using tokio's spawn_blocking. +pub async fn async_save_image( + output: PathBuf, + format: String, + image: String, + size: u32, +) -> Result<()> { + tokio::task::spawn_blocking(move || save_image(&output, &format, &image, size)) + .await + .expect("Task panicked") +} diff --git a/src/main.rs b/src/main.rs index 61376a8..b08fca2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ use anyhow::{Context, Result}; -use clap::{Parser, ValueEnum}; +use clap::{Parser, Subcommand, ValueEnum}; +use directories::ProjectDirs; use log::{info, warn}; use qrcode::{EcLevel, QrCode, render::svg}; use std::{ fmt, fs::File, - io::{BufReader, Read}, + io::{self, BufReader, Read}, path::PathBuf, }; @@ -17,29 +18,67 @@ use crate::{ image_ops::save_image, }; +/// Mature and modular CLI tool to generate QR codes and customize behavior via scripting. +/// +/// # Examples +/// +/// Generate a QR code with a custom configuration: +/// ```sh +/// ciphercanvas generate --ssid MyNetwork --output qrcode.svg --config ./config.toml +/// ``` +/// +/// Run a custom Lua script to alter tool behavior: +/// ```sh +/// ciphercanvas script --script ./customize.lua +/// ``` #[derive(Debug, Parser)] -struct Args { - #[arg(short, long, help = "The WiFi network's SSID (its name)")] - ssid: String, +#[command(author, version, about, long_about = None)] +struct CliArgs { + /// Activate verbose mode for detailed logs + #[arg(short, long)] + verbose: bool, + + /// Optional configuration file. If omitted, the default configuration directory is used. + #[arg(short, long, value_name = "FILE")] + config: Option, - #[arg( - short, - long, - default_value = "wpa", - help = "The encryption of the WiFi network." - )] - encryption: Encryption, + /// Specify subcommand to execute. + #[command(subcommand)] + command: Option, +} - #[arg(short, long, help = "The TOML configuration file.")] - config: PathBuf, +/// List of available subcommands. +#[derive(Debug, Subcommand)] +enum Commands { + /// Generate a QR code image from WiFi credentials. + Generate { + /// The WiFi network's SSID (name) + #[arg(short, long)] + ssid: Option, - #[arg(short, help = "The output file to export the QR code to.")] - output: PathBuf, + /// The encryption type used (WPA, WEP, or None). + #[arg(short, long, default_value = "wpa")] + encryption: Encryption, - #[arg(short, help = "Enable verbose mode and start logging")] - verbose: bool, + /// The output file to export the QR code image. + #[arg(short, long)] + output: Option, + }, + /// Run a Lua script to extend or customize the tool’s behavior. + Script { + /// Path to the Lua script. + #[arg(short, long)] + script: PathBuf, + }, + /// Save frequently used settings in the configuration store. + SaveSettings { + /// Settings in TOML format to save. + #[arg(short, long)] + settings: String, + }, } +/// Valid encryption types for WiFi. #[derive(ValueEnum, Clone, Debug)] enum Encryption { Wpa, @@ -67,77 +106,154 @@ impl Consts { const SIZE: u32 = 512; } -fn main() -> Result<()> { - let args = Args::parse(); +#[tokio::main] +async fn main() -> Result<()> { + let args = CliArgs::parse(); if args.verbose { simple_logger::init().unwrap(); - info!("Logger initialized"); + info!("Verbose logging enabled."); } - info!("Parsed arguments: {:#?}", args); - let config_str = read_config(&args.config)?; - info!("Configuration file loaded successfully"); + // Determine the configuration file: + let config_path = match &args.config { + Some(path) if path.to_string_lossy() != "-" => path.clone(), + _ => { + if let Some(proj_dirs) = ProjectDirs::from("org", "winlogon", "ciphercanvas") { + let default_path = proj_dirs.config_dir().join("config.toml"); + info!("Using default configuration file: {:?}", default_path); + default_path + } else { + anyhow::bail!( + "Unable to determine the default configuration directory. Specify a config file using --config." + ); + } + } + }; + + // Read configuration from file or stdin (if "-" is provided) + let config_str = if config_path.to_string_lossy() == "-" { + let mut input = String::new(); + io::stdin() + .read_to_string(&mut input) + .context("Failed to read configuration from stdin")?; + input + } else { + read_config(&config_path)? + }; + info!("Configuration loaded successfully."); let toml_config: toml::Value = toml::from_str(&config_str).context("Failed to parse the TOML configuration file")?; - info!("TOML configuration parsed successfully"); - - let export_format = get_config_str(&toml_config, "qrcode", "export", Consts::FORMAT); - let size = get_config_int(&toml_config, "qrcode", "size", Consts::SIZE as i64) as u32; - - if size < 256 { - warn!("The image size is lower than 256. The resulting QR code may look cropped."); - } + info!("TOML configuration parsed successfully."); - let foreground = get_config_str(&toml_config, "colors", "foreground", Consts::FOREGROUND); - let background = get_config_str(&toml_config, "colors", "background", Consts::BACKGROUND); + // Process the chosen subcommand + match args.command.unwrap_or(Commands::Generate { + ssid: None, + encryption: Encryption::Wpa, + output: None, + }) { + Commands::Generate { + ssid, + encryption, + output, + } => { + // Get configuration settings for QR code; if not set in TOML, use defaults. + let export_format = get_config_str(&toml_config, "qrcode", "export", Consts::FORMAT); + let size = get_config_int(&toml_config, "qrcode", "size", Consts::SIZE as i64) as u32; + if size < 256 { + warn!("Image size is lower than 256. The resulting QR code may appear cropped."); + } + let foreground = + get_config_str(&toml_config, "colors", "foreground", Consts::FOREGROUND); + let background = + get_config_str(&toml_config, "colors", "background", Consts::BACKGROUND); + let ssid = ssid.unwrap_or_else(|| { + toml_config + .get("wifi") + .and_then(|w| w.get("ssid")) + .and_then(|s| s.as_str()) + .unwrap_or("default_ssid") + .to_string() + }); - let password = toml_config["qrcode"]["password"] - .as_str() - .context("Failed to get the password from the configuration file")?; - info!("Password retrieved from the configuration file"); + let password = toml_config + .get("qrcode") + .and_then(|q| q.get("password")) + .and_then(|p| p.as_str()) + .context("Failed to retrieve the QR code password from configuration. Please check your config file and ensure `[qrcode] password = \"...\"` is present.")?; + info!("Password retrieved from configuration."); - let contents_to_encode = format!( - "WIFI:S:{};T:{};P:{};;", - args.ssid, - args.encryption.to_string().to_uppercase(), - password - ); + let contents_to_encode = format!( + "WIFI:S:{};T:{};P:{};;", + ssid, + encryption.to_string().to_uppercase(), + password + ); - let qrcode = QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) - .context("Failed to generate the QR code")?; - info!("QR code generated successfully"); + let qrcode = + QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) + .context("Failed to generate the QR code")?; + info!("QR code generated successfully."); - let image = qrcode - .render() - .min_dimensions(size, size) - .dark_color(svg::Color(&foreground)) - .light_color(svg::Color(&background)) - .build(); - info!("QR code rendered to image"); + let image = qrcode + .render() + .min_dimensions(size, size) + .dark_color(svg::Color(&foreground)) + .light_color(svg::Color(&background)) + .build(); + info!("QR code rendered to image."); - save_image(&args.output, &export_format, &image, size)?; - info!("Image saved successfully to {:?}", args.output); + if let Some(output_path) = output { + save_image(&output_path, &export_format, &image, size) + .context("Failed to save the generated QR code image")?; + info!("Image saved successfully to {:?}", output_path); + } else { + println!("{}", image); + info!("Image output to stdout."); + } + } + Commands::Script { script } => { + // TODO: expand this + info!("Executing Lua script: {:?}", script); + let lua_script = std::fs::read_to_string(&script) + .with_context(|| format!("Failed to read script file: {:?}", script))?; + let lua = mlua::Lua::new(); + anyhow::Context::context(lua.load(&lua_script).exec(), "Error executing Lua script")?; + info!("Lua script executed successfully."); + } + Commands::SaveSettings { settings } => { + if let Some(proj_dirs) = ProjectDirs::from("org", "winlogon", "ciphercanvas") { + let config_dir = proj_dirs.config_dir(); + let settings_path = config_dir.join("settings.toml"); + std::fs::write(&settings_path, settings) + .with_context(|| format!("Failed to save settings to {:?}", settings_path))?; + info!("Settings saved successfully to {:?}", settings_path); + } else { + anyhow::bail!( + "Unable to determine default configuration directory to save settings." + ); + } + } + } Ok(()) } +/// Reads the configuration file from the given path. fn read_config(config_path: &PathBuf) -> Result { info!("Reading configuration file from {:?}", config_path); - let f = File::open(config_path) - .with_context(|| format!("Failed to open config file '{:?}'", config_path))?; - info!("Configuration file '{:?}' opened successfully", config_path); - + .with_context(|| format!("Failed to open config file: {:?}", config_path))?; let mut reader = BufReader::new(f); let mut config_str = String::new(); - reader .read_to_string(&mut config_str) - .context("Failed to read the config file")?; - info!("Configuration file read successfully"); - + .context("Failed to read the configuration file")?; + info!( + "Configuration file read successfully from {:?}", + config_path + ); Ok(config_str) } From 21e79fb3b20f161d102488837f11165852c85fe7 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 10:35:11 +0200 Subject: [PATCH 04/14] chore: add needed dependencies --- Cargo.lock | 444 ++++++++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 3 + 2 files changed, 407 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5661b2a..477de2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.0" @@ -82,6 +91,21 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + [[package]] name = "base64" version = "0.21.7" @@ -106,6 +130,16 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bytemuck" version = "1.22.0" @@ -118,6 +152,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -136,21 +185,24 @@ version = "0.2.1" dependencies = [ "anyhow", "clap", + "directories", "image 0.24.9", "log", + "mlua", "qrcode", "resvg", "simple_logger", "tiny-skia", + "tokio", "toml", "usvg", ] [[package]] name = "clap" -version = "4.5.31" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", "clap_derive", @@ -158,9 +210,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstream", "anstyle", @@ -170,9 +222,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" dependencies = [ "heck", "proc-macro2", @@ -256,13 +308,34 @@ checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[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", + "windows-sys 0.59.0", +] + [[package]] name = "either" version = "1.15.0" @@ -301,9 +374,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -338,6 +411,42 @@ dependencies = [ "ttf-parser", ] +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gif" version = "0.12.0" @@ -358,11 +467,17 @@ dependencies = [ "weezl", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -420,9 +535,9 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" [[package]] name = "indexmap" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -482,15 +597,35 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.170" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.9.0", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -509,14 +644,51 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" dependencies = [ "adler2", "simd-adler32", ] +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "mlua" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b" +dependencies = [ + "bstr", + "either", + "futures-util", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594" +dependencies = [ + "cc", + "cfg-if", + "pkg-config", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -572,11 +744,49 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] [[package]] name = "pico-args" @@ -584,6 +794,24 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "png" version = "0.17.16" @@ -633,9 +861,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -660,6 +888,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "resvg" version = "0.40.0" @@ -698,6 +946,18 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustybuzz" version = "0.12.1" @@ -714,20 +974,26 @@ dependencies = [ "unicode-script", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -743,6 +1009,21 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -776,6 +1057,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "slotmap" version = "1.0.7" @@ -787,9 +1077,19 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "strict-num" @@ -818,15 +1118,35 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiff" version = "0.9.1" @@ -840,9 +1160,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -857,15 +1177,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -912,6 +1232,35 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "toml" version = "0.8.20" @@ -1033,6 +1382,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "weezl" version = "0.1.8" @@ -1048,6 +1403,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1180,9 +1544,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index beae857..f6c65eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,14 @@ panic = "abort" [dependencies] anyhow = "1.0" clap = { version = "4.5.1", features = ["derive"] } +directories = "6.0.0" image = "0.24.9" log = "0.4.21" +mlua = { version = "0.10.3", features = ["async", "luajit", "send"] } qrcode = "0.12" resvg = "0.40.0" simple_logger = "5.0.0" tiny-skia = "0.11.4" +tokio = { version = "1.44.2", features = ["full"] } toml = "0.8" usvg = "0.40.0" From 1eb6e485a2b5eb7734c0bb7e6d5592766edbead8 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 13:06:14 +0200 Subject: [PATCH 05/14] chore: make some things async --- src/get_config.rs | 3 +- src/lua_api.rs | 135 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 78 ++++++++++++++------------- 3 files changed, 177 insertions(+), 39 deletions(-) create mode 100644 src/lua_api.rs diff --git a/src/get_config.rs b/src/get_config.rs index 76bf560..735e2c7 100644 --- a/src/get_config.rs +++ b/src/get_config.rs @@ -25,7 +25,8 @@ where D: Into>, { match config.get(key).and_then(|k| k.get(second_key)) { - Some(v) => v.as_str() + Some(v) => v + .as_str() .map(Cow::from) .unwrap_or_else(|| { panic!( diff --git a/src/lua_api.rs b/src/lua_api.rs new file mode 100644 index 0000000..651afad --- /dev/null +++ b/src/lua_api.rs @@ -0,0 +1,135 @@ +use crate::image_ops::save_image; +use miette::{Context, IntoDiagnostic, Result}; +use mlua::{Lua, Result as LuaResult, Value as LuaValue}; +use std::{cell::RefCell, path::PathBuf}; +use tokio::task; + +thread_local! { + static IMAGE_SETTINGS: RefCell = RefCell::new(ImageConfig::default()); +} + +/// Struct holding modifiable state used by Lua. +#[derive(Debug, Clone)] +pub struct ImageConfig { + pub size: u32, + pub format: String, + pub foreground: String, + pub background: String, +} + +impl Default for ImageConfig { + fn default() -> Self { + Self { + size: 512, + format: "svg".into(), + foreground: "#ffffff".into(), + background: "#000000".into(), + } + } +} + +pub struct LuaAPI; + +impl LuaAPI { + pub fn new() -> Self { + Self + } + + pub fn register_globals(&self, lua: &Lua) -> LuaResult<()> { + let globals = lua.globals(); + let ciphercanvas = lua.create_table()?; + + // Get current config + ciphercanvas.set( + "get_config", + lua.create_function(|lua, ()| { + IMAGE_SETTINGS.with(|cfg| { + let config = cfg.borrow(); + let table = lua.create_table()?; + table.set("size", config.size)?; + table.set("format", config.format.clone())?; + table.set("foreground", config.foreground.clone())?; + table.set("background", config.background.clone())?; + Ok(table) + }) + })?, + )?; + + // Set a config value + ciphercanvas.set( + "set_config", + lua.create_function(|_, (key, value): (String, LuaValue)| { + IMAGE_SETTINGS.with(|cfg| { + let mut config = cfg.borrow_mut(); + match key.as_str() { + "size" => { + if let LuaValue::Integer(i) = value { + config.size = i as u32; + } + } + "format" => { + if let LuaValue::String(s) = value { + config.format = s.to_str()?.to_string(); + } + } + "foreground" => { + if let LuaValue::String(s) = value { + config.foreground = s.to_str()?.to_string(); + } + } + "background" => { + if let LuaValue::String(s) = value { + config.background = s.to_str()?.to_string(); + } + } + _ => { + return Err(mlua::Error::RuntimeError(format!( + "Unknown config key: {key}" + ))); + } + } + Ok(()) + }) + })?, + )?; + + // Render and save image + ciphercanvas.set( + "save_image", + lua.create_async_function( + |_, (output_path, svg_content): (String, String)| async move { + let config = IMAGE_SETTINGS.with(|cfg| cfg.borrow().clone()); + let output = PathBuf::from(output_path); + task::spawn_blocking(move || { + save_image(&output, &config.format, &svg_content, config.size) + }) + .await + .map_err(|e| mlua::Error::RuntimeError(format!("JoinError: {e}")))? + .map_err(|e| mlua::Error::RuntimeError(format!("SaveError: {e}")))?; + Ok(()) + }, + )?, + )?; + + globals.set("ciphercanvas", ciphercanvas)?; + Ok(()) + } +} + +pub async fn execute_script(script_path: PathBuf) -> Result<()> { + let script_contents = tokio::fs::read_to_string(&script_path) + .await + .into_diagnostic() + .with_context(|| format!("Failed to read Lua script from {:?}", script_path))?; + + let lua = Lua::new(); + let api = LuaAPI::new(); + api.register_globals(&lua).into_diagnostic()?; + + lua.load(&script_contents) + .exec() + .into_diagnostic() + .with_context(|| "Error executing Lua script")?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index b08fca2..0858453 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,18 @@ -use anyhow::{Context, Result}; use clap::{Parser, Subcommand, ValueEnum}; use directories::ProjectDirs; use log::{info, warn}; -use qrcode::{EcLevel, QrCode, render::svg}; -use std::{ - fmt, - fs::File, - io::{self, BufReader, Read}, - path::PathBuf, -}; +use miette::{Context, IntoDiagnostic, Result}; +use qrcode::{render::svg, EcLevel, QrCode}; +use std::{fmt, path::PathBuf}; +use tokio::io::AsyncReadExt; mod get_config; mod image_ops; +mod lua_api; use crate::{ get_config::{get_config_int, get_config_str}, - image_ops::save_image, + image_ops::async_save_image, }; /// Mature and modular CLI tool to generate QR codes and customize behavior via scripting. @@ -125,27 +122,30 @@ async fn main() -> Result<()> { info!("Using default configuration file: {:?}", default_path); default_path } else { - anyhow::bail!( + miette::bail!( "Unable to determine the default configuration directory. Specify a config file using --config." ); } } }; - // Read configuration from file or stdin (if "-" is provided) + // Read configuration from file or from stdin (if "-" is provided) let config_str = if config_path.to_string_lossy() == "-" { let mut input = String::new(); - io::stdin() + tokio::io::stdin() .read_to_string(&mut input) - .context("Failed to read configuration from stdin")?; + .await + .into_diagnostic() + .with_context(|| "Failed to read configuration from stdin")?; input } else { - read_config(&config_path)? + read_config(&config_path).await? }; info!("Configuration loaded successfully."); - let toml_config: toml::Value = - toml::from_str(&config_str).context("Failed to parse the TOML configuration file")?; + let toml_config: toml::Value = toml::from_str(&config_str) + .into_diagnostic() + .with_context(|| "Failed to parse the TOML configuration file")?; info!("TOML configuration parsed successfully."); // Process the chosen subcommand @@ -168,7 +168,7 @@ async fn main() -> Result<()> { let foreground = get_config_str(&toml_config, "colors", "foreground", Consts::FOREGROUND); let background = - get_config_str(&toml_config, "colors", "background", Consts::BACKGROUND); + get_config_str(&toml_config, "colors", "background", Consts::FOREGROUND); let ssid = ssid.unwrap_or_else(|| { toml_config .get("wifi") @@ -182,7 +182,8 @@ async fn main() -> Result<()> { .get("qrcode") .and_then(|q| q.get("password")) .and_then(|p| p.as_str()) - .context("Failed to retrieve the QR code password from configuration. Please check your config file and ensure `[qrcode] password = \"...\"` is present.")?; + .into_diagnostic() + .with_context(|| "Failed to retrieve the QR code password from configuration. Please check your config file and ensure `[qrcode] password = \"...\"` is present.")?; info!("Password retrieved from configuration."); let contents_to_encode = format!( @@ -194,7 +195,8 @@ async fn main() -> Result<()> { let qrcode = QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) - .context("Failed to generate the QR code")?; + .into_diagnostic() + .with_context(|| "Failed to generate the QR code")?; info!("QR code generated successfully."); let image = qrcode @@ -206,32 +208,35 @@ async fn main() -> Result<()> { info!("QR code rendered to image."); if let Some(output_path) = output { - save_image(&output_path, &export_format, &image, size) - .context("Failed to save the generated QR code image")?; - info!("Image saved successfully to {:?}", output_path); + async_save_image(output_path, export_format.into(), image, size) + .await + .into_diagnostic() + .with_context(|| "Failed to save the generated QR code image")?; + info!("Image saved successfully."); } else { println!("{}", image); info!("Image output to stdout."); } } Commands::Script { script } => { - // TODO: expand this info!("Executing Lua script: {:?}", script); - let lua_script = std::fs::read_to_string(&script) - .with_context(|| format!("Failed to read script file: {:?}", script))?; - let lua = mlua::Lua::new(); - anyhow::Context::context(lua.load(&lua_script).exec(), "Error executing Lua script")?; + lua_api::execute_script(script) + .await + .into_diagnostic() + .with_context(|| "Error executing Lua script")?; info!("Lua script executed successfully."); } Commands::SaveSettings { settings } => { if let Some(proj_dirs) = ProjectDirs::from("org", "winlogon", "ciphercanvas") { let config_dir = proj_dirs.config_dir(); let settings_path = config_dir.join("settings.toml"); - std::fs::write(&settings_path, settings) + tokio::fs::write(&settings_path, settings) + .await + .into_diagnostic() .with_context(|| format!("Failed to save settings to {:?}", settings_path))?; info!("Settings saved successfully to {:?}", settings_path); } else { - anyhow::bail!( + miette::bail!( "Unable to determine default configuration directory to save settings." ); } @@ -241,16 +246,13 @@ async fn main() -> Result<()> { Ok(()) } -/// Reads the configuration file from the given path. -fn read_config(config_path: &PathBuf) -> Result { +/// Reads the configuration file asynchronously from the given path. +async fn read_config(config_path: &PathBuf) -> Result { info!("Reading configuration file from {:?}", config_path); - let f = File::open(config_path) - .with_context(|| format!("Failed to open config file: {:?}", config_path))?; - let mut reader = BufReader::new(f); - let mut config_str = String::new(); - reader - .read_to_string(&mut config_str) - .context("Failed to read the configuration file")?; + let config_str = tokio::fs::read_to_string(config_path) + .await + .into_diagnostic() + .with_context(|| format!("Failed to read configuration file from {:?}", config_path))?; info!( "Configuration file read successfully from {:?}", config_path From 4abd84ecfff64416b004f23eed64fa1236bdb387 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 13:06:33 +0200 Subject: [PATCH 06/14] chore: add miette --- Cargo.lock | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + 2 files changed, 164 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 477de2d..3cc02a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + [[package]] name = "base64" version = "0.21.7" @@ -188,6 +197,7 @@ dependencies = [ "directories", "image 0.24.9", "log", + "miette", "mlua", "qrcode", "resvg", @@ -348,6 +358,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "errno" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + [[package]] name = "exr" version = "1.73.0" @@ -543,6 +563,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -611,6 +637,12 @@ dependencies = [ "libc", ] +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "lock_api" version = "0.4.12" @@ -642,6 +674,37 @@ dependencies = [ "libc", ] +[[package]] +name = "miette" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" +dependencies = [ + "backtrace", + "backtrace-ext", + "cfg-if", + "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", + "thiserror 1.0.69", + "unicode-width 0.1.14", +] + +[[package]] +name = "miette-derive" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "miniz_oxide" version = "0.8.7" @@ -765,6 +828,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "owo-colors" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" + [[package]] name = "parking_lot" version = "0.12.3" @@ -905,7 +974,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom", "libredox", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -958,6 +1027,19 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustybuzz" version = "0.12.1" @@ -1106,6 +1188,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "supports-color" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "svgtypes" version = "0.14.0" @@ -1127,13 +1230,53 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "unicode-linebreak", + "unicode-width 0.2.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1325,6 +1468,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-properties" version = "0.1.3" @@ -1343,6 +1492,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "usvg" version = "0.40.0" diff --git a/Cargo.toml b/Cargo.toml index f6c65eb..537cad7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ clap = { version = "4.5.1", features = ["derive"] } directories = "6.0.0" image = "0.24.9" log = "0.4.21" +miette = { version = "7.5.0", features = ["fancy"] } mlua = { version = "0.10.3", features = ["async", "luajit", "send"] } qrcode = "0.12" resvg = "0.40.0" From e5ddcad745fad4f3533a83253d04c2d7796d2f78 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 13:43:31 +0200 Subject: [PATCH 07/14] chore: fix some compilation errors --- src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0858453..504033c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use directories::ProjectDirs; use log::{info, warn}; use miette::{Context, IntoDiagnostic, Result}; -use qrcode::{render::svg, EcLevel, QrCode}; +use qrcode::{EcLevel, QrCode, render::svg}; use std::{fmt, path::PathBuf}; use tokio::io::AsyncReadExt; @@ -182,8 +182,7 @@ async fn main() -> Result<()> { .get("qrcode") .and_then(|q| q.get("password")) .and_then(|p| p.as_str()) - .into_diagnostic() - .with_context(|| "Failed to retrieve the QR code password from configuration. Please check your config file and ensure `[qrcode] password = \"...\"` is present.")?; + .ok_or(miette::miette!("QR code password missing in configuration"))?; info!("Password retrieved from configuration."); let contents_to_encode = format!( @@ -210,8 +209,7 @@ async fn main() -> Result<()> { if let Some(output_path) = output { async_save_image(output_path, export_format.into(), image, size) .await - .into_diagnostic() - .with_context(|| "Failed to save the generated QR code image")?; + .map_err(|e| miette::miette!(e))?; info!("Image saved successfully."); } else { println!("{}", image); @@ -222,7 +220,6 @@ async fn main() -> Result<()> { info!("Executing Lua script: {:?}", script); lua_api::execute_script(script) .await - .into_diagnostic() .with_context(|| "Error executing Lua script")?; info!("Lua script executed successfully."); } From 908ab85e601980beb91d73d2f7004b0452c4f705 Mon Sep 17 00:00:00 2001 From: winlogon Date: Tue, 8 Apr 2025 13:44:49 +0200 Subject: [PATCH 08/14] chore: remove unneeded comment --- src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 504033c..7ead50b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -148,7 +148,6 @@ async fn main() -> Result<()> { .with_context(|| "Failed to parse the TOML configuration file")?; info!("TOML configuration parsed successfully."); - // Process the chosen subcommand match args.command.unwrap_or(Commands::Generate { ssid: None, encryption: Encryption::Wpa, From 1c806a7589184aeaf6c759198553997a47f0cf06 Mon Sep 17 00:00:00 2001 From: winlogon Date: Thu, 6 Nov 2025 13:37:15 +0100 Subject: [PATCH 09/14] chore: replace config & lua api with better CLI opts --- Cargo.lock | 802 +++++++++++++------------------------------- Cargo.toml | 7 +- README.md | 28 +- src/error.rs | 14 + src/get_config.rs | 178 ---------- src/image_ops.rs | 57 ++-- src/lua_api.rs | 135 -------- src/main.rs | 220 +++--------- src/qr_generator.rs | 56 ++++ 9 files changed, 383 insertions(+), 1114 deletions(-) create mode 100644 src/error.rs delete mode 100644 src/get_config.rs delete mode 100644 src/lua_api.rs create mode 100644 src/qr_generator.rs diff --git a/Cargo.lock b/Cargo.lock index 3cc02a1..0f79581 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 4 [[package]] name = "addr2line" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -34,44 +34,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arrayref" @@ -87,15 +87,15 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", @@ -103,7 +103,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -123,9 +123,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bit_field" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" @@ -135,25 +135,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" - -[[package]] -name = "bstr" -version = "1.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" -dependencies = [ - "memchr", - "serde", -] +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" [[package]] name = "byteorder" @@ -161,26 +151,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cc" -version = "1.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c" -dependencies = [ - "shlex", -] - [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "checked_int_cast" @@ -194,25 +169,22 @@ version = "0.2.1" dependencies = [ "anyhow", "clap", - "directories", "image 0.24.9", "log", "miette", - "mlua", "qrcode", "resvg", "simple_logger", + "thiserror", "tiny-skia", - "tokio", - "toml", "usvg", ] [[package]] name = "clap" -version = "4.5.35" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -220,9 +192,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.35" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -232,9 +204,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -244,9 +216,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "color_quant" @@ -256,25 +228,24 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "lazy_static", "windows-sys 0.59.0", ] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -306,66 +277,39 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "data-url" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] -[[package]] -name = "directories" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" -dependencies = [ - "dirs-sys", -] - -[[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", - "windows-sys 0.59.0", -] - [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[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.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -394,9 +338,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -410,9 +354,9 @@ checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] name = "fontconfig-parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ "roxmltree 0.20.0", ] @@ -431,42 +375,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gif" version = "0.12.0" @@ -479,9 +387,9 @@ dependencies = [ [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -489,26 +397,21 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" - [[package]] name = "heck" version = "0.5.0" @@ -539,7 +442,7 @@ dependencies = [ "byteorder", "color_quant", "exr", - "gif 0.13.1", + "gif 0.13.3", "jpeg-decoder", "num-traits", "png", @@ -553,16 +456,6 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" -[[package]] -name = "indexmap" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "is_ci" version = "1.2.0" @@ -571,9 +464,9 @@ checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" @@ -583,9 +476,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" dependencies = [ "rayon", ] @@ -609,76 +502,50 @@ dependencies = [ "smallvec", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "lebe" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.171" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" - -[[package]] -name = "libredox" -version = "0.1.3" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.9.0", - "libc", -] +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "linux-raw-sys" -version = "0.9.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] [[package]] name = "miette" -version = "7.5.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" dependencies = [ "backtrace", "backtrace-ext", @@ -690,15 +557,14 @@ dependencies = [ "supports-unicode", "terminal_size", "textwrap", - "thiserror 1.0.69", "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "7.5.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf45bf44ab49be92fd1227a3be6fc6f617f1a337c06af54981048574d8783147" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", @@ -707,51 +573,14 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", ] -[[package]] -name = "mio" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "mlua" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3f763c1041eff92ffb5d7169968a327e1ed2ebfe425dac0ee5a35f29082534b" -dependencies = [ - "bstr", - "either", - "futures-util", - "mlua-sys", - "num-traits", - "parking_lot", - "rustc-hash", -] - -[[package]] -name = "mlua-sys" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1901c1a635a22fe9250ffcc4fcc937c16b47c2e9e71adba8784af8bca1f69594" -dependencies = [ - "cc", - "cfg-if", - "pkg-config", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -809,53 +638,24 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] -name = "once_cell" -version = "1.21.3" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "owo-colors" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" [[package]] name = "pico-args" @@ -863,24 +663,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "png" version = "0.17.16" @@ -902,9 +684,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -930,18 +712,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -949,34 +731,14 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" -dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "redox_users" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" -dependencies = [ - "getrandom", - "libredox", - "thiserror 2.0.12", -] - [[package]] name = "resvg" version = "0.40.0" @@ -996,9 +758,9 @@ dependencies = [ [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" dependencies = [ "bytemuck", ] @@ -1017,27 +779,21 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "2.1.1" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -1046,7 +802,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.10.0", "bytemuck", "smallvec", "ttf-parser", @@ -1057,55 +813,34 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "serde" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] [[package]] -name = "serde" -version = "1.0.219" +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - [[package]] name = "simd-adler32" version = "0.3.7" @@ -1114,14 +849,14 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simple_logger" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c5dfa5e08767553704aa0ffd9d9794d527103c736aba9854773851fd7497eb" +checksum = "291bee647ce7310b0ea721bfd7e0525517b4468eb7c7e15eb8bd774343179702" dependencies = [ "colored", "log", "time", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -1139,15 +874,6 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - [[package]] name = "slotmap" version = "1.0.7" @@ -1159,19 +885,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" - -[[package]] -name = "socket2" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strict-num" @@ -1221,9 +937,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", @@ -1232,12 +948,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1247,7 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "unicode-linebreak", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] @@ -1256,16 +972,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" -dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl", ] [[package]] @@ -1279,17 +986,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror-impl" -version = "2.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tiff" version = "0.9.1" @@ -1303,9 +999,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -1320,15 +1016,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1362,9 +1058,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -1375,69 +1071,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tokio" -version = "1.44.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.52.0", -] - -[[package]] -name = "tokio-macros" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - [[package]] name = "ttf-parser" version = "0.20.0" @@ -1464,9 +1097,9 @@ checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" @@ -1476,9 +1109,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-properties" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-script" @@ -1500,9 +1133,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "usvg" @@ -1543,58 +1176,43 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-link" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link", ] [[package]] @@ -1606,7 +1224,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -1614,10 +1232,21 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] [[package]] name = "windows_aarch64_gnullvm" @@ -1626,10 +1255,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -1638,10 +1267,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -1649,6 +1278,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -1656,10 +1291,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -1668,10 +1303,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -1680,10 +1315,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -1692,10 +1327,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -1704,13 +1339,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.7.6" +name = "windows_x86_64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" -dependencies = [ - "memchr", -] +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "xmlwriter" @@ -1718,6 +1350,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/Cargo.toml b/Cargo.toml index 537cad7..6e8d411 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,15 +20,14 @@ panic = "abort" [dependencies] anyhow = "1.0" clap = { version = "4.5.1", features = ["derive"] } -directories = "6.0.0" image = "0.24.9" log = "0.4.21" miette = { version = "7.5.0", features = ["fancy"] } -mlua = { version = "0.10.3", features = ["async", "luajit", "send"] } +thiserror = "1.0" + qrcode = "0.12" resvg = "0.40.0" simple_logger = "5.0.0" tiny-skia = "0.11.4" -tokio = { version = "1.44.2", features = ["full"] } -toml = "0.8" + usvg = "0.40.0" diff --git a/README.md b/README.md index 5256c94..1437bab 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ciphercanvas: Wi-Fi QR Code Generator -A robust and efficient program written in Rust that generates QR codes for Wi-Fi +A robust and efficient command-line tool written in Rust that generates QR codes for Wi-Fi networks. It takes inputs such as SSID, encryption type (WPA or WEP), password, and desired output format, producing a QR code that simplifies Wi-Fi access sharing. @@ -13,19 +13,23 @@ sharing. ## Usage -To generate a Wi-Fi QR code using CipherCanvas: +To generate a Wi-Fi QR code using CipherCanvas, use the `generate` subcommand with the appropriate options: -1. **Create a configuration file**: Follow the guidelines in [the - documentation](docs/configuration.md) to create a configuration file that - includes your Wi-Fi network details. +``` console +$ ciphercanvas generate --ssid MyNetwork --password MyPassword --encryption wpa --output qrcode.png --size 512 --foreground "#000000" --background "#ffffff" +``` -2. **Generate the QR code**: Run the `ccanvas` command with the - appropriate options to generate your QR code. This example command creates a - `qrcode.svg` file based on your configuration: - - ``` console - $ ccanvas -s wifi4life -e wpa -c your-config-file.toml -o qrcode.svg - ``` +Alternatively, you can use a configuration file (e.g., `config.toml`) to specify default values: + +``` console +$ ciphercanvas generate --config ./config.toml --ssid MyNetwork --output qrcode.svg +``` + +To save frequently used settings to the default configuration file: + +``` console +$ ciphercanvas save-settings --settings '[wifi]\nssid="MyNetwork"\n[qrcode]\npassword="MyPassword"' +``` ## Contributing diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..6601a6c --- /dev/null +++ b/src/error.rs @@ -0,0 +1,14 @@ +use miette::Diagnostic; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum Error { + #[error("QR code generation error: {0}")] + QrCode(String), + #[error("Image processing error: {0}")] + Image(String), + #[error("Unsupported image format: {0}")] + UnsupportedFormat(String), + #[error(transparent)] + Io(#[from] std::io::Error), +} diff --git a/src/get_config.rs b/src/get_config.rs deleted file mode 100644 index 735e2c7..0000000 --- a/src/get_config.rs +++ /dev/null @@ -1,178 +0,0 @@ -use log::error; -use std::borrow::Cow; -use toml::Value; - -/// Retrieve a string value from the configuration. -/// Returns the provided default if the value is not found. -/// -/// # Examples -/// ```rust -/// use ciphercanvas::get_config_str; -/// let config = toml::from_str(r#" -/// [server] -/// host = "127.0.0.1" -/// "#).unwrap(); -/// let host = get_config_str(&config, "server", "host", "127.0.0.1"); -/// assert_eq!(host, "127.0.0.1"); -/// ``` -pub fn get_config_str<'a, D>( - config: &'a Value, - key: &str, - second_key: &str, - default: D, -) -> Cow<'a, str> -where - D: Into>, -{ - match config.get(key).and_then(|k| k.get(second_key)) { - Some(v) => v - .as_str() - .map(Cow::from) - .unwrap_or_else(|| { - panic!( - "Configuration error: Expected a string value for '{}.{}', but found a different type.", - key, second_key - ) - }), - None => { - let default_cow = default.into(); - error!( - "Configuration error: Unable to find the string value for '{}.{}'. Using default: '{}'.", - key, second_key, default_cow - ); - default_cow - } - } -} - -/// Retrieve an integer value from the configuration. -/// Returns the provided default if the value is not found. -/// -/// # Examples -/// ```rust -/// use ciphercanvas::get_config_int; -/// let config = toml::from_str(r#" -/// [server] -/// port = 8080 -/// "#).unwrap(); -/// let port = get_config_int(&config, "server", "port", 8080); -/// assert_eq!(port, 8080); -/// ``` -pub fn get_config_int( - config: &Value, - key: impl AsRef, - second_key: impl AsRef, - default: impl Into, -) -> i64 { - let key_ref = key.as_ref(); - let second_key_ref = second_key.as_ref(); - let default_val = default.into(); - - config - .get(key_ref) - .and_then(|k| k.get(second_key_ref)) - .and_then(|v| v.as_integer()) - .unwrap_or_else(|| { - error!( - "Configuration error: Unable to find the integer value for '{}.{}'. Using default: '{}'.", - key_ref, second_key_ref, default_val - ); - default_val - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_config_str_found() { - let config = toml::from_str( - r#" - [server] - host = "127.0.0.1" - "#, - ) - .unwrap(); - let host = get_config_str(&config, "server", "host", "localhost"); - assert_eq!(host, "127.0.0.1"); - } - - #[test] - fn test_get_config_str_not_found() { - let config = toml::from_str( - r#" - [server] - port = 8080 - "#, - ) - .unwrap(); - let host = get_config_str(&config, "server", "host", "localhost"); - assert_eq!(host, "localhost"); - } - - #[test] - fn test_get_config_str_default() { - let config = toml::from_str( - r#" - [server] - host = "127.0.0.1" - "#, - ) - .unwrap(); - let host = get_config_str(&config, "server", "host", "localhost"); - assert_eq!(host, "127.0.0.1"); - } - - #[test] - fn test_get_config_int_found() { - let config = toml::from_str( - r#" - [server] - port = 8080 - "#, - ) - .unwrap(); - let port = get_config_int(&config, "server", "port", 8081); - assert_eq!(port, 8080); - } - - #[test] - fn test_get_config_int_not_found() { - let config = toml::from_str( - r#" - [server] - host = "127.0.0.1" - "#, - ) - .unwrap(); - let port = get_config_int(&config, "server", "port", 8081); - assert_eq!(port, 8081); - } - - #[test] - fn test_get_config_int_default() { - let config = toml::from_str( - r#" - [server] - port = 8080 - "#, - ) - .unwrap(); - let port = get_config_int(&config, "server", "port", 8081); - assert_eq!(port, 8080); - } - - #[test] - #[should_panic] - fn test_get_config_str_invalid_type() { - let config = toml::from_str( - r#" - [server] - host = 8080 - "#, - ) - .unwrap(); - get_config_str(&config, "server", "host", "localhost"); - } -} diff --git a/src/image_ops.rs b/src/image_ops.rs index d6eadb2..8ac1b70 100644 --- a/src/image_ops.rs +++ b/src/image_ops.rs @@ -1,4 +1,4 @@ -use anyhow::{Context, Result, bail}; +use crate::error::Error; use log::{error, info}; use resvg::render; use std::{ @@ -10,21 +10,20 @@ use tiny_skia::{Pixmap, Transform}; use usvg::{Options, Tree, fontdb}; /// Load and render SVG content into a Pixmap of the specified size. -fn load_svg(contents: &[u8], size: u32) -> Result { +fn load_svg(contents: &[u8], size: u32) -> Result { info!("Loading SVG content with size {}x{}", size, size); let options = Options::default(); let fontdb = fontdb::Database::new(); - let tree: Tree = Tree::from_data(contents, &options, &fontdb).with_context(|| { - format!( - "Failed to create SVG tree from data of size {}x{}", - size, size - ) + let tree: Tree = Tree::from_data(contents, &options, &fontdb).map_err(|e| { + Error::Image(format!( + "Failed to create SVG tree from data of size {}x{}: {}", + size, size, e + )) })?; - info!("Successfully created SVG tree"); - let mut pixmap: Pixmap = Pixmap::new(size, size).context("Failed to create a new Pixmap")?; - info!("Created Pixmap of size {}x{}", size, size); + let mut pixmap: Pixmap = + Pixmap::new(size, size).ok_or(Error::Image("Failed to create a new Pixmap".to_string()))?; render(&tree, Transform::default(), &mut pixmap.as_mut()); info!("Rendered SVG to Pixmap"); @@ -57,28 +56,23 @@ fn load_svg(contents: &[u8], size: u32) -> Result { /// let output = PathBuf::from("output.png"); /// save_image(&output, &format, &image, size).unwrap(); /// ``` -pub fn save_image(output: &PathBuf, format: &str, image: &str, size: u32) -> Result<()> { +pub fn save_image(output: &PathBuf, format: &str, image: &str, size: u32) -> Result<(), Error> { const SUPPORTED_FORMATS: &[&str] = &["svg", "png"]; info!( "Starting to save image with format '{}' to {:?}", format, output ); - let file_path = output.with_extension(if SUPPORTED_FORMATS.contains(&format) { - format - } else { - bail!("Unsupported image format: '{}'", format); - }); + if !SUPPORTED_FORMATS.contains(&format) { + return Err(Error::UnsupportedFormat(format.to_string())); + } + + let file_path = output.with_extension(format); match format { "svg" => { - let mut writer = BufWriter::new( - File::create(&file_path) - .with_context(|| format!("Failed to create output file {:?}", file_path))?, - ); - writer - .write_all(image.as_bytes()) - .with_context(|| format!("Failed to write SVG image to file {:?}", file_path))?; + let mut writer = BufWriter::new(File::create(&file_path)?); + writer.write_all(image.as_bytes())?; info!("Saved SVG image to {:?}", file_path); } "png" => { @@ -91,27 +85,14 @@ pub fn save_image(output: &PathBuf, format: &str, image: &str, size: u32) -> Res let pixmap = load_svg(image.as_bytes(), size)?; pixmap .save_png(&file_path) - .with_context(|| format!("Failed to save PNG image to file {:?}", file_path))?; + .map_err(|e| Error::Image(e.to_string()))?; info!("Saved PNG image to {:?}", file_path); } _ => { - bail!("Unsupported image format: '{}'", format); + return Err(Error::UnsupportedFormat(format.to_string())); } } info!("Image saved successfully to {:?}", file_path); Ok(()) } - -/// An asynchronous wrapper for `save_image` for heavy I/O operations. -/// This function offloads blocking work to a separate thread using tokio's spawn_blocking. -pub async fn async_save_image( - output: PathBuf, - format: String, - image: String, - size: u32, -) -> Result<()> { - tokio::task::spawn_blocking(move || save_image(&output, &format, &image, size)) - .await - .expect("Task panicked") -} diff --git a/src/lua_api.rs b/src/lua_api.rs deleted file mode 100644 index 651afad..0000000 --- a/src/lua_api.rs +++ /dev/null @@ -1,135 +0,0 @@ -use crate::image_ops::save_image; -use miette::{Context, IntoDiagnostic, Result}; -use mlua::{Lua, Result as LuaResult, Value as LuaValue}; -use std::{cell::RefCell, path::PathBuf}; -use tokio::task; - -thread_local! { - static IMAGE_SETTINGS: RefCell = RefCell::new(ImageConfig::default()); -} - -/// Struct holding modifiable state used by Lua. -#[derive(Debug, Clone)] -pub struct ImageConfig { - pub size: u32, - pub format: String, - pub foreground: String, - pub background: String, -} - -impl Default for ImageConfig { - fn default() -> Self { - Self { - size: 512, - format: "svg".into(), - foreground: "#ffffff".into(), - background: "#000000".into(), - } - } -} - -pub struct LuaAPI; - -impl LuaAPI { - pub fn new() -> Self { - Self - } - - pub fn register_globals(&self, lua: &Lua) -> LuaResult<()> { - let globals = lua.globals(); - let ciphercanvas = lua.create_table()?; - - // Get current config - ciphercanvas.set( - "get_config", - lua.create_function(|lua, ()| { - IMAGE_SETTINGS.with(|cfg| { - let config = cfg.borrow(); - let table = lua.create_table()?; - table.set("size", config.size)?; - table.set("format", config.format.clone())?; - table.set("foreground", config.foreground.clone())?; - table.set("background", config.background.clone())?; - Ok(table) - }) - })?, - )?; - - // Set a config value - ciphercanvas.set( - "set_config", - lua.create_function(|_, (key, value): (String, LuaValue)| { - IMAGE_SETTINGS.with(|cfg| { - let mut config = cfg.borrow_mut(); - match key.as_str() { - "size" => { - if let LuaValue::Integer(i) = value { - config.size = i as u32; - } - } - "format" => { - if let LuaValue::String(s) = value { - config.format = s.to_str()?.to_string(); - } - } - "foreground" => { - if let LuaValue::String(s) = value { - config.foreground = s.to_str()?.to_string(); - } - } - "background" => { - if let LuaValue::String(s) = value { - config.background = s.to_str()?.to_string(); - } - } - _ => { - return Err(mlua::Error::RuntimeError(format!( - "Unknown config key: {key}" - ))); - } - } - Ok(()) - }) - })?, - )?; - - // Render and save image - ciphercanvas.set( - "save_image", - lua.create_async_function( - |_, (output_path, svg_content): (String, String)| async move { - let config = IMAGE_SETTINGS.with(|cfg| cfg.borrow().clone()); - let output = PathBuf::from(output_path); - task::spawn_blocking(move || { - save_image(&output, &config.format, &svg_content, config.size) - }) - .await - .map_err(|e| mlua::Error::RuntimeError(format!("JoinError: {e}")))? - .map_err(|e| mlua::Error::RuntimeError(format!("SaveError: {e}")))?; - Ok(()) - }, - )?, - )?; - - globals.set("ciphercanvas", ciphercanvas)?; - Ok(()) - } -} - -pub async fn execute_script(script_path: PathBuf) -> Result<()> { - let script_contents = tokio::fs::read_to_string(&script_path) - .await - .into_diagnostic() - .with_context(|| format!("Failed to read Lua script from {:?}", script_path))?; - - let lua = Lua::new(); - let api = LuaAPI::new(); - api.register_globals(&lua).into_diagnostic()?; - - lua.load(&script_contents) - .exec() - .into_diagnostic() - .with_context(|| "Error executing Lua script")?; - - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index 7ead50b..525ae9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,13 @@ use clap::{Parser, Subcommand, ValueEnum}; -use directories::ProjectDirs; -use log::{info, warn}; -use miette::{Context, IntoDiagnostic, Result}; -use qrcode::{EcLevel, QrCode, render::svg}; +use log::info; + use std::{fmt, path::PathBuf}; -use tokio::io::AsyncReadExt; -mod get_config; +mod error; mod image_ops; -mod lua_api; +mod qr_generator; -use crate::{ - get_config::{get_config_int, get_config_str}, - image_ops::async_save_image, -}; +use qr_generator::QrCodeOptions; /// Mature and modular CLI tool to generate QR codes and customize behavior via scripting. /// @@ -21,7 +15,7 @@ use crate::{ /// /// Generate a QR code with a custom configuration: /// ```sh -/// ciphercanvas generate --ssid MyNetwork --output qrcode.svg --config ./config.toml +/// ciphercanvas generate --ssid MyNetwork --output qrcode.svg /// ``` /// /// Run a custom Lua script to alter tool behavior: @@ -35,10 +29,6 @@ struct CliArgs { #[arg(short, long)] verbose: bool, - /// Optional configuration file. If omitted, the default configuration directory is used. - #[arg(short, long, value_name = "FILE")] - config: Option, - /// Specify subcommand to execute. #[command(subcommand)] command: Option, @@ -51,7 +41,7 @@ enum Commands { Generate { /// The WiFi network's SSID (name) #[arg(short, long)] - ssid: Option, + ssid: String, /// The encryption type used (WPA, WEP, or None). #[arg(short, long, default_value = "wpa")] @@ -60,18 +50,26 @@ enum Commands { /// The output file to export the QR code image. #[arg(short, long)] output: Option, - }, - /// Run a Lua script to extend or customize the tool’s behavior. - Script { - /// Path to the Lua script. - #[arg(short, long)] - script: PathBuf, - }, - /// Save frequently used settings in the configuration store. - SaveSettings { - /// Settings in TOML format to save. + + /// The WiFi network's password. #[arg(short, long)] - settings: String, + password: String, + + /// The size of the QR code image (e.g., 512). + #[arg(long, default_value_t = 512)] + size: u32, + + /// The output format of the image (e.g., "svg", "png"). + #[arg(long, default_value = "svg")] + format: String, + + /// The foreground color of the QR code (e.g., "#000000"). + #[arg(long, default_value = "#000000")] + foreground: String, + + /// The background color of the QR code (e.g., "#ffffff")] + #[arg(long, default_value = "#ffffff")] + background: String, }, } @@ -94,17 +92,7 @@ impl fmt::Display for Encryption { } } -struct Consts; - -impl Consts { - const FORMAT: &'static str = "svg"; - const BACKGROUND: &'static str = "#000000"; - const FOREGROUND: &'static str = "#ffffff"; - const SIZE: u32 = 512; -} - -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<(), crate::error::Error> { let args = CliArgs::parse(); if args.verbose { @@ -113,145 +101,33 @@ async fn main() -> Result<()> { } info!("Parsed arguments: {:#?}", args); - // Determine the configuration file: - let config_path = match &args.config { - Some(path) if path.to_string_lossy() != "-" => path.clone(), - _ => { - if let Some(proj_dirs) = ProjectDirs::from("org", "winlogon", "ciphercanvas") { - let default_path = proj_dirs.config_dir().join("config.toml"); - info!("Using default configuration file: {:?}", default_path); - default_path - } else { - miette::bail!( - "Unable to determine the default configuration directory. Specify a config file using --config." - ); - } - } - }; - - // Read configuration from file or from stdin (if "-" is provided) - let config_str = if config_path.to_string_lossy() == "-" { - let mut input = String::new(); - tokio::io::stdin() - .read_to_string(&mut input) - .await - .into_diagnostic() - .with_context(|| "Failed to read configuration from stdin")?; - input - } else { - read_config(&config_path).await? - }; - info!("Configuration loaded successfully."); - - let toml_config: toml::Value = toml::from_str(&config_str) - .into_diagnostic() - .with_context(|| "Failed to parse the TOML configuration file")?; - info!("TOML configuration parsed successfully."); - - match args.command.unwrap_or(Commands::Generate { - ssid: None, - encryption: Encryption::Wpa, - output: None, - }) { - Commands::Generate { + match args.command { + Some(Commands::Generate { ssid, encryption, output, - } => { - // Get configuration settings for QR code; if not set in TOML, use defaults. - let export_format = get_config_str(&toml_config, "qrcode", "export", Consts::FORMAT); - let size = get_config_int(&toml_config, "qrcode", "size", Consts::SIZE as i64) as u32; - if size < 256 { - warn!("Image size is lower than 256. The resulting QR code may appear cropped."); - } - let foreground = - get_config_str(&toml_config, "colors", "foreground", Consts::FOREGROUND); - let background = - get_config_str(&toml_config, "colors", "background", Consts::FOREGROUND); - let ssid = ssid.unwrap_or_else(|| { - toml_config - .get("wifi") - .and_then(|w| w.get("ssid")) - .and_then(|s| s.as_str()) - .unwrap_or("default_ssid") - .to_string() - }); - - let password = toml_config - .get("qrcode") - .and_then(|q| q.get("password")) - .and_then(|p| p.as_str()) - .ok_or(miette::miette!("QR code password missing in configuration"))?; - info!("Password retrieved from configuration."); - - let contents_to_encode = format!( - "WIFI:S:{};T:{};P:{};;", + password, + size, + format, + foreground, + background, + }) => { + let options = QrCodeOptions { ssid, - encryption.to_string().to_uppercase(), - password - ); - - let qrcode = - QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) - .into_diagnostic() - .with_context(|| "Failed to generate the QR code")?; - info!("QR code generated successfully."); - - let image = qrcode - .render() - .min_dimensions(size, size) - .dark_color(svg::Color(&foreground)) - .light_color(svg::Color(&background)) - .build(); - info!("QR code rendered to image."); - - if let Some(output_path) = output { - async_save_image(output_path, export_format.into(), image, size) - .await - .map_err(|e| miette::miette!(e))?; - info!("Image saved successfully."); - } else { - println!("{}", image); - info!("Image output to stdout."); - } - } - Commands::Script { script } => { - info!("Executing Lua script: {:?}", script); - lua_api::execute_script(script) - .await - .with_context(|| "Error executing Lua script")?; - info!("Lua script executed successfully."); - } - Commands::SaveSettings { settings } => { - if let Some(proj_dirs) = ProjectDirs::from("org", "winlogon", "ciphercanvas") { - let config_dir = proj_dirs.config_dir(); - let settings_path = config_dir.join("settings.toml"); - tokio::fs::write(&settings_path, settings) - .await - .into_diagnostic() - .with_context(|| format!("Failed to save settings to {:?}", settings_path))?; - info!("Settings saved successfully to {:?}", settings_path); - } else { - miette::bail!( - "Unable to determine default configuration directory to save settings." - ); - } + encryption: encryption.to_string(), + password, + output_path: output + .map(|p| p.to_str().unwrap().to_string()) + .unwrap_or_default(), + dark_color: foreground, + light_color: background, + size, + format, + }; + qr_generator::generate_qr_code(options)?; } + None => {} } Ok(()) } - -/// Reads the configuration file asynchronously from the given path. -async fn read_config(config_path: &PathBuf) -> Result { - info!("Reading configuration file from {:?}", config_path); - let config_str = tokio::fs::read_to_string(config_path) - .await - .into_diagnostic() - .with_context(|| format!("Failed to read configuration file from {:?}", config_path))?; - info!( - "Configuration file read successfully from {:?}", - config_path - ); - Ok(config_str) -} diff --git a/src/qr_generator.rs b/src/qr_generator.rs new file mode 100644 index 0000000..b00832d --- /dev/null +++ b/src/qr_generator.rs @@ -0,0 +1,56 @@ +use crate::error::Error; +use log::{info, warn}; +use miette::Result; +use qrcode::{EcLevel, QrCode, render::svg}; +use std::path::PathBuf; + +use crate::image_ops::save_image; + +pub struct QrCodeOptions { + pub ssid: String, + pub encryption: String, + pub password: String, + pub output_path: String, + pub dark_color: String, + pub light_color: String, + pub size: u32, + pub format: String, +} + +pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { + if options.size < 256 { + warn!("Image size is lower than 256. The resulting QR code may appear cropped."); + } + + let contents_to_encode = format!( + "WIFI:S:{};T:{};P:{};;", + options.ssid, + options.encryption.to_uppercase(), + options.password + ); + + let qrcode = QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) + .map_err(|e| Error::QrCode(format!("Failed to generate the QR code: {}", e)))?; + info!("QR code generated successfully."); + + let image = qrcode + .render() + .min_dimensions(options.size, options.size) + .dark_color(svg::Color(&options.dark_color)) + .light_color(svg::Color(&options.light_color)) + .build(); + info!("QR code rendered to image."); + + if !options.output_path.is_empty() { + save_image( + &PathBuf::from(options.output_path), + &options.format, + &image, + options.size, + )?; + } else { + println!("{}", image); + info!("Image output to stdout."); + } + Ok(()) +} From 5d0dc89e0682bda2a4f925f2bcee9e78d1007c60 Mon Sep 17 00:00:00 2001 From: winlogon Date: Fri, 7 Nov 2025 05:16:12 +0100 Subject: [PATCH 10/14] chore(api): use &Path instead of &PathBuf --- src/image_ops.rs | 4 ++-- src/qr_generator.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/image_ops.rs b/src/image_ops.rs index 8ac1b70..8c630c9 100644 --- a/src/image_ops.rs +++ b/src/image_ops.rs @@ -4,7 +4,7 @@ use resvg::render; use std::{ fs::File, io::{BufWriter, Write}, - path::PathBuf, + path::Path, }; use tiny_skia::{Pixmap, Transform}; use usvg::{Options, Tree, fontdb}; @@ -56,7 +56,7 @@ fn load_svg(contents: &[u8], size: u32) -> Result { /// let output = PathBuf::from("output.png"); /// save_image(&output, &format, &image, size).unwrap(); /// ``` -pub fn save_image(output: &PathBuf, format: &str, image: &str, size: u32) -> Result<(), Error> { +pub fn save_image(output: &Path, format: &str, image: &str, size: u32) -> Result<(), Error> { const SUPPORTED_FORMATS: &[&str] = &["svg", "png"]; info!( "Starting to save image with format '{}' to {:?}", diff --git a/src/qr_generator.rs b/src/qr_generator.rs index b00832d..f9f9ecc 100644 --- a/src/qr_generator.rs +++ b/src/qr_generator.rs @@ -1,7 +1,7 @@ use crate::error::Error; use log::{info, warn}; use miette::Result; -use qrcode::{EcLevel, QrCode, render::svg}; +use qrcode::{render::svg, EcLevel, QrCode}; use std::path::PathBuf; use crate::image_ops::save_image; From cabc1c22adf94e745a9956b1f05a81db41123329 Mon Sep 17 00:00:00 2001 From: winlogon Date: Mon, 10 Nov 2025 01:31:09 +0100 Subject: [PATCH 11/14] refactor: improve image saving and CLI docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move SUPPORTED_FORMATS constant to module scope in `image_ops.rs` to avoid redefining it within `save_image`, and replace `PathBuf` with `Path` for cleaner and more idiomatic API design. This improves consistency across the module and simplifies path handling. Simplify the CLI `about` text in `main.rs` to make the tool’s purpose clearer and reduce verbosity in help messages. Add minor formatting polish in `qr_generator.rs` for readability by adding a blank line after QR code rendering. --- src/image_ops.rs | 5 +++-- src/main.rs | 14 +------------- src/qr_generator.rs | 3 ++- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/image_ops.rs b/src/image_ops.rs index 8c630c9..1c29c45 100644 --- a/src/image_ops.rs +++ b/src/image_ops.rs @@ -3,12 +3,14 @@ use log::{error, info}; use resvg::render; use std::{ fs::File, - io::{BufWriter, Write}, + io::{BufWriter, prelude::*}, path::Path, }; use tiny_skia::{Pixmap, Transform}; use usvg::{Options, Tree, fontdb}; +const SUPPORTED_FORMATS: &[&str] = &["svg", "png"]; + /// Load and render SVG content into a Pixmap of the specified size. fn load_svg(contents: &[u8], size: u32) -> Result { info!("Loading SVG content with size {}x{}", size, size); @@ -57,7 +59,6 @@ fn load_svg(contents: &[u8], size: u32) -> Result { /// save_image(&output, &format, &image, size).unwrap(); /// ``` pub fn save_image(output: &Path, format: &str, image: &str, size: u32) -> Result<(), Error> { - const SUPPORTED_FORMATS: &[&str] = &["svg", "png"]; info!( "Starting to save image with format '{}' to {:?}", format, output diff --git a/src/main.rs b/src/main.rs index 525ae9c..0b8b970 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,19 +9,7 @@ mod qr_generator; use qr_generator::QrCodeOptions; -/// Mature and modular CLI tool to generate QR codes and customize behavior via scripting. -/// -/// # Examples -/// -/// Generate a QR code with a custom configuration: -/// ```sh -/// ciphercanvas generate --ssid MyNetwork --output qrcode.svg -/// ``` -/// -/// Run a custom Lua script to alter tool behavior: -/// ```sh -/// ciphercanvas script --script ./customize.lua -/// ``` +/// Mature and modular CLI tool to generate QR codes. #[derive(Debug, Parser)] #[command(author, version, about, long_about = None)] struct CliArgs { diff --git a/src/qr_generator.rs b/src/qr_generator.rs index f9f9ecc..c8764df 100644 --- a/src/qr_generator.rs +++ b/src/qr_generator.rs @@ -1,7 +1,7 @@ use crate::error::Error; use log::{info, warn}; use miette::Result; -use qrcode::{render::svg, EcLevel, QrCode}; +use qrcode::{EcLevel, QrCode, render::svg}; use std::path::PathBuf; use crate::image_ops::save_image; @@ -39,6 +39,7 @@ pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { .dark_color(svg::Color(&options.dark_color)) .light_color(svg::Color(&options.light_color)) .build(); + info!("QR code rendered to image."); if !options.output_path.is_empty() { From 26b056ef512e76cd2105c7047cc7c17f1af4a7c2 Mon Sep 17 00:00:00 2001 From: winlogon Date: Wed, 12 Nov 2025 05:09:51 +0100 Subject: [PATCH 12/14] refactor: make QrCodeOptions store a PathBuf --- src/main.rs | 4 +--- src/qr_generator.rs | 12 +++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0b8b970..5c8c075 100644 --- a/src/main.rs +++ b/src/main.rs @@ -104,9 +104,7 @@ fn main() -> Result<(), crate::error::Error> { ssid, encryption: encryption.to_string(), password, - output_path: output - .map(|p| p.to_str().unwrap().to_string()) - .unwrap_or_default(), + output_path: output, dark_color: foreground, light_color: background, size, diff --git a/src/qr_generator.rs b/src/qr_generator.rs index c8764df..ecc145b 100644 --- a/src/qr_generator.rs +++ b/src/qr_generator.rs @@ -10,7 +10,7 @@ pub struct QrCodeOptions { pub ssid: String, pub encryption: String, pub password: String, - pub output_path: String, + pub output_path: Option, pub dark_color: String, pub light_color: String, pub size: u32, @@ -42,16 +42,10 @@ pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { info!("QR code rendered to image."); - if !options.output_path.is_empty() { - save_image( - &PathBuf::from(options.output_path), - &options.format, - &image, - options.size, - )?; + if let Some(path) = options.output_path { + save_image(&PathBuf::from(path), &options.format, &image, options.size)?; } else { println!("{}", image); - info!("Image output to stdout."); } Ok(()) } From 7c9d3d2e0c0b3f7c7993e0108db109a4b21bb76d Mon Sep 17 00:00:00 2001 From: winlogon Date: Mon, 17 Nov 2025 00:07:25 +0100 Subject: [PATCH 13/14] fix(error,docs): update coc contact and io error import - Replace the email address with the Matrix handle `@winlogon.exe:matrix.org` in CODE_OF_CONDUCT.md. - Remove the redundant `std::io` import and use a direct `io::Error` alias in error.rs --- CODE_OF_CONDUCT.md | 3 +-- src/error.rs | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 6c3bcea..c707252 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -61,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -. +`@winlogon.exe:matrix.org` All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the diff --git a/src/error.rs b/src/error.rs index 6601a6c..ef6bc40 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ use miette::Diagnostic; +use std::io; use thiserror::Error; #[derive(Error, Debug, Diagnostic)] @@ -10,5 +11,5 @@ pub enum Error { #[error("Unsupported image format: {0}")] UnsupportedFormat(String), #[error(transparent)] - Io(#[from] std::io::Error), + Io(#[from] io::Error), } From dcea3852b40e22169f0b00eeff5bc6928f7bb5f1 Mon Sep 17 00:00:00 2001 From: winlogon Date: Mon, 17 Nov 2025 00:34:57 +0100 Subject: [PATCH 14/14] feat: add file overwrite support and improve logging - Introduce FileExists variant in Error enum with proper error message - Extende save_image signature to accept an overwrite flag - Change save_image to return Error::FileExists with already-existing files - Update logging statements to use display() and interpolated strings for clearer output - Add overwrite CLI option (--overwrite) to `Generate` command and propagated it through QrCodeOptions - Adjust QrCodeOptions struct to include overwrite - Refactor imports and minor formatting improvements across modules --- src/error.rs | 2 ++ src/image_ops.rs | 36 +++++++++++++++++++++++------------- src/main.rs | 29 +++++++++++++++++------------ src/qr_generator.rs | 19 ++++++++++++------- 4 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/error.rs b/src/error.rs index ef6bc40..ec376d7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -10,6 +10,8 @@ pub enum Error { Image(String), #[error("Unsupported image format: {0}")] UnsupportedFormat(String), + #[error("File already exists: {0}")] + FileExists(String), #[error(transparent)] Io(#[from] io::Error), } diff --git a/src/image_ops.rs b/src/image_ops.rs index 1c29c45..2cba397 100644 --- a/src/image_ops.rs +++ b/src/image_ops.rs @@ -13,14 +13,13 @@ const SUPPORTED_FORMATS: &[&str] = &["svg", "png"]; /// Load and render SVG content into a Pixmap of the specified size. fn load_svg(contents: &[u8], size: u32) -> Result { - info!("Loading SVG content with size {}x{}", size, size); + info!("Loading SVG content with size {size}x{size}"); let options = Options::default(); let fontdb = fontdb::Database::new(); let tree: Tree = Tree::from_data(contents, &options, &fontdb).map_err(|e| { Error::Image(format!( - "Failed to create SVG tree from data of size {}x{}: {}", - size, size, e + "Failed to create SVG tree from data of size {size}x{size}: {e}" )) })?; @@ -58,10 +57,17 @@ fn load_svg(contents: &[u8], size: u32) -> Result { /// let output = PathBuf::from("output.png"); /// save_image(&output, &format, &image, size).unwrap(); /// ``` -pub fn save_image(output: &Path, format: &str, image: &str, size: u32) -> Result<(), Error> { +pub fn save_image( + output: &Path, + format: &str, + image: &str, + size: u32, + overwrite: bool, +) -> Result<(), Error> { info!( - "Starting to save image with format '{}' to {:?}", - format, output + "Starting to save image with format '{}' to {}", + format, + output.display() ); if !SUPPORTED_FORMATS.contains(&format) { @@ -70,30 +76,34 @@ pub fn save_image(output: &Path, format: &str, image: &str, size: u32) -> Result let file_path = output.with_extension(format); + if file_path.exists() && !overwrite { + return Err(Error::FileExists(format!( + "File already exists: {}. Use --overwrite to force overwrite.", + file_path.display() + ))); + } + match format { "svg" => { let mut writer = BufWriter::new(File::create(&file_path)?); writer.write_all(image.as_bytes())?; - info!("Saved SVG image to {:?}", file_path); + info!("Saved SVG image to {}", file_path.display()); } "png" => { if size <= 256 { - error!( - "Warning: Image size is {}x{}, which may result in lower quality.", - size, size - ); + error!("Warning: Image size is {size}x{size}, which may result in lower quality.",); } let pixmap = load_svg(image.as_bytes(), size)?; pixmap .save_png(&file_path) .map_err(|e| Error::Image(e.to_string()))?; - info!("Saved PNG image to {:?}", file_path); + info!("Saved PNG image to {}", file_path.display()); } _ => { return Err(Error::UnsupportedFormat(format.to_string())); } } - info!("Image saved successfully to {:?}", file_path); + info!("Image saved successfully to {}", file_path.display()); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 5c8c075..e3b16a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use clap::{Parser, Subcommand, ValueEnum}; use log::info; - use std::{fmt, path::PathBuf}; mod error; @@ -25,9 +24,9 @@ struct CliArgs { /// List of available subcommands. #[derive(Debug, Subcommand)] enum Commands { - /// Generate a QR code image from WiFi credentials. + /// Generate a QR code image from Wi-Fi credentials. Generate { - /// The WiFi network's SSID (name) + /// The Wi-Fi network's SSID (name) #[arg(short, long)] ssid: String, @@ -39,7 +38,7 @@ enum Commands { #[arg(short, long)] output: Option, - /// The WiFi network's password. + /// The Wi-Fi network's password. #[arg(short, long)] password: String, @@ -58,11 +57,15 @@ enum Commands { /// The background color of the QR code (e.g., "#ffffff")] #[arg(long, default_value = "#ffffff")] background: String, + + /// Overwrite existing files without prompt. + #[arg(long, default_value_t = false)] + overwrite: bool, }, } -/// Valid encryption types for WiFi. -#[derive(ValueEnum, Clone, Debug)] +/// Valid encryption types for Wi-Fi. +#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] enum Encryption { Wpa, Wep, @@ -76,18 +79,18 @@ impl fmt::Display for Encryption { Encryption::Wep => "WEP", Encryption::None => "nopass", }; - write!(f, "{}", encryption_str) + write!(f, "{encryption_str}") } } -fn main() -> Result<(), crate::error::Error> { +fn main() -> Result<(), error::Error> { let args = CliArgs::parse(); if args.verbose { simple_logger::init().unwrap(); info!("Verbose logging enabled."); } - info!("Parsed arguments: {:#?}", args); + info!("Parsed arguments: {args:#?}"); match args.command { Some(Commands::Generate { @@ -99,16 +102,18 @@ fn main() -> Result<(), crate::error::Error> { format, foreground, background, + overwrite, }) => { let options = QrCodeOptions { ssid, encryption: encryption.to_string(), password, output_path: output, - dark_color: foreground, - light_color: background, + dark_color: foreground.clone(), + light_color: background.clone(), size, - format, + format: format.clone(), + overwrite, }; qr_generator::generate_qr_code(options)?; } diff --git a/src/qr_generator.rs b/src/qr_generator.rs index ecc145b..ed35f95 100644 --- a/src/qr_generator.rs +++ b/src/qr_generator.rs @@ -1,11 +1,9 @@ -use crate::error::Error; +use crate::{error::Error, image_ops::save_image}; use log::{info, warn}; use miette::Result; -use qrcode::{EcLevel, QrCode, render::svg}; +use qrcode::{render::svg, EcLevel, QrCode}; use std::path::PathBuf; -use crate::image_ops::save_image; - pub struct QrCodeOptions { pub ssid: String, pub encryption: String, @@ -15,6 +13,7 @@ pub struct QrCodeOptions { pub light_color: String, pub size: u32, pub format: String, + pub overwrite: bool, } pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { @@ -30,7 +29,7 @@ pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { ); let qrcode = QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) - .map_err(|e| Error::QrCode(format!("Failed to generate the QR code: {}", e)))?; + .map_err(|e| Error::QrCode(format!("Failed to generate the QR code: {e}")))?; info!("QR code generated successfully."); let image = qrcode @@ -43,9 +42,15 @@ pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { info!("QR code rendered to image."); if let Some(path) = options.output_path { - save_image(&PathBuf::from(path), &options.format, &image, options.size)?; + save_image( + &path, + &options.format, + &image, + options.size, + options.overwrite, + )?; } else { - println!("{}", image); + println!("{image}"); } Ok(()) }