diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt index e785eeb..59d6d04 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net47.verified.txt @@ -4,5 +4,6 @@ namespace Pathy public static class ChainablePathExtensions { public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, params string[] globPatterns) { } } } \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt index e785eeb..59d6d04 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.net8.0.verified.txt @@ -4,5 +4,6 @@ namespace Pathy public static class ChainablePathExtensions { public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, params string[] globPatterns) { } } } \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt index e785eeb..59d6d04 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.0.verified.txt @@ -4,5 +4,6 @@ namespace Pathy public static class ChainablePathExtensions { public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, params string[] globPatterns) { } } } \ No newline at end of file diff --git a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt index e785eeb..59d6d04 100644 --- a/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt +++ b/Pathy.ApiVerificationTests/ApprovedApi/pathy.globbing.netstandard2.1.verified.txt @@ -4,5 +4,6 @@ namespace Pathy public static class ChainablePathExtensions { public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, string globPattern) { } + public static Pathy.ChainablePath[] GlobFiles(this Pathy.ChainablePath path, params string[] globPatterns) { } } } \ No newline at end of file diff --git a/Pathy.Globbing/PathyGlobbing.cs b/Pathy.Globbing/PathyGlobbing.cs index 74a13f5..e3ca735 100644 --- a/Pathy.Globbing/PathyGlobbing.cs +++ b/Pathy.Globbing/PathyGlobbing.cs @@ -31,8 +31,39 @@ internal static class ChainablePathExtensions /// The glob pattern used to match file paths, e.g. **/*.md or dir/**/* public static ChainablePath[] GlobFiles(this ChainablePath path, string globPattern) { + return GlobFiles(path, new[] { globPattern }); + } + + /// + /// Matches files in the specified directory or subdirectories according to the provided glob patterns + /// and returns them as an array of objects. + /// + /// + /// See also + /// + /// The base directory path to start the glob search from. + /// One or more glob patterns used to match file paths, e.g. **/*.md or dir/**/* + /// Thrown if no glob patterns are provided or if any pattern is null or empty. + public static ChainablePath[] GlobFiles(this ChainablePath path, params string[] globPatterns) + { + if (globPatterns == null || globPatterns.Length == 0) + { + throw new ArgumentException("At least one glob pattern must be provided", nameof(globPatterns)); + } + + foreach (string pattern in globPatterns) + { + if (string.IsNullOrWhiteSpace(pattern)) + { + throw new ArgumentException("Glob patterns cannot be null or empty", nameof(globPatterns)); + } + } + Matcher matcher = new(StringComparison.OrdinalIgnoreCase); - matcher.AddInclude(globPattern); + foreach (string pattern in globPatterns) + { + matcher.AddInclude(pattern); + } return matcher .Execute(new DirectoryInfoWrapper(path.ToDirectoryInfo())) diff --git a/Pathy.Specs/ChainablePathSpecs.cs b/Pathy.Specs/ChainablePathSpecs.cs index 94fa5f3..babbf68 100644 --- a/Pathy.Specs/ChainablePathSpecs.cs +++ b/Pathy.Specs/ChainablePathSpecs.cs @@ -465,6 +465,98 @@ public void Can_find_files_using_globbing_patterns() ], options => options.ComparingRecordsByValue()); } + [Fact] + public void Can_find_files_using_multiple_globbing_patterns() + { + // Arrange + var temp = ChainablePath.Temp / "dir1" / "dir2" / "dir3" / "file.txt"; + + temp.CreateDirectoryRecursively(); + File.WriteAllText(temp / "file.txt", "Hello World!"); + File.WriteAllText(temp / "file2.txt", "Hello World!"); + File.WriteAllText(temp / "file3.doc", "Hello World!"); + File.WriteAllText(temp / "file4.md", "# Markdown"); + + // Act + var files = (ChainablePath.Temp / "dir1").GlobFiles("**/*.txt", "**/*.doc"); + + // Assert + files.Should().BeEquivalentTo([ + temp / "file.txt", + temp / "file2.txt", + temp / "file3.doc" + ], options => options.ComparingRecordsByValue()); + } + + [Fact] + public void Can_find_files_using_multiple_globbing_patterns_with_different_depths() + { + // Arrange + var baseDir = testFolder / "MultiPatternTest"; + var deepDir = baseDir / "level1" / "level2"; + deepDir.CreateDirectoryRecursively(); + + File.WriteAllText(baseDir / "root.txt", "Root"); + File.WriteAllText(baseDir / "root.md", "# Root"); + File.WriteAllText(deepDir / "deep.txt", "Deep"); + File.WriteAllText(deepDir / "deep.json", "{}"); + + // Act + var files = baseDir.GlobFiles("**/*.txt", "**/*.json"); + + // Assert + files.Should().BeEquivalentTo([ + baseDir / "root.txt", + deepDir / "deep.txt", + deepDir / "deep.json" + ], options => options.ComparingRecordsByValue()); + } + + [Fact] + public void GlobFiles_with_multiple_patterns_throws_when_no_patterns_provided() + { + // Arrange + var temp = ChainablePath.Temp / "dir1"; + temp.CreateDirectoryRecursively(); + + // Act & Assert + var act = () => temp.GlobFiles(new string[0]); + + act.Should().Throw() + .WithMessage("*At least one glob pattern must be provided*") + .WithParameterName("globPatterns"); + } + + [Fact] + public void GlobFiles_with_multiple_patterns_throws_when_pattern_is_null() + { + // Arrange + var temp = ChainablePath.Temp / "dir1"; + temp.CreateDirectoryRecursively(); + + // Act & Assert + var act = () => temp.GlobFiles("**/*.txt", null, "**/*.doc"); + + act.Should().Throw() + .WithMessage("*Glob patterns cannot be null or empty*") + .WithParameterName("globPatterns"); + } + + [Fact] + public void GlobFiles_with_multiple_patterns_throws_when_pattern_is_empty() + { + // Arrange + var temp = ChainablePath.Temp / "dir1"; + temp.CreateDirectoryRecursively(); + + // Act & Assert + var act = () => temp.GlobFiles("**/*.txt", "", "**/*.doc"); + + act.Should().Throw() + .WithMessage("*Glob patterns cannot be null or empty*") + .WithParameterName("globPatterns"); + } + [Fact] public void Can_convert_to_directory_info() { diff --git a/README.md b/README.md index 8b086c0..c4871d9 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,11 @@ If you add the `Pathy.Globbing` NuGet source-only package as well, you'll get ac ```csharp +// Match files with a single pattern ChainablePath[] files = (ChainablePath.Current / "Artifacts").GlobFiles("**/*.json"); + +// Match files with multiple patterns +ChainablePath[] files = (ChainablePath.Current / "Artifacts").GlobFiles("**/*.txt", "**/*.md", "**/*.json"); ``` ### File system operations