Compare commits
	
		
			9 Commits
		
	
	
		
			35f0ba71ab
			...
			0.1.0-expe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6cc31f660c | |||
| 2d79e18b25 | |||
| 1d5a61f308 | |||
| 38e1c3f8b7 | |||
| 8d3fa1ac98 | |||
| 88e330336a | |||
| eca0d90d98 | |||
| d8adf47d10 | |||
| 8a834a9c05 | 
							
								
								
									
										462
									
								
								Redcraft.Utility/Source/Private/Miscellaneous/Console.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										462
									
								
								Redcraft.Utility/Source/Private/Miscellaneous/Console.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,462 @@ | |||||||
|  | #include "Miscellaneous/Console.h" | ||||||
|  |  | ||||||
|  | #include <cstdio> | ||||||
|  |  | ||||||
|  | #if PLATFORM_WINDOWS | ||||||
|  | #	undef TEXT | ||||||
|  | #	include <windows.h> | ||||||
|  | #elif PLATFORM_LINUX | ||||||
|  | #	include <sys/ioctl.h> | ||||||
|  | #	include <unistd.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | NAMESPACE_REDCRAFT_BEGIN | ||||||
|  | NAMESPACE_MODULE_BEGIN(Redcraft) | ||||||
|  | NAMESPACE_MODULE_BEGIN(Utility) | ||||||
|  |  | ||||||
|  | #if PLATFORM_WINDOWS | ||||||
|  |  | ||||||
|  | NODISCARD bool InitANSIConsole() | ||||||
|  | { | ||||||
|  | 	static bool bResult = [] | ||||||
|  | 	{ | ||||||
|  | 		HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return false; | ||||||
|  |  | ||||||
|  | 		DWORD ConsoleMode = 0; | ||||||
|  |  | ||||||
|  | 		if (!GetConsoleMode(Console, &ConsoleMode)) return false; | ||||||
|  |  | ||||||
|  | 		ConsoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; | ||||||
|  |  | ||||||
|  | 		if (!SetConsoleMode(Console, ConsoleMode)) return false; | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | 	(); | ||||||
|  |  | ||||||
|  | 	return bResult; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | EColor GForegroundColor = EColor::Default; | ||||||
|  | EColor GBackgroundColor = EColor::Default; | ||||||
|  |  | ||||||
|  | EColor GetForegroundColor() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		if (InitANSIConsole()) return GForegroundColor; | ||||||
|  |  | ||||||
|  | 		const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return EColor::Default; | ||||||
|  |  | ||||||
|  | 		CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; | ||||||
|  |  | ||||||
|  | 		if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return EColor::Default; | ||||||
|  |  | ||||||
|  | 		const WORD Color = ConsoleInfo.wAttributes; | ||||||
|  |  | ||||||
|  | 		EColor Result = EColor::Black; | ||||||
|  |  | ||||||
|  | 		if (Color & FOREGROUND_RED)       Result |= EColor::Red; | ||||||
|  | 		if (Color & FOREGROUND_GREEN)     Result |= EColor::Green; | ||||||
|  | 		if (Color & FOREGROUND_BLUE)      Result |= EColor::Blue; | ||||||
|  | 		if (Color & FOREGROUND_INTENSITY) Result |= EColor::Intensity; | ||||||
|  |  | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return GForegroundColor; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | EColor GetBackgroundColor() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		if (InitANSIConsole()) return GBackgroundColor; | ||||||
|  |  | ||||||
|  | 		const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return EColor::Default; | ||||||
|  |  | ||||||
|  | 		CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; | ||||||
|  |  | ||||||
|  | 		if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return EColor::Default; | ||||||
|  |  | ||||||
|  | 		const WORD Color = ConsoleInfo.wAttributes; | ||||||
|  |  | ||||||
|  | 		EColor Result = EColor::Black; | ||||||
|  |  | ||||||
|  | 		if (Color & BACKGROUND_RED)       Result |= EColor::Red; | ||||||
|  | 		if (Color & BACKGROUND_GREEN)     Result |= EColor::Green; | ||||||
|  | 		if (Color & BACKGROUND_BLUE)      Result |= EColor::Blue; | ||||||
|  | 		if (Color & BACKGROUND_INTENSITY) Result |= EColor::Intensity; | ||||||
|  |  | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return GBackgroundColor; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | EColor SetForegroundColor(EColor InColor) | ||||||
|  | { | ||||||
|  | 	if (IsOutputRedirected()) return GetForegroundColor(); | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		if (!InitANSIConsole()) | ||||||
|  | 		{ | ||||||
|  | 			const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 			if (Console == INVALID_HANDLE_VALUE) return GetForegroundColor(); | ||||||
|  |  | ||||||
|  | 			CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; | ||||||
|  |  | ||||||
|  | 			if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return GetForegroundColor(); | ||||||
|  |  | ||||||
|  | 			WORD Color = ConsoleInfo.wAttributes & ~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY); | ||||||
|  |  | ||||||
|  | 			if (InColor == EColor::Default) InColor = EColor::White; | ||||||
|  |  | ||||||
|  | 			if (!!(InColor & EColor::Red))       Color |= FOREGROUND_RED; | ||||||
|  | 			if (!!(InColor & EColor::Green))     Color |= FOREGROUND_GREEN; | ||||||
|  | 			if (!!(InColor & EColor::Blue))      Color |= FOREGROUND_BLUE; | ||||||
|  | 			if (!!(InColor & EColor::Intensity)) Color |= FOREGROUND_INTENSITY; | ||||||
|  |  | ||||||
|  | 			if (!SetConsoleTextAttribute(Console, Color)) return GetForegroundColor(); | ||||||
|  |  | ||||||
|  | 			return InColor; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS || PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		int Result; | ||||||
|  |  | ||||||
|  | 		switch (InColor) | ||||||
|  | 		{ | ||||||
|  | 		case EColor::Black:         Result = std::fputs("\033[30m", stdout); break; | ||||||
|  | 		case EColor::Red:           Result = std::fputs("\033[31m", stdout); break; | ||||||
|  | 		case EColor::Green:         Result = std::fputs("\033[32m", stdout); break; | ||||||
|  | 		case EColor::Yellow:        Result = std::fputs("\033[33m", stdout); break; | ||||||
|  | 		case EColor::Blue:          Result = std::fputs("\033[34m", stdout); break; | ||||||
|  | 		case EColor::Magenta:       Result = std::fputs("\033[35m", stdout); break; | ||||||
|  | 		case EColor::Cyan:          Result = std::fputs("\033[36m", stdout); break; | ||||||
|  | 		case EColor::White:         Result = std::fputs("\033[37m", stdout); break; | ||||||
|  | 		case EColor::BrightBlack:   Result = std::fputs("\033[90m", stdout); break; | ||||||
|  | 		case EColor::BrightRed:     Result = std::fputs("\033[91m", stdout); break; | ||||||
|  | 		case EColor::BrightGreen:   Result = std::fputs("\033[92m", stdout); break; | ||||||
|  | 		case EColor::BrightYellow:  Result = std::fputs("\033[93m", stdout); break; | ||||||
|  | 		case EColor::BrightBlue:    Result = std::fputs("\033[94m", stdout); break; | ||||||
|  | 		case EColor::BrightMagenta: Result = std::fputs("\033[95m", stdout); break; | ||||||
|  | 		case EColor::BrightCyan:    Result = std::fputs("\033[96m", stdout); break; | ||||||
|  | 		case EColor::BrightWhite:   Result = std::fputs("\033[97m", stdout); break; | ||||||
|  | 		default:                    Result = std::fputs("\033[39m", stdout); break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (Result == EOF) return GetForegroundColor(); | ||||||
|  |  | ||||||
|  | 		return GForegroundColor = InColor; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return GetForegroundColor(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | EColor SetBackgroundColor(EColor InColor) | ||||||
|  | { | ||||||
|  | 	if (IsOutputRedirected()) return GetBackgroundColor(); | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		if (!InitANSIConsole()) | ||||||
|  | 		{ | ||||||
|  | 			const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 			if (Console == INVALID_HANDLE_VALUE) return GetBackgroundColor(); | ||||||
|  |  | ||||||
|  | 			CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; | ||||||
|  |  | ||||||
|  | 			if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return GetBackgroundColor(); | ||||||
|  |  | ||||||
|  | 			WORD Color = ConsoleInfo.wAttributes & ~(BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY); | ||||||
|  |  | ||||||
|  | 			if (InColor == EColor::Default) InColor = EColor::Black; | ||||||
|  |  | ||||||
|  | 			if (!!(InColor & EColor::Red))       Color |= BACKGROUND_RED; | ||||||
|  | 			if (!!(InColor & EColor::Green))     Color |= BACKGROUND_GREEN; | ||||||
|  | 			if (!!(InColor & EColor::Blue))      Color |= BACKGROUND_BLUE; | ||||||
|  | 			if (!!(InColor & EColor::Intensity)) Color |= BACKGROUND_INTENSITY; | ||||||
|  |  | ||||||
|  | 			if (!SetConsoleTextAttribute(Console, Color)) return GetBackgroundColor(); | ||||||
|  |  | ||||||
|  | 			return InColor; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS || PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		int Result; | ||||||
|  |  | ||||||
|  | 		switch (InColor) | ||||||
|  | 		{ | ||||||
|  | 		case EColor::Black:         Result = std::fputs("\033[40m",  stdout); break; | ||||||
|  | 		case EColor::Red:           Result = std::fputs("\033[41m",  stdout); break; | ||||||
|  | 		case EColor::Green:         Result = std::fputs("\033[42m",  stdout); break; | ||||||
|  | 		case EColor::Yellow:        Result = std::fputs("\033[43m",  stdout); break; | ||||||
|  | 		case EColor::Blue:          Result = std::fputs("\033[44m",  stdout); break; | ||||||
|  | 		case EColor::Magenta:       Result = std::fputs("\033[45m",  stdout); break; | ||||||
|  | 		case EColor::Cyan:          Result = std::fputs("\033[46m",  stdout); break; | ||||||
|  | 		case EColor::White:         Result = std::fputs("\033[47m",  stdout); break; | ||||||
|  | 		case EColor::BrightBlack:   Result = std::fputs("\033[100m", stdout); break; | ||||||
|  | 		case EColor::BrightRed:     Result = std::fputs("\033[101m", stdout); break; | ||||||
|  | 		case EColor::BrightGreen:   Result = std::fputs("\033[102m", stdout); break; | ||||||
|  | 		case EColor::BrightYellow:  Result = std::fputs("\033[103m", stdout); break; | ||||||
|  | 		case EColor::BrightBlue:    Result = std::fputs("\033[104m", stdout); break; | ||||||
|  | 		case EColor::BrightMagenta: Result = std::fputs("\033[105m", stdout); break; | ||||||
|  | 		case EColor::BrightCyan:    Result = std::fputs("\033[106m", stdout); break; | ||||||
|  | 		case EColor::BrightWhite:   Result = std::fputs("\033[107m", stdout); break; | ||||||
|  | 		default:                    Result = std::fputs("\033[49m",  stdout); break; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (Result == EOF) return GetBackgroundColor(); | ||||||
|  |  | ||||||
|  | 		return GBackgroundColor = InColor; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return GetBackgroundColor(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint GetWindowWidth() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return static_cast<uint>(-1); | ||||||
|  |  | ||||||
|  | 		CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; | ||||||
|  |  | ||||||
|  | 		if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return static_cast<uint>(-1); | ||||||
|  |  | ||||||
|  | 		return static_cast<uint>(ConsoleInfo.srWindow.Right - ConsoleInfo.srWindow.Left + 1); | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		winsize Size; | ||||||
|  |  | ||||||
|  | 		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &Size) == -1) return static_cast<uint>(-1); | ||||||
|  |  | ||||||
|  | 		return static_cast<uint>(Size.ws_col); | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return static_cast<uint>(-1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | uint GetWindowHeight() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE Console = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return static_cast<uint>(-1); | ||||||
|  |  | ||||||
|  | 		CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo; | ||||||
|  |  | ||||||
|  | 		if (!GetConsoleScreenBufferInfo(Console, &ConsoleInfo)) return static_cast<uint>(-1); | ||||||
|  |  | ||||||
|  | 		return static_cast<uint>(ConsoleInfo.srWindow.Bottom - ConsoleInfo.srWindow.Top + 1); | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		winsize Size; | ||||||
|  |  | ||||||
|  | 		if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &Size) == -1) return static_cast<uint>(-1); | ||||||
|  |  | ||||||
|  | 		return static_cast<uint>(Size.ws_row); | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return static_cast<uint>(-1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool IsInputRedirected() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE StandardInput = GetStdHandle(STD_INPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (StandardInput == INVALID_HANDLE_VALUE) return false; | ||||||
|  |  | ||||||
|  | 		DWORD FileType = GetFileType(StandardInput); | ||||||
|  |  | ||||||
|  | 		return FileType != FILE_TYPE_CHAR; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		return isatty(fileno(stdin)) == 0; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool IsOutputRedirected() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE StandardOutput = GetStdHandle(STD_OUTPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (StandardOutput == INVALID_HANDLE_VALUE) return false; | ||||||
|  |  | ||||||
|  | 		DWORD FileType = GetFileType(StandardOutput); | ||||||
|  |  | ||||||
|  | 		return FileType != FILE_TYPE_CHAR; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		return isatty(fileno(stdout)) == 0; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool IsErrorRedirected() | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE StandardError = GetStdHandle(STD_ERROR_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (StandardError == INVALID_HANDLE_VALUE) return false; | ||||||
|  |  | ||||||
|  | 		DWORD FileType = GetFileType(StandardError); | ||||||
|  |  | ||||||
|  | 		return FileType != FILE_TYPE_CHAR; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		return isatty(fileno(stderr)) == 0; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Clear() | ||||||
|  | { | ||||||
|  | 	if (IsOutputRedirected()) return; | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		std::system("cls"); | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		Ignore = std::fputs("\033[2J\033[1;1H", stdout); | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | char Input(bool bEcho) | ||||||
|  | { | ||||||
|  | 	if (bEcho || IsOutputRedirected()) | ||||||
|  | 	{ | ||||||
|  | 		const int Result = std::getchar(); | ||||||
|  |  | ||||||
|  | 		if (Result == EOF) return static_cast<char>(-1); | ||||||
|  |  | ||||||
|  | 		return static_cast<char>(Result); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE Console = GetStdHandle(STD_INPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return static_cast<char>(-1); | ||||||
|  |  | ||||||
|  | 		DWORD ConsoleMode = 0; | ||||||
|  |  | ||||||
|  | 		GetConsoleMode(Console, &ConsoleMode); | ||||||
|  |  | ||||||
|  | 		SetConsoleMode(Console, ConsoleMode & ~ENABLE_ECHO_INPUT); | ||||||
|  |  | ||||||
|  | 		const char Result = Input(); | ||||||
|  |  | ||||||
|  | 		SetConsoleMode(Console, ConsoleMode); | ||||||
|  |  | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ } | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return static_cast<char>(-1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | FString InputLn(bool bEcho) | ||||||
|  | { | ||||||
|  | 	if (bEcho || IsOutputRedirected()) | ||||||
|  | 	{ | ||||||
|  | 		FString Result; | ||||||
|  |  | ||||||
|  | 		while (true) | ||||||
|  | 		{ | ||||||
|  | 			const int Char = std::getchar(); | ||||||
|  |  | ||||||
|  | 			if (Char == EOF || Char == '\n') break; | ||||||
|  |  | ||||||
|  | 			Result.PushBack(static_cast<char>(Char)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		const HANDLE Console = GetStdHandle(STD_INPUT_HANDLE); | ||||||
|  |  | ||||||
|  | 		if (Console == INVALID_HANDLE_VALUE) return ""; | ||||||
|  |  | ||||||
|  | 		DWORD ConsoleMode = 0; | ||||||
|  |  | ||||||
|  | 		GetConsoleMode(Console, &ConsoleMode); | ||||||
|  |  | ||||||
|  | 		SetConsoleMode(Console, ConsoleMode & ~ENABLE_ECHO_INPUT); | ||||||
|  |  | ||||||
|  | 		const FString Result = InputLn(); | ||||||
|  |  | ||||||
|  | 		SetConsoleMode(Console, ConsoleMode); | ||||||
|  |  | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ } | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return ""; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Print(char Char) | ||||||
|  | { | ||||||
|  | 	return std::putchar(Char) != EOF; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Error(char Char) | ||||||
|  | { | ||||||
|  | 	return std::fputc(Char, stderr) != EOF; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | NAMESPACE_MODULE_END(Utility) | ||||||
|  | NAMESPACE_MODULE_END(Redcraft) | ||||||
|  | NAMESPACE_REDCRAFT_END | ||||||
							
								
								
									
										596
									
								
								Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										596
									
								
								Redcraft.Utility/Source/Private/Miscellaneous/FileSystem.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,596 @@ | |||||||
|  | #include <Miscellaneous/FileSystem.h> | ||||||
|  |  | ||||||
|  | #include "Numerics/Bit.h" | ||||||
|  | #include "Numerics/Math.h" | ||||||
|  | #include "Templates/ScopeHelper.h" | ||||||
|  | #include "Containers/StaticArray.h" | ||||||
|  |  | ||||||
|  | #include <cstdio> | ||||||
|  |  | ||||||
|  | #if PLATFORM_WINDOWS | ||||||
|  | #	undef TEXT | ||||||
|  | #	include <windows.h> | ||||||
|  | #	undef CreateDirectory | ||||||
|  | #elif PLATFORM_LINUX | ||||||
|  | #	include <unistd.h> | ||||||
|  | #	include <dirent.h> | ||||||
|  | #	include <sys/stat.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #pragma warning(push) | ||||||
|  | #pragma warning(disable: 4996) | ||||||
|  |  | ||||||
|  | NAMESPACE_REDCRAFT_BEGIN | ||||||
|  | NAMESPACE_MODULE_BEGIN(Redcraft) | ||||||
|  | NAMESPACE_MODULE_BEGIN(Utility) | ||||||
|  |  | ||||||
|  | NAMESPACE_BEGIN(FileSystem) | ||||||
|  |  | ||||||
|  | bool LoadFileToArray(TArray<uint8>& Result, FStringView Path) | ||||||
|  | { | ||||||
|  | 	if (!FileSystem::Exists(Path)) return false; | ||||||
|  |  | ||||||
|  | 	FILE* File = std::fopen(*Path, "rb"); | ||||||
|  |  | ||||||
|  | 	if (File == nullptr) return false; | ||||||
|  |  | ||||||
|  | 	auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); | ||||||
|  |  | ||||||
|  | 	if (std::fseek(File, 0, SEEK_END) != 0) return false; | ||||||
|  |  | ||||||
|  | 	const long Length = std::ftell(File); | ||||||
|  |  | ||||||
|  | 	if (!Math::IsWithin(Length, 0, TNumericLimits<long>::Max())) return false; | ||||||
|  |  | ||||||
|  | 	if (std::fseek(File, 0, SEEK_SET) != 0) return false; | ||||||
|  |  | ||||||
|  | 	Result.SetNum(Length); | ||||||
|  |  | ||||||
|  | 	if (std::fread(Result.GetData(), sizeof(uint8), Length, File) != static_cast<size_t>(Length)) return false; | ||||||
|  |  | ||||||
|  | 	FileGuard.Release(); | ||||||
|  |  | ||||||
|  | 	if (std::fclose(File) != 0) return false; | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SaveArrayToFile(TArrayView<const uint8> Data, FStringView Path) | ||||||
|  | { | ||||||
|  | 	FILE* File = std::fopen(*Path, "wb"); | ||||||
|  |  | ||||||
|  | 	if (File == nullptr) return false; | ||||||
|  |  | ||||||
|  | 	auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); | ||||||
|  |  | ||||||
|  | 	if (std::fwrite(Data.GetData(), sizeof(uint8), Data.Num(), File) != Data.Num()) return false; | ||||||
|  |  | ||||||
|  | 	FileGuard.Release(); | ||||||
|  |  | ||||||
|  | 	if (std::fclose(File) != 0) return false; | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <CCharType T> | ||||||
|  | bool LoadFileToString(TString<T>& Result, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bVerify /* = false */) | ||||||
|  | { | ||||||
|  | 	if (!FileSystem::Exists(Path)) return false; | ||||||
|  |  | ||||||
|  | 	FILE* File = std::fopen(*Path, "rb"); | ||||||
|  |  | ||||||
|  | 	if (File == nullptr) return false; | ||||||
|  |  | ||||||
|  | 	auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); | ||||||
|  |  | ||||||
|  | 	if (std::fseek(File, 0, SEEK_END) != 0) return false; | ||||||
|  |  | ||||||
|  | 	long Length = std::ftell(File); | ||||||
|  |  | ||||||
|  | 	if (!Math::IsWithin(Length, 0, TNumericLimits<long>::Max())) return false; | ||||||
|  |  | ||||||
|  | 	if (std::fseek(File, 0, SEEK_SET) != 0) return false; | ||||||
|  |  | ||||||
|  | 	TStaticArray<uint8, 4> Buffer = { 0xAA, 0xAA, 0xAA, 0xAA }; | ||||||
|  |  | ||||||
|  | 	Ignore = std::fread(Buffer.GetData(), sizeof(uint8), Buffer.Num(), File); | ||||||
|  |  | ||||||
|  | 	// Auto-detect the encoding if it is not specified. | ||||||
|  | 	if (Encoding == FileSystem::EEncoding::Default) | ||||||
|  | 	{ | ||||||
|  | 		// Check if the file is a UTF-32 encoded file. | ||||||
|  | 		if      (Buffer[0] == 0x00 && Buffer[1] == 0x00 && Buffer[2] == 0xFE && Buffer[3] == 0xFF) Encoding = FileSystem::EEncoding::UTF32BE; | ||||||
|  | 		else if (Buffer[0] == 0xFF && Buffer[1] == 0xFE && Buffer[2] == 0x00 && Buffer[3] == 0x00) Encoding = FileSystem::EEncoding::UTF32LE; | ||||||
|  |  | ||||||
|  | 		// Check if the file is a UTF-16 encoded file. | ||||||
|  | 		else if (Buffer[0] == 0xFF && Buffer[1] == 0xFE) Encoding = FileSystem::EEncoding::UTF16LE; | ||||||
|  | 		else if (Buffer[0] == 0xFE && Buffer[1] == 0xFF) Encoding = FileSystem::EEncoding::UTF16BE; | ||||||
|  |  | ||||||
|  | 		// Check if the file is a UTF-8 encoded file. | ||||||
|  | 		else if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF) Encoding = FileSystem::EEncoding::UTF8; | ||||||
|  |  | ||||||
|  | 		// Check if the file is a wide character encoded file. | ||||||
|  | 		else if (Buffer[0] == 0x00 || Buffer[1] == 0x00 || Buffer[2] == 0x00 || Buffer[3] == 0x00) Encoding = FileSystem::EEncoding::Wide; | ||||||
|  |  | ||||||
|  | 		// Check if the file is a narrow character encoded file. | ||||||
|  | 		else Encoding = FileSystem::EEncoding::Narrow; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Jump to the BOM character if the file is a UTF-8, UTF-16 or UTF-32 encoded file. | ||||||
|  | 	switch (Encoding) | ||||||
|  | 	{ | ||||||
|  | 	case FileSystem::EEncoding::Narrow: | ||||||
|  | 	case FileSystem::EEncoding::Wide:                                                                                          { Length -= 0; if (std::fseek(File, 0, SEEK_SET) != 0) return false; } break; | ||||||
|  | 	case FileSystem::EEncoding::UTF8:    if (Buffer[0] == 0xEF && Buffer[1] == 0xBB && Buffer[2] == 0xBF)                      { Length -= 3; if (std::fseek(File, 3, SEEK_SET) != 0) return false; } break; | ||||||
|  | 	case FileSystem::EEncoding::UTF16BE: if (Buffer[0] == 0xFE && Buffer[1] == 0xFF)                                           { Length -= 2; if (std::fseek(File, 2, SEEK_SET) != 0) return false; } break; | ||||||
|  | 	case FileSystem::EEncoding::UTF16LE: if (Buffer[0] == 0xFF && Buffer[1] == 0xFE)                                           { Length -= 2; if (std::fseek(File, 2, SEEK_SET) != 0) return false; } break; | ||||||
|  | 	case FileSystem::EEncoding::UTF32BE: if (Buffer[0] == 0x00 && Buffer[1] == 0x00 && Buffer[2] == 0xFE && Buffer[3] == 0xFF) { Length -= 4; if (std::fseek(File, 4, SEEK_SET) != 0) return false; } break; | ||||||
|  | 	case FileSystem::EEncoding::UTF32LE: if (Buffer[0] == 0xFF && Buffer[1] == 0xFE && Buffer[2] == 0x00 && Buffer[3] == 0x00) { Length -= 4; if (std::fseek(File, 4, SEEK_SET) != 0) return false; } break; | ||||||
|  | 	default: check_no_entry(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	check(Math::EEndian::Native == Math::EEndian::Big || Math::EEndian::Native == Math::EEndian::Little); | ||||||
|  |  | ||||||
|  | 	const bool bByteSwap = | ||||||
|  | 		Math::EEndian::Native == Math::EEndian::Big    ? Encoding == FileSystem::EEncoding::UTF16LE || Encoding == FileSystem::EEncoding::UTF32LE : | ||||||
|  | 		Math::EEndian::Native == Math::EEndian::Little ? Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF32BE : false; | ||||||
|  |  | ||||||
|  | 	const auto LoadImpl = [File, Length, bByteSwap]<typename U>(TString<U>& String) -> bool | ||||||
|  | 	{ | ||||||
|  | 		if (Length % sizeof(U) != 0) return false; | ||||||
|  |  | ||||||
|  | 		String.Reserve(Length / sizeof(U)); | ||||||
|  |  | ||||||
|  | 		while (true) | ||||||
|  | 		{ | ||||||
|  | 			U Char; | ||||||
|  |  | ||||||
|  | 			const size_t ReadNum = std::fread(&Char, 1, sizeof(U), File); | ||||||
|  |  | ||||||
|  | 			if (ReadNum == 0) break; | ||||||
|  |  | ||||||
|  | 			if (ReadNum != sizeof(U)) return false; | ||||||
|  |  | ||||||
|  | 			if (bByteSwap) Char = Math::ByteSwap(static_cast<TMakeUnsigned<U>>(Char)); | ||||||
|  |  | ||||||
|  | #			if PLATFORM_WINDOWS | ||||||
|  | 			{ | ||||||
|  | 				if (!String.IsEmpty() && String.Back() == LITERAL(U, '\r') && Char == LITERAL(U, '\n')) | ||||||
|  | 				{ | ||||||
|  | 					String.PopBack(); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | #			endif | ||||||
|  |  | ||||||
|  | 			String.PushBack(Char); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	bool bCompatible = false; | ||||||
|  |  | ||||||
|  | 	if      constexpr (CSameAs<T, char>)    bCompatible |= Encoding == FileSystem::EEncoding::Narrow; | ||||||
|  | 	else if constexpr (CSameAs<T, wchar>)   bCompatible |= Encoding == FileSystem::EEncoding::Wide; | ||||||
|  | 	else if constexpr (CSameAs<T, u8char>)  bCompatible |= Encoding == FileSystem::EEncoding::UTF8; | ||||||
|  | 	else if constexpr (CSameAs<T, u16char>) bCompatible |= Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF16LE; | ||||||
|  | 	else if constexpr (CSameAs<T, u32char>) bCompatible |= Encoding == FileSystem::EEncoding::UTF32BE || Encoding == FileSystem::EEncoding::UTF32LE; | ||||||
|  |  | ||||||
|  | 	else static_assert(sizeof(T) == -1, "Unsupported character type"); | ||||||
|  |  | ||||||
|  | 	if (!bCompatible || bVerify) | ||||||
|  | 	{ | ||||||
|  | 		switch (Encoding) | ||||||
|  | 		{ | ||||||
|  | 		case FileSystem::EEncoding::Narrow:  { FString    Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } | ||||||
|  | 		case FileSystem::EEncoding::Wide:    { FWString   Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } | ||||||
|  | 		case FileSystem::EEncoding::UTF8:    { FU8String  Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } | ||||||
|  | 		case FileSystem::EEncoding::UTF16BE: | ||||||
|  | 		case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } | ||||||
|  | 		case FileSystem::EEncoding::UTF32BE: | ||||||
|  | 		case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!LoadImpl(Temp)) return false; if (!Result.DecodeFrom(Temp)) return false; break; } | ||||||
|  | 		default: check_no_entry(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	else if (!LoadImpl(Result)) return false; | ||||||
|  |  | ||||||
|  | 	FileGuard.Release(); | ||||||
|  |  | ||||||
|  | 	if (std::fclose(File) != 0) return false; | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template REDCRAFTUTILITY_API bool LoadFileToString<char>   (FString&,    FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool LoadFileToString<wchar>  (FWString&,   FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool LoadFileToString<u8char> (FU8String&,  FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool LoadFileToString<u16char>(FU16String&, FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool LoadFileToString<u32char>(FU32String&, FStringView, FileSystem::EEncoding, bool); | ||||||
|  |  | ||||||
|  | template <CCharType T> | ||||||
|  | bool SaveStringToFile(TStringView<T> String, FStringView Path, FileSystem::EEncoding Encoding /* = FileSystem::EEncoding::Default */, bool bWithBOM /* = true */) | ||||||
|  | { | ||||||
|  | 	bool bCompatible = Encoding == FileSystem::EEncoding::Default; | ||||||
|  |  | ||||||
|  | 	if      constexpr (CSameAs<T, char>)    bCompatible |= Encoding == FileSystem::EEncoding::Narrow; | ||||||
|  | 	else if constexpr (CSameAs<T, wchar>)   bCompatible |= Encoding == FileSystem::EEncoding::Wide; | ||||||
|  | 	else if constexpr (CSameAs<T, u8char>)  bCompatible |= Encoding == FileSystem::EEncoding::UTF8; | ||||||
|  | 	else if constexpr (CSameAs<T, u16char>) bCompatible |= Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF16LE; | ||||||
|  | 	else if constexpr (CSameAs<T, u32char>) bCompatible |= Encoding == FileSystem::EEncoding::UTF32BE || Encoding == FileSystem::EEncoding::UTF32LE; | ||||||
|  |  | ||||||
|  | 	else static_assert(sizeof(T) == -1, "Unsupported character type"); | ||||||
|  |  | ||||||
|  | 	if (bCompatible) | ||||||
|  | 	{ | ||||||
|  | 		FILE* File = std::fopen(*Path, "wb"); | ||||||
|  |  | ||||||
|  | 		if (File == nullptr) return false; | ||||||
|  |  | ||||||
|  | 		auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); | ||||||
|  |  | ||||||
|  | 		if (bWithBOM) | ||||||
|  | 		{ | ||||||
|  | 			if constexpr (CSameAs<T, u8char>) | ||||||
|  | 			{ | ||||||
|  | 				if (std::fwrite(U8TEXT("\uFEFF"), 1, 3, File) != 3) return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			else if constexpr (CSameAs<T, u16char>) | ||||||
|  | 			{ | ||||||
|  | 				constexpr TStaticArray<uint8, 2> BufferBE = { 0xFE, 0xFF }; | ||||||
|  | 				constexpr TStaticArray<uint8, 2> BufferLE = { 0xFF, 0xFE }; | ||||||
|  |  | ||||||
|  | 				if      (Encoding == FileSystem::EEncoding::UTF16BE) { if (std::fwrite(BufferBE.GetData(), 1, BufferBE.Num(), File) != BufferBE.Num()) return false; } | ||||||
|  | 				else if (Encoding == FileSystem::EEncoding::UTF16LE) { if (std::fwrite(BufferLE.GetData(), 1, BufferLE.Num(), File) != BufferLE.Num()) return false; } | ||||||
|  |  | ||||||
|  | 				else if (std::fwrite(U16TEXT("\uFEFF"), 1, sizeof(T), File) != sizeof(T)) return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			else if constexpr (CSameAs<T, u32char>) | ||||||
|  | 			{ | ||||||
|  | 				constexpr TStaticArray<uint8, 4> BufferBE = { 0x00, 0x00, 0xFE, 0xFF }; | ||||||
|  | 				constexpr TStaticArray<uint8, 4> BufferLE = { 0xFF, 0xFE, 0x00, 0x00 }; | ||||||
|  |  | ||||||
|  | 				if      (Encoding == FileSystem::EEncoding::UTF32BE) { if (std::fwrite(BufferBE.GetData() , 1, BufferBE.Num(), File) != BufferBE.Num()) return false; } | ||||||
|  | 				else if (Encoding == FileSystem::EEncoding::UTF32LE) { if (std::fwrite(BufferLE.GetData() , 1, BufferLE.Num(), File) != BufferLE.Num()) return false; } | ||||||
|  |  | ||||||
|  | 				else if (std::fwrite(U32TEXT("\uFEFF"), 1, sizeof(T), File) != sizeof(T)) return false; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		check(Math::EEndian::Native == Math::EEndian::Big || Math::EEndian::Native == Math::EEndian::Little); | ||||||
|  |  | ||||||
|  | 		const bool bByteSwap = | ||||||
|  | 			Math::EEndian::Native == Math::EEndian::Big    ? Encoding == FileSystem::EEncoding::UTF16LE || Encoding == FileSystem::EEncoding::UTF32LE : | ||||||
|  | 			Math::EEndian::Native == Math::EEndian::Little ? Encoding == FileSystem::EEncoding::UTF16BE || Encoding == FileSystem::EEncoding::UTF32BE : false; | ||||||
|  |  | ||||||
|  | 		for (T Char : String) | ||||||
|  | 		{ | ||||||
|  | #			if PLATFORM_WINDOWS | ||||||
|  | 			{ | ||||||
|  | 				if (Char == LITERAL(T, '\n')) | ||||||
|  | 				{ | ||||||
|  | 					T Return = LITERAL(T, '\r'); | ||||||
|  |  | ||||||
|  | 					if (bByteSwap) Return = Math::ByteSwap(static_cast<TMakeUnsigned<T>>(Return)); | ||||||
|  |  | ||||||
|  | 					if (std::fwrite(&Return, 1, sizeof(T), File) != sizeof(T)) return false; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | #			endif | ||||||
|  |  | ||||||
|  | 			if (bByteSwap) Char = Math::ByteSwap(static_cast<TMakeUnsigned<T>>(Char)); | ||||||
|  |  | ||||||
|  | 			if (std::fwrite(&Char, 1, sizeof(T), File) != sizeof(T)) return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		FileGuard.Release(); | ||||||
|  |  | ||||||
|  | 		if (std::fclose(File) != 0) return false; | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	FString PathWithNull; | ||||||
|  |  | ||||||
|  | 	PathWithNull.Reserve(Path.Num() + 1); | ||||||
|  |  | ||||||
|  | 	PathWithNull += Path; | ||||||
|  | 	PathWithNull += '\0'; | ||||||
|  |  | ||||||
|  | 	switch (Encoding) | ||||||
|  | 	{ | ||||||
|  | 	case FileSystem::EEncoding::Narrow:  { FString    Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::Narrow,  bWithBOM)) return false; break; } | ||||||
|  | 	case FileSystem::EEncoding::Wide:    { FWString   Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::Wide,    bWithBOM)) return false; break; } | ||||||
|  | 	case FileSystem::EEncoding::UTF8:    { FU8String  Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF8,    bWithBOM)) return false; break; } | ||||||
|  | 	case FileSystem::EEncoding::UTF16BE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF16BE, bWithBOM)) return false; break; } | ||||||
|  | 	case FileSystem::EEncoding::UTF16LE: { FU16String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF16LE, bWithBOM)) return false; break; } | ||||||
|  | 	case FileSystem::EEncoding::UTF32BE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF32BE, bWithBOM)) return false; break; } | ||||||
|  | 	case FileSystem::EEncoding::UTF32LE: { FU32String Temp; if (!Temp.DecodeFrom(String)) return false; if (!FileSystem::SaveStringToFile(Temp, PathWithNull, FileSystem::EEncoding::UTF32LE, bWithBOM)) return false; break; } | ||||||
|  | 	default: check_no_entry(); return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template REDCRAFTUTILITY_API bool SaveStringToFile<char>   (FStringView,    FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool SaveStringToFile<wchar>  (FWStringView,   FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool SaveStringToFile<u8char> (FU8StringView,  FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool SaveStringToFile<u16char>(FU16StringView, FStringView, FileSystem::EEncoding, bool); | ||||||
|  | template REDCRAFTUTILITY_API bool SaveStringToFile<u32char>(FU32StringView, FStringView, FileSystem::EEncoding, bool); | ||||||
|  |  | ||||||
|  | size_t FileSize(FStringView Path) | ||||||
|  | { | ||||||
|  | 	if (!FileSystem::Exists(Path)) return static_cast<size_t>(-1); | ||||||
|  |  | ||||||
|  | 	FILE* File = std::fopen(*Path, "rb"); | ||||||
|  |  | ||||||
|  | 	if (File == nullptr) return static_cast<size_t>(-1); | ||||||
|  |  | ||||||
|  | 	auto FileGuard = TScopeCallback([=] { Ignore = std::fclose(File); }); | ||||||
|  |  | ||||||
|  | 	if (std::fseek(File, 0, SEEK_END) != 0) return static_cast<size_t>(-1); | ||||||
|  |  | ||||||
|  | 	const long Length = std::ftell(File); | ||||||
|  |  | ||||||
|  | 	if (!Math::IsWithin(Length, 0, TNumericLimits<long>::Max())) return static_cast<size_t>(-1); | ||||||
|  |  | ||||||
|  | 	FileGuard.Release(); | ||||||
|  |  | ||||||
|  | 	if (std::fclose(File) != 0) return static_cast<size_t>(-1); | ||||||
|  |  | ||||||
|  | 	return Length; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Delete(FStringView Path) | ||||||
|  | { | ||||||
|  | 	return std::remove(*Path) == 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Exists(FStringView Path) | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		DWORD Attributes = GetFileAttributesA(*Path); | ||||||
|  |  | ||||||
|  | 		if (Attributes == INVALID_FILE_ATTRIBUTES) return false; | ||||||
|  |  | ||||||
|  | 		return !(Attributes & FILE_ATTRIBUTE_DIRECTORY); | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		struct stat FileInfo; | ||||||
|  |  | ||||||
|  | 		FileInfo.st_size = -1; | ||||||
|  |  | ||||||
|  | 		if (stat(*Path, &FileInfo) != 0) return false; | ||||||
|  |  | ||||||
|  | 		if (!S_ISREG(FileInfo.st_mode)) return false; | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Copy(FStringView Destination, FStringView Source) | ||||||
|  | { | ||||||
|  | 	if (!FileSystem::Exists(Source)) return false; | ||||||
|  |  | ||||||
|  | 	FILE* FileA = std::fopen(*Source, "rb"); | ||||||
|  |  | ||||||
|  | 	if (FileA == nullptr) return false; | ||||||
|  |  | ||||||
|  | 	auto FileGuardA = TScopeCallback([=] { Ignore = std::fclose(FileA); }); | ||||||
|  |  | ||||||
|  | 	FILE* FileB = std::fopen(*Destination, "wb"); | ||||||
|  |  | ||||||
|  | 	if (FileB == nullptr) return false; | ||||||
|  |  | ||||||
|  | 	auto FileGuardB = TScopeCallback([=] { Ignore = std::fclose(FileB); }); | ||||||
|  |  | ||||||
|  | 	size_t ReadSize; | ||||||
|  |  | ||||||
|  | 	constexpr size_t BufferSize = 4096; | ||||||
|  |  | ||||||
|  | 	TStaticArray<uint8, BufferSize> Buffer; | ||||||
|  |  | ||||||
|  | 	do | ||||||
|  | 	{ | ||||||
|  | 		ReadSize = std::fread(Buffer.GetData(), 1, BufferSize, FileA); | ||||||
|  |  | ||||||
|  | 		if (std::fwrite(Buffer.GetData(), 1, ReadSize, FileB) != ReadSize) return false; | ||||||
|  | 	} | ||||||
|  | 	while (ReadSize == BufferSize); | ||||||
|  |  | ||||||
|  | 	FileGuardA.Release(); | ||||||
|  |  | ||||||
|  | 	if (std::fclose(FileA) != 0) return false; | ||||||
|  |  | ||||||
|  | 	FileGuardB.Release(); | ||||||
|  |  | ||||||
|  | 	if (std::fclose(FileB) != 0) return false; | ||||||
|  |  | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool Rename(FStringView Destination, FStringView Source) | ||||||
|  | { | ||||||
|  | 	return std::rename(*Source, *Destination) == 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CreateDirectory(FStringView Path, bool bRecursive /* = false */) | ||||||
|  | { | ||||||
|  | 	if (Path.Num() == 0) return false; | ||||||
|  |  | ||||||
|  | 	if (bRecursive) | ||||||
|  | 	{ | ||||||
|  | 		if (Path.Back() == '/' || Path.Back() == '\\') Path = Path.First(Path.Num() - 1); | ||||||
|  |  | ||||||
|  | 		FStringView Parent = Path.First(Path.FindLastOf("/\\")); | ||||||
|  |  | ||||||
|  | 		if (!FileSystem::ExistsDirectory(Parent) && !FileSystem::CreateDirectory(Parent, true)) return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		return CreateDirectoryA(*Path, nullptr) != 0; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		return mkdir(*Path, 0755) == 0; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool DeleteDirectory(FStringView Path, bool bRecursive /* = false */) | ||||||
|  | { | ||||||
|  | 	if (bRecursive) | ||||||
|  | 	{ | ||||||
|  | 		FString Temp; | ||||||
|  |  | ||||||
|  | 		bool bSuccessfully = FileSystem::IterateDirectory(Path, [&](FStringView File, bool bIsDirectory) -> bool | ||||||
|  | 		{ | ||||||
|  | 			Temp.Reset(false); | ||||||
|  |  | ||||||
|  | 			Temp += Path; | ||||||
|  | 			Temp += '/'; | ||||||
|  | 			Temp += File; | ||||||
|  | 			Temp += '\0'; | ||||||
|  |  | ||||||
|  | 			if (bIsDirectory) | ||||||
|  | 			{ | ||||||
|  | 				if (!FileSystem::DeleteDirectory(Temp, true)) return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			else | ||||||
|  | 			{ | ||||||
|  | 				if (!FileSystem::Delete(Temp)) return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return true; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		if (!bSuccessfully) return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		return RemoveDirectoryA(*Path) != 0; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		return rmdir(*Path) == 0; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool ExistsDirectory(FStringView Path) | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		DWORD Attributes = GetFileAttributesA(*Path); | ||||||
|  |  | ||||||
|  | 		if (Attributes == INVALID_FILE_ATTRIBUTES) return false; | ||||||
|  |  | ||||||
|  | 		return Attributes & FILE_ATTRIBUTE_DIRECTORY; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		DIR* Directory = opendir(*Path); | ||||||
|  |  | ||||||
|  | 		if (Directory == nullptr) return false; | ||||||
|  |  | ||||||
|  | 		Ignore = closedir(Directory); | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool IterateDirectory(FStringView Path, TFunctionRef<bool(FStringView /* Path */, bool /* bIsDirectory */)> Visitor) | ||||||
|  | { | ||||||
|  | #	if PLATFORM_WINDOWS | ||||||
|  | 	{ | ||||||
|  | 		FString FindPath; | ||||||
|  |  | ||||||
|  | 		FindPath.Reserve(Path.Num() + 3); | ||||||
|  |  | ||||||
|  | 		FindPath += Path; | ||||||
|  | 		FindPath += '\\'; | ||||||
|  | 		FindPath += '*'; | ||||||
|  | 		FindPath += '\0'; | ||||||
|  |  | ||||||
|  | 		WIN32_FIND_DATA FindData; | ||||||
|  |  | ||||||
|  | 		HANDLE FindHandle = FindFirstFileA(*FindPath, &FindData); | ||||||
|  |  | ||||||
|  | 		auto FindGuard = TScopeCallback([=] { Ignore = FindClose(FindHandle); }); | ||||||
|  |  | ||||||
|  | 		if (FindHandle == INVALID_HANDLE_VALUE) return false; | ||||||
|  |  | ||||||
|  | 		do | ||||||
|  | 		{ | ||||||
|  | 			const FStringView FilePath = FindData.cFileName; | ||||||
|  |  | ||||||
|  | 			if (FilePath == "." || FilePath == "..") continue; | ||||||
|  |  | ||||||
|  | 			const bool bIsDirectory = (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; | ||||||
|  |  | ||||||
|  | 			if (!Visitor(FilePath, bIsDirectory)) return false; | ||||||
|  | 		} | ||||||
|  | 		while (FindNextFileA(FindHandle, &FindData) != 0); | ||||||
|  |  | ||||||
|  | 		FindGuard.Release(); | ||||||
|  |  | ||||||
|  | 		if (!FindClose(FindHandle)) return false; | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | #	elif PLATFORM_LINUX | ||||||
|  | 	{ | ||||||
|  | 		DIR* Directory = opendir(*Path); | ||||||
|  |  | ||||||
|  | 		if (Directory == nullptr) return false; | ||||||
|  |  | ||||||
|  | 		auto DirectoryGuard = TScopeCallback([=] { Ignore = closedir(Directory); }); | ||||||
|  |  | ||||||
|  | 		dirent* Entry; | ||||||
|  |  | ||||||
|  | 		while ((Entry = readdir(Directory)) != nullptr) | ||||||
|  | 		{ | ||||||
|  | 			const FStringView FilePath = Entry->d_name; | ||||||
|  |  | ||||||
|  | 			if (FilePath == "." || FilePath == "..") continue; | ||||||
|  |  | ||||||
|  | 			const bool bIsDirectory = Entry->d_type == DT_DIR; | ||||||
|  |  | ||||||
|  | 			if (!Visitor(FilePath, bIsDirectory)) return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		DirectoryGuard.Release(); | ||||||
|  |  | ||||||
|  | 		if (closedir(Directory) != 0) return false; | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  | #	endif | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | NAMESPACE_END(FileSystem) | ||||||
|  |  | ||||||
|  | NAMESPACE_MODULE_END(Utility) | ||||||
|  | NAMESPACE_MODULE_END(Redcraft) | ||||||
|  | NAMESPACE_REDCRAFT_END | ||||||
|  |  | ||||||
|  | #pragma warning(pop) | ||||||
							
								
								
									
										32
									
								
								Redcraft.Utility/Source/Public/Miscellaneous/BitwiseEnum.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								Redcraft.Utility/Source/Public/Miscellaneous/BitwiseEnum.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "CoreTypes.h" | ||||||
|  | #include "TypeTraits/TypeTraits.h" | ||||||
|  |  | ||||||
|  | NAMESPACE_REDCRAFT_BEGIN | ||||||
|  | NAMESPACE_MODULE_BEGIN(Redcraft) | ||||||
|  | NAMESPACE_MODULE_BEGIN(Utility) | ||||||
|  |  | ||||||
|  | #define ENABLE_ENUM_CLASS_BITWISE_OPERATIONS(Enum)                                                                                                                                         \ | ||||||
|  | 	NODISCARD FORCEINLINE constexpr Enum  operator| (Enum  LHS, Enum RHS) { return static_cast<Enum>(static_cast<TUnderlyingType<Enum>>(LHS) | static_cast<TUnderlyingType<Enum>>(RHS)); } \ | ||||||
|  | 	NODISCARD FORCEINLINE constexpr Enum  operator& (Enum  LHS, Enum RHS) { return static_cast<Enum>(static_cast<TUnderlyingType<Enum>>(LHS) & static_cast<TUnderlyingType<Enum>>(RHS)); } \ | ||||||
|  | 	NODISCARD FORCEINLINE constexpr Enum  operator^ (Enum  LHS, Enum RHS) { return static_cast<Enum>(static_cast<TUnderlyingType<Enum>>(LHS) ^ static_cast<TUnderlyingType<Enum>>(RHS)); } \ | ||||||
|  | 	          FORCEINLINE constexpr Enum& operator|=(Enum& LHS, Enum RHS) { LHS = LHS | RHS; return LHS; }                                                                                 \ | ||||||
|  | 	          FORCEINLINE constexpr Enum& operator&=(Enum& LHS, Enum RHS) { LHS = LHS & RHS; return LHS; }                                                                                 \ | ||||||
|  | 	          FORCEINLINE constexpr Enum& operator^=(Enum& LHS, Enum RHS) { LHS = LHS ^ RHS; return LHS; }                                                                                 \ | ||||||
|  | 	NODISCARD FORCEINLINE constexpr bool  operator! (Enum  E            ) { return                   !static_cast<TUnderlyingType<Enum>>(E);  }                                            \ | ||||||
|  | 	NODISCARD FORCEINLINE constexpr Enum  operator~ (Enum  E            ) { return static_cast<Enum>(~static_cast<TUnderlyingType<Enum>>(E)); } | ||||||
|  |  | ||||||
|  | #define FRIEND_ENUM_CLASS_BITWISE_OPERATIONS(Enum)  \ | ||||||
|  | 	friend constexpr Enum  operator| (Enum , Enum); \ | ||||||
|  | 	friend constexpr Enum  operator& (Enum , Enum); \ | ||||||
|  | 	friend constexpr Enum  operator^ (Enum , Enum); \ | ||||||
|  | 	friend constexpr Enum& operator|=(Enum&, Enum); \ | ||||||
|  | 	friend constexpr Enum& operator&=(Enum&, Enum); \ | ||||||
|  | 	friend constexpr Enum& operator^=(Enum&, Enum); \ | ||||||
|  | 	friend constexpr bool  operator! (Enum );       \ | ||||||
|  | 	friend constexpr Enum  operator~ (Enum ); | ||||||
|  |  | ||||||
|  | NAMESPACE_MODULE_END(Utility) | ||||||
|  | NAMESPACE_MODULE_END(Redcraft) | ||||||
|  | NAMESPACE_REDCRAFT_END | ||||||
							
								
								
									
										217
									
								
								Redcraft.Utility/Source/Public/Miscellaneous/Console.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								Redcraft.Utility/Source/Public/Miscellaneous/Console.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "CoreTypes.h" | ||||||
|  | #include "TypeTraits/TypeTraits.h" | ||||||
|  | #include "Templates/Utility.h" | ||||||
|  | #include "Iterators/Utility.h" | ||||||
|  | #include "Strings/Formatting.h" | ||||||
|  | #include "Strings/StringView.h" | ||||||
|  | #include "Strings/String.h" | ||||||
|  | #include "Miscellaneous/BitwiseEnum.h" | ||||||
|  |  | ||||||
|  | NAMESPACE_REDCRAFT_BEGIN | ||||||
|  | NAMESPACE_MODULE_BEGIN(Redcraft) | ||||||
|  | NAMESPACE_MODULE_BEGIN(Utility) | ||||||
|  |  | ||||||
|  | /** An enumeration that defines the color of the console. */ | ||||||
|  | enum class EColor : uint8 | ||||||
|  | { | ||||||
|  | 	Default = 0xFF, | ||||||
|  |  | ||||||
|  | 	Black     = 0b0000, | ||||||
|  | 	Red       = 0b0001, | ||||||
|  | 	Green     = 0b0010, | ||||||
|  | 	Blue      = 0b0100, | ||||||
|  | 	Intensity = 0b1000, | ||||||
|  |  | ||||||
|  | 	Cyan    = Green | Blue, | ||||||
|  | 	Magenta = Blue  | Red, | ||||||
|  | 	Yellow  = Red   | Green, | ||||||
|  |  | ||||||
|  | 	White = Red | Green | Blue, | ||||||
|  |  | ||||||
|  | 	BrightBlack   = Intensity | Black, | ||||||
|  | 	BrightRed     = Intensity | Red, | ||||||
|  | 	BrightGreen   = Intensity | Green, | ||||||
|  | 	BrightBlue    = Intensity | Blue, | ||||||
|  | 	BrightYellow  = Intensity | Yellow, | ||||||
|  | 	BrightMagenta = Intensity | Magenta, | ||||||
|  | 	BrightCyan    = Intensity | Cyan, | ||||||
|  | 	BrightWhite   = Intensity | White | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | ENABLE_ENUM_CLASS_BITWISE_OPERATIONS(EColor) | ||||||
|  |  | ||||||
|  | /** @return The color of the console. */ | ||||||
|  | NODISCARD REDCRAFTUTILITY_API EColor GetForegroundColor(); | ||||||
|  | NODISCARD REDCRAFTUTILITY_API EColor GetBackgroundColor(); | ||||||
|  |  | ||||||
|  | /** Set the color of the console. Returns the color that was successfully set. */ | ||||||
|  | REDCRAFTUTILITY_API EColor SetForegroundColor(EColor InColor); | ||||||
|  | REDCRAFTUTILITY_API EColor SetBackgroundColor(EColor InColor); | ||||||
|  |  | ||||||
|  | /** @return The size of the console window. */ | ||||||
|  | NODISCARD REDCRAFTUTILITY_API uint GetWindowWidth(); | ||||||
|  | NODISCARD REDCRAFTUTILITY_API uint GetWindowHeight(); | ||||||
|  |  | ||||||
|  | /** @return true if the standard stream is redirected. */ | ||||||
|  | NODISCARD REDCRAFTUTILITY_API bool IsInputRedirected(); | ||||||
|  | NODISCARD REDCRAFTUTILITY_API bool IsOutputRedirected(); | ||||||
|  | NODISCARD REDCRAFTUTILITY_API bool IsErrorRedirected(); | ||||||
|  |  | ||||||
|  | /** Clear the console screen. */ | ||||||
|  | REDCRAFTUTILITY_API void Clear(); | ||||||
|  |  | ||||||
|  | /** @return The input character from the standard input. */ | ||||||
|  | NODISCARD REDCRAFTUTILITY_API char Input(bool bEcho = true); | ||||||
|  |  | ||||||
|  | /** @return The input line from the standard input. */ | ||||||
|  | NODISCARD REDCRAFTUTILITY_API FString InputLn(bool bEcho = true); | ||||||
|  |  | ||||||
|  | /** Print the character to the standard output. */ | ||||||
|  | REDCRAFTUTILITY_API bool Print(char Char); | ||||||
|  |  | ||||||
|  | /** Print the formatted string to the standard output. */ | ||||||
|  | template <CFormattable... Ts> | ||||||
|  | FORCEINLINE bool Print(FStringView Fmt, Ts&&... Args) | ||||||
|  | { | ||||||
|  | 	struct FStandardOutputIterator | ||||||
|  | 	{ | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator=(char Char) | ||||||
|  | 		{ | ||||||
|  | 			bError |= !Print(Char); | ||||||
|  | 			return *this; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		FORCEINLINE constexpr bool operator==(FDefaultSentinel) const { return bError; } | ||||||
|  |  | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator*()     { return *this; } | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator++()    { return *this; } | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator++(int) { return *this; } | ||||||
|  |  | ||||||
|  | 		bool bError = false; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	static_assert(COutputIterator<FStandardOutputIterator, char>); | ||||||
|  |  | ||||||
|  | 	FStandardOutputIterator Iter; | ||||||
|  |  | ||||||
|  | 	auto Range = Ranges::View(Iter, DefaultSentinel); | ||||||
|  |  | ||||||
|  | 	Iter = Algorithms::Format(Range, Fmt, Forward<Ts>(Args)...); | ||||||
|  |  | ||||||
|  | 	return Iter != DefaultSentinel; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the value to the standard output. */ | ||||||
|  | template <CFormattable T> | ||||||
|  | FORCEINLINE bool Print(T&& Value) | ||||||
|  | { | ||||||
|  | 	if constexpr (CSameAs<TRemoveCVRef<T>, char>) | ||||||
|  | 	{ | ||||||
|  | 		return Print(static_cast<char>(Value)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	else if constexpr (CConvertibleTo<T, FStringView>) | ||||||
|  | 	{ | ||||||
|  | 		return Print(static_cast<FStringView>(Value)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	else return Print(TEXT("{0}"), Forward<T>(Value)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the newline character to the standard output. */ | ||||||
|  | FORCEINLINE bool PrintLn() | ||||||
|  | { | ||||||
|  | 	return Print(TEXT("\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the string to the standard output and append the newline character. */ | ||||||
|  | template <CFormattable... Ts> | ||||||
|  | FORCEINLINE bool PrintLn(FStringView Fmt, Ts&&... Args) | ||||||
|  | { | ||||||
|  | 	return Print(Fmt, Forward<Ts>(Args)...) && Print(TEXT("\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the value to the standard output and append the newline character. */ | ||||||
|  | template <CFormattable T> | ||||||
|  | FORCEINLINE bool PrintLn(T&& Value) | ||||||
|  | { | ||||||
|  | 	return Print(Forward<T>(Value)) && Print(TEXT("\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the character to the standard error. */ | ||||||
|  | REDCRAFTUTILITY_API bool Error(char Char); | ||||||
|  |  | ||||||
|  | /** Print the formatted string to the standard error. */ | ||||||
|  | template <CFormattable... Ts> | ||||||
|  | FORCEINLINE bool Error(FStringView Fmt, Ts&&... Args) | ||||||
|  | { | ||||||
|  | 	struct FStandardOutputIterator | ||||||
|  | 	{ | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator=(char Char) | ||||||
|  | 		{ | ||||||
|  | 			bError |= !Error(Char); | ||||||
|  | 			return *this; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		FORCEINLINE constexpr bool operator==(FDefaultSentinel) const { return bError; } | ||||||
|  |  | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator*()     { return *this; } | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator++()    { return *this; } | ||||||
|  | 		FORCEINLINE constexpr FStandardOutputIterator& operator++(int) { return *this; } | ||||||
|  |  | ||||||
|  | 		bool bError = false; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	static_assert(COutputIterator<FStandardOutputIterator, char>); | ||||||
|  |  | ||||||
|  | 	FStandardOutputIterator Iter; | ||||||
|  |  | ||||||
|  | 	auto Range = Ranges::View(Iter, DefaultSentinel); | ||||||
|  |  | ||||||
|  | 	Iter = Algorithms::Format(Range, Fmt, Forward<Ts>(Args)...); | ||||||
|  |  | ||||||
|  | 	return Iter != DefaultSentinel; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the value to the standard error. */ | ||||||
|  | template <CFormattable T> | ||||||
|  | FORCEINLINE bool Error(T&& Value) | ||||||
|  | { | ||||||
|  | 	if constexpr (CSameAs<TRemoveCVRef<T>, char>) | ||||||
|  | 	{ | ||||||
|  | 		return Error(static_cast<char>(Value)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	else if constexpr (CConvertibleTo<T, FStringView>) | ||||||
|  | 	{ | ||||||
|  | 		return Error(static_cast<FStringView>(Value)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	else return Error(TEXT("{0}"), Forward<T>(Value)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the newline character to the standard error. */ | ||||||
|  | FORCEINLINE bool ErrorLn() | ||||||
|  | { | ||||||
|  | 	return Error(TEXT("\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the string to the standard error and append the newline character. */ | ||||||
|  | template <CFormattable... Ts> | ||||||
|  | FORCEINLINE bool ErrorLn(FStringView Fmt, Ts&&... Args) | ||||||
|  | { | ||||||
|  | 	return Error(Fmt, Forward<Ts>(Args)...) && Error(TEXT("\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** Print the value to the standard error and append the newline character. */ | ||||||
|  | template <CFormattable T> | ||||||
|  | FORCEINLINE bool ErrorLn(T&& Value) | ||||||
|  | { | ||||||
|  | 	return Error(Forward<T>(Value)) && Error(TEXT("\n")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | NAMESPACE_MODULE_END(Utility) | ||||||
|  | NAMESPACE_MODULE_END(Redcraft) | ||||||
|  | NAMESPACE_REDCRAFT_END | ||||||
							
								
								
									
										116
									
								
								Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								Redcraft.Utility/Source/Public/Miscellaneous/FileSystem.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "CoreTypes.h" | ||||||
|  | #include "TypeTraits/TypeTraits.h" | ||||||
|  | #include "Templates/Utility.h" | ||||||
|  | #include "Templates/Function.h" | ||||||
|  | #include "Containers/Array.h" | ||||||
|  | #include "Strings/StringView.h" | ||||||
|  | #include "Strings/String.h" | ||||||
|  |  | ||||||
|  | NAMESPACE_REDCRAFT_BEGIN | ||||||
|  | NAMESPACE_MODULE_BEGIN(Redcraft) | ||||||
|  | NAMESPACE_MODULE_BEGIN(Utility) | ||||||
|  |  | ||||||
|  | NAMESPACE_BEGIN(FileSystem) | ||||||
|  |  | ||||||
|  | /** The encoding of the text file. */ | ||||||
|  | enum class EEncoding : uint8 | ||||||
|  | { | ||||||
|  | 	Default, | ||||||
|  | 	Narrow, | ||||||
|  | 	Wide, | ||||||
|  | 	UTF8, | ||||||
|  | 	UTF16BE, | ||||||
|  | 	UTF16LE, | ||||||
|  | 	UTF32BE, | ||||||
|  | 	UTF32LE, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** Loads the file at the specified path into the byte array. */ | ||||||
|  | REDCRAFTUTILITY_API bool LoadFileToArray(TArray<uint8>& Result, FStringView Path); | ||||||
|  |  | ||||||
|  | /** Saves the byte array to the file at the specified path. */ | ||||||
|  | REDCRAFTUTILITY_API bool SaveArrayToFile(TArrayView<const uint8> Data, FStringView Path); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Loads the file at the specified path into the string. | ||||||
|  |  * | ||||||
|  |  * @param Result   - The string to load the file into. | ||||||
|  |  * @param Path     - The path to the file to load. | ||||||
|  |  * @param Encoding - The encoding of the file. The default value indicates automatic detection. | ||||||
|  |  * @param bVerify  - Whether to verify the character validity of the file. | ||||||
|  |  * | ||||||
|  |  * @return true if the file was successfully loaded, false otherwise. | ||||||
|  |  */ | ||||||
|  | template <CCharType T> | ||||||
|  | REDCRAFTUTILITY_API bool LoadFileToString(TString<T>& Result, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bVerify = false); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Saves the string to the file at the specified path. | ||||||
|  |  * | ||||||
|  |  * @param String   - The string to save to the file. | ||||||
|  |  * @param Path     - The path to the file to save. | ||||||
|  |  * @param Encoding - The encoding of the file. The default value indicates the same as the string. | ||||||
|  |  * @param bWithBOM - Whether to write the BOM character at the beginning of the file. Not valid for narrow and wide encoding. | ||||||
|  |  * | ||||||
|  |  * @return true if the file was successfully saved, false otherwise. | ||||||
|  |  */ | ||||||
|  | template <CCharType T> | ||||||
|  | REDCRAFTUTILITY_API bool SaveStringToFile(TStringView<T> String, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bWithBOM = true); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Saves the string to the file at the specified path. | ||||||
|  |  * | ||||||
|  |  * @param String   - The string to save to the file. | ||||||
|  |  * @param Path     - The path to the file to save. | ||||||
|  |  * @param Encoding - The encoding of the file. The default value indicates the same as the string. | ||||||
|  |  * @param bWithBOM - Whether to write the BOM character at the beginning of the file. Not valid for narrow and wide encoding. | ||||||
|  |  * | ||||||
|  |  * @return true if the file was successfully saved, false otherwise. | ||||||
|  |  */ | ||||||
|  | template <typename T> requires (CConvertibleTo<T&&, FStringView> || CConvertibleTo<T&&, FWStringView> | ||||||
|  | 	|| CConvertibleTo<T&&, FU8StringView> || CConvertibleTo<T&&, FU16StringView> || CConvertibleTo<T&&, FU32StringView>) | ||||||
|  | bool SaveStringToFile(T&& String, FStringView Path, FileSystem::EEncoding Encoding = FileSystem::EEncoding::Default, bool bWithBOM = true) | ||||||
|  | { | ||||||
|  | 	if      constexpr (CConvertibleTo<T&&, FStringView>)    return SaveStringToFile(FStringView   (Forward<T>(String)), Path, Encoding, bWithBOM); | ||||||
|  | 	else if constexpr (CConvertibleTo<T&&, FWStringView>)   return SaveStringToFile(FWStringView  (Forward<T>(String)), Path, Encoding, bWithBOM); | ||||||
|  | 	else if constexpr (CConvertibleTo<T&&, FU8StringView>)  return SaveStringToFile(FU8StringView (Forward<T>(String)), Path, Encoding, bWithBOM); | ||||||
|  | 	else if constexpr (CConvertibleTo<T&&, FU16StringView>) return SaveStringToFile(FU16StringView(Forward<T>(String)), Path, Encoding, bWithBOM); | ||||||
|  | 	else if constexpr (CConvertibleTo<T&&, FU32StringView>) return SaveStringToFile(FU32StringView(Forward<T>(String)), Path, Encoding, bWithBOM); | ||||||
|  |  | ||||||
|  | 	return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** @return The size of the file at the specified path. */ | ||||||
|  | REDCRAFTUTILITY_API size_t FileSize(FStringView Path); | ||||||
|  |  | ||||||
|  | /** Deletes the file at the specified path. */ | ||||||
|  | REDCRAFTUTILITY_API bool Delete(FStringView Path); | ||||||
|  |  | ||||||
|  | /** @return true if the regular file at the specified path exists, false otherwise. */ | ||||||
|  | REDCRAFTUTILITY_API bool Exists(FStringView Path); | ||||||
|  |  | ||||||
|  | /** Copies the file from the source path to the destination path. */ | ||||||
|  | REDCRAFTUTILITY_API bool Copy(FStringView Destination, FStringView Source); | ||||||
|  |  | ||||||
|  | /** Renames the file from the source path to the destination path. */ | ||||||
|  | REDCRAFTUTILITY_API bool Rename(FStringView Destination, FStringView Source); | ||||||
|  |  | ||||||
|  | /** Creates the directory at the specified path. If recursive, it will not delete the created items on failure. */ | ||||||
|  | REDCRAFTUTILITY_API bool CreateDirectory(FStringView Path, bool bRecursive = false); | ||||||
|  |  | ||||||
|  | /** Deletes the directory at the specified path. If recursive, it will not recreate the deleted items on failure. */ | ||||||
|  | REDCRAFTUTILITY_API bool DeleteDirectory(FStringView Path, bool bRecursive = false); | ||||||
|  |  | ||||||
|  | /** @return true if the directory at the specified path exists, false otherwise. */ | ||||||
|  | REDCRAFTUTILITY_API bool ExistsDirectory(FStringView Path); | ||||||
|  |  | ||||||
|  | /** Iterates items in the directory at the specified path. */ | ||||||
|  | REDCRAFTUTILITY_API bool IterateDirectory(FStringView Path, TFunctionRef<bool(FStringView /* Path */, bool /* bIsDirectory */)> Visitor); | ||||||
|  |  | ||||||
|  | NAMESPACE_END(FileSystem) | ||||||
|  |  | ||||||
|  | NAMESPACE_MODULE_END(Utility) | ||||||
|  | NAMESPACE_MODULE_END(Redcraft) | ||||||
|  | NAMESPACE_REDCRAFT_END | ||||||
| @@ -15,6 +15,9 @@ | |||||||
|  |  | ||||||
| #include <charconv> | #include <charconv> | ||||||
|  |  | ||||||
|  | #pragma warning(push) | ||||||
|  | #pragma warning(disable: 4244) | ||||||
|  |  | ||||||
| NAMESPACE_REDCRAFT_BEGIN | NAMESPACE_REDCRAFT_BEGIN | ||||||
| NAMESPACE_MODULE_BEGIN(Redcraft) | NAMESPACE_MODULE_BEGIN(Redcraft) | ||||||
| NAMESPACE_MODULE_BEGIN(Utility) | NAMESPACE_MODULE_BEGIN(Utility) | ||||||
| @@ -235,8 +238,6 @@ FORCEINLINE constexpr TRangeIterator<R2> Format(R2&& Output, R1&& Fmt, Ts&&... A | |||||||
| 	// If the output range is insufficient. | 	// If the output range is insufficient. | ||||||
| 	if (OutIter == OutSent) UNLIKELY return OutIter; | 	if (OutIter == OutSent) UNLIKELY return OutIter; | ||||||
|  |  | ||||||
| 	TTuple<TFormatter<TRemoveCVRef<Ts>, FCharType>...> Formatters; |  | ||||||
|  |  | ||||||
| 	// For each character in the format string. | 	// For each character in the format string. | ||||||
| 	for (FCharType Char; FmtIter != FmtSent; ++FmtIter) | 	for (FCharType Char; FmtIter != FmtSent; ++FmtIter) | ||||||
| 	{ | 	{ | ||||||
| @@ -315,35 +316,40 @@ FORCEINLINE constexpr TRangeIterator<R2> Format(R2&& Output, R1&& Fmt, Ts&&... A | |||||||
| 				// Jump over the ':' character. | 				// Jump over the ':' character. | ||||||
| 				if (Char == LITERAL(FCharType, ':')) ++FmtIter; | 				if (Char == LITERAL(FCharType, ':')) ++FmtIter; | ||||||
|  |  | ||||||
| 				FormatStringContext.AdvanceTo(MoveTemp(FmtIter)); | 				if (FmtIter == FmtSent) UNLIKELY | ||||||
|  |  | ||||||
| 				// Parse the format description string. |  | ||||||
| 				FmtIter = Formatters.Visit([&FormatStringContext](auto& Formatter) -> decltype(FmtIter) { return Formatter.Parse(FormatStringContext); }, Index); |  | ||||||
|  |  | ||||||
| 				if (FmtIter == FmtSent || *FmtIter != LITERAL(FCharType, '}')) UNLIKELY |  | ||||||
| 				{ | 				{ | ||||||
| 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
| 					break; | 					break; | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				FormatObjectContext.AdvanceTo(MoveTemp(OutIter)); | 				Char = *FmtIter; | ||||||
|  |  | ||||||
| 				auto FormatHandler = [&]<size_t... Indices>(TIndexSequence<Indices...>) | 				ForwardAsTuple(Forward<Ts>(Args)...).Visit([&]<typename T>(T&& Arg) | ||||||
| 				{ | 				{ | ||||||
| 					TTuple<TConstant<size_t, Indices>...> Visitor; | 					TFormatter<TRemoveCVRef<T>, FCharType> Formatter; | ||||||
|  |  | ||||||
| 					return Visitor.Visit([&]<size_t ConstantIndex>(TConstant<size_t, ConstantIndex>) | 					if (Char != LITERAL(FCharType, '}')) | ||||||
| 					{ | 					{ | ||||||
| 						check(ConstantIndex == Index); | 						FormatStringContext.AdvanceTo(MoveTemp(FmtIter)); | ||||||
|  |  | ||||||
| 						return Formatters.template GetValue<ConstantIndex>().Format(ForwardAsTuple(Forward<Ts>(Args)...).template GetValue<ConstantIndex>(), FormatObjectContext); | 						// Parse the format description string. | ||||||
|  | 						FmtIter = Formatter.Parse(FormatStringContext); | ||||||
|  |  | ||||||
|  | 						if (FmtIter == FmtSent || *FmtIter != LITERAL(FCharType, '}')) UNLIKELY | ||||||
|  | 						{ | ||||||
|  | 							checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 							return; | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 					, Index); |  | ||||||
| 				}; |  | ||||||
|  |  | ||||||
| 				// Format the object and write the result to the context. | 					FormatObjectContext.AdvanceTo(MoveTemp(OutIter)); | ||||||
| 				OutIter = FormatHandler(TIndexSequenceFor<Ts...>()); |  | ||||||
|  | 					// Format the object and write the result to the context. | ||||||
|  | 					OutIter = Formatter.Format(Forward<T>(Arg), FormatObjectContext); | ||||||
|  | 				} | ||||||
|  | 				, Index); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			else | 			else | ||||||
| @@ -925,14 +931,21 @@ public: | |||||||
| 			*Iter++ = LITERAL(FCharType, '\"'); | 			*Iter++ = LITERAL(FCharType, '\"'); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const FCharType* Ptr = Object - 1; | 		const FCharType* Ptr = Object; | ||||||
|  |  | ||||||
|  | 		bool bComplete = false; | ||||||
|  |  | ||||||
| 		// Write the object, include escaped quotes in the counter. | 		// Write the object, include escaped quotes in the counter. | ||||||
| 		for (size_t Index = bEscape ? 1 : 0; Index != MaxDynamicField; ++Index) | 		for (size_t Index = bEscape ? 1 : 0; Index != MaxDynamicField; ++Index) | ||||||
| 		{ | 		{ | ||||||
| 			FCharType Char = *++Ptr; | 			if (*Ptr == LITERAL(FCharType, '\0')) | ||||||
|  | 			{ | ||||||
|  | 				bComplete = true; | ||||||
|  |  | ||||||
| 			if (Char == LITERAL(FCharType, '\0')) break; | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			FCharType Char = *Ptr++; | ||||||
|  |  | ||||||
| 			if (Iter == Sent) UNLIKELY return Iter; | 			if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
| @@ -995,7 +1008,7 @@ public: | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Write the right quote, if the field width is enough. | 		// Write the right quote, if the field width is enough. | ||||||
| 		if (bEscape && *Ptr == LITERAL(FCharType, '\0')) | 		if (bEscape && bComplete) | ||||||
| 		{ | 		{ | ||||||
| 			if (Iter == Sent) UNLIKELY return Iter; | 			if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
| @@ -3166,3 +3179,5 @@ private: | |||||||
| NAMESPACE_MODULE_END(Utility) | NAMESPACE_MODULE_END(Utility) | ||||||
| NAMESPACE_MODULE_END(Redcraft) | NAMESPACE_MODULE_END(Redcraft) | ||||||
| NAMESPACE_REDCRAFT_END | NAMESPACE_REDCRAFT_END | ||||||
|  |  | ||||||
|  | #pragma warning(pop) | ||||||
|   | |||||||
| @@ -1124,10 +1124,7 @@ public: | |||||||
| 	/** @return The non-modifiable standard C character string version of the string. */ | 	/** @return The non-modifiable standard C character string version of the string. */ | ||||||
| 	NODISCARD FORCEINLINE auto operator*() && | 	NODISCARD FORCEINLINE auto operator*() && | ||||||
| 	{ | 	{ | ||||||
| 		if (this->Back() != LITERAL(T, '\0')) | 		if (!EndsWith(LITERAL(T, '\0'))) this->PushBack(LITERAL(T, '\0')); | ||||||
| 		{ |  | ||||||
| 			this->PushBack(LITERAL(T, '\0')); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return AsConst(*this).GetData(); | 		return AsConst(*this).GetData(); | ||||||
| 	} | 	} | ||||||
| @@ -1442,7 +1439,67 @@ using FU32String     = TString<u32char>; | |||||||
| using FUnicodeString = TString<unicodechar>; | using FUnicodeString = TString<unicodechar>; | ||||||
|  |  | ||||||
| template <CCharType T> template <typename Allocator> constexpr TStringView<T>::TStringView(const TString<FElementType, Allocator>& InString) | template <CCharType T> template <typename Allocator> constexpr TStringView<T>::TStringView(const TString<FElementType, Allocator>& InString) | ||||||
| 	: TStringView(InString.GetData(), InString.Num()) { } | 	: TStringView(InString.GetData(), InString.Num()) | ||||||
|  | { } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A formatter for TString. | ||||||
|  |  * | ||||||
|  |  * The syntax of format specifications is: | ||||||
|  |  * | ||||||
|  |  *	[Fill And Align] [Width] [Precision] [Type] [!] [?] | ||||||
|  |  * | ||||||
|  |  * 1. The fill and align part: | ||||||
|  |  * | ||||||
|  |  *	[Fill Character] <Align Option> | ||||||
|  |  * | ||||||
|  |  *	i.   Fill Character: The character is used to fill width of the object. It is optional and cannot be '{' or '}'. | ||||||
|  |  *	                     It should be representable as a single unicode otherwise it is undefined behavior. | ||||||
|  |  * | ||||||
|  |  *  ii.  Align Option: The character is used to indicate the direction of alignment. | ||||||
|  |  * | ||||||
|  |  *		- '<': Align the formatted argument to the left of the available space | ||||||
|  |  *		       by inserting n fill characters after the formatted argument. | ||||||
|  |  *		       This is default option. | ||||||
|  |  *		- '^': Align the formatted argument to the center of the available space | ||||||
|  |  *		       by inserting n fill characters around the formatted argument. | ||||||
|  |  *		       If cannot absolute centering, offset to the left. | ||||||
|  |  *		- '>': Align the formatted argument ro the right of the available space | ||||||
|  |  *		       by inserting n fill characters before the formatted argument. | ||||||
|  |  * | ||||||
|  |  * 2. The width part: | ||||||
|  |  * | ||||||
|  |  *	- 'N':   The number is used to specify the minimum field width of the object. | ||||||
|  |  *	         N should be an unsigned non-zero decimal number. | ||||||
|  |  *	- '{N}': Dynamically determine the minimum field width of the object. | ||||||
|  |  *	         N should be a valid index of the format integral argument. | ||||||
|  |  *	         N is optional, and the default value is automatic indexing. | ||||||
|  |  * | ||||||
|  |  * 3. The precision part: | ||||||
|  |  * | ||||||
|  |  *	- '.N':   The number is used to specify the maximum field width of the object. | ||||||
|  |  *	          N should be an unsigned non-zero decimal number. | ||||||
|  |  *	- '.{N}': Dynamically determine the maximum field width of the object. | ||||||
|  |  *	          N should be a valid index of the format integral argument. | ||||||
|  |  *	          N is optional, and the default value is automatic indexing. | ||||||
|  |  * | ||||||
|  |  * 4. The type indicator part: | ||||||
|  |  * | ||||||
|  |  *	- none: Indicates the as-is formatting. | ||||||
|  |  *	- 'S':  Indicates the as-is formatting. | ||||||
|  |  *	- 's':  Indicates lowercase formatting. | ||||||
|  |  * | ||||||
|  |  * 5. The case indicators part: | ||||||
|  |  * | ||||||
|  |  *	- '!': Indicates capitalize the entire string. | ||||||
|  |  * | ||||||
|  |  * 6. The escape indicators part: | ||||||
|  |  * | ||||||
|  |  *	- '?': Indicates the escape formatting. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | template <CCharType T, typename Allocator> | ||||||
|  | class TFormatter<TString<T, Allocator>, T> : public TFormatter<TStringView<T>, T> { }; | ||||||
|  |  | ||||||
| NAMESPACE_MODULE_END(Utility) | NAMESPACE_MODULE_END(Utility) | ||||||
| NAMESPACE_MODULE_END(Redcraft) | NAMESPACE_MODULE_END(Redcraft) | ||||||
|   | |||||||
| @@ -13,6 +13,7 @@ | |||||||
| #include "Iterators/Sentinel.h" | #include "Iterators/Sentinel.h" | ||||||
| #include "Strings/Char.h" | #include "Strings/Char.h" | ||||||
| #include "Strings/Convert.h" | #include "Strings/Convert.h" | ||||||
|  | #include "Strings/Formatting.h" | ||||||
| #include "Miscellaneous/AssertionMacros.h" | #include "Miscellaneous/AssertionMacros.h" | ||||||
|  |  | ||||||
| #include <cstring> | #include <cstring> | ||||||
| @@ -492,7 +493,7 @@ public: | |||||||
| 	/** @return The non-modifiable standard C character string version of the string view. */ | 	/** @return The non-modifiable standard C character string version of the string view. */ | ||||||
| 	NODISCARD FORCEINLINE auto operator*() const | 	NODISCARD FORCEINLINE auto operator*() const | ||||||
| 	{ | 	{ | ||||||
| 		if (this->Back() == LITERAL(FElementType, '\0') || Contains(LITERAL(FElementType, '\0'))) | 		if (EndsWith(LITERAL(FElementType, '\0')) || Contains(LITERAL(FElementType, '\0'))) | ||||||
| 		{ | 		{ | ||||||
| 			return NAMESPACE_PRIVATE::TCStringFromTStringView<FElementType>(this->GetData(), false); | 			return NAMESPACE_PRIVATE::TCStringFromTStringView<FElementType>(this->GetData(), false); | ||||||
| 		} | 		} | ||||||
| @@ -644,6 +645,634 @@ using FUnicodeStringView = TStringView<unicodechar>; | |||||||
|  |  | ||||||
| // ReSharper restore CppInconsistentNaming | // ReSharper restore CppInconsistentNaming | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * A formatter for TStringView. | ||||||
|  |  * | ||||||
|  |  * The syntax of format specifications is: | ||||||
|  |  * | ||||||
|  |  *	[Fill And Align] [Width] [Precision] [Type] [!] [?] | ||||||
|  |  * | ||||||
|  |  * 1. The fill and align part: | ||||||
|  |  * | ||||||
|  |  *	[Fill Character] <Align Option> | ||||||
|  |  * | ||||||
|  |  *	i.   Fill Character: The character is used to fill width of the object. It is optional and cannot be '{' or '}'. | ||||||
|  |  *	                     It should be representable as a single unicode otherwise it is undefined behavior. | ||||||
|  |  * | ||||||
|  |  *  ii.  Align Option: The character is used to indicate the direction of alignment. | ||||||
|  |  * | ||||||
|  |  *		- '<': Align the formatted argument to the left of the available space | ||||||
|  |  *		       by inserting n fill characters after the formatted argument. | ||||||
|  |  *		       This is default option. | ||||||
|  |  *		- '^': Align the formatted argument to the center of the available space | ||||||
|  |  *		       by inserting n fill characters around the formatted argument. | ||||||
|  |  *		       If cannot absolute centering, offset to the left. | ||||||
|  |  *		- '>': Align the formatted argument ro the right of the available space | ||||||
|  |  *		       by inserting n fill characters before the formatted argument. | ||||||
|  |  * | ||||||
|  |  * 2. The width part: | ||||||
|  |  * | ||||||
|  |  *	- 'N':   The number is used to specify the minimum field width of the object. | ||||||
|  |  *	         N should be an unsigned non-zero decimal number. | ||||||
|  |  *	- '{N}': Dynamically determine the minimum field width of the object. | ||||||
|  |  *	         N should be a valid index of the format integral argument. | ||||||
|  |  *	         N is optional, and the default value is automatic indexing. | ||||||
|  |  * | ||||||
|  |  * 3. The precision part: | ||||||
|  |  * | ||||||
|  |  *	- '.N':   The number is used to specify the maximum field width of the object. | ||||||
|  |  *	          N should be an unsigned non-zero decimal number. | ||||||
|  |  *	- '.{N}': Dynamically determine the maximum field width of the object. | ||||||
|  |  *	          N should be a valid index of the format integral argument. | ||||||
|  |  *	          N is optional, and the default value is automatic indexing. | ||||||
|  |  * | ||||||
|  |  * 4. The type indicator part: | ||||||
|  |  * | ||||||
|  |  *	- none: Indicates the as-is formatting. | ||||||
|  |  *	- 'S':  Indicates the as-is formatting. | ||||||
|  |  *	- 's':  Indicates lowercase formatting. | ||||||
|  |  * | ||||||
|  |  * 5. The case indicators part: | ||||||
|  |  * | ||||||
|  |  *	- '!': Indicates capitalize the entire string. | ||||||
|  |  * | ||||||
|  |  * 6. The escape indicators part: | ||||||
|  |  * | ||||||
|  |  *	- '?': Indicates the escape formatting. | ||||||
|  |  * | ||||||
|  |  */ | ||||||
|  | template <CCharType T> | ||||||
|  | class TFormatter<TStringView<T>, T> | ||||||
|  | { | ||||||
|  | private: | ||||||
|  |  | ||||||
|  | 	using FCharType      = T; | ||||||
|  | 	using FCharTraits    = TChar<FCharType>; | ||||||
|  | 	using FFillCharacter = TStaticArray<FCharType, FCharTraits::MaxCodeUnitLength>; | ||||||
|  |  | ||||||
|  | public: | ||||||
|  |  | ||||||
|  | 	template <CFormatStringContext<FCharType> CTX> | ||||||
|  | 	constexpr TRangeIterator<CTX> Parse(CTX& Context) | ||||||
|  | 	{ | ||||||
|  | 		auto Iter = Ranges::Begin(Context); | ||||||
|  | 		auto Sent = Ranges::End  (Context); | ||||||
|  |  | ||||||
|  | 		// Set the default values. | ||||||
|  | 		{ | ||||||
|  | 			FillUnitLength   = 1; | ||||||
|  | 			FillCharacter[0] = LITERAL(FCharType, ' '); | ||||||
|  | 			AlignOption      = LITERAL(FCharType, '<'); | ||||||
|  |  | ||||||
|  | 			MinFieldWidth =  0; | ||||||
|  | 			MaxFieldWidth = -1; | ||||||
|  |  | ||||||
|  | 			bDynamicMin = false; | ||||||
|  | 			bDynamicMax = false; | ||||||
|  |  | ||||||
|  | 			bLowercase = false; | ||||||
|  | 			bUppercase = false; | ||||||
|  | 			bEscape    = false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// If the format description string is empty. | ||||||
|  | 		if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 		FCharType Char = *Iter; ++Iter; | ||||||
|  |  | ||||||
|  | 		// Try to parse the fill and align part. | ||||||
|  | 		// This code assumes that the format string does not contain multi-unit characters, except for fill character. | ||||||
|  |  | ||||||
|  | 		// If the fill character is multi-unit. | ||||||
|  | 		if (!FCharTraits::IsValid(Char)) | ||||||
|  | 		{ | ||||||
|  | 			FillUnitLength   = 1; | ||||||
|  | 			FillCharacter[0] = Char; | ||||||
|  |  | ||||||
|  | 			while (true) | ||||||
|  | 			{ | ||||||
|  | 				if (Iter == Sent) UNLIKELY | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 					return Iter; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				Char = *Iter; ++Iter; | ||||||
|  |  | ||||||
|  | 				// If the fill character ends. | ||||||
|  | 				if (FillUnitLength == FCharTraits::MaxCodeUnitLength || FCharTraits::IsValid(Char)) break; | ||||||
|  |  | ||||||
|  | 				FillCharacter[FillUnitLength++] = Char; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (Char != LITERAL(FCharType, '<') && Char != LITERAL(FCharType, '^') && Char != LITERAL(FCharType, '>')) UNLIKELY | ||||||
|  | 			{ | ||||||
|  | 				checkf(false, TEXT("Illegal format string. The fill character is not representable as a single unicode.")); | ||||||
|  |  | ||||||
|  | 				return Iter; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			AlignOption = Char; | ||||||
|  |  | ||||||
|  | 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 			Char = *Iter; ++Iter; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// If the fill character is single-unit. | ||||||
|  | 		else do | ||||||
|  | 		{ | ||||||
|  | 			if (Iter == Sent) break; | ||||||
|  |  | ||||||
|  | 			// If the fill character is specified. | ||||||
|  | 			if (*Iter == LITERAL(FCharType, '<') || *Iter == LITERAL(FCharType, '^') || *Iter == LITERAL(FCharType, '>')) | ||||||
|  | 			{ | ||||||
|  | 				FillUnitLength   = 1; | ||||||
|  | 				FillCharacter[0] = Char; | ||||||
|  |  | ||||||
|  | 				Char = *Iter; ++Iter; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// If the fill character is not specified and the align option is not specified. | ||||||
|  | 			else if (Char != LITERAL(FCharType, '<') && Char != LITERAL(FCharType, '^') && Char != LITERAL(FCharType, '>')) break; | ||||||
|  |  | ||||||
|  | 			AlignOption = Char; | ||||||
|  |  | ||||||
|  | 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 			Char = *Iter; ++Iter; | ||||||
|  | 		} | ||||||
|  | 		while (false); | ||||||
|  |  | ||||||
|  | 		// Try to parse the width part. | ||||||
|  | 		{ | ||||||
|  | 			if (Char == LITERAL(FCharType, '{')) | ||||||
|  | 			{ | ||||||
|  | 				bDynamicMin   = true; | ||||||
|  | 				MinFieldWidth = INDEX_NONE; | ||||||
|  |  | ||||||
|  | 				if (Iter == Sent) UNLIKELY | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 					return Iter; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				Char = *Iter; ++Iter; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if ((bDynamicMin || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char)) | ||||||
|  | 			{ | ||||||
|  | 				MinFieldWidth = FCharTraits::ToDigit(Char); | ||||||
|  |  | ||||||
|  | 				while (true) | ||||||
|  | 				{ | ||||||
|  | 					if (Iter == Sent) | ||||||
|  | 					{ | ||||||
|  | 						checkf(!bDynamicMin, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 						return Iter; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if (!bDynamicMin && *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 					Char = *Iter; ++Iter; | ||||||
|  |  | ||||||
|  | 					const uint Digit = FCharTraits::ToDigit(Char); | ||||||
|  |  | ||||||
|  | 					if (Digit >= 10) break; | ||||||
|  |  | ||||||
|  | 					MinFieldWidth = MinFieldWidth * 10 + Digit; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (bDynamicMin) | ||||||
|  | 			{ | ||||||
|  | 				if (Char != LITERAL(FCharType, '}')) UNLIKELY | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 					return Iter; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				do | ||||||
|  | 				{ | ||||||
|  | 					// Try to automatic indexing. | ||||||
|  | 					if (MinFieldWidth == INDEX_NONE) | ||||||
|  | 					{ | ||||||
|  | 						MinFieldWidth = Context.GetNextIndex(); | ||||||
|  |  | ||||||
|  | 						if (MinFieldWidth == INDEX_NONE) UNLIKELY | ||||||
|  | 						{ | ||||||
|  | 							checkf(false, TEXT("Illegal index. Please check the field width.")); | ||||||
|  | 						} | ||||||
|  | 						else break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Try to manual indexing. | ||||||
|  | 					else if (!Context.CheckIndex(MinFieldWidth)) UNLIKELY | ||||||
|  | 					{ | ||||||
|  | 						checkf(false, TEXT("Illegal index. Please check the field width.")); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					else break; | ||||||
|  |  | ||||||
|  | 					bDynamicMin   = false; | ||||||
|  | 					MinFieldWidth = 0; | ||||||
|  | 				} | ||||||
|  | 				while (false); | ||||||
|  |  | ||||||
|  | 				if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 				Char = *Iter; ++Iter; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Try to parse the precision part. | ||||||
|  | 		if (Char == LITERAL(FCharType, '.')) | ||||||
|  | 		{ | ||||||
|  | 			if (Iter == Sent) UNLIKELY | ||||||
|  | 			{ | ||||||
|  | 				checkf(false, TEXT("Illegal format string. Missing precision in format string.")); | ||||||
|  |  | ||||||
|  | 				return Iter; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			Char = *Iter; ++Iter; | ||||||
|  |  | ||||||
|  | 			if (Char == LITERAL(FCharType, '{')) | ||||||
|  | 			{ | ||||||
|  | 				bDynamicMax   = true; | ||||||
|  | 				MaxFieldWidth = INDEX_NONE; | ||||||
|  |  | ||||||
|  | 				if (Iter == Sent) UNLIKELY | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 					return Iter; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				Char = *Iter; ++Iter; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if ((bDynamicMax || Char != LITERAL(FCharType, '0')) && FCharTraits::IsDigit(Char)) | ||||||
|  | 			{ | ||||||
|  | 				MaxFieldWidth = FCharTraits::ToDigit(Char); | ||||||
|  |  | ||||||
|  | 				while (true) | ||||||
|  | 				{ | ||||||
|  | 					if (Iter == Sent) | ||||||
|  | 					{ | ||||||
|  | 						checkf(!bDynamicMax, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 						return Iter; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					if (!bDynamicMax && *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 					Char = *Iter; ++Iter; | ||||||
|  |  | ||||||
|  | 					const uint Digit = FCharTraits::ToDigit(Char); | ||||||
|  |  | ||||||
|  | 					if (Digit >= 10) break; | ||||||
|  |  | ||||||
|  | 					MaxFieldWidth = MaxFieldWidth * 10 + Digit; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			else if (!bDynamicMax) | ||||||
|  | 			{ | ||||||
|  | 				checkf(false, TEXT("Illegal format string. Missing precision in format string.")); | ||||||
|  |  | ||||||
|  | 				return Iter; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (bDynamicMax) | ||||||
|  | 			{ | ||||||
|  | 				if (Char != LITERAL(FCharType, '}')) UNLIKELY | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 					return Iter; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				do | ||||||
|  | 				{ | ||||||
|  | 					// Try to automatic indexing. | ||||||
|  | 					if (MaxFieldWidth == INDEX_NONE) | ||||||
|  | 					{ | ||||||
|  | 						MaxFieldWidth = Context.GetNextIndex(); | ||||||
|  |  | ||||||
|  | 						if (MaxFieldWidth == INDEX_NONE) UNLIKELY | ||||||
|  | 						{ | ||||||
|  | 							checkf(false, TEXT("Illegal index. Please check the precision.")); | ||||||
|  | 						} | ||||||
|  | 						else break; | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					// Try to manual indexing. | ||||||
|  | 					else if (!Context.CheckIndex(MaxFieldWidth)) UNLIKELY | ||||||
|  | 					{ | ||||||
|  | 						checkf(false, TEXT("Illegal index. Please check the precision.")); | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					else break; | ||||||
|  |  | ||||||
|  | 					bDynamicMax   = false; | ||||||
|  | 					MaxFieldWidth = -1; | ||||||
|  | 				} | ||||||
|  | 				while (false); | ||||||
|  |  | ||||||
|  | 				if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 				Char = *Iter; ++Iter; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Try to parse the type indicators part. | ||||||
|  |  | ||||||
|  | 		switch (Char) | ||||||
|  | 		{ | ||||||
|  | 		case LITERAL(FCharType, 's'): bLowercase = true; break; | ||||||
|  | 		default: { } | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		switch (Char) | ||||||
|  | 		{ | ||||||
|  | 		case LITERAL(FCharType, 'S'): | ||||||
|  | 		case LITERAL(FCharType, 's'): if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; Char = *Iter; ++Iter; break; | ||||||
|  | 		default: { } | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Try to parse the case indicators part. | ||||||
|  | 		if (Char == LITERAL(FCharType, '!')) | ||||||
|  | 		{ | ||||||
|  | 			bUppercase = true; | ||||||
|  |  | ||||||
|  | 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 			Char = *Iter; ++Iter; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Try to parse the escape indicators part. | ||||||
|  | 		if (Char == LITERAL(FCharType, '?')) | ||||||
|  | 		{ | ||||||
|  | 			bEscape = true; | ||||||
|  |  | ||||||
|  | 			if (Iter == Sent || *Iter == LITERAL(FCharType, '}')) return Iter; | ||||||
|  |  | ||||||
|  | 			Char = *Iter; ++Iter; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		checkf(false, TEXT("Illegal format string. Missing '}' in format string.")); | ||||||
|  |  | ||||||
|  | 		return Iter; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	template <CFormatObjectContext<FCharType> CTX> | ||||||
|  | 	constexpr TRangeIterator<CTX> Format(TStringView<FCharType> Object, CTX& Context) const | ||||||
|  | 	{ | ||||||
|  | 		auto Iter = Ranges::Begin(Context); | ||||||
|  | 		auto Sent = Ranges::End  (Context); | ||||||
|  |  | ||||||
|  | 		size_t MinDynamicField = MinFieldWidth; | ||||||
|  | 		size_t MaxDynamicField = MaxFieldWidth; | ||||||
|  |  | ||||||
|  | 		// Visit the dynamic width argument. | ||||||
|  | 		if (bDynamicMin) | ||||||
|  | 		{ | ||||||
|  | 			MinDynamicField = Context.Visit([]<typename U>(U&& Value) -> size_t | ||||||
|  | 			{ | ||||||
|  | 				using FDecayU = TRemoveCVRef<U>; | ||||||
|  |  | ||||||
|  | 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>) | ||||||
|  | 				{ | ||||||
|  | 					checkf(Value > 0, TEXT("Illegal format argument. The dynamic width argument must be a unsigned non-zero number.")); | ||||||
|  |  | ||||||
|  | 					return Math::Max(Value, 1); | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format argument. The dynamic width argument must be an integral.")); | ||||||
|  |  | ||||||
|  | 					return 0; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			, MinFieldWidth); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Visit the dynamic precision argument. | ||||||
|  | 		if (bDynamicMax) | ||||||
|  | 		{ | ||||||
|  | 			MaxDynamicField = Context.Visit([]<typename U>(U&& Value) -> size_t | ||||||
|  | 			{ | ||||||
|  | 				using FDecayU = TRemoveCVRef<U>; | ||||||
|  |  | ||||||
|  | 				if constexpr (CIntegral<FDecayU> && !CSameAs<FDecayU, bool>) | ||||||
|  | 				{ | ||||||
|  | 					checkf(Value > 0, TEXT("Illegal format argument. The dynamic precision argument must be a unsigned non-zero number.")); | ||||||
|  |  | ||||||
|  | 					return Math::Max(Value, 1); | ||||||
|  | 				} | ||||||
|  | 				else | ||||||
|  | 				{ | ||||||
|  | 					checkf(false, TEXT("Illegal format argument. The dynamic precision argument must be an integral.")); | ||||||
|  |  | ||||||
|  | 					return 0; | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			, MaxFieldWidth); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		size_t LeftPadding  = 0; | ||||||
|  | 		size_t RightPadding = 0; | ||||||
|  |  | ||||||
|  | 		// Estimate the field width. | ||||||
|  | 		if (MinDynamicField != 0) | ||||||
|  | 		{ | ||||||
|  | 			// If escape formatting is enabled, add quotes characters. | ||||||
|  | 			size_t FieldWidth = bEscape ? 2 : 0; | ||||||
|  |  | ||||||
|  | 			for (auto ObjectIter = Object.Begin(); ObjectIter != Object.End(); ++ObjectIter) | ||||||
|  | 			{ | ||||||
|  | 				if (bEscape) | ||||||
|  | 				{ | ||||||
|  | 					switch (const FCharType Char = *ObjectIter) | ||||||
|  | 					{ | ||||||
|  | 					case LITERAL(FCharType, '\"'): | ||||||
|  | 					case LITERAL(FCharType, '\\'): | ||||||
|  | 					case LITERAL(FCharType, '\a'): | ||||||
|  | 					case LITERAL(FCharType, '\b'): | ||||||
|  | 					case LITERAL(FCharType, '\f'): | ||||||
|  | 					case LITERAL(FCharType, '\n'): | ||||||
|  | 					case LITERAL(FCharType, '\r'): | ||||||
|  | 					case LITERAL(FCharType, '\t'): | ||||||
|  | 					case LITERAL(FCharType, '\v'): FieldWidth += 2; break; | ||||||
|  | 					default: | ||||||
|  | 						{ | ||||||
|  | 							// Use '\x00' format for other non-printable characters. | ||||||
|  | 							if (!FCharTraits::IsASCII(Char) || !FCharTraits::IsPrint(Char)) | ||||||
|  | 							{ | ||||||
|  | 								FieldWidth += 2 + sizeof(FCharType) * 2; | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							else ++FieldWidth; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				else ++FieldWidth; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			const size_t PaddingWidth = MinDynamicField - Math::Min(FieldWidth, MinDynamicField, MaxDynamicField); | ||||||
|  |  | ||||||
|  | 			switch (AlignOption) | ||||||
|  | 			{ | ||||||
|  | 			default: | ||||||
|  | 			case LITERAL(FCharType, '<'): RightPadding = PaddingWidth; break; | ||||||
|  | 			case LITERAL(FCharType, '>'): LeftPadding  = PaddingWidth; break; | ||||||
|  | 			case LITERAL(FCharType, '^'): | ||||||
|  | 				LeftPadding  = Math::DivAndFloor(PaddingWidth, 2); | ||||||
|  | 				RightPadding = PaddingWidth - LeftPadding; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Write the left padding. | ||||||
|  | 		for (size_t Index = 0; Index != LeftPadding; ++Index) | ||||||
|  | 		{ | ||||||
|  | 			for (size_t Jndex = 0; Jndex != FillUnitLength; ++Jndex) | ||||||
|  | 			{ | ||||||
|  | 				if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
|  | 				*Iter++ = FillCharacter[Jndex]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Write the left quote. | ||||||
|  | 		if (bEscape) | ||||||
|  | 		{ | ||||||
|  | 			if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
|  | 			*Iter++ = LITERAL(FCharType, '\"'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		auto ObjectIter = Object.Begin(); | ||||||
|  |  | ||||||
|  | 		bool bComplete = false; | ||||||
|  |  | ||||||
|  | 		// Write the object, include escaped quotes in the counter. | ||||||
|  | 		for (size_t Index = bEscape ? 1 : 0; Index != MaxDynamicField; ++Index) | ||||||
|  | 		{ | ||||||
|  | 			if (ObjectIter == Object.End()) | ||||||
|  | 			{ | ||||||
|  | 				bComplete = true; | ||||||
|  |  | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			FCharType Char = *ObjectIter++; | ||||||
|  |  | ||||||
|  | 			if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
|  | 			// Convert the character case. | ||||||
|  | 			if (bLowercase) Char = FCharTraits::ToLower(Char); | ||||||
|  | 			if (bUppercase) Char = FCharTraits::ToUpper(Char); | ||||||
|  |  | ||||||
|  | 			if (bEscape) | ||||||
|  | 			{ | ||||||
|  | 				switch (Char) | ||||||
|  | 				{ | ||||||
|  | 				case LITERAL(FCharType, '\"'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, '\"'); break; | ||||||
|  | 				case LITERAL(FCharType, '\\'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, '\\'); break; | ||||||
|  | 				case LITERAL(FCharType, '\a'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'a');  break; | ||||||
|  | 				case LITERAL(FCharType, '\b'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'b');  break; | ||||||
|  | 				case LITERAL(FCharType, '\f'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'f');  break; | ||||||
|  | 				case LITERAL(FCharType, '\n'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'n');  break; | ||||||
|  | 				case LITERAL(FCharType, '\r'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'r');  break; | ||||||
|  | 				case LITERAL(FCharType, '\t'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 't');  break; | ||||||
|  | 				case LITERAL(FCharType, '\v'): *Iter++ = LITERAL(FCharType, '\\'); *Iter++ = LITERAL(FCharType, 'v');  break; | ||||||
|  | 				default: | ||||||
|  | 					{ | ||||||
|  | 						// Use '\x00' format for other non-printable characters. | ||||||
|  | 						if (!FCharTraits::IsASCII(Char) || !FCharTraits::IsPrint(Char)) | ||||||
|  | 						{ | ||||||
|  | 							*Iter++ = LITERAL(FCharType, '\\'); | ||||||
|  | 							*Iter++ = LITERAL(FCharType, 'x' ); | ||||||
|  |  | ||||||
|  | 							using FUnsignedT = TMakeUnsigned<FCharType>; | ||||||
|  |  | ||||||
|  | 							constexpr size_t DigitNum = sizeof(FCharType) * 2; | ||||||
|  |  | ||||||
|  | 							FUnsignedT IntValue = static_cast<FUnsignedT>(Char); | ||||||
|  |  | ||||||
|  | 							TStaticArray<FCharType, DigitNum> Buffer; | ||||||
|  |  | ||||||
|  | 							for (size_t Jndex = 0; Jndex != DigitNum; ++Jndex) | ||||||
|  | 							{ | ||||||
|  | 								Buffer[DigitNum - Jndex - 1] = FCharTraits::FromDigit(IntValue & 0xF); | ||||||
|  |  | ||||||
|  | 								IntValue >>= 4; | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							check(IntValue == 0); | ||||||
|  |  | ||||||
|  | 							for (size_t Jndex = 0; Jndex != DigitNum; ++Jndex) | ||||||
|  | 							{ | ||||||
|  | 								if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
|  | 								*Iter++ = Buffer[Jndex]; | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  |  | ||||||
|  | 						else *Iter++ = Char; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			else *Iter++ = Char; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Write the right quote, if the field width is enough. | ||||||
|  | 		if (bEscape && bComplete) | ||||||
|  | 		{ | ||||||
|  | 			if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
|  | 			*Iter++ = LITERAL(FCharType, '\"'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Write the right padding. | ||||||
|  | 		for (size_t Index = 0; Index != RightPadding; ++Index) | ||||||
|  | 		{ | ||||||
|  | 			for (size_t Jndex = 0; Jndex != FillUnitLength; ++Jndex) | ||||||
|  | 			{ | ||||||
|  | 				if (Iter == Sent) UNLIKELY return Iter; | ||||||
|  |  | ||||||
|  | 				*Iter++ = FillCharacter[Jndex]; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return Iter; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |  | ||||||
|  | 	size_t         FillUnitLength = 1; | ||||||
|  | 	FFillCharacter FillCharacter  = { LITERAL(FCharType, ' ') }; | ||||||
|  | 	FCharType      AlignOption    =   LITERAL(FCharType, '<'); | ||||||
|  |  | ||||||
|  | 	size_t MinFieldWidth =  0; | ||||||
|  | 	size_t MaxFieldWidth = -1; | ||||||
|  |  | ||||||
|  | 	bool bDynamicMin = false; | ||||||
|  | 	bool bDynamicMax = false; | ||||||
|  |  | ||||||
|  | 	bool bLowercase = false; | ||||||
|  | 	bool bUppercase = false; | ||||||
|  | 	bool bEscape    = false; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
| NAMESPACE_MODULE_END(Utility) | NAMESPACE_MODULE_END(Utility) | ||||||
| NAMESPACE_MODULE_END(Redcraft) | NAMESPACE_MODULE_END(Redcraft) | ||||||
| NAMESPACE_REDCRAFT_END | NAMESPACE_REDCRAFT_END | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user