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
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Pathy
public static Pathy.ChainablePath Empty { get; }
public static Pathy.ChainablePath New { get; }
public static Pathy.ChainablePath Temp { get; }
public bool HasExtension(string extension) { }
public System.IO.DirectoryInfo ToDirectoryInfo() { }
public System.IO.FileInfo ToFileInfo() { }
public override string ToString() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Pathy
public static Pathy.ChainablePath New { get; }
public static Pathy.ChainablePath Temp { get; }
public Pathy.ChainablePath AsRelativeTo(Pathy.ChainablePath basePath) { }
public bool HasExtension(string extension) { }
public System.IO.DirectoryInfo ToDirectoryInfo() { }
public System.IO.FileInfo ToFileInfo() { }
public override string ToString() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace Pathy
public static Pathy.ChainablePath Empty { get; }
public static Pathy.ChainablePath New { get; }
public static Pathy.ChainablePath Temp { get; }
public bool HasExtension(string extension) { }
public System.IO.DirectoryInfo ToDirectoryInfo() { }
public System.IO.FileInfo ToFileInfo() { }
public override string ToString() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Pathy
public static Pathy.ChainablePath New { get; }
public static Pathy.ChainablePath Temp { get; }
public Pathy.ChainablePath AsRelativeTo(Pathy.ChainablePath basePath) { }
public bool HasExtension(string extension) { }
public System.IO.DirectoryInfo ToDirectoryInfo() { }
public System.IO.FileInfo ToFileInfo() { }
public override string ToString() { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.2">
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
30 changes: 30 additions & 0 deletions Pathy.Specs/ChainablePathSpecs.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using FluentAssertions;
using Xunit;

Expand Down Expand Up @@ -348,6 +349,35 @@ public void Can_add_an_extension()
path.Extension.Should().Be(".txt");
}

[Theory]
[InlineData(".txt", true)]
[InlineData(".TXT", true)]
[InlineData("TXT", true)]
[InlineData("DOC", false)]
public void Can_check_for_an_extension(string extension, bool shouldMatch)
{
// Act
var path = ChainablePath.Temp / "SomeFile.txt";

// Assert
path.HasExtension(extension).Should().Be(shouldMatch);
}

[Theory]
[InlineData(null)]
[InlineData("")]
public void Checking_for_an_extension_requires_a_valid_extension(string extension)
{
// Arrange
var path = ChainablePath.Temp / "SomeFile.txt";

// Act
Action act = () => path.HasExtension(extension);

// Assert
act.Should().Throw<ArgumentException>("*null*empty*");
}

#if NET6_0_OR_GREATER
[Fact]
public void Can_get_the_difference_as_a_relative_path()
Expand Down
9 changes: 1 addition & 8 deletions Pathy.Specs/Pathy.Specs.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,9 @@
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.2">
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Expand Down
22 changes: 22 additions & 0 deletions Pathy/ChainablePath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,28 @@ public FileInfo ToFileInfo()
{
return new FileInfo(ToString());
}

/// <summary>
/// Determines if the current path has the specified file extension.
/// </summary>
/// <param name="extension">
/// The file extension to check for, with or without the leading period (e.g., ".txt").
/// </param>
public bool HasExtension(string extension)
{
if (string.IsNullOrEmpty(extension))
{
throw new ArgumentException("Extension cannot be null or empty", nameof(extension));
}

// Ensure the extension starts with a dot
if (!extension.StartsWith(".", StringComparison.InvariantCulture))
{
extension = '.' + extension;
}

return string.Equals(Extension, extension, StringComparison.OrdinalIgnoreCase);
}
}

#if PATHY_PUBLIC
Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<h1 align="center">
<br>
<img src="./logo.png" style="width:300px" alt="Pathy"/>
<img src="./logo.png" style="width:300px" alt="Pathy"/>
<br>
</h1>

Expand Down Expand Up @@ -39,19 +39,19 @@

### What's this?

Pathy is a tiny source-only library that will allow you to build file and directory paths by chaining together strings like `"c:"`, `"dir1"`, `"dir2"` using
Pathy is a tiny source-only library that will allow you to build file and directory paths by chaining together strings like `"c:"`, `"dir1"`, `"dir2"` using

`ChainablePath.New() / "c:" / "dir1" / "dir2"`.
`ChainablePath.New() / "c:" / "dir1" / "dir2"`.

