Skip to content

Commit

Permalink
Added searchPattern and searchOption to GetDirectories and GetFiles (#8)
Browse files Browse the repository at this point in the history
* Added searchPattern and searchOption to GetDirectories and GetFiles

* Adding some IFileSystem extensions

---------

Co-authored-by: Brad White <bradselw@pm.me>
  • Loading branch information
bradselw and Brad White committed May 14, 2023
1 parent aaf7c40 commit 049b34b
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 17 deletions.
8 changes: 4 additions & 4 deletions src/FileSystem/DefaultFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ public bool FileExists(string path)
return File.Exists(path);
}

public string[] GetDirectories(string path, bool recursive)
public string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
return Directory.GetDirectories(path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
return Directory.GetDirectories(path, searchPattern, searchOption);
}

public string[] GetFiles(string path, bool recursive)
public string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
{
return Directory.GetFiles(path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
return Directory.GetFiles(path, searchPattern, searchOption);
}

public FileStream OpenFile(string path, FileMode mode, FileAccess access, FileShare share)
Expand Down
17 changes: 17 additions & 0 deletions src/FileSystem/Extensions/IFileSystemExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.IO;

namespace DevOptimal.SystemUtilities.FileSystem.Extensions
{
public static class IFileSystemExtensions
{
public static string[] GetDirectories(this IFileSystem fileSystem, string path, bool recursive)
{
return fileSystem.GetDirectories(path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}

public static string[] GetFiles(this IFileSystem fileSystem, string path, bool recursive)
{
return fileSystem.GetFiles(path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
}
}
}
4 changes: 2 additions & 2 deletions src/FileSystem/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ public interface IFileSystem

bool FileExists(string path);

string[] GetDirectories(string path, bool recursive);
string[] GetDirectories(string path, string searchPattern, SearchOption searchOption);

string[] GetFiles(string path, bool recursive);
string[] GetFiles(string path, string searchPattern, SearchOption searchOption);

FileStream OpenFile(string path, FileMode mode, FileAccess access, FileShare share);
}
Expand Down
35 changes: 28 additions & 7 deletions src/FileSystem/MockFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace DevOptimal.SystemUtilities.FileSystem
{
Expand All @@ -12,6 +13,12 @@ public class MockFileSystem : IFileSystem

internal readonly IDictionary<string, byte[]> data;

private readonly static Regex[] invalidSearchPatternRegexes = Path.GetInvalidPathChars()
.Select(c => Regex.Escape(c.ToString()))
.Concat(new[] { $@"\.\.{Regex.Escape(Path.DirectorySeparatorChar.ToString())}", $@"\.\.{Regex.Escape(Path.AltDirectorySeparatorChar.ToString())}", @"\.\.$" })
.Select(s => new Regex(s, RegexOptions.IgnoreCase | RegexOptions.Compiled))
.ToArray();

public MockFileSystem()
{
data = new ConcurrentDictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
Expand Down Expand Up @@ -151,14 +158,14 @@ public bool FileExists(string path)
}
}

public string[] GetDirectories(string path, bool recursive)
public string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
{
return GetFileSystemEntries(path, recursive, includeDirectories: true, includeFiles: false);
return GetFileSystemEntries(path, searchPattern, searchOption, includeDirectories: true, includeFiles: false);
}

public string[] GetFiles(string path, bool recursive)
public string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
{
return GetFileSystemEntries(path, recursive, includeDirectories: false, includeFiles: true);
return GetFileSystemEntries(path, searchPattern, searchOption, includeDirectories: false, includeFiles: true);
}

public FileStream OpenFile(string path, FileMode mode, FileAccess access, FileShare share)
Expand Down Expand Up @@ -195,8 +202,9 @@ private void CreateDirectoryRecurse(string[] pathParts)
}
}

