feat: add more output functions in console helper.

- add more output functions in console helper.
	* split output functions by format string and plain string as Format and Write.
	* add functions which output to stderr.
- improve document.
This commit is contained in:
yyc12345 2024-06-14 11:59:08 +08:00
parent 0ef1939e6e
commit c32806ea03
7 changed files with 199 additions and 81 deletions

View File

@ -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<bool bNeedFmt, bool bIsErr, bool bHasEOL>
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<true, false, false>(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<true, false, true>(u8_fmt, argptr);
va_end(argptr);
}
void Write(const char* u8_strl) {
RawWrite<false, false, false>(u8_strl, va_list());
}
void WriteLine(const char* u8_strl) {
RawWrite<false, false, true>(u8_strl, va_list());
}
void ErrFormat(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, true, false>(u8_fmt, argptr);
va_end(argptr);
}
void ErrFormatLine(const char* u8_fmt, ...) {
va_list argptr;
va_start(argptr, u8_fmt);
RawWrite<true, true, true>(u8_fmt, argptr);
va_end(argptr);
}
void ErrWrite(const char* u8_strl) {
RawWrite<false, true, false>(u8_strl, va_list());
}
void ErrWriteLine(const char* u8_strl) {
RawWrite<false, true, true>(u8_strl, va_list());
}
}

View File

@ -4,8 +4,66 @@
#include <cstdio>
#include <string>
/**
* @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);
}

View File

@ -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<CommonFileDialogType EDialogType>
bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
static bool CommonFileDialog(const FileDialog& params, std::vector<std::string>& ret) {
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog
// prepare result variable
HRESULT hr;

View File

@ -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.
*/

View File

@ -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

View File

@ -8,6 +8,13 @@
#include <Windows.h>
#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);

View File

@ -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());
}
}