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.
This commit is contained in:
@ -29,6 +29,7 @@ PRIVATE
|
|||||||
yycc/carton/termcolor.cpp
|
yycc/carton/termcolor.cpp
|
||||||
yycc/carton/wcwidth.cpp
|
yycc/carton/wcwidth.cpp
|
||||||
yycc/carton/tabulate.cpp
|
yycc/carton/tabulate.cpp
|
||||||
|
yycc/carton/csconsole.cpp
|
||||||
)
|
)
|
||||||
target_sources(YYCCommonplace
|
target_sources(YYCCommonplace
|
||||||
PUBLIC
|
PUBLIC
|
||||||
@ -77,6 +78,7 @@ FILES
|
|||||||
yycc/carton/termcolor.hpp
|
yycc/carton/termcolor.hpp
|
||||||
yycc/carton/wcwidth.hpp
|
yycc/carton/wcwidth.hpp
|
||||||
yycc/carton/tabulate.hpp
|
yycc/carton/tabulate.hpp
|
||||||
|
yycc/carton/csconsole.hpp
|
||||||
)
|
)
|
||||||
# Setup header infomations
|
# Setup header infomations
|
||||||
target_include_directories(YYCCommonplace
|
target_include_directories(YYCCommonplace
|
||||||
|
@ -1,253 +0,0 @@
|
|||||||
#include "ConsoleHelper.hpp"
|
|
||||||
|
|
||||||
#include "EncodingHelper.hpp"
|
|
||||||
#include "StringHelper.hpp"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
// Include Windows used headers in Windows.
|
|
||||||
#if defined(YYCC_OS_WINDOWS)
|
|
||||||
#include "WinImportPrefix.hpp"
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <io.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#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<bool _bIsConsole>
|
|
||||||
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<DWORD>::max()) {
|
|
||||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(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<DWORD>::max()) {
|
|
||||||
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(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<true>(hStdIn);
|
|
||||||
} else {
|
|
||||||
return WinConsoleRead<false>(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<bool bNeedFmt, bool bIsErr, bool bHasEOL>
|
|
||||||
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<true, false, false>(u8_fmt, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FormatLine(const yycc_char8_t* u8_fmt, ...) {
|
|
||||||
va_list argptr;
|
|
||||||
va_start(argptr, u8_fmt);
|
|
||||||
RawWrite<true, false, true>(u8_fmt, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Write(const yycc_char8_t* u8_strl) {
|
|
||||||
va_list empty{};
|
|
||||||
RawWrite<false, false, false>(u8_strl, empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WriteLine(const yycc_char8_t* u8_strl) {
|
|
||||||
va_list empty{};
|
|
||||||
RawWrite<false, false, true>(u8_strl, empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrFormat(const yycc_char8_t* u8_fmt, ...) {
|
|
||||||
va_list argptr;
|
|
||||||
va_start(argptr, u8_fmt);
|
|
||||||
RawWrite<true, true, false>(u8_fmt, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrFormatLine(const yycc_char8_t* u8_fmt, ...) {
|
|
||||||
va_list argptr;
|
|
||||||
va_start(argptr, u8_fmt);
|
|
||||||
RawWrite<true, true, true>(u8_fmt, argptr);
|
|
||||||
va_end(argptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrWrite(const yycc_char8_t* u8_strl) {
|
|
||||||
va_list empty{};
|
|
||||||
RawWrite<false, true, false>(u8_strl, empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ErrWriteLine(const yycc_char8_t* u8_strl) {
|
|
||||||
va_list empty{};
|
|
||||||
RawWrite<false, true, true>(u8_strl, empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "YYCCInternal.hpp"
|
|
||||||
|
|
||||||
#include <cstdio>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
}
|
|
247
src/yycc/carton/csconsole.cpp
Normal file
247
src/yycc/carton/csconsole.cpp
Normal file
@ -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 <iostream>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#if defined(YYCC_OS_WINDOWS)
|
||||||
|
#include "../windows/import_guard_head.hpp"
|
||||||
|
#include <Windows.h>
|
||||||
|
#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<bool BIsConsole>
|
||||||
|
static std::u8string win_console_read(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
|
||||||
|
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<DWORD>::max()) {
|
||||||
|
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(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<DWORD>::max()) {
|
||||||
|
WriteFile(hStdOut, strl.data(), static_cast<DWORD>(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<true>(hStdIn);
|
||||||
|
} else {
|
||||||
|
return win_console_read<false>(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<bool BNeedFmt, bool BIsErr, bool BHasEOL>
|
||||||
|
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<true, false, false>(u8_fmt, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void format_line(const char8_t* u8_fmt, ...) {
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, u8_fmt);
|
||||||
|
raw_write<true, false, true>(u8_fmt, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write(const char8_t* u8_strl) {
|
||||||
|
va_list empty{};
|
||||||
|
raw_write<false, false, false>(u8_strl, empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_line(const char8_t* u8_strl) {
|
||||||
|
va_list empty{};
|
||||||
|
raw_write<false, false, true>(u8_strl, empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void eformat(const char8_t* u8_fmt, ...) {
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, u8_fmt);
|
||||||
|
raw_write<true, true, false>(u8_fmt, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void eformat_line(const char8_t* u8_fmt, ...) {
|
||||||
|
va_list argptr;
|
||||||
|
va_start(argptr, u8_fmt);
|
||||||
|
raw_write<true, true, true>(u8_fmt, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ewrite(const char8_t* u8_strl) {
|
||||||
|
va_list empty{};
|
||||||
|
raw_write<false, true, false>(u8_strl, empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ewrite_line(const char8_t* u8_strl) {
|
||||||
|
va_list empty{};
|
||||||
|
raw_write<false, true, true>(u8_strl, empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
}
|
111
src/yycc/carton/csconsole.hpp
Normal file
111
src/yycc/carton/csconsole.hpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user