Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,4 @@ Tests/FluentAssertions.Specs/FluentAssertions.Specs.xml
Tests/Benchmarks/BenchmarkDotNet.Artifacts/

# Documentation spell check
node_modules/
node_modules/
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace Pathy
public static void DeleteFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> paths) { }
public static void MoveFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> sourcePaths, Pathy.ChainablePath destinationDirectory) { }
public static void MoveFileOrDirectory(this Pathy.ChainablePath sourcePath, Pathy.ChainablePath destinationDirectory, string newName = null) { }
public static Pathy.ChainablePath ResolveFile(this Pathy.ChainablePath path, string fileName) { }
}
public static class StringExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ namespace Pathy
public static void DeleteFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> paths) { }
public static void MoveFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> sourcePaths, Pathy.ChainablePath destinationDirectory) { }
public static void MoveFileOrDirectory(this Pathy.ChainablePath sourcePath, Pathy.ChainablePath destinationDirectory, string newName = null) { }
public static Pathy.ChainablePath ResolveFile(this Pathy.ChainablePath path, string fileName) { }
}
public static class StringExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace Pathy
public static void DeleteFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> paths) { }
public static void MoveFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> sourcePaths, Pathy.ChainablePath destinationDirectory) { }
public static void MoveFileOrDirectory(this Pathy.ChainablePath sourcePath, Pathy.ChainablePath destinationDirectory, string newName = null) { }
public static Pathy.ChainablePath ResolveFile(this Pathy.ChainablePath path, string fileName) { }
}
public static class StringExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ namespace Pathy
public static void DeleteFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> paths) { }
public static void MoveFileOrDirectory(this System.Collections.Generic.IEnumerable<Pathy.ChainablePath> sourcePaths, Pathy.ChainablePath destinationDirectory) { }
public static void MoveFileOrDirectory(this Pathy.ChainablePath sourcePath, Pathy.ChainablePath destinationDirectory, string newName = null) { }
public static Pathy.ChainablePath ResolveFile(this Pathy.ChainablePath path, string fileName) { }
}
public static class StringExtensions
{
Expand Down
93 changes: 93 additions & 0 deletions Pathy.Specs/ChainablePathSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,99 @@ public void Cannot_find_the_first_existing_path_from_an_empty_array()
.WithParameterName("paths");
}

[Fact]
public void Can_resolve_a_file_when_path_is_the_file_itself()
{
// Arrange
var file = testFolder / "test.txt";
File.WriteAllText(file, "content");

// Act
var result = file.ResolveFile("test.txt");

// Assert
result.ToString().Should().Be(file.ToString());
result.FileExists.Should().BeTrue();
}

[Fact]
public void Can_resolve_a_file_when_path_is_a_directory_containing_the_file()
{
// Arrange
var file = testFolder / "test.txt";
File.WriteAllText(file, "content");

// Act
var result = testFolder.ResolveFile("test.txt");

// Assert
result.ToString().Should().Be(file.ToString());
result.FileExists.Should().BeTrue();
}

[Fact]
public void ResolveFile_is_case_insensitive_when_path_is_the_file()
{
// Arrange
var file = testFolder / "Test.txt";
File.WriteAllText(file, "content");

// Act
var result = file.ResolveFile("test.TXT");

// Assert
result.ToString().Should().Be(file.ToString());
result.FileExists.Should().BeTrue();
}

[Fact]
public void ResolveFile_returns_empty_when_file_does_not_exist_in_directory()
{
// Act
var result = testFolder.ResolveFile("nonexistent.txt");

// Assert
result.Should().Be(ChainablePath.Empty);
}

[Fact]
public void ResolveFile_returns_empty_when_path_is_a_file_with_different_name()
{
// Arrange
var file = testFolder / "actual.txt";
File.WriteAllText(file, "content");

// Act
var result = file.ResolveFile("different.txt");

// Assert
result.Should().Be(ChainablePath.Empty);
}

[Fact]
public void ResolveFile_throws_when_fileName_is_null()
{
// Act
var act = () => testFolder.ResolveFile(null);

// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*File name cannot be null or empty*")
.WithParameterName("fileName");
}

[Fact]
public void ResolveFile_throws_when_fileName_is_empty()
{
// Act
var act = () => testFolder.ResolveFile(string.Empty);

// Assert
act.Should().Throw<ArgumentException>()
.WithMessage("*File name cannot be null or empty*")
.WithParameterName("fileName");
}

[Fact]
public void Can_get_last_write_time_utc_for_file()
{
Expand Down
38 changes: 38 additions & 0 deletions Pathy/ChainablePathExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,42 @@ public static void MoveFileOrDirectory(this System.Collections.Generic.IEnumerab
sourcePath.MoveFileOrDirectory(destinationDirectory);
}
}

/// <summary>
/// Resolves a file name within the current path.
/// If the path represents a file with the specified name and that file exists, returns the path.
/// If the path is a directory that contains a file with the specified name, returns the path to that file.
/// Otherwise, returns <see cref="ChainablePath.Empty"/>.
/// </summary>
/// <param name="path">The base path to resolve from (can be a file or directory).</param>
/// <param name="fileName">The file name to resolve.</param>
/// <returns>
/// A <see cref="ChainablePath"/> representing the resolved file if found; otherwise, <see cref="ChainablePath.Empty"/>.
/// </returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="fileName"/> is null or empty.</exception>
public static ChainablePath ResolveFile(this ChainablePath path, string fileName)
{
if (string.IsNullOrEmpty(fileName))
{
throw new ArgumentException("File name cannot be null or empty", nameof(fileName));
}

// Case 1: If the path is a file with the specified name and exists, return it
if (path.IsFile && string.Equals(path.Name, fileName, StringComparison.OrdinalIgnoreCase))
{
return path;
}

// Case 2: If the path is a directory, check if it contains the file
if (path.IsDirectory)
{
ChainablePath filePath = path / fileName;
if (filePath.FileExists)
{
return filePath;
}
}

return ChainablePath.Empty;
}
}
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ var filesToMove = (ChainablePath.Current / "source").GlobFiles("*.txt");
filesToMove.MoveFileOrDirectory(ChainablePath.Current / "destination");
```

### Resolving files

If you have a `ChainablePath` that could represent either a file or a directory, and you want to resolve a specific file name, you can use the `ResolveFile` extension method:

```csharp
// When the path is a directory containing the file
var directory = ChainablePath.From("c:/projects/myapp");
var configFile = directory.ResolveFile("appsettings.json");
// Returns: c:/projects/myapp/appsettings.json (if it exists)

// When the path is already the file itself
var filePath = ChainablePath.From("c:/projects/myapp/appsettings.json");
var resolved = filePath.ResolveFile("appsettings.json");
// Returns: c:/projects/myapp/appsettings.json (if it exists)

// When the file doesn't exist
var missing = directory.ResolveFile("missing.txt");
// Returns: ChainablePath.Empty
```

The method performs case-insensitive file name matching, so `ResolveFile("CONFIG.JSON")` will match `config.json`.

## Building

To build this repository locally so you can contribute to it, you need the following:
Expand Down