private string[] GetFileSystemEntries(string ancestorPath, bool recursive, bool includeDirectories, bool includeFiles)
private string[] GetFileSystemEntries(string ancestorPath, string searchPattern, SearchOption searchOption, bool includeDirectories, bool includeFiles)
{
var searchPatternRegex = GetSearchPatternRegex(searchPattern);
var ancestorPathParts = Path.GetFullPath(ancestorPath).Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

var result = new List<string>();
Expand All @@ -206,7 +214,7 @@ private string[] GetFileSystemEntries(string ancestorPath, bool recursive, bool
{
var pathParts = Path.GetFullPath(path).Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

if (pathParts.Length > ancestorPathParts.Length && (recursive || pathParts.Length == ancestorPathParts.Length + 1))
if (pathParts.Length > ancestorPathParts.Length && (searchOption == SearchOption.AllDirectories || pathParts.Length == ancestorPathParts.Length + 1))
{
var match = true;
for (var i = 0; i < ancestorPathParts.Length; i++)
Expand All @@ -218,7 +226,7 @@ private string[] GetFileSystemEntries(string ancestorPath, bool recursive, bool
}
}

if (match)
if (match && searchPatternRegex.IsMatch(string.Join(Path.DirectorySeparatorChar.ToString(), pathParts.Skip(ancestorPathParts.Length))))
{
result.Add(path);
}
Expand All @@ -228,5 +236,18 @@ private string[] GetFileSystemEntries(string ancestorPath, bool recursive, bool

return result.ToArray();
}

private Regex GetSearchPatternRegex(string searchPattern)
{
foreach (var invalidSearchPatternRegex in invalidSearchPatternRegexes)
{
if (invalidSearchPatternRegex.IsMatch(searchPattern))
{
throw new ArgumentException("Search pattern is invalid", nameof(searchPattern));
}
}

return new Regex($"^{Regex.Escape(searchPattern.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar)).Replace(@"\*", ".*").Replace(@"\?", ".")}$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
}
}
40 changes: 36 additions & 4 deletions test/FileSystem.Tests/DirectoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void GetsDirectoriesNonRecursively()
fileSystem.data[Path.Combine(path, "bar")] = null;
fileSystem.data[Path.Combine(path, "bar", "baz")] = null;

Assert.AreEqual(1, fileSystem.GetDirectories(path, recursive: false).Length);
Assert.AreEqual(1, fileSystem.GetDirectories(path, "*", SearchOption.TopDirectoryOnly).Length);
}

[TestMethod]
Expand All @@ -66,7 +66,7 @@ public void GetsDirectoriesRecursively()
fileSystem.data[Path.Combine(path, "bar")] = null;
fileSystem.data[Path.Combine(path, "bar", "baz")] = null;

Assert.AreEqual(2, fileSystem.GetDirectories(path, recursive: true).Length);
Assert.AreEqual(2, fileSystem.GetDirectories(path, "*", SearchOption.AllDirectories).Length);
}

[TestMethod]
Expand All @@ -76,7 +76,7 @@ public void GetsFilesNonRecursively()
fileSystem.data[Path.Combine(path, "bar.txt")] = Array.Empty<byte>();
fileSystem.data[Path.Combine(path, "bar", "baz.txt")] = Array.Empty<byte>();

Assert.AreEqual(1, fileSystem.GetFiles(path, recursive: false).Length);
Assert.AreEqual(1, fileSystem.GetFiles(path, "*", SearchOption.TopDirectoryOnly).Length);
}

[TestMethod]
Expand All @@ -86,7 +86,7 @@ public void GetsFilesRecursively()
fileSystem.data[Path.Combine(path, "bar.txt")] = Array.Empty<byte>();
fileSystem.data[Path.Combine(path, "bar", "baz.txt")] = Array.Empty<byte>();

Assert.AreEqual(2, fileSystem.GetFiles(path, recursive: true).Length);
Assert.AreEqual(2, fileSystem.GetFiles(path, "*", SearchOption.AllDirectories).Length);
}

[TestMethod]
Expand All @@ -98,5 +98,37 @@ public void CreatesDirectoriesRecursively()
Assert.IsTrue(fileSystem.DirectoryExists(@"C:\foo"));
Assert.IsTrue(fileSystem.DirectoryExists(@"C:\foo\bar"));
}

[TestMethod]
public void SearchPatternIsAppliedCorrectly()
{
fileSystem.CreateDirectory(@"C:\foo\bar");
fileSystem.CreateDirectory(@"C:\foo\baz");
fileSystem.CreateDirectory(@"C:\foo\baz\bar");
fileSystem.CreateFile(@"C:\foo\bar.txt");
fileSystem.CreateFile(@"C:\foo\baz.txt");
fileSystem.CreateFile(@"C:\foo\baz\bar.txt");

Assert.AreEqual(2, fileSystem.GetDirectories(@"C:\foo", "*bar*", SearchOption.AllDirectories).Length);
Assert.AreEqual(2, fileSystem.GetFiles(@"C:\foo", "*bar*", SearchOption.AllDirectories).Length);
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ThrowsOnSearchPatternEndingWithTwoDots()
{
fileSystem.CreateDirectory(@"C:\foo\bar");

_ = fileSystem.GetDirectories(@"C:\foo", "*..", SearchOption.AllDirectories);
}

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ThrowsOnSearchPatternWithInvalidPathChars()
{
fileSystem.CreateDirectory(@"C:\foo\bar");

_ = fileSystem.GetDirectories(@"C:\foo", "*bar|", SearchOption.AllDirectories);
}
}
}

0 comments on commit 049b34b

Please sign in to comment.