diff options
82 files changed, 651 insertions, 400 deletions
diff --git a/dev_tools/Cargo.toml b/dev_tools/Cargo.toml index 56f89d3..280a50c 100644 --- a/dev_tools/Cargo.toml +++ b/dev_tools/Cargo.toml @@ -2,6 +2,13 @@ name = "tools" version = "0.1.0" edition = "2024" +authors = ["Weicao-CatilGrass"] +description = "Development tools for mingling" +license = "MIT OR Apache-2.0" +repository = "https://github.com/catilgrass/mingling" +readme = "../README.md" +keywords = ["cli", "development", "tools"] +categories = ["command-line-interface", "development-tools"] [dependencies] just_template = "0.1.3" diff --git a/dev_tools/src/bin/ci.rs b/dev_tools/src/bin/ci.rs index 3169b95..86b930c 100644 --- a/dev_tools/src/bin/ci.rs +++ b/dev_tools/src/bin/ci.rs @@ -1,4 +1,4 @@ -use std::io::Write; +use std::io::Write as _; use std::process::exit; use tools::{cargo_tomls, eprintln_cargo_style, println_cargo_style, run_cmd}; @@ -26,7 +26,7 @@ fn main() { } if let Err(exit_code) = ci() { - restore_workspace().unwrap(); + restore_workspace(needs_commit_temp).unwrap(); exit(exit_code) } @@ -39,7 +39,7 @@ fn main() { let _ = run_cmd!("git status"); if needs_commit_temp { - restore_workspace().unwrap(); + restore_workspace(true).unwrap(); } exit(1) } @@ -47,14 +47,16 @@ fn main() { println_cargo_style!("Done: All check passed!"); if needs_commit_temp { - restore_workspace().unwrap(); + restore_workspace(true).unwrap(); } } -fn restore_workspace() -> Result<(), i32> { +fn restore_workspace(undo_commit: bool) -> Result<(), i32> { run_cmd!("git reset --hard --quiet")?; - run_cmd!("git reset --soft HEAD~1 --quiet")?; - run_cmd!("git reset --quiet")?; + if undo_commit { + run_cmd!("git reset --soft HEAD~1 --quiet")?; + run_cmd!("git reset --quiet")?; + } Ok(()) } diff --git a/dev_tools/src/bin/docsify-sidebar-gen.rs b/dev_tools/src/bin/docsify-sidebar-gen.rs index e0f9370..ccd2641 100644 --- a/dev_tools/src/bin/docsify-sidebar-gen.rs +++ b/dev_tools/src/bin/docsify-sidebar-gen.rs @@ -1,4 +1,5 @@ use std::collections::BTreeMap; +use std::fmt::Write; use std::path::Path; use tools::println_cargo_style; @@ -56,7 +57,7 @@ fn gen_translation_sidebars() { } } -/// Build sidebar content: scan .md files in pages_dir and return a formatted sidebar string +/// Build sidebar content: scan .md files in `pages_dir` and return a formatted sidebar string fn build_sidebar_content(base_dir: &Path, pages_dir: &Path, sidebar_head: &str) -> String { let mut lines = String::from(sidebar_head); @@ -95,7 +96,7 @@ fn build_sidebar_content(base_dir: &Path, pages_dir: &Path, sidebar_head: &str) // Append root-level files for f in &root_files { - lines.push_str(&format!("* [{}]({})\n", f.title, f.link)); + let _ = writeln!(lines, "* [{}]({})", f.title, f.link); } // Append subdirectory groups @@ -104,9 +105,9 @@ fn build_sidebar_content(base_dir: &Path, pages_dir: &Path, sidebar_head: &str) sorted_entries.sort_by(|a, b| a.link.cmp(&b.link)); // Directory header with 2-space indent - lines.push_str(&format!("* {}\n", dir_name)); + let _ = writeln!(lines, "* {dir_name}"); for f in &sorted_entries { - lines.push_str(&format!(" * [{}]({})\n", f.title, f.link)); + let _ = writeln!(lines, " * [{}]({})", f.title, f.link); } } @@ -160,9 +161,10 @@ fn extract_title(path: &Path) -> String { } } // Fallback: use file stem - path.file_stem() - .map(|s| s.to_string_lossy().to_string()) - .unwrap_or_else(|| "Untitled".to_string()) + path.file_stem().map_or_else( + || "Untitled".to_string(), + |s| s.to_string_lossy().to_string(), + ) } fn find_git_repo() -> Option<std::path::PathBuf> { diff --git a/dev_tools/src/bin/refresh-docs.rs b/dev_tools/src/bin/refresh-docs.rs index ffa80a2..71143d1 100644 --- a/dev_tools/src/bin/refresh-docs.rs +++ b/dev_tools/src/bin/refresh-docs.rs @@ -49,7 +49,7 @@ fn gen_example_doc_module() { let template_str = template.to_string(); let template_str = template_str .lines() - .map(|line| line.trim_end()) + .map(str::trim_end) .collect::<Vec<_>>() .join("\n") + "\n"; @@ -91,19 +91,19 @@ impl ExampleContent { let cargo_toml = cargo_toml .lines() - .map(|line| format!("/// {}", line)) + .map(|line| format!("/// {line}")) .collect::<Vec<_>>() .join("\n"); let header = header .lines() - .map(|line| format!("/// {}", line)) + .map(|line| format!("/// {line}")) .collect::<Vec<_>>() .join("\n"); let code = code .lines() - .map(|line| format!("/// {}", line)) + .map(|line| format!("/// {line}")) .collect::<Vec<_>>() .join("\n"); diff --git a/dev_tools/src/bin/refresh-feature-mod.rs b/dev_tools/src/bin/refresh-feature-mod.rs index 6265e15..2255dbc 100644 --- a/dev_tools/src/bin/refresh-feature-mod.rs +++ b/dev_tools/src/bin/refresh-feature-mod.rs @@ -41,7 +41,7 @@ fn gen_feature_module() { let template_str = template.to_string(); let template_str = template_str .lines() - .map(|line| line.trim_end()) + .map(str::trim_end) .collect::<Vec<_>>() .join("\n") + "\n"; diff --git a/dev_tools/src/bin/test-examples.rs b/dev_tools/src/bin/test-examples.rs index 21abaef..ddf5f7c 100644 --- a/dev_tools/src/bin/test-examples.rs +++ b/dev_tools/src/bin/test-examples.rs @@ -75,7 +75,7 @@ fn run_all_tests(config: &TestConfig) -> (usize, usize) { /// Build the example binary, return true on success fn build_example(example_name: &str) -> bool { - let manifest = format!("examples/{}/Cargo.toml", example_name); + let manifest = format!("examples/{example_name}/Cargo.toml"); run_cmd!("cargo build --manifest-path {}", manifest).is_ok() } @@ -132,7 +132,7 @@ fn run_single_test(example_name: &str, test_case: &TestCase) -> bool { fn get_binary_name(example_name: &str) -> String { let base = example_name; if cfg!(target_os = "windows") { - format!("{}.exe", base) + format!("{base}.exe") } else { base.to_string() } diff --git a/dev_tools/src/lib.rs b/dev_tools/src/lib.rs index 1e62a2d..59eed0a 100644 --- a/dev_tools/src/lib.rs +++ b/dev_tools/src/lib.rs @@ -30,6 +30,11 @@ macro_rules! eprintln_cargo_style { }; } +/// Print a message in cargo style format, with bold green prefix. +/// +/// # Panics +/// +/// Panics if the prefix (text before the first `:`) exceeds 12 characters. pub fn println_cargo_style(str: impl Into<String>) { let s = str.into(); let (prefix, content) = if let Some(pos) = s.find(':') { @@ -38,16 +43,15 @@ pub fn println_cargo_style(str: impl Into<String>) { s[pos + 1..].trim_start().to_string(), ) } else { - ("".to_string(), s.trim().to_string()) + (String::new(), s.trim().to_string()) }; - if prefix.len() > 12 { - panic!( - "prefix length exceeds 12: '{}' has length {}", - prefix, - prefix.len() - ); - } + assert!( + prefix.len() <= 12, + "prefix length exceeds 12: '{}' has length {}", + prefix, + prefix.len() + ); let padding = " ".repeat(12 - prefix.len()); @@ -63,6 +67,15 @@ pub fn eprintln_cargo_style(str: impl Into<String>) { println!("{}: {}", "error".bold().bright_red(), str.into()); } +/// Run a shell command and return its exit status. +/// +/// # Panics +/// +/// Panics if the shell command cannot be spawned (e.g. the shell binary is not found). +/// +/// # Errors +/// +/// Returns `Err` with the exit code if the command finishes with a non-zero exit code. pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> { let shell = if cfg!(target_os = "windows") { "powershell" @@ -84,6 +97,7 @@ pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> { } } +#[must_use] pub fn cargo_tomls() -> Vec<std::path::PathBuf> { let mut cargo_tomls = Vec::new(); let mut dirs = vec![std::path::PathBuf::from(".")]; diff --git a/examples/example-argument-parse/src/main.rs b/examples/example-argument-parse/src/main.rs index f63fdad..316e52f 100644 --- a/examples/example-argument-parse/src/main.rs +++ b/examples/example-argument-parse/src/main.rs @@ -39,7 +39,7 @@ fn handle_transfer_parse(args: EntryTransfer) -> Next { // Name // ^^^^_ finally, pick positional arg .pick::<String>(()) - .after(|str| str.trim().replace(" ", "")) + .after(|str| str.trim().replace(' ', "")) // Unpack to tuple (is_dir, size, name) .unpack() // Convert into ResultFile @@ -60,7 +60,7 @@ fn handle_strict_transfer_parse(args: EntryStrictTransfer) -> Next { .pick_or::<usize>("--size", 1024 * 1024_usize) // Finally parse the positional argument; if not found, route to `ErrorNoNameProvided` .pick_or_route::<String, _>((), ErrorNoNameProvided::default().to_chain()) - .after(|str| str.trim().replace(" ", "")) + .after(|str| str.trim().replace(' ', "")) .unpack() } // Convert into ResultFile @@ -69,6 +69,7 @@ fn handle_strict_transfer_parse(args: EntryStrictTransfer) -> Next { result.to_chain() } +/// Renders the parsed transfer result (file/dir, size, name). #[renderer] fn render_result_file(result: ResultFile) { let (is_dir, size, name) = result.into(); @@ -80,6 +81,7 @@ fn render_result_file(result: ResultFile) { ) } +/// Renders the error when no name is provided. #[renderer] fn render_error_no_name_provided(_: ErrorNoNameProvided) { r_println!("Error: name is not provided") diff --git a/examples/example-async-support/src/main.rs b/examples/example-async-support/src/main.rs index 12b1b9c..5ded5e5 100644 --- a/examples/example-async-support/src/main.rs +++ b/examples/example-async-support/src/main.rs @@ -53,6 +53,7 @@ pub async fn handle_download(args: EntryDownload) -> Next { fake_download(file_name).await } +/// Renders the downloaded file name. #[renderer] // But renderers cannot use the `async` keyword pub fn render_downloaded(result: ResultDownloaded) { diff --git a/examples/example-basic/src/main.rs b/examples/example-basic/src/main.rs index d741c3b..3d94a4b 100644 --- a/examples/example-basic/src/main.rs +++ b/examples/example-basic/src/main.rs @@ -58,6 +58,7 @@ fn handle_greet(args: EntryGreet) -> Next { } // Define renderer `render_name`, used to render `ResultName` +/// Renders the greeting message with the provided name. #[renderer] fn render_name(name: ResultName) { r_println!("Hello, {}!", *name); diff --git a/examples/example-clap-binding/src/main.rs b/examples/example-clap-binding/src/main.rs index d3ad573..19d794b 100644 --- a/examples/example-clap-binding/src/main.rs +++ b/examples/example-clap-binding/src/main.rs @@ -87,6 +87,7 @@ pub struct EntryGreet { repeat: i32, } +/// Renders the greet output with optional repetition. #[renderer] fn render_greet(greet: EntryGreet) { let name = greet.name; @@ -102,9 +103,10 @@ fn render_greet(greet: EntryGreet) { r_println!("!"); } +/// Renders the error message when greet argument parsing fails. #[renderer] fn render_greet_parse_failed(err: ErrorGreetParsed) { - r_println!("{}", err.to_string()); + r_println!("{}", *err); } gen_program!(); diff --git a/examples/example-completion/src/main.rs b/examples/example-completion/src/main.rs index a34ccab..0c64d03 100644 --- a/examples/example-completion/src/main.rs +++ b/examples/example-completion/src/main.rs @@ -116,6 +116,7 @@ fn handle_greet(args: EntryGreet) -> Next { result } +/// Renders the greeting with the result name and repeat count. #[renderer] fn render_name(result: ResultName) { let (repeat, name) = result.inner; diff --git a/examples/example-custom-pickable/src/main.rs b/examples/example-custom-pickable/src/main.rs index 466ae43..a2aa662 100644 --- a/examples/example-custom-pickable/src/main.rs +++ b/examples/example-custom-pickable/src/main.rs @@ -14,7 +14,7 @@ //! Failed to parse address //! ``` -use mingling::{Groupped, macros::route, parser::Pickable, prelude::*}; +use mingling::{macros::route, parser::Pickable, prelude::*, Groupped}; // Define types that can be recognized by Mingling // ________________________ `Pickable` trait needs to implement Default @@ -32,7 +32,7 @@ impl Pickable for Address { type Output = Address; fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> { // Extract the raw string from Argument using the Flag - let raw: String = args.pick_argument(flag)?.to_string(); + let raw: String = args.pick_argument(flag)?.clone(); // Use TryFrom to parse the address Address::try_from(raw).ok() @@ -50,11 +50,13 @@ fn handle_connect(prev: EntryConnect) -> Next { connect.to_chain() } +/// Renders the connected address. #[renderer] fn render_address(addr: Address) { r_println!("Connected to \"{}\"", addr.to_string()); } +/// Renders the error message when address parsing fails. #[renderer] fn render_error_parse_address_failed(_: ErrorParseAddressFailed) { r_println!("Failed to parse address"); @@ -93,13 +95,13 @@ impl TryFrom<String> for Address { for (i, part) in ip_parts.iter().enumerate() { ip[i] = part .parse::<u8>() - .map_err(|_| format!("Invalid IP octet: {}", part))?; + .map_err(|_| format!("Invalid IP octet: {part}"))?; } // Parse port let port = port_str .parse::<u16>() - .map_err(|_| format!("Invalid port: {}", port_str))?; + .map_err(|_| format!("Invalid port: {port_str}"))?; Ok(Address { ip, port }) } diff --git a/examples/example-dispatch-tree/src/main.rs b/examples/example-dispatch-tree/src/main.rs index 08714d1..e087b1e 100644 --- a/examples/example-dispatch-tree/src/main.rs +++ b/examples/example-dispatch-tree/src/main.rs @@ -48,9 +48,10 @@ fn main() { // // it'll be collected automatically once the `dispatch_tree` feature is enabled // program.with_dispatcher(...); - program.exec_and_exit() + program.exec_and_exit(); } +/// Renders the confirmation message for the `cmd5` command. #[renderer] fn render_cmd5(_: Entry5) { r_println!("It's works!"); diff --git a/examples/example-enum-tag/src/main.rs b/examples/example-enum-tag/src/main.rs index 30439f5..ad9db82 100644 --- a/examples/example-enum-tag/src/main.rs +++ b/examples/example-enum-tag/src/main.rs @@ -81,6 +81,7 @@ fn handle_language_selection(args: EntryLanguageSelection) -> Next { lang } +/// Renders the selected programming language with its name and description. #[renderer] fn render_programming_language(lang: ProgrammingLanguages) { // You can use `enum_info()` to get the name and description of the current enum diff --git a/examples/example-error-handling/src/main.rs b/examples/example-error-handling/src/main.rs index d4d073e..ae79b97 100644 --- a/examples/example-error-handling/src/main.rs +++ b/examples/example-error-handling/src/main.rs @@ -59,29 +59,34 @@ fn handle_hello(args: EntryHello) -> Next { ResultName::new(name).to_render() } +/// Renders a successful greeting with the given name. #[renderer] fn render_result_name(name: ResultName) { r_println!("Hello, {}", *name); } +/// Renders the error when no name is provided. #[renderer] fn render_error_no_name_provided(_: ErrorNoNameProvided) { // Prompt when no name is provided r_println!("No name provided"); } +/// Renders the error when the name is already taken. #[renderer] fn render_error_name_not_available(_: ErrorNameNotAvailable) { // Prompt when name is already taken r_println!("Name not available"); } +/// Renders the error when the name exceeds the maximum length. #[renderer] fn render_error_name_too_long(len: ErrorNameTooLong) { // Prompt when name is too long, showing actual length r_println!("Name too long: {} > 10", *len); } +/// Renders the error when the dispatcher (subcommand) is not found. #[renderer] fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { // Prompt when command is not found, showing the input command diff --git a/examples/example-exitcode/src/main.rs b/examples/example-exitcode/src/main.rs index 178fa78..e1f60ee 100644 --- a/examples/example-exitcode/src/main.rs +++ b/examples/example-exitcode/src/main.rs @@ -44,6 +44,7 @@ fn handle_hello(args: EntryHello) -> Next { ResultName::new(name).to_render() } +/// Renders a successful greeting with the given name. #[renderer] fn render_result_name(name: ResultName) { r_println!("Hello, {}", *name); @@ -51,6 +52,7 @@ fn render_result_name(name: ResultName) { // Define renderer, render error message _____________ Inject exit code resource // / +/// Renders the error when no name is provided | #[renderer] // vvvvvvvvvvvvv fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ExitCode) { ec.exit_code = 1; diff --git a/examples/example-general-renderer/src/main.rs b/examples/example-general-renderer/src/main.rs index 3ba4433..c923d28 100644 --- a/examples/example-general-renderer/src/main.rs +++ b/examples/example-general-renderer/src/main.rs @@ -18,7 +18,7 @@ //! ``` use mingling::prelude::*; -use mingling::{Groupped, parser::Picker, setup::GeneralRendererSetup}; +use mingling::{parser::Picker, setup::GeneralRendererSetup, Groupped}; use serde::Serialize; dispatcher!("render", CMDRender => EntryRender); @@ -28,7 +28,7 @@ fn main() { // Add `GeneralRendererSetup` to receive user input `--json` `--yaml` parameters program.with_setup(GeneralRendererSetup); program.with_dispatcher(CMDRender); - program.exec(); + let _ = program.exec(); } // --------- IMPORTANT --------- @@ -61,7 +61,7 @@ fn parse_render(prev: EntryRender) -> Next { Info { name, age }.to_render() } -// Implement default renderer for when general_renderer is not specified +/// Implement default renderer for when general_renderer is not specified #[renderer] fn render_info(prev: Info) { r_println!("{} is {} years old", prev.name, prev.age); diff --git a/examples/example-hook/src/main.rs b/examples/example-hook/src/main.rs index 373c76d..d6a2dd0 100644 --- a/examples/example-hook/src/main.rs +++ b/examples/example-hook/src/main.rs @@ -31,17 +31,17 @@ fn main() { program.with_hook( ProgramHook::<ThisProgram>::empty() .on_begin(|| println!("[DEBUG] Program is begin")) - .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {:?}", args)) - .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {:?}", c)) + .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {args:?}")) + .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {c:?}")) .on_pre_chain(|c: &_, _| { - println!("[DEBUG] Pre chain: {}", c); + println!("[DEBUG] Pre chain: {c}"); }) .on_post_chain(|any_output| println!("[DEBUG] Post chain: {}", any_output.member_id)) .on_finish(|| { println!("[DEBUG] Loop end"); 0 // Override exit code }) - .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {}", c)) + .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {c}")) .on_post_render(|_| println!("[DEBUG] Post render")), ); // --------- IMPORTANT --------- @@ -63,6 +63,7 @@ fn handle_greet(args: EntryGreet) -> Next { name } +/// Renders the greeting message with the provided name. #[renderer] fn render_name(name: ResultName) { r_println!("Hello, {}!", *name); diff --git a/examples/example-panic-unwind/src/main.rs b/examples/example-panic-unwind/src/main.rs index 18cf4d6..bb25541 100644 --- a/examples/example-panic-unwind/src/main.rs +++ b/examples/example-panic-unwind/src/main.rs @@ -29,11 +29,10 @@ fn main() { program.stdout_setting.silence_panic = true; // Define a hook to output &ProgramPanic when a Panic occurs - program - .with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {}", info))); + program.with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {info}"))); // --------- IMPORTANT --------- - program.exec(); + let _ = program.exec(); } #[chain] @@ -48,6 +47,7 @@ fn handle_panic(prev: EntryPanic) -> Next { } } +/// Renders the message when no panic occurs. #[renderer] fn render(_: NotPanic) { r_println!("Program not panic"); diff --git a/examples/example-repl-basic/src/main.rs b/examples/example-repl-basic/src/main.rs index 630c419..d44c92a 100644 --- a/examples/example-repl-basic/src/main.rs +++ b/examples/example-repl-basic/src/main.rs @@ -66,7 +66,7 @@ fn main() { // Add hooks to handle REPL-related events program.with_hook(ProgramHook::empty().on_repl_begin(|| { // Print welcome message - println!("Welcome!") + println!("Welcome!"); })); // Start the REPL loop @@ -118,11 +118,11 @@ fn handle_ls(_prev: EntryLs, current_dir: &ResCurrentDir) -> Next { let dir = ¤t_dir.dir; let entries: Vec<String> = std::fs::read_dir(dir) .into_iter() - .flat_map(|rd| rd.filter_map(|e| e.ok())) + .flat_map(|rd| rd.filter_map(std::result::Result::ok)) .map(|e| { let name = e.file_name().to_string_lossy().to_string(); if e.file_type().map(|t| t.is_dir()).unwrap_or(false) { - format!("{}/", name) + format!("{name}/") } else { name } @@ -133,11 +133,11 @@ fn handle_ls(_prev: EntryLs, current_dir: &ResCurrentDir) -> Next { ResultList::new(entries).to_render() } -// Render ResultList data +/// Render ResultList data #[renderer] fn render_list(list: ResultList) { for item in list.inner { - r_println!("{}", item) + r_println!("{}", item); } } @@ -151,20 +151,21 @@ fn handle_exit( repl.exit = true; } -// Handle clear command event +/// Handle clear command event #[chain] fn handle_clear(_prev: EntryClear) { // Clear the terminal screen print!("\x1B[2J\x1B[1;1H"); } -// Handle path not found event +/// Handle path not found event #[renderer] fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) { r_println!("Directory not found: {}", err.inner.display()) } -// Handle dispatcher not found event +/// Handle dispatcher not found event +/// Renders the error when a command is not found. #[renderer] fn dispatcher_not_found(prev: ErrorDispatcherNotFound) { r_println!("Command not found: \"{}\"", prev.join(", ")) diff --git a/examples/example-resources/src/main.rs b/examples/example-resources/src/main.rs index ae2c8f2..6c5bf84 100644 --- a/examples/example-resources/src/main.rs +++ b/examples/example-resources/src/main.rs @@ -56,6 +56,7 @@ fn render_modify_current(args: EntryModifyCurrent, current_dir: &mut ResCurrentD // Define renderer for output current path _____________ Injected resource // / +/// Renders the current directory path. | #[renderer] // vvvvvvvvvvvvvv fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) { r_println!("Current directory: {}", current_dir.current_dir.display()); diff --git a/examples/example-setup/src/main.rs b/examples/example-setup/src/main.rs index c445276..cd432dd 100644 --- a/examples/example-setup/src/main.rs +++ b/examples/example-setup/src/main.rs @@ -2,7 +2,7 @@ //! //! > This example demonstrates how to build a custom Setup for modular management of project components -use mingling::{Program, macros::program_setup, prelude::*}; +use mingling::{macros::program_setup, prelude::*, Program}; fn main() { let mut program = ThisProgram::new(); diff --git a/examples/example-unit-test/src/main.rs b/examples/example-unit-test/src/main.rs index 62c7fbf..7808d0d 100644 --- a/examples/example-unit-test/src/main.rs +++ b/examples/example-unit-test/src/main.rs @@ -89,26 +89,31 @@ fn handle_hello(args: EntryHello) -> Next { ResultName::new(name).to_render() } +/// Renders a successful greeting with the given name. #[renderer] fn render_result_name(name: ResultName) -> String { r_println!("Hello, {}!", *name); } +/// Renders the error when no name is provided. #[renderer] fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String { r_println!("No name provided"); } +/// Renders the error when the name is already taken. #[renderer] fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String { r_println!("Name not available"); } +/// Renders the error when the name exceeds the maximum length. #[renderer] fn render_error_name_too_long(len: ErrorNameTooLong) -> String { r_println!("Name too long: {} > 10", *len); } +/// Renders the error when the dispatcher (subcommand) is not found. #[renderer] fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { r_println!("Command not found: \"{}\"", err.inner.join(" ")); diff --git a/mingling/src/example_docs.rs b/mingling/src/example_docs.rs index b665485..7e87d9c 100644 --- a/mingling/src/example_docs.rs +++ b/mingling/src/example_docs.rs @@ -57,7 +57,7 @@ /// // Name /// // ^^^^_ finally, pick positional arg /// .pick::<String>(()) -/// .after(|str| str.trim().replace(" ", "")) +/// .after(|str| str.trim().replace(' ', "")) /// // Unpack to tuple (is_dir, size, name) /// .unpack() /// // Convert into ResultFile @@ -78,7 +78,7 @@ /// .pick_or::<usize>("--size", 1024 * 1024_usize) /// // Finally parse the positional argument; if not found, route to `ErrorNoNameProvided` /// .pick_or_route::<String, _>((), ErrorNoNameProvided::default().to_chain()) -/// .after(|str| str.trim().replace(" ", "")) +/// .after(|str| str.trim().replace(' ', "")) /// .unpack() /// } /// // Convert into ResultFile @@ -87,6 +87,7 @@ /// result.to_chain() /// } /// +/// /// Renders the parsed transfer result (file/dir, size, name). /// #[renderer] /// fn render_result_file(result: ResultFile) { /// let (is_dir, size, name) = result.into(); @@ -98,6 +99,7 @@ /// ) /// } /// +/// /// Renders the error when no name is provided. /// #[renderer] /// fn render_error_no_name_provided(_: ErrorNoNameProvided) { /// r_println!("Error: name is not provided") @@ -189,6 +191,7 @@ pub mod example_argument_parse {} /// fake_download(file_name).await /// } /// +/// /// Renders the downloaded file name. /// #[renderer] /// // But renderers cannot use the `async` keyword /// pub fn render_downloaded(result: ResultDownloaded) { @@ -277,6 +280,7 @@ pub mod example_async_support {} /// } /// /// // Define renderer `render_name`, used to render `ResultName` +/// /// Renders the greeting message with the provided name. /// #[renderer] /// fn render_name(name: ResultName) { /// r_println!("Hello, {}!", *name); @@ -403,6 +407,7 @@ pub mod example_basic {} /// repeat: i32, /// } /// +/// /// Renders the greet output with optional repetition. /// #[renderer] /// fn render_greet(greet: EntryGreet) { /// let name = greet.name; @@ -418,9 +423,10 @@ pub mod example_basic {} /// r_println!("!"); /// } /// +/// /// Renders the error message when greet argument parsing fails. /// #[renderer] /// fn render_greet_parse_failed(err: ErrorGreetParsed) { -/// r_println!("{}", err.to_string()); +/// r_println!("{}", *err); /// } /// /// gen_program!(); @@ -575,6 +581,7 @@ pub mod example_clap_binding {} /// result /// } /// +/// /// Renders the greeting with the result name and repeat count. /// #[renderer] /// fn render_name(result: ResultName) { /// let (repeat, name) = result.inner; @@ -619,7 +626,7 @@ pub mod example_completion {} /// /// Source code (./src/main.rs) /// ```ignore -/// use mingling::{Groupped, macros::route, parser::Pickable, prelude::*}; +/// use mingling::{macros::route, parser::Pickable, prelude::*, Groupped}; /// /// // Define types that can be recognized by Mingling /// // ________________________ `Pickable` trait needs to implement Default @@ -637,7 +644,7 @@ pub mod example_completion {} /// type Output = Address; /// fn pick(args: &mut mingling::parser::Argument, flag: mingling::Flag) -> Option<Self::Output> { /// // Extract the raw string from Argument using the Flag -/// let raw: String = args.pick_argument(flag)?.to_string(); +/// let raw: String = args.pick_argument(flag)?.clone(); /// /// // Use TryFrom to parse the address /// Address::try_from(raw).ok() @@ -655,11 +662,13 @@ pub mod example_completion {} /// connect.to_chain() /// } /// +/// /// Renders the connected address. /// #[renderer] /// fn render_address(addr: Address) { /// r_println!("Connected to \"{}\"", addr.to_string()); /// } /// +/// /// Renders the error message when address parsing fails. /// #[renderer] /// fn render_error_parse_address_failed(_: ErrorParseAddressFailed) { /// r_println!("Failed to parse address"); @@ -698,13 +707,13 @@ pub mod example_completion {} /// for (i, part) in ip_parts.iter().enumerate() { /// ip[i] = part /// .parse::<u8>() -/// .map_err(|_| format!("Invalid IP octet: {}", part))?; +/// .map_err(|_| format!("Invalid IP octet: {part}"))?; /// } /// /// // Parse port /// let port = port_str /// .parse::<u16>() -/// .map_err(|_| format!("Invalid port: {}", port_str))?; +/// .map_err(|_| format!("Invalid port: {port_str}"))?; /// /// Ok(Address { ip, port }) /// } @@ -796,9 +805,10 @@ pub mod example_custom_pickable {} /// // // it'll be collected automatically once the `dispatch_tree` feature is enabled /// // program.with_dispatcher(...); /// -/// program.exec_and_exit() +/// program.exec_and_exit(); /// } /// +/// /// Renders the confirmation message for the `cmd5` command. /// #[renderer] /// fn render_cmd5(_: Entry5) { /// r_println!("It's works!"); @@ -908,6 +918,7 @@ pub mod example_dispatch_tree {} /// lang /// } /// +/// /// Renders the selected programming language with its name and description. /// #[renderer] /// fn render_programming_language(lang: ProgrammingLanguages) { /// // You can use `enum_info()` to get the name and description of the current enum @@ -1005,29 +1016,34 @@ pub mod example_enum_tag {} /// ResultName::new(name).to_render() /// } /// +/// /// Renders a successful greeting with the given name. /// #[renderer] /// fn render_result_name(name: ResultName) { /// r_println!("Hello, {}", *name); /// } /// +/// /// Renders the error when no name is provided. /// #[renderer] /// fn render_error_no_name_provided(_: ErrorNoNameProvided) { /// // Prompt when no name is provided /// r_println!("No name provided"); /// } /// +/// /// Renders the error when the name is already taken. /// #[renderer] /// fn render_error_name_not_available(_: ErrorNameNotAvailable) { /// // Prompt when name is already taken /// r_println!("Name not available"); /// } /// +/// /// Renders the error when the name exceeds the maximum length. /// #[renderer] /// fn render_error_name_too_long(len: ErrorNameTooLong) { /// // Prompt when name is too long, showing actual length /// r_println!("Name too long: {} > 10", *len); /// } /// +/// /// Renders the error when the dispatcher (subcommand) is not found. /// #[renderer] /// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { /// // Prompt when command is not found, showing the input command @@ -1102,6 +1118,7 @@ pub mod example_error_handling {} /// ResultName::new(name).to_render() /// } /// +/// /// Renders a successful greeting with the given name. /// #[renderer] /// fn render_result_name(name: ResultName) { /// r_println!("Hello, {}", *name); @@ -1109,6 +1126,7 @@ pub mod example_error_handling {} /// /// // Define renderer, render error message _____________ Inject exit code resource /// // / +/// /// Renders the error when no name is provided | /// #[renderer] // vvvvvvvvvvvvv /// fn render_error_no_name_provided(_: ErrorNoNameProvided, ec: &mut ExitCode) { /// ec.exit_code = 1; @@ -1160,7 +1178,7 @@ pub mod example_exitcode {} /// Source code (./src/main.rs) /// ```ignore /// use mingling::prelude::*; -/// use mingling::{Groupped, parser::Picker, setup::GeneralRendererSetup}; +/// use mingling::{parser::Picker, setup::GeneralRendererSetup, Groupped}; /// use serde::Serialize; /// /// dispatcher!("render", CMDRender => EntryRender); @@ -1170,7 +1188,7 @@ pub mod example_exitcode {} /// // Add `GeneralRendererSetup` to receive user input `--json` `--yaml` parameters /// program.with_setup(GeneralRendererSetup); /// program.with_dispatcher(CMDRender); -/// program.exec(); +/// let _ = program.exec(); /// } /// /// // --------- IMPORTANT --------- @@ -1203,7 +1221,7 @@ pub mod example_exitcode {} /// Info { name, age }.to_render() /// } /// -/// // Implement default renderer for when general_renderer is not specified +/// /// Implement default renderer for when general_renderer is not specified /// #[renderer] /// fn render_info(prev: Info) { /// r_println!("{} is {} years old", prev.name, prev.age); @@ -1314,17 +1332,17 @@ pub mod example_help {} /// program.with_hook( /// ProgramHook::<ThisProgram>::empty() /// .on_begin(|| println!("[DEBUG] Program is begin")) -/// .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {:?}", args)) -/// .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {:?}", c)) +/// .on_pre_dispatch(|args| println!("[DEBUG] Pre dispatch: {args:?}")) +/// .on_post_dispatch(|c: &_| println!("[DEBUG] Post dispatch: {c:?}")) /// .on_pre_chain(|c: &_, _| { -/// println!("[DEBUG] Pre chain: {}", c); +/// println!("[DEBUG] Pre chain: {c}"); /// }) /// .on_post_chain(|any_output| println!("[DEBUG] Post chain: {}", any_output.member_id)) /// .on_finish(|| { /// println!("[DEBUG] Loop end"); /// 0 // Override exit code /// }) -/// .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {}", c)) +/// .on_pre_render(|c: &_, _| println!("[DEBUG] Pre render: {c}")) /// .on_post_render(|_| println!("[DEBUG] Post render")), /// ); /// // --------- IMPORTANT --------- @@ -1346,6 +1364,7 @@ pub mod example_help {} /// name /// } /// +/// /// Renders the greeting message with the provided name. /// #[renderer] /// fn render_name(name: ResultName) { /// r_println!("Hello, {}!", *name); @@ -1446,11 +1465,10 @@ pub mod example_implicit_dispatcher {} /// program.stdout_setting.silence_panic = true; /// /// // Define a hook to output &ProgramPanic when a Panic occurs -/// program -/// .with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {}", info))); +/// program.with_hook(ProgramHook::empty().on_exec_panic(|info| println!("Program panic: {info}"))); /// // --------- IMPORTANT --------- /// -/// program.exec(); +/// let _ = program.exec(); /// } /// /// #[chain] @@ -1465,6 +1483,7 @@ pub mod example_implicit_dispatcher {} /// } /// } /// +/// /// Renders the message when no panic occurs. /// #[renderer] /// fn render(_: NotPanic) { /// r_println!("Program not panic"); @@ -1558,7 +1577,7 @@ pub mod example_panic_unwind {} /// // Add hooks to handle REPL-related events /// program.with_hook(ProgramHook::empty().on_repl_begin(|| { /// // Print welcome message -/// println!("Welcome!") +/// println!("Welcome!"); /// })); /// /// // Start the REPL loop @@ -1610,11 +1629,11 @@ pub mod example_panic_unwind {} /// let dir = ¤t_dir.dir; /// let entries: Vec<String> = std::fs::read_dir(dir) /// .into_iter() -/// .flat_map(|rd| rd.filter_map(|e| e.ok())) +/// .flat_map(|rd| rd.filter_map(std::result::Result::ok)) /// .map(|e| { /// let name = e.file_name().to_string_lossy().to_string(); /// if e.file_type().map(|t| t.is_dir()).unwrap_or(false) { -/// format!("{}/", name) +/// format!("{name}/") /// } else { /// name /// } @@ -1625,11 +1644,11 @@ pub mod example_panic_unwind {} /// ResultList::new(entries).to_render() /// } /// -/// // Render ResultList data +/// /// Render ResultList data /// #[renderer] /// fn render_list(list: ResultList) { /// for item in list.inner { -/// r_println!("{}", item) +/// r_println!("{}", item); /// } /// } /// @@ -1643,20 +1662,21 @@ pub mod example_panic_unwind {} /// repl.exit = true; /// } /// -/// // Handle clear command event +/// /// Handle clear command event /// #[chain] /// fn handle_clear(_prev: EntryClear) { /// // Clear the terminal screen /// print!("\x1B[2J\x1B[1;1H"); /// } /// -/// // Handle path not found event +/// /// Handle path not found event /// #[renderer] /// fn render_error_directory_not_exist(err: ErrorDirectoryNotExist) { /// r_println!("Directory not found: {}", err.inner.display()) /// } /// -/// // Handle dispatcher not found event +/// /// Handle dispatcher not found event +/// /// Renders the error when a command is not found. /// #[renderer] /// fn dispatcher_not_found(prev: ErrorDispatcherNotFound) { /// r_println!("Command not found: \"{}\"", prev.join(", ")) @@ -1737,6 +1757,7 @@ pub mod example_repl_basic {} /// /// // Define renderer for output current path _____________ Injected resource /// // / +/// /// Renders the current directory path. | /// #[renderer] // vvvvvvvvvvvvvv /// fn render_current(_: EntryCurrent, current_dir: &ResCurrentDir) { /// r_println!("Current directory: {}", current_dir.current_dir.display()); @@ -1762,7 +1783,7 @@ pub mod example_resources {} /// /// Source code (./src/main.rs) /// ```ignore -/// use mingling::{Program, macros::program_setup, prelude::*}; +/// use mingling::{macros::program_setup, prelude::*, Program}; /// /// fn main() { /// let mut program = ThisProgram::new(); @@ -1897,26 +1918,31 @@ pub mod example_setup {} /// ResultName::new(name).to_render() /// } /// +/// /// Renders a successful greeting with the given name. /// #[renderer] /// fn render_result_name(name: ResultName) -> String { /// r_println!("Hello, {}!", *name); /// } /// +/// /// Renders the error when no name is provided. /// #[renderer] /// fn render_error_no_name_provided(_: ErrorNoNameProvided) -> String { /// r_println!("No name provided"); /// } /// +/// /// Renders the error when the name is already taken. /// #[renderer] /// fn render_error_name_not_available(_: ErrorNameNotAvailable) -> String { /// r_println!("Name not available"); /// } /// +/// /// Renders the error when the name exceeds the maximum length. /// #[renderer] /// fn render_error_name_too_long(len: ErrorNameTooLong) -> String { /// r_println!("Name too long: {} > 10", *len); /// } /// +/// /// Renders the error when the dispatcher (subcommand) is not found. /// #[renderer] /// fn render_dispatcher_not_found(err: ErrorDispatcherNotFound) { /// r_println!("Command not found: \"{}\"", err.inner.join(" ")); diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs index ee01fb7..db9b063 100644 --- a/mingling/src/lib.rs +++ b/mingling/src/lib.rs @@ -100,11 +100,11 @@ pub mod macros { /// Used to create a wrapper type for use with `Chain` and `Renderer` pub use mingling_macros::pack; #[cfg(feature = "comp")] - /// Internal macro for 'gen_program' used to finally generate the completion structure + /// Internal macro for '`gen_program`' used to finally generate the completion structure pub use mingling_macros::program_comp_gen; - /// Internal macro for 'gen_program' used to finally generate the fallback + /// Internal macro for '`gen_program`' used to finally generate the fallback pub use mingling_macros::program_fallback_gen; - /// Internal macro for 'gen_program' used to finally generate the program + /// Internal macro for '`gen_program`' used to finally generate the program pub use mingling_macros::program_final_gen; /// Used to generate program setup #[cfg(feature = "extra_macros")] @@ -115,7 +115,7 @@ pub mod macros { pub use mingling_macros::r_println; /// Used to register a chain pub use mingling_macros::register_chain; - /// Used to register a dispatcher for dispatch_tree feature + /// Used to register a dispatcher for `dispatch_tree` feature pub use mingling_macros::register_dispatcher; /// Used to register a help pub use mingling_macros::register_help; @@ -136,7 +136,7 @@ pub mod macros { pub use mingling_macros::suggest_enum; } -/// derive macro EnumTag +/// derive macro `EnumTag` pub use mingling_macros::EnumTag; /// derive macro Groupped diff --git a/mingling/src/parser/args.rs b/mingling/src/parser/args.rs index 2dc0feb..23275c2 100644 --- a/mingling/src/parser/args.rs +++ b/mingling/src/parser/args.rs @@ -11,7 +11,10 @@ pub struct Argument { impl From<Vec<&str>> for Argument { fn from(vec: Vec<&str>) -> Self { Argument { - vec: vec.into_iter().map(|s| s.to_string()).collect(), + vec: vec + .into_iter() + .map(std::string::ToString::to_string) + .collect(), } } } @@ -85,18 +88,17 @@ impl Argument { } let flag: Flag = flag.into(); - if !flag.is_empty() { - // Has any flag - for argument in flag.iter() { - let value = special_argument!(self.vec, argument); - if value.is_some() { - return value; - } - } - } else { + if flag.is_empty() { // No flag return Some(self.vec.remove(0)); } + // Has any flag + for argument in flag.iter() { + let value = special_argument!(self.vec, argument); + if value.is_some() { + return value; + } + } None } @@ -135,15 +137,7 @@ impl Argument { } let flag: Flag = flag.into(); - if !flag.is_empty() { - // Has any flag - for argument in flag.iter() { - let enabled = special_flag!(self.vec, argument); - if enabled { - return enabled; - } - } - } else { + if flag.is_empty() { let first = self.vec.remove(0); let first_lower = first.to_lowercase(); let trimmed = first_lower.trim(); @@ -154,6 +148,13 @@ impl Argument { }; return result; } + // Has any flag + for argument in flag.iter() { + let enabled = special_flag!(self.vec, argument); + if enabled { + return enabled; + } + } false } @@ -167,6 +168,7 @@ impl Argument { /// /// This method filters out all command-line style flags from the arguments, /// returning a new `Argument` instance containing only non-flag arguments. + #[must_use] pub fn strip_all_flags(mut self) -> Self { self.vec.retain(|f| !f.starts_with('-')); self diff --git a/mingling/src/parser/picker.rs b/mingling/src/parser/picker.rs index b0cdb70..21ba9a6 100644 --- a/mingling/src/parser/picker.rs +++ b/mingling/src/parser/picker.rs @@ -71,15 +71,12 @@ impl Picker { where TNext: Pickable<Output = TNext> + Default, { - let v = match TNext::pick(&mut self.args, val.into()) { - Some(value) => value, - None => { - return PickWithRoute1 { - args: self.args, - val_1: TNext::default(), - route: Some(route), - }; - } + let Some(v) = TNext::pick(&mut self.args, val.into()) else { + return PickWithRoute1 { + args: self.args, + val_1: TNext::default(), + route: Some(route), + }; }; PickWithRoute1 { args: self.args, @@ -112,6 +109,7 @@ impl Picker { /// Takes a closure that receives the current `Argument` and returns a new `Argument`. /// The returned `Argument` replaces the original arguments in the builder. /// This method can be used to modify or transform the parsed arguments before extracting values. + #[must_use] pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { self.args = operation(self.args); self @@ -141,7 +139,7 @@ pub trait Pickable { // Non-routed Pick structs (no R parameter, no route field) /// Internal macro: generates the struct definition and common methods -/// (after, after_or_route, operate_args) for non-routed Pick structs. +/// (after, `after_or_route`, `operate_args`) for non-routed Pick structs. macro_rules! define_pick_struct { ($n:ident $final:ident $final_val:ident $route_self:ident $($T:ident $val:ident),+ $(,)?) => { #[doc(hidden)] @@ -163,6 +161,7 @@ macro_rules! define_pick_struct { /// Takes a closure that receives the last extracted value and returns a new value of the same type. /// The transformed value replaces the original value in the builder. /// This method can be used to modify or validate the extracted value before final unpacking. + #[must_use] pub fn after<F>(mut self, mut edit: F) -> Self where F: FnMut($final) -> $final, @@ -177,6 +176,7 @@ macro_rules! define_pick_struct { /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder. /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling. /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved. + #[must_use] pub fn after_or_route<F, R>(mut self, mut edit: F) -> $route_self<$($T,)+ R> where F: FnMut(&$final) -> Result<$final, R>, @@ -205,6 +205,7 @@ macro_rules! define_pick_struct { /// Takes a closure that receives the current `Argument` and returns a new `Argument`. /// The returned `Argument` replaces the original arguments in the builder. /// This method can be used to modify or transform the parsed arguments before extracting values. + #[must_use] pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { self.args = operation(self.args); self @@ -359,17 +360,14 @@ macro_rules! impl_pick_next { where TNext: Pickable<Output = TNext> + Default, { - let v = match TNext::pick(&mut self.args, val.into()) { - Some(value) => value, - None => { - return $route_next { - args: self.args, - $($val: self.$val,)+ - $next_val: TNext::default(), - route: Some(route), - }; - } - }; + let Some(v) = TNext::pick(&mut self.args, val.into()) else { + return $route_next { + args: self.args, + $($val: self.$val,)+ + $next_val: TNext::default(), + route: Some(route), + }; + }; $route_next { args: self.args, $($val: self.$val,)+ @@ -413,7 +411,7 @@ impl_pick_next! { Pick11 Pick12 val_12 PickWithRoute12 T1 val_1, T2 val_2, T3 va // Routed PickWithRoute structs (with R parameter, route field) /// Internal macro: generates the routed struct definition and common methods -/// (after, after_or_route, operate_args) for PickWithRoute structs. +/// (after, `after_or_route`, `operate_args`) for `PickWithRoute` structs. macro_rules! define_pick_with_route_struct { ($n:ident $final:ident $final_val:ident $($T:ident $val:ident),+) => { #[doc(hidden)] @@ -436,6 +434,7 @@ macro_rules! define_pick_with_route_struct { /// Takes a closure that receives the last extracted value and returns a new value of the same type. /// The transformed value replaces the original value in the builder. /// This method can be used to modify or validate the extracted value before final unpacking. + #[must_use] pub fn after<F>(mut self, mut edit: F) -> Self where F: FnMut($final) -> $final, @@ -450,6 +449,7 @@ macro_rules! define_pick_with_route_struct { /// If the closure returns `Ok(new_value)`, the new value replaces the original value in the builder. /// If the closure returns `Err(route)`, the provided `route` is stored in the builder for later error handling. /// If a route was already stored from a previous `pick_or_route` call, the existing route is preserved. + #[must_use] pub fn after_or_route<F>(mut self, mut edit: F) -> Self where F: FnMut(&$final) -> Result<$final, R>, @@ -475,6 +475,7 @@ macro_rules! define_pick_with_route_struct { /// Takes a closure that receives the current `Argument` and returns a new `Argument`. /// The returned `Argument` replaces the original arguments in the builder. /// This method can be used to modify or transform the parsed arguments before extracting values. + #[must_use] pub fn operate_args<F: FnOnce(Argument) -> Argument>(mut self, operation: F) -> Self { self.args = operation(self.args); self @@ -483,7 +484,7 @@ macro_rules! define_pick_with_route_struct { }; } -/// Internal macro: generates `From` impl for routed PickWithRouteN into a tuple. +/// Internal macro: generates `From` impl for routed `PickWithRouteN` into a tuple. macro_rules! impl_pick_with_route_from_tuple { ($n:ident $($T:ident $val:ident),+) => { impl<$($T,)+ R> From<$n<$($T,)+ R>> for ($($T,)+) @@ -497,7 +498,7 @@ macro_rules! impl_pick_with_route_from_tuple { }; } -/// Internal macro: generates `unpack` and `unpack_directly` for routed PickWithRouteN (N >= 2). +/// Internal macro: generates `unpack` and `unpack_directly` for routed `PickWithRouteN` (N >= 2). macro_rules! impl_pick_with_route_unpack_tuple { ($n:ident $($T:ident $val:ident),+) => { impl<$($T,)+ R> $n<$($T,)+ R> @@ -508,6 +509,10 @@ macro_rules! impl_pick_with_route_unpack_tuple { /// /// Returns `Ok((T1, T2, ...))` if no route was stored. /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. + /// + /// # Errors + /// + /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. pub fn unpack(self) -> Result<($($T,)+), R> { match self.route { Some(route) => Err(route), @@ -518,6 +523,7 @@ macro_rules! impl_pick_with_route_unpack_tuple { /// Unpacks the builder into a tuple of extracted values. /// /// Returns the tuple of extracted values regardless of route state. + #[must_use] pub fn unpack_directly(self) -> ($($T,)+) { ($(self.$val,)+) } @@ -546,6 +552,10 @@ where /// /// Returns `Ok(T1)` if no route was stored. /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. + /// + /// # Errors + /// + /// Returns `Err(R)` if a route was stored via `pick_or_route` or `after_or_route`. pub fn unpack(self) -> Result<T1, R> { match self.route { Some(route) => Err(route), @@ -556,6 +566,7 @@ where /// Unpacks the builder into the extracted value. /// /// Returns the extracted value regardless of route state. + #[must_use] pub fn unpack_directly(self) -> T1 { self.val_1 } @@ -650,6 +661,7 @@ macro_rules! impl_pick_with_route_next { /// /// If a route was already stored from a previous `pick_or_route` or `after_or_route` call, /// the existing route is preserved and the new `route` parameter is ignored. + #[allow(clippy::manual_let_else)] pub fn pick_or_route<TNext>(mut self, val: impl Into<mingling_core::Flag>, route: R) -> $next<$($T,)+ TNext, R> where TNext: Pickable<Output = TNext> + Default, diff --git a/mingling/src/parser/picker/bools.rs b/mingling/src/parser/picker/bools.rs index aa2335a..ede8812 100644 --- a/mingling/src/parser/picker/bools.rs +++ b/mingling/src/parser/picker/bools.rs @@ -37,10 +37,12 @@ impl std::ops::Deref for Yes { } impl Yes { + #[must_use] pub fn is_yes(&self) -> bool { matches!(self, Yes::Yes) } + #[must_use] pub fn is_no(&self) -> bool { matches!(self, Yes::No) } @@ -92,10 +94,12 @@ impl std::ops::Deref for True { } impl True { + #[must_use] pub fn is_true(&self) -> bool { matches!(self, True::True) } + #[must_use] pub fn is_false(&self) -> bool { matches!(self, True::False) } diff --git a/mingling/src/parser/picker/builtin.rs b/mingling/src/parser/picker/builtin.rs index e7a178d..6194955 100644 --- a/mingling/src/parser/picker/builtin.rs +++ b/mingling/src/parser/picker/builtin.rs @@ -68,7 +68,7 @@ impl Pickable for usize { let picked = args.pick_argument(flag)?; let size_parse = Size::from_str(picked.as_str()); match size_parse { - Ok(size) => Some(size.bytes() as usize), + Ok(size) => usize::try_from(size.bytes()).ok(), Err(_) => None, } } @@ -84,7 +84,7 @@ impl Pickable for Vec<usize> { for picked in picked_vec { let size_parse = Size::from_str(picked.as_str()); match size_parse { - Ok(size) => result.push(size.bytes() as usize), + Ok(size) => result.push(usize::try_from(size.bytes()).unwrap_or(usize::MAX)), Err(_) => return None, } } diff --git a/mingling/src/parser/picker/path.rs b/mingling/src/parser/picker/path.rs index c97250f..961542e 100644 --- a/mingling/src/parser/picker/path.rs +++ b/mingling/src/parser/picker/path.rs @@ -91,7 +91,7 @@ impl<T: Into<PathBuf>> PathChecker for T where T: Into<PathBuf> {} fn check_paths(path: impl Into<Vec<PathBuf>>, rule: &PathCheckRule) -> Result<(), ()> { let paths = path.into(); - for p in paths.iter() { + for p in &paths { check_exist(p, rule)?; check_type(p, rule)?; } diff --git a/mingling/src/parser/picker/path/rule.rs b/mingling/src/parser/picker/path/rule.rs index 07df705..bf5cab3 100644 --- a/mingling/src/parser/picker/path/rule.rs +++ b/mingling/src/parser/picker/path/rule.rs @@ -25,6 +25,7 @@ pub struct PathTypeCheck { impl PathCheckRule { /// Creates a new `PathCheckRule` with default values + #[must_use] pub fn new() -> Self { Self { exist_check: None, @@ -33,6 +34,7 @@ impl PathCheckRule { } /// Allows the path to be a file + #[must_use] pub fn allow_file(self) -> Self { match self.type_check { Some(type_check) => Self { @@ -55,6 +57,7 @@ impl PathCheckRule { } /// Allows the path to be a directory + #[must_use] pub fn allow_dir(self) -> Self { match self.type_check { Some(type_check) => Self { @@ -77,6 +80,7 @@ impl PathCheckRule { } /// Allows the path to be a symlink + #[must_use] pub fn allow_symlink(self) -> Self { match self.type_check { Some(type_check) => Self { @@ -99,6 +103,7 @@ impl PathCheckRule { } /// Denies the path from being a file + #[must_use] pub fn deny_file(self) -> Self { match self.type_check { Some(type_check) => Self { @@ -121,6 +126,7 @@ impl PathCheckRule { } /// Denies the path from being a directory + #[must_use] pub fn deny_dir(self) -> Self { match self.type_check { Some(type_check) => Self { @@ -143,6 +149,7 @@ impl PathCheckRule { } /// Denies the path from being a symlink + #[must_use] pub fn deny_symlink(self) -> Self { match self.type_check { Some(type_check) => Self { @@ -165,6 +172,7 @@ impl PathCheckRule { } /// Requires the path to be a file (overrides type checks) + #[must_use] pub fn must_file(self) -> Self { Self { type_check: Some(PathTypeCheck { @@ -177,6 +185,7 @@ impl PathCheckRule { } /// Requires the path to be a directory (overrides type checks) + #[must_use] pub fn must_dir(self) -> Self { Self { type_check: Some(PathTypeCheck { @@ -189,6 +198,7 @@ impl PathCheckRule { } /// Requires the path to be a symlink (overrides type checks) + #[must_use] pub fn must_symlink(self) -> Self { Self { type_check: Some(PathTypeCheck { @@ -201,6 +211,7 @@ impl PathCheckRule { } /// Requires the path to exist + #[must_use] pub fn must_exist(self) -> Self { Self { exist_check: Some(PathExistCheck::Exists), @@ -209,6 +220,7 @@ impl PathCheckRule { } /// Requires the path to not exist + #[must_use] pub fn must_not_exist(self) -> Self { Self { exist_check: Some(PathExistCheck::NotExists), diff --git a/mingling/src/res/exit_code.rs b/mingling/src/res/exit_code.rs index b483139..e90d067 100644 --- a/mingling/src/res/exit_code.rs +++ b/mingling/src/res/exit_code.rs @@ -24,6 +24,7 @@ where /// Retrieves the globally stored exit code for the given `ProgramCollect` type. /// Returns `0` if no exit code has been set. +#[must_use] pub fn exit_code<C>() -> i32 where C: ProgramCollect<Enum = C> + 'static, diff --git a/mingling/src/setups/exit_code.rs b/mingling/src/setups/exit_code.rs index 0412a78..9513363 100644 --- a/mingling/src/setups/exit_code.rs +++ b/mingling/src/setups/exit_code.rs @@ -18,7 +18,7 @@ where { fn default() -> Self { Self { - _collect: Default::default(), + _collect: PhantomData, } } } diff --git a/mingling/src/setups/general_renderer.rs b/mingling/src/setups/general_renderer.rs index 81b6cd0..e0a0d61 100644 --- a/mingling/src/setups/general_renderer.rs +++ b/mingling/src/setups/general_renderer.rs @@ -35,7 +35,7 @@ where fn setup(&mut self, program: &mut Program<C>) { #[cfg(feature = "json_serde_fmt")] program.global_flag("--json", |p| { - p.general_renderer_name = crate::GeneralRendererSetting::Json + p.general_renderer_name = crate::GeneralRendererSetting::Json; }); #[cfg(feature = "json_serde_fmt")] program.global_flag("--json-pretty", |p| { diff --git a/mingling_core/Cargo.toml b/mingling_core/Cargo.toml index 26e642b..4ce9ecd 100644 --- a/mingling_core/Cargo.toml +++ b/mingling_core/Cargo.toml @@ -2,9 +2,13 @@ name = "mingling_core" version.workspace = true edition.workspace = true +authors = ["Weicao-CatilGrass"] license.workspace = true description = "Core of the mingling library" +readme = "README.md" repository.workspace = true +keywords = ["cli", "cli-framework", "framework", "procedural", "command-line"] +categories = ["command-line-interface"] [features] nightly = [] diff --git a/mingling_core/src/any.rs b/mingling_core/src/any.rs index 3dedea4..46ebced 100644 --- a/mingling_core/src/any.rs +++ b/mingling_core/src/any.rs @@ -10,7 +10,7 @@ pub mod group; /// Any type output /// /// Accepts any type that implements `Send + Groupped<G>` -/// After being passed into AnyOutput, it will be converted to `Box<dyn Any + Send + 'static>` +/// After being passed into `AnyOutput`, it will be converted to `Box<dyn Any + Send + 'static>` /// /// Note: /// - If an enum value that does not belong to this type is incorrectly specified, it will be **unsafely** unwrapped by the scheduler @@ -24,7 +24,7 @@ pub struct AnyOutput<G> { } impl<G> AnyOutput<G> { - /// Create an AnyOutput from a `Send + Groupped<G> + Serialize` type + /// Create an `AnyOutput` from a `Send + Groupped<G> + Serialize` type #[cfg(feature = "general_renderer")] pub fn new<T>(value: T) -> Self where @@ -37,7 +37,7 @@ impl<G> AnyOutput<G> { } } - /// Create an AnyOutput from a `Send + Groupped<G>` type + /// Create an `AnyOutput` from a `Send + Groupped<G>` type #[cfg(not(feature = "general_renderer"))] pub fn new<T>(value: T) -> Self where @@ -50,7 +50,15 @@ impl<G> AnyOutput<G> { } } - /// Downcast the AnyOutput to a concrete type T + /// Attempt to downcast the `AnyOutput` to a concrete type. + /// + /// # Errors + /// + /// Returns `Err(self)` if the downcast fails. + /// + /// # Panics + /// + /// Panics if the inner value is not of type `T`. pub fn downcast<T: 'static>(self) -> Result<T, Self> { if self.type_id == std::any::TypeId::of::<T>() { Ok(*self.inner.downcast::<T>().unwrap()) @@ -75,7 +83,7 @@ impl<G> AnyOutput<G> { } #[cfg(feature = "general_renderer")] - /// Restore AnyOutput back to the original Serialize type + /// Restore `AnyOutput` back to the original Serialize type pub fn restore<T: Serialize + 'static>(self) -> Option<T> { if self.type_id == std::any::TypeId::of::<T>() { match self.inner.downcast::<T>() { diff --git a/mingling_core/src/asset/chain.rs b/mingling_core/src/asset/chain.rs index 1b488fe..423e218 100644 --- a/mingling_core/src/asset/chain.rs +++ b/mingling_core/src/asset/chain.rs @@ -3,7 +3,7 @@ use crate::ChainProcess; #[doc(hidden)] pub mod error; -/// Takes over a type (G: Previous) and converts it to another [AnyOutput](./struct.AnyOutput.html) +/// Takes over a type (G: Previous) and converts it to another [`AnyOutput`](./struct.AnyOutput.html) pub trait Chain<G> { /// The previous type in the chain type Previous; diff --git a/mingling_core/src/asset/chain/error.rs b/mingling_core/src/asset/chain/error.rs index ce69bc5..29abba1 100644 --- a/mingling_core/src/asset/chain/error.rs +++ b/mingling_core/src/asset/chain/error.rs @@ -13,8 +13,8 @@ pub enum ChainProcessError { impl std::fmt::Display for ChainProcessError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ChainProcessError::Other(s) => write!(f, "Other error: {}", s), - ChainProcessError::IO(e) => write!(f, "IO error: {}", e), + ChainProcessError::Other(s) => write!(f, "Other error: {s}"), + ChainProcessError::IO(e) => write!(f, "IO error: {e}"), } } } @@ -41,14 +41,14 @@ impl From<ProgramInternalExecuteError> for ChainProcessError { ChainProcessError::Other("DispatcherNotFound".into()) } ProgramInternalExecuteError::RendererNotFound(r) => { - ChainProcessError::Other(format!("RendererNotFound: {}", r)) + ChainProcessError::Other(format!("RendererNotFound: {r}")) } ProgramInternalExecuteError::Other(e) => ChainProcessError::Other(e), ProgramInternalExecuteError::IO(e) => { - ChainProcessError::Other(format!("IOError: {:?}", e)) + ChainProcessError::Other(format!("IOError: {e:?}")) } ProgramInternalExecuteError::REPLPanic(program_panic) => { - ChainProcessError::Other(format!("REPLPanic: {}", program_panic)) + ChainProcessError::Other(format!("REPLPanic: {program_panic}")) } } } diff --git a/mingling_core/src/asset/dispatcher.rs b/mingling_core/src/asset/dispatcher.rs index 95b3305..b62a0d0 100644 --- a/mingling_core/src/asset/dispatcher.rs +++ b/mingling_core/src/asset/dispatcher.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use crate::{ChainProcess, Program, ProgramCollect, asset::node::Node}; -/// Dispatches user input commands to specific [ChainProcess](./enum.ChainProcess.html) +/// Dispatches user input commands to specific [`ChainProcess`](./enum.ChainProcess.html) /// /// Note: If you are using [mingling_macros](https://crates.io/crates/mingling_macros), /// you can use the `dispatcher!("node.subnode", CommandType => Entry)` macro to declare a `Dispatcher` @@ -10,7 +10,7 @@ pub trait Dispatcher<C> { /// Returns a command node for matching user input fn node(&self) -> Node; - /// Returns a [ChainProcess](./enum.ChainProcess.html) based on user input arguments, + /// Returns a [`ChainProcess`](./enum.ChainProcess.html) based on user input arguments, /// to be sent to the specific invocation fn begin(&self, args: Vec<String>) -> ChainProcess<C>; diff --git a/mingling_core/src/asset/enum_tag.rs b/mingling_core/src/asset/enum_tag.rs index 28428a6..d830e62 100644 --- a/mingling_core/src/asset/enum_tag.rs +++ b/mingling_core/src/asset/enum_tag.rs @@ -1,4 +1,4 @@ -/// Marker trait for EnumTag +/// Marker trait for `EnumTag` pub trait EnumTag { /// Get the name and description of this enum fn enum_info(&self) -> (&'static str, &'static str); diff --git a/mingling_core/src/asset/global_resource.rs b/mingling_core/src/asset/global_resource.rs index 98a8160..d03c6ea 100644 --- a/mingling_core/src/asset/global_resource.rs +++ b/mingling_core/src/asset/global_resource.rs @@ -25,11 +25,8 @@ where Res: 'static + Default + ResourceMarker + Send + Sync, Return: Default, { - let mut guard = match self.resources.lock() { - Ok(guard) => guard, - Err(_) => { - return Return::default(); - } + let Ok(mut guard) = self.resources.lock() else { + return Return::default(); }; if let Some(arc_res) = guard .get_mut(&TypeId::of::<Res>()) @@ -56,12 +53,9 @@ where Res: 'static + Default + ResourceMarker + Send + Sync, Return: Into<ChainProcess<C>>, { - let mut guard = match self.resources.lock() { - Ok(guard) => guard, - Err(_) => { - let mut default_res = Res::res_default(); - return f(&mut default_res); - } + let Ok(mut guard) = self.resources.lock() else { + let mut default_res = Res::res_default(); + return f(&mut default_res); }; if let Some(arc_res) = guard .get_mut(&TypeId::of::<Res>()) @@ -81,6 +75,7 @@ where } /// Get an resources by type, returning `Res` if present + #[must_use] pub fn res<Res: 'static + Send + Sync>(&self) -> Option<GlobalResource<Res>> { let guard = self.resources.lock().ok()?; let boxed_any = guard.get(&TypeId::of::<Res>())?; @@ -100,6 +95,7 @@ where } /// Get a resource by type, returning `GlobalResource<Res>` or inserting a default + #[must_use] pub fn res_or_default<Res: 'static + Send + Sync + ResourceMarker>( &self, ) -> GlobalResource<Res> { @@ -144,6 +140,7 @@ impl<ResType: 'static + Send + Sync> AsRef<ResType> for GlobalResource<ResType> /// Resource marker trait, types that implement the Clone and Default traits can be considered as resources pub trait ResourceMarker { + #[must_use] fn res_clone(&self) -> Self; fn res_default() -> Self; fn modify<C>(f: impl FnOnce(&mut Self)) diff --git a/mingling_core/src/asset/node.rs b/mingling_core/src/asset/node.rs index 035d227..4dfdb48 100644 --- a/mingling_core/src/asset/node.rs +++ b/mingling_core/src/asset/node.rs @@ -11,6 +11,7 @@ pub struct Node { impl Node { /// Append a new part to the node path. + #[must_use] pub fn join(self, node: impl Into<String>) -> Node { let mut new_node = self.node; new_node.push(node.into()); diff --git a/mingling_core/src/asset/renderer.rs b/mingling_core/src/asset/renderer.rs index de417a2..1d5a2c1 100644 --- a/mingling_core/src/asset/renderer.rs +++ b/mingling_core/src/asset/renderer.rs @@ -1,6 +1,6 @@ use crate::RenderResult; -/// Takes over a type (Self::Previous) and converts it to a [`RenderResult`](./struct.RenderResult.html) +/// Takes over a type (`Self::Previous`) and converts it to a [`RenderResult`](./struct.RenderResult.html) pub trait Renderer { /// The previous type in the chain type Previous; diff --git a/mingling_core/src/builds/comp.rs b/mingling_core/src/builds/comp.rs index 8b884c8..aa08627 100644 --- a/mingling_core/src/builds/comp.rs +++ b/mingling_core/src/builds/comp.rs @@ -10,7 +10,7 @@ const TMPL_COMP_FISH: &str = include_str!("../../tmpls/comps/fish.fish"); const TMPL_COMP_PWSH: &str = include_str!("../../tmpls/comps/pwsh.ps1"); /// Generate shell completion scripts for a given binary name. -/// On Windows, generates PowerShell completion. +/// On Windows, generates `PowerShell` completion. /// On Linux, generates Zsh, Bash, and Fish completions. /// Scripts are written to the `OUT_DIR` (or `target/` if `OUT_DIR` is not set). /// @@ -63,7 +63,7 @@ pub fn build_comp_scripts(name: &str) -> Result<(), std::io::Error> { /// ``` pub fn build_comp_script(shell_flag: &ShellFlag, bin_name: &str) -> Result<(), std::io::Error> { let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap()); - let target_dir = out_dir.join("../../../").to_path_buf(); + let target_dir = out_dir.join("../../../").clone(); build_comp_script_to(shell_flag, bin_name, &target_dir.to_string_lossy()) } @@ -89,7 +89,7 @@ pub fn build_comp_script_to( tmpl_param!(tmpl, bin_name = bin_name); let target_path = std::path::PathBuf::from(target_dir); std::fs::create_dir_all(&target_path)?; - let output_path = target_path.join(format!("{}_comp{}", bin_name, ext)); + let output_path = target_path.join(format!("{bin_name}_comp{ext}")); std::fs::write(&output_path, tmpl.to_string()) } diff --git a/mingling_core/src/comp.rs b/mingling_core/src/comp.rs index 8d55c5d..4f3890a 100644 --- a/mingling_core/src/comp.rs +++ b/mingling_core/src/comp.rs @@ -43,6 +43,7 @@ pub trait CompletionEntry { /// format appropriate for the target shell. pub struct CompletionHelper; impl CompletionHelper { + #[must_use] pub fn exec_completion<P>(ctx: &ShellContext) -> Suggest where P: ProgramCollect<Enum = P> + Display + PartialEq + 'static + std::fmt::Debug, @@ -114,6 +115,7 @@ impl CompletionHelper { } } + #[allow(clippy::needless_pass_by_value)] pub fn render_suggest<P>(ctx: ShellContext, suggest: Suggest) where P: ProgramCollect<Enum = P> + Display + 'static, @@ -130,15 +132,15 @@ impl CompletionHelper { match ctx.shell_flag { ShellFlag::Zsh | ShellFlag::Powershell => { trace!("using zsh/pwsh format"); - print_suggest_with_description(suggestions) + print_suggest_with_description(suggestions); } ShellFlag::Fish => { trace!("using fish format"); - print_suggest_with_description_fish(suggestions) + print_suggest_with_description_fish(suggestions); } _ => { trace!("using default format"); - print_suggest(suggestions) + print_suggest(suggestions); } } } @@ -162,7 +164,7 @@ where if ctx.word_index < 1 { debug!("word_index < 1, returning file suggestions"); return file_suggest(); - }; + } // Get the current input path let input_end = ctx.word_index.min(ctx.all_words.len()); @@ -178,7 +180,7 @@ where .unwrap_or(&[]) .iter() .filter(|s| !s.is_empty()) - .map(|s| s.as_str()) + .map(std::string::String::as_str) .collect(); debug!( "input_path={:?}, current_word='{}'", diff --git a/mingling_core/src/comp/flags.rs b/mingling_core/src/comp/flags.rs index 452126b..424fe8b 100644 --- a/mingling_core/src/comp/flags.rs +++ b/mingling_core/src/comp/flags.rs @@ -14,7 +14,7 @@ pub enum ShellFlag { Zsh, /// Represents the Fish shell. Fish, - /// Represents PowerShell. + /// Represents `PowerShell`. Powershell, /// A custom or unsupported shell type, identified by the provided string. Other(String), diff --git a/mingling_core/src/comp/shell_ctx.rs b/mingling_core/src/comp/shell_ctx.rs index 3134cd6..35758e9 100644 --- a/mingling_core/src/comp/shell_ctx.rs +++ b/mingling_core/src/comp/shell_ctx.rs @@ -73,8 +73,7 @@ impl TryFrom<Vec<String>> for ShellContext { let shell_flag = arg_map .get("-F") .cloned() - .map(ShellFlag::from) - .unwrap_or(ShellFlag::Other("unknown".to_string())); + .map_or(ShellFlag::Other("unknown".to_string()), ShellFlag::from); let all_words = command_line .split_whitespace() @@ -120,7 +119,7 @@ impl ShellContext { let flag = flag.into(); if self.filling_argument(&flag) { let mut flag_appears = 0; - for w in self.all_words.iter() { + for w in &self.all_words { for f in flag.iter() { if *f == w { flag_appears += 1; @@ -190,6 +189,7 @@ impl ShellContext { /// // } /// } /// ``` + #[must_use] pub fn typing_argument(&self) -> bool { #[cfg(target_os = "windows")] { @@ -207,6 +207,7 @@ impl ShellContext { /// in the command line. It is useful for preventing duplicate flag suggestions /// when the user has already typed certain flags. The method processes both /// regular suggestion sets and file completion suggestions differently. + #[must_use] pub fn strip_typed_argument(&self, suggest: Suggest) -> Suggest { let typed = Self::get_typed_arguments(self); match suggest { @@ -223,11 +224,12 @@ impl ShellContext { /// This method collects all words in the shell context that start with a dash (`-`), /// which typically represent command-line flags or options. It returns a vector /// containing these flag strings, converted to owned `String` values. + #[must_use] pub fn get_typed_arguments(&self) -> HashSet<String> { self.all_words .iter() - .filter(|word| word.starts_with("-")) - .map(|word| word.to_string()) + .filter(|word| word.starts_with('-')) + .cloned() .collect() } } diff --git a/mingling_core/src/comp/suggest.rs b/mingling_core/src/comp/suggest.rs index 6d64341..cd025a4 100644 --- a/mingling_core/src/comp/suggest.rs +++ b/mingling_core/src/comp/suggest.rs @@ -16,17 +16,20 @@ pub enum Suggest { } impl Suggest { - /// Creates a new Suggest variant containing a BTreeSet of suggestions. + /// Creates a new Suggest variant containing a `BTreeSet` of suggestions. + #[must_use] pub fn new() -> Self { Self::Suggest(BTreeSet::new()) } - /// Creates a FileCompletion variant. + /// Creates a `FileCompletion` variant. + #[must_use] pub fn file_comp() -> Self { Self::FileCompletion } /// Filters out already typed flag arguments from suggestion results. + #[must_use] pub fn strip_typed_argument(self, ctx: &ShellContext) -> Self { ctx.strip_typed_argument(self) } @@ -103,40 +106,44 @@ impl Ord for SuggestItem { impl SuggestItem { /// Creates a new simple suggestion without description. + #[must_use] pub fn new(suggest: String) -> Self { Self::Simple(suggest) } /// Creates a new suggestion with a description. + #[must_use] pub fn new_with_desc(suggest: String, description: String) -> Self { Self::WithDescription(suggest, description) } /// Adds a description to this suggestion, replacing any existing description. + #[must_use] pub fn with_desc(self, description: String) -> Self { match self { - Self::Simple(suggest) => Self::WithDescription(suggest, description), - Self::WithDescription(suggest, _) => Self::WithDescription(suggest, description), + Self::Simple(suggest) | Self::WithDescription(suggest, _) => { + Self::WithDescription(suggest, description) + } } } /// Returns the suggestion text. + #[must_use] pub fn suggest(&self) -> &String { match self { - Self::Simple(suggest) => suggest, - Self::WithDescription(suggest, _) => suggest, + Self::Simple(suggest) | Self::WithDescription(suggest, _) => suggest, } } /// Updates the suggestion text. pub fn set_suggest(&mut self, new_suggest: String) { match self { - Self::Simple(suggest) => *suggest = new_suggest, - Self::WithDescription(suggest, _) => *suggest = new_suggest, + Self::Simple(suggest) | Self::WithDescription(suggest, _) => *suggest = new_suggest, } } /// Returns the description if present. + #[must_use] pub fn description(&self) -> Option<&String> { match self { Self::Simple(_) => None, diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 912975d..2e861e4 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -70,6 +70,7 @@ where C: ProgramCollect<Enum = C>, { /// Creates a new Program instance, initializing command-line arguments from the environment. + #[must_use] pub fn new() -> Self { #[cfg(not(windows))] return Self::new_with_args(env::args().collect::<Vec<String>>()); @@ -96,8 +97,8 @@ where #[cfg(not(feature = "dispatch_tree"))] dispatcher: Vec::new(), - stdout_setting: Default::default(), - user_context: Default::default(), + stdout_setting: ProgramStdoutSetting::default(), + user_context: ProgramUserContext::default(), #[cfg(feature = "general_renderer")] general_renderer_name: GeneralRendererSetting::Disable, @@ -109,6 +110,10 @@ where } /// Returns a reference to the current program instance, if set. + /// + /// # Panics + /// + /// Panics if the program has not been initialized yet. pub fn this_program() -> &'static Program<C> where C: 'static, @@ -123,6 +128,7 @@ where } /// Get all registered dispatcher names from the program + #[must_use] pub fn get_nodes( &'static self, ) -> Vec<(String, &'static (dyn Dispatcher<C> + Send + Sync + 'static))> { @@ -130,11 +136,17 @@ where } /// Dynamically dispatch input arguments to registered entry types + /// + /// # Errors + /// + /// Returns `Err(ChainProcessError)` if the dispatch fails, + /// e.g., if no dispatcher is found for the given arguments. pub fn dispatch_args_dynamic( &'static self, args: impl Into<StringVec>, ) -> Result<AnyOutput<C>, ChainProcessError> { - match exec::dispatch_args_dynamic(self, &args.into().into()) { + let sv: Vec<String> = args.into().into(); + match exec::dispatch_args_dynamic(self, &sv) { Ok(ok) => Ok(ok), Err(e) => Err(e.into()), } @@ -229,6 +241,7 @@ macro_rules! __dispatch_program_chains { /// Get all registered dispatcher names from the program #[allow(unused_variables)] +#[must_use] pub fn get_nodes<C: ProgramCollect<Enum = C>>( program: &'static Program<C>, ) -> Vec<(String, &'static (dyn Dispatcher<C> + Send + Sync + 'static))> { diff --git a/mingling_core/src/program/collection.rs b/mingling_core/src/program/collection.rs index ff26411..d3d18d6 100644 --- a/mingling_core/src/program/collection.rs +++ b/mingling_core/src/program/collection.rs @@ -25,23 +25,23 @@ pub trait ProgramCollect { /// Use a prefix tree to quickly match arguments and dispatch to an Entry #[cfg(feature = "dispatch_tree")] fn dispatch_args_trie( - raw: &Vec<String>, + raw: &[String], ) -> Result<AnyOutput<Self::Enum>, crate::error::ProgramInternalExecuteError>; /// Get all registered dispatcher names from the program #[cfg(feature = "dispatch_tree")] fn get_nodes() -> Vec<(String, &'static (dyn Dispatcher<Self::Enum> + Send + Sync))>; - /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a renderer was not found + /// Build an [`AnyOutput`](./struct.AnyOutput.html) to indicate that a renderer was not found fn build_renderer_not_found(member_id: Self::Enum) -> AnyOutput<Self::Enum>; - /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that a dispatcher was not found + /// Build an [`AnyOutput`](./struct.AnyOutput.html) to indicate that a dispatcher was not found fn build_dispatcher_not_found(args: Vec<String>) -> AnyOutput<Self::Enum>; - /// Build an [AnyOutput](./struct.AnyOutput.html) to indicate that the chain returned an empty result + /// Build an [`AnyOutput`](./struct.AnyOutput.html) to indicate that the chain returned an empty result fn build_empty_result() -> AnyOutput<Self::Enum>; - /// Render the input [AnyOutput](./struct.AnyOutput.html) + /// Render the input [`AnyOutput`](./struct.AnyOutput.html) fn render(any: AnyOutput<Self::Enum>, r: &mut RenderResult); /// Render help for Entry @@ -53,7 +53,7 @@ pub trait ProgramCollect { any: AnyOutput<Self::Enum>, ) -> Pin<Box<dyn Future<Output = ChainProcess<Self::Enum>> + Send>>; - /// Find a matching chain to continue execution based on the input [AnyOutput](./struct.AnyOutput.html), returning a new [AnyOutput](./struct.AnyOutput.html) + /// Find a matching chain to continue execution based on the input [`AnyOutput`](./struct.AnyOutput.html), returning a new [`AnyOutput`](./struct.AnyOutput.html) #[cfg(not(feature = "async"))] fn do_chain(any: AnyOutput<Self::Enum>) -> ChainProcess<Self::Enum>; @@ -61,13 +61,18 @@ pub trait ProgramCollect { #[cfg(feature = "comp")] fn do_comp(any: &AnyOutput<Self::Enum>, ctx: &ShellContext) -> Suggest; - /// Whether the program has a renderer that can handle the current [AnyOutput](./struct.AnyOutput.html) + /// Whether the program has a renderer that can handle the current [`AnyOutput`](./struct.AnyOutput.html) fn has_renderer(any: &AnyOutput<Self::Enum>) -> bool; - /// Whether the program has a chain that can handle the current [AnyOutput](./struct.AnyOutput.html) + /// Whether the program has a chain that can handle the current [`AnyOutput`](./struct.AnyOutput.html) fn has_chain(any: &AnyOutput<Self::Enum>) -> bool; /// Perform general rendering and presentation of any type + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization of the + /// output value fails. #[cfg(feature = "general_renderer")] fn general_render( any: AnyOutput<Self::Enum>, diff --git a/mingling_core/src/program/config.rs b/mingling_core/src/program/config.rs index b5b46a0..5c104ab 100644 --- a/mingling_core/src/program/config.rs +++ b/mingling_core/src/program/config.rs @@ -109,7 +109,7 @@ impl std::str::FromStr for GeneralRendererSetting { "ron" => Ok(GeneralRendererSetting::Ron), #[cfg(feature = "ron_serde_fmt")] "ron-pretty" => Ok(GeneralRendererSetting::RonPretty), - _ => Err(format!("Invalid renderer: '{}'", s)), + _ => Err(format!("Invalid renderer: '{s}'")), } } } diff --git a/mingling_core/src/program/error.rs b/mingling_core/src/program/error.rs index 03e9af6..822e429 100644 --- a/mingling_core/src/program/error.rs +++ b/mingling_core/src/program/error.rs @@ -9,9 +9,9 @@ pub struct ProgramPanic { impl fmt::Display for ProgramPanic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(s) = self.payload.downcast_ref::<&str>() { - write!(f, "{}", s) + write!(f, "{s}") } else if let Some(s) = self.payload.downcast_ref::<String>() { - write!(f, "{}", s) + write!(f, "{s}") } else { write!(f, "") } @@ -19,6 +19,7 @@ impl fmt::Display for ProgramPanic { } impl ProgramPanic { + #[must_use] pub fn new(payload: Box<dyn Any + Send>) -> Self { ProgramPanic { payload } } diff --git a/mingling_core/src/program/exec.rs b/mingling_core/src/program/exec.rs index 72a20b9..0cadc6a 100644 --- a/mingling_core/src/program/exec.rs +++ b/mingling_core/src/program/exec.rs @@ -15,8 +15,7 @@ pub async fn exec<C>( where C: ProgramCollect<Enum = C>, { - let args = program.args.clone(); - exec_with_args(program, args).await + exec_with_args(program, &program.args).await } #[cfg(not(feature = "async"))] @@ -24,26 +23,25 @@ pub fn exec<C>(program: &'static Program<C>) -> Result<RenderResult, ProgramInte where C: ProgramCollect<Enum = C>, { - let args = program.args.clone(); - exec_with_args(program, args) + exec_with_args(program, &program.args) } #[cfg(feature = "async")] pub async fn exec_with_args<C>( program: &'static Program<C>, - args: Vec<String>, + args: &[String], ) -> Result<RenderResult, ProgramInternalExecuteError> where C: ProgramCollect<Enum = C>, { // Run hooks - program.run_hook_pre_dispatch(&args); + program.run_hook_pre_dispatch(args); #[cfg(not(feature = "dispatch_tree"))] - let mut current = dispatch_args_dynamic(program, &args)?; + let mut current = dispatch_args_dynamic(program, args)?; #[cfg(feature = "dispatch_tree")] - let mut current = C::dispatch_args_trie(&args)?; + let mut current = C::dispatch_args_trie(args)?; // Run hook program.run_hook_post_dispatch(¤t.member_id); @@ -125,19 +123,19 @@ where #[cfg(not(feature = "async"))] pub fn exec_with_args<C>( program: &'static Program<C>, - args: Vec<String>, + args: &[String], ) -> Result<RenderResult, ProgramInternalExecuteError> where C: ProgramCollect<Enum = C>, { // Run hooks - program.run_hook_pre_dispatch(&args); + program.run_hook_pre_dispatch(args); #[cfg(not(feature = "dispatch_tree"))] - let mut current = dispatch_args_dynamic(program, &args)?; + let mut current = dispatch_args_dynamic(program, args)?; #[cfg(feature = "dispatch_tree")] - let mut current = C::dispatch_args_trie(&args)?; + let mut current = C::dispatch_args_trie(args)?; // Run hook program.run_hook_post_dispatch(¤t.member_id); @@ -221,7 +219,7 @@ where /// Dynamically dispatch input arguments to registered entry types pub(crate) fn dispatch_args_dynamic<C>( program: &'static Program<C>, - args: &Vec<String>, + args: &[String], ) -> Result<AnyOutput<C>, ProgramInternalExecuteError> where C: ProgramCollect<Enum = C>, @@ -236,7 +234,7 @@ where } Err(ProgramInternalExecuteError::DispatcherNotFound) => { // No matching Dispatcher is found - C::build_dispatcher_not_found(args.clone()) + C::build_dispatcher_not_found(args.to_vec()) } Err(e) => return Err(e), }; @@ -245,10 +243,9 @@ where /// Match user input against registered dispatchers and return the matched dispatcher and remaining arguments. #[allow(clippy::type_complexity)] -#[allow(clippy::ptr_arg)] pub(crate) fn match_user_input<C>( program: &'static Program<C>, - args: &Vec<String>, + args: &[String], ) -> Result<(&'static (dyn Dispatcher<C> + Send + Sync), Vec<String>), ProgramInternalExecuteError> where C: ProgramCollect<Enum = C>, @@ -260,7 +257,7 @@ where let matching_nodes: Vec<&(String, &(dyn Dispatcher<C> + Send + Sync))> = nodes .iter() // Also add a space to the node string to ensure consistent matching logic - .filter(|(node_str, _)| command.starts_with(&format!("{} ", node_str))) + .filter(|(node_str, _)| command.starts_with(&format!("{node_str} "))) .collect(); match matching_nodes.len() { @@ -289,7 +286,7 @@ where } } -#[inline(always)] +#[inline] #[allow(unused_variables)] fn render<C: ProgramCollect<Enum = C>>(program: &Program<C>, any: AnyOutput<C>) -> RenderResult { #[cfg(not(feature = "general_renderer"))] @@ -312,7 +309,7 @@ fn render<C: ProgramCollect<Enum = C>>(program: &Program<C>, any: AnyOutput<C>) } } -#[inline(always)] +#[inline] #[allow(unused_variables)] fn render_help<C: ProgramCollect<Enum = C>>( program: &Program<C>, diff --git a/mingling_core/src/program/exec/error.rs b/mingling_core/src/program/exec/error.rs index 0f2d875..944e89a 100644 --- a/mingling_core/src/program/exec/error.rs +++ b/mingling_core/src/program/exec/error.rs @@ -26,10 +26,10 @@ impl fmt::Display for ProgramExecuteError { match self { ProgramExecuteError::DispatcherNotFound => write!(f, "No Dispatcher Found"), ProgramExecuteError::RendererNotFound(s) => { - write!(f, "No Renderer (`{}`) Found", s) + write!(f, "No Renderer (`{s}`) Found") } - ProgramExecuteError::Panic(p) => write!(f, "Panic: {:?}", p), - ProgramExecuteError::Other(s) => write!(f, "Other error: {}", s), + ProgramExecuteError::Panic(p) => write!(f, "Panic: {p:?}"), + ProgramExecuteError::Other(s) => write!(f, "Other error: {s}"), } } } @@ -74,12 +74,12 @@ impl fmt::Display for ProgramInternalExecuteError { write!(f, "No Dispatcher Found") } ProgramInternalExecuteError::RendererNotFound(s) => { - write!(f, "No Renderer (`{}`) Found", s) + write!(f, "No Renderer (`{s}`) Found") } - ProgramInternalExecuteError::Other(s) => write!(f, "Other error: {}", s), - ProgramInternalExecuteError::IO(e) => write!(f, "IO error: {}", e), + ProgramInternalExecuteError::Other(s) => write!(f, "Other error: {s}"), + ProgramInternalExecuteError::IO(e) => write!(f, "IO error: {e}"), ProgramInternalExecuteError::REPLPanic(panic) => { - write!(f, "A single REPL execution failed: {}", panic) + write!(f, "A single REPL execution failed: {panic}") } } } @@ -110,11 +110,10 @@ impl From<ProgramInternalExecuteError> for ProgramExecuteError { ProgramExecuteError::RendererNotFound(s) } ProgramInternalExecuteError::Other(s) => ProgramExecuteError::Other(s), - ProgramInternalExecuteError::IO(e) => ProgramExecuteError::Other(format!("{}", e)), - ProgramInternalExecuteError::REPLPanic(p) => ProgramExecuteError::Other(format!( - "A single REPL execution failed: {}", - p - )), + ProgramInternalExecuteError::IO(e) => ProgramExecuteError::Other(format!("{e}")), + ProgramInternalExecuteError::REPLPanic(p) => { + ProgramExecuteError::Other(format!("A single REPL execution failed: {p}")) + } } } } diff --git a/mingling_core/src/program/flag.rs b/mingling_core/src/program/flag.rs index 210f2d6..13f6ea9 100644 --- a/mingling_core/src/program/flag.rs +++ b/mingling_core/src/program/flag.rs @@ -51,7 +51,7 @@ impl From<&Flag> for Flag { } impl From<()> for Flag { - fn from(_: ()) -> Self { + fn from((): ()) -> Self { Flag { vec: vec![] } } } diff --git a/mingling_core/src/program/hook.rs b/mingling_core/src/program/hook.rs index 3520084..929eac2 100644 --- a/mingling_core/src/program/hook.rs +++ b/mingling_core/src/program/hook.rs @@ -16,7 +16,7 @@ where pub begin: Option<fn()>, /// Executes before the program dispatches - pub pre_dispatch: Option<fn(args: &Vec<String>)>, + pub pre_dispatch: Option<fn(args: &[String])>, /// Executes after the program dispatches pub post_dispatch: Option<fn(entry: &C)>, @@ -98,19 +98,19 @@ where for hook in &self.hooks { if let Some(begin) = hook.begin { - begin() + begin(); } } } - pub(crate) fn run_hook_pre_dispatch(&self, args: &Vec<String>) { + pub(crate) fn run_hook_pre_dispatch(&self, args: &[String]) { if !self.user_context.run_hook { return; } for hook in &self.hooks { if let Some(pre_dispatch) = hook.pre_dispatch { - pre_dispatch(args) + pre_dispatch(args); } } } @@ -122,7 +122,7 @@ where for hook in &self.hooks { if let Some(post_dispatch) = hook.post_dispatch { - post_dispatch(entry) + post_dispatch(entry); } } } @@ -134,7 +134,7 @@ where for hook in &self.hooks { if let Some(pre_chain) = hook.pre_chain { - pre_chain(input, raw) + pre_chain(input, raw); } } } @@ -146,7 +146,7 @@ where for hook in &self.hooks { if let Some(post_chain) = hook.post_chain { - post_chain(output) + post_chain(output); } } } @@ -158,7 +158,7 @@ where for hook in &self.hooks { if let Some(pre_render) = hook.pre_render { - pre_render(input, raw) + pre_render(input, raw); } } } @@ -170,7 +170,7 @@ where for hook in &self.hooks { if let Some(post_render) = hook.post_render { - post_render(result) + post_render(result); } } } @@ -184,7 +184,7 @@ where for hook in &self.hooks { if let Some(exec_panic) = hook.exec_panic { - exec_panic(panic_info) + exec_panic(panic_info); } } } @@ -354,6 +354,7 @@ where C: ProgramCollect<Enum = C>, { /// Creates a new empty hook set with no handlers. + #[must_use] pub fn empty() -> Self { Self { begin: None, @@ -390,48 +391,56 @@ where } /// Sets the handler for the `begin` event. + #[must_use] pub fn on_begin(mut self, handler: fn()) -> Self { let _ = self.begin.insert(handler); self } /// Sets the handler for the `pre_dispatch` event. - pub fn on_pre_dispatch(mut self, handler: fn(args: &Vec<String>)) -> Self { + #[must_use] + pub fn on_pre_dispatch(mut self, handler: fn(args: &[String])) -> Self { let _ = self.pre_dispatch.insert(handler); self } /// Sets the handler for the `post_dispatch` event. + #[must_use] pub fn on_post_dispatch(mut self, handler: fn(entry: &C)) -> Self { let _ = self.post_dispatch.insert(handler); self } /// Sets the handler for the `pre_chain` event. + #[must_use] pub fn on_pre_chain(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self { let _ = self.pre_chain.insert(handler); self } /// Sets the handler for the `post_chain` event. + #[must_use] pub fn on_post_chain(mut self, handler: fn(output: &AnyOutput<C>)) -> Self { let _ = self.post_chain.insert(handler); self } /// Sets the handler for the `pre_render` event. + #[must_use] pub fn on_pre_render(mut self, handler: fn(input: &C, raw: &dyn Any)) -> Self { let _ = self.pre_render.insert(handler); self } /// Sets the handler for the `post_render` event. + #[must_use] pub fn on_post_render(mut self, handler: fn(result: &RenderResult)) -> Self { let _ = self.post_render.insert(handler); self } /// Sets the handler for the `finish` event. + #[must_use] pub fn on_finish(mut self, handler: fn() -> i32) -> Self { let _ = self.finish.insert(handler); self @@ -439,6 +448,7 @@ where /// Sets the handler for the `exec_panic` event. #[cfg(not(feature = "async"))] + #[must_use] pub fn on_exec_panic(mut self, handler: fn(&ProgramPanic)) -> Self { let _ = self.exec_panic.insert(handler); self @@ -446,6 +456,7 @@ where /// Sets the handler for the REPL begin event (only available with `repl` feature). #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_begin(mut self, handler: fn()) -> Self { let _ = self.repl_on_begin.insert(handler); self @@ -454,6 +465,7 @@ where /// Sets the handler for the REPL pre-readline event (only available with `repl` feature). /// This hook runs after `on_repl_begin` but before reading the next input line. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_pre_readline(mut self, handler: fn()) -> Self { let _ = self.repl_pre_readline.insert(handler); self @@ -463,6 +475,7 @@ where /// If set, this function will be called to read a line instead of the default mechanism. /// Returning `None` signals that there is no input (e.g., EOF). #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_readline(mut self, handler: fn() -> Option<String>) -> Self { let _ = self.repl_readline.insert(handler); self @@ -471,6 +484,7 @@ where /// Sets the handler for the REPL post-readline event (only available with `repl` feature). /// This hook runs after reading a line of input and receives a mutable reference to the line. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_post_readline(mut self, handler: fn(line: &mut String)) -> Self { let _ = self.repl_post_readline.insert(handler); self @@ -479,6 +493,7 @@ where /// Sets the handler for the REPL pre-exec event (only available with `repl` feature). /// This hook runs before executing a REPL command, receiving the parsed arguments. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_pre_exec(mut self, handler: fn(args: &[String])) -> Self { let _ = self.repl_pre_exec.insert(handler); self @@ -487,6 +502,7 @@ where /// Sets the handler for the REPL post-exec event (only available with `repl` feature). /// This hook runs after executing a REPL command. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_post_exec(mut self, handler: fn()) -> Self { let _ = self.repl_post_exec.insert(handler); self @@ -495,6 +511,7 @@ where /// Sets the handler for the REPL receive result event (only available with `repl` feature). /// This hook runs after a command is executed, receiving the render result on success. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_receive_result(mut self, handler: fn(result: &RenderResult)) -> Self { let _ = self.repl_on_receive_result.insert(handler); self @@ -502,6 +519,7 @@ where /// Sets the handler for the REPL panic event (only available with `repl` feature). #[cfg(all(feature = "repl", not(feature = "async")))] + #[must_use] pub fn on_repl_panic(mut self, handler: fn(panic: &ProgramPanic)) -> Self { let _ = self.repl_on_panic.insert(handler); self @@ -510,6 +528,7 @@ where /// Sets the handler for the REPL exit event (only available with `repl` feature). /// This hook runs when the REPL is about to exit. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_exit(mut self, handler: fn()) -> Self { let _ = self.repl_exit.insert(handler); self @@ -518,6 +537,7 @@ where /// Sets the handler for the REPL loop_once event (only available with `repl` feature). /// This hook runs after each REPL loop iteration. #[cfg(feature = "repl")] + #[must_use] pub fn on_repl_loop_once(mut self, handler: fn()) -> Self { let _ = self.repl_loop_once.insert(handler); self diff --git a/mingling_core/src/program/once_exec.rs b/mingling_core/src/program/once_exec.rs index e1c0956..f757893 100644 --- a/mingling_core/src/program/once_exec.rs +++ b/mingling_core/src/program/once_exec.rs @@ -29,6 +29,15 @@ where } /// Run the command line program + /// + /// # Errors + /// + /// Returns `Err(ProgramExecuteError)` if execution fails, + /// e.g., if no dispatcher is found or a chain error occurs. + /// + /// # Panics + /// + /// Panics if the program encounters a non-recoverable internal error. pub async fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> where C: 'static + Send + Sync, @@ -127,6 +136,15 @@ where } /// Run the command line program + /// + /// # Errors + /// + /// Returns `Err(ProgramExecuteError)` if execution fails, + /// e.g., if no dispatcher is found or a chain error occurs. + /// + /// # Panics + /// + /// Panics if the program encounters a non-recoverable internal error. pub fn exec_without_render(mut self) -> Result<RenderResult, ProgramExecuteError> where C: 'static + Send + Sync, @@ -141,7 +159,7 @@ where #[cfg(not(panic = "abort"))] match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - self.exec_wrapper(|p| crate::exec::exec(p).map_err(|e| e.into())) + self.exec_wrapper(|p| crate::exec::exec(p).map_err(std::convert::Into::into)) })) { Ok(result) => result, Err(panic_info) => { @@ -164,6 +182,7 @@ where } /// Run the command line program + #[must_use] pub fn exec(self) -> i32 where C: 'static + Send + Sync, @@ -179,15 +198,15 @@ where return 1; } ProgramExecuteError::RendererNotFound(renderer_name) => { - eprintln!("Renderer `{}` not found", renderer_name); + eprintln!("Renderer `{renderer_name}` not found"); return 1; } ProgramExecuteError::Other(e) => { - eprintln!("{}", e); + eprintln!("{e}"); return 1; } ProgramExecuteError::Panic(unwinded_error) => { - eprintln!("{}", unwinded_error); + eprintln!("{unwinded_error}"); return 1; } }, @@ -196,12 +215,12 @@ where // Render result if stdout_setting.render_output && !result.is_empty() { let exit_code = result.exit_code; - print!("{}", result); + print!("{result}"); if let Err(e) = std::io::Write::flush(&mut std::io::stdout()) && stdout_setting.error_output { - eprintln!("{}", e); + eprintln!("{e}"); 1 } else { exit_code diff --git a/mingling_core/src/program/repl_exec.rs b/mingling_core/src/program/repl_exec.rs index 3d82b74..d7ee8e8 100644 --- a/mingling_core/src/program/repl_exec.rs +++ b/mingling_core/src/program/repl_exec.rs @@ -115,12 +115,12 @@ where C: ProgramCollect<Enum = C> + Send + Sync + 'static, { #[cfg(panic = "abort")] - let exec_result = super::exec::exec_with_args(p, args); + let exec_result = super::exec::exec_with_args(p, &args); #[cfg(not(panic = "abort"))] let exec_result = { let exec_unwind_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - super::exec::exec_with_args(p, args) + super::exec::exec_with_args(p, &args) })); match exec_unwind_result { @@ -153,5 +153,5 @@ async fn exec_once<C>( where C: ProgramCollect<Enum = C> + Send + Sync + 'static, { - super::exec::exec_with_args(p, args).await + super::exec::exec_with_args(p, &args).await } diff --git a/mingling_core/src/program/single_instance.rs b/mingling_core/src/program/single_instance.rs index 45d4d33..70771d5 100644 --- a/mingling_core/src/program/single_instance.rs +++ b/mingling_core/src/program/single_instance.rs @@ -7,6 +7,11 @@ pub(crate) static THIS_PROGRAM: OnceLock<Option<Box<dyn std::any::Any + Send + S OnceLock::new(); /// Returns a reference to the current program instance, panics if not set. +/// +/// # Panics +/// +/// Panics if the program has not been initialized yet. +#[must_use] pub fn this<C>() -> &'static Program<C> where C: ProgramCollect<Enum = C> + 'static, diff --git a/mingling_core/src/renderer/general.rs b/mingling_core/src/renderer/general.rs index 7d07bac..0ea82c1 100644 --- a/mingling_core/src/renderer/general.rs +++ b/mingling_core/src/renderer/general.rs @@ -14,7 +14,11 @@ pub mod error; pub struct GeneralRenderer; impl GeneralRenderer { - // Renders data in the specified format to the given RenderResult. + /// Renders data in the specified format to the given `RenderResult`. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[allow(unused_variables)] pub fn render<T: Serialize + Send>( data: &T, @@ -39,6 +43,10 @@ impl GeneralRenderer { } /// Serializes data to JSON format and writes it to the render result. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "json_serde_fmt")] pub fn render_to_json<T: Serialize + Send>( data: &T, @@ -46,11 +54,15 @@ impl GeneralRenderer { ) -> Result<(), GeneralRendererSerializeError> { let json_string = serde_json::to_string(data) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(json_string.to_string().as_str()); + r.print(json_string.clone().as_str()); Ok(()) } /// Serializes data to pretty-printed JSON format and writes it to the render result. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "json_serde_fmt")] pub fn render_to_json_pretty<T: Serialize + Send>( data: &T, @@ -58,11 +70,15 @@ impl GeneralRenderer { ) -> Result<(), GeneralRendererSerializeError> { let json_string = serde_json::to_string_pretty(data) .map_err(|e| GeneralRendererSerializeError::new(e.to_string()))?; - r.print(json_string.to_string().as_str()); + r.print(json_string.clone().as_str()); Ok(()) } /// Serializes data to RON format and writes it to the render result. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "ron_serde_fmt")] pub fn render_to_ron<T: Serialize + Send>( data: &T, @@ -75,6 +91,10 @@ impl GeneralRenderer { } /// Serializes data to pretty-printed RON format and writes it to the render result. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "ron_serde_fmt")] pub fn render_to_ron_pretty<T: Serialize + Send>( data: &T, @@ -91,6 +111,10 @@ impl GeneralRenderer { } /// Serializes data to TOML format and writes it to the render result. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "toml_serde_fmt")] pub fn render_to_toml<T: Serialize + Send>( data: &T, @@ -103,6 +127,10 @@ impl GeneralRenderer { } /// Serializes data to YAML format and writes it to the render result. + /// + /// # Errors + /// + /// Returns `Err(GeneralRendererSerializeError)` if serialization fails. #[cfg(feature = "yaml_serde_fmt")] pub fn render_to_yaml<T: Serialize + Send>( data: &T, diff --git a/mingling_core/src/renderer/general/error.rs b/mingling_core/src/renderer/general/error.rs index a61b19d..eb76a8b 100644 --- a/mingling_core/src/renderer/general/error.rs +++ b/mingling_core/src/renderer/general/error.rs @@ -9,6 +9,7 @@ pub struct GeneralRendererSerializeError { } impl GeneralRendererSerializeError { + #[must_use] pub fn new(error: String) -> Self { Self { error } } diff --git a/mingling_core/src/tester/chain_process_tester.rs b/mingling_core/src/tester/chain_process_tester.rs index 8189c28..ca809e4 100644 --- a/mingling_core/src/tester/chain_process_tester.rs +++ b/mingling_core/src/tester/chain_process_tester.rs @@ -47,7 +47,7 @@ where } } ChainProcess::Err(chain_process_error) => { - panic!("Chain process error: {}", chain_process_error); + panic!("Chain process error: {chain_process_error}"); } } } @@ -87,7 +87,7 @@ where ChainProcess::Ok((any, _next)) => any .downcast_ref::<Type>() .expect("Type mismatch: expected type does not match actual output type"), - ChainProcess::Err(chain_process_error) => panic!("{:?}", chain_process_error), + ChainProcess::Err(chain_process_error) => panic!("{chain_process_error:?}"), } } @@ -187,7 +187,7 @@ macro_rules! assert_render_result { }; } -/// Asserts that the result's output type matches the expected member_id. +/// Asserts that the result's output type matches the expected `member_id`. /// /// This macro checks that the `ChainProcess` result is `Ok` and that its output type identifier /// matches the expected type. It is a convenience wrapper around `assert_next_eq` with the `next` diff --git a/mingling_macros/Cargo.toml b/mingling_macros/Cargo.toml index 4fc5c75..a5fdabf 100644 --- a/mingling_macros/Cargo.toml +++ b/mingling_macros/Cargo.toml @@ -2,9 +2,13 @@ name = "mingling_macros" version.workspace = true edition.workspace = true +authors = ["Weicao-CatilGrass"] license.workspace = true +readme = "README.md" repository.workspace = true description = "Macros of the mingling library" +keywords = ["cli", "macros", "procedural", "command-line", "framework"] +categories = ["command-line-interface", "development-tools::procedural-macros"] [lib] proc-macro = true diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index ac05480..b0ea8ae 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -229,8 +229,8 @@ fn generate_struct_and_impl( group_name: &proc_macro2::TokenStream, program_type: &proc_macro2::TokenStream, use_crate_prefix: bool, - proc_fn: proc_macro2::TokenStream, - origin_proc_fn: proc_macro2::TokenStream, + proc_fn: &proc_macro2::TokenStream, + origin_proc_fn: &proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let chain_type = if use_crate_prefix { program_type @@ -389,8 +389,8 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { &group_name, &program_type, use_crate_prefix, - proc_fn, - origin_proc_fn, + &proc_fn, + &origin_proc_fn, ); expanded.into() diff --git a/mingling_macros/src/dispatch_tree_gen.rs b/mingling_macros/src/dispatch_tree_gen.rs index a2cd52c..66fb6e7 100644 --- a/mingling_macros/src/dispatch_tree_gen.rs +++ b/mingling_macros/src/dispatch_tree_gen.rs @@ -43,7 +43,7 @@ pub fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> TokenStre quote! { fn dispatch_args_trie( - raw: &Vec<String>, + raw: &[String], ) -> Result<::mingling::AnyOutput<Self::Enum>, ::mingling::error::ProgramInternalExecuteError> { let raw_string = format!("{} ", raw.join(" ")); @@ -63,7 +63,7 @@ pub fn gen_dispatch_args_trie(entries: &[(String, String, String)]) -> TokenStre fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream { if nodes.is_empty() { return quote! { - return Ok(Self::build_dispatcher_not_found(raw.clone())); + return Ok(Self::build_dispatcher_not_found(raw.to_vec())); }; } @@ -121,7 +121,7 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream arms.push(quote! { Some(#ch_char) => { #arm - return Ok(Self::build_dispatcher_not_found(raw.clone())); + return Ok(Self::build_dispatcher_not_found(raw.to_vec())); } }); } else { @@ -150,7 +150,7 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream let match_body = quote! { match raw_chars.nth(0) { #(#arms)* - _ => return Ok(Self::build_dispatcher_not_found(raw.clone())), + _ => return Ok(Self::build_dispatcher_not_found(raw.to_vec())), } }; quote! { @@ -161,19 +161,19 @@ fn build_dispatch_body(nodes: &[(String, String)], depth: usize) -> TokenStream // Only exact nodes, no deeper groups quote! { #(#exact_checks)* - return Ok(Self::build_dispatcher_not_found(raw.clone())); + return Ok(Self::build_dispatcher_not_found(raw.to_vec())); } } else if arms.is_empty() { // Only fallback (shouldn't happen if nodes is non-empty) quote! { - return Ok(Self::build_dispatcher_not_found(raw.clone())); + return Ok(Self::build_dispatcher_not_found(raw.to_vec())); } } else { // Only group arms quote! { match raw_chars.nth(0) { #(#arms)* - _ => return Ok(Self::build_dispatcher_not_found(raw.clone())), + _ => return Ok(Self::build_dispatcher_not_found(raw.to_vec())), } } } diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs index b7952a1..7e973eb 100644 --- a/mingling_macros/src/dispatcher.rs +++ b/mingling_macros/src/dispatcher.rs @@ -104,6 +104,7 @@ impl Parse for DispatcherChainInput { // Additionally, the token stream generation patterns are nearly identical between // the two main functions and could benefit from refactoring. +#[allow(clippy::too_many_lines)] pub fn dispatcher(input: TokenStream) -> TokenStream { // Parse the input let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput); @@ -332,7 +333,7 @@ pub fn register_dispatcher(_input: TokenStream) -> TokenStream { quote! {}.into() } -/// Converts a dotted command name (e.g. "remote.add") to PascalCase (e.g. "RemoteAdd"). +/// Converts a dotted command name (e.g. "remote.add") to `PascalCase` (e.g. "`RemoteAdd`"). /// /// Each segment is split by `.`, the first character of each segment is uppercased, /// and the segments are joined. This is used by the abbreviated `dispatcher!` syntax diff --git a/mingling_macros/src/entry.rs b/mingling_macros/src/entry.rs index 6237e41..2ac5d6b 100644 --- a/mingling_macros/src/entry.rs +++ b/mingling_macros/src/entry.rs @@ -42,21 +42,16 @@ fn parse_strings(input: &syn::parse::ParseBuffer) -> syn::Result<Vec<String>> { pub fn entry(input: TokenStream) -> TokenStream { let parsed = parse_macro_input!(input as EntryInput); - let string_exprs = match &parsed { - EntryInput::Typed { .. } | EntryInput::Untyped { .. } => { - let strings = match &parsed { - EntryInput::Typed { strings, .. } => strings, - EntryInput::Untyped { strings } => strings, - }; - strings - .iter() - .map(|s| { - let lit = syn::LitStr::new(s, proc_macro2::Span::call_site()); - quote! { #lit.to_string() } - }) - .collect::<Vec<_>>() - } + let strings = match &parsed { + EntryInput::Typed { strings, .. } | EntryInput::Untyped { strings } => strings, }; + let string_exprs = strings + .iter() + .map(|s| { + let lit = syn::LitStr::new(s, proc_macro2::Span::call_site()); + quote! { #lit.to_string() } + }) + .collect::<Vec<_>>(); let expanded = match parsed { EntryInput::Typed { ident, .. } => { diff --git a/mingling_macros/src/enum_tag.rs b/mingling_macros/src/enum_tag.rs index 8f0576a..6277b69 100644 --- a/mingling_macros/src/enum_tag.rs +++ b/mingling_macros/src/enum_tag.rs @@ -13,7 +13,7 @@ pub fn derive_enum_tag(input: TokenStream) -> TokenStream { } } -/// Implementation of the EnumTag derive macro +/// Implementation of the `EnumTag` derive macro fn derive_enum_tag_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream> { let enum_name = &input.ident; let generics = &input.generics; @@ -42,7 +42,7 @@ fn derive_enum_tag_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream> for variant in data.variants { process_variant( - variant, + &variant, enum_name, &mut variant_info, &mut match_arms, @@ -82,7 +82,7 @@ fn derive_enum_tag_impl(input: DeriveInput) -> Result<proc_macro2::TokenStream> /// Process a single enum variant fn process_variant( - variant: Variant, + variant: &Variant, enum_name: &Ident, variant_info: &mut Vec<proc_macro2::TokenStream>, match_arms: &mut Vec<proc_macro2::TokenStream>, @@ -97,10 +97,9 @@ fn process_variant( } Fields::Named(_) | Fields::Unnamed(_) => { return Err(Error::new_spanned( - &variant, + variant, format!( - "EnumTag cannot be derived for enum variant `{}` with fields. Only unit variants are supported.", - variant_name + "EnumTag cannot be derived for enum variant `{variant_name}` with fields. Only unit variants are supported." ), )); } @@ -132,7 +131,7 @@ fn process_variant( Ok(()) } -/// Extract description from #[enum_desc] attribute +/// Extract description from #[`enum_desc`] attribute fn extract_description(attrs: &[Attribute]) -> Result<Option<String>> { for attr in attrs { if attr.path().is_ident("enum_desc") { @@ -150,7 +149,7 @@ fn extract_description(attrs: &[Attribute]) -> Result<Option<String>> { Ok(None) } -/// Extract rename from #[enum_rename] attribute +/// Extract rename from #[`enum_rename`] attribute fn extract_rename(attrs: &[Attribute]) -> Result<Option<String>> { for attr in attrs { if attr.path().is_ident("enum_rename") { diff --git a/mingling_macros/src/groupped.rs b/mingling_macros/src/groupped.rs index e385812..534e2a6 100644 --- a/mingling_macros/src/groupped.rs +++ b/mingling_macros/src/groupped.rs @@ -23,13 +23,10 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream { // Parse attributes to find #[group(...)] let group_ident: proc_macro2::TokenStream = parse_group_attribute(&input.attrs) - .map(|ident| quote! { #ident }) - .unwrap_or_else(crate::default_program_path); + .map_or_else(crate::default_program_path, |ident| quote! { #ident }); - let any_output_convert_impls = proc_macro2::TokenStream::from(build_any_output_convert_impls( - struct_name.clone(), - group_ident.clone(), - )); + let any_output_convert_impls = + proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident)); // Generate the Groupped trait implementation let expanded = quote! { @@ -55,13 +52,10 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { // Parse attributes to find #[group(...)] let group_ident: proc_macro2::TokenStream = parse_group_attribute(&input_parsed.attrs) - .map(|ident| quote! { #ident }) - .unwrap_or_else(crate::default_program_path); + .map_or_else(crate::default_program_path, |ident| quote! { #ident }); - let any_output_convert_impls = proc_macro2::TokenStream::from(build_any_output_convert_impls( - struct_name.clone(), - group_ident.clone(), - )); + let any_output_convert_impls = + proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident)); // Generate both Serialize and Groupped implementations let expanded = quote! { @@ -83,8 +77,8 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { } fn build_any_output_convert_impls( - struct_name: Ident, - group_ident: proc_macro2::TokenStream, + struct_name: &Ident, + group_ident: &proc_macro2::TokenStream, ) -> TokenStream { quote! { impl ::std::convert::Into<::mingling::AnyOutput<#group_ident>> for #struct_name { diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 8cae29f..7a93895 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -82,7 +82,7 @@ pub(crate) static CHAINS_EXIST: Registry = OnceLock::new(); pub(crate) static RENDERERS_EXIST: Registry = OnceLock::new(); pub(crate) static HELP_REQUESTS: Registry = OnceLock::new(); -/// Checks that a TypePath is a simple single-segment identifier (no `::` in the path). +/// Checks that a `TypePath` is a simple single-segment identifier (no `::` in the path). /// /// This is used by `#[renderer]`, `#[help]`, `#[chain]`, and `#[completion]` attribute macros /// to ensure that the type in the function signature is a bare identifier like `Empty`, @@ -307,7 +307,7 @@ pub fn empty_result(_input: TokenStream) -> TokenStream { /// /// When the `extra_macros` feature is enabled, the `CommandStruct => EntryStruct` /// portion can be omitted. The struct names are auto-derived from the command path -/// using PascalCase conversion: +/// using `PascalCase` conversion: /// /// ```rust,ignore /// // Auto-derives: "remote.add" → CMDRemoteAdd ⇒ EntryRemoteAdd @@ -1188,7 +1188,7 @@ pub fn program_comp_gen(input: TokenStream) -> TokenStream { /// Registers a type into the global packed types registry for inclusion in /// the program enum generated by `gen_program!`. /// -/// This macro is called internally by `pack!` and `#[derive(Groupped)]`(macro.derive_groupped.html) +/// This macro is called internally by `pack!` and `#[derive(Groupped)]`(`macro.derive_groupped.html`) /// and is generally not needed in user code. However, it can be used for manual /// registration if you are implementing custom type registration outside of /// the standard macros. @@ -1201,6 +1201,10 @@ pub fn program_comp_gen(input: TokenStream) -> TokenStream { /// /// Each call inserts the type's name into the `PACKED_TYPES` global set, which /// is later consumed by `program_final_gen!` to generate enum variants. +/// +/// # Panics +/// +/// Panics if the global `PACKED_TYPES` mutex is poisoned. #[proc_macro] pub fn register_type(input: TokenStream) -> TokenStream { let type_ident = parse_macro_input!(input as syn::Ident); @@ -1348,7 +1352,13 @@ pub fn program_fallback_gen(input: TokenStream) -> TokenStream { /// pub fn new() -> Program<MyProgram> { Program::new() } /// } /// ``` +/// +/// # Panics +/// +/// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.) +/// are poisoned. #[proc_macro] +#[allow(clippy::too_many_lines)] pub fn program_final_gen(input: TokenStream) -> TokenStream { let name = read_name(&input); @@ -1479,11 +1489,11 @@ pub fn program_final_gen(input: TokenStream) -> TokenStream { .collect(); let num_variants = packed_types.len(); - let repr_type = if num_variants <= u8::MAX as usize { + let repr_type = if u8::try_from(num_variants).is_ok() { quote! { u8 } - } else if num_variants <= u16::MAX as usize { + } else if u16::try_from(num_variants).is_ok() { quote! { u16 } - } else if num_variants <= u32::MAX as usize { + } else if u32::try_from(num_variants).is_ok() { quote! { u32 } } else { quote! { u128 } @@ -1612,7 +1622,7 @@ pub fn program_final_gen(input: TokenStream) -> TokenStream { /// /// # Related /// -/// - `suggest_enum!`(macro.suggest_enum.html) — Build suggestions from an `EnumTag` enum. +/// - `suggest_enum!`(`macro.suggest_enum.html`) — Build suggestions from an `EnumTag` enum. #[cfg(feature = "comp")] #[proc_macro] pub fn suggest(input: TokenStream) -> TokenStream { diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/pack.rs index 954a052..5a1c388 100644 --- a/mingling_macros/src/pack.rs +++ b/mingling_macros/src/pack.rs @@ -59,6 +59,7 @@ impl Parse for PackInput { } } +#[allow(clippy::too_many_lines)] pub fn pack(input: TokenStream) -> TokenStream { // Parse the input let pack_input = syn::parse_macro_input!(input as PackInput); diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index ae75895..4cf9fc1 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -7,20 +7,21 @@ use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; /// Extracts and returns the return type from the function signature (or None for `()` / no return type). -fn extract_return_type(sig: &Signature) -> syn::Result<Option<syn::Type>> { +fn extract_return_type(sig: &Signature) -> Option<syn::Type> { match &sig.output { ReturnType::Type(_, ty) => { match &**ty { // `()` means no custom return type - Type::Tuple(tuple) if tuple.elems.is_empty() => Ok(None), + Type::Tuple(tuple) if tuple.elems.is_empty() => None, // Any other return type is allowed - custom_ty => Ok(Some((*custom_ty).clone())), + custom_ty => Some((*custom_ty).clone()), } } - ReturnType::Default => Ok(None), + ReturnType::Default => None, } } +#[allow(clippy::too_many_lines)] pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse attribute arguments for program path (e.g. #[renderer(my_crate::Program)]) let (program_path, _use_crate_prefix) = parse_renderer_attr_args(attr); @@ -48,10 +49,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { } // Validate return type – now returns Some(type) if custom type, None if () - let return_type = match extract_return_type(&input_fn.sig) { - Ok(rt) => rt, - Err(e) => return e.to_compile_error().into(), - }; + let return_type = extract_return_type(&input_fn.sig); // Get function body statements let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone(); @@ -83,23 +81,20 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect(); // Determine public return type and the expression to return dummy_r - let (public_return_type, result_return) = match &return_type { + let (public_return_type, result_return) = if let Some(custom_ty) = &return_type { // User specified a custom return type (e.g. -> String) - Some(custom_ty) => { - let ret_ty = quote! { #custom_ty }; - let expr = quote! { dummy_r.into() }; - (ret_ty, expr) - } + let ret_ty = quote! { #custom_ty }; + let expr = quote! { dummy_r.into() }; + (ret_ty, expr) + } else { // Return type is () — no custom return type specified - None => { - let ret_ty = quote! { () }; - let expr = quote! { - if !dummy_r.is_empty() { - ::std::println!("{}", &*dummy_r); - } - }; - (ret_ty, expr) - } + let ret_ty = quote! { () }; + let expr = quote! { + if !dummy_r.is_empty() { + ::std::println!("{}", &*dummy_r); + } + }; + (ret_ty, expr) }; let inner_body_with_resources = if has_mut_resources { diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/res_injection.rs index bdb3b73..f2280e3 100644 --- a/mingling_macros/src/res_injection.rs +++ b/mingling_macros/src/res_injection.rs @@ -13,6 +13,7 @@ pub(crate) struct ResourceInjection { /// Extracts the previous type and parameter name from function arguments, /// and collects resource injection parameters from the 2nd argument onward. +#[allow(clippy::too_many_lines)] pub(crate) fn extract_args_info( sig: &Signature, ) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> { @@ -183,7 +184,7 @@ pub(crate) fn wrap_body_with_mut_resources( #(#fn_body_stmts)* }; - for res in mut_resources.iter() { + for res in mut_resources { let var_name = &res.var_name; let inner_type = &res.inner_type; wrapped = quote! { diff --git a/mling/Cargo.toml b/mling/Cargo.toml index 2d01bdf..df9c675 100644 --- a/mling/Cargo.toml +++ b/mling/Cargo.toml @@ -2,9 +2,13 @@ name = "mingling-cli" version.workspace = true edition.workspace = true +authors = ["Weicao-CatilGrass"] license.workspace = true +readme = "README.md" repository.workspace = true description = "Mingling's scaffolding tool" +keywords = ["cli", "scaffolding", "command-line", "framework"] +categories = ["command-line-interface"] [[bin]] name = "mling" diff --git a/mling/src/cli.rs b/mling/src/cli.rs index 705c6b4..b628021 100644 --- a/mling/src/cli.rs +++ b/mling/src/cli.rs @@ -17,6 +17,11 @@ pub use read::*; pub mod install; pub use install::*; +/// Entry point for the CLI application. +/// +/// # Panics +/// +/// Panics on Windows if the virtual terminal processing cannot be enabled. pub fn cli_entry() { let mut program = ThisProgram::new(); @@ -62,7 +67,7 @@ pub fn cli_entry() { #[cfg(windows)] colored::control::set_virtual_terminal(true).unwrap(); - program.exec(); + let _ = program.exec(); } #[renderer] diff --git a/mling/src/cli/list.rs b/mling/src/cli/list.rs index ac9a65f..a2a9434 100644 --- a/mling/src/cli/list.rs +++ b/mling/src/cli/list.rs @@ -78,44 +78,44 @@ pub(crate) fn render_installed(prev: ResultInstalledNamespaces) { match prev.option { StateListInstalledOptions::All => { print_list( - "Trusted".bright_green().bold().to_string(), - prev.trusted, + &"Trusted".bright_green().bold().to_string(), + &prev.trusted, __renderer_inner_result, ); print_list( - "Untrusted".bright_red().bold().to_string(), - prev.untrusted, + &"Untrusted".bright_red().bold().to_string(), + &prev.untrusted, __renderer_inner_result, ); print_list( - "Untagged".bright_black().bold().to_string(), - prev.untagged, + &"Untagged".bright_black().bold().to_string(), + &prev.untagged, __renderer_inner_result, ); } StateListInstalledOptions::OnlyTrusted => { print_list( - "Trusted".bright_green().bold().to_string(), - prev.trusted, + &"Trusted".bright_green().bold().to_string(), + &prev.trusted, __renderer_inner_result, ); } StateListInstalledOptions::OnlyUntrusted => { print_list( - "Untrusted".bright_red().bold().to_string(), - prev.untrusted, + &"Untrusted".bright_red().bold().to_string(), + &prev.untrusted, __renderer_inner_result, ); } } } -fn print_list(title: String, list: Vec<String>, __renderer_inner_result: &mut RenderResult) { +fn print_list(title: &str, list: &[String], __renderer_inner_result: &mut RenderResult) { if list.is_empty() { return; } - r_println!("{}", title); + r_println!("{title}"); for (i, namespace) in (1..).zip(list.iter()) { r_println!(" {}. {}", i.to_string(), namespace.bold()); diff --git a/mling/src/cli/read.rs b/mling/src/cli/read.rs index 82f8fd5..e51e78f 100644 --- a/mling/src/cli/read.rs +++ b/mling/src/cli/read.rs @@ -25,7 +25,8 @@ pub(crate) struct ResultBinaries { } #[chain] -pub(crate) fn handle_target_dir_entry(_prev: ReadTargetDirEntry) -> Next { +#[allow(unused_variables)] +pub(crate) fn handle_target_dir_entry(entry: ReadTargetDirEntry) -> Next { match solve_current_dir() { Ok(solved) => { let dir = solved.target_dir; @@ -36,7 +37,8 @@ pub(crate) fn handle_target_dir_entry(_prev: ReadTargetDirEntry) -> Next { } #[chain] -pub(crate) fn handle_workspace_root_entry(_prev: ReadWorkspaceRootEntry) -> Next { +#[allow(unused_variables)] +pub(crate) fn handle_workspace_root_entry(entry: ReadWorkspaceRootEntry) -> Next { match solve_current_dir() { Ok(solved) => { let dir = solved.workspace_root; @@ -47,7 +49,8 @@ pub(crate) fn handle_workspace_root_entry(_prev: ReadWorkspaceRootEntry) -> Next } #[chain] -pub(crate) fn handle_binaries_entry(_prev: ReadBinariesEntry) -> Next { +#[allow(unused_variables)] +pub(crate) fn handle_binaries_entry(entry: ReadBinariesEntry) -> Next { match solve_current_dir() { Ok(solved) => { let binaries = solved.binaries; diff --git a/mling/src/display.rs b/mling/src/display.rs index 9182692..3816d89 100644 --- a/mling/src/display.rs +++ b/mling/src/display.rs @@ -120,8 +120,7 @@ pub fn markdown(text: impl AsRef<str>) -> String { // Format heading as white background, black text, bold // ANSI codes: \x1b[1m for bold, \x1b[47m for white background, \x1b[30m for black text - let formatted_heading = - format!("\x1b[1m\x1b[47m\x1b[30m {} \x1b[0m", processed_content); + let formatted_heading = format!("\x1b[1m\x1b[47m\x1b[30m {processed_content} \x1b[0m"); // Add indentation to the heading line itself // Heading indentation = level - 1 @@ -187,7 +186,7 @@ fn process_line_with_quote(line: &str) -> String { let processed_rest = process_line(&rest_of_line); // Combine the gray background space with the processed rest - format!("{}{}", gray_bg_space, processed_rest) + format!("{gray_bg_space}{processed_rest}") } else { // No > at the beginning, process normally process_line(line) @@ -266,7 +265,7 @@ fn process_line(line: &str) -> String { && let Some(end) = find_matching(&chars, i + 1, "_") { let underline_text: String = chars[i + 1..end].iter().collect(); - let mut formatted_text = format!("\x1b[4m{}\x1b[0m", underline_text); + let mut formatted_text = format!("\x1b[4m{underline_text}\x1b[0m"); apply_color_stack(&mut formatted_text, &color_stack); result.push_str(&formatted_text); i = end + 1; @@ -357,16 +356,18 @@ fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { let text = text.as_ref(); let color_name = color_name.as_ref(); match color_name { - // Normal colors - "black" => text.black().to_string(), - "red" => text.red().to_string(), - "green" => text.green().to_string(), - "yellow" => text.yellow().to_string(), - "blue" => text.blue().to_string(), - "magenta" => text.magenta().to_string(), - "cyan" => text.cyan().to_string(), - "white" => text.white().to_string(), - "bright_black" => text.bright_black().to_string(), + // Normal colors and their bright short aliases + "black" | "b_black" => text.black().to_string(), + "red" | "b_red" => text.red().to_string(), + "green" | "b_green" => text.green().to_string(), + "yellow" | "b_yellow" => text.yellow().to_string(), + "blue" | "b_blue" => text.blue().to_string(), + "magenta" | "b_magenta" => text.magenta().to_string(), + "cyan" | "b_cyan" => text.cyan().to_string(), + "white" | "b_white" | "bright_gray" | "bright_grey" | "b_gray" | "b_grey" => { + text.white().to_string() + } + "bright_black" | "gray" | "grey" => text.bright_black().to_string(), "bright_red" => text.bright_red().to_string(), "bright_green" => text.bright_green().to_string(), "bright_yellow" => text.bright_yellow().to_string(), @@ -375,21 +376,6 @@ fn apply_color(text: impl AsRef<str>, color_name: impl AsRef<str>) -> String { "bright_cyan" => text.bright_cyan().to_string(), "bright_white" => text.bright_white().to_string(), - // Short aliases for bright colors - "b_black" => text.black().to_string(), - "b_red" => text.red().to_string(), - "b_green" => text.green().to_string(), - "b_yellow" => text.yellow().to_string(), - "b_blue" => text.blue().to_string(), - "b_magenta" => text.magenta().to_string(), - "b_cyan" => text.cyan().to_string(), - "b_white" => text.white().to_string(), - - // Gray colors using truecolor - "gray" | "grey" => text.bright_black().to_string(), - "bright_gray" | "bright_grey" => text.white().to_string(), - "b_gray" | "b_grey" => text.white().to_string(), - // Default to white if color not recognized _ => text.to_string(), } diff --git a/mling/src/namespace_manager.rs b/mling/src/namespace_manager.rs index 51181c3..d5176dd 100644 --- a/mling/src/namespace_manager.rs +++ b/mling/src/namespace_manager.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use just_fmt::kebab_case; +#[must_use] pub fn list_namespaces( show_trusted: bool, show_untrusted: bool, @@ -13,14 +14,12 @@ pub fn list_namespaces( } let mut namespaces = Vec::new(); - let entries = match std::fs::read_dir(&wdir) { - Ok(entries) => entries, - Err(_) => return Vec::new(), + let Ok(entries) = std::fs::read_dir(&wdir) else { + return Vec::new(); }; for entry in entries { - let entry = match entry { - Ok(e) => e, - Err(_) => continue, + let Ok(entry) = entry else { + continue; }; let path = entry.path(); if path.is_dir() @@ -71,24 +70,34 @@ pub fn remove_namespace(namespace: String) { } } +/// Returns the mingling data directory. +/// +/// # Panics +/// +/// Panics if the platform's data directory cannot be determined. +#[must_use] pub fn working_dir() -> PathBuf { dirs::data_dir().unwrap().join("mingling") } +#[must_use] pub fn namespace_dir(namespace: String) -> PathBuf { working_dir().join(kebab_case!(namespace)) } +#[must_use] pub fn is_untrusted_namespace(namespace: String) -> bool { let untrusted_file = namespace_dir(namespace).join("UNTRUSTED"); untrusted_file.exists() } +#[must_use] pub fn is_trusted_namespace(namespace: String) -> bool { let trusted = namespace_dir(namespace).join("TRUSTED"); trusted.exists() } +#[must_use] pub fn is_untagged_namespace(namespace: String) -> bool { let ndir = namespace_dir(namespace); let trusted = ndir.join("TRUSTED"); @@ -96,14 +105,17 @@ pub fn is_untagged_namespace(namespace: String) -> bool { !trusted.exists() && !untrusted.exists() } +#[must_use] pub fn bin_dir(namespace: String) -> PathBuf { namespace_dir(namespace).join("bin") } +#[must_use] pub fn comp_dir(namespace: String) -> PathBuf { namespace_dir(namespace).join("comp") } +#[must_use] pub fn exe_path(namespace: String, bin_name_without_ext: String) -> PathBuf { if cfg!(target_os = "windows") { bin_dir(namespace).join(bin_name_without_ext + ".exe") diff --git a/mling/src/project_installer.rs b/mling/src/project_installer.rs index 2e9ca8d..983307f 100644 --- a/mling/src/project_installer.rs +++ b/mling/src/project_installer.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use mingling::{ShellFlag, build::build_comp_script_to}; use crate::{ @@ -21,15 +19,27 @@ struct Package { name: String, } +/// Installs all projects and shell scripts. +/// +/// # Errors +/// +/// Returns an `io::Error` if the current directory cannot be determined, if the project +/// installation fails, or if the shell scripts cannot be installed. pub fn install_all(clean_before_build: bool) -> Result<(), std::io::Error> { let current = std::env::current_dir()?; - install_this_project(current, clean_before_build)?; + install_this_project(¤t, clean_before_build)?; install_shell_scripts()?; Ok(()) } +/// Installs a project from the given path. +/// +/// # Errors +/// +/// Returns an `io::Error` if the project installation fails, e.g., if `cargo build` +/// fails, the Cargo.toml cannot be parsed, or file operations (copy, create dir) fail. pub fn install_this_project( - current: PathBuf, + current: &std::path::PathBuf, clean_before_build: bool, ) -> Result<(), std::io::Error> { // Obtain context data @@ -95,6 +105,11 @@ pub fn install_this_project( Ok(()) } +/// Installs shell completion scripts for the `mling` command. +/// +/// # Errors +/// +/// Returns an `io::Error` if the shell scripts cannot be built or installed. pub fn install_shell_scripts() -> Result<(), std::io::Error> { // Get the working directory (mingling data dir) let wdir = working_dir(); @@ -138,7 +153,7 @@ pub fn install_shell_scripts() -> Result<(), std::io::Error> { .args(["+x", &dest.to_string_lossy()]) .status()?; if !status.success() { - eprintln!("Failed to chmod {}", filename); + eprintln!("Failed to chmod {filename}"); } } } diff --git a/mling/src/project_solver.rs b/mling/src/project_solver.rs index b6517b3..3aec2b4 100644 --- a/mling/src/project_solver.rs +++ b/mling/src/project_solver.rs @@ -17,13 +17,24 @@ pub struct BinaryItem { pub path: PathBuf, } +/// Solves the current directory for project metadata. +/// +/// # Errors +/// +/// Returns an `io::Error` if the current directory cannot be determined +/// or if `cargo metadata` fails. pub fn solve_current_dir() -> Result<ProjectSolveResult, std::io::Error> { let current = std::env::current_dir()?; - solve(current) + solve(¤t) } -pub fn solve(current: PathBuf) -> Result<ProjectSolveResult, std::io::Error> { - let (target_dir, workspace_root, binaries) = solve_inner(¤t)?; +/// Solves the given directory path for project metadata. +/// +/// # Errors +/// +/// Returns an `io::Error` if `cargo metadata` fails for the given path. +pub fn solve(current: &PathBuf) -> Result<ProjectSolveResult, std::io::Error> { + let (target_dir, workspace_root, binaries) = solve_inner(current)?; Ok(ProjectSolveResult { target_dir, workspace_root, @@ -40,9 +51,9 @@ fn solve_inner(current: &PathBuf) -> Result<(PathBuf, PathBuf, Vec<BinaryItem>), .output()?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); - return Err(std::io::Error::other( - format!("cargo metadata failed: {}", stderr), - )); + return Err(std::io::Error::other(format!( + "cargo metadata failed: {stderr}" + ))); } let metadata: serde_json::Value = serde_json::from_slice(&output.stdout) .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; @@ -75,9 +86,7 @@ fn solve_inner(current: &PathBuf) -> Result<(PathBuf, PathBuf, Vec<BinaryItem>), if let Some(targets) = pkg["targets"].as_array() { for target in targets { let kind = target["kind"].as_array(); - let is_bin = kind - .map(|k| k.iter().any(|v| v.as_str() == Some("bin"))) - .unwrap_or(false); + let is_bin = kind.is_some_and(|k| k.iter().any(|v| v.as_str() == Some("bin"))); if is_bin { let name = target["name"].as_str().ok_or_else(|| { std::io::Error::new( |
