Add ResolvePath extension from MPF

This commit is contained in:
Matt Nadareski
2026-07-02 10:01:18 -04:00
parent 99caebd49b
commit a893de700a
2 changed files with 163 additions and 1 deletions

View File

@@ -355,6 +355,106 @@ namespace SabreTools.IO.Extensions.Test
#endregion
#region ResolvePath
[Theory]
[InlineData(null)]
[InlineData("")]
public void ResolvePath_NullOrEmpty_ReturnsNull(string? path)
{
string? actual = path.ResolvePath();
Assert.Null(actual);
}
// TODO: Add tests for home path without modifying the runner's home path
[Fact]
public void ResolvePath_AbsolutePath_Exists_ReturnsAsIs()
{
string path = typeof(IOExtensionsTests).Assembly.Location;
string? actual = path.ResolvePath();
Assert.Equal(path, actual);
}
[Fact]
public void ResolvePath_AbsolutePath_Missing_ReturnsNull()
{
string path = Path.Combine(Path.GetTempPath(), "fake-path.bin");
string? actual = path.ResolvePath();
Assert.Null(actual);
}
// TODO: Add ResolvePath_RelativePath_Exists_ReturnsAsIs
[Fact]
public void ResolvePath_RelativePath_Missing_ReturnsNull()
{
string path = Path.Combine("INVALID", "fake-path.bin");
string? actual = path.ResolvePath();
Assert.Null(actual);
}
[Fact]
public void ResolvePath_BareName_RuntimeDirectory_ReturnsFullPath()
{
// Create a file in the test assembly's runtime directory
string filename = "fake-path.bin";
string path = Path.Combine(AppContext.BaseDirectory, filename);
File.WriteAllBytes(path, []);
try
{
string? actual = filename.ResolvePath();
Assert.Equal(path, actual);
}
finally
{
File.Delete(path);
}
}
[Fact]
public void ResolvePath_BareName_Path_ReturnsFullPath()
{
// Create a temporary directory for testing
string tempDir = Path.Combine(Path.GetTempPath(), "fake-path");
Directory.CreateDirectory(tempDir);
// Create a file in the temporary directory
string filename = "fake-path.bin";
string path = Path.Combine(tempDir, filename);
File.WriteAllBytes(path, []);
// Prepend the temp directory to PATH and check it exists
// TODO: See if there's a way around changing the actual environment variables
string? originalPath = Environment.GetEnvironmentVariable("PATH");
try
{
Environment.SetEnvironmentVariable("PATH", tempDir + Path.PathSeparator + (originalPath ?? string.Empty));
string? actual = filename.ResolvePath();
Assert.Equal(path, actual);
}
finally
{
Environment.SetEnvironmentVariable("PATH", originalPath);
Directory.Delete(tempDir, recursive: true);
}
}
[Fact]
public void ResolvePath_BareName_Missing_ReturnsNull()
{
string filename = "fake-path.bin";
string? actual = filename.ResolvePath();
Assert.Null(actual);
}
#endregion
#region SafeGetDirectories
[Fact]

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
@@ -417,6 +418,67 @@ namespace SabreTools.IO.Extensions
return path ?? string.Empty;
}
/// <summary>
/// Resolve a file path that may be relative or contained within a PATH directory.
/// </summary>
/// <param name="path">Raw value from the user's options</param>
/// <returns>The absolute path of the located files, or null on failure</returns>
/// <remarks>
/// A value containing a path separator is treated as an explicit location and
/// returned as-is when it exists. A bare name (no separator) is searched in the
/// runtime directory first, then in each PATH entry.
/// </remarks>
public static string? ResolvePath(this string? path)
{
// Invalid paths always return null
if (string.IsNullOrEmpty(path))
return null;
// If the configured path starts with a home character
if (path!.StartsWith("~/") || path.StartsWith("~\\"))
{
string homeDirectory = PathTool.GetHomeDirectory();
path = path.Substring(2);
path = Path.Combine(homeDirectory, path);
return File.Exists(path) ? path : null;
}
// Explicit location (absolute or relative path)
if (path.Contains("/") || path.Contains("\\"))
return File.Exists(path) ? path : null;
// Check the runtime directory if no directory path is provided
string runtimeDir = PathTool.GetRuntimeDirectory();
if (!string.IsNullOrEmpty(runtimeDir))
{
string candidate = Path.Combine(runtimeDir, path);
if (File.Exists(candidate))
return candidate;
}
// Attempt to get the PATH variable for searching
string? pathEnv = Environment.GetEnvironmentVariable("PATH");
if (string.IsNullOrEmpty(pathEnv))
return null;
// Loop through all entries in PATH
var pathParts = pathEnv!.Split(Path.PathSeparator);
foreach (string dir in pathParts)
{
if (string.IsNullOrEmpty(dir))
continue;
string candidate = Path.Combine(dir, path);
if (File.Exists(candidate))
return candidate;
}
// All options failed
return null;
}
#endregion
#region Safe Directory Enumeration