1
0

fix: change the behavior of printf in string op.

- add compiler hint for checking the arguments of printf.
- change the return value of printf. from std::expected to normal value. use C++ exception to indicate error.
	* the error of printf usually caused by programmer. so it can be found when testing program.
	* so i use std::logic_error to indicate this and programmer should fix this before releasing program.
- change the use of encoding convertion. for those cases that convertion must be safe, we unwrap it directly.
This commit is contained in:
2025-09-22 22:14:36 +08:00
parent 45e4031b5c
commit c85830902b
22 changed files with 240 additions and 164 deletions

View File

@ -31,6 +31,7 @@ PRIVATE
yycc/carton/tabulate.cpp yycc/carton/tabulate.cpp
yycc/carton/ironpad.cpp yycc/carton/ironpad.cpp
yycc/carton/csconsole.cpp yycc/carton/csconsole.cpp
#yycc/carton/clap/option.cpp
) )
target_sources(YYCCommonplace target_sources(YYCCommonplace
PUBLIC PUBLIC
@ -46,6 +47,7 @@ FILES
yycc/macro/compiler_detector.hpp yycc/macro/compiler_detector.hpp
yycc/macro/ptr_size_detector.hpp yycc/macro/ptr_size_detector.hpp
yycc/macro/class_copy_move.hpp yycc/macro/class_copy_move.hpp
yycc/macro/printf_checker.hpp
yycc/flag_enum.hpp yycc/flag_enum.hpp
yycc/string/reinterpret.hpp yycc/string/reinterpret.hpp
yycc/string/op.hpp yycc/string/op.hpp
@ -81,6 +83,9 @@ FILES
yycc/carton/tabulate.hpp yycc/carton/tabulate.hpp
yycc/carton/ironpad.hpp yycc/carton/ironpad.hpp
yycc/carton/csconsole.hpp yycc/carton/csconsole.hpp
yycc/carton/clap.hpp
yycc/carton/clap/types.hpp
yycc/carton/clap/option.hpp
) )
# Setup header infomations # Setup header infomations
target_include_directories(YYCCommonplace target_include_directories(YYCCommonplace

5
src/yycc/carton/clap.hpp Normal file
View File

@ -0,0 +1,5 @@
#pragma once
namespace yycc::carton::clap {
}

View File

@ -0,0 +1,5 @@
#include "option.hpp"
namespace yycc::carton::clap::option {
}

View File

@ -0,0 +1,85 @@
#pragma once
#include "types.hpp"
#include "../../macro/class_copy_move.hpp"
#include "../../string/op.hpp"
#include <optional>
#include <stdexcept>
#include <format>
#include <string>
#define NS_YYCC_CLAP_TYPES ::yycc::carton::clap::types
namespace yycc::carton::clap::option {
class Option {
public:
Option(std::optional<std::u8string_view> short_name,
std::optional<std::u8string_view> long_name,
std::optional<std::u8string_view> value_hint,
const std::u8string& description) :
short_name(short_name), long_name(long_name), value_hint(value_hint), description(description) {
if (!short_name.has_value() && !long_name.has_value()) {
throw std::logic_error("must have at least one name, short or long name");
}
if (short_name.has_value()) {
const auto& short_name_value = short_name.value();
if (!legal_short_name(short_name_value)) {
throw std::logic_error(std::format("invalid short name {}", short_name_value));
}
}
if (long_name.has_value()) {
const auto& long_name_value = long_name.value();
if (!legal_long_name(long_name_value)) {
throw std::logic_error(std::format("invalid long name {}", long_name_value));
}
}
}
~Option() {}
YYCC_DEFAULT_COPY_MOVE(Option)
public:
std::optional<std::u8string_view> get_short_name() const { return this->short_name; }
std::optional<std::u8string_view> get_long_name() const { return this->long_name; }
std::optional<std::u8string_view> get_value_hint() const { return this->value_hint; }
std::u8string_view get_description() const { return this->description; }
std::u8string to_showcase_name() {
namespace op = ::yycc::string::op;
if (short_name.has_value()) {
if (long_name.has_value()) {
} else {
}
} else {
if (long_name.has_value()) {
op::printf
} else {
throw std::runtime_error("both long name and short name are empty");
}
}
}
private:
static bool legal_short_name(const std::u8string_view& name) {
if (name.empty()) return false;
if (name.starts_with(NS_YYCC_CLAP_TYPES::DASH)) return false;
return true;
}
static bool legal_long_name(const std::u8string_view& name) {
if (name.empty()) return false;
return true;
}
private:
std::optional<std::u8string> short_name;
std::optional<std::u8string> long_name;
std::optional<std::u8string> value_hint;
std::u8string description;
};
} // namespace yycc::carton::clap::option
#undef NS_YYCC_CLAP_TYPES

View File

@ -0,0 +1,19 @@
#pragma once
#include <expected>
#include <string_view>
namespace yycc::carton::clap::types {
enum class ClapError {
};
template<typename T>
using ClapResult = std::expected<T, ClapError>;
inline constexpr std::u8string_view DASH = u8"-";
inline constexpr std::u8string_view DOUBLE_DASH = u8"--";
using Token = size_t;
} // namespace yycc::carton::clap::types

View File

@ -108,9 +108,7 @@ namespace yycc::carton::csconsole {
if (GetConsoleMode(hStdOut, &dwConsoleMode)) { if (GetConsoleMode(hStdOut, &dwConsoleMode)) {
// console handle, use WriteConsoleW. // console handle, use WriteConsoleW.
// convert utf8 string to wide char first // convert utf8 string to wide char first
auto rv = ENC::to_wchar(strl); std::wstring wstrl = ENC::to_wchar(strl).value();
if (!rv.has_value()) return;
std::wstring wstrl(std::move(rv.value()));
size_t wstrl_size = wstrl.size(); size_t wstrl_size = wstrl.size();
// write string with size check // write string with size check
if (wstrl_size <= std::numeric_limits<DWORD>::max()) { if (wstrl_size <= std::numeric_limits<DWORD>::max()) {
@ -171,11 +169,8 @@ namespace yycc::carton::csconsole {
// treat as format string // treat as format string
va_list argcpy; va_list argcpy;
va_copy(argcpy, argptr); va_copy(argcpy, argptr);
auto rv = OP::vprintf(u8_fmt, argcpy); strl = OP::vprintf(u8_fmt, argcpy);
va_end(argcpy); va_end(argcpy);
// check format result
if (!rv.has_value()) return;
else strl = std::move(rv.value());
} else { } else {
// treat as plain string // treat as plain string
strl = u8_fmt; strl = u8_fmt;

View File

@ -63,11 +63,9 @@ namespace yycc::carton::ironpad {
// check singleton // check singleton
// build mutex string first // build mutex string first
auto mutex_name = OP::printf(u8"Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}", GetCurrentProcessId()); auto mutex_name = OP::printf(u8"Global\\%" PRIu32 ".{61634294-d23c-43f9-8490-b5e09837eede}", GetCurrentProcessId());
if (!mutex_name.has_value()) return false; auto wmutex_name = ENC::to_wchar(mutex_name).value();
auto w_mutex_name = ENC::to_wchar(mutex_name.value());
if (!w_mutex_name.has_value()) return false;
// create mutex // create mutex
m_SingletonMutex = CreateMutexW(NULL, FALSE, w_mutex_name.value().c_str()); m_SingletonMutex = CreateMutexW(NULL, FALSE, wmutex_name.c_str());
DWORD errcode = GetLastError(); DWORD errcode = GetLastError();
// check whether be created // check whether be created
if (m_SingletonMutex == NULL) return false; if (m_SingletonMutex == NULL) return false;
@ -303,18 +301,17 @@ namespace yycc::carton::ironpad {
DWORD process_id = GetCurrentProcessId(); DWORD process_id = GetCurrentProcessId();
// conbine them as a file name prefix // conbine them as a file name prefix
auto u8_filename_prefix = OP::printf(u8"%s.%" PRIu32, u8_process_name.c_str(), process_id); auto u8_filename_prefix = OP::printf(u8"%s.%" PRIu32, u8_process_name.c_str(), process_id);
if (!u8_filename_prefix.has_value()) return std::nullopt;
// then get file name for log and minidump // then get file name for log and minidump
std::u8string u8_filename; std::u8string u8_filename;
switch (kind) { switch (kind) {
case FileKind::LogFile: case FileKind::LogFile:
u8_filename = u8_filename_prefix.value() + u8".log"; u8_filename = u8_filename_prefix + u8".log";
break; break;
case FileKind::CoredumpFile: case FileKind::CoredumpFile:
u8_filename = u8_filename_prefix.value() + u8".dmp"; u8_filename = u8_filename_prefix + u8".dmp";
break; break;
default: default:
u8_filename = u8_filename_prefix.value(); u8_filename = u8_filename_prefix;
break; break;
} }
@ -451,11 +448,9 @@ namespace yycc::carton::ironpad {
* @details This function will write coredump of given exception into given file path. * @details This function will write coredump of given exception into given file path.
*/ */
void do_coredump(const std::u8string_view& u8_filename, LPEXCEPTION_POINTERS info) { void do_coredump(const std::u8string_view& u8_filename, LPEXCEPTION_POINTERS info) {
// convert file encoding // convert file encoding.
// if convertion failed, return // it must be okey.
auto filename_rv = ENC::to_wchar(u8_filename); auto filename = ENC::to_wchar(u8_filename).value();
if (!filename_rv.has_value()) return;
std::wstring filename = filename_rv.value();
// open file and write // open file and write
HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE hFile = CreateFileW(filename.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
@ -554,15 +549,11 @@ namespace yycc::carton::ironpad {
* @param[in] ... The argument to be formatted. * @param[in] ... The argument to be formatted.
*/ */
void log_format_line(std::FILE* fs, const char8_t* fmt, ...) { void log_format_line(std::FILE* fs, const char8_t* fmt, ...) {
// do format first // write to file and console
va_list arg; va_list arg;
va_start(arg, fmt); va_start(arg, fmt);
auto fmt_rv = OP::vprintf(fmt, arg); log_write_line(fs, OP::vprintf(fmt, arg).c_str());
va_end(arg); va_end(arg);
// write to file and console
if (fmt_rv.has_value()) {
log_write_line(fs, fmt_rv.value().c_str());
}
} }
}; };

View File

@ -468,11 +468,8 @@ namespace yycc::carton::wcwidth {
} }
Result<size_t> wcswidth(const std::u8string_view& rhs) { Result<size_t> wcswidth(const std::u8string_view& rhs) {
// Cast encoding // Cast encoding and call underlying function
auto u32str = ENC::to_utf32(rhs); return wcswidth(ENC::to_utf32(rhs).value());
if (!u32str.has_value()) return std::unexpected(Error::BadEncoding);
// Call underlying function
return wcswidth(u32str.value());
} }
} // namespace yycc::carton::wcwidth } // namespace yycc::carton::wcwidth

View File

@ -31,7 +31,6 @@ namespace yycc::carton::wcwidth {
/// @brief Error occurs in this module /// @brief Error occurs in this module
enum class Error { enum class Error {
BadEncoding, ///< Given
BadAnsiEscSeq, ///< Bad char when processing ANSI Escape Sequence BadAnsiEscSeq, ///< Bad char when processing ANSI Escape Sequence
BadCsiSeq, ///< Bad char when processing CSI Sequence. BadCsiSeq, ///< Bad char when processing CSI Sequence.
}; };

View File

@ -0,0 +1,28 @@
#pragma once
#include "compiler_detector.hpp"
#include "stl_detector.hpp"
// YYC MARK:
// This code is copied from Qt project.
#if defined(YYCC_CC_GCC)
// GCC has its special attribute
#define YYCC_PRINTF_CHECK_ATTR(A, B) __attribute__((format(gnu_printf, (A), (B))))
#elif defined(YYCC_CC_CLANG)
// Clang use its own attribute
#define YYCC_PRINTF_CHECK_ATTR(A, B) __attribute__((format(printf, (A), (B))))
#else
// Other CC do not support this (like MSVC), skip it
#define YYCC_PRINTF_CHECK_ATTR(A, B)
#endif
#if defined(YYCC_STL_MSSTL)
// On Microsoft STL, we can use some mechanisms to check it.
#include "../windows/import_guard_head.hpp"
#include <sal.h>
#include "../windows/import_guard_tail.hpp"
#define YYCC_PRINTF_CHECK_FMTSTR _Printf_format_string_
#else
// Other STL do not have this.
#define YYCC_PRINTF_CHECK_FMTSTR
#endif

View File

@ -18,19 +18,9 @@ namespace yycc::patch::fopen {
std::FILE* fopen(const char8_t* u8_filepath, const char8_t* u8_mode) { std::FILE* fopen(const char8_t* u8_filepath, const char8_t* u8_mode) {
#if defined(YYCC_OS_WINDOWS) #if defined(YYCC_OS_WINDOWS)
// convert mode and file path to wchar // Convert encoding first, and call MSVCRT specified fopen which support wchar as argument.
auto wmode = ENC::to_wchar(u8_mode); // Reference: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170
auto wpath = ENC::to_wchar(u8_filepath); return _wfopen(ENC::to_wchar(u8_filepath).value().c_str(), ENC::to_wchar(u8_mode).value().c_str());
// check convertion success
if (wmode.has_value() && wpath.has_value()) {
// Call MSVCRT specified fopen which support wchar as argument.
// Reference: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-170
return _wfopen(wpath.value().c_str(), wmode.value().c_str());
} else {
// fail to convert encoding
return nullptr;
}
#else #else
return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode)); return std::fopen(REINTERPRET::as_ordinary(u8_filepath), REINTERPRET::as_ordinary(u8_mode));
#endif #endif

View File

@ -25,10 +25,7 @@ namespace yycc::rust::env {
#if defined(YYCC_OS_WINDOWS) #if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew // Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-getenvironmentvariablew
// Convert to wchar // Convert to wchar
auto wname = ENC::to_wchar(name); auto wname = ENC::to_wchar(name).value();
if (!wname.has_value()) {
return std::unexpected(EnvError::BadEncoding);
}
// Prepare a variable with proper init size for holding value. // Prepare a variable with proper init size for holding value.
std::wstring wvalue; std::wstring wvalue;
@ -42,7 +39,7 @@ namespace yycc::rust::env {
// So we forcely use checked add and sub for this bad behavior. // So we forcely use checked add and sub for this bad behavior.
auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1); auto fct_size = SAFEOP::checked_add<size_t>(wvalue.size(), 1);
if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic); if (!fct_size.has_value()) return std::unexpected(EnvError::BadArithmetic);
auto rv = ::GetEnvironmentVariableW(wname.value().c_str(), wvalue.data(), fct_size.value()); auto rv = ::GetEnvironmentVariableW(wname.c_str(), wvalue.data(), fct_size.value());
// Check the return value // Check the return value
if (rv == 0) { if (rv == 0) {
@ -95,20 +92,10 @@ namespace yycc::rust::env {
#if defined(YYCC_OS_WINDOWS) #if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew // Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
// Convert to wchar // Convert to wchar, set variable, and check result.
auto wname = ENC::to_wchar(name); auto rv = ::SetEnvironmentVariableW(ENC::to_wchar(name).value().c_str(), ENC::to_wchar(value).value().c_str());
auto wvalue = ENC::to_wchar(value); if (!rv) return std::unexpected(EnvError::BadCall);
if (!(wname.has_value() && wvalue.has_value())) { else return {};
return std::unexpected(EnvError::BadEncoding);
}
// Delete variable and check result.
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), wvalue.value().c_str());
if (!rv) {
return std::unexpected(EnvError::BadCall);
}
return {};
#else #else
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html // Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/setenv.html
@ -129,19 +116,10 @@ namespace yycc::rust::env {
#if defined(YYCC_OS_WINDOWS) #if defined(YYCC_OS_WINDOWS)
// Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew // Reference: https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-setenvironmentvariablew
// Convert to wchar // Convert to wchar, delete variable, and check result.
auto wname = ENC::to_wchar(name); auto rv = ::SetEnvironmentVariableW(ENC::to_wchar(name).value().c_str(), NULL);
if (!wname.has_value()) { if (!rv) return std::unexpected(EnvError::BadCall);
return std::unexpected(EnvError::BadEncoding); else return {};
}
// Delete variable and check result.
auto rv = ::SetEnvironmentVariableW(wname.value().c_str(), NULL);
if (!rv) {
return std::unexpected(EnvError::BadCall);
}
return {};
#else #else
// Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html // Reference: https://pubs.opengroup.org/onlinepubs/9699919799/functions/unsetenv.html

View File

@ -1,19 +1,21 @@
#include "op.hpp" #include "op.hpp"
#include <type_traits>
#include <algorithm> #include <algorithm>
#include <stdexcept>
namespace yycc::string::op { namespace yycc::string::op {
#pragma region Printf VPrintf #pragma region Printf VPrintf
/// @brief The concept for all viable char type in printf function family
template<typename TChar> template<typename TChar>
requires(sizeof(TChar) == sizeof(char)) concept PrintfSupportedChar = std::is_same_v<TChar, char> || std::is_same_v<TChar, char8_t>;
static FormatResult<std::basic_string<TChar>> generic_printf(const TChar* format, va_list argptr) {
template<PrintfSupportedChar TChar>
static std::basic_string<TChar> generic_printf(const TChar* format, va_list argptr) {
// Prepare result // Prepare result
std::basic_string<TChar> rv; std::basic_string<TChar> rv;
// Check format
if (format == nullptr) return std::unexpected(FormatError::NullFormat);
// Prepare variable arguments // Prepare variable arguments
va_list args1; va_list args1;
va_copy(args1, argptr); va_copy(args1, argptr);
@ -21,12 +23,13 @@ namespace yycc::string::op {
va_copy(args2, argptr); va_copy(args2, argptr);
// The return value is desired char count without NULL terminal. // The return value is desired char count without NULL terminal.
// Minus number means error. // Negative number means error.
int count = std::vsnprintf(nullptr, 0, reinterpret_cast<const char*>(format), args1); int count = std::vsnprintf(nullptr, 0, reinterpret_cast<const char*>(format), args1);
// Check expected size. // Check expected size.
if (count < 0) { if (count < 0) {
// Invalid length returned by vsnprintf. // Invalid length returned by vsnprintf.
return std::unexpected(FormatError::NoDesiredSize); // This may be caused by invalid format string
throw std::logic_error("fail to determine the size of formatted string");
} }
va_end(args1); va_end(args1);
@ -39,14 +42,15 @@ namespace yycc::string::op {
// Check written size. // Check written size.
if (write_result < 0 || write_result > count) { if (write_result < 0 || write_result > count) {
// Invalid write result in vsnprintf. // Invalid write result in vsnprintf.
return std::unexpected(FormatError::BadWrittenSize); // Idk why this can happen.
throw std::logic_error("the size of written formatted string is not expected");
} }
// Return value // Return value
return rv; return rv;
} }
FormatResult<std::u8string> printf(const char8_t* format, ...) { std::u8string printf(const char8_t* format, ...) {
va_list argptr; va_list argptr;
va_start(argptr, format); va_start(argptr, format);
auto rv = vprintf(format, argptr); auto rv = vprintf(format, argptr);
@ -54,11 +58,11 @@ namespace yycc::string::op {
return rv; return rv;
} }
FormatResult<std::u8string> vprintf(const char8_t* format, va_list argptr) { std::u8string vprintf(const char8_t* format, va_list argptr) {
return generic_printf(format, argptr); return generic_printf(format, argptr);
} }
FormatResult<std::string> printf(const char* format, ...) { std::string printf(const char* format, ...) {
va_list argptr; va_list argptr;
va_start(argptr, format); va_start(argptr, format);
auto rv = vprintf(format, argptr); auto rv = vprintf(format, argptr);
@ -66,7 +70,7 @@ namespace yycc::string::op {
return rv; return rv;
} }
FormatResult<std::string> vprintf(const char* format, va_list argptr) { std::string vprintf(const char* format, va_list argptr) {
return generic_printf(format, argptr); return generic_printf(format, argptr);
} }

View File

@ -1,50 +1,41 @@
#pragma once #pragma once
#include "../macro/printf_checker.hpp"
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <cstdarg> #include <cstdarg>
#include <functional> #include <functional>
#include <vector> #include <vector>
#include <expected>
namespace yycc::string::op { namespace yycc::string::op {
enum class FormatError {
NullFormat, ///< Given format string is nullptr.
NoDesiredSize, ///< Fail to fetch the expected size of result.
BadWrittenSize, ///< The written size is different with expected size.
};
template<typename T>
using FormatResult = std::expected<T, FormatError>;
/** /**
* @brief Perform an UTF8 string formatting operation. * @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string. * @param[in] format The format string.
* @param[in] ... Argument list of format string. * @param[in] ... Argument list of format string.
* @return The formatted result, or the fail reason. * @return The formatted result.
*/ */
FormatResult<std::u8string> printf(const char8_t* format, ...); std::u8string printf(YYCC_PRINTF_CHECK_FMTSTR const char8_t* format, ...) YYCC_PRINTF_CHECK_ATTR(1, 2);
/** /**
* @brief Perform an UTF8 string formatting operation. * @brief Perform an UTF8 string formatting operation.
* @param[in] format The format string. * @param[in] format The format string.
* @param[in] argptr Argument list of format string. * @param[in] argptr Argument list of format string.
* @return The formatted result, or the fail reason. * @return The formatted result.
*/ */
FormatResult<std::u8string> vprintf(const char8_t* format, va_list argptr); std::u8string vprintf(const char8_t* format, va_list argptr);
/** /**
* @brief Perform an ordinary string formatting operation. * @brief Perform an ordinary string formatting operation.
* @param[in] format The format string. * @param[in] format The format string.
* @param[in] ... Argument list of format string. * @param[in] ... Argument list of format string.
* @return The formatted result, or the fail reason. * @return The formatted result.
*/ */
FormatResult<std::string> printf(const char* format, ...); std::string printf(YYCC_PRINTF_CHECK_FMTSTR const char* format, ...) YYCC_PRINTF_CHECK_ATTR(1, 2);
/** /**
* @brief Perform an ordinary string formatting operation. * @brief Perform an ordinary string formatting operation.
* @param[in] format The format string. * @param[in] format The format string.
* @param[in] argptr Argument list of format string. * @param[in] argptr Argument list of format string.
* @return The formatted result, or the fail reason. * @return The formatted result.
*/ */
FormatResult<std::string> vprintf(const char* format, va_list argptr); std::string vprintf(const char* format, va_list argptr);
/** /**
* @brief Modify given string with all occurrences of substring \e old replaced by \e new. * @brief Modify given string with all occurrences of substring \e old replaced by \e new.

View File

@ -130,21 +130,15 @@ namespace yycc::windows::dialog {
// build new Windows oriented string vector // build new Windows oriented string vector
for (const auto& item : m_Filters) { for (const auto& item : m_Filters) {
// convert name to wchar // convert name to wchar
auto win_name = ENC::to_wchar(item.first); auto win_name = ENC::to_wchar(item.first).value();
if (!win_name.has_value()) {
return std::unexpected(DialogError::BadEncoding);
}
// join pattern string and convert to wchar // join pattern string and convert to wchar
const auto& modes = item.second; const auto& modes = item.second;
auto joined_modes = OP::join(modes.begin(), modes.end(), u8";"); auto joined_modes = OP::join(modes.begin(), modes.end(), u8";");
auto win_modes = ENC::to_wchar(joined_modes); auto win_modes = ENC::to_wchar(joined_modes).value();
if (!win_modes.has_value()) {
return std::unexpected(DialogError::BadEncoding);
}
// append this pair // append this pair
rv.m_WinFilters.emplace_back(std::make_pair(win_name.value(), win_modes.value())); rv.m_WinFilters.emplace_back(std::make_pair(win_name, win_modes));
} }
// update data struct // update data struct
@ -305,28 +299,21 @@ namespace yycc::windows::dialog {
// build title and init file name // build title and init file name
if (m_Title.has_value()) { if (m_Title.has_value()) {
auto win_title = ENC::to_wchar(m_Title.value()); rv.m_WinTitle = ENC::to_wchar(m_Title.value()).value();
if (!win_title.has_value()) return std::unexpected(DialogError::BadEncoding);
else rv.m_WinTitle = win_title.value();
} else rv.m_WinTitle = std::nullopt; } else rv.m_WinTitle = std::nullopt;
if (m_InitFileName.has_value()) { if (m_InitFileName.has_value()) {
auto win_init_file_name = ENC::to_wchar(m_InitFileName.value()); rv.m_WinInitFileName = ENC::to_wchar(m_InitFileName.value()).value();
if (!win_init_file_name.has_value()) return std::unexpected(DialogError::BadEncoding);
else rv.m_WinInitFileName = win_init_file_name.value();
} else rv.m_WinInitFileName = std::nullopt; } else rv.m_WinInitFileName = std::nullopt;
// fetch init directory // fetch init directory
if (m_InitDirectory.has_value()) { if (m_InitDirectory.has_value()) {
// convert to wchar path // convert to wchar path
auto w_init_dir = ENC::to_wchar(m_InitDirectory.value()); auto w_init_dir = ENC::to_wchar(m_InitDirectory.value()).value();
if (!w_init_dir.has_value()) {
return std::unexpected(DialogError::BadEncoding);
}
// fetch IShellItem* // fetch IShellItem*
// Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface // Ref: https://stackoverflow.com/questions/76306324/how-to-set-default-folder-for-ifileopendialog-interface
IShellItem* init_directory = NULL; IShellItem* init_directory = NULL;
HRESULT hr = SHCreateItemFromParsingName(w_init_dir.value().c_str(), NULL, IID_PPV_ARGS(&init_directory)); HRESULT hr = SHCreateItemFromParsingName(w_init_dir.c_str(), NULL, IID_PPV_ARGS(&init_directory));
if (FAILED(hr)) return std::unexpected(DialogError::NoSuchDir); if (FAILED(hr)) return std::unexpected(DialogError::NoSuchDir);
// assign IShellItem* // assign IShellItem*
@ -378,7 +365,7 @@ namespace yycc::windows::dialog {
WINCOM::SmartLPWSTR display_name(display_name_ptr); WINCOM::SmartLPWSTR display_name(display_name_ptr);
// convert result and return // convert result and return
return ENC::to_utf8(display_name.get()).transform_error([](auto err) { return DialogError::BadEncoding; }); return ENC::to_utf8(display_name.get()).value();
} }
/** /**

View File

@ -29,7 +29,6 @@ namespace yycc::windows::dialog {
/// @brief The error occurs in this module. /// @brief The error occurs in this module.
enum class DialogError { enum class DialogError {
BadEncoding, ///< Error occurs when perform encoding convertion.
TooManyFilters, ///< The size of file filters list is too large for Windows. TooManyFilters, ///< The size of file filters list is too large for Windows.
IndexOverflow, ///< Default filter index is too large for Windows. IndexOverflow, ///< Default filter index is too large for Windows.
EmptyFilters, ///< File filters is empty when picking file. EmptyFilters, ///< File filters is empty when picking file.
@ -310,7 +309,7 @@ namespace yycc::windows::dialog {
*/ */
DialogResult<DialogOutcome<std::u8string>> open_folder(const FileDialog& params); DialogResult<DialogOutcome<std::u8string>> open_folder(const FileDialog& params);
} } // namespace yycc::windows::dialog
#undef NS_YYCC_WINDOWS_COM #undef NS_YYCC_WINDOWS_COM

View File

@ -21,7 +21,7 @@ namespace yycc::windows::winfct {
(LPCWSTR) get_current_module, (LPCWSTR) get_current_module,
&hModule); &hModule);
if (rv) return hModule; if (rv) return hModule;
else return std::unexpected(WinFctError::Backend); else return std::unexpected(WinFctError::Win32);
} }
WinFctResult<std::u8string> get_temp_directory() { WinFctResult<std::u8string> get_temp_directory() {
@ -33,7 +33,7 @@ namespace yycc::windows::winfct {
while (true) { while (true) {
if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) { if ((expected_size = ::GetTempPathW(static_cast<DWORD>(wpath.size()), wpath.data())) == 0) {
// failed, return // failed, return
return std::unexpected(WinFctError::Backend); return std::unexpected(WinFctError::Win32);
} }
if (expected_size > static_cast<DWORD>(wpath.size())) { if (expected_size > static_cast<DWORD>(wpath.size())) {
@ -47,7 +47,7 @@ namespace yycc::windows::winfct {
} }
// convert to utf8 and return // convert to utf8 and return
return ENC::to_utf8(wpath).transform_error([](auto err) { return WinFctError::Encoding; }); return ENC::to_utf8(wpath).value();
} }
WinFctResult<std::u8string> get_module_file_name(HINSTANCE hModule) { WinFctResult<std::u8string> get_module_file_name(HINSTANCE hModule) {
@ -58,7 +58,7 @@ namespace yycc::windows::winfct {
while (true) { while (true) {
if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) { if ((copied_size = ::GetModuleFileNameW(hModule, wpath.data(), static_cast<DWORD>(wpath.size()))) == 0) {
// failed, return // failed, return
return std::unexpected(WinFctError::Backend); return std::unexpected(WinFctError::Win32);
} }
// check insufficient buffer // check insufficient buffer
@ -73,7 +73,7 @@ namespace yycc::windows::winfct {
} }
// convert to utf8 and return // convert to utf8 and return
return ENC::to_utf8(wpath).transform_error([](auto err) { return WinFctError::Encoding; }); return ENC::to_utf8(wpath).value();
} }
bool is_valid_code_page(UINT code_page) { bool is_valid_code_page(UINT code_page) {
@ -82,41 +82,32 @@ namespace yycc::windows::winfct {
} }
WinFctResult<void> copy_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName, BOOL bFailIfExists) { WinFctResult<void> copy_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName, BOOL bFailIfExists) {
auto wExistingFileName = ENC::to_wchar(lpExistingFileName); auto wExistingFileName = ENC::to_wchar(lpExistingFileName).value();
auto wNewFileName = ENC::to_wchar(lpNewFileName); auto wNewFileName = ENC::to_wchar(lpNewFileName).value();
if (!(wExistingFileName.has_value() && wNewFileName.has_value())) {
return std::unexpected(WinFctError::Encoding);
}
if (!::CopyFileW(wExistingFileName.value().c_str(), wNewFileName.value().c_str(), bFailIfExists)) { if (!::CopyFileW(wExistingFileName.c_str(), wNewFileName.c_str(), bFailIfExists)) {
return std::unexpected(WinFctError::Backend); return std::unexpected(WinFctError::Win32);
} }
return {}; return {};
} }
WinFctResult<void> move_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName) { WinFctResult<void> move_file(const std::u8string_view& lpExistingFileName, const std::u8string_view& lpNewFileName) {
auto wExistingFileName = ENC::to_wchar(lpExistingFileName); auto wExistingFileName = ENC::to_wchar(lpExistingFileName).value();
auto wNewFileName = ENC::to_wchar(lpNewFileName); auto wNewFileName = ENC::to_wchar(lpNewFileName).value();
if (!(wExistingFileName.has_value() && wNewFileName.has_value())) {
return std::unexpected(WinFctError::Encoding);
}
if (!::MoveFileW(wExistingFileName.value().c_str(), wNewFileName.value().c_str())) { if (!::MoveFileW(wExistingFileName.c_str(), wNewFileName.c_str())) {
return std::unexpected(WinFctError::Backend); return std::unexpected(WinFctError::Win32);
} }
return {}; return {};
} }
WinFctResult<void> delete_file(const std::u8string_view& lpFileName) { WinFctResult<void> delete_file(const std::u8string_view& lpFileName) {
auto wFileName = ENC::to_wchar(lpFileName); auto wFileName = ENC::to_wchar(lpFileName).value();
if (!wFileName.has_value()) {
return std::unexpected(WinFctError::Encoding);
}
if (!::DeleteFileW(wFileName.value().c_str())) { if (!::DeleteFileW(wFileName.c_str())) {
return std::unexpected(WinFctError::Backend); return std::unexpected(WinFctError::Win32);
} }
return {}; return {};
@ -144,11 +135,11 @@ namespace yycc::windows::winfct {
// fetch path // fetch path
LPWSTR raw_known_path; LPWSTR raw_known_path;
HRESULT hr = SHGetKnownFolderPath(*pId, KF_FLAG_CREATE, NULL, &raw_known_path); HRESULT hr = SHGetKnownFolderPath(*pId, KF_FLAG_CREATE, NULL, &raw_known_path);
if (FAILED(hr)) return std::unexpected(WinFctError::Backend); if (FAILED(hr)) return std::unexpected(WinFctError::Win32);
COM::SmartLPWSTR known_path(raw_known_path); COM::SmartLPWSTR known_path(raw_known_path);
// convert to utf8 and return // convert to utf8 and return
return ENC::to_utf8(known_path.get()).transform_error([](auto err) { return WinFctError::Encoding; }); return ENC::to_utf8(known_path.get()).value();
} }
#endif #endif

View File

@ -15,8 +15,7 @@ namespace yycc::windows::winfct {
/// @brief All errors occur in this module. /// @brief All errors occur in this module.
enum class WinFctError { enum class WinFctError {
Backend, ///< Error occurs when calling Win32 functions. Win32, ///< Error occurs when calling Win32 functions.
Encoding, ///< Can not perform encoding convertion.
NoCom, ///< No COM environment. NoCom, ///< No COM environment.
}; };

View File

@ -39,6 +39,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/clap.cpp
) )
target_sources(YYCCTestbench target_sources(YYCCTestbench
PRIVATE PRIVATE

View File

@ -10,12 +10,11 @@ namespace yycctest::patch::ptr_pad {
TEST(PatchPtrPad, Normal) { TEST(PatchPtrPad, Normal) {
auto rv = OP::printf(u8"0x%" PRIXPTR_LPAD PRIXPTR, nullptr); auto rv = OP::printf(u8"0x%" PRIXPTR_LPAD PRIXPTR, nullptr);
EXPECT_TRUE(rv.has_value());
#if defined(YYCC_PTRSIZE_32) #if defined(YYCC_PTRSIZE_32)
EXPECT_EQ(rv.value(), u8"0x00000000"); EXPECT_EQ(rv, u8"0x00000000");
#else #else
EXPECT_EQ(rv.value(), u8"0x0000000000000000"); EXPECT_EQ(rv, u8"0x0000000000000000");
#endif #endif
} }

View File

@ -10,8 +10,16 @@ using namespace std::literals::string_view_literals;
namespace yycctest::string::op { namespace yycctest::string::op {
TEST(StringOp, Printf) { TEST(StringOp, Printf) {
auto rv = OP::printf(u8"%s == %s", u8"Hello World", u8"Hello, world"); // UTF8 string
EXPECT_EQ(rv, u8"Hello World == Hello, world"); {
auto rv = OP::printf(u8"%s == %s", u8"Hello World", u8"Hello, world");
EXPECT_EQ(rv, u8"Hello World == Hello, world");
}
// Ordinary string
{
auto rv = OP::printf("%s == %s", "Hello World", "Hello, world");
EXPECT_EQ(rv, "Hello World == Hello, world");
}
} }
TEST(StringOp, Replace) { TEST(StringOp, Replace) {