From 563eabb6c435d3a6467cf786a55b9d0f2e22013f Mon Sep 17 00:00:00 2001 From: Timerix22 Date: Wed, 8 Feb 2023 02:02:55 +0600 Subject: [PATCH] IOPath --- DTLib.Tests/Program.cs | 15 ++-- DTLib/DependencyResolver.cs | 6 +- DTLib/Filesystem/Directory.cs | 126 +++++++++++---------------- DTLib/Filesystem/File.cs | 95 ++++++++------------- DTLib/Filesystem/IOPath.cs | 155 ++++++++++++++++++++++++++++++++++ DTLib/Filesystem/Path.cs | 144 ++++++++++++++++--------------- 6 files changed, 328 insertions(+), 213 deletions(-) create mode 100644 DTLib/Filesystem/IOPath.cs diff --git a/DTLib.Tests/Program.cs b/DTLib.Tests/Program.cs index eff8654..08c644e 100644 --- a/DTLib.Tests/Program.cs +++ b/DTLib.Tests/Program.cs @@ -30,16 +30,13 @@ public static class Program try { - string path = "file"; - string path2 = path + "_temp"; - // Ensure that the target does not exist. - File.Delete(path2); - // Copy the file. - File.Copy(path, path2); + IOPath path = "file"; + IOPath path2 = "dir/"+ path + "_temp"; + File.Create(path); + File.Copy(path, path2, true); System.Console.WriteLine("{0} was copied to {1}.", path, path2); - // Delete the newly created file. - File.Delete(path2); - System.Console.WriteLine("{0} was successfully deleted.", path2); + Directory.Copy(path2.ParentDir(), "dir2/c/", true); + System.Console.WriteLine($"dir/c/:\n"+Directory.GetAllFiles("dir2\\c").MergeToString("\n\t")); return; new LaunchArgumentParser().WithNoExit().ParseAndHandle(args); diff --git a/DTLib/DependencyResolver.cs b/DTLib/DependencyResolver.cs index 3bc3c89..21dd6b8 100644 --- a/DTLib/DependencyResolver.cs +++ b/DTLib/DependencyResolver.cs @@ -29,10 +29,10 @@ public static class DependencyResolver _ => throw new Exception($"unsupported platform {RuntimeInformation.ProcessArchitecture}") }; - string[] possibleLibDirs = + IOPath[] possibleLibDirs = new [] { - Path.Concat("runtimes", $"{os}"), - Path.Concat("runtimes", $"{os}", "native"), + Path.Concat("runtimes", os), + Path.Concat("runtimes", os, "native"), Path.Concat("runtimes", $"{os}-{arch}"), Path.Concat("runtimes", $"{os}-{arch}", "native") }; diff --git a/DTLib/Filesystem/Directory.cs b/DTLib/Filesystem/Directory.cs index c2fc351..06dae54 100644 --- a/DTLib/Filesystem/Directory.cs +++ b/DTLib/Filesystem/Directory.cs @@ -2,125 +2,101 @@ public static class Directory { - public static bool Exists(string dir, bool separatorsFixed) - { - if(!separatorsFixed) - dir = Path.FixSeparators(dir); - return System.IO.Directory.Exists(dir); - } + public static bool Exists(IOPath dir) => System.IO.Directory.Exists(dir.Str); /// создает папку, если её не существует - public static void Create(string dir, bool separatorsFixed) + public static void Create(IOPath dir) { - if(!separatorsFixed) - dir = Path.FixSeparators(dir); - if (!Exists(dir)) + if (Exists(dir)) return; + + // creation of parent directories + if (dir.Contains(Path.Sep)) { - // проверяет существование папки, в которой нужно создать dir - if (dir.Contains(Path.Sep)) - { - string parentDir = dir.Remove(dir.LastIndexOf(Path.Sep)); - if(!Exists(parentDir,true)) - Create(parentDir,true); - } - else System.IO.Directory.CreateDirectory(dir); + var parentDir = dir.ParentDir(); + if(!Exists(parentDir)) + Create(parentDir); } + + System.IO.Directory.CreateDirectory(dir.Str); } /// копирует все файлы и папки - public static void Copy(string source_dir, string new_dir, bool owerwrite) + public static void Copy(IOPath sourceDir, IOPath newDir, bool owerwrite) { - Copy_internal(source_dir, new_dir, owerwrite, null); + Copy_internal(sourceDir, newDir, owerwrite, null); } /// копирует все файлы и папки и выдаёт список конфликтующих файлов - public static void Copy(string source_dir, string new_dir, bool owerwrite, out List conflicts) + public static void Copy(IOPath sourceDir, IOPath newDir, bool owerwrite, out List conflicts) { - conflicts = new List(); - Copy_internal(source_dir, new_dir, owerwrite, conflicts); + conflicts = new List(); + Copy_internal(sourceDir, newDir, owerwrite, conflicts); } - private static void Copy_internal(string source_dir, string new_dir, bool owerwrite, List conflicts) + private static void Copy_internal(IOPath sourceDir, IOPath newDir, bool owerwrite, List conflicts) { - bool countConflicts = conflicts is null; - List files = GetAllFiles(source_dir); - Create(new_dir); + bool countConflicts = conflicts is not null; + List files = GetAllFiles(sourceDir); + Create(newDir); for (int i = 0; i < files.Count; i++) { - string newfile = Path.ReplaceBase(files[i], source_dir, new_dir); + var newfile = files[i].ReplaceBase(sourceDir, newDir); if (countConflicts && File.Exists(newfile)) - conflicts!.Add(newfile); + conflicts.Add(newfile); File.Copy(files[i], newfile, owerwrite); } } /// удаляет папку со всеми подпапками и файлами - public static void Delete(string dir, bool separatorsFixed) - { - if(!separatorsFixed) - dir = Path.FixSeparators(dir); - System.IO.Directory.Delete(dir, true); - } + public static void Delete(IOPath dir) => + System.IO.Directory.Delete(dir.Str, true); - public static string[] GetFiles(string dir, bool separatorsFixed) - { - if (!separatorsFixed) - dir = Path.FixSeparators(dir); - return System.IO.Directory.GetFiles(dir); - } + public static IOPath[] GetFiles(IOPath dir) => + IOPath.ArrayCast(System.IO.Directory.GetFiles(dir.Str)); - public static string[] GetFiles(string dir, string searchPattern, bool separatorsFixed) - { - if (!separatorsFixed) - dir = Path.FixSeparators(dir); - return System.IO.Directory.GetFiles(dir, searchPattern); - } + public static IOPath[] GetFiles(IOPath dir, string searchPattern) => + IOPath.ArrayCast(System.IO.Directory.GetFiles(dir.Str, searchPattern)); - public static string[] GetDirectories(string dir, bool separatorsFixed) - { - if (!separatorsFixed) - dir = Path.FixSeparators(dir); - return System.IO.Directory.GetDirectories(dir); - } + public static IOPath[] GetDirectories(IOPath dir) => + IOPath.ArrayCast(System.IO.Directory.GetDirectories(dir.Str)); + + public static IOPath[] GetDirectories(IOPath dir, string searchPattern) => + IOPath.ArrayCast(System.IO.Directory.GetDirectories(dir.Str, searchPattern)); /// выдает список всех файлов - public static List GetAllFiles(string dir) + public static List GetAllFiles(IOPath dir) { - var all_files = new List(); - string[] cur_files = GetFiles(dir); - for (int i = 0; i < cur_files.Length; i++) - all_files.Add(cur_files[i]); - string[] cur_subdirs = GetDirectories(dir); - for (int i = 0; i < cur_subdirs.Length; i++) - all_files.AddRange(GetAllFiles(cur_subdirs[i])); - return all_files; + return GetAllFiles_internal(dir, null); } /// выдает список всех файлов и подпапок в папке - public static List GetAllFiles(string dir, ref List all_subdirs) + public static List GetAllFiles(IOPath dir, out List all_subdirs) { - var all_files = new List(); - string[] cur_files = GetFiles(dir); + all_subdirs = new List(); + return GetAllFiles_internal(dir, all_subdirs); + } + private static List GetAllFiles_internal(IOPath dir, List all_subdirs) + { + bool rememberSubdirs = all_subdirs is not null; + var all_files = new List(); + IOPath[] cur_files = GetFiles(dir); for (int i = 0; i < cur_files.Length; i++) all_files.Add(cur_files[i]); - string[] cur_subdirs = GetDirectories(dir); + IOPath[] cur_subdirs = GetDirectories(dir); for (int i = 0; i < cur_subdirs.Length; i++) { - all_subdirs.Add(cur_subdirs[i]); - all_files.AddRange(GetAllFiles(cur_subdirs[i], ref all_subdirs)); + if(rememberSubdirs) + all_subdirs.Add(cur_subdirs[i]); + all_files.AddRange(GetAllFiles_internal(cur_subdirs[i], all_subdirs)); } return all_files; } public static string GetCurrent() => System.IO.Directory.GetCurrentDirectory(); - public static void CreateSymlink(string sourcePath, string symlinkPath, bool separatorsFixed) + public static void CreateSymlink(string sourcePath, string symlinkPath) { - if (!separatorsFixed) - { - sourcePath = Path.FixSeparators(sourcePath); - symlinkPath = Path.FixSeparators(symlinkPath); - } - Create(Path.ParentDir(symlinkPath, true), true); + if (symlinkPath.Contains(Path.Sep)) + Create(Path.ParentDir(symlinkPath)); if (!Symlink.CreateSymbolicLink(symlinkPath, sourcePath, Symlink.SymlinkTarget.Directory)) throw new InvalidOperationException($"some error occured while creating symlink\nDirectory.CreateSymlink({symlinkPath}, {sourcePath})"); } diff --git a/DTLib/Filesystem/File.cs b/DTLib/Filesystem/File.cs index ad1a26c..6c2d118 100644 --- a/DTLib/Filesystem/File.cs +++ b/DTLib/Filesystem/File.cs @@ -3,60 +3,45 @@ public static class File { /// возвращает размер файла в байтах - public static long GetSize(string file, bool separatorsFixed) - { - if (!separatorsFixed) - file = Path.FixSeparators(file); - return new System.IO.FileInfo(file).Length; - } + public static long GetSize(IOPath file) => new System.IO.FileInfo(file.Str).Length; - public static bool Exists(string file, bool separatorsFixed) + public static bool Exists(IOPath file) { - if (!separatorsFixed) - file = Path.FixSeparators(file); - return System.IO.File.Exists(file); + if (System.IO.File.Exists(file.Str)) return true; + return false; } /// если файл не существует, создаёт файл с папками из его пути и закрывает этот фвйл - public static void Create(string file, bool separatorsFixed) + public static void Create(IOPath file) { - if (!separatorsFixed) - file = Path.FixSeparators(file); - if (!Exists(file, true)) - { - Directory.Create(Path.ParentDir(file, true), true); - using System.IO.FileStream stream = System.IO.File.Create(file); - stream.Close(); - } + if (Exists(file)) return; + + Directory.Create(file.ParentDir()); + using System.IO.FileStream stream = System.IO.File.Create(file.Str); + stream.Close(); } - public static void Copy(string srcPath, string newPath, bool overwrite = false, bool separatorsFixed) + public static void Copy(IOPath srcPath, IOPath newPath, bool overwrite) { - if (!separatorsFixed) - { - srcPath = Path.FixSeparators(srcPath); - newPath = Path.FixSeparators(newPath); - } - if (Exists(newPath)) { - if(overwrite) System.IO.File.Delete(newPath); + if(overwrite) System.IO.File.Delete(newPath.Str); else throw new Exception($"file <{newPath}> alredy exists"); } - else Directory.Create(Path.ParentDir(newPath, true)); - using var srcFile=System.IO.File.Open(srcPath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); - using var newFile=System.IO.File.Open(newPath, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); + else Directory.Create(newPath.ParentDir()); + using var srcFile=System.IO.File.Open(srcPath.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); + using var newFile=System.IO.File.Open(newPath.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); srcFile.CopyTo(newFile); srcFile.Close(); newFile.Flush(); newFile.Close(); } - public static void Delete(string file) => System.IO.File.Delete(Path.FixSeparators(file)); + public static void Delete(IOPath file) => System.IO.File.Delete(file.Str); - public static byte[] ReadAllBytes(string file) + public static byte[] ReadAllBytes(IOPath file) { - file = Path.FixSeparators(file); + using System.IO.FileStream stream = OpenRead(file); int size = GetSize(file).ToInt(); byte[] output = new byte[size]; @@ -66,58 +51,52 @@ public static class File return output; } - public static string ReadAllText(string file) => ReadAllBytes(file).BytesToString(StringConverter.UTF8); + public static string ReadAllText(IOPath file) => ReadAllBytes(file).BytesToString(StringConverter.UTF8); - public static void WriteAllBytes(string file, byte[] content) + public static void WriteAllBytes(IOPath file, byte[] content) { - file = Path.FixSeparators(file); using System.IO.FileStream stream = OpenWrite(file); stream.Write(content, 0, content.Length); stream.Close(); } - public static void WriteAllText(string file, string content) => WriteAllBytes(file, content.ToBytes(StringConverter.UTF8)); + public static void WriteAllText(IOPath file, string content) => WriteAllBytes(file, content.ToBytes(StringConverter.UTF8)); - public static void AppendAllBytes(string file, byte[] content) + public static void AppendAllBytes(IOPath file, byte[] content) { - file = Path.FixSeparators(file); using System.IO.FileStream stream = OpenAppend(file); stream.Write(content, 0, content.Length); stream.Close(); } - public static void AppendAllText(string file, string content) => AppendAllBytes(file, content.ToBytes(StringConverter.UTF8)); + public static void AppendAllText(IOPath file, string content) => AppendAllBytes(file, content.ToBytes(StringConverter.UTF8)); - public static System.IO.FileStream OpenRead(string file) + public static System.IO.FileStream OpenRead(IOPath file) { - file = Path.FixSeparators(file); - if (Exists(file)) - return System.IO.File.Open(file, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); - throw new Exception($"file not found: <{file}>"); + if (!Exists(file)) + throw new Exception($"file not found: <{file}>"); + return System.IO.File.Open(file.Str, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); } - public static System.IO.FileStream OpenWrite(string file) + public static System.IO.FileStream OpenWrite(IOPath file) { - file = Path.FixSeparators(file); if (Exists(file)) Delete(file); Create(file); - return System.IO.File.Open(file, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); + return System.IO.File.Open(file.Str, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); } - public static System.IO.FileStream OpenAppend(string file) + public static System.IO.FileStream OpenAppend(IOPath file) { - file = Path.FixSeparators(file); + Create(file); - return System.IO.File.Open(file, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); + return System.IO.File.Open(file.Str, System.IO.FileMode.Append, System.IO.FileAccess.Write, System.IO.FileShare.ReadWrite); } - public static void CreateSymlink(string sourceName, string symlinkName) + public static void CreateSymlink(IOPath sourcePath, IOPath symlinkPath) { - sourceName = Path.FixSeparators(sourceName); - symlinkName = Path.FixSeparators(symlinkName); - if (symlinkName.Contains(Path.Sep)) - Directory.Create(symlinkName.Remove(symlinkName.LastIndexOf(Path.Sep))); - if (!Symlink.CreateSymbolicLink(symlinkName, sourceName, Symlink.SymlinkTarget.File)) - throw new InvalidOperationException($"some error occured while creating symlink\nFile.CreateSymlink({symlinkName}, {sourceName})"); + if (symlinkPath.Contains(Path.Sep)) + Directory.Create(symlinkPath.ParentDir()); + if (!Symlink.CreateSymbolicLink(symlinkPath.Str, sourcePath.Str, Symlink.SymlinkTarget.File)) + throw new InvalidOperationException($"some error occured while creating symlink\nFile.CreateSymlink({symlinkPath}, {sourcePath})"); } } diff --git a/DTLib/Filesystem/IOPath.cs b/DTLib/Filesystem/IOPath.cs new file mode 100644 index 0000000..ed70edb --- /dev/null +++ b/DTLib/Filesystem/IOPath.cs @@ -0,0 +1,155 @@ +#if NETSTANDARD2_1 || NET6_0 || NET7_0 || NET8_0 + #define USE_SPAN +#endif + +using System.Runtime.CompilerServices; + +namespace DTLib.Filesystem; + +public readonly struct IOPath +{ + public readonly string Str; + + public IOPath(string path, bool separatorsFixed=false) + { + if (!separatorsFixed) + { + var chars = path.ToCharArray(); + for(int i=0; i a) + { + IOPath[] b = new IOPath[a.Count]; + for (int i = 0; i < a.Count; i++) + b[i] = (IOPath)a[i]; + return b; + } + + + // public static IOPath operator +(IOPath a, IOPath b) => Concat(a, b); + public static bool operator ==(IOPath a, IOPath b) => a.Str==b.Str; + public static bool operator !=(IOPath a, IOPath b) => a.Str!=b.Str; + public static implicit operator IOPath(string s) => new(s); + public static explicit operator string(IOPath p) => p.Str; + public override string ToString() => Str; + public override bool Equals(object obj) => Str.Equals(obj); + + public override int GetHashCode() => Str.GetHashCode(); + + public char this[int i] => Str[i]; + public int Length => Str.Length; + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public char[] ToCharArray() => Str.ToCharArray(); + +#if USE_SPAN + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan AsSpan() => Str.AsSpan(); +#endif + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(char c) => Str.Contains(c); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(string s) => Str.Contains(s); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(IOPath p) => Str.Contains(p.Str); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool StartsWith(char c) => Str.StartsWith(c); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool StartsWith(string s) => Str.StartsWith(s); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool StartsWith(IOPath p) => Str.StartsWith(p.Str); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool EndsWith(char c) => Str.EndsWith(c); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool EndsWith(string s) => Str.EndsWith(s); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool EndsWith(IOPath p) => Str.EndsWith(p.Str); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IOPath Remove(int startIndex) => new(Str.Remove(startIndex), true); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IOPath Remove(int startIndex, int count) => new(Str.Remove(startIndex, count), true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IOPath Substring(int startIndex) => new(Str.Substring(startIndex), true); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IOPath Substring(int startIndex, int count) => new(Str.Substring(startIndex, count), true); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IOPath Replace(char oldChar, char newChar) => new(Str.Replace(oldChar, newChar), true); + [Obsolete("use different replace methods")] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public IOPath Replace(string oldStr, string newStr) => new(Str.Replace(oldStr, newStr), true); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(char c) => Str.IndexOf(c); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(char c, int startIndex) => Str.IndexOf(c, startIndex); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(char c, int startIndex, int count) => Str.IndexOf(c, startIndex, count); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(string s) => Str.IndexOf(s, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(string s, int startIndex) => Str.IndexOf(s, startIndex, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(string s, int startIndex, int count) => Str.IndexOf(s, startIndex, count, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(IOPath p) => Str.IndexOf(p.Str, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(IOPath p, int startIndex) => Str.IndexOf(p.Str, startIndex, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(IOPath p, int startIndex, int count) => Str.IndexOf(p.Str, startIndex, count, StringComparison.Ordinal); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(char c) => Str.LastIndexOf(c); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(char c, int startLastIndex) => Str.LastIndexOf(c, startLastIndex); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(char c, int startLastIndex, int count) => Str.LastIndexOf(c, startLastIndex, count); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(string s) => Str.LastIndexOf(s, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(string s, int startLastIndex) => Str.LastIndexOf(s, startLastIndex, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(string s, int startLastIndex, int count) => Str.LastIndexOf(s, startLastIndex, count, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(IOPath p) => Str.LastIndexOf(p.Str, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(IOPath p, int startLastIndex) => Str.LastIndexOf(p.Str, startLastIndex, StringComparison.Ordinal); + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int LastIndexOf(IOPath p, int startLastIndex, int count) => Str.LastIndexOf(p.Str, startLastIndex, count, StringComparison.Ordinal); +} \ No newline at end of file diff --git a/DTLib/Filesystem/Path.cs b/DTLib/Filesystem/Path.cs index 9fa8620..e476998 100644 --- a/DTLib/Filesystem/Path.cs +++ b/DTLib/Filesystem/Path.cs @@ -1,73 +1,32 @@ +#if NETSTANDARD2_1 || NET6_0 || NET7_0 || NET8_0 + #define USE_SPAN +#endif + using System.Runtime.CompilerServices; namespace DTLib.Filesystem; public static class Path { - public static readonly char Sep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '\\' : '/'; - private static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\' ; + public static readonly char NotSep = Environment.OSVersion.Platform == PlatformID.Win32NT ? '/' : '\\' ; - /// does not correct separators, use Resolve for correction - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Concat(string path, string addition) - { - if (!path.EndsWith(Sep) && !addition.StartsWith(Sep)) - path += Sep; - return path + addition; - } - - /// - public static string Concat(params string[] parts) - { - StringBuilder builder = new StringBuilder(); - builder.Append(parts[0]); - for (int i = 1; i < parts.Length; i++) - { - char lastC = builder[builder.Length - 1]; - if(lastC!=Sep && lastC!=NotSep) - builder.Append(Sep); - builder.Append(parts[i]); - } - return builder.ToString(); - } - - public static string FixSeparators(string path) - { - var chars = path.ToCharArray(); - int length = path.Length; - for(int i=0; i FixSeparators(Concat(parts)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string Resolve(string path, string addition) => FixSeparators(Concat(path, addition)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfEscapes(string path) + public static void ThrowIfEscapes(this IOPath path) { - if (path.Contains("..")) + if (path.Str.Contains("..")) throw new Exception($"path <{path}> uses <..>, that's not allowed"); } - + /// Replaces restricted characters in string - public static string CorrectString(string str) + public static IOPath ReplaceRestrictedChars(string str) { -#if NETSTANDARD2_1 || NET6_0 || NET7_0 || NET8_0 - var a = str.AsSpan(); -#else - var a = str.ToArray(); -#endif - char[] r = new char[a.Length]; - for (int i = 0; i < a.Length; i++) + char[] r = str.ToCharArray(); + + for (int i = 0; i < str.Length; i++) { - switch (a[i]) + switch (r[i]) { case '/': case '\\': case ':': case ';': @@ -82,44 +41,93 @@ public static class Path case '`': case '|': case '=': r[i] = '_'; break; - default: - r[i] = a[i]; - break; } } - return new string(r); + return new IOPath(r); } - public static string FileName(string path, bool separatorsFixed) +#if !USE_SPAN + private static unsafe void CopyTo(this string s, char* b) + { + for (int i = 0; i < s.Length; i++) + b[i] = s[i]; + } +#endif + + public static IOPath Concat(params IOPath[] parts) + { +#if USE_SPAN + Span +#else + unsafe + { + bool* +#endif + needSeparator = stackalloc bool[parts.Length-1]; + int lengthSum = 0; + for (int i = 0; i < parts.Length-1; i++) + { + lengthSum += parts[i].Length; + if (!parts[i].Str.EndsWith(Sep) && !parts[i + 1].Str.StartsWith(Sep)) + { + needSeparator[i] = true; + lengthSum++; + } + else needSeparator[i] = false; + } + lengthSum += parts[parts.Length-1].Length; +#if USE_SPAN + Span +#else + char* +#endif + buffer = stackalloc char[lengthSum]; + parts[0].Str.CopyTo(buffer); + int copiedChars = parts[0].Length; + for (int i = 1; i < parts.Length; i++) + { + if (needSeparator[i-1]) + buffer[copiedChars++] = Sep; +#if USE_SPAN + parts[i].Str.CopyTo(buffer.Slice(copiedChars)); +#else + parts[i].Str.CopyTo(buffer+copiedChars); +#endif + copiedChars += parts[i].Length; + } + + return new IOPath(new string(buffer), true); +#if !USE_SPAN + } +#endif + } + + public static IOPath FileName(this IOPath path) { - if(!separatorsFixed) - path = FixSeparators(path); int i = path.LastIndexOf(Sep); if (i == -1) return path; return path.Substring(i+1); } - public static string Extension(string path) + public static IOPath Extension(this IOPath path) { int i = path.LastIndexOf('.'); if (i == -1) return FileName(path); return path.Substring(i + 1); } - public static string ParentDir(string path, bool separatorsFixed) + public static IOPath ParentDir(this IOPath path) { - if(!separatorsFixed) - path = FixSeparators(path); int i = path.LastIndexOf(Sep); if (i == path.Length - 1) // ends with separator - i = path.LastIndexOf(Sep, 0, i); + i = path.LastIndexOf(Sep, i-1); if (i == -1) // no parent dir return $".{Sep}"; - return path.Substring(0, i); + return path.Remove(i+1); } - public static string ReplaceBase(string path, string baseDir, string otherDir) + public static IOPath ReplaceBase(this IOPath path, IOPath baseDir, IOPath otherDir) { if (!path.StartsWith(baseDir)) throw new Exception($"path <{path}> doesnt starts with <{baseDir}");