Compare commits
	
		
			18 Commits
		
	
	
		
			68cd2c310a
			...
			0.1.0-expe
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 6cc31f660c | |||
| 2d79e18b25 | |||
| 1d5a61f308 | |||
| 38e1c3f8b7 | |||
| 8d3fa1ac98 | |||
| 88e330336a | |||
| eca0d90d98 | |||
| d8adf47d10 | |||
| 8a834a9c05 | |||
| 35f0ba71ab | |||
| db7a40cb30 | |||
| 6a70f0c501 | |||
| 5629e31d49 | |||
| c16da1af53 | |||
| 88aba14620 | |||
| 23963e0389 | |||
| 0c6f33762a | |||
| 9024957ff2 | 
							
								
								
									
										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)
 | 
			
		||||
@@ -565,29 +565,37 @@ void TestConvert()
 | 
			
		||||
	Test(InPlaceType<unicodechar>);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void TestStringConversion()
 | 
			
		||||
void TestFormatting()
 | 
			
		||||
{
 | 
			
		||||
	auto Test = []<typename T>(TInPlaceType<T>)
 | 
			
		||||
	{
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), true ) == LITERAL(T, "#True#" ));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), false) == LITERAL(T, "#False#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), LITERAL(T, "Hello, World!")) == LITERAL(T, "#Hello, World!#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), +0) == LITERAL(T, "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"),  0) == LITERAL(T, "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), -0) == LITERAL(T, "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), true ) == LITERAL(T, "#True#" ));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), false) == LITERAL(T, "#False#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), 42) == LITERAL(T, "#42#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), +0) == LITERAL(T, "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"),  0) == LITERAL(T, "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), -0) == LITERAL(T, "#0#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), +0.0) == LITERAL(T,  "#0.000000#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"),  0.0) == LITERAL(T,  "#0.000000#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), -0.0) == LITERAL(T, "#-0.000000#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), 42) == LITERAL(T, "#42#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), 3.14) == LITERAL(T, "#3.140000#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), +0.0) == LITERAL(T,  "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"),  0.0) == LITERAL(T,  "#0#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), -0.0) == LITERAL(T, "#-0#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), +TNumericLimits<float>::Infinity()) == LITERAL(T,  "#Infinity#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), -TNumericLimits<float>::Infinity()) == LITERAL(T, "#-Infinity#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), +TNumericLimits<float>::QuietNaN()) == LITERAL(T,  "#NaN#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{}#"), -TNumericLimits<float>::QuietNaN()) == LITERAL(T, "#-NaN#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), 3.14) == LITERAL(T, "#3.14#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0:.6F}#"), +0.0) == LITERAL(T,  "#0.000000#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0:.6F}#"),  0.0) == LITERAL(T,  "#0.000000#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0:.6F}#"), -0.0) == LITERAL(T, "#-0.000000#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0:.6F}#"), 3.14) == LITERAL(T, "#3.140000#"));
 | 
			
		||||
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), +TNumericLimits<float>::Infinity()) == LITERAL(T,  "#Infinity#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), -TNumericLimits<float>::Infinity()) == LITERAL(T, "#-Infinity#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), +TNumericLimits<float>::QuietNaN()) == LITERAL(T,  "#NaN#"));
 | 
			
		||||
		always_check(TString<T>::Format(LITERAL(T, "#{0}#"), -TNumericLimits<float>::QuietNaN()) == LITERAL(T, "#-NaN#"));
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			always_check(TString<T>::FromBool(true ) == LITERAL(T, "True" ));
 | 
			
		||||
@@ -632,7 +640,7 @@ void TestString()
 | 
			
		||||
	NAMESPACE_PRIVATE::TestStringView();
 | 
			
		||||
	NAMESPACE_PRIVATE::TestString();
 | 
			
		||||
	NAMESPACE_PRIVATE::TestConvert();
 | 
			
		||||
	NAMESPACE_PRIVATE::TestStringConversion();
 | 
			
		||||
	NAMESPACE_PRIVATE::TestFormatting();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NAMESPACE_END(Testing)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
@@ -110,13 +110,13 @@ struct TChar
 | 
			
		||||
		// Windows uses UTF-16 encoding for wchar.
 | 
			
		||||
		else if constexpr (PLATFORM_WINDOWS && (CSameAs<FCharType, wchar>))
 | 
			
		||||
		{
 | 
			
		||||
			return TChar::IsValid(static_cast<u16char>(InChar));
 | 
			
		||||
			return TChar<u16char>::IsValid(static_cast<u16char>(InChar));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Linux uses UTF-32 encoding for wchar.
 | 
			
		||||
		else if constexpr (PLATFORM_LINUX && (CSameAs<FCharType, wchar>))
 | 
			
		||||
		{
 | 
			
		||||
			return TChar::IsValid(static_cast<u32char>(InChar));
 | 
			
		||||
			return TChar<u32char>::IsValid(static_cast<u32char>(InChar));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else static_assert(sizeof(FCharType) == -1, "Unsupported character type");
 | 
			
		||||
@@ -153,13 +153,13 @@ struct TChar
 | 
			
		||||
		// Windows uses UTF-16 encoding for wchar.
 | 
			
		||||
		else if constexpr (PLATFORM_WINDOWS && (CSameAs<FCharType, wchar>))
 | 
			
		||||
		{
 | 
			
		||||
			return TChar::IsNonch(static_cast<u16char>(InChar));
 | 
			
		||||
			return TChar<u16char>::IsNonch(static_cast<u16char>(InChar));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Linux uses UTF-32 encoding for wchar.
 | 
			
		||||
		else if constexpr (PLATFORM_LINUX && (CSameAs<FCharType, wchar>))
 | 
			
		||||
		{
 | 
			
		||||
			return TChar::IsNonch(static_cast<u32char>(InChar));
 | 
			
		||||
			return TChar<u32char>::IsNonch(static_cast<u32char>(InChar));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else static_assert(sizeof(FCharType) == -1, "Unsupported character type");
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -9,12 +9,14 @@
 | 
			
		||||
#include "Containers/Array.h"
 | 
			
		||||
#include "Containers/ArrayView.h"
 | 
			
		||||
#include "Iterators/Utility.h"
 | 
			
		||||
#include "Iterators/BasicIterator.h"
 | 
			
		||||
#include "Iterators/Sentinel.h"
 | 
			
		||||
#include "Iterators/BasicIterator.h"
 | 
			
		||||
#include "Iterators/InsertIterator.h"
 | 
			
		||||
#include "Ranges/Utility.h"
 | 
			
		||||
#include "Ranges/Factory.h"
 | 
			
		||||
#include "Strings/Char.h"
 | 
			
		||||
#include "Strings/StringView.h"
 | 
			
		||||
#include "Strings/Formatting.h"
 | 
			
		||||
#include "Miscellaneous/AssertionMacros.h"
 | 
			
		||||
 | 
			
		||||
#include <locale>
 | 
			
		||||
@@ -1122,10 +1124,7 @@ public:
 | 
			
		||||
	/** @return The non-modifiable standard C character string version of the string. */
 | 
			
		||||
	NODISCARD FORCEINLINE auto operator*() &&
 | 
			
		||||
	{
 | 
			
		||||
		if (this->Back() != LITERAL(T, '\0'))
 | 
			
		||||
		{
 | 
			
		||||
			this->PushBack(LITERAL(T, '\0'));
 | 
			
		||||
		}
 | 
			
		||||
		if (!EndsWith(LITERAL(T, '\0'))) this->PushBack(LITERAL(T, '\0'));
 | 
			
		||||
 | 
			
		||||
		return AsConst(*this).GetData();
 | 
			
		||||
	}
 | 
			
		||||
@@ -1231,7 +1230,7 @@ public:
 | 
			
		||||
	 * @return The string containing the integer value.
 | 
			
		||||
	 */
 | 
			
		||||
	template <CIntegral U = int> requires (!CSameAs<U, bool> && !CConst<U> && !CVolatile<U>)
 | 
			
		||||
	NODISCARD static FORCEINLINE TString FromInt(U Value, unsigned Base = 10)
 | 
			
		||||
	NODISCARD static FORCEINLINE TString FromInt(U Value, uint Base = 10)
 | 
			
		||||
	{
 | 
			
		||||
		checkf(Base >= 2 && Base <= 36, TEXT("Illegal base. Please check the base."));
 | 
			
		||||
 | 
			
		||||
@@ -1289,7 +1288,7 @@ public:
 | 
			
		||||
	 * @return
 | 
			
		||||
	 */
 | 
			
		||||
	template <CFloatingPoint U = float> requires (!CConst<U> && !CVolatile<U>)
 | 
			
		||||
	NODISCARD static FORCEINLINE TString FromFloat(U Value, bool bFixed, bool bScientific, unsigned Precision)
 | 
			
		||||
	NODISCARD static FORCEINLINE TString FromFloat(U Value, bool bFixed, bool bScientific, uint Precision)
 | 
			
		||||
	{
 | 
			
		||||
		TString Result;
 | 
			
		||||
 | 
			
		||||
@@ -1299,23 +1298,84 @@ public:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** Converts a boolean value into a string and appends it to the string. */
 | 
			
		||||
	void AppendBool(bool Value);
 | 
			
		||||
	FORCEINLINE void AppendBool(bool Value)
 | 
			
		||||
	{
 | 
			
		||||
		auto Inserter = Ranges::View(MakeBackInserter(*this), UnreachableSentinel);
 | 
			
		||||
 | 
			
		||||
		Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0}"), Value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** Converts an integer value into a string and appends it to the string. */
 | 
			
		||||
	template <CIntegral U = int> requires (!CSameAs<U, bool> && !CConst<U> && !CVolatile<U>)
 | 
			
		||||
	void AppendInt(U Value, unsigned Base = 10);
 | 
			
		||||
	FORCEINLINE void AppendInt(U Value, uint Base = 10)
 | 
			
		||||
	{
 | 
			
		||||
		auto Inserter = Ranges::View(MakeBackInserter(*this), UnreachableSentinel);
 | 
			
		||||
 | 
			
		||||
		Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:_{1}I}"), Value, Base);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** Converts a floating-point value into a string and appends it to the string. */
 | 
			
		||||
	template <CFloatingPoint U = float> requires (!CConst<U> && !CVolatile<U>)
 | 
			
		||||
	void AppendFloat(U Value);
 | 
			
		||||
	FORCEINLINE void AppendFloat(U Value)
 | 
			
		||||
	{
 | 
			
		||||
		auto Inserter = Ranges::View(MakeBackInserter(*this), UnreachableSentinel);
 | 
			
		||||
 | 
			
		||||
		Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0}"), Value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** Converts a floating-point value into a string and appends it to the string. */
 | 
			
		||||
	template <CFloatingPoint U = float> requires (!CConst<U> && !CVolatile<U>)
 | 
			
		||||
	void AppendFloat(U Value, bool bFixed, bool bScientific);
 | 
			
		||||
	FORCEINLINE void AppendFloat(U Value, bool bFixed, bool bScientific)
 | 
			
		||||
	{
 | 
			
		||||
		auto Inserter = Ranges::View(MakeBackInserter(*this), UnreachableSentinel);
 | 
			
		||||
 | 
			
		||||
		if (bFixed && bScientific)
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:G}"), Value);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (bFixed)
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:F}"), Value);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (bScientific)
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:E}"), Value);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:A}"), Value);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/** Converts a floating-point value into a string and appends it to the string. */
 | 
			
		||||
	template <CFloatingPoint U = float> requires (!CConst<U> && !CVolatile<U>)
 | 
			
		||||
	void AppendFloat(U Value, bool bFixed, bool bScientific, unsigned Precision);
 | 
			
		||||
	FORCEINLINE void AppendFloat(U Value, bool bFixed, bool bScientific, uint Precision)
 | 
			
		||||
	{
 | 
			
		||||
		auto Inserter = Ranges::View(MakeBackInserter(*this), UnreachableSentinel);
 | 
			
		||||
 | 
			
		||||
		if (bFixed && bScientific)
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:.{1}G}"), Value, Precision);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (bFixed)
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:.{1}F}"), Value, Precision);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (bScientific)
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:.{1}E}"), Value, Precision);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			Algorithms::Format(Inserter, LITERAL_VIEW(FElementType, "{0:.{1}A}"), Value, Precision);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
@@ -1339,7 +1399,12 @@ public:
 | 
			
		||||
 | 
			
		||||
	/** Format some objects using a format string and append to the string. */
 | 
			
		||||
	template <typename... Ts>
 | 
			
		||||
	void AppendFormat(TStringView<FElementType> Fmt, const Ts&... Args);
 | 
			
		||||
	FORCEINLINE void AppendFormat(TStringView<FElementType> Fmt, const Ts&... Args)
 | 
			
		||||
	{
 | 
			
		||||
		auto Inserter = Ranges::View(MakeBackInserter(*this), UnreachableSentinel);
 | 
			
		||||
 | 
			
		||||
		Algorithms::Format(Inserter, Fmt, Args...);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
 | 
			
		||||
@@ -1374,10 +1439,68 @@ using FU32String     = TString<u32char>;
 | 
			
		||||
using FUnicodeString = TString<unicodechar>;
 | 
			
		||||
 | 
			
		||||
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(Redcraft)
 | 
			
		||||
NAMESPACE_REDCRAFT_END
 | 
			
		||||
 | 
			
		||||
#include "Strings/Conversion.h.inl"
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@
 | 
			
		||||
#include "Iterators/Sentinel.h"
 | 
			
		||||
#include "Strings/Char.h"
 | 
			
		||||
#include "Strings/Convert.h"
 | 
			
		||||
#include "Strings/Formatting.h"
 | 
			
		||||
#include "Miscellaneous/AssertionMacros.h"
 | 
			
		||||
 | 
			
		||||
#include <cstring>
 | 
			
		||||
@@ -492,7 +493,7 @@ public:
 | 
			
		||||
	/** @return The non-modifiable standard C character string version of the string view. */
 | 
			
		||||
	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);
 | 
			
		||||
		}
 | 
			
		||||
@@ -644,6 +645,634 @@ using FUnicodeStringView = TStringView<unicodechar>;
 | 
			
		||||
 | 
			
		||||
// 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(Redcraft)
 | 
			
		||||
NAMESPACE_REDCRAFT_END
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user