@@ -74,7 +74,7 @@ public override int Execute(CommandContext context, Settings settings, Cancellat
7474 var format = settings . GetOutputFormat ( ) ;
7575 if ( format == OutputFormat . Table )
7676 {
77- AnsiConsole . MarkupLine ( $ "[blue]Scanning:[/] { resourcePath } ") ;
77+ AnsiConsole . MarkupLine ( $ "[blue]Scanning:[/] { Markup . Escape ( resourcePath ) } ") ;
7878 AnsiConsole . WriteLine ( ) ;
7979 }
8080
@@ -116,6 +116,16 @@ public override int Execute(CommandContext context, Settings settings, Cancellat
116116
117117 // Find matching keys
118118 List < string > matchedKeys ;
119+ bool usedWildcards = false ;
120+ string originalPattern = settings . Key ;
121+
122+ // Auto-detect and convert wildcard patterns to regex
123+ if ( ! settings . UseRegex && IsWildcardPattern ( settings . Key ) )
124+ {
125+ settings . Key = ConvertWildcardToRegex ( settings . Key ) ;
126+ settings . UseRegex = true ;
127+ usedWildcards = true ;
128+ }
119129
120130 if ( settings . UseRegex )
121131 {
@@ -149,7 +159,7 @@ public override int Execute(CommandContext context, Settings settings, Cancellat
149159 var existingEntry = defaultFile . Entries . FirstOrDefault ( e => e . Key == settings . Key ) ;
150160 if ( existingEntry == null )
151161 {
152- AnsiConsole . MarkupLine ( $ "[red]✗ Key '{ settings . Key } ' not found![/]") ;
162+ AnsiConsole . MarkupLine ( $ "[red]✗ Key '{ Markup . Escape ( settings . Key ) } ' not found![/]") ;
153163 return 1 ;
154164 }
155165 matchedKeys = new List < string > { settings . Key } ;
@@ -178,23 +188,25 @@ public override int Execute(CommandContext context, Settings settings, Cancellat
178188 // Check if we have any matches
179189 if ( matchedKeys . Count == 0 )
180190 {
181- var patternType = settings . UseRegex ? "pattern" : "key" ;
182- AnsiConsole . MarkupLine ( $ "[red]✗ No keys match { patternType } '{ settings . Key } '[/]") ;
191+ var patternType = usedWildcards ? "wildcard" : ( settings . UseRegex ? "pattern" : "key" ) ;
192+ var displayPattern = usedWildcards ? originalPattern : settings . Key ;
193+ // Escape pattern to prevent Spectre.Console markup interpretation
194+ AnsiConsole . MarkupLine ( $ "[red]✗ No keys match { patternType } '{ Markup . Escape ( displayPattern ) } '[/]") ;
183195 return 1 ;
184196 }
185197
186198 // Display based on format
187199 switch ( format )
188200 {
189201 case OutputFormat . Json :
190- DisplayJson ( matchedKeys , resourceFiles , settings . ShowComments , settings ) ;
202+ DisplayJson ( matchedKeys , resourceFiles , settings . ShowComments , settings , usedWildcards , originalPattern ) ;
191203 break ;
192204 case OutputFormat . Simple :
193- DisplaySimple ( matchedKeys , resourceFiles , settings . ShowComments , settings ) ;
205+ DisplaySimple ( matchedKeys , resourceFiles , settings . ShowComments , settings , usedWildcards , originalPattern ) ;
194206 break ;
195207 case OutputFormat . Table :
196208 default :
197- DisplayTable ( matchedKeys , resourceFiles , settings . ShowComments , settings ) ;
209+ DisplayTable ( matchedKeys , resourceFiles , settings . ShowComments , settings , usedWildcards , originalPattern ) ;
198210 break ;
199211 }
200212
@@ -221,7 +233,7 @@ private void DisplayConfigNotice(Settings settings)
221233 }
222234 }
223235
224- private void DisplayTable ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings )
236+ private void DisplayTable ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings , bool usedWildcards , string originalPattern )
225237 {
226238 DisplayConfigNotice ( settings ) ;
227239
@@ -233,13 +245,13 @@ private void DisplayTable(List<string> keys, List<Core.Models.ResourceFile> reso
233245 else
234246 {
235247 // Multiple keys - new grouped format
236- DisplayMultipleKeysTable ( keys , resourceFiles , showComments , settings ) ;
248+ DisplayMultipleKeysTable ( keys , resourceFiles , showComments , settings , usedWildcards , originalPattern ) ;
237249 }
238250 }
239251
240252 private void DisplaySingleKeyTable ( string key , List < Core . Models . ResourceFile > resourceFiles , bool showComments )
241253 {
242- AnsiConsole . MarkupLine ( $ "[yellow]Key:[/] [bold]{ key } [/]") ;
254+ AnsiConsole . MarkupLine ( $ "[yellow]Key:[/] [bold]{ Markup . Escape ( key ) } [/]") ;
243255 AnsiConsole . WriteLine ( ) ;
244256
245257 var table = new Table ( ) ;
@@ -303,9 +315,24 @@ private void DisplaySingleKeyTable(string key, List<Core.Models.ResourceFile> re
303315 AnsiConsole . MarkupLine ( $ "[dim]Present in { present } /{ total } language(s), { empty } empty value(s)[/]") ;
304316 }
305317
306- private void DisplayMultipleKeysTable ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings )
318+ private void DisplayMultipleKeysTable ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings , bool usedWildcards , string originalPattern )
307319 {
308- var patternDisplay = settings . UseRegex ? $ "Pattern: { settings . Key } " : $ "Keys: { keys . Count } ";
320+ string patternDisplay ;
321+ if ( usedWildcards )
322+ {
323+ // Escape pattern to prevent Spectre.Console markup interpretation
324+ patternDisplay = $ "Pattern: { Markup . Escape ( originalPattern ) } [dim](wildcard)[/]";
325+ }
326+ else if ( settings . UseRegex )
327+ {
328+ // Escape pattern to prevent Spectre.Console markup interpretation
329+ patternDisplay = $ "Pattern: { Markup . Escape ( originalPattern ) } [dim](regex)[/]";
330+ }
331+ else
332+ {
333+ patternDisplay = $ "Keys: { keys . Count } ";
334+ }
335+
309336 AnsiConsole . MarkupLine ( $ "[yellow]{ patternDisplay } [/]") ;
310337 AnsiConsole . MarkupLine ( $ "[dim]Matched { keys . Count } key(s)[/]") ;
311338 AnsiConsole . WriteLine ( ) ;
@@ -353,7 +380,7 @@ private void DisplayMultipleKeysTable(List<string> keys, List<Core.Models.Resour
353380 AnsiConsole . MarkupLine ( $ "[dim]Showing { keys . Count } key(s) across { resourceFiles . Count } language(s)[/]") ;
354381 }
355382
356- private void DisplayJson ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings )
383+ private void DisplayJson ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings , bool usedWildcards , string originalPattern )
357384 {
358385 if ( keys . Count == 1 )
359386 {
@@ -363,7 +390,7 @@ private void DisplayJson(List<string> keys, List<Core.Models.ResourceFile> resou
363390 else
364391 {
365392 // Multiple keys - array format
366- DisplayMultipleKeysJson ( keys , resourceFiles , showComments , settings ) ;
393+ DisplayMultipleKeysJson ( keys , resourceFiles , showComments , settings , usedWildcards , originalPattern ) ;
367394 }
368395 }
369396
@@ -399,7 +426,7 @@ private void DisplaySingleKeyJson(string key, List<Core.Models.ResourceFile> res
399426 Console . WriteLine ( OutputFormatter . FormatJson ( output ) ) ;
400427 }
401428
402- private void DisplayMultipleKeysJson ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings )
429+ private void DisplayMultipleKeysJson ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings , bool usedWildcards , string originalPattern )
403430 {
404431 var keyObjects = new List < object > ( ) ;
405432
@@ -434,15 +461,16 @@ private void DisplayMultipleKeysJson(List<string> keys, List<Core.Models.Resourc
434461
435462 var output = new
436463 {
437- pattern = settings . UseRegex ? settings . Key : ( string ? ) null ,
464+ pattern = settings . UseRegex || usedWildcards ? originalPattern : ( string ? ) null ,
465+ patternType = usedWildcards ? "wildcard" : ( settings . UseRegex ? "regex" : ( string ? ) null ) ,
438466 matchCount = keys . Count ,
439467 keys = keyObjects
440468 } ;
441469
442470 Console . WriteLine ( OutputFormatter . FormatJson ( output ) ) ;
443471 }
444472
445- private void DisplaySimple ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings )
473+ private void DisplaySimple ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings , bool usedWildcards , string originalPattern )
446474 {
447475 if ( keys . Count == 1 )
448476 {
@@ -452,7 +480,7 @@ private void DisplaySimple(List<string> keys, List<Core.Models.ResourceFile> res
452480 else
453481 {
454482 // Multiple keys
455- DisplayMultipleKeysSimple ( keys , resourceFiles , showComments , settings ) ;
483+ DisplayMultipleKeysSimple ( keys , resourceFiles , showComments , settings , usedWildcards , originalPattern ) ;
456484 }
457485 }
458486
@@ -476,9 +504,22 @@ private void DisplaySingleKeySimple(string key, List<Core.Models.ResourceFile> r
476504 }
477505 }
478506
479- private void DisplayMultipleKeysSimple ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings )
507+ private void DisplayMultipleKeysSimple ( List < string > keys , List < Core . Models . ResourceFile > resourceFiles , bool showComments , Settings settings , bool usedWildcards , string originalPattern )
480508 {
481- var patternDisplay = settings . UseRegex ? $ "Pattern: { settings . Key } " : $ "Keys: { keys . Count } ";
509+ string patternDisplay ;
510+ if ( usedWildcards )
511+ {
512+ patternDisplay = $ "Pattern: { originalPattern } (wildcard)";
513+ }
514+ else if ( settings . UseRegex )
515+ {
516+ patternDisplay = $ "Pattern: { originalPattern } (regex)";
517+ }
518+ else
519+ {
520+ patternDisplay = $ "Keys: { keys . Count } ";
521+ }
522+
482523 Console . WriteLine ( patternDisplay ) ;
483524 Console . WriteLine ( $ "Matched { keys . Count } key(s)") ;
484525 Console . WriteLine ( ) ;
@@ -511,4 +552,84 @@ private void DisplayMultipleKeysSimple(List<string> keys, List<Core.Models.Resou
511552 }
512553 }
513554 }
555+
556+ /// <summary>
557+ /// Detects if a pattern contains wildcard characters (* or ?) that should be converted to regex.
558+ /// Handles backslash escaping for literal wildcard characters.
559+ /// </summary>
560+ internal static bool IsWildcardPattern ( string pattern )
561+ {
562+ // Simply check if pattern contains unescaped wildcards
563+ // The --regex flag takes precedence, so we don't need "smart" detection
564+ for ( int i = 0 ; i < pattern . Length ; i ++ )
565+ {
566+ char c = pattern [ i ] ;
567+
568+ // Check if this is an escaped character
569+ if ( c == '\\ ' && i + 1 < pattern . Length )
570+ {
571+ i ++ ; // Skip next character
572+ continue ;
573+ }
574+
575+ // Check for unescaped wildcards
576+ if ( c == '*' || c == '?' )
577+ {
578+ return true ;
579+ }
580+ }
581+
582+ return false ;
583+ }
584+
585+ /// <summary>
586+ /// Converts a wildcard pattern to a regex pattern.
587+ /// Supports:
588+ /// - * for zero or more characters
589+ /// - ? for exactly one character
590+ /// - \* and \? for literal asterisk and question mark
591+ /// </summary>
592+ internal static string ConvertWildcardToRegex ( string wildcardPattern )
593+ {
594+ var result = new System . Text . StringBuilder ( ) ;
595+
596+ for ( int i = 0 ; i < wildcardPattern . Length ; i ++ )
597+ {
598+ char c = wildcardPattern [ i ] ;
599+
600+ if ( c == '\\ ' && i + 1 < wildcardPattern . Length )
601+ {
602+ char next = wildcardPattern [ i + 1 ] ;
603+ if ( next == '*' || next == '?' )
604+ {
605+ // Escaped wildcard - treat as literal
606+ result . Append ( '\\ ' ) . Append ( next ) ;
607+ i ++ ; // Skip next character
608+ }
609+ else
610+ {
611+ // Other escaped character - escape for regex
612+ result . Append ( Regex . Escape ( c . ToString ( ) ) ) ;
613+ }
614+ }
615+ else if ( c == '*' )
616+ {
617+ // Wildcard: match any characters
618+ result . Append ( ".*" ) ;
619+ }
620+ else if ( c == '?' )
621+ {
622+ // Wildcard: match single character
623+ result . Append ( '.' ) ;
624+ }
625+ else
626+ {
627+ // Regular character - escape for regex
628+ result . Append ( Regex . Escape ( c . ToString ( ) ) ) ;
629+ }
630+ }
631+
632+ // Anchor to match entire string
633+ return "^" + result . ToString ( ) + "$" ;
634+ }
514635}
0 commit comments