Filter by wildcard

I have a list of path I want to filter out:

root/group1/subgroup1/project1
root/group2/subgroup1/project2
root/group2/subgroup2/project3
root/group2/subgroup2/project4

I want to keep only paths matching a specific pattern and remove all paths matching another specific pattern. To achieve the actual matching we will use FileSystemName.MatchesSimpleExpression:

Verifies if the given expression matches the given name. Supports the following wildcards: '*' and '?'. The backslash character '' escapes.

public static bool MatchesSimpleExpression (`ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true);

Small notable caveat: MatchesSimpleExpression plays with ReadOnlySpan<char> instead of string. No problem, let's add a small converter:

public static ReadOnlySpan<char> StringToSpan(string s)
=> new ReadOnlySpan<char>(s.ToCharArray());

Our paths are a plain IEnumerable<string>, so our main function will be:

public static IEnumerable<string> FilterByPattern(string includePattern, string excludePattern, IEnumerable<string> paths)
=> paths.Where(patternPredicate);

Finally let's write the PatternPredicate:

public static string PatternPredicate(string includePattern, string excludePattern, string path) =>
(string.IsNullOrEmpty(includePattern) || MatchWildcard(includePattern, path)) &&
(string.IsNullOrEmpty(excludePattern) || !MatchWildcard(excludePattern, path));

So the complete snippet is now:

public static ReadOnlySpan<char> StringToSpan(string s)
=> new ReadOnlySpan<char>(s.ToCharArray());

public static bool MatchWildcard(string pattern, string text)
=> FileSystemName.MatchesSimpleExpression(StringToSpan(pattern), StringToSpan(text));

public static string PatternPredicate(string includePattern, string excludePattern, string path) =>
(string.IsNullOrEmpty(includePattern) || MatchWildcard(includePattern, path)) &&
(string.IsNullOrEmpty(excludePattern) || !MatchWildcard(excludePattern, path));

public static IEnumerable<string> FilterByPattern(string includePattern, string excludePattern, IEnumerable<string> paths)
=> paths.Where(patternPredicate);

and here is the F# version:

let stringToSpan (s: string) =
ReadOnlySpan<char>(s.ToCharArray())

let matchWildcard pattern text =
FileSystemName.MatchesSimpleExpression(stringToSpan pattern, stringToSpan text)

let patternPredicate (includePattern: string Option) (excludePattern: string Option) path =
(includePattern.IsNone || matchWildcard includePattern.Value path) &&
(excludePattern.IsNone || not (matchWildcard excludePattern.Value path))

let filterByPattern (includePattern: string Option) (excludePattern: string Option) (paths: string seq) :string seq =
Seq.filter (fun path -> patternPredicate includePattern excludePattern path) paths

FilterByPattern was not written as is when I first wrote it. I posted a quick walkthrough: Optimize boolean logic;

The code comes from my open source project to clone GitLab organisation in one command line: Kamino! Check the Pull Request.



Tags: dotnet, csharp, fsharp

← Back home