From f73f7f83a45f768272fb653da42be25d2185006c Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:25:03 +0200 Subject: [PATCH 1/5] feat: enhance function listing with category and description filters --- dsc/locales/en-us.toml | 3 ++ dsc/src/args.rs | 5 ++ dsc/src/subcommand.rs | 32 ++++++++++-- dsc/tests/dsc_function_list.tests.ps1 | 71 +++++++++++++++++++++++++++ lib/dsc-lib/locales/en-us.toml | 1 + lib/dsc-lib/src/functions/mod.rs | 43 ++++++++++++++++ 6 files changed, 151 insertions(+), 4 deletions(-) diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 49e9b50dd..ca6ca852d 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -34,6 +34,8 @@ getAll = "Get all instances of the resource" resource = "The name of the resource to invoke" functionAbout = "Operations on DSC functions" listFunctionAbout = "List or find functions" +functionCategory = "Category to filter functions on; specify multiple times to require all categories" +functionDescription = "Description to search for in the function description, accepts wildcards" version = "The version of the resource to invoke in semver format" serverAbout = "Use DSC as a server over JSON-RPC (useful as MCP server)" bicepAbout = "Use DSC as a Bicep server over gRPC" @@ -152,6 +154,7 @@ tableHeader_functionName = "Function" tableHeader_functionCategory = "Category" tableHeader_syntax = "Syntax" invalidFunctionFilter = "Invalid function filter" +invalidFunctionDescriptionFilter = "Invalid function description filter" maxInt = "maxInt" invalidManifest = "Error in manifest for" jsonArrayNotSupported = "JSON array output format is only supported for `--all'" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 4d4622287..3108c6317 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use clap_complete::Shell; use dsc_lib::dscresources::command_resource::TraceLevel; +use dsc_lib::functions::FunctionCategory; use dsc_lib::progress::ProgressFormat; use dsc_lib::types::{FullyQualifiedTypeName, ResourceVersionReq, TypeNameFilter}; use rust_i18n::t; @@ -193,6 +194,10 @@ pub enum FunctionSubCommand { List { /// Optional function name to filter the list function_name: Option, + #[clap(short = 'c', long = "category", help = t!("args.functionCategory").to_string())] + category: Vec, + #[clap(short, long, help = t!("args.functionDescription").to_string())] + description: Option, #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 1c86d9ae5..f0c7fb3b9 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -28,7 +28,7 @@ use dsc_lib::{ }, dscresources::dscresource::{Capability, ImplementedAs, validate_json, validate_properties}, extensions::dscextension::Capability as ExtensionCapability, - functions::FunctionDispatcher, + functions::{FunctionCategory, FunctionDispatcher}, progress::ProgressFormat, util::convert_wildcard_to_regex, }; @@ -541,8 +541,8 @@ pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressForm pub fn function(subcommand: &FunctionSubCommand) { let functions = FunctionDispatcher::new(); match subcommand { - FunctionSubCommand::List { function_name, output_format } => { - list_functions(&functions, function_name.as_ref(), output_format.as_ref()); + FunctionSubCommand::List { function_name, category, description, output_format } => { + list_functions(&functions, function_name.as_ref(), category, description.as_ref(), output_format.as_ref()); }, } } @@ -689,7 +689,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format } } -fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, output_format: Option<&ListOutputFormat>) { +fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, category: &[FunctionCategory], description: Option<&String>, output_format: Option<&ListOutputFormat>) { let write_table = should_write_table(output_format); let mut table = Table::new(&[ t!("subcommand.tableHeader_functionCategory").to_string().as_ref(), @@ -709,6 +709,19 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> exit(EXIT_INVALID_ARGS); }; + let description_regex = description.map(|description| { + let regex_str = convert_wildcard_to_regex(description); + // strip the `^` and `$` anchors so the filter searches within the description text + let regex_str = ®ex_str[1..regex_str.len() - 1]; + let mut regex_builder = RegexBuilder::new(regex_str); + regex_builder.case_insensitive(true); + let Ok(regex) = regex_builder.build() else { + error!("{}: {}", t!("subcommand.invalidFunctionDescriptionFilter"), regex_str); + exit(EXIT_INVALID_ARGS); + }; + regex + }); + let mut functions_list = functions.list(); functions_list.sort(); for function in functions_list { @@ -716,6 +729,17 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> continue; } + // all specified categories must be present on the function (AND filter) + if !category.iter().all(|c| function.category.contains(c)) { + continue; + } + + if let Some(ref description_regex) = description_regex { + if !description_regex.is_match(&function.description) { + continue; + } + } + if write_table { table.add_row(vec![ function.category.iter().map(std::string::ToString::to_string).collect::>().join(", "), diff --git a/dsc/tests/dsc_function_list.tests.ps1 b/dsc/tests/dsc_function_list.tests.ps1 index f3b8b9ae7..43bf1d7c1 100644 --- a/dsc/tests/dsc_function_list.tests.ps1 +++ b/dsc/tests/dsc_function_list.tests.ps1 @@ -42,4 +42,75 @@ Describe 'Tests for function list subcommand' { } $foundWideLine | Should -BeTrue } + + Context 'Category filter' { + It 'Should filter by a single category' { + $out = dsc function list --category resource | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.category | Should -Contain 'resource' + } + $out.name | Should -Contain 'resourceId' + } + + It 'Should treat multiple categories as an AND filter' { + $out = dsc function list --category array --category object | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.category | Should -Contain 'array' + $function.category | Should -Contain 'object' + } + $out.name | Should -Contain 'tryGet' + } + + It 'Should match category case-insensitively' { + $out = dsc function list --category Array | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.category | Should -Contain 'array' + } + } + + It 'Should error on an invalid category' { + $out = dsc function list --category notARealCategory 2>&1 + $LASTEXITCODE | Should -Not -Be 0 + ($out | Out-String) | Should -Match 'Invalid function category' + } + } + + Context 'Description filter' { + It 'Should filter on description text' { + $out = dsc function list --description base64 | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.description | Should -Match 'base64' + } + $out.name | Should -Contain 'base64' + $out.name | Should -Contain 'base64ToString' + } + + It 'Should support wildcards in the description filter' { + $out = dsc function list --description 'encodes*base64' | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.name | Should -Contain 'base64' + } + + It 'Should return nothing when the description does not match' { + $out = dsc function list --description 'zzzNoSuchDescriptionzzz' | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -BeNullOrEmpty + } + } + + Context 'Combined filters' { + It 'Should combine name, category, and description filters' { + $out = dsc function list 'try*' --category array --category object --description 'retrieve' | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.name | Should -BeExactly 'tryGet' + } + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 968c2c674..0bd57fe9e 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -284,6 +284,7 @@ noObjectArgs = "Function '%{name}' does not accept object arguments, accepted ty noStringArgs = "Function '%{name}' does not accept string arguments, accepted types are: %{accepted_args_string}" lambdaNotFound = "Function '%{name}' could not find lambda with ID '%{id}'" lambdaTooManyParams = "Function '%{name}' requires lambda with 1 or 2 parameters (element and optional index)" +invalidCategory = "Invalid function category '%{category}', valid categories are: %{valid_categories}" [functions.add] description = "Adds two or more numbers together" diff --git a/lib/dsc-lib/src/functions/mod.rs b/lib/dsc-lib/src/functions/mod.rs index 344c421e2..37c8f8e5a 100644 --- a/lib/dsc-lib/src/functions/mod.rs +++ b/lib/dsc-lib/src/functions/mod.rs @@ -428,3 +428,46 @@ impl Display for FunctionCategory { } } } + +impl FunctionCategory { + /// All defined function categories. + pub const ALL: [FunctionCategory; 12] = [ + FunctionCategory::Array, + FunctionCategory::Cidr, + FunctionCategory::Comparison, + FunctionCategory::Date, + FunctionCategory::Deployment, + FunctionCategory::Lambda, + FunctionCategory::Logical, + FunctionCategory::Numeric, + FunctionCategory::Object, + FunctionCategory::Resource, + FunctionCategory::String, + FunctionCategory::System, + ]; +} + +impl std::str::FromStr for FunctionCategory { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "array" => Ok(FunctionCategory::Array), + "cidr" => Ok(FunctionCategory::Cidr), + "comparison" => Ok(FunctionCategory::Comparison), + "date" => Ok(FunctionCategory::Date), + "deployment" => Ok(FunctionCategory::Deployment), + "lambda" => Ok(FunctionCategory::Lambda), + "logical" => Ok(FunctionCategory::Logical), + "numeric" => Ok(FunctionCategory::Numeric), + "object" => Ok(FunctionCategory::Object), + "resource" => Ok(FunctionCategory::Resource), + "string" => Ok(FunctionCategory::String), + "system" => Ok(FunctionCategory::System), + _ => { + let valid = Self::ALL.iter().map(std::string::ToString::to_string).collect::>().join(", "); + Err(t!("functions.invalidCategory", category = s, valid_categories = valid).to_string()) + }, + } + } +} From b6e2d6d82682874dc83cea5bb30ac5162be1f619 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:00:44 +0200 Subject: [PATCH 2/5] Take suggesting Copilot --- lib/dsc-lib/src/util.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/dsc-lib/src/util.rs b/lib/dsc-lib/src/util.rs index ba68e031c..56807111e 100644 --- a/lib/dsc-lib/src/util.rs +++ b/lib/dsc-lib/src/util.rs @@ -72,8 +72,15 @@ pub fn parse_input_to_json(value: &str) -> Result { /// A string that holds the regex pattern. #[must_use] pub fn convert_wildcard_to_regex(wildcard: &str) -> String { - let mut regex = wildcard.to_string().replace('.', "\\.").replace('?', ".").replace('*', ".*?"); - regex.insert(0, '^'); + let mut regex = String::with_capacity(wildcard.len() + 2); + regex.push('^'); + for c in wildcard.chars() { + match c { + '*' => regex.push_str(".*?"), + '?' => regex.push('.'), + _ => regex.push_str(®ex::escape(&c.to_string())), + } + } regex.push('$'); regex } @@ -95,6 +102,10 @@ mod tests { let wildcard = "r*"; let regex = convert_wildcard_to_regex(wildcard); assert_eq!(regex, "^r.*?$"); + + let wildcard = "a+b(c)[d]^e$f|g\\h"; + let regex = convert_wildcard_to_regex(wildcard); + assert_eq!(regex, r"^a\+b\(c\)\[d\]\^e\$f\|g\\h$"); } } From dc4d53f2fb2a68fb2b3a37289a150c6f94805280 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:25:03 +0200 Subject: [PATCH 3/5] feat: enhance function listing with category and description filters --- dsc/locales/en-us.toml | 3 ++ dsc/src/args.rs | 5 ++ dsc/src/subcommand.rs | 32 ++++++++++-- dsc/tests/dsc_function_list.tests.ps1 | 71 +++++++++++++++++++++++++++ lib/dsc-lib/locales/en-us.toml | 1 + lib/dsc-lib/src/functions/mod.rs | 43 ++++++++++++++++ 6 files changed, 151 insertions(+), 4 deletions(-) diff --git a/dsc/locales/en-us.toml b/dsc/locales/en-us.toml index 49e9b50dd..ca6ca852d 100644 --- a/dsc/locales/en-us.toml +++ b/dsc/locales/en-us.toml @@ -34,6 +34,8 @@ getAll = "Get all instances of the resource" resource = "The name of the resource to invoke" functionAbout = "Operations on DSC functions" listFunctionAbout = "List or find functions" +functionCategory = "Category to filter functions on; specify multiple times to require all categories" +functionDescription = "Description to search for in the function description, accepts wildcards" version = "The version of the resource to invoke in semver format" serverAbout = "Use DSC as a server over JSON-RPC (useful as MCP server)" bicepAbout = "Use DSC as a Bicep server over gRPC" @@ -152,6 +154,7 @@ tableHeader_functionName = "Function" tableHeader_functionCategory = "Category" tableHeader_syntax = "Syntax" invalidFunctionFilter = "Invalid function filter" +invalidFunctionDescriptionFilter = "Invalid function description filter" maxInt = "maxInt" invalidManifest = "Error in manifest for" jsonArrayNotSupported = "JSON array output format is only supported for `--all'" diff --git a/dsc/src/args.rs b/dsc/src/args.rs index 4d4622287..3108c6317 100644 --- a/dsc/src/args.rs +++ b/dsc/src/args.rs @@ -4,6 +4,7 @@ use clap::{Parser, Subcommand, ValueEnum}; use clap_complete::Shell; use dsc_lib::dscresources::command_resource::TraceLevel; +use dsc_lib::functions::FunctionCategory; use dsc_lib::progress::ProgressFormat; use dsc_lib::types::{FullyQualifiedTypeName, ResourceVersionReq, TypeNameFilter}; use rust_i18n::t; @@ -193,6 +194,10 @@ pub enum FunctionSubCommand { List { /// Optional function name to filter the list function_name: Option, + #[clap(short = 'c', long = "category", help = t!("args.functionCategory").to_string())] + category: Vec, + #[clap(short, long, help = t!("args.functionDescription").to_string())] + description: Option, #[clap(short = 'o', long, help = t!("args.outputFormat").to_string())] output_format: Option, }, diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index 1c86d9ae5..f0c7fb3b9 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -28,7 +28,7 @@ use dsc_lib::{ }, dscresources::dscresource::{Capability, ImplementedAs, validate_json, validate_properties}, extensions::dscextension::Capability as ExtensionCapability, - functions::FunctionDispatcher, + functions::{FunctionCategory, FunctionDispatcher}, progress::ProgressFormat, util::convert_wildcard_to_regex, }; @@ -541,8 +541,8 @@ pub fn extension(subcommand: &ExtensionSubCommand, progress_format: ProgressForm pub fn function(subcommand: &FunctionSubCommand) { let functions = FunctionDispatcher::new(); match subcommand { - FunctionSubCommand::List { function_name, output_format } => { - list_functions(&functions, function_name.as_ref(), output_format.as_ref()); + FunctionSubCommand::List { function_name, category, description, output_format } => { + list_functions(&functions, function_name.as_ref(), category, description.as_ref(), output_format.as_ref()); }, } } @@ -689,7 +689,7 @@ fn list_extensions(dsc: &mut DscManager, extension_name: &TypeNameFilter, format } } -fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, output_format: Option<&ListOutputFormat>) { +fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String>, category: &[FunctionCategory], description: Option<&String>, output_format: Option<&ListOutputFormat>) { let write_table = should_write_table(output_format); let mut table = Table::new(&[ t!("subcommand.tableHeader_functionCategory").to_string().as_ref(), @@ -709,6 +709,19 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> exit(EXIT_INVALID_ARGS); }; + let description_regex = description.map(|description| { + let regex_str = convert_wildcard_to_regex(description); + // strip the `^` and `$` anchors so the filter searches within the description text + let regex_str = ®ex_str[1..regex_str.len() - 1]; + let mut regex_builder = RegexBuilder::new(regex_str); + regex_builder.case_insensitive(true); + let Ok(regex) = regex_builder.build() else { + error!("{}: {}", t!("subcommand.invalidFunctionDescriptionFilter"), regex_str); + exit(EXIT_INVALID_ARGS); + }; + regex + }); + let mut functions_list = functions.list(); functions_list.sort(); for function in functions_list { @@ -716,6 +729,17 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> continue; } + // all specified categories must be present on the function (AND filter) + if !category.iter().all(|c| function.category.contains(c)) { + continue; + } + + if let Some(ref description_regex) = description_regex { + if !description_regex.is_match(&function.description) { + continue; + } + } + if write_table { table.add_row(vec![ function.category.iter().map(std::string::ToString::to_string).collect::>().join(", "), diff --git a/dsc/tests/dsc_function_list.tests.ps1 b/dsc/tests/dsc_function_list.tests.ps1 index f3b8b9ae7..43bf1d7c1 100644 --- a/dsc/tests/dsc_function_list.tests.ps1 +++ b/dsc/tests/dsc_function_list.tests.ps1 @@ -42,4 +42,75 @@ Describe 'Tests for function list subcommand' { } $foundWideLine | Should -BeTrue } + + Context 'Category filter' { + It 'Should filter by a single category' { + $out = dsc function list --category resource | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.category | Should -Contain 'resource' + } + $out.name | Should -Contain 'resourceId' + } + + It 'Should treat multiple categories as an AND filter' { + $out = dsc function list --category array --category object | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.category | Should -Contain 'array' + $function.category | Should -Contain 'object' + } + $out.name | Should -Contain 'tryGet' + } + + It 'Should match category case-insensitively' { + $out = dsc function list --category Array | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.category | Should -Contain 'array' + } + } + + It 'Should error on an invalid category' { + $out = dsc function list --category notARealCategory 2>&1 + $LASTEXITCODE | Should -Not -Be 0 + ($out | Out-String) | Should -Match 'Invalid function category' + } + } + + Context 'Description filter' { + It 'Should filter on description text' { + $out = dsc function list --description base64 | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -Not -BeNullOrEmpty + foreach ($function in $out) { + $function.description | Should -Match 'base64' + } + $out.name | Should -Contain 'base64' + $out.name | Should -Contain 'base64ToString' + } + + It 'Should support wildcards in the description filter' { + $out = dsc function list --description 'encodes*base64' | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.name | Should -Contain 'base64' + } + + It 'Should return nothing when the description does not match' { + $out = dsc function list --description 'zzzNoSuchDescriptionzzz' | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out | Should -BeNullOrEmpty + } + } + + Context 'Combined filters' { + It 'Should combine name, category, and description filters' { + $out = dsc function list 'try*' --category array --category object --description 'retrieve' | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 + $out.name | Should -BeExactly 'tryGet' + } + } } diff --git a/lib/dsc-lib/locales/en-us.toml b/lib/dsc-lib/locales/en-us.toml index 968c2c674..0bd57fe9e 100644 --- a/lib/dsc-lib/locales/en-us.toml +++ b/lib/dsc-lib/locales/en-us.toml @@ -284,6 +284,7 @@ noObjectArgs = "Function '%{name}' does not accept object arguments, accepted ty noStringArgs = "Function '%{name}' does not accept string arguments, accepted types are: %{accepted_args_string}" lambdaNotFound = "Function '%{name}' could not find lambda with ID '%{id}'" lambdaTooManyParams = "Function '%{name}' requires lambda with 1 or 2 parameters (element and optional index)" +invalidCategory = "Invalid function category '%{category}', valid categories are: %{valid_categories}" [functions.add] description = "Adds two or more numbers together" diff --git a/lib/dsc-lib/src/functions/mod.rs b/lib/dsc-lib/src/functions/mod.rs index 344c421e2..37c8f8e5a 100644 --- a/lib/dsc-lib/src/functions/mod.rs +++ b/lib/dsc-lib/src/functions/mod.rs @@ -428,3 +428,46 @@ impl Display for FunctionCategory { } } } + +impl FunctionCategory { + /// All defined function categories. + pub const ALL: [FunctionCategory; 12] = [ + FunctionCategory::Array, + FunctionCategory::Cidr, + FunctionCategory::Comparison, + FunctionCategory::Date, + FunctionCategory::Deployment, + FunctionCategory::Lambda, + FunctionCategory::Logical, + FunctionCategory::Numeric, + FunctionCategory::Object, + FunctionCategory::Resource, + FunctionCategory::String, + FunctionCategory::System, + ]; +} + +impl std::str::FromStr for FunctionCategory { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "array" => Ok(FunctionCategory::Array), + "cidr" => Ok(FunctionCategory::Cidr), + "comparison" => Ok(FunctionCategory::Comparison), + "date" => Ok(FunctionCategory::Date), + "deployment" => Ok(FunctionCategory::Deployment), + "lambda" => Ok(FunctionCategory::Lambda), + "logical" => Ok(FunctionCategory::Logical), + "numeric" => Ok(FunctionCategory::Numeric), + "object" => Ok(FunctionCategory::Object), + "resource" => Ok(FunctionCategory::Resource), + "string" => Ok(FunctionCategory::String), + "system" => Ok(FunctionCategory::System), + _ => { + let valid = Self::ALL.iter().map(std::string::ToString::to_string).collect::>().join(", "); + Err(t!("functions.invalidCategory", category = s, valid_categories = valid).to_string()) + }, + } + } +} From 337ad4d8620a59fcf6737ec0884d5c6247de1d36 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:00:44 +0200 Subject: [PATCH 4/5] Take suggesting Copilot --- lib/dsc-lib/src/util.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/dsc-lib/src/util.rs b/lib/dsc-lib/src/util.rs index ba68e031c..56807111e 100644 --- a/lib/dsc-lib/src/util.rs +++ b/lib/dsc-lib/src/util.rs @@ -72,8 +72,15 @@ pub fn parse_input_to_json(value: &str) -> Result { /// A string that holds the regex pattern. #[must_use] pub fn convert_wildcard_to_regex(wildcard: &str) -> String { - let mut regex = wildcard.to_string().replace('.', "\\.").replace('?', ".").replace('*', ".*?"); - regex.insert(0, '^'); + let mut regex = String::with_capacity(wildcard.len() + 2); + regex.push('^'); + for c in wildcard.chars() { + match c { + '*' => regex.push_str(".*?"), + '?' => regex.push('.'), + _ => regex.push_str(®ex::escape(&c.to_string())), + } + } regex.push('$'); regex } @@ -95,6 +102,10 @@ mod tests { let wildcard = "r*"; let regex = convert_wildcard_to_regex(wildcard); assert_eq!(regex, "^r.*?$"); + + let wildcard = "a+b(c)[d]^e$f|g\\h"; + let regex = convert_wildcard_to_regex(wildcard); + assert_eq!(regex, r"^a\+b\(c\)\[d\]\^e\$f\|g\\h$"); } } From 0e6c8e760ab3d160aaa3fa8e91fa1a71a2383e66 Mon Sep 17 00:00:00 2001 From: "G.Reijn" <26114636+Gijsreyn@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:11:15 +0200 Subject: [PATCH 5/5] simplify regex matching logic in list_functions --- dsc/src/subcommand.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/dsc/src/subcommand.rs b/dsc/src/subcommand.rs index f0c7fb3b9..180346e9a 100644 --- a/dsc/src/subcommand.rs +++ b/dsc/src/subcommand.rs @@ -734,10 +734,9 @@ fn list_functions(functions: &FunctionDispatcher, function_name: Option<&String> continue; } - if let Some(ref description_regex) = description_regex { - if !description_regex.is_match(&function.description) { - continue; - } + if let Some(ref description_regex) = description_regex + && !description_regex.is_match(&function.description) { + continue; } if write_table {