feat: finish console input.
- finish console input function. add test for it. - add Replace function in string helper.
This commit is contained in:
parent
1e990b74ae
commit
629a608133
|
@ -32,10 +32,116 @@ namespace YYCC::ConsoleHelper {
|
|||
return true;
|
||||
}
|
||||
|
||||
//template<typename _Ty, std::enable_if_t<std::is_same_v<_Ty, char> || std::is_same_v<_Ty, wchar_t>, int> = 0>
|
||||
//static bool FetchEOL(std::basic_string<_Ty>& internal_buffer, std::basic_string<_Ty>& result_buffer) {
|
||||
// // try finding EOL in internal buffer
|
||||
// size_t pos;
|
||||
// if constexpr (std::is_same_v<_Ty, char>) internal_buffer.find_first_of('\n');
|
||||
// else internal_buffer.find_first_of(L'\n');
|
||||
|
||||
// // check finding result
|
||||
// if (pos == std::wstring::npos) {
|
||||
// // the whole string do not include EOL, fully appended to return value
|
||||
// result_buffer += internal_buffer;
|
||||
// internal_buffer.clear();
|
||||
// // return false mean need more data
|
||||
// return false;
|
||||
// } else {
|
||||
// // split result
|
||||
// // push into result and remain some in internal buffer.
|
||||
// result_buffer.append(internal_buffer, 0u, pos);
|
||||
// internal_buffer.erase(0u, pos + 1u); // +1 because EOL take one place.
|
||||
// // return true mean success finding
|
||||
// return true;
|
||||
// }
|
||||
//}
|
||||
|
||||
template<bool _bIsConsole>
|
||||
static std::string PlainRead(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::string real_return_buffer;
|
||||
if constexpr (_bIsConsole) {
|
||||
// console mode need convert wchar to utf8
|
||||
YYCC::EncodingHelper::WcharToUTF8(return_buffer.c_str(), real_return_buffer);
|
||||
} else {
|
||||
// non-console just copt the result
|
||||
real_return_buffer = return_buffer;
|
||||
}
|
||||
// every mode need delete \r words
|
||||
YYCC::StringHelper::Replace(real_return_buffer, "\r", "");
|
||||
// return value
|
||||
return real_return_buffer;
|
||||
}
|
||||
|
||||
std::string ReadLine() {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
return std::string();
|
||||
// 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 PlainRead<true>(hStdIn);
|
||||
} else {
|
||||
return PlainRead<false>(hStdIn);
|
||||
}
|
||||
|
||||
#elif YYCC_OS == YYCC_OS_LINUX
|
||||
|
||||
|
@ -52,19 +158,22 @@ namespace YYCC::ConsoleHelper {
|
|||
static void PlainWrite(const std::string& strl) {
|
||||
#if YYCC_OS == YYCC_OS_WINDOWS
|
||||
|
||||
// Prepare some Win32 variables
|
||||
// fetch stdout handle first
|
||||
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
HANDLE hStdOut = GetStdHandle(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
|
||||
DWORD console_mode;
|
||||
if (GetConsoleMode(hstdout, &console_mode)) {
|
||||
if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
|
||||
// console handle, use WriteConsoleW.
|
||||
// convert utf8 string to wide char first
|
||||
std::wstring wstrl(YYCC::EncodingHelper::UTF8ToWchar(strl.c_str()));
|
||||
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), NULL, NULL);
|
||||
WriteConsoleW(hStdOut, wstrl.c_str(), static_cast<DWORD>(wstrl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
} else {
|
||||
// anything else, use WriteFile instead.
|
||||
|
@ -73,7 +182,7 @@ namespace YYCC::ConsoleHelper {
|
|||
size_t strl_size = strl.size() * sizeof(std::string::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), NULL, NULL);
|
||||
WriteFile(hStdOut, strl.c_str(), static_cast<DWORD>(strl_size), &dwWrittenNumberOfChars, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,37 @@ namespace YYCC::StringHelper {
|
|||
return ret;
|
||||
}
|
||||
|
||||
void Replace(std::string& strl, const char* _from_strl, const char* _to_strl) {
|
||||
// Reference: https://stackoverflow.com/questions/3418231/replace-part-of-a-string-with-another-string
|
||||
|
||||
// check requirements
|
||||
// from string and to string should not be nullptr.
|
||||
if (_from_strl == nullptr || _to_strl == nullptr) return;
|
||||
// from string should not be empty
|
||||
std::string from_strl(_from_strl);
|
||||
std::string to_strl(_to_strl);
|
||||
if (from_strl.empty()) return;
|
||||
|
||||
// start replace one by one
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = strl.find(from_strl, start_pos)) != std::string::npos) {
|
||||
strl.replace(start_pos, from_strl.size(), to_strl);
|
||||
start_pos += to_strl.size(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
|
||||
std::string Replace(const char* _strl, const char* _from_strl, const char* _to_strl) {
|
||||
// prepare result
|
||||
std::string strl;
|
||||
// if given string is not nullptr, assign it and process it.
|
||||
if (_strl != nullptr) {
|
||||
strl = _strl;
|
||||
Replace(strl, _from_strl, _to_strl);
|
||||
}
|
||||
// return value
|
||||
return strl;
|
||||
}
|
||||
|
||||
std::string Join(JoinDataProvider fct_data, const char* decilmer) {
|
||||
std::string ret;
|
||||
bool is_first = true;
|
||||
|
@ -116,6 +147,7 @@ namespace YYCC::StringHelper {
|
|||
|
||||
template<bool bIsToLower>
|
||||
void GeneralStringLowerUpper(std::string& strl) {
|
||||
// References:
|
||||
// https://en.cppreference.com/w/cpp/algorithm/transform
|
||||
// https://en.cppreference.com/w/cpp/string/byte/tolower
|
||||
std::transform(
|
||||
|
|
|
@ -7,19 +7,22 @@
|
|||
#include <vector>
|
||||
|
||||
namespace YYCC::StringHelper {
|
||||
|
||||
|
||||
bool Printf(std::string& strl, const char* format, ...);
|
||||
bool VPrintf(std::string& strl, const char* format, va_list argptr);
|
||||
|
||||
std::string Printf(const char* format, ...);
|
||||
std::string VPrintf(const char* format, va_list argptr);
|
||||
|
||||
void Replace(std::string& strl, const char* _from_strl, const char* _to_strl);
|
||||
std::string Replace(const char* _strl, const char* _from_strl, const char* _to_strl);
|
||||
|
||||
/**
|
||||
* @brief The data provider of general Join function.
|
||||
* This function pointer return non-null string pointer to represent a element of joined series.
|
||||
* otherwise return nullptr to terminate the joining process.
|
||||
*/
|
||||
using JoinDataProvider = std::function<const char*()>;
|
||||
using JoinDataProvider = std::function<const char* ()>;
|
||||
/**
|
||||
* @brief General Join function.
|
||||
* @details This function use function pointer as a general data provider interface,
|
||||
|
@ -31,24 +34,24 @@ namespace YYCC::StringHelper {
|
|||
std::string Join(JoinDataProvider fct_data, const char* decilmer);
|
||||
/**
|
||||
* @brief Specialized Join function for common used container.
|
||||
* @param data
|
||||
* @param decilmer
|
||||
* @param reversed
|
||||
* @return
|
||||
* @param data
|
||||
* @param decilmer
|
||||
* @param reversed
|
||||
* @return
|
||||
*/
|
||||
std::string Join(const std::vector<std::string>& data, const char* decilmer, bool reversed = false);
|
||||
|
||||
/**
|
||||
* @brief Transform string to lower.
|
||||
* @param strl
|
||||
* @return
|
||||
* @param strl
|
||||
* @return
|
||||
*/
|
||||
std::string Lower(const char* strl);
|
||||
void Lower(std::string& strl);
|
||||
/**
|
||||
* @brief Transform string to upper.
|
||||
* @param strl
|
||||
* @return
|
||||
* @param strl
|
||||
* @return
|
||||
*/
|
||||
std::string Upper(const char* strl);
|
||||
void Upper(std::string& strl);
|
||||
|
@ -59,7 +62,7 @@ namespace YYCC::StringHelper {
|
|||
* If this is nullptr, the result will be empty.
|
||||
* @param _decilmer[in] The decilmer for splitting.
|
||||
* If decilmer is nullptr or zero length, the result will only have 1 element which is original string.
|
||||
* @return
|
||||
* @return
|
||||
* @remarks This function may be low performance because it just a homebrew Split functon.
|
||||
* It can works in most toy cases but not suit for high performance scenario.
|
||||
* Also, this function will produce a copy of original string because it is not zero copy.
|
||||
|
|
|
@ -19,8 +19,8 @@ namespace YYCCTestbench {
|
|||
Console::EnableColorfulConsole(stdout);
|
||||
Console::WriteLine("Color Test:");
|
||||
|
||||
// U+2588 is full block
|
||||
#define TEST_MACRO(col) Console::WriteLine("\t" YYCC_COLOR_ ## col ("\u2588\u2588") YYCC_COLOR_LIGHT_ ## col("\u2588\u2588") " " #col " / LIGHT " #col );
|
||||
// U+2588 is full block
|
||||
|
||||
TEST_MACRO(BLACK);
|
||||
TEST_MACRO(RED);
|
||||
|
@ -35,7 +35,6 @@ namespace YYCCTestbench {
|
|||
|
||||
// UTF8 Output Test
|
||||
// Ref: https://stackoverflow.com/questions/478201/how-to-test-an-application-for-correct-encoding-e-g-utf-8
|
||||
//Console::EnableUTF8Console(stdout);
|
||||
Console::WriteLine("UTF8 Output Test:");
|
||||
static std::vector<const char*> c_TestStrings {
|
||||
"\u30E6\u30FC\u30B6\u30FC\u5225\u30B5\u30A4\u30C8", // JAPAN
|
||||
|
@ -55,18 +54,48 @@ namespace YYCCTestbench {
|
|||
Console::WriteLine("\t%s", ptr);
|
||||
}
|
||||
|
||||
// UTF8 Input Test
|
||||
Console::WriteLine("UTF8 Input Test:");
|
||||
for (const auto* ptr : c_TestStrings) {
|
||||
Console::WriteLine("\tPlease type: %s", ptr);
|
||||
Console::Write("\t> ");
|
||||
|
||||
std::string gotten(Console::ReadLine());
|
||||
Assert(gotten == ptr, YYCC::StringHelper::Printf("Got: %s", gotten.c_str()).c_str());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void StringTestbench() {
|
||||
// Test Printf
|
||||
auto test_printf = YYCC::StringHelper::Printf("%s == %s", "Hello World", "Hello, world");
|
||||
Assert(test_printf == "Hello World == Hello, world", "YYCC::StringHelper::Printf");
|
||||
|
||||
|
||||
// Test Replace
|
||||
auto test_replace = YYCC::StringHelper::Replace("aabbcc", "bb", "dd"); // normal case
|
||||
Assert(test_replace == "aaddcc", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace("aabbcc", "zz", "yy"); // no replace
|
||||
Assert(test_replace == "aabbcc", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace("aabbcc", "", "zz"); // empty finding
|
||||
Assert(test_replace == "aabbcc", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace("aabbcc", nullptr, "zz"); // nullptr finding
|
||||
Assert(test_replace == "aabbcc", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace("aaaabbaa", "aa", ""); // no replaced string
|
||||
Assert(test_replace == "bb", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace("aaxcc", "x", "yx"); // nested replacing
|
||||
Assert(test_replace == "aayxcc", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace("", "", "xy"); // empty source string
|
||||
Assert(test_replace == "", "YYCC::StringHelper::Replace");
|
||||
test_replace = YYCC::StringHelper::Replace(nullptr, "", "xy"); // nullptr source string
|
||||
Assert(test_replace == "", "YYCC::StringHelper::Replace");
|
||||
|
||||
// Test Upper / Lower
|
||||
auto test_lower = YYCC::StringHelper::Lower("LOWER");
|
||||
Assert(test_lower == "lower", "YYCC::StringHelper::Lower");
|
||||
auto test_upper = YYCC::StringHelper::Upper("upper");
|
||||
Assert(test_upper == "UPPER", "YYCC::StringHelper::Upper");
|
||||
|
||||
|
||||
// Test Join
|
||||
std::vector<std::string> test_join_container {
|
||||
"", "1", "2", ""
|
||||
};
|
||||
|
@ -75,6 +104,7 @@ namespace YYCCTestbench {
|
|||
test_join = YYCC::StringHelper::Join(test_join_container, ", ", true);
|
||||
Assert(test_join == ", 2, 1, ", "YYCC::StringHelper::Join");
|
||||
|
||||
// Test Split
|
||||
auto test_split = YYCC::StringHelper::Split(", 1, 2, ", ", ");
|
||||
Assert(test_split.size() == 4u, "YYCC::StringHelper::Split");
|
||||
Assert(test_split[0] == "", "YYCC::StringHelper::Split");
|
||||
|
|
Loading…
Reference in New Issue
Block a user