From 4777b6556701d4471686a1380fc189ef032ec820 Mon Sep 17 00:00:00 2001 From: Alix ANNERAUD Date: Sun, 28 Jun 2026 18:52:05 +0200 Subject: [PATCH] Externalize executable macros crate and update dependencies to use getargs-derive --- Cargo.toml | 2 +- executables/shell/command_line/Cargo.toml | 2 +- .../shell/command_line/locales/en.json | 1 + .../shell/command_line/locales/fr.json | 1 + .../src/commands/change_directory.rs | 2 +- .../shell/command_line/src/commands/clear.rs | 2 +- .../command_line/src/commands/directory.rs | 2 +- .../shell/command_line/src/commands/dns.rs | 2 +- .../src/commands/environment_variables.rs | 2 +- .../shell/command_line/src/commands/exit.rs | 2 +- .../shell/command_line/src/commands/head.rs | 2 +- .../shell/command_line/src/commands/ip.rs | 2 +- .../shell/command_line/src/commands/list.rs | 2 +- .../shell/command_line/src/commands/ping.rs | 2 +- .../command_line/src/commands/statistics.rs | 2 +- .../shell/command_line/src/commands/tail.rs | 2 +- .../shell/command_line/src/commands/which.rs | 2 +- .../command_line/src/commands/word_count.rs | 2 +- executables/shell/command_line/src/error.rs | 10 + executables/shell/graphical/Cargo.toml | 2 +- executables/shell/graphical/src/lib.rs | 2 +- executables/wasm/Cargo.toml | 2 +- executables/wasm/src/host/mod.rs | 2 +- .../wasm/src/host/virtual_machine/error.rs | 14 + modules/executable/macros/Cargo.toml | 15 - modules/executable/macros/src/lib.rs | 349 ------------------ modules/executable/src/arguments_parser.rs | 222 ----------- modules/executable/src/lib.rs | 2 - 28 files changed, 46 insertions(+), 608 deletions(-) delete mode 100644 modules/executable/macros/Cargo.toml delete mode 100644 modules/executable/macros/src/lib.rs delete mode 100644 modules/executable/src/arguments_parser.rs diff --git a/Cargo.toml b/Cargo.toml index 8d16d338..d55f1e76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,6 +135,7 @@ embedded-io = { version = "0.7", features = ["alloc"] } embedded-tls = { version = "0.19", default-features = false, features = [ "alloc", ] } +getargs-derive = { git = "https://github.com/AlixANNERAUD/getargs-derive.git", branch = "main" } [workspace] members = [ @@ -153,7 +154,6 @@ members = [ "executables/file_manager", "executables/shell/command_line", "modules/executable", - "modules/executable/macros", "executables/wasm", "executables/wasm/bindings", "executables/wasm/tests/wasm_test", diff --git a/executables/shell/command_line/Cargo.toml b/executables/shell/command_line/Cargo.toml index be1e9f29..ec45a1ff 100644 --- a/executables/shell/command_line/Cargo.toml +++ b/executables/shell/command_line/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] xila = { path = "../../../" } getargs = { version = "0.5" } -executable_macros = { path = "../../../modules/executable/macros" } +getargs-derive = { workspace = true } [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))'.dev-dependencies] drivers_native = { workspace = true } diff --git a/executables/shell/command_line/locales/en.json b/executables/shell/command_line/locales/en.json index 6885a3bc..cf1c75e1 100644 --- a/executables/shell/command_line/locales/en.json +++ b/executables/shell/command_line/locales/en.json @@ -1,4 +1,5 @@ { + "Argument parsing error: {}": "Argument parsing error: {}", "Accessed": "Accessed", "Authentication failed: {}": "Authentication failed: {}", "Cannot resolve {}: Unknown host": "Cannot resolve {}: Unknown host", diff --git a/executables/shell/command_line/locales/fr.json b/executables/shell/command_line/locales/fr.json index 6dc6faa5..58155e2d 100644 --- a/executables/shell/command_line/locales/fr.json +++ b/executables/shell/command_line/locales/fr.json @@ -1,4 +1,5 @@ { + "Argument parsing error: {}": "Erreur d'analyse des arguments: {}", "Accessed": "Accédé", "Authentication failed: {}": "Échec de l'authentification: {}", "Cannot resolve {}: Unknown host": "Impossible de résoudre {}: Hôte inconnu", diff --git a/executables/shell/command_line/src/commands/change_directory.rs b/executables/shell/command_line/src/commands/change_directory.rs index 2805d5be..dd138a84 100644 --- a/executables/shell/command_line/src/commands/change_directory.rs +++ b/executables/shell/command_line/src/commands/change_directory.rs @@ -1,6 +1,6 @@ use crate::{Error, Result}; use alloc::borrow::ToOwned; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::Path, virtual_file_system::{self, Directory}, diff --git a/executables/shell/command_line/src/commands/clear.rs b/executables/shell/command_line/src/commands/clear.rs index 345dd806..e5e7ae0d 100644 --- a/executables/shell/command_line/src/commands/clear.rs +++ b/executables/shell/command_line/src/commands/clear.rs @@ -1,6 +1,6 @@ use crate::Result; -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::file_system::Path; use super::{CommandContext, UserCommand}; diff --git a/executables/shell/command_line/src/commands/directory.rs b/executables/shell/command_line/src/commands/directory.rs index 1cedc15e..f68e6952 100644 --- a/executables/shell/command_line/src/commands/directory.rs +++ b/executables/shell/command_line/src/commands/directory.rs @@ -1,7 +1,7 @@ use crate::{Error, Result}; use alloc::borrow::ToOwned; -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::{ file_system::{Kind, Path}, virtual_file_system::{self, Directory}, diff --git a/executables/shell/command_line/src/commands/dns.rs b/executables/shell/command_line/src/commands/dns.rs index d90cdde7..232aa8c6 100644 --- a/executables/shell/command_line/src/commands/dns.rs +++ b/executables/shell/command_line/src/commands/dns.rs @@ -1,6 +1,6 @@ use crate::Result; -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::{ file_system::Path, internationalization::translate, diff --git a/executables/shell/command_line/src/commands/environment_variables.rs b/executables/shell/command_line/src/commands/environment_variables.rs index 0d4271e0..e0a2e726 100644 --- a/executables/shell/command_line/src/commands/environment_variables.rs +++ b/executables/shell/command_line/src/commands/environment_variables.rs @@ -1,6 +1,6 @@ use crate::{Error, Result}; -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::{file_system::Path, task}; use super::{CommandContext, UserCommand}; diff --git a/executables/shell/command_line/src/commands/exit.rs b/executables/shell/command_line/src/commands/exit.rs index b93a6621..1f1729ed 100644 --- a/executables/shell/command_line/src/commands/exit.rs +++ b/executables/shell/command_line/src/commands/exit.rs @@ -1,5 +1,5 @@ -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::file_system::Path; use crate::Result; diff --git a/executables/shell/command_line/src/commands/head.rs b/executables/shell/command_line/src/commands/head.rs index be6d9348..469ad71a 100644 --- a/executables/shell/command_line/src/commands/head.rs +++ b/executables/shell/command_line/src/commands/head.rs @@ -1,6 +1,6 @@ use crate::{Error, Result}; use alloc::{borrow::ToOwned, vec::Vec}; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::{AccessFlags, Path}, virtual_file_system::{self, File}, diff --git a/executables/shell/command_line/src/commands/ip.rs b/executables/shell/command_line/src/commands/ip.rs index d890863f..5e5952d9 100644 --- a/executables/shell/command_line/src/commands/ip.rs +++ b/executables/shell/command_line/src/commands/ip.rs @@ -1,5 +1,5 @@ use crate::error::{Error, Result}; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::{AccessFlags, Path}, log, diff --git a/executables/shell/command_line/src/commands/list.rs b/executables/shell/command_line/src/commands/list.rs index 0f91a159..fecc8f68 100644 --- a/executables/shell/command_line/src/commands/list.rs +++ b/executables/shell/command_line/src/commands/list.rs @@ -1,7 +1,7 @@ use crate::{Error, Result}; use alloc::borrow::ToOwned; use alloc::string::ToString; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::{Kind, Path}, log, users, diff --git a/executables/shell/command_line/src/commands/ping.rs b/executables/shell/command_line/src/commands/ping.rs index 865039da..a750d1f7 100644 --- a/executables/shell/command_line/src/commands/ping.rs +++ b/executables/shell/command_line/src/commands/ping.rs @@ -1,4 +1,4 @@ -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::Path, internationalization::translate, diff --git a/executables/shell/command_line/src/commands/statistics.rs b/executables/shell/command_line/src/commands/statistics.rs index 48735603..1eb5ae2e 100644 --- a/executables/shell/command_line/src/commands/statistics.rs +++ b/executables/shell/command_line/src/commands/statistics.rs @@ -1,7 +1,7 @@ use crate::{Error, Result}; use alloc::{borrow::ToOwned, format, string::String}; -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::{ file_system::Path, internationalization::translate, diff --git a/executables/shell/command_line/src/commands/tail.rs b/executables/shell/command_line/src/commands/tail.rs index 2226adf9..19506ccb 100644 --- a/executables/shell/command_line/src/commands/tail.rs +++ b/executables/shell/command_line/src/commands/tail.rs @@ -1,6 +1,6 @@ use crate::{Error, Result}; use alloc::{borrow::ToOwned, vec::Vec}; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::{AccessFlags, Path}, virtual_file_system::{self, File}, diff --git a/executables/shell/command_line/src/commands/which.rs b/executables/shell/command_line/src/commands/which.rs index da1f354d..d286fad9 100644 --- a/executables/shell/command_line/src/commands/which.rs +++ b/executables/shell/command_line/src/commands/which.rs @@ -1,6 +1,6 @@ use alloc::borrow::ToOwned; -use executable_macros::GetArgs; use getargs::Options; +use getargs_derive::GetArgs; use xila::{file_system::Path, virtual_file_system}; use crate::{Result, error::Error, resolver::resolve}; diff --git a/executables/shell/command_line/src/commands/word_count.rs b/executables/shell/command_line/src/commands/word_count.rs index 55298b2d..7b5256fb 100644 --- a/executables/shell/command_line/src/commands/word_count.rs +++ b/executables/shell/command_line/src/commands/word_count.rs @@ -1,6 +1,6 @@ use crate::{Error, Result}; use alloc::borrow::ToOwned; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::{ file_system::{AccessFlags, Path}, virtual_file_system::{self, File}, diff --git a/executables/shell/command_line/src/error.rs b/executables/shell/command_line/src/error.rs index b9633aee..b5c5f82b 100644 --- a/executables/shell/command_line/src/error.rs +++ b/executables/shell/command_line/src/error.rs @@ -35,6 +35,7 @@ pub enum Error { FailedToReadFile(virtual_file_system::Error), RequiresValue, DoesNotRequireValue, + ArgumentParsing(getargs_derive::Error), InvalidArgument, MissingPositionalArgument(&'static str), InvalidOption, @@ -55,6 +56,12 @@ impl From> for Error { } } +impl From for Error { + fn from(value: getargs_derive::Error) -> Self { + Error::ArgumentParsing(value) + } +} + impl Error { pub fn get_discriminant(&self) -> NonZeroU16 { unsafe { *<*const _>::from(self).cast::() } @@ -188,6 +195,9 @@ impl Display for Error { Error::FailedToCreateSocket(error) => { write!(formatter, translate!("Failed to create socket: {}"), error) } + Error::ArgumentParsing(error) => { + write!(formatter, translate!("Argument parsing error: {}"), error) + } Error::Format => { write!(formatter, translate!("Format error")) } diff --git a/executables/shell/graphical/Cargo.toml b/executables/shell/graphical/Cargo.toml index c94b356a..bbac084a 100644 --- a/executables/shell/graphical/Cargo.toml +++ b/executables/shell/graphical/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" xila = { path = "../../../", features = ["graphics"] } miniserde = { workspace = true } getargs = { version = "0.5" } -executable_macros = { path = "../../../modules/executable/macros" } +getargs-derive = { workspace = true } [target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))'.dev-dependencies] drivers_native = { workspace = true } diff --git a/executables/shell/graphical/src/lib.rs b/executables/shell/graphical/src/lib.rs index 7bfd9c8c..ef891e63 100644 --- a/executables/shell/graphical/src/lib.rs +++ b/executables/shell/graphical/src/lib.rs @@ -15,7 +15,7 @@ use alloc::{boxed::Box, string::String, vec::Vec}; use core::fmt::Write; use core::num::NonZeroUsize; use core::time::Duration; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use home::Home; use layout::Layout; use login::Login; diff --git a/executables/wasm/Cargo.toml b/executables/wasm/Cargo.toml index 8b34d0a4..5906bfbd 100644 --- a/executables/wasm/Cargo.toml +++ b/executables/wasm/Cargo.toml @@ -12,7 +12,7 @@ wamr-rust-sdk = { git = "https://github.com/bytecodealliance/wamr-rust-sdk.git", "instruction-metering", ], optional = true } getargs = { version = "0.5" } -executable_macros = { path = "../../modules/executable/macros" } +getargs-derive = { workspace = true } smol_str = { workspace = true } [features] diff --git a/executables/wasm/src/host/mod.rs b/executables/wasm/src/host/mod.rs index fa1d245f..baf630a2 100644 --- a/executables/wasm/src/host/mod.rs +++ b/executables/wasm/src/host/mod.rs @@ -6,7 +6,7 @@ use alloc::string::ToString; use alloc::{borrow::ToOwned, string::String, vec, vec::Vec}; use core::fmt::Write; use core::num::{NonZeroU32, NonZeroUsize}; -use executable_macros::GetArgs; +use getargs_derive::GetArgs; use xila::executable::ExecutableTrait; use xila::executable::MainFuture; use xila::executable::Standard; diff --git a/executables/wasm/src/host/virtual_machine/error.rs b/executables/wasm/src/host/virtual_machine/error.rs index bf1eeee9..ef0d7cd4 100644 --- a/executables/wasm/src/host/virtual_machine/error.rs +++ b/executables/wasm/src/host/virtual_machine/error.rs @@ -108,6 +108,20 @@ impl From for Error { } } } +impl From for Error { + fn from(value: getargs_derive::Error) -> Self { + match value { + getargs_derive::Error::MissingPositionalArgument(name) => { + Error::MissingPositionalArgument(name) + } + getargs_derive::Error::UnknownOption => Error::InvalidOption, + getargs_derive::Error::MissingOptionValue(name) => Error::MissingArgument(name), + getargs_derive::Error::ParseError(_) => Error::InvalidOption, + getargs_derive::Error::InvalidNumberOfArguments => Error::InvalidNumberOfArguments, + } + } +} + impl From for Error { fn from(error: task::Error) -> Self { Error::FailedToGetTaskInformations(error) diff --git a/modules/executable/macros/Cargo.toml b/modules/executable/macros/Cargo.toml deleted file mode 100644 index e7adeaaa..00000000 --- a/modules/executable/macros/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "executable_macros" -version = "0.1.0" -edition = "2024" - -[dependencies] -syn = { workspace = true } -quote = { workspace = true } -proc-macro2 = { workspace = true } -regex = { workspace = true } -once_cell = "1.21" -serde_json = "1.0" - -[lib] -proc-macro = true diff --git a/modules/executable/macros/src/lib.rs b/modules/executable/macros/src/lib.rs deleted file mode 100644 index fdd9a9c7..00000000 --- a/modules/executable/macros/src/lib.rs +++ /dev/null @@ -1,349 +0,0 @@ -use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::{ - Data, DeriveInput, Expr, Field, Fields, GenericParam, LitChar, LitStr, Type, parse_macro_input, - spanned::Spanned, -}; - -fn is_bool_type(ty: &Type) -> bool { - match ty { - Type::Path(path) => path - .path - .segments - .last() - .map(|segment| segment.ident == "bool") - .unwrap_or(false), - _ => false, - } -} - -// Added helper to detect NonZero types (e.g., NonZeroU32, NonZeroI64, NonZero, etc.) -fn is_nonzero_type(ty: &Type) -> bool { - match ty { - Type::Path(path) => path - .path - .segments - .last() - .map(|segment| segment.ident.to_string().starts_with("NonZero")) - .unwrap_or(false), - _ => false, - } -} - -fn is_str_reference_type(ty: &Type) -> bool { - match ty { - Type::Reference(reference) => match reference.elem.as_ref() { - Type::Path(path) => path - .path - .segments - .last() - .map(|segment| segment.ident == "str") - .unwrap_or(false), - _ => false, - }, - _ => false, - } -} - -fn to_kebab_case(name: &str) -> String { - name.replace('_', "-") -} - -#[derive(Default)] -struct FieldConfig { - positional: bool, - flag: bool, - short: Option, - long: Option, - default: Option, - legacy_default_used: bool, -} - -fn parse_field_config(field: &Field) -> syn::Result { - let mut config = FieldConfig::default(); - - for attr in &field.attrs { - if attr.path().is_ident("arg") { - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("positional") { - config.positional = true; - return Ok(()); - } - - if meta.path.is_ident("flag") { - config.flag = true; - return Ok(()); - } - - if meta.path.is_ident("short") { - let value: LitChar = meta.value()?.parse()?; - config.short = Some(value.value()); - return Ok(()); - } - - if meta.path.is_ident("long") { - let value: LitStr = meta.value()?.parse()?; - config.long = Some(value.value()); - return Ok(()); - } - - if meta.path.is_ident("default") { - let value: Expr = meta.value()?.parse()?; - config.default = Some(value); - return Ok(()); - } - - Err(meta.error( - "unsupported argument, expected one of: positional, flag, short, long, default", - )) - })?; - } - - if attr.path().is_ident("default") { - config.legacy_default_used = true; - - match &attr.meta { - syn::Meta::List(list) => { - let value: Expr = syn::parse2(list.tokens.clone())?; - config.default = Some(value); - } - syn::Meta::NameValue(named) => { - config.default = Some(named.value.clone()); - } - syn::Meta::Path(_) => { - return Err(syn::Error::new( - attr.span(), - "#[default] requires a value, e.g. #[default(4)]", - )); - } - } - } - } - - Ok(config) -} - -#[proc_macro_derive(GetArgs, attributes(arg, default))] -pub fn derive_get_args(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = &input.ident; - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let lifetime_params: Vec<_> = input - .generics - .params - .iter() - .filter_map(|parameter| match parameter { - GenericParam::Lifetime(lifetime) => Some(lifetime.lifetime.clone()), - _ => None, - }) - .collect(); - - let fields = match &input.data { - Data::Struct(data) => match &data.fields { - Fields::Named(fields) => &fields.named, - _ => { - return syn::Error::new_spanned( - &input, - "GetArgs only works on structs with named fields", - ) - .to_compile_error() - .into(); - } - }, - _ => { - return syn::Error::new_spanned(&input, "GetArgs only works on structs") - .to_compile_error() - .into(); - } - }; - - let mut initializers = Vec::new(); - let mut option_match_arms = Vec::new(); - let mut positional_match_arms = Vec::new(); - let mut required_checks = Vec::new(); - let mut field_builds = Vec::new(); - - let mut positional_index = 0usize; - - for field in fields { - let Some(field_name) = field.ident.as_ref() else { - return syn::Error::new_spanned(field, "GetArgs only works on named fields") - .to_compile_error() - .into(); - }; - - let field_type = &field.ty; - let field_str = field_name.to_string(); - let config = match parse_field_config(field) { - Ok(config) => config, - Err(error) => return error.to_compile_error().into(), - }; - - let derived_long = to_kebab_case(&field_str); - let derived_short = field_str - .chars() - .next() - .expect("field name should never be empty"); - - let is_option = !config.positional - && (config.short.is_some() - || config.long.is_some() - || config.flag - || config.default.is_some() - || config.legacy_default_used); - - let is_required = config.default.is_none() && !config.flag; - - // Adjusted default value resolution to handle NonZero types cleanly - let default_value = config.default.unwrap_or_else(|| { - if config.flag { - syn::parse_quote!(false) - } else if is_nonzero_type(field_type) { - // NonZero types don't implement Default. Use MIN (which is 1 for Unsigned) - // as a safe implicit fallback value if a default wasn't provided. - syn::parse_quote!(<#field_type>::MIN) - } else { - syn::parse_quote!(Default::default()) - } - }); - - let value_ident = format_ident!("__value_{}", field_name); - let seen_ident = format_ident!("__seen_{}", field_name); - - if is_option { - if config.flag && !is_bool_type(field_type) { - return syn::Error::new_spanned( - field, - "#[arg(flag)] can only be used with bool fields", - ) - .to_compile_error() - .into(); - } - - let long_name = config.long.unwrap_or(derived_long); - let short_name = config.short.unwrap_or(derived_short); - - let assign_value = if config.flag { - quote! { - #value_ident = true; - } - } else if is_str_reference_type(field_type) { - quote! { - let value = options - .value() - .map_err(|_| crate::Error::MissingPositionalArgument(#long_name))?; - #value_ident = value; - } - } else { - quote! { - let value = options - .value() - .map_err(|_| crate::Error::MissingPositionalArgument(#long_name))?; - #value_ident = value.parse().map_err(|_| crate::Error::InvalidOption)?; - } - }; - - initializers.push(quote! { - let mut #value_ident: #field_type = #default_value; - let mut #seen_ident = false; - }); - - option_match_arms.push(quote! { - getargs::Arg::Short(#short_name) | getargs::Arg::Long(#long_name) => { - #assign_value - #seen_ident = true; - } - }); - - if is_required { - required_checks.push(quote! { - if !#seen_ident { - return Err(crate::Error::MissingPositionalArgument(#long_name)); - } - }); - } - } else { - let current_index = positional_index; - positional_index += 1; - - initializers.push(quote! { - let mut #value_ident: #field_type = #default_value; - let mut #seen_ident = false; - }); - - let assign_positional = if is_str_reference_type(field_type) { - quote! { - #value_ident = value; - } - } else { - quote! { - #value_ident = value.parse().map_err(|_| crate::Error::InvalidOption)?; - } - }; - - positional_match_arms.push(quote! { - #current_index => { - #assign_positional - #seen_ident = true; - } - }); - - if is_required { - required_checks.push(quote! { - if !#seen_ident { - return Err(crate::Error::MissingPositionalArgument(#field_str)); - } - }); - } - } - - field_builds.push(quote! { #field_name: #value_ident }); - } - - let positional_handler = if positional_match_arms.is_empty() { - quote! { - getargs::Arg::Positional(_) => { - return Err(crate::Error::InvalidNumberOfArguments); - } - } - } else { - quote! { - getargs::Arg::Positional(value) => { - match __positional_index { - #(#positional_match_arms)* - _ => return Err(crate::Error::InvalidNumberOfArguments), - } - __positional_index += 1; - } - } - }; - - let expanded = quote! { - impl #impl_generics #name #ty_generics #where_clause { - pub fn parse<'__getargs, I>(options: &mut getargs::Options<&'__getargs str, I>) -> core::result::Result - where - I: Iterator, - #('__getargs: #lifetime_params,)* - { - #(#initializers)* - let mut __positional_index = 0_usize; - - while let Some(argument) = options.next_arg().map_err(|_| crate::Error::InvalidOption)? { - match argument { - #(#option_match_arms)* - #positional_handler - _ => return Err(crate::Error::InvalidOption), - } - } - - #(#required_checks)* - - Ok(Self { - #(#field_builds),* - }) - } - } - }; - - TokenStream::from(expanded) -} diff --git a/modules/executable/src/arguments_parser.rs b/modules/executable/src/arguments_parser.rs deleted file mode 100644 index bdde6e93..00000000 --- a/modules/executable/src/arguments_parser.rs +++ /dev/null @@ -1,222 +0,0 @@ -use alloc::string::String; - -#[derive(Debug, PartialEq)] -pub struct OptionArgument<'a>(&'a [String]); - -impl<'a> OptionArgument<'a> { - pub fn get_option_long(&self, name: &str) -> Option<&'a str> { - self.0 - .iter() - .filter_map(|s| s.strip_prefix("--")) - .map(|s| s.split_once('=').unwrap_or((s, ""))) - .find_map(|(key, value)| if key == name { Some(value) } else { None }) - } - - pub fn get_option_short(&self, name: char) -> Option<&'a str> { - self.0 - .iter() - .filter_map(|s| s.strip_prefix('-')) - .map(|s| s.split_once('=').unwrap_or((s, ""))) - .find_map(|(key, value)| { - if key.chars().next()? == name { - Some(value) - } else { - None - } - }) - } - - pub fn get_option(&self, name: &str) -> Option<&'a str> { - if let Some(first_char) = name.chars().next() { - self.get_option_long(name) - .or_else(|| self.get_option_short(first_char)) - } else { - None - } - } -} - -#[derive(Debug, PartialEq)] -pub struct PositionalArgument<'a> { - pub value: Option<&'a str>, - pub options: OptionArgument<'a>, -} - -#[derive(Debug, Clone)] -pub struct ArgumentsParser<'a> { - arguments: &'a [String], -} - -impl<'a> ArgumentsParser<'a> { - pub fn new(arguments: &'a [String]) -> Self { - Self { arguments } - } -} - -impl<'a> Iterator for ArgumentsParser<'a> { - type Item = PositionalArgument<'a>; - - fn next(&mut self) -> Option { - if self.arguments.is_empty() { - return None; - } - - let value = if let Some(item) = self.arguments.first() - && !item.starts_with('-') - { - self.arguments = &self.arguments[1..]; - Some(item.as_str()) - } else { - None - }; - - let options_end = self - .arguments - .iter() - .position(|arg| !arg.starts_with('-')) - .unwrap_or(self.arguments.len()); - - let options = &self.arguments[..options_end]; - self.arguments = &self.arguments[options_end..]; - - Some(PositionalArgument { - value, - options: OptionArgument(options), - }) - } -} - -#[cfg(test)] -mod tests { - use alloc::{string::ToString, vec, vec::Vec}; - extern crate std; - - use super::*; - - #[test] - fn test_empty_arguments() { - let args: Vec = vec![]; - let mut parser = ArgumentsParser::new(&args); - assert!(parser.next().is_none()); - } - - #[test] - fn test_single_positional_argument() { - let args: Vec = vec!["value1".to_string()]; - let mut parser = ArgumentsParser::new(&args); - let result = parser.next().unwrap(); - assert_eq!(result.value, Some("value1")); - assert!(result.options.get_option("test").is_none()); - assert!(parser.next().is_none()); - } - - #[test] - fn test_positional_with_options() { - let args: Vec = vec![ - "value1".to_string(), - "--opt1=test".to_string(), - "-opt2".to_string(), - ]; - let mut parser = ArgumentsParser::new(&args); - let result = parser.next().unwrap(); - assert_eq!(result.value, Some("value1")); - assert_eq!(result.options.get_option("opt1"), Some("test")); - assert_eq!(result.options.get_option("opt2"), Some("")); - } - - #[test] - fn test_multiple_positional_arguments() { - let args: Vec = vec![ - "value1".to_string(), - "value2".to_string(), - "value3".to_string(), - ]; - let mut parser = ArgumentsParser::new(&args); - assert_eq!( - parser.next(), - Some(PositionalArgument { - value: Some("value1"), - options: OptionArgument(&[]), - }) - ); - assert_eq!( - parser.next(), - Some(PositionalArgument { - value: Some("value2"), - options: OptionArgument(&[]), - }) - ); - assert_eq!( - parser.next(), - Some(PositionalArgument { - value: Some("value3"), - options: OptionArgument(&[]), - }) - ); - assert!(parser.next().is_none()); - } - - #[test] - fn test_options_only() { - let args: Vec = vec!["--opt1=value".to_string(), "-opt2".to_string()]; - let mut parser = ArgumentsParser::new(&args); - let result = parser.next().unwrap(); - assert_eq!(result.value, None); - assert_eq!(result.options.get_option("opt1"), Some("value")); - assert_eq!(result.options.get_option("opt2"), Some("")); - } - - #[test] - fn test_mixed_arguments() { - let args: Vec = vec![ - "pos1".to_string(), - "--opt1=val1".to_string(), - "pos2".to_string(), - "-opt2".to_string(), - "--opt3=val3".to_string(), - "pos3".to_string(), - ]; - let mut parser = ArgumentsParser::new(&args); - - std::println!("{:?}", parser.clone().collect::>()); - - let first = parser.next().unwrap(); - assert_eq!(first.value, Some("pos1")); - assert_eq!(first.options.get_option("opt1"), Some("val1")); - - let second = parser.next().unwrap(); - assert_eq!(second.value, Some("pos2")); - assert_eq!(second.options.get_option("opt2"), Some("")); - assert_eq!(second.options.get_option("opt3"), Some("val3")); - - let third = parser.next().unwrap(); - assert_eq!(third.value, Some("pos3")); - } - - #[test] - fn test_option_not_found() { - let args: Vec = vec!["--opt1=value".to_string()]; - let mut parser = ArgumentsParser::new(&args); - let result = parser.next().unwrap(); - assert!(result.options.get_option("nonexistent").is_none()); - } - - #[test] - fn test_option_with_equals_in_value() { - let args: Vec = vec!["--url=https://example.com?key=value".to_string()]; - let mut parser = ArgumentsParser::new(&args); - let result = parser.next().unwrap(); - assert_eq!( - result.options.get_option("url"), - Some("https://example.com?key=value") - ); - } - - #[test] - fn test_double_dash_prefix() { - let args: Vec = vec!["--option=test".to_string()]; - let mut parser = ArgumentsParser::new(&args); - let result = parser.next().unwrap(); - assert_eq!(result.options.get_option("option"), Some("test")); - } -} diff --git a/modules/executable/src/lib.rs b/modules/executable/src/lib.rs index af28b93f..d25e8929 100644 --- a/modules/executable/src/lib.rs +++ b/modules/executable/src/lib.rs @@ -2,14 +2,12 @@ extern crate alloc; -mod arguments_parser; #[cfg(feature = "building")] mod building; mod error; mod standard; mod traits; -pub use arguments_parser::*; #[cfg(feature = "building")] pub use building::*; pub use error::*;