It was heavily inspired by the best build pipeline framework available in the .NET space, [Nuke](https://nuke.build/). Nuke has supported these concepts for many years, but I needed this capability outside build pipelines. Lots of kudos to [Matthias Koch](https://www.linkedin.com/in/matthias-koch-jb/) for what I see as a brilliant idea.

It doesn't have any dependencies and runs on .NET 4.7, .NET 8, as well as frameworks supporting .NET Standard 2.0 and 2.1.
It doesn't have any dependencies and runs on .NET 4.7, .NET 8, as well as frameworks supporting .NET Standard 2.0 and 2.1.

### What's so special about that?

It makes those chained calls to `Path.Combine` a thing from the past and hides the ugliness of dealing with (trailing) slashes.
It makes those chained calls to `Path.Combine` a thing from the past and hides the ugliness of dealing with (trailing) slashes.

It ships as a source-only package, which means you can use it in your own libraries and projects, without incurring any dependency pain on your consuming projects.
It ships as a source-only package, which means you can use it in your own libraries and projects, without incurring any dependency pain on your consuming projects.

The core Pathy package does not have any dependencies, and I purposely moved the [globbing](https://learn.microsoft.com/en-us/dotnet/core/extensions/file-globbing#pattern-formats) functionality into a separate package as it depends on `Microsoft.Extensions.FileSystemGlobbing`.

Expand All @@ -69,7 +69,7 @@ This library is available as [a NuGet package](https://www.nuget.org/packages/Pa
## How do I use it?

### To ChainablePath and back to string
It all starts with the construction of a `ChainablePath` instance to represent a path to a file or directory.
It all starts with the construction of a `ChainablePath` instance to represent a path to a file or directory.

There are several ways of doing that.

Expand All @@ -80,11 +80,11 @@ var path = "c:/mypath/to/a/directory".ToPath();
var path = (ChainablePath)"c:/mypath/to/a/directory";
```

Additionally, you can use `ChainablePath.Current` to get the current working directory as an instance of `ChainablePath`, and `ChainablePath.Temp` to get that for the user's temporary folder.
Additionally, you can use `ChainablePath.Current` to get the current working directory as an instance of `ChainablePath`, and `ChainablePath.Temp` to get that for the user's temporary folder.

The first thing you'll notice is how the `/` operator is used to chain multiple parts of a path together. This is the primary feature of Pathy. And it doesn't matter if you do that on Linux or Windows. Internally it'll use whatever path separator is suitable.
The first thing you'll notice is how the `/` operator is used to chain multiple parts of a path together. This is the primary feature of Pathy. And it doesn't matter if you do that on Linux or Windows. Internally it'll use whatever path separator is suitable.

You can also use the `+` operator to add some phrase to the path _without_ using a separator.
You can also use the `+` operator to add some phrase to the path _without_ using a separator.

```csharp
var path = ChainablePath.From("c:") / "my-path" / "to" / "a" / "directory";
Expand All @@ -95,7 +95,7 @@ path = path + "2"
string result = path.ToString();
```

To convert an instance of `ChainablePath` back to a `string`, you can either call `ToString()` or cast the instance to a `string`.
To convert an instance of `ChainablePath` back to a `string`, you can either call `ToString()` or cast the instance to a `string`.

```csharp
string rawPath = path.ToString();
Expand All @@ -108,10 +108,11 @@ Know that `ChainablePath` overrides `Equals` and `GetHashCode`, so you can alway

Given an instance of `ChainablePath`, you can get a lot of useful information:
* `Name` returns the full name, but without the directory, whereas `Extension` gives you the extension _including_ the dot.
* `Directory`, `Parent` or `DirectoryName` give you the (parent) directory of a file or directory.
* `Directory`, `Parent` or `DirectoryName` give you the (parent) directory of a file or directory.
* To see if a path is absolute, use `IsRooted`
* Not sure if a path points to an actual file system entry? Use `IsFile`, `IsDirectory` or `Exists`
* Want to know the delta between two paths? Use `AsRelativeTo`.
* To determine if a file has a case-insensitive extension, use `HasExtension(".txt")` or `HasExtension("txt")`.

And if the built-in functionality really isn't enough, you can always call `ToDirectoryInfo` or `ToFileInfo` to continue with an instance of `DirectoryInfo` and `FileInfo`.

Expand Down