aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml19
-rw-r--r--examples/example-combine-pathf-dispatch-tree/Cargo.lock1
-rw-r--r--examples/example-pathfinder/Cargo.lock1
-rw-r--r--mingling_pathf/src/pattern_analyzer.rs2
-rw-r--r--mingling_pathf/src/patterns/dispatcher_clap.rs51
-rw-r--r--mingling_pathf/src/patterns/groupped_derive.rs53
-rw-r--r--mingling_pathf/test/src/lib.rs30
-rw-r--r--mingling_pathf/test/src/test_files/test_groupped_derive.rs17
8 files changed, 139 insertions, 35 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 68db23e..441ea47 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,6 +1,11 @@
name: CI
-on: [push]
+on:
+ push:
+ branches: [main]
+
+permissions:
+ contents: write
jobs:
ci:
@@ -12,3 +17,15 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo ci
+
+ move-unreleased-tag:
+ if: github.ref == 'refs/heads/main'
+ needs: ci
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - run: |
+ git tag -f unreleased
+ git push origin unreleased --force
diff --git a/examples/example-combine-pathf-dispatch-tree/Cargo.lock b/examples/example-combine-pathf-dispatch-tree/Cargo.lock
index 4d3e64a..8b1f186 100644
--- a/examples/example-combine-pathf-dispatch-tree/Cargo.lock
+++ b/examples/example-combine-pathf-dispatch-tree/Cargo.lock
@@ -45,6 +45,7 @@ dependencies = [
name = "mingling_pathf"
version = "0.2.0"
dependencies = [
+ "just_fmt",
"proc-macro2",
"syn",
]
diff --git a/examples/example-pathfinder/Cargo.lock b/examples/example-pathfinder/Cargo.lock
index 92af163..36840e7 100644
--- a/examples/example-pathfinder/Cargo.lock
+++ b/examples/example-pathfinder/Cargo.lock
@@ -45,6 +45,7 @@ dependencies = [
name = "mingling_pathf"
version = "0.2.0"
dependencies = [
+ "just_fmt",
"proc-macro2",
"syn",
]
diff --git a/mingling_pathf/src/pattern_analyzer.rs b/mingling_pathf/src/pattern_analyzer.rs
index bfc2dc3..c4b1971 100644
--- a/mingling_pathf/src/pattern_analyzer.rs
+++ b/mingling_pathf/src/pattern_analyzer.rs
@@ -23,7 +23,7 @@ pub fn init_with_config(config: PathfinderConfig) -> PatternAnalyzer {
analyzer.add_pattern(HelpPattern);
analyzer.add_pattern(CompletionPattern);
analyzer.add_pattern(DispatcherPattern::new(config.use_dispatch_tree));
- analyzer.add_pattern(DispatcherClapPattern);
+ analyzer.add_pattern(DispatcherClapPattern::new(config.use_dispatch_tree));
analyzer
}
diff --git a/mingling_pathf/src/patterns/dispatcher_clap.rs b/mingling_pathf/src/patterns/dispatcher_clap.rs
index aed96e5..2e1ec6c 100644
--- a/mingling_pathf/src/patterns/dispatcher_clap.rs
+++ b/mingling_pathf/src/patterns/dispatcher_clap.rs
@@ -7,13 +7,22 @@ use crate::pattern_analyzer::{AnalyzeItem, AnalyzePattern};
/// - The dispatcher struct (`CMD*`, always)
/// - The error type, if `error = ErrorType` is specified
/// - The help internal struct, if `help = true` is specified
+/// - `__internal_dispatcher_*` — dispatch tree static (when `use_dispatch_tree` is true)
///
/// Covers forms:
/// - `#[dispatcher_clap("greet", CMDGreet)] struct EntryGreet { ... }`
/// - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet)] struct EntryGreet { ... }`
/// - `#[dispatcher_clap("greet", CMDGreet, help = true)] struct EntryGreet { ... }`
/// - `#[dispatcher_clap("greet", CMDGreet, error = ErrorGreet, help = true)] struct EntryGreet { ... }`
-pub struct DispatcherClapPattern;
+pub struct DispatcherClapPattern {
+ pub use_dispatch_tree: bool,
+}
+
+impl DispatcherClapPattern {
+ pub fn new(use_dispatch_tree: bool) -> Self {
+ Self { use_dispatch_tree }
+ }
+}
impl AnalyzePattern for DispatcherClapPattern {
fn contains(&self, content: &str) -> bool {
@@ -65,11 +74,6 @@ impl AnalyzePattern for DispatcherClapPattern {
}
// Help internal struct — if help = true
- // The dispatcher_clap macro generates:
- // __{cmd_snake}_help (via `format!("__{}_help", snake_case(dispatcher_struct))`)
- // The `#[help]` macro then generates:
- // __internal_help_{fn_snake} (via `format!("__internal_help_{}", snake_case(fn_name))`)
- // Final name: __internal_help_{snake_case("__{cmd_snake}_help")}
if parsed.help_enabled
&& let Some(ref cmd) = parsed.cmd_type
{
@@ -81,6 +85,20 @@ impl AnalyzePattern for DispatcherClapPattern {
item_name: help_struct,
});
}
+
+ // __internal_dispatcher_* — when configured
+ if self.use_dispatch_tree
+ && let Some(ref cmd_name) = parsed.cmd_name
+ {
+ let internal_name = format!(
+ "__internal_dispatcher_{}",
+ just_fmt::snake_case!(cmd_name)
+ );
+ items.push(AnalyzeItem {
+ module: String::new(),
+ item_name: internal_name,
+ });
+ }
}
}
Item::Mod(item_mod) => {
@@ -135,6 +153,20 @@ impl AnalyzePattern for DispatcherClapPattern {
item_name: help_struct,
});
}
+
+ // __internal_dispatcher_* — when configured
+ if self.use_dispatch_tree
+ && let Some(ref cmd_name) = parsed.cmd_name
+ {
+ let internal_name = format!(
+ "__internal_dispatcher_{}",
+ just_fmt::snake_case!(cmd_name)
+ );
+ items.push(AnalyzeItem {
+ module: item_mod.ident.to_string(),
+ item_name: internal_name,
+ });
+ }
}
}
}
@@ -149,6 +181,7 @@ impl AnalyzePattern for DispatcherClapPattern {
}
struct ParsedClapArgs {
+ cmd_name: Option<String>,
cmd_type: Option<String>,
error_type: Option<String>,
help_enabled: bool,
@@ -156,17 +189,18 @@ struct ParsedClapArgs {
/// Parse `#[dispatcher_clap("cmd", CMDType, error = ErrorType, help = true)]` arguments.
fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs {
+ let mut cmd_name = None;
let mut cmd_type = None;
let mut error_type = None;
let mut help_enabled = false;
let args = args.trim();
- // Find the first quoted string (the command name) and skip it
- // After that, look for ident-like tokens separated by commas
+ // Extract the first quoted string (the command name)
let after_cmd = if let Some(start) = args.find('"') {
let after_open = &args[start + 1..];
if let Some(end) = after_open.find('"') {
+ cmd_name = Some(after_open[..end].to_string());
after_open[end + 1..].trim()
} else {
args
@@ -212,6 +246,7 @@ fn parse_dispatcher_clap_args(args: &str) -> ParsedClapArgs {
}
ParsedClapArgs {
+ cmd_name,
cmd_type,
error_type,
help_enabled,
diff --git a/mingling_pathf/src/patterns/groupped_derive.rs b/mingling_pathf/src/patterns/groupped_derive.rs
index 44e7731..8491121 100644
--- a/mingling_pathf/src/patterns/groupped_derive.rs
+++ b/mingling_pathf/src/patterns/groupped_derive.rs
@@ -24,27 +24,24 @@ impl AnalyzePattern for GrouppedDerivePattern {
for item in &syntax.items {
match item {
- Item::Struct(s)
- if has_groupped_derive(&s.attrs) => {
- items.push(AnalyzeItem {
- module: String::new(),
- item_name: s.ident.to_string(),
- });
- }
- Item::Enum(e)
- if has_groupped_derive(&e.attrs) => {
- items.push(AnalyzeItem {
- module: String::new(),
- item_name: e.ident.to_string(),
- });
- }
- Item::Union(u)
- if has_groupped_derive(&u.attrs) => {
- items.push(AnalyzeItem {
- module: String::new(),
- item_name: u.ident.to_string(),
- });
- }
+ Item::Struct(s) if has_groupped_derive(&s.attrs) => {
+ items.push(AnalyzeItem {
+ module: String::new(),
+ item_name: s.ident.to_string(),
+ });
+ }
+ Item::Enum(e) if has_groupped_derive(&e.attrs) => {
+ items.push(AnalyzeItem {
+ module: String::new(),
+ item_name: e.ident.to_string(),
+ });
+ }
+ Item::Union(u) if has_groupped_derive(&u.attrs) => {
+ items.push(AnalyzeItem {
+ module: String::new(),
+ item_name: u.ident.to_string(),
+ });
+ }
Item::Mod(item_mod) => {
if let Some((_, nested)) = &item_mod.content {
for n in nested {
@@ -77,12 +74,18 @@ impl AnalyzePattern for GrouppedDerivePattern {
fn has_groupped_derive(attrs: &[syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
- attr.parse_args::<syn::MetaList>().ok().is_some_and(|meta| {
- meta.path.segments.iter().any(|seg| {
- let name = seg.ident.to_string();
+ // Correctly parse comma-separated paths in #[derive(Groupped, Debug, ...)]
+ attr.parse_args_with(|input: syn::parse::ParseStream| {
+ let paths =
+ syn::punctuated::Punctuated::<syn::Path, syn::Token![,]>::parse_terminated(
+ input,
+ )?;
+ Ok(paths.iter().any(|p| {
+ let name = p.segments.last().unwrap().ident.to_string();
name == "Groupped" || name == "GrouppedSerialize"
- })
+ }))
})
+ .unwrap_or(false)
} else {
false
}
diff --git a/mingling_pathf/test/src/lib.rs b/mingling_pathf/test/src/lib.rs
index 6fce3b2..824cbbf 100644
--- a/mingling_pathf/test/src/lib.rs
+++ b/mingling_pathf/test/src/lib.rs
@@ -233,8 +233,11 @@ fn test_groupped_derive_analyze() {
"::Derived1",
"::Derived2",
"::Derived3",
+ "::EnumDerived1",
+ "::EnumDerived2",
"::sub::Derived1",
"::sub::Derived3",
+ "::sub::EnumDerived1",
];
assert_eq!(r.len(), required.len());
@@ -357,3 +360,30 @@ fn test_dispatcher_clap_analyze() {
assert!(r.contains(*entry), "Result should contain: {}", entry);
}
}
+
+#[test]
+fn test_dispatcher_clap_dispatch_tree() {
+ use mingling_pathf::config::PathfinderConfig;
+ use mingling_pathf::pattern_analyzer;
+
+ let file = current_dir()
+ .unwrap()
+ .join("src/test_files/test_dispatcher_clap.rs");
+
+ // Without dispatch_tree: 26 items (same set as test_dispatcher_clap_analyze)
+ let r1 = pattern_analyzer::init().analyze_file(&file).unwrap();
+ assert_eq!(r1.len(), 26);
+
+ // With dispatch_tree: 26 + 4 __internal (root) + 3 __internal (sub, no "full") = 33
+ let r2 = pattern_analyzer::init_with_config(PathfinderConfig::with_dispatch_tree())
+ .analyze_file(&file)
+ .unwrap();
+ assert_eq!(r2.len(), 33);
+ assert!(r2.contains("::__internal_dispatcher_greet"));
+ assert!(r2.contains("::__internal_dispatcher_delete"));
+ assert!(r2.contains("::__internal_dispatcher_helpcmd"));
+ assert!(r2.contains("::__internal_dispatcher_full"));
+ assert!(r2.contains("::sub::__internal_dispatcher_greet"));
+ assert!(r2.contains("::sub::__internal_dispatcher_delete"));
+ assert!(r2.contains("::sub::__internal_dispatcher_helpcmd"));
+}
diff --git a/mingling_pathf/test/src/test_files/test_groupped_derive.rs b/mingling_pathf/test/src/test_files/test_groupped_derive.rs
index f6c6fa9..913587c 100644
--- a/mingling_pathf/test/src/test_files/test_groupped_derive.rs
+++ b/mingling_pathf/test/src/test_files/test_groupped_derive.rs
@@ -13,6 +13,18 @@ struct Derived3 {
value: bool,
}
+#[derive(Groupped)]
+enum EnumDerived1 {
+ A,
+ B,
+}
+
+#[derive(GrouppedSerialize)]
+enum EnumDerived2 {
+ X(String),
+ Y(i32),
+}
+
pub mod sub {
#[derive(Groupped)]
struct Derived1 {
@@ -23,4 +35,9 @@ pub mod sub {
struct Derived3 {
value: bool,
}
+
+ #[derive(Groupped)]
+ enum EnumDerived1 {
+ A,
+ }
}