diff --git a/nob.c b/nob.c index 58c0500..cf2a161 100644 --- a/nob.c +++ b/nob.c @@ -18,6 +18,7 @@ const char *test_names[] = { "cmd_run_dont_reset", "chain", "private_functions_inside_public_macros", + "unicode", }; #define test_names_count ARRAY_LEN(test_names) @@ -162,15 +163,6 @@ void print_available_commands(Commands commands) } } -#if defined(_WIN32) && defined(_MSC_VER) && defined(__cplusplus) -// TODO: I don't know why, but when you compile nob.c with -// cl.exe /std:c++20 /TP nob.c -// It just can't find the declaration of SetConsoleOutputCP(). -// This is probably something about how we include windows.h in nob.h -extern "C" { - WINBASEAPI BOOL WINAPI SetConsoleOutputCP(_In_ UINT wCodePageID); -} -#endif int main(int argc, char **argv) { diff --git a/nob.h b/nob.h index 2735a93..275c8c0 100644 --- a/nob.h +++ b/nob.h @@ -164,6 +164,7 @@ # include # include # include +# include #else # ifdef __APPLE__ # include @@ -307,13 +308,31 @@ NOBDEF bool nob_walk_dir_opt(const char *root, Nob_Walk_Func func, Nob_Walk_Dir_ #define nob_walk_dir(root, func, ...) nob_walk_dir_opt((root), (func), NOB_CLIT(Nob_Walk_Dir_Opt){__VA_ARGS__}) +#ifdef _WIN32 +/* +UTF-16 is variable length encoding. Every code point could be encoded by 1 or 2 UTF-16 code units. Every UTF-16 code units is two bytes. +UTF-8 is variable length encoding. Every code point could be encoded by 1, 2, 3 or 4 UTF-8 code units. Every UTF-8 code units is one byte. +In the worst case, single UTF-16 code unit code point could be encoded by 3 UTF-8 code units. Code points from U+0800 to U+FFFF. Meaning from 1 wchar_t to 3 char. +In the worst case, doble UTF-16 code unit code point could be encoded by 4 UTF-8 code units. Code points from U+010000 to U+10FFFF. Meaning from 2 wchar_t to 4 char. +Given the rules above, we need to allcoate 3 char for each 1 wchar_t in order to be able to represent any sequecne of any code points. +*/ +#define nob__worst_case_utf16_to_utf8(count) (3 * (count)) +#endif // _WIN32 + +#ifdef _WIN32 +typedef struct { + WIN32_FIND_DATAW win_find_data; + char utf8_file_name[nob__worst_case_utf16_to_utf8(MAX_PATH)]; +} nob__win32_find_dataw; +#endif // _WIN32 + typedef struct { char *name; bool error; struct { #ifdef _WIN32 - WIN32_FIND_DATA win32_data; + nob__win32_find_dataw find_data; HANDLE win32_hFind; bool win32_init; #else @@ -976,6 +995,58 @@ NOBDEF void nob__cmd_append(Nob_Cmd *cmd, size_t n, const char **args) } } +#ifdef _WIN32 + + +wchar_t* nob__unicode_utf8_to_unicode_utf16_temp(const char* narrow_str) +{ + int wide_len_a; + DWORD err; + int wide_len_b; + wchar_t* wide_str; + + NOB_ASSERT(narrow_str); + wide_len_a = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, narrow_str, -1, NULL, 0); + if (wide_len_a == 0) { + err = GetLastError(); + (void)err; + return NULL; + } + NOB_ASSERT(wide_len_a >= 1); + wide_str = (wchar_t*)nob_temp_alloc(wide_len_a * sizeof(wchar_t)); + NOB_ASSERT(wide_str); + wide_len_b = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, narrow_str, -1, wide_str, wide_len_a); + if (wide_len_b == 0) { + err = GetLastError(); + (void)err; + return NULL; + } + NOB_ASSERT(wide_len_b == wide_len_a); + return wide_str; +} + +int nob__unicode_utf16_to_unicode_utf8(const wchar_t* wide_str, int wide_len, char* narrow_str, int narrow_capacity) +{ + int narrow_len; + DWORD err; + + NOB_ASSERT(wide_str); + NOB_ASSERT(wide_len >= 1); + NOB_ASSERT(narrow_str); + NOB_ASSERT(narrow_capacity >= 1); + + narrow_len = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, wide_str, wide_len, narrow_str, narrow_capacity, NULL, NULL); + if (narrow_len == 0) { + err = GetLastError(); + (void)err; + } + return narrow_len; +} + + +#endif // _WIN32 + + #ifdef _WIN32 // Base on https://stackoverflow.com/a/75644008 @@ -987,31 +1058,32 @@ NOBDEF void nob__cmd_append(Nob_Cmd *cmd, size_t n, const char **args) #endif // NOB_WIN32_ERR_MSG_SIZE NOBDEF char *nob_win32_error_message(DWORD err) { - static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0}; - DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg, + static wchar_t wide_win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0}; + static char narrow_win32ErrMsg[nob__worst_case_utf16_to_utf8(NOB_ARRAY_LEN(wide_win32ErrMsg))] = {0}; + DWORD errMsgSize = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, wide_win32ErrMsg, NOB_WIN32_ERR_MSG_SIZE, NULL); if (errMsgSize == 0) { if (GetLastError() != ERROR_MR_MID_NOT_FOUND) { - if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) { - return (char *)&win32ErrMsg; + if (sprintf(narrow_win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) { + return (char *)&narrow_win32ErrMsg; } else { return NULL; } } else { - if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) { - return (char *)&win32ErrMsg; + if (sprintf(narrow_win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) { + return (char *)&narrow_win32ErrMsg; } else { return NULL; } } } - - while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) { - win32ErrMsg[--errMsgSize] = '\0'; + errMsgSize = nob__unicode_utf16_to_unicode_utf8(wide_win32ErrMsg, errMsgSize + 1, narrow_win32ErrMsg, NOB_ARRAY_LEN(narrow_win32ErrMsg)) - 1; + while (errMsgSize > 1 && isspace(narrow_win32ErrMsg[errMsgSize - 1])) { + narrow_win32ErrMsg[--errMsgSize] = '\0'; } - return win32ErrMsg; + return narrow_win32ErrMsg; } #endif // _WIN32 @@ -1076,10 +1148,42 @@ static char nob_temp[NOB_TEMP_CAPACITY] = {0}; NOBDEF bool nob_mkdir_if_not_exists(const char *path) { #ifdef _WIN32 - int result = _mkdir(path); + size_t mark; + wchar_t *wide_path; + BOOL b; + DWORD err; + + mark = nob_temp_save(); + wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + b = CreateDirectoryW(wide_path, NULL); + err = GetLastError(); + nob_temp_rewind(mark); + if(b != 0) + { + #ifndef NOB_NO_ECHO + nob_log(NOB_INFO, "created directory `%s`", path); + #endif // NOB_NO_ECHO + return true; + } + else if(b == 0 && err == ERROR_ALREADY_EXISTS) + { + #ifndef NOB_NO_ECHO + nob_log(NOB_INFO, "directory `%s` already exists", path); + #endif // NOB_NO_ECHO + return true; + } + else if(b == 0 && err == ERROR_PATH_NOT_FOUND) + { + NOB_TODO("One or more intermediate directories do not exist; this function will only create the final directory in the path."); + return false; + } + else + { + nob_log(NOB_ERROR, "Could not create directory: %s", nob_win32_error_message(err)); + return false; + } #else int result = mkdir(path, 0755); -#endif if (result < 0) { if (errno == EEXIST) { #ifndef NOB_NO_ECHO @@ -1095,6 +1199,7 @@ NOBDEF bool nob_mkdir_if_not_exists(const char *path) nob_log(NOB_INFO, "created directory `%s`", path); #endif // NOB_NO_ECHO return true; +#endif // _WIN32 } NOBDEF bool nob_copy_file(const char *src_path, const char *dst_path) @@ -1103,11 +1208,21 @@ NOBDEF bool nob_copy_file(const char *src_path, const char *dst_path) nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path); #endif // NOB_NO_ECHO #ifdef _WIN32 - if (!CopyFile(src_path, dst_path, FALSE)) { + bool ret; + size_t mark; + wchar_t* wide_src_path; + wchar_t* wide_dst_path; + + ret = true; + mark = nob_temp_save(); + wide_src_path = nob__unicode_utf8_to_unicode_utf16_temp(src_path); + wide_dst_path = nob__unicode_utf8_to_unicode_utf16_temp(dst_path); + if (!CopyFileW(wide_src_path, wide_dst_path, FALSE)) { nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError())); - return false; + ret = false; } - return true; + nob_temp_rewind(mark); + return ret; #else int src_fd = -1; int dst_fd = -1; @@ -1473,9 +1588,9 @@ static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, #ifdef _WIN32 // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output - STARTUPINFO siStartInfo; + STARTUPINFOW siStartInfo; ZeroMemory(&siStartInfo, sizeof(siStartInfo)); - siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.cb = sizeof(siStartInfo); // NOTE: theoretically setting NULL to std handles should not be a problem // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior // TODO: check for errors in GetStdHandle @@ -1490,7 +1605,10 @@ static Nob_Proc nob__cmd_start_process(Nob_Cmd cmd, Nob_Fd *fdin, Nob_Fd *fdout, Nob_String_Builder quoted = {0}; nob__win32_cmd_quote(cmd, "ed); nob_sb_append_null("ed); - BOOL bSuccess = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); + size_t mark = nob_temp_save(); + wchar_t* wide_command_line = nob__unicode_utf8_to_unicode_utf16_temp(quoted.items); + BOOL bSuccess = CreateProcessW(NULL, wide_command_line, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); + nob_temp_rewind(mark); nob_sb_free(quoted); if (!bSuccess) { @@ -1593,14 +1711,17 @@ NOBDEF Nob_Fd nob_fd_open_for_read(const char *path) saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; - Nob_Fd result = CreateFile( - path, + size_t mark = nob_temp_save(); + wchar_t *wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + Nob_Fd result = CreateFileW( + wide_path, GENERIC_READ, - 0, + FILE_SHARE_READ | FILE_SHARE_DELETE, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + nob_temp_rewind(mark); if (result == INVALID_HANDLE_VALUE) { nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); @@ -1627,8 +1748,10 @@ NOBDEF Nob_Fd nob_fd_open_for_write(const char *path) saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; - Nob_Fd result = CreateFile( - path, // name of the write + size_t mark = nob_temp_save(); + wchar_t *wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + Nob_Fd result = CreateFileW( + wide_path, // name of the write GENERIC_WRITE, // open for writing 0, // do not share &saAttr, // default security @@ -1636,6 +1759,7 @@ NOBDEF Nob_Fd nob_fd_open_for_write(const char *path) FILE_ATTRIBUTE_NORMAL, // normal file NULL // no attr. template ); + nob_temp_rewind(mark); if (result == INVALID_HANDLE_VALUE) { nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); @@ -1959,8 +2083,9 @@ NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir) memset(dir, 0, sizeof(*dir)); #ifdef _WIN32 size_t temp_mark = nob_temp_save(); - char *buffer = nob_temp_sprintf("%s\\*", dir_path); - dir->nob__private.win32_hFind = FindFirstFile(buffer, &dir->nob__private.win32_data); + char *narrow_path = nob_temp_sprintf("%s\\*", dir_path); + wchar_t *wide_path = nob__unicode_utf8_to_unicode_utf16_temp(narrow_path); + dir->nob__private.win32_hFind = FindFirstFileW(wide_path, &dir->nob__private.find_data.win_find_data); nob_temp_rewind(temp_mark); if (dir->nob__private.win32_hFind == INVALID_HANDLE_VALUE) { @@ -1968,6 +2093,12 @@ NOBDEF bool nob_dir_entry_open(const char *dir_path, Nob_Dir_Entry *dir) dir->error = true; return false; } + + const wchar_t *wide_name = dir->nob__private.find_data.win_find_data.cFileName; + char *narrow_name = dir->nob__private.find_data.utf8_file_name; + int narrow_cap = NOB_ARRAY_LEN(dir->nob__private.find_data.utf8_file_name); + int narrow_len = nob__unicode_utf16_to_unicode_utf8(wide_name, (int)wcslen(wide_name) + 1, narrow_name, narrow_cap) - 1; + (void)narrow_len; #else dir->nob__private.posix_dir = opendir(dir_path); if (dir->nob__private.posix_dir == NULL) { @@ -1984,17 +2115,22 @@ NOBDEF bool nob_dir_entry_next(Nob_Dir_Entry *dir) #ifdef _WIN32 if (!dir->nob__private.win32_init) { dir->nob__private.win32_init = true; - dir->name = dir->nob__private.win32_data.cFileName; + dir->name = dir->nob__private.find_data.utf8_file_name; return true; } - if (!FindNextFile(dir->nob__private.win32_hFind, &dir->nob__private.win32_data)) { + if (!FindNextFileW(dir->nob__private.win32_hFind, &dir->nob__private.find_data.win_find_data)) { if (GetLastError() == ERROR_NO_MORE_FILES) return false; nob_log(NOB_ERROR, "Could not read next directory entry: %s", nob_win32_error_message(GetLastError())); dir->error = true; return false; } - dir->name = dir->nob__private.win32_data.cFileName; + const wchar_t *wide_name = dir->nob__private.find_data.win_find_data.cFileName; + char *narrow_name = dir->nob__private.find_data.utf8_file_name; + int narrow_cap = NOB_ARRAY_LEN(dir->nob__private.find_data.utf8_file_name); + int narrow_len = nob__unicode_utf16_to_unicode_utf8(wide_name, (int)wcslen(wide_name) + 1, narrow_name, narrow_cap); + (void)narrow_len; + dir->name = narrow_name; #else errno = 0; dir->nob__private.posix_ent = readdir(dir->nob__private.posix_dir); @@ -2137,6 +2273,42 @@ NOBDEF bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children) NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t size) { +#ifdef _WIN32 + bool result; + HANDLE file; + size_t mark; + wchar_t *wide_path; + DWORD err; + BOOL b; + DWORD written; + + result = true; + file = INVALID_HANDLE_VALUE; + mark = nob_temp_save(); + wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + file = CreateFileW(wide_path, GENERIC_WRITE, FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + err = GetLastError(); + nob_temp_rewind(mark); + if (file == INVALID_HANDLE_VALUE) + { + nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, nob_win32_error_message(err)); + nob_return_defer(false); + } + b = WriteFile(file, data, (DWORD)size, &written, NULL); + if (!(b != FALSE && written == (DWORD)size)) + { + err = GetLastError(); + nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, nob_win32_error_message(err)); + nob_return_defer(false); + } +defer: + if (file != INVALID_HANDLE_VALUE) + { + b = CloseHandle(file); + NOB_ASSERT(b != FALSE); + } + return result; +#else bool result = true; const char *buf = NULL; @@ -2166,12 +2338,16 @@ NOBDEF bool nob_write_entire_file(const char *path, const void *data, size_t siz defer: if (f) fclose(f); return result; +#endif // _WIN32 } NOBDEF Nob_File_Type nob_get_file_type(const char *path) { #ifdef _WIN32 - DWORD attr = GetFileAttributesA(path); + size_t mark = nob_temp_save(); + wchar_t *wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + DWORD attr = GetFileAttributesW(wide_path); + nob_temp_rewind(mark); if (attr == INVALID_FILE_ATTRIBUTES) { nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError())); return (Nob_File_Type)-1; @@ -2200,21 +2376,34 @@ NOBDEF bool nob_delete_file(const char *path) nob_log(NOB_INFO, "deleting %s", path); #endif // NOB_NO_ECHO #ifdef _WIN32 + bool ret; + size_t mark; + wchar_t *wide_path; Nob_File_Type type = nob_get_file_type(path); switch (type) { case NOB_FILE_DIRECTORY: - if (!RemoveDirectoryA(path)) { + ret = true; + mark = nob_temp_save(); + wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + if (!RemoveDirectoryW(wide_path)) { nob_log(NOB_ERROR, "Could not delete directory %s: %s", path, nob_win32_error_message(GetLastError())); - return false; + ret = false; } + nob_temp_rewind(mark); + return ret; break; case NOB_FILE_REGULAR: case NOB_FILE_SYMLINK: case NOB_FILE_OTHER: - if (!DeleteFileA(path)) { + ret = true; + mark = nob_temp_save(); + wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + if (!DeleteFileW(wide_path)) { nob_log(NOB_ERROR, "Could not delete file %s: %s", path, nob_win32_error_message(GetLastError())); - return false; + ret = false; } + nob_temp_rewind(mark); + return ret; break; default: NOB_UNREACHABLE("Nob_File_Type"); } @@ -2371,9 +2560,10 @@ NOBDEF const char *nob_temp_sv_to_cstr(Nob_String_View sv) NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) { #ifdef _WIN32 - BOOL bSuccess; - - HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + size_t mark = nob_temp_save(); + wchar_t *wide_output_path = nob__unicode_utf8_to_unicode_utf16_temp(output_path); + HANDLE output_path_fd = CreateFileW(wide_output_path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + nob_temp_rewind(mark); if (output_path_fd == INVALID_HANDLE_VALUE) { // NOTE: if output does not exist it 100% must be rebuilt if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1; @@ -2381,7 +2571,7 @@ NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, return -1; } FILETIME output_path_time; - bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); + BOOL bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); CloseHandle(output_path_fd); if (!bSuccess) { nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError())); @@ -2390,7 +2580,10 @@ NOBDEF int nob_needs_rebuild(const char *output_path, const char **input_paths, for (size_t i = 0; i < input_paths_count; ++i) { const char *input_path = input_paths[i]; - HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + mark = nob_temp_save(); + wchar_t *wide_input_path = nob__unicode_utf8_to_unicode_utf16_temp(input_path); + HANDLE input_path_fd = CreateFileW(wide_input_path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); + nob_temp_rewind(mark); if (input_path_fd == INVALID_HANDLE_VALUE) { // NOTE: non-existing input is an error cause it is needed for building in the first place nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError())); @@ -2460,21 +2653,86 @@ NOBDEF bool nob_rename(const char *old_path, const char *new_path) nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path); #endif // NOB_NO_ECHO #ifdef _WIN32 - if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { + bool ret; + size_t mark; + wchar_t *wide_old_path; + wchar_t *wide_new_path; + + ret = true; + mark = nob_temp_save(); + wide_old_path = nob__unicode_utf8_to_unicode_utf16_temp(old_path); + wide_new_path = nob__unicode_utf8_to_unicode_utf16_temp(new_path); + if (!MoveFileExW(wide_old_path, wide_new_path, MOVEFILE_REPLACE_EXISTING)) { nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError())); - return false; + ret = false; } + nob_temp_rewind(mark); + return ret; #else if (rename(old_path, new_path) < 0) { nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); return false; } -#endif // _WIN32 return true; +#endif // _WIN32 } NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) { +#ifdef _WIN32 + bool result; + HANDLE file; + DWORD chunk_size; + size_t mark; + wchar_t *wide_path; + DWORD err; + size_t new_count; + BOOL b; + DWORD read; + + result = true; + file = INVALID_HANDLE_VALUE; + chunk_size = 64 * 1024; + mark = nob_temp_save(); + wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + file = CreateFileW(wide_path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + err = GetLastError(); + nob_temp_rewind(mark); + if (file == INVALID_HANDLE_VALUE) + { + nob_log(NOB_ERROR, "Could not open file %s for reading: %s\n", path, nob_win32_error_message(err)); + nob_return_defer(false); + } + for (;;) + { + new_count = sb->count + chunk_size; + if (new_count > sb->capacity) + { + sb->items = NOB_DECLTYPE_CAST(sb->items)NOB_REALLOC(sb->items, 2 * new_count); + NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); + sb->capacity = new_count; + } + b = ReadFile(file, sb->items + sb->count, chunk_size, &read, NULL); + if (b == FALSE) + { + err = GetLastError(); + nob_log(NOB_ERROR, "Could not read from file %s: %s\n", path, nob_win32_error_message(err)); + nob_return_defer(false); + } + sb->count += read; + if (read != chunk_size) + { + break; + } + } +defer: + if (file != INVALID_HANDLE_VALUE) + { + b = CloseHandle(file); + NOB_ASSERT(b != FALSE); + } + return result; +#else bool result = true; FILE *f = fopen(path, "rb"); @@ -2482,11 +2740,7 @@ NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) long long m = 0; if (f == NULL) nob_return_defer(false); if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); -#ifndef _WIN32 m = ftell(f); -#else - m = _telli64(_fileno(f)); -#endif if (m < 0) nob_return_defer(false); if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); @@ -2508,6 +2762,7 @@ NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); if (f) fclose(f); return result; +#endif // _WIN32 } NOBDEF int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) @@ -2705,28 +2960,47 @@ NOBDEF bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_pref NOBDEF int nob_file_exists(const char *file_path) { #if _WIN32 - return GetFileAttributesA(file_path) != INVALID_FILE_ATTRIBUTES; + size_t mark; + wchar_t *wide_file_path; + bool ret; + + mark = nob_temp_save(); + wide_file_path = nob__unicode_utf8_to_unicode_utf16_temp(file_path); + ret = GetFileAttributesW(wide_file_path) != INVALID_FILE_ATTRIBUTES; + nob_temp_rewind(mark); + return ret; #else return access(file_path, F_OK) == 0; -#endif +#endif // _WIN32 } NOBDEF const char *nob_get_current_dir_temp(void) { #ifdef _WIN32 - DWORD nBufferLength = GetCurrentDirectory(0, NULL); - if (nBufferLength == 0) { + DWORD nBufferLengthA; + wchar_t *wide_buffer; + DWORD nBufferLengthB; + char *narrow_buffer; + int narrow_len; + + nBufferLengthA = GetCurrentDirectoryW(0, NULL); + if (nBufferLengthA == 0) { nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); return NULL; } - char *buffer = (char*) nob_temp_alloc(nBufferLength); - if (GetCurrentDirectory(nBufferLength, buffer) == 0) { + wide_buffer = (wchar_t*)nob_temp_alloc(nBufferLengthA * sizeof(wchar_t)); + nBufferLengthB = GetCurrentDirectoryW(nBufferLengthA, wide_buffer); + if (nBufferLengthB == 0) { nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); return NULL; } + NOB_ASSERT(nBufferLengthB == nBufferLengthA - 1); - return buffer; + narrow_buffer = (char*)nob_temp_alloc(nob__worst_case_utf16_to_utf8(nBufferLengthA)); + narrow_len = nob__unicode_utf16_to_unicode_utf8(wide_buffer, nBufferLengthA, narrow_buffer, nob__worst_case_utf16_to_utf8(nBufferLengthA)) - 1; + (void)narrow_len; + return narrow_buffer; #else char *buffer = (char*) nob_temp_alloc(PATH_MAX); if (getcwd(buffer, PATH_MAX) == NULL) { @@ -2741,11 +3015,19 @@ NOBDEF const char *nob_get_current_dir_temp(void) NOBDEF bool nob_set_current_dir(const char *path) { #ifdef _WIN32 - if (!SetCurrentDirectory(path)) { + bool ret; + size_t mark; + wchar_t *wide_path; + + ret = true; + mark = nob_temp_save(); + wide_path = nob__unicode_utf8_to_unicode_utf16_temp(path); + if (!SetCurrentDirectoryW(wide_path)) { nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError())); - return false; + ret = false; } - return true; + nob_temp_rewind(mark); + return ret; #else if (chdir(path) < 0) { nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno)); @@ -2823,9 +3105,21 @@ NOBDEF char *nob_temp_running_executable_path(void) if (length < 0) return nob_temp_strdup(""); return nob_temp_strndup(buf, length); #elif defined(_WIN32) - char buf[MAX_PATH]; - int length = GetModuleFileNameA(NULL, buf, MAX_PATH); - return nob_temp_strndup(buf, length); + wchar_t wide_buf[4096]; /* in reality max path len is 64 kB, meaning 32 thousand UTF-16 code units */ + char narrow_buf[nob__worst_case_utf16_to_utf8(NOB_ARRAY_LEN(wide_buf))]; + DWORD wide_len; + DWORD err; + wide_len = GetModuleFileNameW(NULL, wide_buf, NOB_ARRAY_LEN(wide_buf)); + if (wide_len == 0) { + err = GetLastError(); + (void)err; + return NULL; + } + if (!(wide_len < NOB_ARRAY_LEN(wide_buf))) { + NOB_TODO("Increase wide_buf size."); + } + int narrow_len = nob__unicode_utf16_to_unicode_utf8(wide_buf, wide_len + 1, narrow_buf, NOB_ARRAY_LEN(narrow_buf)) - 1; + return nob_temp_strndup(narrow_buf, narrow_len); #elif defined(__APPLE__) char buf[4096]; uint32_t size = NOB_ARRAY_LEN(buf); diff --git a/tests/unicode.c b/tests/unicode.c new file mode 100644 index 0000000..6436d5e --- /dev/null +++ b/tests/unicode.c @@ -0,0 +1,126 @@ +#define NOB_IMPLEMENTATION +#include "nob.h" + + +#define stringify_impl(x) #x +#define stringify(x) stringify_impl(x) +#define test(x) do{ if(!(x)){ nob_log(NOB_ERROR, "TEST FAILED in file " __FILE__ " on line " stringify(__LINE__) " with expression `" stringify(x) "'."); } }while(false) + + +static char const* const k_strings[] = +{ + "Здравствуйте", /* Russian */ + "Γεια σας", /* Greek */ + "안녕", /* Korean */ + "こんにちは", /* Japanese */ + "您好", /* Chinese */ +}; + + +static void test_unicode_utf8_printf(void) +{ + int n; + int i; + + nob_log(NOB_INFO, "%s", "Testing log..."); + n = NOB_ARRAY_LEN(k_strings); + for(i = 0; i != n; ++i) + { + nob_log(NOB_INFO, "%s", k_strings[i]); + } +} + +static void test_unicode_utf8_dir(void) +{ + int n; + int i; + bool b; + size_t mark; + char const* curr_dir; + + nob_log(NOB_INFO, "%s", "Testing mkdir..."); + n = NOB_ARRAY_LEN(k_strings); + for(i = 0; i != n; ++i) + { + b = nob_mkdir_if_not_exists(k_strings[i]); + test(b); + + b = nob_set_current_dir(k_strings[i]); + test(b); + + mark = nob_temp_save(); + curr_dir = nob_get_current_dir_temp(); + test(memcmp(curr_dir + strlen(curr_dir) - strlen(k_strings[i]), k_strings[i], strlen(k_strings[i])) == 0); + nob_temp_rewind(mark); + + b = nob_set_current_dir(".."); + test(b); + + b = nob_delete_file(k_strings[i]); + test(b); + } +} + +static void test_unicode_utf8_file_operations(void) +{ + int n; + int i; + Nob_Fd fd; + Nob_File_Type ft; + bool b; + Nob_String_Builder sb; + + nob_log(NOB_INFO, "%s", "Testing file operations..."); + n = NOB_ARRAY_LEN(k_strings); + for(i = 0; i != n; ++i) + { + fd = nob_fd_open_for_write(k_strings[i]); + test(fd != NOB_INVALID_FD); + nob_fd_close(fd); + + fd = nob_fd_open_for_read(k_strings[i]); + test(fd != NOB_INVALID_FD); + nob_fd_close(fd); + + ft = nob_get_file_type(k_strings[i]); + test(ft == NOB_FILE_REGULAR); + + b = nob_rename(k_strings[i], k_strings[(i + 1) % NOB_ARRAY_LEN(k_strings)]); + test(b); + b = nob_rename(k_strings[(i + 1) % NOB_ARRAY_LEN(k_strings)], k_strings[i]); + test(b); + + b = nob_write_entire_file(k_strings[i], "test", 4); + test(b); + + sb.items = NULL; + sb.capacity = 0; + sb.count = 0; + b = nob_read_entire_file(k_strings[i], &sb); + test(b); + test(sb.count == 4); + test(memcmp(sb.items, "test", 4) == 0); + NOB_FREE(sb.items); + + b = nob_file_exists(k_strings[i]); + test(b); + + b = nob_delete_file(k_strings[i]); + test(b); + + b = nob_file_exists(k_strings[i]); + test(!b); + } +} + + +int main(void) +{ +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif // _WIN32 + test_unicode_utf8_printf(); + test_unicode_utf8_dir(); + test_unicode_utf8_file_operations(); + return 0; +} diff --git a/tests/unicode.stderr.txt b/tests/unicode.stderr.txt new file mode 100644 index 0000000..ae89ebd --- /dev/null +++ b/tests/unicode.stderr.txt @@ -0,0 +1,33 @@ +[INFO] Testing log... +[INFO] Здравствуйте +[INFO] Γεια σας +[INFO] 안녕 +[INFO] こんにちは +[INFO] 您好 +[INFO] Testing mkdir... +[INFO] created directory `Здравствуйте` +[INFO] deleting Здравствуйте +[INFO] created directory `Γεια σας` +[INFO] deleting Γεια σας +[INFO] created directory `안녕` +[INFO] deleting 안녕 +[INFO] created directory `こんにちは` +[INFO] deleting こんにちは +[INFO] created directory `您好` +[INFO] deleting 您好 +[INFO] Testing file operations... +[INFO] renaming Здравствуйте -> Γεια σας +[INFO] renaming Γεια σας -> Здравствуйте +[INFO] deleting Здравствуйте +[INFO] renaming Γεια σας -> 안녕 +[INFO] renaming 안녕 -> Γεια σας +[INFO] deleting Γεια σας +[INFO] renaming 안녕 -> こんにちは +[INFO] renaming こんにちは -> 안녕 +[INFO] deleting 안녕 +[INFO] renaming こんにちは -> 您好 +[INFO] renaming 您好 -> こんにちは +[INFO] deleting こんにちは +[INFO] renaming 您好 -> Здравствуйте +[INFO] renaming Здравствуйте -> 您好 +[INFO] deleting 您好 diff --git a/tests/unicode.stdout.txt b/tests/unicode.stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/unicode.win32.stdout.txt b/tests/unicode.win32.stdout.txt new file mode 100644 index 0000000..e69de29