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
54 changes: 54 additions & 0 deletions Semantics.Paths/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,36 @@
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:ktsu.Semantics.Paths.DirectoryName.Equals(ktsu.Semantics.Paths.DirectoryName):[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:ktsu.Semantics.Paths.DirectoryName.Equals(ktsu.Semantics.Strings.SemanticString{ktsu.Semantics.Paths.DirectoryName})$0:[T:System.Runtime.CompilerServices.NullableAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:ktsu.Semantics.Paths.DirectoryName.Equals(System.Object):[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:ktsu.Semantics.Paths.DirectoryName.op_Equality(ktsu.Semantics.Paths.DirectoryName,ktsu.Semantics.Paths.DirectoryName):[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:ktsu.Semantics.Paths.DirectoryName.op_Inequality(ktsu.Semantics.Paths.DirectoryName,ktsu.Semantics.Paths.DirectoryName):[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>M:ktsu.Semantics.Paths.DirectoryPath.Equals(ktsu.Semantics.Paths.DirectoryPath):[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
Expand Down Expand Up @@ -1363,6 +1393,18 @@
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>T:ktsu.Semantics.Paths.DirectoryName:[T:System.Runtime.CompilerServices.NullableAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>T:ktsu.Semantics.Paths.DirectoryName:[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>T:ktsu.Semantics.Paths.DirectoryPath:[T:System.Runtime.CompilerServices.NullableAttribute]</Target>
Expand Down Expand Up @@ -1417,6 +1459,18 @@
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>T:ktsu.Semantics.Paths.IsValidDirectoryNameAttribute:[T:System.Runtime.CompilerServices.NullableAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>T:ktsu.Semantics.Paths.IsValidDirectoryNameAttribute:[T:System.Runtime.CompilerServices.NullableContextAttribute]</Target>
<Left>lib/net7.0/ktsu.Semantics.Paths.dll</Left>
<Right>lib/net8.0/ktsu.Semantics.Paths.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0016</DiagnosticId>
<Target>T:ktsu.Semantics.Paths.IsValidFileNameAttribute:[T:System.Runtime.CompilerServices.NullableAttribute]</Target>
Expand Down
16 changes: 16 additions & 0 deletions Semantics.Paths/Implementations/AbsoluteDirectoryPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ protected override IDirectoryPath CreateDirectoryPath(string directoryPath) =>
return AbsoluteFilePath.Create<AbsoluteFilePath>(combinedPath);
}

/// <summary>
/// Combines an absolute directory path with a directory name using the '/' operator.
/// </summary>
/// <param name="left">The base absolute directory path.</param>
/// <param name="right">The directory name to append.</param>
/// <returns>A new <see cref="AbsoluteDirectoryPath"/> representing the combined path.</returns>
[SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Path combination is the semantic meaning, not mathematical division")]
public static AbsoluteDirectoryPath operator /(AbsoluteDirectoryPath left, DirectoryName right)
{
Guard.NotNull(left);
Guard.NotNull(right);

string combinedPath = PooledStringBuilder.CombinePaths(left.WeakString, right.WeakString);
return Create<AbsoluteDirectoryPath>(combinedPath);
}

/// <summary>
/// Combines an absolute directory path with a file name using the '/' operator.
/// </summary>
Expand Down
21 changes: 21 additions & 0 deletions Semantics.Paths/Implementations/DirectoryPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,27 @@ public RelativeDirectoryPath AsRelative(AbsoluteDirectoryPath baseDirectory)
return FilePath.Create<FilePath>(combinedPath);
}

/// <summary>
/// Combines a directory path with a directory name using the '/' operator.
/// </summary>
/// <param name="left">The base directory path.</param>
/// <param name="right">The directory name to append.</param>
/// <returns>A new <see cref="DirectoryPath"/> representing the combined path.</returns>
[SuppressMessage("Usage", "CA2225:Operator overloads have named alternates", Justification = "Path combination is the semantic meaning, not mathematical division")]
public static DirectoryPath operator /(DirectoryPath left, DirectoryName right)
{
#if NET6_0_OR_GREATER

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These guards exist here but not in the / overload within AbsoluteDirectoryPath.

Either this is not required here, or it needs to be added to the other location?

Guard.NotNull(left);
Guard.NotNull(right);
#else
ArgumentNullExceptionPolyfill.ThrowIfNull(left);
ArgumentNullExceptionPolyfill.ThrowIfNull(right);
#endif

string combinedPath = Path.Combine(left.WeakString, right.WeakString);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use PooledStringBuilder like the other function, or should the other function not use it? (or is both valid)

return Create<DirectoryPath>(combinedPath);
}

/// <summary>
/// Combines a directory path with a file name using the '/' operator.
/// </summary>
Expand Down
8 changes: 4 additions & 4 deletions Semantics.Paths/Implementations/RelativeDirectoryPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace ktsu.Semantics.Paths;
/// <summary>
/// Represents a relative directory path
/// </summary>
[IsPath, IsRelativePath, IsDirectoryPath]
[IsPath, IsRelativePath]
public sealed record RelativeDirectoryPath : SemanticDirectoryPath<RelativeDirectoryPath>, IRelativeDirectoryPath
{
// Cache for expensive parent directory computation
Expand All @@ -26,13 +26,13 @@ public sealed record RelativeDirectoryPath : SemanticDirectoryPath<RelativeDirec
public RelativeDirectoryPath Parent => _cachedParent ??= Create<RelativeDirectoryPath>(Path.GetDirectoryName(WeakString) ?? "");

// Cache for directory name
private FileName? _cachedName;
private DirectoryName? _cachedName;

/// <summary>
/// Gets the name of this directory (the last component of the path).
/// </summary>
/// <value>A <see cref="FileName"/> representing just the directory name.</value>
public FileName Name => _cachedName ??= FileName.Create<FileName>(Path.GetFileName(WeakString) ?? "");
/// <value>A <see cref="DirectoryName"/> representing just the directory name.</value>
public DirectoryName Name => _cachedName ??= DirectoryName.Create<DirectoryName>(Path.GetFileName(WeakString) ?? "");

// Cache for depth calculation
private int? _cachedDepth;
Expand Down
2 changes: 1 addition & 1 deletion Semantics.Paths/Implementations/RelativeFilePath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace ktsu.Semantics.Paths;
/// <summary>
/// Represents a relative file path
/// </summary>
[IsPath, IsRelativePath, IsFilePath]
[IsPath, IsRelativePath]
public sealed record RelativeFilePath : SemanticFilePath<RelativeFilePath>, IRelativeFilePath
{
// Cache for expensive directory path computation
Expand Down
12 changes: 12 additions & 0 deletions Semantics.Paths/Interfaces/IDirectoryName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) ktsu.dev
// All rights reserved.
// Licensed under the MIT license.

namespace ktsu.Semantics.Paths;

/// <summary>
/// Interface for directory names
/// </summary>
public interface IDirectoryName
{
}
15 changes: 15 additions & 0 deletions Semantics.Paths/Primitives/DirectoryName.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) ktsu.dev
// All rights reserved.
// Licensed under the MIT license.

namespace ktsu.Semantics.Paths;

using ktsu.Semantics.Strings;

/// <summary>
/// Represents a directory name
/// </summary>
[IsValidDirectoryName]
public sealed record DirectoryName : SemanticString<DirectoryName>, IDirectoryName
{
}
2 changes: 1 addition & 1 deletion Semantics.Paths/Primitives/FileName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace ktsu.Semantics.Paths;
/// <summary>
/// Represents a filename (without directory path)
/// </summary>
[IsFileName]
[IsValidFileName]
public sealed record FileName : SemanticString<FileName>, IFileName
{
}
55 changes: 0 additions & 55 deletions Semantics.Paths/Validation/Attributes/Path/IsFileNameAttribute.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright (c) ktsu.dev
// All rights reserved.
// Licensed under the MIT license.

namespace ktsu.Semantics.Paths;

using System;
using System.IO;
using ktsu.Semantics.Strings;

/// <summary>
/// Validates that a path string contains valid directory name characters (no path separators) using span semantics.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class IsValidDirectoryNameAttribute : NativeSemanticStringValidationAttribute
{
/// <summary>
/// Creates the validation adapter for valid directory name validation.
/// </summary>
/// <returns>A validation adapter for valid directory name strings</returns>
protected override ValidationAdapter CreateValidator() => new ValidDirectoryNameValidator();

/// <summary>
/// validation adapter for valid directory name strings.
/// </summary>
private sealed class ValidDirectoryNameValidator : ValidationAdapter
{
private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars();

/// <summary>
/// Validates that a directory name string contains only valid characters and no path separators.
/// </summary>
/// <param name="value">The string value to validate</param>
/// <returns>A validation result indicating success or failure</returns>
protected override ValidationResult ValidateValue(string value)
{
if (string.IsNullOrEmpty(value))
{
return ValidationResult.Success();
}

// Check for invalid filename characters
#if NETSTANDARD2_0
bool hasInvalidChars = value.IndexOfAny(InvalidFileNameChars) != -1;
#else
ReadOnlySpan<char> valueSpan = value.AsSpan();
bool hasInvalidChars = valueSpan.IndexOfAny(InvalidFileNameChars) != -1;
#endif
if (hasInvalidChars)
{
return ValidationResult.Failure("The directory name contains invalid characters.");
}

// Check for path separators (directory names shouldn't contain path separators)
if (value.Contains(Path.DirectorySeparatorChar) || value.Contains(Path.AltDirectorySeparatorChar))
{
return ValidationResult.Failure("The directory name contains path separators.");
}

return ValidationResult.Success();
}
}
}
Loading
Loading