aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-13 21:19:03 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-13 21:19:03 +0800
commit17cf59fe8220ac5befbbe8333fa0515ab532103d (patch)
treec2470e6ecc44a74fc40eef8b5bc36901b9e939c6
parentb4cef6e601777b8c4b18df118689f8ac310fa835 (diff)
Remove comp module and add EnumTag derive macro
-rw-r--r--examples/example-basic/Cargo.lock4
-rw-r--r--examples/example-picker/Cargo.lock4
-rw-r--r--mingling/src/comp.rs157
-rw-r--r--mingling/src/lib.rs13
-rw-r--r--mingling_cli/Cargo.lock4
-rw-r--r--mingling_core/src/asset.rs3
-rw-r--r--mingling_core/src/asset/enum_tag.rs11
-rw-r--r--mingling_core/src/lib.rs1
-rw-r--r--mingling_macros/src/enum_tag.rs164
-rw-r--r--mingling_macros/src/lib.rs12
-rw-r--r--mingling_macros/src/suggest.rs21
11 files changed, 218 insertions, 176 deletions
diff --git a/examples/example-basic/Cargo.lock b/examples/example-basic/Cargo.lock
index 02c99b3..114e049 100644
--- a/examples/example-basic/Cargo.lock
+++ b/examples/example-basic/Cargo.lock
@@ -33,8 +33,6 @@ dependencies = [
[[package]]
name = "mingling_core"
version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c2b64cf9820a2ffd0ba1b1a6fdee99f1cce8e102e19d088cda796158b97ea45"
dependencies = [
"just_fmt",
"once_cell",
@@ -45,8 +43,6 @@ dependencies = [
[[package]]
name = "mingling_macros"
version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a053a1e0644b9dd1abd4117d080a12386d1fb7ade3103bbe0bca693fc659716"
dependencies = [
"just_fmt",
"once_cell",
diff --git a/examples/example-picker/Cargo.lock b/examples/example-picker/Cargo.lock
index 3147d64..7f22d09 100644
--- a/examples/example-picker/Cargo.lock
+++ b/examples/example-picker/Cargo.lock
@@ -34,8 +34,6 @@ dependencies = [
[[package]]
name = "mingling_core"
version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c2b64cf9820a2ffd0ba1b1a6fdee99f1cce8e102e19d088cda796158b97ea45"
dependencies = [
"just_fmt",
"once_cell",
@@ -46,8 +44,6 @@ dependencies = [
[[package]]
name = "mingling_macros"
version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a053a1e0644b9dd1abd4117d080a12386d1fb7ade3103bbe0bca693fc659716"
dependencies = [
"just_fmt",
"once_cell",
diff --git a/mingling/src/comp.rs b/mingling/src/comp.rs
deleted file mode 100644
index 7136ccc..0000000
--- a/mingling/src/comp.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-use std::collections::HashSet;
-
-use mingling_core::{Flag, ShellContext, Suggest};
-
-pub struct ShellContextHelper {
- ctx: ShellContext,
-}
-
-impl ShellContextHelper {
- /// Checks if a flag appears exactly once in the command line arguments.
- ///
- /// This method is useful for determining whether a flag should be processed
- /// when it should only be applied once, even if it appears multiple times
- /// in the command line. It returns `true` if the flag is present and
- /// appears exactly once among all words in the shell context.
- ///
- /// # Example
- ///
- /// ```
- /// # use mingling_core::ShellContext;
- /// # use mingling_macros::suggest;
- /// # use mingling::comp_tools::ShellContextHelper;
- ///
- /// let ctx = ShellContext::default();
- /// let helper = ShellContextHelper::from(ctx);
- ///
- /// // Check if either "--insert" or "-i" appears exactly once
- /// if helper.filling_argument_first(["--insert", "-i"]) {
- /// // Perform action that should only happen once, example:
- /// // return suggest! {
- /// // "A", "B", "C"
- /// // }
- /// }
- /// ```
- pub fn filling_argument_first(&self, flag: impl Into<Flag>) -> bool {
- let flag = flag.into();
- if self.filling_argument(&flag) {
- let mut flag_appears = 0;
- for w in self.ctx.all_words.iter() {
- for f in flag.iter() {
- if *f == w {
- flag_appears += 1;
- }
- }
- }
- if flag_appears < 2 {
- return true;
- }
- }
- return false;
- }
-
- /// Checks if the previous word in the command line arguments matches any of the given flags.
- ///
- /// This method determines whether a flag is currently being processed
- /// by checking the word immediately before the cursor position. It returns
- /// `true` if the previous word matches any of the provided flag strings.
- ///
- /// # Example
- ///
- /// ```
- /// # use mingling_core::ShellContext;
- /// # use mingling_macros::suggest;
- /// # use mingling::comp_tools::ShellContextHelper;
- ///
- /// let ctx = ShellContext::default();
- /// let helper = ShellContextHelper::from(ctx);
- ///
- /// // Check if the previous word is either "--file" or "-f"
- /// if helper.filling_argument(["--file", "-f"]) {
- /// // The user is likely expecting a file argument next, example:
- /// // return suggest! {
- /// // "src/main.rs", "Cargo.toml", "README.md"
- /// // }
- /// }
- /// ```
- pub fn filling_argument(&self, flag: impl Into<Flag>) -> bool {
- for f in flag.into().iter() {
- if self.ctx.previous_word == **f {
- return true;
- }
- }
- return false;
- }
-
- /// Checks if the user is currently typing a flag argument.
- ///
- /// This method determines whether the current word being typed starts with
- /// a dash (`-`), indicating that the user is likely in the process of
- /// entering a command-line flag. It returns `true` if the current word
- /// begins with a dash character.
- ///
- /// # Example
- ///
- /// ```
- /// # use mingling_core::ShellContext;
- /// # use mingling_macros::suggest;
- /// # use mingling::comp_tools::ShellContextHelper;
- ///
- /// let ctx = ShellContext::default();
- /// let helper = ShellContextHelper::from(ctx);
- ///
- /// // Check if the user is typing a flag
- /// if helper.typing_argument() {
- /// // The user is likely entering a flag, example:
- /// // return suggest! {
- /// // "--help", "--version", "--verbose"
- /// // }
- /// }
- /// ```
- pub fn typing_argument(&self) -> bool {
- self.ctx.current_word.starts_with("-")
- }
-
- /// Filters out already typed flag arguments from suggestion results.
- ///
- /// This method removes any suggestions that match flag arguments already present
- /// 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.
- pub fn strip_typed_argument(&self, suggest: Suggest) -> Suggest {
- let typed = Self::get_typed_arguments(&self);
- match suggest {
- Suggest::Suggest(mut set) => {
- set.retain(|item| !typed.contains(item.suggest()));
- Suggest::Suggest(set)
- }
- Suggest::FileCompletion => Suggest::FileCompletion,
- }
- }
-
- /// Retrieves all flag arguments from the command line.
- ///
- /// 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.
- pub fn get_typed_arguments(&self) -> HashSet<String> {
- self.ctx
- .all_words
- .iter()
- .filter(|word| word.starts_with("-"))
- .map(|word| word.to_string())
- .collect()
- }
-}
-
-impl From<ShellContext> for ShellContextHelper {
- fn from(ctx: ShellContext) -> Self {
- Self { ctx }
- }
-}
-
-impl From<ShellContextHelper> for ShellContext {
- fn from(helper: ShellContextHelper) -> Self {
- helper.ctx
- }
-}
diff --git a/mingling/src/lib.rs b/mingling/src/lib.rs
index e2aa54a..5f45228 100644
--- a/mingling/src/lib.rs
+++ b/mingling/src/lib.rs
@@ -59,8 +59,6 @@
//! `Mingling` provides detailed usage examples for your reference.
//! See [Examples](_mingling_examples/index.html)
-#[cfg(feature = "comp")]
-mod comp;
mod example_docs;
// Re-export Core lib
@@ -105,16 +103,17 @@ pub mod macros {
#[cfg(feature = "comp")]
/// Used to generate suggestions
pub use mingling_macros::suggest;
+ #[cfg(feature = "comp")]
+ /// Used to generate enum suggestions
+ pub use mingling_macros::suggest_enum;
}
+/// derive macro EnumTag
+pub use mingling_macros::EnumTag;
+
/// derive macro Groupped
pub use mingling_macros::Groupped;
-#[cfg(feature = "comp")]
-pub mod comp_tools {
- pub use crate::comp::*;
-}
-
/// Example projects for `Mingling`, for learning how to use `Mingling`
pub mod _mingling_examples {
pub use crate::example_docs::*;
diff --git a/mingling_cli/Cargo.lock b/mingling_cli/Cargo.lock
index 8a0f2a2..737f437 100644
--- a/mingling_cli/Cargo.lock
+++ b/mingling_cli/Cargo.lock
@@ -72,8 +72,6 @@ dependencies = [
[[package]]
name = "mingling_core"
version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c2b64cf9820a2ffd0ba1b1a6fdee99f1cce8e102e19d088cda796158b97ea45"
dependencies = [
"just_fmt",
"once_cell",
@@ -84,8 +82,6 @@ dependencies = [
[[package]]
name = "mingling_macros"
version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a053a1e0644b9dd1abd4117d080a12386d1fb7ade3103bbe0bca693fc659716"
dependencies = [
"just_fmt",
"once_cell",
diff --git a/mingling_core/src/asset.rs b/mingling_core/src/asset.rs
index 1270a50..a4254ef 100644
--- a/mingling_core/src/asset.rs
+++ b/mingling_core/src/asset.rs
@@ -9,6 +9,9 @@ pub mod comp;
pub mod dispatcher;
#[doc(hidden)]
+pub mod enum_tag;
+
+#[doc(hidden)]
pub mod node;
#[doc(hidden)]
diff --git a/mingling_core/src/asset/enum_tag.rs b/mingling_core/src/asset/enum_tag.rs
new file mode 100644
index 0000000..563d826
--- /dev/null
+++ b/mingling_core/src/asset/enum_tag.rs
@@ -0,0 +1,11 @@
+/// Marker trait for EnumTag
+pub trait EnumTag {
+ /// Get the name and description of this enum
+ fn enum_info(&self) -> (&'static str, &'static str);
+
+ /// Get all possible enum variant names and descriptions
+ fn enums() -> &'static [(&'static str, &'static str)];
+
+ /// Build the enum from a name
+ fn build_enum(name: String) -> Self;
+}
diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs
index 2c5cf55..87be806 100644
--- a/mingling_core/src/lib.rs
+++ b/mingling_core/src/lib.rs
@@ -24,6 +24,7 @@ pub use crate::asset::chain::*;
#[cfg(feature = "comp")]
pub use crate::asset::comp::*;
pub use crate::asset::dispatcher::*;
+pub use crate::asset::enum_tag::*;
pub use crate::asset::node::*;
pub use crate::asset::renderer::*;
diff --git a/mingling_macros/src/enum_tag.rs b/mingling_macros/src/enum_tag.rs
new file mode 100644
index 0000000..a30a0d5
--- /dev/null
+++ b/mingling_macros/src/enum_tag.rs
@@ -0,0 +1,164 @@
+//! EnumTag derive macro implementation
+//!
+//! This module provides the `#[derive(EnumTag)]` procedural macro for enums.
+//! The macro generates implementations of the `EnumTag` trait for enums with
+//! unit variants only (no fields). Variants can have an optional `#[enum_desc]`
+//! attribute to provide descriptions.
+
+use proc_macro::TokenStream;
+use proc_macro2::Span;
+use quote::quote;
+use syn::{
+ Attribute, Data, DeriveInput, Error, Expr, Fields, Ident, Lit, LitStr, Meta, MetaNameValue,
+ Result, Variant, parse_macro_input,
+};
+
+pub fn derive_enum_tag(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+
+ match derive_enum_tag_impl(input) {
+ Ok(tokens) => tokens.into(),
+ Err(err) => err.to_compile_error().into(),
+ }
+}
+
+/// 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;
+
+ // Extract enum data
+ let data = match input.data {
+ Data::Enum(data_enum) => data_enum,
+ Data::Struct(_) => {
+ return Err(Error::new_spanned(
+ enum_name,
+ "EnumTag can only be derived for enums, not structs",
+ ));
+ }
+ Data::Union(_) => {
+ return Err(Error::new_spanned(
+ enum_name,
+ "EnumTag can only be derived for enums, not unions",
+ ));
+ }
+ };
+
+ // Process each variant
+ let mut variant_info = Vec::new();
+ let mut match_arms = Vec::new();
+ let mut build_match_arms = Vec::new();
+
+ for variant in data.variants {
+ process_variant(
+ variant,
+ enum_name,
+ &mut variant_info,
+ &mut match_arms,
+ &mut build_match_arms,
+ )?;
+ }
+
+ // Generate the implementation
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let expanded = quote! {
+ impl #impl_generics ::mingling::EnumTag for #enum_name #ty_generics #where_clause {
+ fn enum_info(&self) -> (&'static str, &'static str) {
+ match self {
+ #(#match_arms)*
+ }
+ }
+
+ fn build_enum(name: String) -> Self {
+ match name.as_str() {
+ #(#build_match_arms)*
+ _ => panic!("Invalid enum variant name: {}", name),
+ }
+ }
+
+ fn enums() -> &'static [(&'static str, &'static str)] {
+ &[#(#variant_info),*]
+ }
+ }
+ };
+
+ Ok(expanded)
+}
+
+/// Process a single enum variant
+fn process_variant(
+ variant: Variant,
+ enum_name: &Ident,
+ variant_info: &mut Vec<proc_macro2::TokenStream>,
+ match_arms: &mut Vec<proc_macro2::TokenStream>,
+ build_match_arms: &mut Vec<proc_macro2::TokenStream>,
+) -> Result<()> {
+ let variant_name = variant.ident.clone();
+
+ // Check if variant has fields
+ match &variant.fields {
+ Fields::Unit => {
+ // Good, unit variant
+ }
+ Fields::Named(_) | Fields::Unnamed(_) => {
+ return Err(Error::new_spanned(
+ &variant,
+ format!(
+ "EnumTag cannot be derived for enum variant `{}` with fields. Only unit variants are supported.",
+ variant_name
+ ),
+ ));
+ }
+ }
+
+ // Extract description from #[enum_desc] attribute
+ let description = extract_description(&variant.attrs, &variant_name)?;
+
+ // Generate tokens for this variant
+ let variant_name_str = variant_name.to_string();
+ let description_str = description.value();
+
+ variant_info.push(quote! {
+ (#variant_name_str, #description_str)
+ });
+
+ match_arms.push(quote! {
+ #enum_name::#variant_name => (#variant_name_str, #description_str),
+ });
+
+ build_match_arms.push(quote! {
+ #variant_name_str => #enum_name::#variant_name,
+ });
+
+ Ok(())
+}
+
+/// Extract description from #[enum_desc] attribute
+fn extract_description(attrs: &[Attribute], variant_name: &Ident) -> Result<LitStr> {
+ for attr in attrs {
+ if attr.path().is_ident("enum_desc") {
+ return match attr.parse_args::<Meta>() {
+ Ok(Meta::NameValue(MetaNameValue {
+ value:
+ Expr::Lit(syn::PatLit {
+ lit: Lit::Str(lit_str),
+ ..
+ }),
+ ..
+ })) => Ok(lit_str),
+ Ok(_) => Err(Error::new_spanned(
+ attr,
+ "#[enum_desc] attribute must be in the form `#[enum_desc(\"description\")]`",
+ )),
+ Err(_) => Err(Error::new_spanned(
+ attr,
+ "Failed to parse #[enum_desc] attribute",
+ )),
+ };
+ }
+ }
+
+ // If no #[enum_desc] attribute, use variant name as description
+ Ok(LitStr::new(&variant_name.to_string(), Span::call_site()))
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index f97c458..a989ce3 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -15,6 +15,7 @@ mod chain;
#[cfg(feature = "comp")]
mod completion;
mod dispatcher_chain;
+mod enum_tag;
mod groupped;
mod node;
mod pack;
@@ -98,6 +99,11 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream {
groupped::derive_groupped(input)
}
+#[proc_macro_derive(EnumTag, attributes(enum_desc))]
+pub fn derive_enum_tag(input: TokenStream) -> TokenStream {
+ enum_tag::derive_enum_tag(input)
+}
+
#[cfg(feature = "general_renderer")]
#[proc_macro_derive(GrouppedSerialize, attributes(group))]
pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream {
@@ -343,6 +349,12 @@ pub fn suggest(input: TokenStream) -> TokenStream {
suggest::suggest(input)
}
+#[cfg(feature = "comp")]
+#[proc_macro]
+pub fn suggest_enum(input: TokenStream) -> TokenStream {
+ suggest::suggest_enum(input)
+}
+
fn read_name(input: &TokenStream) -> Ident {
if input.is_empty() {
Ident::new("ThisProgram", proc_macro2::Span::call_site())
diff --git a/mingling_macros/src/suggest.rs b/mingling_macros/src/suggest.rs
index 7354ff0..d3ab446 100644
--- a/mingling_macros/src/suggest.rs
+++ b/mingling_macros/src/suggest.rs
@@ -70,3 +70,24 @@ pub fn suggest(input: TokenStream) -> TokenStream {
expanded.into()
}
+
+pub fn suggest_enum(input: TokenStream) -> TokenStream {
+ let enum_type = parse_macro_input!(input as syn::Type);
+
+ let expanded = quote! {{
+ let mut enum_suggest = ::mingling::Suggest::new();
+ for (name, desc) in <#enum_type>::enums() {
+ if desc.is_empty() {
+ enum_suggest.insert(::mingling::SuggestItem::new(name.to_string()));
+ } else {
+ enum_suggest.insert(::mingling::SuggestItem::new_with_desc(
+ name.to_string(),
+ desc.to_string(),
+ ));
+ }
+ }
+ enum_suggest
+ }};
+
+ expanded.into()
+}