From cb6c81bf02f1c0b9cdce372e93b17dab1a1343e6 Mon Sep 17 00:00:00 2001 From: matyalatte Date: Mon, 4 Nov 2024 16:21:23 +0900 Subject: [PATCH] Replace std::string with a custom string class (#56) * replace std::string with a custom string class * remove unnecessary include guard * fix a bug where sometimes tuw can't redirect stdout on linux --- include/component.h | 44 ++--- include/exe_container.h | 8 +- include/exec.h | 10 +- include/json_utils.h | 10 +- include/main_frame.h | 16 +- include/string_utils.h | 163 +++++++++++++++-- include/validator.h | 21 +-- src/component.cpp | 54 +++--- src/exe_container.cpp | 52 +++--- src/exec.cpp | 32 ++-- src/json_utils.cpp | 78 ++++---- src/main.cpp | 17 +- src/main_frame.cpp | 88 +++++---- src/string_utils.cpp | 277 ++++++++++++++++++++++++----- src/validator.cpp | 3 +- tests/exe_container_test.cpp | 4 +- tests/json_check_test.cpp | 2 - tests/main_frame_test.cpp | 20 +-- tests/string_utils_test.cpp | 334 ++++++++++++++++++++++++++++++++--- tests/test_utils.h | 1 - 20 files changed, 948 insertions(+), 286 deletions(-) diff --git a/include/component.h b/include/component.h index 7af4bfe7..60958a8f 100644 --- a/include/component.h +++ b/include/component.h @@ -1,8 +1,8 @@ #pragma once -#include #include #include "rapidjson/document.h" #include "ui.h" +#include "string_utils.h" #include "validator.h" #define UNUSED(x) (void)(x) @@ -11,15 +11,15 @@ class Component { protected: void* m_widget; - std::string m_label; - std::string m_id; + tuwString m_label; + tuwString m_id; bool m_has_string; bool m_is_wide; Validator m_validator; uiLabel* m_error_widget; bool m_optional; - std::string m_prefix; - std::string m_suffix; + tuwString m_prefix; + tuwString m_suffix; private: bool m_add_quotes; @@ -27,9 +27,9 @@ class Component { public: explicit Component(const rapidjson::Value& j); virtual ~Component(); - virtual std::string GetRawString() { return "";} - std::string GetString(); - const std::string& GetID() const { return m_id; } + virtual tuwString GetRawString() { return "";} + tuwString GetString(); + const tuwString& GetID() const { return m_id; } virtual void SetConfig(const rapidjson::Value& config) { UNUSED(config); } virtual void GetConfig(rapidjson::Document& config) { UNUSED(config); } @@ -38,7 +38,7 @@ class Component { bool IsWide() const { return m_is_wide; } bool Validate(bool* redraw_flag); - const std::string& GetValidationError() const; + const tuwString& GetValidationError() const; void PutErrorWidget(uiBox* box); static Component* PutComponent(uiBox* box, const rapidjson::Value& j); @@ -47,10 +47,10 @@ class Component { // containers for Combo and CheckArray class MultipleValuesContainer { protected: - std::vector m_values; + std::vector m_values; public: - void SetValues(std::vector values){ + void SetValues(std::vector values){ m_values = values; } }; @@ -74,9 +74,9 @@ class StringComponentBase : public Component { class FilePicker : public StringComponentBase { private: - std::string m_ext; + tuwString m_ext; public: - std::string GetRawString() override; + tuwString GetRawString() override; FilePicker(uiBox* box, const rapidjson::Value& j); void SetConfig(const rapidjson::Value& config) override; void OpenFile(); @@ -84,7 +84,7 @@ class FilePicker : public StringComponentBase { class DirPicker : public StringComponentBase { public: - std::string GetRawString() override; + tuwString GetRawString() override; DirPicker(uiBox* box, const rapidjson::Value& j); void SetConfig(const rapidjson::Value& config) override; void OpenFolder(); @@ -92,7 +92,7 @@ class DirPicker : public StringComponentBase { class ComboBox : public StringComponentBase, MultipleValuesContainer { public: - std::string GetRawString() override; + tuwString GetRawString() override; ComboBox(uiBox* box, const rapidjson::Value& j); void GetConfig(rapidjson::Document& config) override; void SetConfig(const rapidjson::Value& config) override; @@ -100,7 +100,7 @@ class ComboBox : public StringComponentBase, MultipleValuesContainer { class RadioButtons : public StringComponentBase, MultipleValuesContainer { public: - std::string GetRawString() override; + tuwString GetRawString() override; RadioButtons(uiBox* box, const rapidjson::Value& j); void GetConfig(rapidjson::Document& config) override; void SetConfig(const rapidjson::Value& config) override; @@ -108,9 +108,9 @@ class RadioButtons : public StringComponentBase, MultipleValuesContainer { class CheckBox : public Component { private: - std::string m_value; + tuwString m_value; public: - std::string GetRawString() override; + tuwString GetRawString() override; CheckBox(uiBox* box, const rapidjson::Value& j); void GetConfig(rapidjson::Document& config) override; void SetConfig(const rapidjson::Value& config) override; @@ -118,7 +118,7 @@ class CheckBox : public Component { class CheckArray : public StringComponentBase, MultipleValuesContainer { public: - std::string GetRawString() override; + tuwString GetRawString() override; CheckArray(uiBox* box, const rapidjson::Value& j); void GetConfig(rapidjson::Document& config) override; void SetConfig(const rapidjson::Value& config) override; @@ -126,7 +126,7 @@ class CheckArray : public StringComponentBase, MultipleValuesContainer { class TextBox : public StringComponentBase { public: - std::string GetRawString() override; + tuwString GetRawString() override; TextBox(uiBox* box, const rapidjson::Value& j); void SetConfig(const rapidjson::Value& config) override; }; @@ -134,7 +134,7 @@ class TextBox : public StringComponentBase { class IntPicker : public StringComponentBase { public: IntPicker(uiBox* box, const rapidjson::Value& j); - std::string GetRawString() override; + tuwString GetRawString() override; void GetConfig(rapidjson::Document& config) override; void SetConfig(const rapidjson::Value& config) override; }; @@ -143,7 +143,7 @@ class IntPicker : public StringComponentBase { class FloatPicker : public StringComponentBase { public: FloatPicker(uiBox* box, const rapidjson::Value& j); - std::string GetRawString() override; + tuwString GetRawString() override; void GetConfig(rapidjson::Document& config) override; void SetConfig(const rapidjson::Value& config) override; }; diff --git a/include/exe_container.h b/include/exe_container.h index b29e4c16..a6cb4689 100644 --- a/include/exe_container.h +++ b/include/exe_container.h @@ -1,11 +1,11 @@ #pragma once -#include #include "rapidjson/document.h" #include "json_utils.h" +#include "string_utils.h" class ExeContainer { private: - std::string m_exe_path; + tuwString m_exe_path; uint32_t m_exe_size; rapidjson::Document m_json; @@ -15,8 +15,8 @@ class ExeContainer { m_json() { m_json.SetObject(); } - json_utils::JsonResult Read(const std::string& exe_path); - json_utils::JsonResult Write(const std::string& exe_path); + json_utils::JsonResult Read(const tuwString& exe_path); + json_utils::JsonResult Write(const tuwString& exe_path); bool HasJson() { return m_json.IsObject() && !m_json.ObjectEmpty(); } void GetJson(rapidjson::Document& json) { json.CopyFrom(m_json, json.GetAllocator()); } void SetJson(rapidjson::Document& json) { m_json.CopyFrom(json, m_json.GetAllocator()); } diff --git a/include/exec.h b/include/exec.h index 9d0df56b..aa343ec3 100644 --- a/include/exec.h +++ b/include/exec.h @@ -1,13 +1,13 @@ #pragma once -#include +#include "string_utils.h" struct ExecuteResult { int exit_code; - std::string err_msg; - std::string last_line; + tuwString err_msg; + tuwString last_line; }; // When use_utf8_on_windows is true, // Tuw converts output strings from UTF-8 to UTF-16 on Windows. -ExecuteResult Execute(const std::string& cmd, bool use_utf8_on_windows = false); -ExecuteResult LaunchDefaultApp(const std::string& url); +ExecuteResult Execute(const tuwString& cmd, bool use_utf8_on_windows = false); +ExecuteResult LaunchDefaultApp(const tuwString& url); diff --git a/include/json_utils.h b/include/json_utils.h index ae3c2dcf..843d4fd6 100644 --- a/include/json_utils.h +++ b/include/json_utils.h @@ -1,8 +1,8 @@ // Functions related to json. #pragma once -#include #include "rapidjson/document.h" +#include "string_utils.h" enum CmdPredefinedIds: int { CMD_ID_PERCENT = -1, @@ -18,7 +18,7 @@ namespace json_utils { struct JsonResult { bool ok; - std::string msg; + tuwString msg; }; #define JSON_RESULT_OK { true, "" } @@ -26,9 +26,9 @@ struct JsonResult { // Max binary size for JSON files. #define JSON_SIZE_MAX 128 * 1024 -JsonResult LoadJson(const std::string& file, rapidjson::Document& json); -JsonResult SaveJson(rapidjson::Document& json, const std::string& file); -std::string JsonToString(rapidjson::Document& json); +JsonResult LoadJson(const tuwString& file, rapidjson::Document& json); +JsonResult SaveJson(rapidjson::Document& json, const tuwString& file); +tuwString JsonToString(rapidjson::Document& json); const char* GetString(const rapidjson::Value& json, const char* key, const char* def); bool GetBool(const rapidjson::Value& json, const char* key, bool def); diff --git a/include/main_frame.h b/include/main_frame.h index e7524391..dc98d895 100644 --- a/include/main_frame.h +++ b/include/main_frame.h @@ -1,9 +1,9 @@ #pragma once #include -#include #include "rapidjson/document.h" #include "component.h" #include "json_utils.h" +#include "string_utils.h" #include "ui.h" // Main window @@ -26,9 +26,15 @@ class MainFrame { void CreateMenu(); json_utils::JsonResult CheckDefinition(rapidjson::Document& definition); void UpdateConfig(); - void ShowSuccessDialog(const std::string& msg, const std::string& title = "Success"); - void ShowErrorDialog(const std::string& msg, const std::string& title = "Error"); - void JsonLoadFailed(const std::string& msg); + void ShowSuccessDialog(const char* msg, const char* title = "Success"); + void ShowErrorDialog(const char* msg, const char* title = "Error"); + inline void ShowSuccessDialog(const tuwString& msg, const tuwString& title = "Success") { + ShowSuccessDialog(msg.c_str(), title.c_str()); + } + inline void ShowErrorDialog(const tuwString& msg, const tuwString& title = "Error") { + ShowErrorDialog(msg.c_str(), title.c_str()); + } + void JsonLoadFailed(const tuwString& msg); public: explicit MainFrame(const rapidjson::Document& definition = @@ -38,7 +44,7 @@ class MainFrame { void UpdatePanel(unsigned definition_id); void OpenURL(int id); bool Validate(); - std::string GetCommand(); + tuwString GetCommand(); void RunCommand(); void GetDefinition(rapidjson::Document& json); void SaveConfig(); diff --git a/include/string_utils.h b/include/string_utils.h index b94153dc..fb6fc872 100644 --- a/include/string_utils.h +++ b/include/string_utils.h @@ -1,30 +1,161 @@ #pragma once -#include #include "env_utils.h" -inline std::string envuStr(char *cstr) { - if (cstr == NULL) +enum StringError : int { + STR_OK = 0, + STR_ALLOCATION_ERROR, // Failed to allocate memory for string. + STR_BOUNDARY_ERROR, // Accessed out-of-bounds with substr or []. + STR_FORMAT_ERROR, // Failed to convert number to string. + STR_ERROR_MAX, +}; + +// Returns the error status for tuwString. +StringError GetStringError(); + +// Set STR_OK to the error status. +void ClearStringError(); + +// String class that doesn't raise std errors. +// It works without any crashes even when it got a memory allocation error. +// But you have to check GetStringError() after string allocations +// or it might have an unexpected value (an empty string). +class tuwString { + private: + char* m_str; + size_t m_size; + void alloc_cstr(const char* str, size_t size); + void append(const char* str, size_t size); + + public: + tuwString() : m_str(nullptr), m_size(0) {} + tuwString(const char* str); // NOLINT(runtime/explicit) + tuwString(const char* str, size_t size); + tuwString(const tuwString& str); + tuwString(tuwString&& str); + + // This allocates null bytes to use them as a buffer. + explicit tuwString(size_t size); + + ~tuwString() { clear(); } + size_t length() const { return m_size; } + size_t size() const { return m_size; } + + bool empty() const { return !m_str || m_str[0] == '\0' || m_size == 0; } + + // Returns an immutable C string. This can't be null. + const char* c_str() const { + if (m_str) + return m_str; return ""; - std::string str = cstr; - envuFree(cstr); - return str; + } + + // Returns a pointer to the actual buffer. + char* data() const { return m_str; } + + void clear() { + if (m_str) + free(m_str); + m_str = nullptr; + m_size = 0; + } + + const char& operator[](size_t id) const; + + tuwString& operator=(const char* str); + tuwString& operator=(const tuwString& str); + tuwString& operator=(tuwString&& str); + + tuwString& operator+=(const char* str); + tuwString& operator+=(const tuwString& str); + + tuwString operator+(const char* str) const; + tuwString operator+(const tuwString& str) const; + + // You can append numbers as strings + tuwString operator+(int num) const; + tuwString operator+(size_t num) const; + tuwString operator+(uint32_t num) const; + + bool operator==(const char* str) const; + bool operator==(const tuwString& str) const; + inline bool operator!=(const char* str) const { + return !(*this == str); + } + inline bool operator!=(const tuwString& str) const { + return !(*this == str); + } + + const char* begin() const { + return c_str(); + } + + const char* end() const { + return c_str() + m_size; + } + + static const size_t npos; + size_t find(const char c) const; + size_t find(const char* str) const; + inline size_t find(const tuwString& str) const { + return find(str.c_str()); + } + + void push_back(const char c) { + this->append(&c, 1); + } + + tuwString substr(size_t start, size_t size) const; +}; + +tuwString operator+(const char* str1, const tuwString& str2); + +inline bool operator==(const char* str1, const tuwString& str2) { + return str2 == str1; } -uint32_t Fnv1Hash32(const std::string& str); +inline bool operator!=(const char* str1, const tuwString& str2) { + return str2 != str1; +} -// Efficient way to concat two or three char* strings. -// str2 can be int, size_t, or uint32_t as well. It will be converted to string internally. -template -std::string ConcatCStrings(const char* str1, T str2, const char* str3); +class tuwWstring { + private: + wchar_t* m_str; + size_t m_size; -template -inline std::string ConcatCStrings(const char* str1, T str2) { - return ConcatCStrings(str1, str2, nullptr); + public: + tuwWstring(const wchar_t* str); // NOLINT(runtime/explicit) + ~tuwWstring() { clear(); } + size_t length() const { return m_size; } + size_t size() const { return m_size; } + bool empty() const { return !m_str || m_str[0] == L'\0' || m_size == 0; } + + const wchar_t* c_str() const { + if (m_str) + return m_str; + return L""; + } + + void clear() { + if (m_str) + free(m_str); + m_str = nullptr; + m_size = 0; + } +}; + +inline tuwString envuStr(char *cstr) { + if (cstr == NULL) + return ""; + tuwString str = cstr; + envuFree(cstr); + return str; } +uint32_t Fnv1Hash32(const tuwString& str); + #ifdef _WIN32 -std::string UTF16toUTF8(const wchar_t* str); -std::wstring UTF8toUTF16(const char* str); +tuwString UTF16toUTF8(const wchar_t* str); +tuwWstring UTF8toUTF16(const char* str); void PrintFmt(const char* fmt, ...); void EnableCSI(); #elif defined(__TUW_UNIX__) diff --git a/include/validator.h b/include/validator.h index 62bf5017..0d942a7b 100644 --- a/include/validator.h +++ b/include/validator.h @@ -1,19 +1,20 @@ #pragma once -#include #include #include "rapidjson/document.h" +#include "string_utils.h" + class Validator { private: - std::string m_regex; - std::string m_wildcard; + tuwString m_regex; + tuwString m_wildcard; bool m_not_empty; bool m_exist; - std::string m_regex_error; - std::string m_wildcard_error; - std::string m_not_empty_error; - std::string m_exist_error; - std::string m_error_msg; + tuwString m_regex_error; + tuwString m_wildcard_error; + tuwString m_not_empty_error; + tuwString m_exist_error; + tuwString m_error_msg; public: Validator() : m_regex(""), m_wildcard(""), @@ -21,6 +22,6 @@ class Validator { m_error_msg("") {} ~Validator() {} void Initialize(const rapidjson::Value& j); - bool Validate(const std::string& str); - const std::string& GetError() const { return m_error_msg; } + bool Validate(const tuwString& str); + const tuwString& GetError() const { return m_error_msg; } }; diff --git a/src/component.cpp b/src/component.cpp index 952c4fbf..efdca525 100644 --- a/src/component.cpp +++ b/src/component.cpp @@ -29,7 +29,7 @@ Component::Component(const rapidjson::Value& j) { m_id = json_utils::GetString(j, "id", ""); if (m_id.empty()) { uint32_t hash = Fnv1Hash32(j["label"].GetString()); - m_id = ConcatCStrings("_", hash); + m_id = tuwString("_") + hash; } m_add_quotes = json_utils::GetBool(j, "add_quotes", false); if (j.HasMember("validator")) @@ -42,18 +42,18 @@ Component::Component(const rapidjson::Value& j) { Component::~Component() { } -std::string Component::GetString() { - std::string str = GetRawString(); +tuwString Component::GetString() { + tuwString str = GetRawString(); if (m_optional && str.empty()) return ""; if (m_add_quotes) - str = ConcatCStrings("\"", str.c_str(), "\""); - return ConcatCStrings(m_prefix.c_str(), str.c_str(), m_suffix.c_str()); + str = tuwString("\"") + str + "\""; + return m_prefix + str + m_suffix; } bool Component::Validate(bool* redraw_flag) { // Main frame should run Fit() after this function. - std::string str = GetRawString(); + tuwString str = GetRawString(); if (m_optional && str.empty()) return true; @@ -80,7 +80,7 @@ bool Component::Validate(bool* redraw_flag) { return validate; } -const std::string& Component::GetValidationError() const { +const tuwString& Component::GetValidationError() const { return m_validator.GetError(); } @@ -214,15 +214,15 @@ FilePicker::FilePicker(uiBox* box, const rapidjson::Value& j) m_widget = putPathPicker(this, box, j, onOpenFileClicked); } -std::string FilePicker::GetRawString() { +tuwString FilePicker::GetRawString() { char* text = uiEntryText(static_cast(m_widget)); - std::string str = text; + tuwString str = text; uiFreeText(text); return str; } static void setConfigForTextBox(const rapidjson::Value& config, - const std::string& id, void *widget) { + const tuwString& id, void *widget) { const char* str = json_utils::GetString(config, id.c_str(), nullptr); if (str) { uiEntry* entry = static_cast(widget); @@ -263,7 +263,7 @@ class FilterList { public: FilterList(): filter_buf(NULL), filters(), ui_filters(NULL) {} - void MakeFilters(const std::string& ext) { + void MakeFilters(const tuwString& ext) { if (filter_buf != NULL) delete[] filter_buf; filter_buf = new char[ext.length() + 1]; @@ -364,9 +364,9 @@ DirPicker::DirPicker(uiBox* box, const rapidjson::Value& j) m_widget = putPathPicker(this, box, j, onOpenFolderClicked); } -std::string DirPicker::GetRawString() { +tuwString DirPicker::GetRawString() { char* text = uiEntryText(static_cast(m_widget)); - std::string str = text; + tuwString str = text; uiFreeText(text); return str; } @@ -398,7 +398,7 @@ void DirPicker::OpenFolder() { ComboBox::ComboBox(uiBox* box, const rapidjson::Value& j) : StringComponentBase(box, j) { uiCombobox* combo = uiNewCombobox(); - std::vector values; + std::vector values; for (const rapidjson::Value& i : j["items"].GetArray()) { const char* label = i["label"].GetString(); uiComboboxAppend(combo, label); @@ -416,7 +416,7 @@ ComboBox::ComboBox(uiBox* box, const rapidjson::Value& j) m_widget = combo; } -std::string ComboBox::GetRawString() { +tuwString ComboBox::GetRawString() { int sel = uiComboboxSelected(static_cast(m_widget)); return m_values[sel]; } @@ -441,7 +441,7 @@ void ComboBox::GetConfig(rapidjson::Document& config) { RadioButtons::RadioButtons(uiBox* box, const rapidjson::Value& j) : StringComponentBase(box, j) { uiRadioButtons* radio = uiNewRadioButtons(); - std::vector values; + std::vector values; for (const rapidjson::Value& i : j["items"].GetArray()) { const char* label = i["label"].GetString(); uiRadioButtonsAppend(radio, label); @@ -459,7 +459,7 @@ RadioButtons::RadioButtons(uiBox* box, const rapidjson::Value& j) m_widget = radio; } -std::string RadioButtons::GetRawString() { +tuwString RadioButtons::GetRawString() { int sel = uiRadioButtonsSelected(static_cast(m_widget)); return m_values[sel]; } @@ -495,7 +495,7 @@ CheckBox::CheckBox(uiBox* box, const rapidjson::Value& j) m_widget = check; } -std::string CheckBox::GetRawString() { +tuwString CheckBox::GetRawString() { if (uiCheckboxChecked(static_cast(m_widget))) return m_value; return ""; @@ -518,7 +518,7 @@ void CheckBox::GetConfig(rapidjson::Document& config) { CheckArray::CheckArray(uiBox* box, const rapidjson::Value& j) : StringComponentBase(box, j) { std::vector* checks = new std::vector(); - std::vector values; + std::vector values; uiBox* check_array_box = uiNewVerticalBox(); uiBoxSetSpacing(check_array_box, tuw_constants::BOX_CHECKS_SPACE); size_t id = 0; @@ -541,8 +541,8 @@ CheckArray::CheckArray(uiBox* box, const rapidjson::Value& j) m_widget = checks; } -std::string CheckArray::GetRawString() { - std::string str; +tuwString CheckArray::GetRawString() { + tuwString str; std::vector checks; checks = *(std::vector*)m_widget; for (size_t i = 0; i < checks.size(); i++) { @@ -592,9 +592,9 @@ TextBox::TextBox(uiBox* box, const rapidjson::Value& j) m_widget = entry; } -std::string TextBox::GetRawString() { +tuwString TextBox::GetRawString() { char* text = uiEntryText(static_cast(m_widget)); - std::string str = text; + tuwString str = text; uiFreeText(text); return str; } @@ -639,9 +639,9 @@ IntPicker::IntPicker(uiBox* box, const rapidjson::Value& j) m_widget = picker; } -std::string IntPicker::GetRawString() { +tuwString IntPicker::GetRawString() { char* text = uiSpinboxValueText(static_cast(m_widget)); - std::string str(text); + tuwString str(text); uiFreeText(text); return str; } @@ -685,9 +685,9 @@ FloatPicker::FloatPicker(uiBox* box, const rapidjson::Value& j) m_widget = picker; } -std::string FloatPicker::GetRawString() { +tuwString FloatPicker::GetRawString() { char* text = uiSpinboxValueText(static_cast(m_widget)); - std::string str(text); + tuwString str(text); uiFreeText(text); return str; } diff --git a/src/exe_container.cpp b/src/exe_container.cpp index e99da26a..e24409bc 100644 --- a/src/exe_container.cpp +++ b/src/exe_container.cpp @@ -28,14 +28,14 @@ static void WriteUint32(FILE* io, const uint32_t& num) { #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) #define BUF_SIZE 1024 -static std::string ReadStr(FILE* io, const uint32_t& size) { - std::string str(size, '\0'); - if (fread(&str[0], 1, size, io) != size) +static tuwString ReadStr(FILE* io, const uint32_t& size) { + tuwString str(size); + if (fread(str.data(), 1, size, io) != size) return ""; return str; } -static void WriteStr(FILE* io, const std::string& str) { +static void WriteStr(FILE* io, const tuwString& str) { fwrite(str.data(), 1, str.size(), io); // Zero padding @@ -74,7 +74,12 @@ static uint32_t Length(FILE* io) { static const uint32_t EXE_SIZE_MAX = 20000000; // Allowed size of exe -json_utils::JsonResult ExeContainer::Read(const std::string& exe_path) { +json_utils::JsonResult ExeContainer::Read(const tuwString& exe_path) { + if (GetStringError() != STR_OK) { + // Reject the operation as the exe_path might have an unexpected value. + return { false, "Fatal error has occurred while editing strings." }; + } + m_exe_path = exe_path; FILE* file_io = fopen(exe_path.c_str(), "rb"); if (!file_io) @@ -99,7 +104,7 @@ json_utils::JsonResult ExeContainer::Read(const std::string& exe_path) { m_exe_size = end_off + ReadUint32(file_io); if (EXE_SIZE_MAX <= m_exe_size || end_off < m_exe_size) { fclose(file_io); - return { false, ConcatCStrings("Unexpected exe size. (", m_exe_size, ")") }; + return { false, tuwString("Unexpected exe size. (") + m_exe_size + ")" }; } fseek(file_io, m_exe_size, SEEK_SET); @@ -107,57 +112,62 @@ json_utils::JsonResult ExeContainer::Read(const std::string& exe_path) { ReadMagic(file_io, magic); if (strcmp(magic, "JSON") != 0) { fclose(file_io); - return { false, ConcatCStrings("Invalid magic. (", magic, ")") }; + return { false, tuwString("Invalid magic. (") + magic + ")" }; } uint32_t json_size = ReadUint32(file_io); uint32_t stored_hash = ReadUint32(file_io); if (JSON_SIZE_MAX <= json_size || end_off < m_exe_size + json_size + 20) { fclose(file_io); - return { false, ConcatCStrings("Unexpected json size. (", json_size, ")") }; + return { false, tuwString("Unexpected json size. (") + json_size + ")" }; } // Read json data - std::string json_str = ReadStr(file_io, json_size); + tuwString json_str = ReadStr(file_io, json_size); fclose(file_io); if (json_str.length() != json_size) return { false, "Unexpected char detected." }; if (stored_hash != Fnv1Hash32(json_str)) - return { false, ConcatCStrings("Invalid JSON hash. (", stored_hash, ")") }; + return { false, tuwString("Invalid JSON hash. (") + stored_hash + ")" }; rapidjson::ParseResult ok = m_json.Parse(json_str.c_str()); if (!ok) { return { false, - ConcatCStrings("Failed to parse JSON: ", - rapidjson::GetParseError_En(ok.Code()), - ConcatCStrings(" (offset: ", ok.Offset(), ")").c_str()) + tuwString("Failed to parse JSON: ") + + rapidjson::GetParseError_En(ok.Code()) + + " (offset: " + ok.Offset() + ")" }; } return JSON_RESULT_OK; } -json_utils::JsonResult ExeContainer::Write(const std::string& exe_path) { +json_utils::JsonResult ExeContainer::Write(const tuwString& exe_path) { + if (GetStringError() != STR_OK) { + // Reject the operation as the exe_path might have an unexpected value. + return { false, "Fatal error has occurred while editing strings." }; + } + assert(!m_exe_path.empty()); - std::string json_str; + tuwString json_str; if (HasJson()) json_str = json_utils::JsonToString(m_json); uint32_t json_size = static_cast(json_str.length()); if (JSON_SIZE_MAX <= json_size) - return { false, ConcatCStrings("Unexpected json size. (", json_size, ")") }; + return { false, tuwString("Unexpected json size. (") + json_size + ")" }; FILE* old_io = fopen(m_exe_path.c_str(), "rb"); if (!old_io) - return { false, ConcatCStrings("Failed to open a file. (", m_exe_path.c_str(), ")") }; + return { false, "Failed to open a file. (" + m_exe_path + ")" }; FILE* new_io = fopen(exe_path.c_str(), "wb"); if (!new_io) { fclose(old_io); - return { false, ConcatCStrings("Failed to open a file. (", exe_path.c_str(), ")") }; + return { false, "Failed to open a file. (" + exe_path + ")" }; } m_exe_path = exe_path; @@ -165,8 +175,8 @@ json_utils::JsonResult ExeContainer::Write(const std::string& exe_path) { if (!ok) { fclose(old_io); fclose(new_io); - return { false, ConcatCStrings("Failed to copy the original executable (", - m_exe_path.c_str(), ")") }; + return { false, "Failed to copy the original executable (" + + m_exe_path + ")" }; } uint32_t pos = ftell(old_io); @@ -176,7 +186,7 @@ json_utils::JsonResult ExeContainer::Write(const std::string& exe_path) { if (strcmp(magic, "JSON") != 0) { fclose(old_io); fclose(new_io); - return { false, ConcatCStrings("Invalid magic. (", magic, ")") }; + return { false, tuwString("Invalid magic. (") + magic + ")" }; } } diff --git a/src/exec.cpp b/src/exec.cpp index 79dcf72c..246db7bc 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -10,7 +10,7 @@ inline bool IsNewline(char ch) { return ch == '\n' || ch == '\r'; } -static std::string GetLastLine(const std::string& input) { +static tuwString GetLastLine(const tuwString& input) { if (input.empty()) return ""; size_t end = input.length(); @@ -31,7 +31,7 @@ enum READ_IO_TYPE : int { unsigned ReadIO(subprocess_s &process, int read_io_type, char *buf, const unsigned buf_size, - std::string& str, const unsigned str_size) { + tuwString& str, const unsigned str_size) { unsigned read_size = 0; if (read_io_type == READ_STDOUT) { read_size = subprocess_read_stdout(&process, buf, buf_size); @@ -48,16 +48,23 @@ unsigned ReadIO(subprocess_s &process, return read_size; } -void DestroyProcess(subprocess_s &process, int *return_code, std::string &err_msg) { +void DestroyProcess(subprocess_s &process, int *return_code, tuwString &err_msg) { if (subprocess_join(&process, return_code) || subprocess_destroy(&process)) { *return_code = -1; err_msg = "Failed to manage subprocess.\n"; } } -ExecuteResult Execute(const std::string& cmd, bool use_utf8_on_windows) { +ExecuteResult Execute(const tuwString& cmd, bool use_utf8_on_windows) { #ifdef _WIN32 - std::wstring wcmd = UTF8toUTF16(cmd.c_str()); + tuwWstring wcmd = UTF8toUTF16(cmd.c_str()); + + if (GetStringError() != STR_OK) { + // Reject the command as it might have unexpected value. + return { -1, + "Fatal error has occored while editing strings.\n", + "" }; + } int argc; wchar_t** parsed = CommandLineToArgvW(wcmd.c_str(), &argc); @@ -91,8 +98,8 @@ ExecuteResult Execute(const std::string& cmd, bool use_utf8_on_windows) { const unsigned BUF_SIZE = 1024; char out_buf[BUF_SIZE + 1]; char err_buf[BUF_SIZE + 1]; - std::string last_line; - std::string err_msg; + tuwString last_line; + tuwString err_msg; unsigned out_read_size = 0; unsigned err_read_size = 0; @@ -102,7 +109,7 @@ ExecuteResult Execute(const std::string& cmd, bool use_utf8_on_windows) { if (out_read_size) { #ifdef _WIN32 if (use_utf8_on_windows) { - std::wstring wout = UTF8toUTF16(out_buf); + tuwWstring wout = UTF8toUTF16(out_buf); printf("%ls", wout.c_str()); } else { printf("%s", out_buf); @@ -113,7 +120,8 @@ ExecuteResult Execute(const std::string& cmd, bool use_utf8_on_windows) { } } while (subprocess_alive(&process) || out_read_size || err_read_size); - // Sometimes stderr still have unread characters + // Sometimes stdout and stderr still have unread characters + ReadIO(process, READ_STDOUT, out_buf, BUF_SIZE, last_line, BUF_SIZE); ReadIO(process, READ_STDERR, err_buf, BUF_SIZE, err_msg, BUF_SIZE * 2); int return_code; @@ -124,9 +132,9 @@ ExecuteResult Execute(const std::string& cmd, bool use_utf8_on_windows) { return { return_code, err_msg, last_line }; } -ExecuteResult LaunchDefaultApp(const std::string& url) { +ExecuteResult LaunchDefaultApp(const tuwString& url) { #ifdef _WIN32 - std::wstring utf16_url = UTF8toUTF16(url.c_str()); + tuwWstring utf16_url = UTF8toUTF16(url.c_str()); const wchar_t* argv[] = {L"cmd.exe", L"/c", L"start", utf16_url.c_str(), NULL}; #elif defined(__TUW_UNIX__) && !defined(__HAIKU__) // Linux and BSD @@ -143,7 +151,7 @@ ExecuteResult LaunchDefaultApp(const std::string& url) { return { -1, "Failed to create a subprocess.\n", ""}; int return_code; - std::string err_msg; + tuwString err_msg; DestroyProcess(process, &return_code, err_msg); return { return_code, err_msg, "" }; diff --git a/src/json_utils.cpp b/src/json_utils.cpp index 466c6bd8..e81242a7 100644 --- a/src/json_utils.cpp +++ b/src/json_utils.cpp @@ -34,7 +34,7 @@ namespace json_utils { constexpr auto JSONC_FLAGS = rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag; - JsonResult LoadJson(const std::string& file, rapidjson::Document& json) { + JsonResult LoadJson(const tuwString& file, rapidjson::Document& json) { FILE* fp = fopen(file.c_str(), "rb"); if (!fp) return { false, "Failed to open " + file }; @@ -46,9 +46,9 @@ namespace json_utils { fclose(fp); if (!ok) { - std::string msg("Failed to parse JSON: "); - msg += rapidjson::GetParseError_En(ok.Code()) + - ConcatCStrings(" (offset: ", ok.Offset(), ")"); + tuwString msg = tuwString("Failed to parse JSON: ") + + rapidjson::GetParseError_En(ok.Code()) + + " (offset: " + ok.Offset() + ")"; return { false, msg }; } if (!json.IsObject()) @@ -57,10 +57,10 @@ namespace json_utils { return JSON_RESULT_OK; } - JsonResult SaveJson(rapidjson::Document& json, const std::string& file) { + JsonResult SaveJson(rapidjson::Document& json, const tuwString& file) { FILE* fp = fopen(file.c_str(), "wb"); if (!fp) - return { false, ConcatCStrings("Failed to open ", file.c_str(), ".") }; + return { false, "Failed to open " + file + "." }; char writeBuffer[JSON_SIZE_MAX]; rapidjson::FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer)); @@ -70,7 +70,7 @@ namespace json_utils { return JSON_RESULT_OK; } - std::string JsonToString(rapidjson::Document& json) { + tuwString JsonToString(rapidjson::Document& json) { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); json.Accept(writer); @@ -101,12 +101,12 @@ namespace json_utils { return def; } - static std::string GetLabel(const char* label, const char* key) { - std::string msg; + static tuwString GetLabel(const char* label, const char* key) { + tuwString msg; if (*label != '\0') { - msg = ConcatCStrings("['", label, "']"); + msg = tuwString("['") + label + "']"; } - msg += ConcatCStrings("['", key, "']"); + msg += tuwString("['") + key + "']"; return msg; } @@ -159,7 +159,7 @@ namespace json_utils { } if (!valid) { result.ok = false; - result.msg = GetLabel(label, key) + ConcatCStrings(" should be ", type_name, "."); + result.msg = GetLabel(label, key) + " should be " + type_name + "."; } } @@ -223,7 +223,7 @@ namespace json_utils { } if (!valid) { result.ok = false; - result.msg = GetLabel(label, key) + ConcatCStrings(" should be ", type_name, "."); + result.msg = GetLabel(label, key) + " should be " + type_name + "."; } } @@ -257,12 +257,12 @@ namespace json_utils { } } - static std::vector SplitString(const char* str, + static std::vector SplitString(const char* str, const char delimiter) { if (!str) return {}; - std::vector tokens; + std::vector tokens; const char* start = str; while (*start != '\0') { @@ -278,19 +278,19 @@ namespace json_utils { } static void CheckIndexDuplication(JsonResult& result, - const std::vector& component_ids) { + const std::vector& component_ids) { size_t size = component_ids.size(); if (size == 0) return; for (size_t i = 0; i < size - 1; i++) { - const std::string& str = component_ids[i]; + const tuwString& str = component_ids[i]; if (str.empty()) { continue; } for (size_t j = i + 1; j < size; j++) { if (str == component_ids[j]) { result.ok = false; - result.msg = ConcatCStrings("[components][id]" - " should not be duplicated in a gui definition. (", - str.c_str(), ")"); + result.msg = "[components][id]" + " should not be duplicated in a gui definition. (" + + str + ")"; return; } } @@ -300,17 +300,17 @@ namespace json_utils { // split command by "%" symbol, and calculate which component should be inserted there. static void CompileCommand(JsonResult& result, rapidjson::Value& sub_definition, - const std::vector& comp_ids, + const std::vector& comp_ids, rapidjson::Document::AllocatorType& alloc) { - std::vector cmd = SplitString(sub_definition["command"].GetString(), '%'); - std::vector cmd_ids = std::vector(0); - std::vector splitted_cmd = std::vector(0); + std::vector cmd = SplitString(sub_definition["command"].GetString(), '%'); + std::vector cmd_ids = std::vector(0); + std::vector splitted_cmd = std::vector(0); if (sub_definition.HasMember("command_splitted")) sub_definition.RemoveMember("command_splitted"); rapidjson::Value splitted_cmd_json(rapidjson::kArrayType); bool store_ids = false; - for (const std::string& token : cmd) { + for (const tuwString& token : cmd) { if (store_ids) { cmd_ids.emplace_back(token); } else { @@ -324,12 +324,12 @@ namespace json_utils { rapidjson::Value& components = sub_definition["components"];; rapidjson::Value cmd_int_ids(rapidjson::kArrayType); - std::string cmd_str; + tuwString cmd_str; int comp_size = static_cast(comp_ids.size()); int non_id_comp = 0; for (int i = 0; i < static_cast(cmd_ids.size()); i++) { cmd_str += splitted_cmd[i]; - const std::string& id = cmd_ids[i]; + const tuwString& id = cmd_ids[i]; int j; if (id == CMD_TOKEN_PERCENT) { j = CMD_ID_PERCENT; @@ -359,7 +359,7 @@ namespace json_utils { if (j >= comp_size) cmd_str += "__comp???__"; else if (j >= 0) - cmd_str += ConcatCStrings("__comp", j, "__"); + cmd_str += tuwString("__comp") + j + "__"; } if (cmd_ids.size() < splitted_cmd.size()) cmd_str += splitted_cmd.back(); @@ -374,8 +374,8 @@ namespace json_utils { if (id.GetInt() == j) { found = true; break; } if (!found) { result.ok = false; - result.msg = ConcatCStrings("[\"components\"][", j, - "] is unused in the command; ") + cmd_str; + result.msg = tuwString("[\"components\"][") + j + + "] is unused in the command; " + cmd_str; if (!comp_ids[j].empty()) result.msg = "The ID of " + result.msg; return; @@ -452,7 +452,7 @@ namespace json_utils { CheckJsonType(result, sub_definition, "window_name", JsonType::STRING, "", OPTIONAL); if (!sub_definition.HasMember("label")) { - std::string default_label = ConcatCStrings("GUI ", index); + tuwString default_label = tuwString("GUI ") + index; const char* label = GetString(sub_definition, "window_name", default_label.c_str()); rapidjson::Value n(label, alloc); sub_definition.AddMember("label", n, alloc); @@ -472,7 +472,7 @@ namespace json_utils { if (strcmp(codepage, "utf8") != 0 && strcmp(codepage, "utf-8") != 0 && strcmp(codepage, "default") != 0) { result.ok = false; - result.msg = ConcatCStrings("Unknown codepage: ", codepage); + result.msg = tuwString("Unknown codepage: ") + codepage; return; } } @@ -484,7 +484,7 @@ namespace json_utils { if (!result.ok) return; // check components - std::vector comp_ids; + std::vector comp_ids; for (rapidjson::Value& c : sub_definition["components"].GetArray()) { // check if type and label exist CheckJsonType(result, c, "label", JsonType::STRING, "components"); @@ -558,7 +558,7 @@ namespace json_utils { break; case COMP_UNKNOWN: result.ok = false; - result.msg = ConcatCStrings("Unknown component type: ", type_str); + result.msg = tuwString("Unknown component type: ") + type_str; break; } if (!result.ok) return; @@ -644,14 +644,14 @@ namespace json_utils { // vX.Y.Z -> 10000*X + 100 * Y + Z static int VersionStringToInt(JsonResult& result, const char* string) { - std::vector version_strings = + std::vector version_strings = SplitString(string, '.'); int digit = 10000; int version_int = 0; - for (const std::string& str : version_strings) { + for (const tuwString& str : version_strings) { if (str.length() == 0 || str.length() > 2) { result.ok = false; - result.msg = ConcatCStrings("Can NOT convert '", string, "' to int."); + result.msg = tuwString("Can NOT convert '") + string + "' to int."; return 0; } if (str.length() == 1) { @@ -686,7 +686,7 @@ namespace json_utils { int required_int = VersionStringToInt(result, required); if (tuw_constants::VERSION_INT < required_int) { result.ok = false; - result.msg = ConcatCStrings("Version ", required, " is required."); + result.msg = tuwString("Version ") + required + " is required."; } } } @@ -729,7 +729,7 @@ namespace json_utils { CheckJsonType(result, h, "path", JsonType::STRING); } else { result.ok = false; - result.msg = ConcatCStrings("Unsupported help type: ", type); + result.msg = tuwString("Unsupported help type: ") + type; return; } } diff --git a/src/main.cpp b/src/main.cpp index 2af174cb..ddc42a3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,4 @@ #include -#include #ifdef _WIN32 #include #else @@ -49,8 +48,8 @@ bool AskOverwrite(const char *path) { return ret == 1 && (answer == "y"[0] || answer == "Y"[0]); } -json_utils::JsonResult Merge(const std::string& exe_path, const std::string& json_path, - const std::string& new_path, const bool force) { +json_utils::JsonResult Merge(const tuwString& exe_path, const tuwString& json_path, + const tuwString& new_path, const bool force) { rapidjson::Document json; json_utils::JsonResult result = json_utils::LoadJson(json_path, json); if (!result.ok) return result; @@ -82,8 +81,8 @@ json_utils::JsonResult Merge(const std::string& exe_path, const std::string& jso return JSON_RESULT_OK; } -json_utils::JsonResult Split(const std::string& exe_path, const std::string& json_path, - const std::string& new_path, const bool force) { +json_utils::JsonResult Split(const tuwString& exe_path, const tuwString& json_path, + const tuwString& new_path, const bool force) { ExeContainer exe; json_utils::JsonResult result = exe.Read(exe_path); if (!result.ok) return result; @@ -207,7 +206,7 @@ int wmain(int argc, wchar_t* argv[]) { #else int main(int argc, char* argv[]) { #endif - std::vector args; + std::vector args; for (int i = 0; i < argc; i++) { #ifdef _WIN32 args.emplace_back(UTF16toUTF8(argv[i])); @@ -218,7 +217,7 @@ int main(int argc, char* argv[]) { char *exe_path_cstr = envuGetExecutablePath(); char *exe_dir = envuGetDirectory(exe_path_cstr); envuSetCwd(exe_dir); - std::string exe_path = envuStr(exe_path_cstr); + tuwString exe_path = envuStr(exe_path_cstr); envuFree(exe_dir); // Launch GUI if no args. @@ -232,8 +231,8 @@ int main(int argc, char* argv[]) { return 1; } - std::string json_path; - std::string new_exe_path; + tuwString json_path; + tuwString new_exe_path; bool force = false; for (int i = 2; i < argc; i++) { diff --git a/src/main_frame.cpp b/src/main_frame.cpp index 59c73c1d..01d40e67 100644 --- a/src/main_frame.cpp +++ b/src/main_frame.cpp @@ -17,7 +17,7 @@ MainFrame::MainFrame(const rapidjson::Document& definition, const rapidjson::Doc m_grid = NULL; m_menu_item = NULL; - std::string exe_path = envuStr(envuGetExecutablePath()); + tuwString exe_path = envuStr(envuGetExecutablePath()); m_definition.CopyFrom(definition, m_definition.GetAllocator()); m_config.CopyFrom(config, m_config.GetAllocator()); @@ -88,8 +88,8 @@ MainFrame::MainFrame(const rapidjson::Document& definition, const rapidjson::Doc #endif if (ignore_external_json) { - std::string msg = ConcatCStrings("WARNING: Using embedded JSON. ", - json_path, " was ignored.\n"); + tuwString msg = tuwString("WARNING: Using embedded JSON. ") + + json_path + " was ignored.\n"; PrintFmt("[LoadDefinition] %s", msg.c_str()); ShowSuccessDialog(msg, "Warning"); } @@ -216,9 +216,9 @@ void MainFrame::CreateMenu() { m_menu_item = uiMenuAppendCheckItem(menu, "Safe Mode"); } -static bool IsValidURL(const std::string &url) { +static bool IsValidURL(const tuwString &url) { for (const char c : { ' ', ';', '|', '&', '\r', '\n' }) { - if (url.find(c) != std::string::npos) + if (url.find(c) != tuwString::npos) return false; } return true; @@ -227,7 +227,7 @@ static bool IsValidURL(const std::string &url) { void MainFrame::OpenURL(int id) { rapidjson::Value& help = m_definition["help"].GetArray()[id]; const char* type = help["type"].GetString(); - std::string url; + tuwString url; const char* tag = ""; if (strcmp(type, "url") == 0) { @@ -235,19 +235,18 @@ void MainFrame::OpenURL(int id) { tag = "[OpenURL] "; size_t pos = url.find("://"); - if (pos != std::string::npos) { - std::string scheme = url.substr(0, pos); + if (pos != tuwString::npos) { + tuwString scheme = url.substr(0, pos); // scheme should be http or https if (scheme == "file") { - std::string msg = ConcatCStrings("Use 'file' type for a path, not 'url' type. (", - url.c_str(), ")"); + tuwString msg = "Use 'file' type for a path, not 'url' type. (" + + url + ")"; PrintFmt("%sError: %s\n", tag, msg.c_str()); ShowErrorDialog(msg); return; } else if (scheme != "https" && scheme != "http") { - std::string msg = ConcatCStrings( - "Unsupported scheme detected. " - "It should be http or https. (", scheme.c_str(), ")"); + tuwString msg = "Unsupported scheme detected. " + "It should be http or https. (" + scheme + ")"; PrintFmt("%sError: %s\n", tag, msg.c_str()); ShowErrorDialog(msg); return; @@ -263,7 +262,7 @@ void MainFrame::OpenURL(int id) { tag = "[OpenFile] "; if (!exists) { - std::string msg = ConcatCStrings("File does not exist. (", url.c_str(), ")"); + tuwString msg = "File does not exist. (" + url + ")"; PrintFmt("%sError: %s\n", tag, msg.c_str()); ShowErrorDialog(msg); return; @@ -277,7 +276,7 @@ void MainFrame::OpenURL(int id) { } if (!IsValidURL(url)) { - std::string msg = "URL should NOT contains ' ', ';', '|', '&', '\\r', nor '\\n'.\n" + tuwString msg = "URL should NOT contains ' ', ';', '|', '&', '\\r', nor '\\n'.\n" "URL: " + url; PrintFmt("%sError: %s\n", tag, msg.c_str()); ShowErrorDialog(msg.c_str()); @@ -285,17 +284,26 @@ void MainFrame::OpenURL(int id) { } if (IsSafeMode()) { - std::string msg = "The URL was not opened because the safe mode is enabled.\n" + tuwString msg = "The URL was not opened because the safe mode is enabled.\n" "You can disable it from the menu bar (Debug > Safe Mode.)\n" "\n" "URL: " + url; ShowSuccessDialog(msg, "Safe Mode"); } else { - ExecuteResult result = LaunchDefaultApp(url); - if (result.exit_code != 0) { - std::string msg = ConcatCStrings("Failed to open a ", type, " by an unexpected error."); - PrintFmt("%sError: %s\n", tag, msg.c_str()); - ShowErrorDialog(msg.c_str()); + if (GetStringError() != STR_OK) { + // Reject the URL as it might have an unexpected value. + const char* msg = "The URL was not opened " + "because a fatal error has occurred while editing strings. " + "Please reboot the application."; + PrintFmt("%sError: %s\n", tag, msg); + ShowErrorDialog(msg); + } else { + ExecuteResult result = LaunchDefaultApp(url); + if (result.exit_code != 0) { + tuwString msg = tuwString("Failed to open a ") + type + " by an unexpected error."; + PrintFmt("%sError: %s\n", tag, msg.c_str()); + ShowErrorDialog(msg.c_str()); + } } } } @@ -389,10 +397,10 @@ void MainFrame::Fit(bool keep_width) { bool MainFrame::Validate() { bool validate = true; bool redraw_flag = false; - std::string val_first_err; + tuwString val_first_err; for (Component* comp : m_components) { if (!comp->Validate(&redraw_flag)) { - const std::string& val_err = comp->GetValidationError(); + const tuwString& val_err = comp->GetValidationError(); if (validate) val_first_err = val_err; validate = false; @@ -414,8 +422,8 @@ bool MainFrame::Validate() { } // Make command string -std::string MainFrame::GetCommand() { - std::vector cmd_ary; +tuwString MainFrame::GetCommand() { + std::vector cmd_ary; rapidjson::Value& sub_definition = m_definition["gui"][m_definition_id]; for (rapidjson::Value& c : sub_definition["command_splitted"].GetArray()) cmd_ary.emplace_back(c.GetString()); @@ -423,12 +431,12 @@ std::string MainFrame::GetCommand() { for (rapidjson::Value& c : sub_definition["command_ids"].GetArray()) cmd_ids.emplace_back(c.GetInt()); - std::vector comp_strings = std::vector(m_components.size()); + std::vector comp_strings = std::vector(m_components.size()); for (size_t i = 0; i < m_components.size(); i++) { comp_strings[i] = m_components[i]->GetString(); } - std::string cmd = cmd_ary[0]; + tuwString cmd = cmd_ary[0]; for (size_t i = 0; i < cmd_ids.size(); i++) { int id = cmd_ids[i]; if (id == CMD_ID_PERCENT) { @@ -448,11 +456,11 @@ std::string MainFrame::GetCommand() { } void MainFrame::RunCommand() { - std::string cmd = GetCommand(); + tuwString cmd = GetCommand(); PrintFmt("[RunCommand] Command: %s\n", cmd.c_str()); if (IsSafeMode()) { - std::string msg = "The command was not executed because the safe mode is enabled.\n" + tuwString msg = "The command was not executed because the safe mode is enabled.\n" "You can disable it from the menu bar (Debug > Safe Mode.)\n" "\n" "Command: " + cmd; @@ -480,6 +488,14 @@ void MainFrame::RunCommand() { bool show_last_line = json_utils::GetBool(sub_definition, "show_last_line", false); bool show_success_dialog = json_utils::GetBool(sub_definition, "show_success_dialog", true); + if (GetStringError() != STR_OK) { + const char* msg = "Fatal error has occurred while editing strings. " + "Please reboot the application."; + PrintFmt("[RunCommand] Error: %s\n", msg); + ShowErrorDialog(msg); + return; + } + if (!result.err_msg.empty()) { PrintFmt("[RunCommand] Error: %s\n", result.err_msg.c_str()); ShowErrorDialog(result.err_msg); @@ -487,11 +503,11 @@ void MainFrame::RunCommand() { } if (check_exit_code && result.exit_code != exit_success) { - std::string err_msg; + tuwString err_msg; if (show_last_line) err_msg = result.last_line; else - err_msg = ConcatCStrings("Invalid exit code (", result.exit_code, ")"); + err_msg = tuwString("Invalid exit code (") + result.exit_code + ")"; PrintFmt("[RunCommand] Error: %s\n", err_msg.c_str()); ShowErrorDialog(err_msg); return; @@ -530,7 +546,7 @@ json_utils::JsonResult MainFrame::CheckDefinition(rapidjson::Document& definitio return result; } -void MainFrame::JsonLoadFailed(const std::string& msg) { +void MainFrame::JsonLoadFailed(const tuwString& msg) { PrintFmt("[LoadDefinition] Error: %s\n", msg.c_str()); ShowErrorDialog(msg); } @@ -555,14 +571,14 @@ void MainFrame::SaveConfig() { bool g_no_dialog = false; -void MainFrame::ShowSuccessDialog(const std::string& msg, const std::string& title) { +void MainFrame::ShowSuccessDialog(const char* msg, const char* title) { if (g_no_dialog) return; - uiMsgBox(m_mainwin, title.c_str(), msg.c_str()); + uiMsgBox(m_mainwin, title, msg); } -void MainFrame::ShowErrorDialog(const std::string& msg, const std::string& title) { +void MainFrame::ShowErrorDialog(const char* msg, const char* title) { if (g_no_dialog) return; - uiMsgBoxError(m_mainwin, title.c_str(), msg.c_str()); + uiMsgBoxError(m_mainwin, title, msg); } void MainFrameDisableDialog() { diff --git a/src/string_utils.cpp b/src/string_utils.cpp index 279fbd70..b69aa409 100644 --- a/src/string_utils.cpp +++ b/src/string_utils.cpp @@ -1,66 +1,263 @@ #include "string_utils.h" -#include "inttypes.h" +#include +#include +#include +#include #ifdef _WIN32 #include "windows/uipriv_windows.hpp" -#else -#include #endif -static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U; -static const uint32_t FNV_PRIME_32 = 16777619U; +StringError g_error_status = STR_OK; +StringError GetStringError() { return g_error_status; } +void ClearStringError() { g_error_status = STR_OK; } +inline static void SetStringError(StringError err) { g_error_status = err; } -uint32_t Fnv1Hash32(const std::string& str) { - uint32_t hash = FNV_OFFSET_BASIS_32; - for (const char& c : str) hash = (FNV_PRIME_32 * hash) ^ c; - return hash; +void tuwString::alloc_cstr(const char *str, size_t size) { + clear(); + if (!str || size == 0) + return; + + m_size = size; + m_str = reinterpret_cast(malloc(size + 1)); + if (!m_str) { + m_size = 0; + SetStringError(STR_ALLOCATION_ERROR); + return; + } + memcpy(m_str, str, size); + m_str[size] = '\0'; +} + +#define get_strlen(str) (str) ? strlen(str) : 0 + +tuwString::tuwString(const char* str) : + m_str(nullptr), m_size(0) { + alloc_cstr(str, get_strlen(str)); +} + +tuwString::tuwString(const char* str, size_t size) : + m_str(nullptr), m_size(0) { + alloc_cstr(str, size); +} + +tuwString::tuwString(const tuwString& str) : + m_str(nullptr), m_size(0) { + alloc_cstr(str.c_str(), str.size()); +} + +tuwString::tuwString(tuwString&& str) : + m_str(str.m_str), m_size(str.m_size) { + str.m_str = nullptr; + str.m_size = 0; +} + +tuwString::tuwString(size_t size) : m_size(size) { + m_str = reinterpret_cast(calloc(size + 1, sizeof(char))); + if (!m_str) { + m_size = 0; + SetStringError(STR_ALLOCATION_ERROR); + } +} + +tuwString& tuwString::operator=(const char* str) { + alloc_cstr(str, get_strlen(str)); + return *this; +} + +tuwString& tuwString::operator=(const tuwString& str) { + if (this == &str) return *this; + alloc_cstr(str.c_str(), str.size()); + return *this; +} + +tuwString& tuwString::operator=(tuwString&& str) { + if (this == &str) return *this; + clear(); + m_str = str.m_str; + m_size = str.m_size; + str.m_str = nullptr; + str.m_size = 0; + return *this; +} + +void tuwString::append(const char* str, size_t size) { + if (!str || size == 0) + return; + + size_t new_size = m_size + size; + char* new_cstr = reinterpret_cast(malloc(new_size + 1)); + if (!new_cstr) { + SetStringError(STR_ALLOCATION_ERROR); + return; + } + + if (!empty()) + memcpy(new_cstr, m_str, m_size); + memcpy(new_cstr + m_size, str, size); + new_cstr[new_size] = '\0'; + + clear(); + m_str = new_cstr; + m_size = new_size; +} + +tuwString& tuwString::operator+=(const char* str) { + append(str, get_strlen(str)); + return *this; } -template<> -std::string ConcatCStrings(const char* str1, const char* str2, const char* str3) { - size_t len1 = strlen(str1), len2 = strlen(str2), len3 = str3 ? strlen(str3) : 0; - char* buffer = new char[len1 + len2 + len3 + 1]; - memcpy(buffer, str1, len1); - memcpy(buffer + len1, str2, len2); - if (str3) memcpy(buffer + len1 + len2, str3, len3); - buffer[len1 + len2 + len3] = '\0'; - std::string result(buffer); - delete[] buffer; - return result; +tuwString& tuwString::operator+=(const tuwString& str) { + append(str.c_str(), str.size()); + return *this; } -template<> -std::string ConcatCStrings(const char* str1, char* str2, const char* str3) { - return ConcatCStrings(str1, str2, str3); +tuwString tuwString::operator+(const char* str) const { + tuwString new_str(*this); + new_str += str; + return new_str; } -#define DEFINE_CONCAT_CSTR_NUM(num_type, fmt) \ -template<> \ -std::string ConcatCStrings(const char* str1, num_type num, const char* str2) { \ - int num_len = snprintf(nullptr, 0, "%" fmt, num); \ - char* num_str = new char[num_len + 1]; \ - snprintf(num_str, num_len + 1, "%" fmt, num); \ - std::string ret = ConcatCStrings(str1, num_str, str2); \ - delete[] num_str; \ - return ret; \ +tuwString tuwString::operator+(const tuwString& str) const { + tuwString new_str(*this); + new_str += str; + return new_str; } -DEFINE_CONCAT_CSTR_NUM(int, "d") -DEFINE_CONCAT_CSTR_NUM(size_t, "zu") -DEFINE_CONCAT_CSTR_NUM(uint32_t, PRIu32) +// Convert number to c string, and append it to string. +# define DEFINE_PLUS_FOR_NUM(num_type, fmt) \ +tuwString tuwString::operator+(num_type num) const { \ + tuwString new_str(*this); \ + \ + int num_size = snprintf(nullptr, 0, "%" fmt, num); \ + if (num_size <= 0) { \ + SetStringError(STR_FORMAT_ERROR); \ + return new_str; \ + } \ + \ + char* num_str = reinterpret_cast(malloc(num_size + 1)); \ + if (!num_str) { \ + SetStringError(STR_ALLOCATION_ERROR); \ + return new_str; \ + } \ + \ + int ret = snprintf(num_str, num_size + 1, "%" fmt, num); \ + if (ret == num_size) { \ + new_str.append(num_str, num_size); \ + } else { \ + SetStringError(STR_FORMAT_ERROR); \ + } \ + free(num_str); \ + return new_str; \ +} + +DEFINE_PLUS_FOR_NUM(int, "d") +DEFINE_PLUS_FOR_NUM(size_t, "zu") +DEFINE_PLUS_FOR_NUM(uint32_t, PRIu32) + +#define null_to_empty(str) (str) ? str : "" + +bool tuwString::operator==(const char* str) const { + return strcmp(c_str(), null_to_empty(str)) == 0; +} + +bool tuwString::operator==(const tuwString& str) const { + return strcmp(c_str(), str.c_str()) == 0; +} + +const size_t tuwString::npos = static_cast(-1); + +size_t tuwString::find(const char c) const { + if (empty()) return npos; + const char* p = begin(); + for (; p < end(); p++) { + if (*p == c) + return static_cast(p - m_str); + } + return npos; +} + +size_t tuwString::find(const char* str) const { + if (empty() || !str) return npos; + + // Note: This function uses a slow algorithm. + const char* p = begin(); + for (; p < end(); p++) { + const char* tmp_p = p; + const char* str_p = str; + while (tmp_p < end() && *str_p != '\0' && *tmp_p == *str_p) { + tmp_p++; + str_p++; + } + if (*str_p == '\0') + return static_cast(p - m_str); + } + return npos; +} + +tuwString tuwString::substr(size_t start, size_t size) const { + if (start + size > m_size) { + SetStringError(STR_BOUNDARY_ERROR); + return tuwString(); + } + tuwString new_str(m_str + start, size); + return new_str; +} + +const char& tuwString::operator[](size_t id) const { + if (id > m_size) { + SetStringError(STR_BOUNDARY_ERROR); + return ""[0]; + } + return c_str()[id]; +} + +tuwString operator+(const char* str1, const tuwString& str2) { + tuwString new_str(str1); + new_str += str2; + return new_str; +} + +tuwWstring::tuwWstring(const wchar_t* str) : + m_str(nullptr), m_size(0) { + if (!str) return; + + size_t size = wcslen(str); + if (size == 0) + return; + + m_str = reinterpret_cast(malloc((size + 1) * sizeof(wchar_t))); + if (!m_str) { + SetStringError(STR_ALLOCATION_ERROR); + return; + } + + m_size = size; + memcpy(m_str, str, size * sizeof(wchar_t)); + m_str[size] = L'\0'; +} + +static const uint32_t FNV_OFFSET_BASIS_32 = 2166136261U; +static const uint32_t FNV_PRIME_32 = 16777619U; + +uint32_t Fnv1Hash32(const tuwString& str) { + uint32_t hash = FNV_OFFSET_BASIS_32; + for (const char c : str) hash = (FNV_PRIME_32 * hash) ^ c; + return hash; +} #ifdef _WIN32 -std::string UTF16toUTF8(const wchar_t* str) { +tuwString UTF16toUTF8(const wchar_t* str) { char* uchar = toUTF8(str); - std::string ustr = uchar; + tuwString ustr = uchar; uiprivFree(uchar); return ustr; } -std::wstring UTF8toUTF16(const char* str) { +tuwWstring UTF8toUTF16(const char* str) { wchar_t* widechar = toUTF16(str); - std::wstring wstr = widechar; + tuwWstring wstr = widechar; uiprivFree(widechar); return wstr; } @@ -382,7 +579,7 @@ void ConvertAnsiToPango(TagStack* stack, const char *input, char *output) { class Logger { private: uiMultilineEntry* m_log_entry; - std::string m_log_buffer; + tuwString m_log_buffer; TagStack m_tag_stack; // stack for markup tags int m_buffer_length; diff --git a/src/validator.cpp b/src/validator.cpp index 1bc3d697..2258196f 100644 --- a/src/validator.cpp +++ b/src/validator.cpp @@ -2,6 +2,7 @@ #include "validator.h" #include "json_utils.h" #include "env_utils.h" +#include "string_utils.h" void Validator::Initialize(const rapidjson::Value& j) { m_regex = json_utils::GetString(j, "regex", ""); @@ -32,7 +33,7 @@ static int IsUnsupportedPattern(const char *pattern) { return 0; } -bool Validator::Validate(const std::string& str) { +bool Validator::Validate(const tuwString& str) { if (m_not_empty && str.empty()) { if (m_not_empty_error.empty()) m_error_msg = "Empty string is NOT allowed."; diff --git a/tests/exe_container_test.cpp b/tests/exe_container_test.cpp index a8924f43..f5e415e9 100644 --- a/tests/exe_container_test.cpp +++ b/tests/exe_container_test.cpp @@ -1,4 +1,3 @@ -#pragma once // Tests for json embedding // Todo: Write more tests @@ -12,9 +11,11 @@ TEST(JsonEmbeddingTest, Embed) { ExeContainer exe; json_utils::JsonResult result = exe.Read(JSON_ALL_KEYS); EXPECT_TRUE(result.ok); + EXPECT_STREQ("", result.msg.c_str()); exe.SetJson(test_json); result = exe.Write("embedded.json"); EXPECT_TRUE(result.ok); + EXPECT_STREQ("", result.msg.c_str()); } { rapidjson::Document test_json; @@ -25,6 +26,7 @@ TEST(JsonEmbeddingTest, Embed) { ExeContainer exe; json_utils::JsonResult result = exe.Read("embedded.json"); EXPECT_TRUE(result.ok); + EXPECT_STREQ("", result.msg.c_str()); exe.GetJson(embedded_json); EXPECT_EQ(embedded_json, test_json); } diff --git a/tests/json_check_test.cpp b/tests/json_check_test.cpp index 9dd62c2f..70f442fe 100644 --- a/tests/json_check_test.cpp +++ b/tests/json_check_test.cpp @@ -1,5 +1,3 @@ -#pragma once - // Tests for json checking // Todo: Write more tests diff --git a/tests/main_frame_test.cpp b/tests/main_frame_test.cpp index d0dc26fa..291bbefe 100644 --- a/tests/main_frame_test.cpp +++ b/tests/main_frame_test.cpp @@ -1,5 +1,3 @@ -#pragma once - // Tests for main_frame.cpp // Todo: Write more tests @@ -17,6 +15,7 @@ class MainFrameTest : public ::testing::Test { if (msg != NULL) printf("%s\n", msg); EXPECT_TRUE(msg == NULL); + EXPECT_EQ(STR_OK, GetStringError()); uiMainSteps(); } @@ -27,9 +26,10 @@ class MainFrameTest : public ::testing::Test { // Need to reset the pointer to the log. SetLogEntry(NULL); #endif + EXPECT_EQ(STR_OK, GetStringError()); } - void TestConfig(rapidjson::Document& test_json, std::string config) { + void TestConfig(rapidjson::Document& test_json, tuwString config) { rapidjson::Document test_config; json_utils::JsonResult result = json_utils::LoadJson(config, test_config); EXPECT_TRUE(result.ok); @@ -98,7 +98,7 @@ TEST_F(MainFrameTest, GetCommand2) { rapidjson::Document dummy_config; GetDummyConfig(dummy_config); main_frame = new MainFrame(test_json, dummy_config); - std::string expected = "echo file: \"test.txt\" & echo folder: \"testdir\""; + tuwString expected = "echo file: \"test.txt\" & echo folder: \"testdir\""; expected += " & echo combo: value3 & echo radio: value3 & echo check: flag!"; expected += " & echo check_array: --f2 & echo textbox: remove this text!"; expected += " & echo int: 10 & echo float: 0.01"; @@ -111,7 +111,7 @@ TEST_F(MainFrameTest, GetCommand3) { rapidjson::Document dummy_config; GetDummyConfig(dummy_config); main_frame = new MainFrame(test_json, dummy_config); - std::string expected = "echo file: & echo folder: & echo combo: value1 & echo radio: value1"; + tuwString expected = "echo file: & echo folder: & echo combo: value1 & echo radio: value1"; expected += " & echo check: & echo check_array: & echo textbox: "; expected += " & echo int: 0 & echo float: 0.0"; EXPECT_STREQ(expected.c_str(), main_frame->GetCommand().c_str()); @@ -128,7 +128,7 @@ TEST_F(MainFrameTest, RunCommandSuccess) { main_frame->GetDefinition(actual_json); ASSERT_EQ(test_json["help"], actual_json["help"]); - std::string cmd = main_frame->GetCommand(); + tuwString cmd = main_frame->GetCommand(); ExecuteResult result = Execute(cmd); EXPECT_EQ(0, result.exit_code); EXPECT_STREQ("", result.err_msg.c_str()); @@ -143,7 +143,7 @@ TEST_F(MainFrameTest, RunCommandFail) { GetDummyConfig(dummy_config); main_frame = new MainFrame(test_json, dummy_config); - std::string cmd = main_frame->GetCommand(); + tuwString cmd = main_frame->GetCommand(); ExecuteResult result = Execute(cmd); EXPECT_NE(0, result.exit_code); EXPECT_STRNE("", result.err_msg.c_str()); @@ -169,7 +169,7 @@ TEST_F(MainFrameTest, RunCommandShowLast) { main_frame = new MainFrame(test_json, dummy_config); main_frame->UpdatePanel(2); - std::string cmd = main_frame->GetCommand(); + tuwString cmd = main_frame->GetCommand(); ExecuteResult result = Execute(cmd); EXPECT_EQ(0, result.exit_code); EXPECT_STREQ("", result.err_msg.c_str()); @@ -187,8 +187,8 @@ TEST_F(MainFrameTest, LoadSaveConfigUTF) { rapidjson::Document test_json; GetTestJson(test_json); test_json["gui"][0].Swap(test_json["gui"][1]); - std::string cmd = test_json["gui"][0]["command"].GetString(); - cmd.replace(12, 4, "ファイル"); + tuwString cmd = test_json["gui"][0]["command"].GetString(); + memcpy(cmd.data() + 12, "ファイル", 4); test_json["gui"][0]["command"].SetString(rapidjson::StringRef(cmd.c_str())); test_json["gui"][0]["components"][1]["id"].SetString("ファイル"); TestConfig(test_json, JSON_CONFIG_UTF); diff --git a/tests/string_utils_test.cpp b/tests/string_utils_test.cpp index 9ec76a29..806eabe8 100644 --- a/tests/string_utils_test.cpp +++ b/tests/string_utils_test.cpp @@ -1,37 +1,331 @@ -#pragma once - -// Tests for json checking +// Tests for string utils // Todo: Write more tests #include "test_utils.h" -TEST(StringUtilsTest, ConcatCStringsChar) { - std::string result = ConcatCStrings("test", "foo", "bar"); - EXPECT_STREQ("testfoobar", result.c_str()); - result = ConcatCStrings("test", "foo", nullptr); - EXPECT_STREQ("testfoo", result.c_str()); +void expect_nullstr(const tuwString& str) { + EXPECT_TRUE(str.empty()); + EXPECT_EQ(str.size(), 0); + EXPECT_EQ(str.data(), nullptr); + EXPECT_STREQ(str.c_str(), ""); + EXPECT_EQ(str, ""); +} + +void expect_tuwstr(const char* expected, const tuwString& actual) { + size_t size = strlen(expected); + EXPECT_EQ(actual.size(), size); + EXPECT_STREQ(actual.data(), expected); + EXPECT_STREQ(actual.c_str(), expected); + EXPECT_EQ(actual, expected); +} + +// Test tuwString() +TEST(tuwStringTest, Construct) { + tuwString str; + expect_nullstr(str); +} + +TEST(tuwStringTest, ConstructWithNull) { + tuwString str(nullptr); + expect_nullstr(str); +} + +TEST(tuwStringTest, ConstructWithCstr) { + tuwString str("test"); + EXPECT_FALSE(str.empty()); + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, ConstructWithCstrAndSize) { + tuwString str("testtest", 4); + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, ConstructWithNullAndSize) { + tuwString str(nullptr, 4); + expect_nullstr(str); +} + +TEST(tuwStringTest, ConstructWithTuwstr) { + tuwString str2("test"); + tuwString str(str2); + expect_tuwstr("test", str); + expect_tuwstr("test", str2); +} + +TEST(tuwStringTest, ConstructWithMovedTuwstr) { + tuwString str2("test"); + tuwString str(std::move(str2)); + expect_tuwstr("test", str); + expect_nullstr(str2); +} + +TEST(tuwStringTest, ConstructWithSize) { + tuwString str(4); + EXPECT_TRUE(str.empty()); + EXPECT_EQ(str.size(), 4); + EXPECT_EQ(str.data()[3], '\0'); + EXPECT_STREQ(str.c_str(), ""); + EXPECT_EQ(str, ""); +} + +// Test = +TEST(tuwStringTest, AssignNull) { + tuwString str = nullptr; + expect_nullstr(str); +} + +TEST(tuwStringTest, AssignCstr) { + tuwString str = "test"; + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, AssignTuwstr) { + tuwString str2 ="test"; + tuwString str = str2; + expect_tuwstr("test", str); + expect_tuwstr("test", str2); +} + +TEST(tuwStringTest, AssingMovedTuwstr) { + tuwString str2 = "test"; + tuwString str = std::move(str2); + expect_tuwstr("test", str); + expect_nullstr(str2); +} + +TEST(tuwStringTest, AssingSelf) { + tuwString str = "test"; + str = str; + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, AssingMovedSelf) { + tuwString str = "test"; + str = std::move(str); + expect_tuwstr("test", str); +} + +// Test += +TEST(tuwStringTest, AppendCstr) { + tuwString str = "test"; + str += "foo"; + expect_tuwstr("testfoo", str); +} + +TEST(tuwStringTest, AppendNull) { + tuwString str = "test"; + str += nullptr; + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, AppendToNull) { + tuwString str = nullptr; + str += "test"; + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, AppendTuwstr) { + tuwString str = "test"; + tuwString str2 = "foo"; + str += str2; + expect_tuwstr("testfoo", str); + expect_tuwstr("foo", str2); +} + +// Test + +TEST(tuwStringTest, PlusCstr) { + tuwString str = tuwString("test") + "foo"; + expect_tuwstr("testfoo", str); +} + +TEST(tuwStringTest, PlusNull) { + tuwString str = tuwString("test") + nullptr; + expect_tuwstr("test", str); } -TEST(StringUtilsTest, ConcatCStringsInt) { +TEST(tuwStringTest, PlusToNull) { + tuwString str = tuwString(nullptr) + "test"; + expect_tuwstr("test", str); +} + +TEST(tuwStringTest, PlusTuwstr) { + tuwString str = "test"; + tuwString str2 = "foo"; + tuwString str3 = str + str2; + expect_tuwstr("test", str); + expect_tuwstr("foo", str2); + expect_tuwstr("testfoo", str3); +} + +TEST(tuwStringTest, PlusInt) { int a = -1; - std::string result = ConcatCStrings("test", a, "bar"); + tuwString result = tuwString("test") + a + "bar"; EXPECT_STREQ("test-1bar", result.c_str()); - result = ConcatCStrings("test", a, nullptr); - EXPECT_STREQ("test-1", result.c_str()); } -TEST(StringUtilsTest, ConcatCStringsSizet) { +TEST(tuwStringTest, PlusSizet) { size_t a = 100; - std::string result = ConcatCStrings("test", a, "bar"); + tuwString result = tuwString("test") + a + "bar"; EXPECT_STREQ("test100bar", result.c_str()); - result = ConcatCStrings("test", a, nullptr); - EXPECT_STREQ("test100", result.c_str()); } -TEST(StringUtilsTest, ConcatCStringsUint32) { +TEST(tuwStringTest, PlusUint32) { uint32_t a = 100; - std::string result = ConcatCStrings("test", a, "bar"); + tuwString result = tuwString("test") + a + "bar"; EXPECT_STREQ("test100bar", result.c_str()); - result = ConcatCStrings("test", a, nullptr); - EXPECT_STREQ("test100", result.c_str()); +} + +TEST(tuwStringTest, CstrPlusTuwstr) { + tuwString str = "test" + tuwString("foo"); + expect_tuwstr("testfoo", str); +} + +TEST(tuwStringTest, NullPlusTuwstr) { + tuwString str = nullptr + tuwString("test"); + expect_tuwstr("test", str); +} + +// Test [] +TEST(tuwStringTest, Index) { + tuwString str = "test"; + EXPECT_EQ('t', str[0]); + EXPECT_EQ('e', str[1]); + EXPECT_EQ('s', str[2]); + EXPECT_EQ('t', str[3]); + EXPECT_EQ('\0', str[4]); + EXPECT_EQ(STR_OK, GetStringError()); + EXPECT_EQ('\0', str[5]); + EXPECT_EQ(STR_BOUNDARY_ERROR, GetStringError()); + ClearStringError(); +} + +TEST(tuwStringTest, IndexNull) { + tuwString str; + EXPECT_EQ('\0', str[0]); +} + +// Test == and != +TEST(tuwStringTest, EqualToStr) { + tuwString str = "test"; + EXPECT_TRUE(str != nullptr); + EXPECT_EQ(str, "test"); + EXPECT_EQ(str, tuwString("test")); + EXPECT_NE(str, "task"); + EXPECT_NE(str, tuwString("task")); +} + +TEST(tuwStringTest, EqualToStrReverse) { + tuwString str = "test"; + EXPECT_NE(nullptr, str); + EXPECT_EQ("test", str); + EXPECT_NE("task", str); +} + +TEST(tuwStringTest, EqualToNull) { + tuwString str; + EXPECT_EQ(str, nullptr); + EXPECT_EQ(str, ""); + EXPECT_EQ(str, tuwString()); + EXPECT_NE(str, "test"); + EXPECT_NE(str, tuwString("test")); +} + +TEST(tuwStringTest, EqualToNullReverse) { + tuwString str; + EXPECT_EQ(nullptr, str); + EXPECT_EQ("", str); + EXPECT_NE("test", str); +} + +// Test find() +TEST(tuwStringTest, FindChar) { + tuwString str = "footestfoo"; + EXPECT_EQ(0, str.find('f')); + EXPECT_EQ(3, str.find('t')); + EXPECT_EQ(5, str.find('s')); + EXPECT_EQ(tuwString::npos, str.find('a')); + EXPECT_EQ(tuwString::npos, tuwString().find('a')); +} + +TEST(tuwStringTest, FindCstr) { + tuwString str = "footestfoo"; + EXPECT_EQ(0, str.find("foo")); + EXPECT_EQ(3, str.find("test")); + EXPECT_EQ(tuwString::npos, str.find(nullptr)); + EXPECT_EQ(tuwString::npos, str.find("task")); + EXPECT_EQ(tuwString::npos, str.find("fooo")); + EXPECT_EQ(tuwString::npos, tuwString().find("test")); +} + +TEST(tuwStringTest, FindTuwstr) { + tuwString str = "footestfoo"; + EXPECT_EQ(0, str.find(tuwString("foo"))); + EXPECT_EQ(3, str.find(tuwString("test"))); + EXPECT_EQ(0, str.find(tuwString(nullptr))); + EXPECT_EQ(tuwString::npos, str.find(tuwString("task"))); + EXPECT_EQ(tuwString::npos, str.find(tuwString("fooo"))); + EXPECT_EQ(tuwString::npos, tuwString().find(tuwString("test"))); +} + +// Test push_back() +TEST(tuwStringTest, Pushback) { + tuwString str; + expect_nullstr(str); + str.push_back('t'); + expect_tuwstr("t", str); + str.push_back('e'); + str.push_back('s'); + str.push_back('t'); + expect_tuwstr("test", str); +} + +// Test substr() +TEST(tuwStringTest, Substr) { + tuwString str = "footestfoo"; + expect_tuwstr("foo", str.substr(0, 3)); + expect_tuwstr("test", str.substr(3, 4)); + EXPECT_EQ(STR_OK, GetStringError()); + expect_nullstr(str.substr(7, 10)); + expect_nullstr(str.substr(20, 4)); + expect_nullstr(tuwString().substr(0, 3)); + EXPECT_EQ(STR_BOUNDARY_ERROR, GetStringError()); + ClearStringError(); +} + +// Test begin() and end() +TEST(tuwStringTest, Iter) { + tuwString str = "footestfoo"; + EXPECT_EQ(&str.data()[0], str.begin()); + EXPECT_EQ(&str.data()[str.size()], str.end()); +} + +TEST(tuwStringTest, IterForNull) { + tuwString str; + EXPECT_EQ(&str.c_str()[0], str.begin()); + EXPECT_EQ(&str.c_str()[0], str.end()); +} + +// Test tuwWstring +void expect_nullwstr(const tuwWstring& str) { + EXPECT_TRUE(str.empty()); + EXPECT_EQ(str.size(), 0); + EXPECT_STREQ(str.c_str(), L""); +} + +void expect_tuwwstr(const wchar_t* expected, const tuwWstring& actual) { + size_t size = wcslen(expected); + EXPECT_EQ(actual.size(), size); + EXPECT_STREQ(actual.c_str(), expected); +} + +TEST(tuwWstringTest, ConstructWithNull) { + tuwWstring str(nullptr); + expect_nullwstr(str); +} + +TEST(tuwWstringTest, ConstructWithCstr) { + tuwWstring str(L"test"); + EXPECT_FALSE(str.empty()); + expect_tuwwstr(L"test", str); } diff --git a/tests/test_utils.h b/tests/test_utils.h index 545f230f..ecf268bb 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include "main_frame.h" #include "string_utils.h" #include "exec.h"