mirror of https://github.com/icsharpcode/ILSpy.git
				
				
			
			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(); | 
						|
		} | 
						|
	} | 
						|
}
 | 
						|
 |