From ccd0219ead52f68b1341d932b1fea90bb7d17997 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Fri, 22 Aug 2025 23:28:17 +0800 Subject: [PATCH] refactor: migrate console helper. - migrate csharp style console helper. - i just do a simple migration and mark it as deprecated. it should works like 1.x version. --- src/CMakeLists.txt | 2 + src/YYCCLegacy/ConsoleHelper.cpp | 253 ------------------------------- src/YYCCLegacy/ConsoleHelper.hpp | 82 ---------- src/yycc/carton/csconsole.cpp | 247 ++++++++++++++++++++++++++++++ src/yycc/carton/csconsole.hpp | 111 ++++++++++++++ 5 files changed, 360 insertions(+), 335 deletions(-) delete mode 100644 src/YYCCLegacy/ConsoleHelper.cpp delete mode 100644 src/YYCCLegacy/ConsoleHelper.hpp create mode 100644 src/yycc/carton/csconsole.cpp create mode 100644 src/yycc/carton/csconsole.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32e56c2..e3df9d6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,6 +29,7 @@ PRIVATE yycc/carton/termcolor.cpp yycc/carton/wcwidth.cpp yycc/carton/tabulate.cpp + yycc/carton/csconsole.cpp ) target_sources(YYCCommonplace PUBLIC @@ -77,6 +78,7 @@ FILES yycc/carton/termcolor.hpp yycc/carton/wcwidth.hpp yycc/carton/tabulate.hpp + yycc/carton/csconsole.hpp ) # Setup header infomations target_include_directories(YYCCommonplace diff --git a/src/YYCCLegacy/ConsoleHelper.cpp b/src/YYCCLegacy/ConsoleHelper.cpp deleted file mode 100644 index 4cc491c..0000000 --- a/src/YYCCLegacy/ConsoleHelper.cpp +++ /dev/null @@ -1,253 +0,0 @@ -#include "ConsoleHelper.hpp" - -#include "EncodingHelper.hpp" -#include "StringHelper.hpp" -#include - -// Include Windows used headers in Windows. -#if defined(YYCC_OS_WINDOWS) -#include "WinImportPrefix.hpp" -#include -#include -#include -#include "WinImportSuffix.hpp" -#endif - -namespace YYCC::ConsoleHelper { - -#pragma region Windows Specific Functions -#if defined(YYCC_OS_WINDOWS) - - /* - Reference: - * https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows - * https://stackoverflow.com/questions/69830460/reading-utf-8-input - - There is 3 way to make Windows console enable UTF8 mode. - - First one is calling SetConsoleCP and SetConsoleOutputCP. - The side effect of this is std::cin and std::cout is broken, - however there is a patch for this issue. - - Second one is calling _set_mode with _O_U8TEXT or _O_U16TEXT to enable Unicode mode for Windows console. - This also have side effect which is stronger than first one. - All puts family functions (ASCII-based output functions) will throw assertion exception. - You only can use putws family functions (wide-char-based output functions). - However these functions can not be used without calling _set_mode in Windows design. - - There still is another method, using WriteConsoleW directly visiting console. - This function family can output correct string without calling any extra functions! - This method is what we adopted. - */ - - template - static yycc_u8string WinConsoleRead(HANDLE hStdIn) { - using _TChar = std::conditional_t<_bIsConsole, wchar_t, char>; - - // Prepare an internal buffer because the read data may not be fully used. - // For example, we may read x\ny in a single calling but after processing \n, this function will return - // so y will temporarily stored in this internal buffer for next using. - // Thus this function is not thread safe. - static std::basic_string<_TChar> internal_buffer; - // create return value buffer - std::basic_string<_TChar> return_buffer; - - // Prepare some variables - DWORD dwReadNumberOfChars; - _TChar szReadChars[64]; - size_t eol_pos; - - // try fetching EOL - while (true) { - // if internal buffer is empty, - // try fetching it. - if (internal_buffer.empty()) { - // console and non-console use different method to read. - if constexpr (_bIsConsole) { - // console handle, use ReadConsoleW. - // read from console, the read data is wchar based - if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(_TChar), &dwReadNumberOfChars, NULL)) - break; - } else { - // anything else, use ReadFile instead. - // the read data is utf8 based - if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL)) - break; - } - - // send to internal buffer - if (dwReadNumberOfChars == 0) break; - internal_buffer.append(szReadChars, dwReadNumberOfChars); - } - - // try finding EOL in internal buffer - if constexpr (std::is_same_v<_TChar, char>) eol_pos = internal_buffer.find_first_of('\n'); - else eol_pos = internal_buffer.find_first_of(L'\n'); - // check finding result - if (eol_pos == std::wstring::npos) { - // the whole string do not include EOL, fully appended to return value - return_buffer += internal_buffer; - internal_buffer.clear(); - // need more data, continue while - } else { - // split result - // push into result and remain some in internal buffer. - return_buffer.append(internal_buffer, 0u, eol_pos); - internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place. - // break while mean success finding - break; - } - } - - // post-process for return value - yycc_u8string real_return_buffer; - if constexpr (_bIsConsole) { - // console mode need convert wchar to utf8 - YYCC::EncodingHelper::WcharToUTF8(return_buffer, real_return_buffer); - } else { - // non-console just copt the result - real_return_buffer = EncodingHelper::ToUTF8(return_buffer); - } - // every mode need delete \r words - YYCC::StringHelper::Replace(real_return_buffer, YYCC_U8("\r"), YYCC_U8("")); - // return value - return real_return_buffer; - } - - static void WinConsoleWrite(const yycc_u8string& strl, bool to_stderr) { - // Prepare some Win32 variables - // fetch stdout handle first - HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE); - DWORD dwConsoleMode; - DWORD dwWrittenNumberOfChars; - - // if stdout was redirected, this handle may point to a file handle or anything else, - // WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle - if (GetConsoleMode(hStdOut, &dwConsoleMode)) { - // console handle, use WriteConsoleW. - // convert utf8 string to wide char first - std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl)); - size_t wstrl_size = wstrl.size(); - // write string with size check - if (wstrl_size <= std::numeric_limits::max()) { - WriteConsoleW(hStdOut, wstrl.c_str(), static_cast(wstrl_size), &dwWrittenNumberOfChars, NULL); - } - } else { - // anything else, use WriteFile instead. - // WriteFile do not need extra convertion, because it is direct writing. - // check whether string length is overflow - size_t strl_size = strl.size() * sizeof(yycc_u8string::value_type); - // write string with size check - if (strl_size <= std::numeric_limits::max()) { - WriteFile(hStdOut, strl.c_str(), static_cast(strl_size), &dwWrittenNumberOfChars, NULL); - } - } - } - -#endif -#pragma endregion - - yycc_u8string ReadLine() { -#if defined(YYCC_OS_WINDOWS) - - // get stdin mode - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - // use different method to get according to whether stdin is redirected - DWORD dwConsoleMode; - if (GetConsoleMode(hStdIn, &dwConsoleMode)) { - return WinConsoleRead(hStdIn); - } else { - return WinConsoleRead(hStdIn); - } - -#else - - // in linux, directly use C++ function to fetch. - std::string cmd; - if (std::getline(std::cin, cmd).fail()) cmd.clear(); - return EncodingHelper::ToUTF8(cmd); - -#endif - } - - template - static void RawWrite(const yycc_char8_t* 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 - yycc_u8string 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 += YYCC_U8("\n"); - } - -#if defined(YYCC_OS_WINDOWS) - // call Windows specific writer - WinConsoleWrite(strl, bIsErr); -#else - // in linux, directly use C function to write. - std::fputs(EncodingHelper::ToOrdinary(strl.c_str()), bIsErr ? stderr : stdout); -#endif - } - - void Format(const yycc_char8_t* u8_fmt, ...) { - va_list argptr; - va_start(argptr, u8_fmt); - RawWrite(u8_fmt, argptr); - va_end(argptr); - } - - void FormatLine(const yycc_char8_t* u8_fmt, ...) { - va_list argptr; - va_start(argptr, u8_fmt); - RawWrite(u8_fmt, argptr); - va_end(argptr); - } - - void Write(const yycc_char8_t* u8_strl) { - va_list empty{}; - RawWrite(u8_strl, empty); - } - - void WriteLine(const yycc_char8_t* u8_strl) { - va_list empty{}; - RawWrite(u8_strl, empty); - } - - void ErrFormat(const yycc_char8_t* u8_fmt, ...) { - va_list argptr; - va_start(argptr, u8_fmt); - RawWrite(u8_fmt, argptr); - va_end(argptr); - } - - void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) { - va_list argptr; - va_start(argptr, u8_fmt); - RawWrite(u8_fmt, argptr); - va_end(argptr); - } - - void ErrWrite(const yycc_char8_t* u8_strl) { - va_list empty{}; - RawWrite(u8_strl, empty); - } - - void ErrWriteLine(const yycc_char8_t* u8_strl) { - va_list empty{}; - RawWrite(u8_strl, empty); - } - -} - diff --git a/src/YYCCLegacy/ConsoleHelper.hpp b/src/YYCCLegacy/ConsoleHelper.hpp deleted file mode 100644 index fea84cb..0000000 --- a/src/YYCCLegacy/ConsoleHelper.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once -#include "YYCCInternal.hpp" - -#include -#include - -/** - * @brief The helper providing universal C\# style console function and other console related stuff - * @details - * For how to utilize this functions provided by this namespace, please view \ref console_helper. -*/ -namespace YYCC::ConsoleHelper { - - /** - * @brief Reads the next line of UTF8 characters from the standard input stream. - * @return - * The next line of UTF8 characters from the input stream. - * Empty string if user just press Enter key or function failed. - */ - yycc_u8string ReadLine(); - - /** - * @brief - * Writes the text representation of the specified object - * to the standard output stream using the specified format information. - * @param[in] u8_fmt The format string. - * @param[in] ... The arguments of format string. - */ - void Format(const yycc_char8_t* u8_fmt, ...); - /** - * @brief - * Writes the text representation of the specified object, - * followed by the current line terminator, - * to the standard output stream using the specified format information. - * @param[in] u8_fmt The format string. - * @param[in] ... The arguments of format string. - */ - void FormatLine(const yycc_char8_t* u8_fmt, ...); - /** - * @brief Writes the specified string value to the standard output stream. - * @param[in] u8_strl The value to write. - */ - void Write(const yycc_char8_t* u8_strl); - /** - * @brief - * Writes the specified string value, followed by the current line terminator, - * to the standard output stream. - * @param[in] u8_strl The value to write. - */ - void WriteLine(const yycc_char8_t* u8_strl); - - /** - * @brief - * Writes the text representation of the specified object - * to the standard error stream using the specified format information. - * @param[in] u8_fmt The format string. - * @param[in] ... The arguments of format string. - */ - void ErrFormat(const yycc_char8_t* u8_fmt, ...); - /** - * @brief - * Writes the text representation of the specified object, - * followed by the current line terminator, - * to the standard error stream using the specified format information. - * @param[in] u8_fmt The format string. - * @param[in] ... The arguments of format string. - */ - void ErrFormatLine(const yycc_char8_t* u8_fmt, ...); - /** - * @brief Writes the specified string value to the standard error stream. - * @param[in] u8_strl The value to write. - */ - void ErrWrite(const yycc_char8_t* u8_strl); - /** - * @brief - * Writes the specified string value, followed by the current line terminator, - * to the standard error stream. - * @param[in] u8_strl The value to write. - */ - void ErrWriteLine(const yycc_char8_t* u8_strl); - -} diff --git a/src/yycc/carton/csconsole.cpp b/src/yycc/carton/csconsole.cpp new file mode 100644 index 0000000..f911a10 --- /dev/null +++ b/src/yycc/carton/csconsole.cpp @@ -0,0 +1,247 @@ +#include "csconsole.hpp" +#include "../macro/os_detector.hpp" + +#include "../string/op.hpp" +#include "../string/reinterpret.hpp" +#include "../encoding/windows.hpp" +#include +#include +#include + +#if defined(YYCC_OS_WINDOWS) +#include "../windows/import_guard_head.hpp" +#include +#include "../windows/import_guard_tail.hpp" +#endif + +#define OP ::yycc::string::op +#define REINTERPRET ::yycc::string::reinterpret +#define ENC ::yycc::encoding::windows + +namespace yycc::carton::csconsole { + +#pragma region Windows Console Specific Functions +#if defined(YYCC_OS_WINDOWS) + + template + static std::u8string win_console_read(HANDLE hStdIn) { + using TChar = std::conditional_t; + + // Prepare an internal buffer because the read data may not be fully used. + // For example, we may read x\ny in a single calling but after processing \n, this function will return + // so y will temporarily stored in this internal buffer for next using. + // Thus this function is not thread safe. + static std::basic_string internal_buffer; + // create return value buffer + std::basic_string return_buffer; + + // Prepare some variables + DWORD dwReadNumberOfChars; + TChar szReadChars[64]; + size_t eol_pos; + + // try fetching EOL + while (true) { + // if internal buffer is empty, + // try fetching it. + if (internal_buffer.empty()) { + // console and non-console use different method to read. + if constexpr (BIsConsole) { + // console handle, use ReadConsoleW. + // read from console, the read data is wchar based + if (!ReadConsoleW(hStdIn, szReadChars, sizeof(szReadChars) / sizeof(TChar), &dwReadNumberOfChars, NULL)) break; + } else { + // anything else, use ReadFile instead. + // the read data is utf8 based + if (!ReadFile(hStdIn, szReadChars, sizeof(szReadChars), &dwReadNumberOfChars, NULL)) break; + } + + // send to internal buffer + if (dwReadNumberOfChars == 0) break; + internal_buffer.append(szReadChars, dwReadNumberOfChars); + } + + // try finding EOL in internal buffer + if constexpr (std::is_same_v) eol_pos = internal_buffer.find_first_of('\n'); + else eol_pos = internal_buffer.find_first_of(L'\n'); + // check finding result + if (eol_pos == std::wstring::npos) { + // the whole string do not include EOL, fully appended to return value + return_buffer += internal_buffer; + internal_buffer.clear(); + // need more data, continue while + } else { + // split result + // push into result and remain some in internal buffer. + return_buffer.append(internal_buffer, 0u, eol_pos); + internal_buffer.erase(0u, eol_pos + 1u); // +1 because EOL take one place. + // break while mean success finding + break; + } + } + + // post-process for return value + std::u8string real_return_buffer; + if constexpr (BIsConsole) { + // console mode need convert wchar to utf8 + auto rv = ENC::to_utf8(return_buffer); + if (rv.has_value()) real_return_buffer = std::move(rv.value()); + } else { + // non-console just copt the result + real_return_buffer = REINTERPRET::as_utf8(return_buffer); + } + // every mode need delete \r words + OP::replace(real_return_buffer, u8"\r", u8""); + // return value + return real_return_buffer; + } + + static void win_console_write(const std::u8string_view& strl, bool to_stderr) { + // Prepare some Win32 variables + // fetch stdout handle first + HANDLE hStdOut = GetStdHandle(to_stderr ? STD_ERROR_HANDLE : STD_OUTPUT_HANDLE); + DWORD dwConsoleMode; + DWORD dwWrittenNumberOfChars; + + // if stdout was redirected, this handle may point to a file handle or anything else, + // WriteConsoleW can not write data into such scenario, so we need check whether this handle is console handle + if (GetConsoleMode(hStdOut, &dwConsoleMode)) { + // console handle, use WriteConsoleW. + // convert utf8 string to wide char first + auto rv = ENC::to_wchar(strl); + if (!rv.has_value()) return; + std::wstring wstrl(std::move(rv.value())); + size_t wstrl_size = wstrl.size(); + // write string with size check + if (wstrl_size <= std::numeric_limits::max()) { + WriteConsoleW(hStdOut, wstrl.c_str(), static_cast(wstrl_size), &dwWrittenNumberOfChars, NULL); + } + } else { + // anything else, use WriteFile instead. + // WriteFile do not need extra convertion, because it is direct writing. + // check whether string length is overflow + size_t strl_size = strl.size() * sizeof(std::u8string_view::value_type); + // write string with size check + if (strl_size <= std::numeric_limits::max()) { + WriteFile(hStdOut, strl.data(), static_cast(strl_size), &dwWrittenNumberOfChars, NULL); + } + } + } + +#endif +#pragma endregion + +#pragma region Read Functions + + std::u8string read_line() { +#if defined(YYCC_OS_WINDOWS) + + // get stdin mode + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); + // use different method to get according to whether stdin is redirected + DWORD dwConsoleMode; + if (GetConsoleMode(hStdIn, &dwConsoleMode)) { + return win_console_read(hStdIn); + } else { + return win_console_read(hStdIn); + } + +#else + + // in linux, directly use C++ function to fetch. + std::string cmd; + if (std::getline(std::cin, cmd).fail()) cmd.clear(); + return REINTERPRET::as_utf8(cmd); + +#endif + } + +#pragma endregion + +#pragma region Write Functions + + template + static void raw_write(const char8_t* 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::u8string strl; + if constexpr (BNeedFmt) { + // treat as format string + va_list argcpy; + va_copy(argcpy, argptr); + auto rv = OP::vprintf(u8_fmt, argcpy); + va_end(argcpy); + // check format result + if (!rv.has_value()) return; + else strl = std::move(rv.value()); + } else { + // treat as plain string + strl = u8_fmt; + } + // Checkout whether add EOL + if constexpr (BHasEOL) { + strl += u8'\n'; + } + +#if defined(YYCC_OS_WINDOWS) + // call Windows specific writer + win_console_write(strl, BIsErr); +#else + // in linux, directly use C function to write. + std::fputs(REINTERPRET::as_ordinary(strl.c_str()), BIsErr ? stderr : stdout); +#endif + } + + void format(const char8_t* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + raw_write(u8_fmt, argptr); + va_end(argptr); + } + + void format_line(const char8_t* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + raw_write(u8_fmt, argptr); + va_end(argptr); + } + + void write(const char8_t* u8_strl) { + va_list empty{}; + raw_write(u8_strl, empty); + } + + void write_line(const char8_t* u8_strl) { + va_list empty{}; + raw_write(u8_strl, empty); + } + + void eformat(const char8_t* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + raw_write(u8_fmt, argptr); + va_end(argptr); + } + + void eformat_line(const char8_t* u8_fmt, ...) { + va_list argptr; + va_start(argptr, u8_fmt); + raw_write(u8_fmt, argptr); + va_end(argptr); + } + + void ewrite(const char8_t* u8_strl) { + va_list empty{}; + raw_write(u8_strl, empty); + } + + void ewrite_line(const char8_t* u8_strl) { + va_list empty{}; + raw_write(u8_strl, empty); + } + +#pragma endregion + +} diff --git a/src/yycc/carton/csconsole.hpp b/src/yycc/carton/csconsole.hpp new file mode 100644 index 0000000..7b7024e --- /dev/null +++ b/src/yycc/carton/csconsole.hpp @@ -0,0 +1,111 @@ +#pragma once +#include + +/** + * @brief The helper providing universal C\# style console function and other console related stuff + * @details + * The origin of this namespace is coming from the requirement of UTF8 console in Windows. + * There are 3 ways to make Windows console enable UTF8 mode. + * + * First one is calling \c SetConsoleCP and \c SetConsoleOutputCP. + * The side effect of this is \c std::cin and \c std::cout is broken, + * however there is a patch for this issue. + * + * Second one is calling \c _set_mode with \c _O_U8TEXT or \c _O_U16TEXT to enable Unicode mode for Windows console. + * This also have side effect which is stronger than first one. + * All "puts" family functions (ASCII-based output functions) will throw assertion exception. + * You only can use "putws" family functions (wide-char-based output functions). + * However these functions can not be used without calling \c _set_mode in Windows design. + * + * There still is another method, using \c WriteConsoleW directly visiting console. + * This function family can output correct string without calling any extra functions! + * This method is what we adopted and finally become this namespace. + * + * Reference: + * \li https://stackoverflow.com/questions/45575863/how-to-print-utf-8-strings-to-stdcout-on-windows + * \li https://stackoverflow.com/questions/69830460/reading-utf-8-input + * + * For how to utilize this functions provided by this namespace, please view \ref console_helper. + * + * @warning + * All functions provided by this namespace are too aggressive. + * Once you use it, you should not use any other input output functions. + * + * @deprecated + * This namespace provided functions are too aggressive and can not cover all use scenario. + * So I start to give this up when migrating this namespace during developing YYCC 2.x version. + * I just do a simple type fix and rename when migrating this namespace to make it "just works". + * There is no suggestion for using this namespace. And there is no update for this namespace. + * Programmer should treat Windows UTF8 issue on their own. +*/ +namespace yycc::carton::csconsole { + + /** + * @brief Reads the next line of UTF8 characters from the standard input stream. + * @return + * The next line of UTF8 characters from the input stream. + * Empty string if user just press Enter key or function failed. + */ + std::u8string read_line(); + + /** + * @brief + * Writes the text representation of the specified object + * to the standard output stream using the specified format information. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments of format string. + */ + void format(const char8_t* u8_fmt, ...); + /** + * @brief + * Writes the text representation of the specified object, + * followed by the current line terminator, + * to the standard output stream using the specified format information. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments of format string. + */ + void format_line(const char8_t* u8_fmt, ...); + /** + * @brief Writes the specified string value to the standard output stream. + * @param[in] u8_strl The value to write. + */ + void write(const char8_t* u8_strl); + /** + * @brief + * Writes the specified string value, followed by the current line terminator, + * to the standard output stream. + * @param[in] u8_strl The value to write. + */ + void write_line(const char8_t* u8_strl); + + /** + * @brief + * Writes the text representation of the specified object + * to the standard error stream using the specified format information. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments of format string. + */ + void eformat(const char8_t* u8_fmt, ...); + /** + * @brief + * Writes the text representation of the specified object, + * followed by the current line terminator, + * to the standard error stream using the specified format information. + * @param[in] u8_fmt The format string. + * @param[in] ... The arguments of format string. + */ + void eformat_line(const char8_t* u8_fmt, ...); + /** + * @brief Writes the specified string value to the standard error stream. + * @param[in] u8_strl The value to write. + */ + void ewrite(const char8_t* u8_strl); + /** + * @brief + * Writes the specified string value, followed by the current line terminator, + * to the standard error stream. + * @param[in] u8_strl The value to write. + */ + void ewrite_line(const char8_t* u8_strl); + +}