Skip to content

Commit

Permalink
Merge branch 'refs/heads/feature/get-file-extension'
Browse files Browse the repository at this point in the history
  • Loading branch information
ForNeVeR committed May 3, 2024
2 parents 1557e0d + f16c3d1 commit cd2ae07
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased] (1.2.0)
### Added
- New `IPath` and `IPath<TPath>` interfaces that allow to process any paths (`LocalPath` and `AbsolutePath`) in a polymorphic way.
- `GetExtensionWithDot` and `GetExtensionWithoutDot` extension methods for `IPath`.

### Changed
- [#19](https://github.com/ForNeVeR/TruePath/issues/19): Optimize `PathStrings.Normalize` method.
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SPDX-License-Identifier: MIT
<PropertyGroup Label="Packaging">
<Version>1.1.0</Version>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<Copyright>© 2024 Friedrich von Never</Copyright>
<Copyright>2024 TruePath contributors &lt;https://github.com/ForNeVeR/TruePath&gt;</Copyright>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>true</IncludeSymbols>
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,23 @@ This functions basically the same as the `LocalPath`, but it is _always_ an abso

To convert from `LocalPath` to `AbsolutePath` and vice versa, you can use the constructors of `AbsolutePath` and `LocalPath` respectively. Any `AbsolutePath` constructor (from either a string or a `LocalPath`) has same check for absolute path, and any `LocalPath` constructor (from either a string or an `AbsolutePath`) doesn't have any checks.

### `IPath`
This is an interface that is implemented by both `LocalPath` and `AbsolutePath`. It allows to process any paths in a polymorphic way.

### `LocalPathPattern`
This is a marker type that doesn't offer any advanced functionality over the contained string. It is used to mark paths that include wildcards, for further integration with external libraries, such as [Microsoft.Extensions.FileSystemGlobbing][file-system-globbing.nuget].

### Path Features
Aside from the strict types, the following features are supported for the paths:
- `IPath::Value` returns the normalized path string;
- `IPath::FileName` returns the last component of the path;
- `IPath::Parent` returns the parent path item (`null` for the root path or top-level relative path);
- `IPath<T>` supports operators to join it with `LocalPath` or a `string` (note that in both cases appending an absolute path to path of another kind will take over: the last absolute path in chain will win and destroy all the previous ones; this is the standard behavior of path-combining methods — use `AbsolutePath` in combination with `RelativePath` if you want to avoid this behavior);
- `LocalPath::IsAbsolute` to check the path kind (since it supports both kinds);
- `LocalPath::IsPrefixOf` to check path prefixes;
- `LocalPath::RelativeTo` to get a relative part between two paths, if possible;
- extension methods on `IPath`: `GetExtensionWithDot` and `GetExtensionWithoutDot` to get the file extension with or without the leading dot (note that `GetExtensionWithDot` will behave differently for paths ending with dots and paths without dot at all, which allows to reconstruct such a file name from its part without extension and the "extension with dot").

Documentation
-------------
- [Contributor Guide][docs.contributing]
Expand Down
45 changes: 45 additions & 0 deletions TruePath.Tests/PathExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2024 TruePath contributors <https://github.com/ForNeVeR/TruePath>
//
// SPDX-License-Identifier: MIT

namespace TruePath.Tests;

public class PathExtensionsTests
{
[Theory]
[InlineData("..", ".")]
[InlineData("foo/bar.txt", ".txt")]
[InlineData("/foo/bar.txt", ".txt")]
[InlineData("foo/bar.", ".")]
[InlineData("foo/bar", "")]
[InlineData(".gitignore", ".gitignore")]
public void GetExtensionWithDotTests(string path, string expected)
{
IPath local = new LocalPath(path);
Assert.Equal(expected, local.GetExtensionWithDot());

if (!path.StartsWith('/')) return;

IPath a = new AbsolutePath(path);
Assert.Equal(expected, a.GetExtensionWithDot());
}

[Theory]
[InlineData(".", "")]
[InlineData("..", "")]
[InlineData("foo/bar.txt", "txt")]
[InlineData("/foo/bar.txt", "txt")]
[InlineData("foo/bar.", "")]
[InlineData("foo/bar", "")]
[InlineData(".gitignore", "gitignore")]
public void GetExtensionWithoutDotTests(string path, string expected)
{
IPath l = new LocalPath(path);
Assert.Equal(expected, l.GetExtensionWithoutDot());

if (!path.StartsWith('/')) return;

IPath a = new AbsolutePath(path);
Assert.Equal(expected, a.GetExtensionWithoutDot());
}
}
4 changes: 2 additions & 2 deletions TruePath/AbsolutePath.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2024 Friedrich von Never <[email protected]>
// SPDX-FileCopyrightText: 2024 TruePath contributors <https://github.com/ForNeVeR/TruePath>
//
// SPDX-License-Identifier: MIT

Expand Down Expand Up @@ -31,7 +31,7 @@ public AbsolutePath(string value)
/// <exception cref="ArgumentException">Thrown if the passed path is not absolute.</exception>
public AbsolutePath(LocalPath localPath) : this(localPath.Value) {}

/// <summary>The normalized path string.</summary>
/// <inheritdoc cref="IPath.Value"/>
public string Value => Underlying.Value;

/// <inheritdoc cref="IPath.Parent"/>
Expand Down
7 changes: 5 additions & 2 deletions TruePath/IPath.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2024 Friedrich von Never <[email protected]>
// SPDX-FileCopyrightText: 2024 TruePath contributors <https://github.com/ForNeVeR/TruePath>
//
// SPDX-License-Identifier: MIT

Expand All @@ -7,7 +7,10 @@ namespace TruePath;
/// <summary>Represents a path in a file system.</summary>
public interface IPath
{
/// <summary>The full name of the last component of this path.</summary>
/// <summary>The normalized path string.</summary>
string Value { get; }

/// <summary>The name of the last component of this path.</summary>
string FileName { get; }

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions TruePath/LocalPath.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2024 Friedrich von Never <[email protected]>
// SPDX-FileCopyrightText: 2024 TruePath contributors <https://github.com/ForNeVeR/TruePath>
//
// SPDX-License-Identifier: MIT

Expand All @@ -14,7 +14,7 @@ namespace TruePath;
/// </summary>
public readonly struct LocalPath(string value) : IEquatable<LocalPath>, IPath, IPath<LocalPath>
{
/// <summary>The normalized path string.</summary>
/// <inheritdoc cref="IPath.Value"/>
public string Value { get; } = PathStrings.Normalize(value);

/// <summary>
Expand Down
41 changes: 41 additions & 0 deletions TruePath/PathExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-FileCopyrightText: 2024 TruePath contributors <https://github.com/ForNeVeR/TruePath>
//
// SPDX-License-Identifier: MIT

namespace TruePath;

/// <summary>
/// Extension methods for <see cref="IPath"/> and <see cref="IPath{TPath}"/>.
/// </summary>
public static class PathExtensions
{
/// <summary>
/// <para>Gets the extension of the file name of the <paramref name="path"/> with the dot character.</para>
/// <para>For example, for the path <c>file.txt</c>, this method will return a string <c>.txt</c>.</para>
/// <para>File name entirely consisting of extension, such as <c>.gitignore</c>, is returned as-is.</para>
/// </summary>
/// <returns>The extension of the file name of the path with the dot character (if present).</returns>
/// <remarks>
/// This method will return an empty string for paths without extensions, and will return a dot for paths whose
/// names end with a dot (even though it is an unusual path). This behavior allows to distinguish such paths,
/// and potentially reconstruct the file name from its part without the extension and the "extension with dot".
/// </remarks>
public static string GetExtensionWithDot(this IPath path) =>
path.FileName.EndsWith('.') ? "." : Path.GetExtension(path.FileName);

/// <summary>
/// <para>Gets the extension of the file name of the <paramref name="path"/> without the dot character.</para>
/// <para>For example, for the path <c>file.txt</c>, this method will return a string <c>txt</c>.</para>
/// <para>
/// File name entirely consisting of extension, such as <c>.gitignore</c>, is returned with its leading dot
/// trimmed.
/// </para>
/// </summary>
/// <returns>The extension of the file name of the path without the dot.</returns>
/// <remarks>
/// This method will return an empty string for paths without extensions and with empty extensions (ending with
/// dot, which may be unusual). This behavior doesn't allow to distinguish such paths using this method, to
/// reconstruct the original name from its name without extension and its extension without dot.
/// </remarks>
public static string GetExtensionWithoutDot(this IPath path) => GetExtensionWithDot(path).TrimStart('.');
}

0 comments on commit cd2ae07

Please sign in to comment.