diff --git a/src/ConsoleHelper.cpp b/src/ConsoleHelper.cpp index a5eeee2..a6ba727 100644 --- a/src/ConsoleHelper.cpp +++ b/src/ConsoleHelper.cpp @@ -127,10 +127,10 @@ namespace YYCC::ConsoleHelper { return real_return_buffer; } - static void WinConsoleWrite(const std::string& strl) { + static void WinConsoleWrite(const std::string& strl, bool to_stderr) { // Prepare some Win32 variables // fetch stdout handle first - HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE); DWORD dwConsoleMode; DWORD dwWrittenNumberOfChars; @@ -198,35 +198,80 @@ namespace YYCC::ConsoleHelper { #endif } - static void RawWrite(const std::string& strl) { + template + static void RawWrite(const char* u8_fmt, va_list argptr) { + // Buiild string need to be written first + // If no format string or plain string for writing, return. + if (u8_fmt == nullptr) return; + // Build or simply copy string + std::string strl; + if constexpr (bNeedFmt) { + // treat as format string + va_list argcpy; + va_copy(argcpy, argptr); + strl = YYCC::StringHelper::VPrintf(u8_fmt, argcpy); + va_end(argcpy); + } else { + // treat as plain string + strl = u8_fmt; + } + // Checkout whether add EOL + if constexpr (bHasEOL) { + strl += "\n"; + } + #if YYCC_OS == YYCC_OS_WINDOWS - // call Windows specific writer - WinConsoleWrite(strl); - + WinConsoleWrite(strl, bIsErr); #else - // in linux, directly use C function to write. - std::fputs(strl.c_str(), stdout); - + std::fputs(strl.c_str(), to_stderr ? stderr : stdout); #endif } - void Write(const char* u8_fmt, ...) { + void Format(const char* u8_fmt, ...) { va_list argptr; va_start(argptr, u8_fmt); - RawWrite(YYCC::StringHelper::VPrintf(u8_fmt, argptr)); + RawWrite(u8_fmt, argptr); va_end(argptr); } - void WriteLine(const char* u8_fmt, ...) { + void FormatLine(const char* u8_fmt, ...) { va_list argptr; va_start(argptr, u8_fmt); - std::string cache(YYCC::StringHelper::VPrintf(u8_fmt, argptr)); - cache += "\n"; - RawWrite(cache); + RawWrite(u8_fmt, argptr); va_end(argptr); } + void Write(const char* u8_strl) { + RawWrite(u8_strl, va_list()); + } + + void WriteLine(const char* u8_strl) { + RawWrite(u8_strl, va_list()); + } + + void ErrFormat(const char* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + RawWrite(u8_fmt, argptr); + va_end(argptr); + } + + void ErrFormatLine(const char* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + RawWrite(u8_fmt, argptr); + va_end(argptr); + } + + void ErrWrite(const char* u8_strl) { + RawWrite(u8_strl, va_list()); + } + + void ErrWriteLine(const char* u8_strl) { + RawWrite(u8_strl, va_list()); + } + } diff --git a/src/ConsoleHelper.hpp b/src/ConsoleHelper.hpp index a72e5dc..683dbbc 100644 --- a/src/ConsoleHelper.hpp +++ b/src/ConsoleHelper.hpp @@ -4,8 +4,66 @@ #include #include +/** + * @brief The namespace providing universal Console visiting functions like C-Sharp Console class. + * @details + * \par Why this Namespace + * Windows console doesn't support UTF8 very well. + * The standard input output functions can not work properly on Windows with UTF8. + * So we create this namespace and provide various console-related functions + * to patch Windows console and let it more like the console in other platforms. + * \par + * The function provided in this function can be called in any platforms. + * In Windows, the implementation will use Windows native function, + * and in other platform, the implementation will redirect request to standard C function + * like std::fputs and etc. + * So the programmer do not need to be worried about which function should they use, + * and don't need to use macro to use different IO function in different platforms. + * It is just enough that fully use the functions provided in this namespace. + * \par + * All IO functions this namespace provided are UTF8-based. + * It also means that input output string should always be UTF8 encoded. + * + * \par Input Functions + * Please note that EOL will automatically converted into LF on Windows platform, not CRLF. + * This action actually is removing all CR chars in result string. + * This behavior affect nothing in most cases but it still is possible break something in some special case. + * \par + * Due to implementation, if you decide to use this function, + * you should give up using any other function to read stdin stream, + * such as std::gets() and std::cin. + * Because this function may read chars which is more than needed. + * These extra chars will be stored in this function and can be used next calling. + * But these chars can not be visited by stdin again. + * This behavior may cause bug. + * So if you decide using this function, stick on it and do not change. + * \par + * Due to implementation, this function do not support hot switch of stdin. + * It means that stdin can be redirected before first calling of this function, + * but it should not be redirected during program running. + * The reason is the same one introduced above. + * + * \par Output Functions + * In current implementation, EOL will not be converted automatically to CRLF. + * This is different with other stream read functions provided in this namespace. + * \par + * Comparing with other stream read functions provided in this namespace, + * stream write function support hot switch of stdout and stderr. + * Because they do not have internal buffer storing something. + * \par + * In this namespace, there are various stream write function. + * There is a list telling you how to choose one from them for using: + * \li Functions with leading "Err" will write data into stderr, + * otherwise they will write data into stdout. + * \li Functions with embedded "Format" are output functions with format feature + * like std::fprintf(), otherwise the functions with embedded "Write" will + * only write plain string like std::fputs(). + * \li Functions with trailing "Line" will write extra EOL to break current line. + * This is commonly used, otherwise functions will only write the text provided by arguments, + * without adding something. +*/ namespace YYCC::ConsoleHelper { - + #define YYCC_COLORHDR_BLACK "\033[30m" #define YYCC_COLORHDR_RED "\033[31m" #define YYCC_COLORHDR_GREEN "\033[32m" @@ -53,57 +111,65 @@ namespace YYCC::ConsoleHelper { * because we assume all terminals existing on other platform support color feature as default. */ bool EnableColorfulConsole(); - + /** * @brief Universal console read function - * @details This function is more like C# Console.ReadLine(). + * @return + * The UTF8 encoded string this function read. EOL is excluded. + * Empty string if user just press Enter key or function failed. + * @remarks + * This function is more like C# Console.ReadLine(). * It read user input with UTF8 encoding until reaching EOL. - * - * This function provide an universal, platform-independent way to read UTF8 string from console, - * no matter whether it is redirected. - * @return The UTF8 encoded string this function read. EOL is excluded. - * @remarks In Windows, this function will try use native Win32 function for reading, - * because standard C/C++ function can not handle UTF8 input on Windows normally. - * In other platforms, this function will redirect request to std::readline with std::cin, - * which is all programmer commonly used method. - * It also mean that we assume stdin is encoded by UTF8 on these platforms. - * - * Please note that EOL will automatically converted into LF on Windows platform, not CRLF. - * This action is actually remove all CR chars in result string. - * + * \par * This function also can be used as ordering user press Enter key by * simply calling this function and ignoring its return value. */ std::string ReadLine(); + /** - * @brief Universal console write function - * @details This function is more like C# Console.Write(). - * It write user given UTF8 string into console. - * - * This function provide an universal, platform-independent way to write UTF8 string into console, - * no matter whether it is redirected. - * @param u8_fmt[in] The format string. - * If you just want to write a pure string, you should escape formatter chars (%) in this string, - * because this function always take this parameter as a format string. - * @param ...[in] The arguments to be formatted. - * @remarks In Windows, this funcion will use native Win32 function for writing, - * because standard C/C++ function can not handle UTF8 output on Windows normally. - * In other platforms, this function will redirect request to std::fprintf with stdout, - * which is all programmer commonly used method. - * It also mean that we assume stdout is encoded by UTF8 on these platforms. + * @brief Universal console write function with format feature. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments to be formatted. */ - void Write(const char* u8_fmt, ...); + void Format(const char* u8_fmt, ...); /** - * @brief Universal console write function with automatic EOL - * @details This function is same as Write(const char*, ...), - * but it will automatically add EOL in output to break line. - * This is commonly used. - * @param u8_fmt[in] The format string. - * If you just want to write a pure string, you should escape formatter chars (%) in this string, - * because this function always take this parameter as a format string. - * If you want to output plain string, you need escape format - * @param ...[in] The arguments to be formatted. + * @brief Universal console write function with format and auto EOL feature. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments to be formatted. */ - void WriteLine(const char* u8_fmt, ...); + void FormatLine(const char* u8_fmt, ...); + /** + * @brief Universal console write function. + * @param[in] u8_strl The string to be written. + */ + void Write(const char* u8_strl); + /** + * @brief Universal console write function with auto EOL feature. + * @param[in] u8_strl The string to be written. + */ + void WriteLine(const char* u8_strl); + + /** + * @brief Universal console error write function with format and feature. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments to be formatted. + */ + void ErrFormat(const char* u8_fmt, ...); + /** + * @brief Universal console error write function with format and auto EOL feature. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments to be formatted. + */ + void ErrFormatLine(const char* u8_fmt, ...); + /** + * @brief Universal console error write function. + * @param[in] u8_strl The string to be written. + */ + void ErrWrite(const char* u8_strl); + /** + * @brief Universal console error write function with auto EOL feature. + * @param[in] u8_strl The string to be written. + */ + void ErrWriteLine(const char* u8_strl); } diff --git a/src/DialogHelper.cpp b/src/DialogHelper.cpp index 995f0e2..3d304cd 100644 --- a/src/DialogHelper.cpp +++ b/src/DialogHelper.cpp @@ -180,7 +180,7 @@ namespace YYCC::DialogHelper { * @return True if success, otherwise false. * @remarks This is an assist function of CommonFileDialog. */ - bool ExtractDisplayName(IShellItem* item, std::string& ret) { + static bool ExtractDisplayName(IShellItem* item, std::string& ret) { // fetch display name from IShellItem* LPWSTR _name; HRESULT hr = item->GetDisplayName(SIGDN_FILESYSPATH, &_name); @@ -205,7 +205,7 @@ namespace YYCC::DialogHelper { * @remarks This function is the real underlying function of all dialog functions. */ template - bool CommonFileDialog(const FileDialog& params, std::vector& ret) { + static bool CommonFileDialog(const FileDialog& params, std::vector& ret) { // Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog // prepare result variable HRESULT hr; diff --git a/src/ExceptionHelper.hpp b/src/ExceptionHelper.hpp index 29e8d9b..abe9e40 100644 --- a/src/ExceptionHelper.hpp +++ b/src/ExceptionHelper.hpp @@ -8,12 +8,12 @@ namespace YYCC::ExceptionHelper { * @brief Register unhandled exception handler * @details * This function will set an internal function as unhandled exception handler on Windows. - * \n + * * When unhandled exception raised, * That internal function will output error stacktrace in standard output * and log file (located in temp folder), and also generate a dump file * in temp folder (for convenient debugging of developer when reporting bugs) if it can. - * \n + * * This function usually is called at the start of program. * @remarks This function is Windows only. */ @@ -22,10 +22,10 @@ namespace YYCC::ExceptionHelper { * @brief Unregister unhandled exception handler * @details * The reverse operation of Register(). - * \n + * * This function and Register() should always be used as a pair. * You must call this function if you have called Register() before. - * \n + * * This function usually is called at the end of program. * @remarks This function is Windows only. */ diff --git a/src/IOHelper.hpp b/src/IOHelper.hpp index 47e6690..0e5f641 100644 --- a/src/IOHelper.hpp +++ b/src/IOHelper.hpp @@ -14,7 +14,7 @@ namespace YYCC::IOHelper { * @details * When printing a pointer with HEX style, we always hope it can be left-padded with some zero for easy reading. * In different architecture, the size of this padding is differnet too so we create this macro. - * \n + * * In 32-bit environment, it will be "08" meaning left pad zero until 8 number position. * In 64-bit environment, it will be "016" meaning left pad zero until 16 number position. */ @@ -25,8 +25,8 @@ namespace YYCC::IOHelper { /** * @brief The UTF8 version of std::fopen. - * @param u8_filepath[in] The UTF8 encoded path to the file to be opened. - * @param u8_mode[in] UTF8 encoded mode string of the file to be opened. + * @param[in] u8_filepath The UTF8 encoded path to the file to be opened. + * @param[in] u8_mode UTF8 encoded mode string of the file to be opened. * @remarks * This function is suit for Windows because std::fopen do not support UTF8 on Windows. * On other platforms, this function will delegate request directly to std::fopen. @@ -36,7 +36,7 @@ namespace YYCC::IOHelper { /** * @brief Build std::filesystem::path from UTF8 string. - * @param u8_path[in] UTF8 path string for building this std::filesystem::path. + * @param[in] u8_path UTF8 path string for building this std::filesystem::path. * @return std::filesystem::path instance. * @exception std::invalid_argument Fail to parse given UTF8 string (maybe invalid?). * @remarks diff --git a/src/WinFctHelper.hpp b/src/WinFctHelper.hpp index 7846534..c26c834 100644 --- a/src/WinFctHelper.hpp +++ b/src/WinFctHelper.hpp @@ -8,6 +8,13 @@ #include #include "WinImportSuffix.hpp" +/** + * @brief The helper providing assistance to Win32 functions. + * @details + * This helper is Windows specific. + * If current environment is not Windows, + * the whole namespace will disappear. +*/ namespace YYCC::WinFctHelper { /** @@ -15,7 +22,7 @@ namespace YYCC::WinFctHelper { * @details * If your target is EXE, the current module simply is your program self. * However, if your target is DLL, the current module is your DLL, not the EXE loading your DLL. - * \n + * * This function is frequently used by DLL. * Because some design need the HANDLE of current module, not the host EXE loading your DLL. * For example, you may want to get the name of your built DLL at runtime, then you should pass current module HANDLE, not the HANDLE of EXE. @@ -32,9 +39,9 @@ namespace YYCC::WinFctHelper { /** * @brief Get the file name of given module HANDLE - * @param hModule[in] + * @param[in] hModule * The HANDLE to the module where we want get file name. - * It is same as the HANDLE parameter of GetModuleFileName. + * It is same as the HANDLE parameter of \c GetModuleFileName. * @return UTF8 encoded file name of given module. Empty string if failed. */ std::string GetModuleName(HINSTANCE hModule); diff --git a/testbench/main.cpp b/testbench/main.cpp index 75f17e3..c8aa2cb 100644 --- a/testbench/main.cpp +++ b/testbench/main.cpp @@ -7,9 +7,9 @@ namespace YYCCTestbench { static void Assert(bool condition, const char* description) { if (condition) { - Console::WriteLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description); + Console::FormatLine(YYCC_COLOR_LIGHT_GREEN("OK: %s"), description); } else { - Console::WriteLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description); + Console::FormatLine(YYCC_COLOR_LIGHT_RED("Failed: %s\n"), description); std::abort(); } } @@ -51,13 +51,13 @@ namespace YYCCTestbench { "\xF0\x9F\x8D\xA3 \xE2\x9C\x96 \xF0\x9F\x8D\xBA", // EMOJI }; for (const auto* ptr : c_TestStrings) { - Console::WriteLine("\t%s", ptr); + Console::FormatLine("\t%s", ptr); } // UTF8 Input Test Console::WriteLine("UTF8 Input Test:"); for (const auto* ptr : c_TestStrings) { - Console::WriteLine("\tPlease type: %s", ptr); + Console::FormatLine("\tPlease type: %s", ptr); Console::Write("\t> "); std::string gotten(Console::ReadLine()); @@ -198,27 +198,27 @@ namespace YYCCTestbench { filters.Add("All Files (*.*)", {"*.*"}); params.SetDefaultFileTypeIndex(0u); if (YYCC::DialogHelper::OpenFileDialog(params, ret)) { - Console::WriteLine("Open File: %s", ret.c_str()); + Console::FormatLine("Open File: %s", ret.c_str()); } if (YYCC::DialogHelper::OpenMultipleFileDialog(params, rets)) { Console::WriteLine("Open Multiple Files:"); for (const auto& item : rets) { - Console::WriteLine("\t%s", item.c_str()); + Console::FormatLine("\t%s", item.c_str()); } } if (YYCC::DialogHelper::SaveFileDialog(params, ret)) { - Console::WriteLine("Save File: %s", ret.c_str()); + Console::FormatLine("Save File: %s", ret.c_str()); } params.Clear(); if (YYCC::DialogHelper::OpenFolderDialog(params, ret)) { - Console::WriteLine("Open Folder: %s", ret.c_str()); + Console::FormatLine("Open Folder: %s", ret.c_str()); } } static void WinFctTestbench() { - Console::WriteLine("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, YYCC::WinFctHelper::GetCurrentModule()); - Console::WriteLine("Temp Directory: %s", YYCC::WinFctHelper::GetTempDirectory().c_str()); - Console::WriteLine("Current Module Name: %s", YYCC::WinFctHelper::GetModuleName(YYCC::WinFctHelper::GetCurrentModule()).c_str()); + Console::FormatLine("Current Module HANDLE: 0x%" PRI_XPTR_LEFT_PADDING PRIXPTR, YYCC::WinFctHelper::GetCurrentModule()); + Console::FormatLine("Temp Directory: %s", YYCC::WinFctHelper::GetTempDirectory().c_str()); + Console::FormatLine("Current Module Name: %s", YYCC::WinFctHelper::GetModuleName(YYCC::WinFctHelper::GetCurrentModule()).c_str()); } }