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
85 changes: 69 additions & 16 deletions src/libraries/Common/src/System/IO/PathInternal.Unix.cs
Original file line number Diff line number Diff line change
@@ -1,44 +1,97 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Text;
using System.Diagnostics.CodeAnalysis;

namespace System.IO
{
/// <summary>Contains internal path helpers that are shared between many projects.</summary>
internal static partial class PathInternal
{
internal const char DirectorySeparatorChar = '/';
internal const char AltDirectorySeparatorChar = '/';
internal const char VolumeSeparatorChar = '/';
internal const char PathSeparator = ':';
internal const string DirectorySeparatorCharAsString = "/";
internal const string ParentDirectoryPrefix = @"../";

internal static int GetRootLength(ReadOnlySpan<char> path)
{
return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
}

internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path)
=> path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]);

internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) =>
EndsInDirectorySeparator(path) && !IsRoot(path) ?
path.Slice(0, path.Length - 1) :
path;

internal static bool IsRoot(ReadOnlySpan<char> path)
=> path.Length == GetRootLength(path);

internal static bool IsDirectorySeparator(char c)
{
// The alternate directory separator char is the same as the directory separator,
// so we only need to check one.
Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar);
return c == Path.DirectorySeparatorChar;
Debug.Assert(DirectorySeparatorChar == AltDirectorySeparatorChar);
return c == DirectorySeparatorChar;
}

internal static bool IsPartiallyQualified(string path)
/// <summary>
/// Normalize separators in the given path. Compresses forward slash runs.
/// </summary>
[return: NotNullIfNotNull("path")]
internal static string? NormalizeDirectorySeparators(string? path)
{
if (string.IsNullOrEmpty(path))
return path;

// Make a pass to see if we need to normalize so we can potentially skip allocating
bool normalized = true;

for (int i = 0; i < path.Length; i++)
{
if (IsDirectorySeparator(path[i])
&& (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
{
normalized = false;
break;
}
}

if (normalized)
return path;

StringBuilder builder = new StringBuilder(path.Length);

for (int i = 0; i < path.Length; i++)
{
char current = path[i];

// Skip if we have another separator following
if (IsDirectorySeparator(current)
&& (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
continue;

builder.Append(current);
}

return builder.ToString();
}

internal static bool IsPartiallyQualified(ReadOnlySpan<char> path)
{
// This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative)
// As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified.
return string.IsNullOrEmpty(path) || path[0] != Path.DirectorySeparatorChar;
return !Path.IsPathRooted(path);
}

/// <summary>
/// Returns true if the path is effectively empty for the current OS.
/// For unix, this is empty or null. For Windows, this is empty, null, or
/// just spaces ((char)32).
/// </summary>
internal static bool IsEffectivelyEmpty(string? path)
{
return string.IsNullOrEmpty(path);
}

internal static bool IsEffectivelyEmpty(ReadOnlySpan<char> path)
{
return path.IsEmpty;
}
}
}
Loading