Compare commits
20 Commits
e498b39d3d
...
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 | |||
68cd2c310a | |||
594acf219a |
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