KKdLib
This commit is contained in:
@@ -0,0 +1,690 @@
|
||||
/*
|
||||
by korenkonder
|
||||
GitHub/GitLab: korenkonder
|
||||
*/
|
||||
|
||||
#include "str_utils.hpp"
|
||||
|
||||
bool str_utils_check_ends_with(const char* str, const char* mask) {
|
||||
if (!str || !mask)
|
||||
return false;
|
||||
|
||||
size_t mask_len = utf8_length(mask);
|
||||
size_t len = utf8_length(str);
|
||||
const char* t = str;
|
||||
while (t) {
|
||||
t = strstr(t, mask);
|
||||
if (t) {
|
||||
t += mask_len;
|
||||
if (t == str + len)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool str_utils_check_ends_with(const wchar_t* str, const wchar_t* mask) {
|
||||
if (!str || !mask)
|
||||
return false;
|
||||
|
||||
size_t mask_len = utf16_length(mask);
|
||||
size_t len = utf16_length(str);
|
||||
const wchar_t* t = str;
|
||||
while (t) {
|
||||
t = wcsstr(t, mask);
|
||||
if (t) {
|
||||
t += mask_len;
|
||||
if (t == str + len)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* str_utils_get_next_int32_t(const char* str, int32_t& value, const char split) {
|
||||
std::string s;
|
||||
str = str_utils_get_next_string(str, s, split);
|
||||
sscanf_s(s.c_str(), "%d", &value);
|
||||
return str;
|
||||
}
|
||||
|
||||
const wchar_t* str_utils_get_next_int32_t(const wchar_t* str, int32_t& value, const wchar_t split) {
|
||||
std::wstring s;
|
||||
str = str_utils_get_next_string(str, s, split);
|
||||
swscanf_s(s.c_str(), L"%d", &value);
|
||||
return str;
|
||||
}
|
||||
|
||||
const char* str_utils_get_next_float_t(const char* str, float_t& value, const char split) {
|
||||
std::string s;
|
||||
str = str_utils_get_next_string(str, s, split);
|
||||
sscanf_s(s.c_str(), "%f", &value);
|
||||
return str;
|
||||
}
|
||||
|
||||
const wchar_t* str_utils_get_next_float_t(const wchar_t* str, float_t& value, const wchar_t split) {
|
||||
std::wstring s;
|
||||
str = str_utils_get_next_string(str, s, split);
|
||||
swscanf_s(s.c_str(), L"%f", &value);
|
||||
return str;
|
||||
}
|
||||
|
||||
const char* str_utils_get_next_string(const char* str, std::string& value, const char split) {
|
||||
value.clear();
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strchr(str, split);
|
||||
if (!t) {
|
||||
value.assign(str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
value.assign(str, t - str);
|
||||
t++;
|
||||
return *t ? t : 0;
|
||||
}
|
||||
|
||||
const wchar_t* str_utils_get_next_string(const wchar_t* str, std::wstring& value, const wchar_t split) {
|
||||
value.clear();
|
||||
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcschr(str, split);
|
||||
if (!t) {
|
||||
value.assign(str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
value.assign(str, t - str);
|
||||
t++;
|
||||
return *t ? t : 0;
|
||||
}
|
||||
|
||||
char* str_utils_split_get_right(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strchr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
t++;
|
||||
|
||||
size_t len = utf8_length(t);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, t, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_get_right(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcschr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
t++;
|
||||
|
||||
size_t len = utf16_length(t);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, t, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_get_left(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strchr(str, split);
|
||||
|
||||
size_t len = t ? t - str : utf8_length(str);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, str, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_get_left(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcschr(str, split);
|
||||
|
||||
size_t len = t ? t - str : utf16_length(str);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, str, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_get_right_include(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strchr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
|
||||
size_t len = utf8_length(t);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, t, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_get_right_include(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcschr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
|
||||
size_t len = utf16_length(t);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, t, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_get_left_include(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strchr(str, split);
|
||||
t++;
|
||||
|
||||
size_t len = t ? t - str : utf8_length(str);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, str, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_get_left_include(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcschr(str, split);
|
||||
t++;
|
||||
|
||||
size_t len = t ? t - str : utf16_length(str);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, str, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_right_get_right(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strrchr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
t++;
|
||||
|
||||
size_t len = t - str;
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, t, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_right_get_right(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcsrchr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
t++;
|
||||
|
||||
size_t len = t - str;
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, t, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_right_get_left(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strrchr(str, split);
|
||||
|
||||
size_t len = t ? t - str : utf8_length(str);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, str, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_right_get_left(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcsrchr(str, split);
|
||||
|
||||
size_t len = t ? t - str : utf16_length(str);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, str, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_right_get_right_include(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strrchr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
|
||||
size_t len = utf8_length(t);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, t, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_right_get_right_include(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcsrchr(str, split);
|
||||
if (!t)
|
||||
return str_utils_copy(str);
|
||||
|
||||
size_t len = utf16_length(t);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, t, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_split_right_get_left_include(const char* str, const char split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strrchr(str, split);
|
||||
if (t)
|
||||
t++;
|
||||
|
||||
size_t len = t ? t - str : utf8_length(str);
|
||||
char* p = force_malloc<char>(len + 1);
|
||||
memcpy(p, str, len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_split_right_get_left_include(const wchar_t* str, const wchar_t split) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcsrchr(str, split);
|
||||
if (t)
|
||||
t++;
|
||||
|
||||
size_t len = t ? t - str : utf16_length(str);
|
||||
wchar_t* p = force_malloc<wchar_t>(len + 1);
|
||||
memcpy(p, str, sizeof(wchar_t) * len);
|
||||
p[len] = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
char* str_utils_get_extension(const char* str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strrchr(str, '\\');
|
||||
return str_utils_split_right_get_right_include(t ? t + 1 : str, '.');
|
||||
}
|
||||
|
||||
wchar_t* str_utils_get_extension(const wchar_t* str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcsrchr(str, L'\\');
|
||||
return str_utils_split_right_get_right_include(t ? t + 1 : str, L'.');
|
||||
}
|
||||
|
||||
char* str_utils_get_without_extension(const char* str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const char* t = strrchr(str, '\\');
|
||||
return str_utils_split_right_get_left(t ? t + 1 : str, '.');
|
||||
}
|
||||
|
||||
wchar_t* str_utils_get_without_extension(const wchar_t* str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
const wchar_t* t = wcsrchr(str, L'\\');
|
||||
return str_utils_split_right_get_left(t ? t + 1 : str, L'.');
|
||||
}
|
||||
|
||||
char* str_utils_add(const char* str0, const char* str1) {
|
||||
if (str0 && str1) {
|
||||
size_t str0_len = utf8_length(str0);
|
||||
size_t str1_len = utf8_length(str1);
|
||||
char* p = force_malloc<char>(str0_len + str1_len + 1);
|
||||
memcpy(p, str0, str0_len + 1);
|
||||
memcpy(p + str0_len, str1, str1_len + 1);
|
||||
return p;
|
||||
}
|
||||
else if (str0)
|
||||
return str_utils_copy(str0);
|
||||
else if (str1)
|
||||
return str_utils_copy(str1);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_add(const wchar_t* str0, const wchar_t* str1) {
|
||||
if (str0 && str1) {
|
||||
size_t str0_len = utf16_length(str0);
|
||||
size_t str1_len = utf16_length(str1);
|
||||
wchar_t* p = force_malloc<wchar_t>(str0_len + str1_len + 1);
|
||||
memcpy(p, str0, sizeof(wchar_t) * (str0_len + 1));
|
||||
memcpy(p + str0_len, str1, sizeof(wchar_t) * (str1_len + 1));
|
||||
return p;
|
||||
}
|
||||
else if (str0)
|
||||
return str_utils_copy(str0);
|
||||
else if (str1)
|
||||
return str_utils_copy(str1);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* str_utils_copy(const char* str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
size_t len = utf8_length(str) + 1;
|
||||
char* p = force_malloc<char>(len);
|
||||
memcpy(p, str, len);
|
||||
return p;
|
||||
}
|
||||
|
||||
wchar_t* str_utils_copy(const wchar_t* str) {
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
size_t len = utf16_length(str) + 1;
|
||||
wchar_t* p = force_malloc<wchar_t>(len);
|
||||
memcpy(p, str, sizeof(wchar_t) * len);
|
||||
return p;
|
||||
}
|
||||
|
||||
inline int32_t str_utils_compare_length(const char* str0, size_t str0_len, const char* str1, size_t str1_len) {
|
||||
if (!str0_len)
|
||||
return -*str1;
|
||||
else if (!str1_len)
|
||||
return *str0;
|
||||
|
||||
size_t str0_len_act = str0_len;
|
||||
const char* i0 = str0;
|
||||
for (size_t i = str0_len; i; i--)
|
||||
if (!*i0++) {
|
||||
str0_len_act = i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t str1_len_act = str1_len;
|
||||
const char* i1 = str1;
|
||||
for (size_t i = str1_len; i; i--)
|
||||
if (!*i1++) {
|
||||
str1_len_act = i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
str0_len = str0_len_act;
|
||||
str1_len = str1_len_act;
|
||||
|
||||
int32_t diff = 0;
|
||||
char c0;
|
||||
char c1;
|
||||
do {
|
||||
c0 = *str0++;
|
||||
c1 = *str1++;
|
||||
if (!c0 || !c1)
|
||||
return c0 - c1;
|
||||
} while (c0 == c1 && --str0_len && --str1_len);
|
||||
return c0 - c1;
|
||||
}
|
||||
|
||||
inline int32_t str_utils_compare_length(const wchar_t* str0, size_t str0_len, const wchar_t* str1, size_t str1_len) {
|
||||
if (!str0_len)
|
||||
return -*str1;
|
||||
else if (!str1_len)
|
||||
return *str0;
|
||||
|
||||
size_t str0_len_act = str0_len;
|
||||
const wchar_t* i0 = str0;
|
||||
for (size_t i = str0_len; i; i--)
|
||||
if (!*i0++) {
|
||||
str0_len_act = i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t str1_len_act = str1_len;
|
||||
const wchar_t* i1 = str1;
|
||||
for (size_t i = str1_len; i; i--)
|
||||
if (!*i1++) {
|
||||
str1_len_act = i + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
str0_len = str0_len_act;
|
||||
str1_len = str1_len_act;
|
||||
|
||||
int32_t diff = 0;
|
||||
wchar_t c0;
|
||||
wchar_t c1;
|
||||
do {
|
||||
c0 = *str0++;
|
||||
c1 = *str1++;
|
||||
if (!c0 || !c1)
|
||||
return c0 - c1;
|
||||
} while (c0 == c1 && --str0_len && --str1_len);
|
||||
return c0 - c1;
|
||||
}
|
||||
|
||||
size_t str_utils_get_substring_offset(const char* str0, size_t str0_len,
|
||||
size_t str0_off, const char* str1, size_t str1_len) {
|
||||
if (!str1_len && str0_off <= str0_len)
|
||||
return str0_off;
|
||||
|
||||
if (str0_off < str0_len && str1_len <= str0_len - str0_off) {
|
||||
size_t len = str0_len - str1_len - str0_off + 1;
|
||||
const char* str = &str0[str0_off];
|
||||
for (; len; ) {
|
||||
const char* s = (const char*)memchr(str, *str1, len);
|
||||
if (!s)
|
||||
break;
|
||||
|
||||
if (!str1_len || !memcmp(s, str1, str1_len))
|
||||
return s - str0;
|
||||
|
||||
len += str - (s + 1);
|
||||
str = s + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t str_utils_get_substring_offset(const wchar_t* str0, size_t str0_len,
|
||||
size_t str0_off, const wchar_t* str1, size_t str1_len) {
|
||||
if (!str1_len && str0_off <= str0_len)
|
||||
return str0_off;
|
||||
|
||||
if (str0_off < str0_len && str1_len <= str0_len - str0_off) {
|
||||
size_t len = str0_len - str1_len - str0_off + 1;
|
||||
const wchar_t* str = &str0[str0_off];
|
||||
for (; len; ) {
|
||||
const wchar_t* s = wmemchr(str, *str1, len);
|
||||
if (!s)
|
||||
break;
|
||||
|
||||
if (!str1_len || !memcmp(s, str1, sizeof(wchar_t) * str1_len))
|
||||
return s - str0;
|
||||
|
||||
len += str - (s + 1);
|
||||
str = s + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool str_utils_text_file_parse(const void* data, size_t size,
|
||||
char*& buf, char**& lines, size_t& count) {
|
||||
if (!data || !size)
|
||||
return false;
|
||||
|
||||
const char* d = (const char*)data;
|
||||
bool del = false;
|
||||
size_t c;
|
||||
buf = 0;
|
||||
lines = 0;
|
||||
count = 0;
|
||||
if ((uint8_t)d[0] == 0x00)
|
||||
return false;
|
||||
else if (d[0] == 0xFF) {
|
||||
if (size == 1 || (uint8_t)d[1] != 0xFE || size == 2)
|
||||
return false;
|
||||
|
||||
wchar_t* w_d = (wchar_t*)data + 1;
|
||||
d = utf16_to_utf8(w_d);
|
||||
size = utf8_length(d);
|
||||
del = true;
|
||||
goto decode_utf8_ansi;
|
||||
}
|
||||
else if ((uint8_t)d[0] == 0xFE) {
|
||||
if (size == 1 || (uint8_t)d[1] != 0xFF || size == 2)
|
||||
return false;
|
||||
|
||||
size /= 2;
|
||||
wchar_t* w_d = (wchar_t*)data + 1;
|
||||
w_d = str_utils_copy(w_d);
|
||||
for (size_t i = 0; i < size; i++)
|
||||
w_d[i] = (wchar_t)reverse_endianness_uint16_t((uint16_t)w_d[i]);
|
||||
d = utf16_to_utf8(w_d);
|
||||
size = utf8_length(d);
|
||||
del = true;
|
||||
free_def(w_d);
|
||||
goto decode_utf8_ansi;
|
||||
}
|
||||
else if ((uint8_t)d[0] == 0xEF) {
|
||||
if (size == 1 || (uint8_t)d[1] != 0xBB || size == 2 || (uint8_t)d[2] != 0xBF || size == 3)
|
||||
return false;
|
||||
|
||||
d += 3;
|
||||
size -= 3;
|
||||
goto decode_utf8_ansi;
|
||||
}
|
||||
else {
|
||||
decode_utf8_ansi:
|
||||
c = 1;
|
||||
bool lf;
|
||||
char ch;
|
||||
const char* t;
|
||||
lf = false;
|
||||
t = d;
|
||||
ch = 0;
|
||||
|
||||
size_t buf_len = size;
|
||||
for (size_t i = 0, l = 0, m = 0; i < size; i++) {
|
||||
ch = *t++;
|
||||
if (ch == '\r') {
|
||||
if (i + 1 < size && *t == '\n') {
|
||||
i++;
|
||||
t++;
|
||||
l++;
|
||||
}
|
||||
lf = true;
|
||||
}
|
||||
else if (ch == '\n')
|
||||
lf = true;
|
||||
|
||||
if (lf) {
|
||||
if (!l && c > 1)
|
||||
buf_len--;
|
||||
c++;
|
||||
l = 0;
|
||||
m = 0;
|
||||
lf = false;
|
||||
}
|
||||
else {
|
||||
l++;
|
||||
m++;
|
||||
}
|
||||
}
|
||||
|
||||
if (ch != '\r' && ch != '\n')
|
||||
buf_len++;
|
||||
else
|
||||
c--;
|
||||
|
||||
lf = false;
|
||||
t = d;
|
||||
char* temp_buf = force_malloc<char>(buf_len);
|
||||
char** temp_lines = force_malloc<char*>(c);
|
||||
|
||||
char* b = temp_buf;
|
||||
for (size_t i = 0, j = 0, l = 0, m = 0; j < c; i++) {
|
||||
ch = *t++;
|
||||
if (ch == '\r') {
|
||||
if (i + 1 < size && *t == '\n') {
|
||||
i++;
|
||||
t++;
|
||||
l++;
|
||||
}
|
||||
lf = true;
|
||||
}
|
||||
else if (ch == '\n')
|
||||
lf = true;
|
||||
|
||||
if (i >= size || lf) {
|
||||
temp_lines[j] = b;
|
||||
if (l) {
|
||||
memcpy(b, d + i - l, m);
|
||||
b[l] = 0;
|
||||
b += l + 1;
|
||||
}
|
||||
else if (j)
|
||||
temp_lines[j]--;
|
||||
else
|
||||
*b++ = 0;
|
||||
j++;
|
||||
|
||||
if (!lf)
|
||||
break;
|
||||
|
||||
l = 0;
|
||||
m = 0;
|
||||
lf = false;
|
||||
}
|
||||
else {
|
||||
l++;
|
||||
m++;
|
||||
}
|
||||
}
|
||||
|
||||
buf = temp_buf;
|
||||
lines = temp_lines;
|
||||
count = c;
|
||||
}
|
||||
|
||||
if (del) {
|
||||
void* data = (void*)d;
|
||||
free_def(data);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user