You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
310 lines
8.9 KiB
310 lines
8.9 KiB
// Copyright (c) 2020 Daniel Grunwald
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
#nullable enable
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace ICSharpCode.Decompiler.Util
|
|
{
|
|
static class FileUtility
|
|
{
|
|
/// <summary>
|
|
/// Gets the normalized version of fileName.
|
|
/// Slashes are replaced with backslashes, backreferences "." and ".." are 'evaluated'.
|
|
/// </summary>
|
|
[return: NotNullIfNotNull("fileName")]
|
|
public static string? NormalizePath(string? fileName)
|
|
{
|
|
if (fileName == null || fileName.Length == 0)
|
|
return fileName;
|
|
|
|
int i;
|
|
|
|
bool isWeb = false;
|
|
for (i = 0; i < fileName.Length; i++)
|
|
{
|
|
if (fileName[i] == '/' || fileName[i] == '\\')
|
|
break;
|
|
if (fileName[i] == ':')
|
|
{
|
|
if (i > 1)
|
|
isWeb = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
char outputSeparator = '/';
|
|
bool isRelative;
|
|
bool isAbsoluteUnixPath = false;
|
|
|
|
StringBuilder result = new StringBuilder();
|
|
if (isWeb == false && IsUNCPath(fileName))
|
|
{
|
|
// UNC path
|
|
i = 2;
|
|
outputSeparator = '\\';
|
|
result.Append(outputSeparator);
|
|
isRelative = false;
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
isAbsoluteUnixPath = fileName[0] == '/';
|
|
isRelative = !isWeb && !isAbsoluteUnixPath && (fileName.Length < 2 || fileName[1] != ':');
|
|
if (fileName.Length >= 2 && fileName[1] == ':')
|
|
{
|
|
outputSeparator = '\\';
|
|
}
|
|
}
|
|
int levelsBack = 0;
|
|
int segmentStartPos = i;
|
|
for (; i <= fileName.Length; i++)
|
|
{
|
|
if (i == fileName.Length || fileName[i] == '/' || fileName[i] == '\\')
|
|
{
|
|
int segmentLength = i - segmentStartPos;
|
|
switch (segmentLength)
|
|
{
|
|
case 0:
|
|
// ignore empty segment (if not in web mode)
|
|
if (isWeb)
|
|
{
|
|
result.Append(outputSeparator);
|
|
}
|
|
break;
|
|
case 1:
|
|
// ignore /./ segment, but append other one-letter segments
|
|
if (fileName[segmentStartPos] != '.')
|
|
{
|
|
if (result.Length > 0)
|
|
result.Append(outputSeparator);
|
|
result.Append(fileName[segmentStartPos]);
|
|
}
|
|
break;
|
|
case 2:
|
|
if (fileName[segmentStartPos] == '.' && fileName[segmentStartPos + 1] == '.')
|
|
{
|
|
// remove previous segment
|
|
int j;
|
|
for (j = result.Length - 1; j >= 0 && result[j] != outputSeparator; j--)
|
|
{
|
|
}
|
|
if (j > 0)
|
|
{
|
|
result.Length = j;
|
|
}
|
|
else if (isAbsoluteUnixPath)
|
|
{
|
|
result.Length = 0;
|
|
}
|
|
else if (isRelative)
|
|
{
|
|
if (result.Length == 0)
|
|
levelsBack++;
|
|
else
|
|
result.Length = 0;
|
|
}
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// append normal segment
|
|
goto default;
|
|
}
|
|
default:
|
|
if (result.Length > 0)
|
|
result.Append(outputSeparator);
|
|
result.Append(fileName, segmentStartPos, segmentLength);
|
|
break;
|
|
}
|
|
segmentStartPos = i + 1; // remember start position for next segment
|
|
}
|
|
}
|
|
if (isWeb == false)
|
|
{
|
|
if (isRelative)
|
|
{
|
|
for (int j = 0; j < levelsBack; j++)
|
|
{
|
|
result.Insert(0, ".." + outputSeparator);
|
|
}
|
|
}
|
|
if (result.Length > 0 && result[result.Length - 1] == outputSeparator)
|
|
{
|
|
result.Length -= 1;
|
|
}
|
|
if (isAbsoluteUnixPath)
|
|
{
|
|
result.Insert(0, '/');
|
|
}
|
|
if (result.Length == 2 && result[1] == ':')
|
|
{
|
|
result.Append(outputSeparator);
|
|
}
|
|
if (result.Length == 0)
|
|
return ".";
|
|
}
|
|
return result.ToString();
|
|
}
|
|
|
|
static bool IsUNCPath(string fileName)
|
|
{
|
|
return fileName.Length > 2
|
|
&& (fileName[0] == '\\' || fileName[0] == '/')
|
|
&& (fileName[1] == '\\' || fileName[1] == '/');
|
|
}
|
|
|
|
public static bool IsEqualFileName(string? fileName1, string? fileName2)
|
|
{
|
|
return string.Equals(NormalizePath(fileName1),
|
|
NormalizePath(fileName2),
|
|
StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public static bool IsBaseDirectory(string? baseDirectory, string? testDirectory)
|
|
{
|
|
if (baseDirectory == null || testDirectory == null)
|
|
return false;
|
|
baseDirectory = NormalizePath(baseDirectory);
|
|
if (baseDirectory == "." || baseDirectory == "")
|
|
return !Path.IsPathRooted(testDirectory);
|
|
baseDirectory = AddTrailingSeparator(baseDirectory);
|
|
testDirectory = AddTrailingSeparator(NormalizePath(testDirectory));
|
|
|
|
return testDirectory.StartsWith(baseDirectory, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[return: NotNullIfNotNull("input")]
|
|
static string? AddTrailingSeparator(string? input)
|
|
{
|
|
if (input == null || input.Length == 0)
|
|
return input;
|
|
if (input[input.Length - 1] == Path.DirectorySeparatorChar || input[input.Length - 1] == Path.AltDirectorySeparatorChar)
|
|
return input;
|
|
else
|
|
return input + GetSeparatorForPath(input).ToString();
|
|
}
|
|
|
|
static char GetSeparatorForPath(string input)
|
|
{
|
|
if (input.Length > 2 && input[1] == ':' || IsUNCPath(input))
|
|
return '\\';
|
|
return '/';
|
|
}
|
|
|
|
readonly static char[] separators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
|
|
|
|
/// <summary>
|
|
/// Converts a given absolute path and a given base path to a path that leads
|
|
/// from the base path to the absoulte path. (as a relative path)
|
|
/// </summary>
|
|
public static string GetRelativePath(string? baseDirectoryPath, string absPath)
|
|
{
|
|
if (baseDirectoryPath == null || baseDirectoryPath.Length == 0)
|
|
{
|
|
return absPath;
|
|
}
|
|
|
|
baseDirectoryPath = NormalizePath(baseDirectoryPath);
|
|
absPath = NormalizePath(absPath);
|
|
|
|
string[] bPath = baseDirectoryPath != "." ? baseDirectoryPath.TrimEnd(separators).Split(separators) : new string[0];
|
|
string[] aPath = absPath != "." ? absPath.TrimEnd(separators).Split(separators) : new string[0];
|
|
int indx = 0;
|
|
for (; indx < Math.Min(bPath.Length, aPath.Length); ++indx)
|
|
{
|
|
if (!bPath[indx].Equals(aPath[indx], StringComparison.OrdinalIgnoreCase))
|
|
break;
|
|
}
|
|
|
|
if (indx == 0 && (Path.IsPathRooted(baseDirectoryPath) || Path.IsPathRooted(absPath)))
|
|
{
|
|
return absPath;
|
|
}
|
|
|
|
if (indx == bPath.Length && indx == aPath.Length)
|
|
{
|
|
return ".";
|
|
}
|
|
StringBuilder erg = new StringBuilder();
|
|
for (int i = indx; i < bPath.Length; ++i)
|
|
{
|
|
erg.Append("..");
|
|
erg.Append(Path.DirectorySeparatorChar);
|
|
}
|
|
erg.Append(String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx));
|
|
if (erg[erg.Length - 1] == Path.DirectorySeparatorChar)
|
|
erg.Length -= 1;
|
|
return erg.ToString();
|
|
}
|
|
|
|
[return: NotNullIfNotNull("path")]
|
|
public static string? TrimPath(string? path, int max_chars)
|
|
{
|
|
const char ellipsis = '\u2026'; // HORIZONTAL ELLIPSIS
|
|
const int ellipsisLength = 2;
|
|
|
|
if (path == null || path.Length <= max_chars)
|
|
return path;
|
|
char sep = Path.DirectorySeparatorChar;
|
|
if (path.IndexOf(Path.AltDirectorySeparatorChar) >= 0 && path.IndexOf(Path.DirectorySeparatorChar) < 0)
|
|
{
|
|
sep = Path.AltDirectorySeparatorChar;
|
|
}
|
|
string[] parts = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
|
|
int len = ellipsisLength; // For initial ellipsis
|
|
int index = parts.Length;
|
|
// From the end of the path, fit as many parts as possible:
|
|
while (index > 1 && len + parts[index - 1].Length < max_chars)
|
|
{
|
|
len += parts[index - 1].Length + 1;
|
|
index--;
|
|
}
|
|
|
|
StringBuilder result = new StringBuilder();
|
|
result.Append(ellipsis);
|
|
// If there's 5 chars left, partially fit another part:
|
|
if (index > 1 && len + 5 <= max_chars)
|
|
{
|
|
if (index == 2 && parts[0].Length <= ellipsisLength)
|
|
{
|
|
// If the partial part is part #1,
|
|
// and part #0 is as short as the ellipsis
|
|
// (e.g. just a drive letter), use part #0
|
|
// instead of the ellipsis.
|
|
result.Clear();
|
|
result.Append(parts[0]);
|
|
}
|
|
result.Append(sep);
|
|
result.Append(parts[index - 1], 0, max_chars - len - 3);
|
|
result.Append(ellipsis);
|
|
}
|
|
while (index < parts.Length)
|
|
{
|
|
result.Append(sep);
|
|
result.Append(parts[index]);
|
|
index++;
|
|
}
|
|
return result.ToString();
|
|
}
|
|
}
|
|
}
|