File: src\libraries\Common\src\System\IO\PathInternal.Unix.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
 
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 const string DirectorySeparators = DirectorySeparatorCharAsString;
        internal static ReadOnlySpan<byte> Utf8DirectorySeparators => "/"u8;
 
        internal static int GetRootLength(ReadOnlySpan<char> path)
        {
            return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
        }
 
        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(DirectorySeparatorChar == AltDirectorySeparatorChar);
            return c == DirectorySeparatorChar;
        }
 
        /// <summary>
        /// Normalize separators in the given path. Compresses forward slash runs.
        /// </summary>
        [return: NotNullIfNotNull(nameof(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 !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;
        }
    }
}