827 lines
25 KiB
C++
827 lines
25 KiB
C++
// Copyright (c) 2023, AgiBot Inc.
|
||
// All rights reserved.
|
||
|
||
#pragma once
|
||
|
||
#include <algorithm>
|
||
#include <charconv>
|
||
#include <cstdint>
|
||
#include <cstring>
|
||
#include <iomanip>
|
||
#include <map>
|
||
#include <regex>
|
||
#include <set>
|
||
#include <sstream>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
namespace aimrt::common::util {
|
||
|
||
/**
|
||
* @brief 修剪字符串
|
||
*
|
||
* @param[inout] s 待处理字符串
|
||
* @return std::string& 处理后的字符串s的引用
|
||
*/
|
||
inline std::string& Trim(std::string& s) {
|
||
if (s.empty()) return s;
|
||
s.erase(s.find_last_not_of(' ') + 1);
|
||
s.erase(0, s.find_first_not_of(' '));
|
||
return s;
|
||
}
|
||
|
||
inline std::string_view& Trim(std::string_view& s) {
|
||
if (s.empty()) return s;
|
||
auto p = s.find_last_not_of(' ');
|
||
s = s.substr(0, (p != std::string_view::npos) ? (p + 1) : 0);
|
||
p = s.find_first_not_of(' ');
|
||
s = s.substr((p != std::string_view::npos) ? p : s.size());
|
||
return s;
|
||
}
|
||
|
||
/**
|
||
* @brief 将类似于a=1&b=2&c=3这样的字符串解析到map中
|
||
* @note
|
||
* 分割原则:在两个vsep之间的子字符串,如果包含msep,则以msep为界分为key、val
|
||
* @param[in] source 待分割字符串
|
||
* @param[in] vsep 多个kv之间的分隔符,不可为空
|
||
* @param[in] msep 单个kv内部的分隔符,不可为空
|
||
* @param[in] trim 是否去除k和v里的空格
|
||
* @param[in] clear 是否去除key为空的情况
|
||
* @return std::map<std::string, std::string> 解析后的map
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::map<StringType, StringType> SplitToMap(std::string_view source,
|
||
std::string_view vsep = "&",
|
||
std::string_view msep = "=",
|
||
bool trim = true,
|
||
bool clear = true) {
|
||
std::map<StringType, StringType> result;
|
||
|
||
if (source.empty() || vsep.empty() || msep.empty() || vsep == msep)
|
||
return result;
|
||
size_t v_pos_end = 0, v_pos_start = 0;
|
||
do {
|
||
v_pos_end = source.find(vsep, v_pos_start);
|
||
if (v_pos_end == std::string_view::npos) v_pos_end = source.length();
|
||
if (v_pos_end > v_pos_start) {
|
||
auto kv_str = source.substr(v_pos_start, v_pos_end - v_pos_start);
|
||
size_t msep_pos = kv_str.find(msep);
|
||
if (msep_pos != std::string_view::npos) {
|
||
auto first = kv_str.substr(0, msep_pos);
|
||
auto second = kv_str.substr(msep_pos + msep.size());
|
||
if (trim) {
|
||
Trim(first);
|
||
if (!(clear && first.empty())) {
|
||
result[StringType(first)] = StringType(Trim(second));
|
||
}
|
||
} else {
|
||
if (!(clear && first.empty())) {
|
||
result[StringType(first)] = StringType(second);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
v_pos_start = v_pos_end + vsep.size();
|
||
} while (v_pos_end < source.length());
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 拼接map为a=1&b=2&c=3形式的string
|
||
*
|
||
* @param[in] kvmap std::map<std::string,std::string>结构体
|
||
* @param[in] vsep 多个kv之间的分隔符
|
||
* @param[in] msep 单个kv内部的分隔符
|
||
* @return std::string 拼接后的字符串
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::string JoinMap(const std::map<StringType, StringType>& kvmap,
|
||
std::string_view vsep = "&",
|
||
std::string_view msep = "=") {
|
||
std::stringstream result;
|
||
for (auto itr = kvmap.begin(); itr != kvmap.end(); ++itr) {
|
||
if (itr != kvmap.begin()) result << vsep;
|
||
result << itr->first << msep << itr->second;
|
||
}
|
||
return result.str();
|
||
}
|
||
|
||
/**
|
||
* @brief 带默认值的获取map<std::string,std::string>中val数据
|
||
*
|
||
* @param[in] m std::map<std::string,std::string>结构体
|
||
* @param[in] key 要获取数据的key
|
||
* @param[in] defval 默认字符串,当m中没有对应的key时,返回defval
|
||
* @return std::string 结果
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline const StringType& GetMapItemWithDef(const std::map<StringType, StringType>& m,
|
||
const StringType& key,
|
||
const StringType& defval = "") {
|
||
auto finditr = m.find(key);
|
||
return (finditr != m.end()) ? (finditr->second) : defval;
|
||
}
|
||
|
||
/**
|
||
* @brief 向s中添加kv字段
|
||
*
|
||
* @param[inout] s 待处理字符串
|
||
* @param[in] key key
|
||
* @param[in] val val
|
||
* @param[in] vsep 多个kv之间的分隔符
|
||
* @param[in] msep 单个kv内部的分隔符
|
||
* @return std::string& 拼接后的字符串s的引用
|
||
*/
|
||
inline std::string& AddKV(std::string& s,
|
||
std::string_view key,
|
||
std::string_view val,
|
||
std::string_view vsep = "&",
|
||
std::string_view msep = "=") {
|
||
if (!s.empty()) s += vsep;
|
||
s += key;
|
||
s += msep;
|
||
s += val;
|
||
return s;
|
||
}
|
||
|
||
/**
|
||
* @brief 从类似于a=1&b=2&c=3这样的字符串中得到key对应的val
|
||
* @note
|
||
* 比直接分割成map再find要快一些,但如果有多次调用,还是建议先分割成map再find
|
||
* @param[in] str 待处理字符串
|
||
* @param[in] key key,不可为空,不能包含vsep或msep,否则返回空结果
|
||
* @param[in] vsep 多个kv之间的分隔符,不可为空
|
||
* @param[in] msep 单个kv内部的分隔符,不可为空
|
||
* @param[in] trim 是否不计空格
|
||
* @return std::string key对应的val
|
||
*/
|
||
inline std::string_view GetValueFromStrKV(std::string_view str,
|
||
std::string_view key,
|
||
std::string_view vsep = "&",
|
||
std::string_view msep = "=",
|
||
bool trim = true) {
|
||
if (key.empty() || vsep.empty() || msep.empty() || vsep == msep) return "";
|
||
if (key.find(vsep) != std::string_view::npos || key.find(msep) != std::string_view::npos)
|
||
return "";
|
||
|
||
size_t key_pos = str.find(key);
|
||
while (key_pos != std::string_view::npos) {
|
||
size_t msep_pos = str.find(msep, key_pos + key.size());
|
||
if (msep_pos == std::string_view::npos) return "";
|
||
|
||
size_t key_start_pos = str.rfind(vsep, key_pos);
|
||
if (key_start_pos == std::string_view::npos)
|
||
key_start_pos = 0;
|
||
else
|
||
key_start_pos += vsep.length();
|
||
|
||
bool right_key_flag = false;
|
||
if (trim) {
|
||
auto real_key = str.substr(key_start_pos, msep_pos - key_start_pos);
|
||
Trim(real_key);
|
||
if (real_key == key) right_key_flag = true;
|
||
} else {
|
||
if ((key_start_pos == key_pos) && (msep_pos == (key_pos + key.size())))
|
||
right_key_flag = true;
|
||
}
|
||
|
||
if (right_key_flag) {
|
||
size_t val_start_pos = msep_pos + msep.size();
|
||
size_t val_end_pos = str.find(vsep, val_start_pos);
|
||
if (val_end_pos == std::string_view::npos) val_end_pos = str.length();
|
||
|
||
if (trim) {
|
||
auto re = str.substr(val_start_pos, val_end_pos - val_start_pos);
|
||
return Trim(re);
|
||
}
|
||
return str.substr(val_start_pos, val_end_pos - val_start_pos);
|
||
}
|
||
|
||
key_pos = str.find(key, msep_pos + msep.length());
|
||
}
|
||
return "";
|
||
}
|
||
|
||
/**
|
||
* @brief 分割字符串到vector
|
||
*
|
||
* @param[in] source 待处理字符串
|
||
* @param[in] sep 分隔符
|
||
* @param[in] trim 是否对每项结果去除空格
|
||
* @param[in] clear 是否去除空项
|
||
* @return std::vector<std::string> 分割结果
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::vector<StringType> SplitToVec(std::string_view source,
|
||
std::string_view sep,
|
||
bool trim = true,
|
||
bool clear = true) {
|
||
std::vector<StringType> result;
|
||
if (source.empty() || sep.empty()) return result;
|
||
|
||
size_t pos_end = 0, pos_start = 0;
|
||
do {
|
||
pos_end = source.find(sep, pos_start);
|
||
if (pos_end == std::string_view::npos) pos_end = source.length();
|
||
|
||
auto sub_str = source.substr(pos_start, pos_end - pos_start);
|
||
|
||
if (trim) Trim(sub_str);
|
||
|
||
if (!(clear && sub_str.empty())) result.emplace_back(sub_str);
|
||
|
||
pos_start = pos_end + sep.size();
|
||
} while (pos_end < source.length());
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 拼接vector到string
|
||
*
|
||
* @param[in] vec 待处理vector
|
||
* @param[in] sep 分隔符
|
||
* @return std::string 拼接后的字符串
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::string JoinVec(const std::vector<StringType>& vec, std::string_view sep) {
|
||
std::string result;
|
||
for (auto itr = vec.begin(); itr != vec.end(); ++itr) {
|
||
if (itr != vec.begin()) result += sep;
|
||
result += *itr;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 分割字符串到set,自动去重
|
||
*
|
||
* @param[in] source 待处理字符串
|
||
* @param[in] sep 分隔符
|
||
* @param[in] trim 是否对每项结果去除空格
|
||
* @param[in] clear 是否去除空项
|
||
* @return std::set<std::string> 分割结果
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::set<StringType> SplitToSet(std::string_view source,
|
||
std::string_view sep,
|
||
bool trim = true,
|
||
bool clear = true) {
|
||
std::set<StringType> result;
|
||
if (source.empty() || sep.empty()) return result;
|
||
|
||
size_t pos_end = 0, pos_start = 0;
|
||
do {
|
||
pos_end = source.find(sep, pos_start);
|
||
if (pos_end == std::string_view::npos) pos_end = source.length();
|
||
|
||
auto sub_str = source.substr(pos_start, pos_end - pos_start);
|
||
|
||
if (trim) Trim(sub_str);
|
||
|
||
if (!(clear && sub_str.empty())) result.emplace(sub_str);
|
||
|
||
pos_start = pos_end + sep.size();
|
||
} while (pos_end < source.length());
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 拼接set到string
|
||
*
|
||
* @param[in] st 待处理set
|
||
* @param[in] sep 分隔符
|
||
* @return std::string 拼接后的字符串
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::string JoinSet(const std::set<StringType>& st, std::string_view sep) {
|
||
std::string result;
|
||
for (auto itr = st.begin(); itr != st.end(); ++itr) {
|
||
if (itr != st.begin()) result += sep;
|
||
result += *itr;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 判断key字符串是否在list字符串中,如"123"是否在"123,456,789"中
|
||
* @note
|
||
* 比直接分割成vector再find要快一些,但如果有多次调用,还是建议先分割成vector再find
|
||
* @param[in] str list字符串
|
||
* @param[in] key key字符串,不可为空,不能包含sep
|
||
* @param[in] sep list的分隔符,不可为空
|
||
* @param[in] trim 是否不计空格
|
||
* @return true key在list中
|
||
* @return false key不在list中
|
||
*/
|
||
inline bool CheckIfInList(std::string_view str,
|
||
std::string_view key,
|
||
std::string_view sep = ",",
|
||
bool trim = true) {
|
||
if (key.empty() || sep.empty()) return false;
|
||
if (key.find(sep) != std::string_view::npos) return false;
|
||
|
||
size_t key_pos = str.find(key);
|
||
while (key_pos != std::string_view::npos) {
|
||
size_t key_start_pos = str.rfind(sep, key_pos);
|
||
if (key_start_pos == std::string_view::npos)
|
||
key_start_pos = 0;
|
||
else
|
||
key_start_pos += sep.length();
|
||
|
||
size_t key_end_pos = str.find(sep, key_pos + key.length());
|
||
if (key_end_pos == std::string_view::npos) key_end_pos = str.length();
|
||
|
||
if (trim) {
|
||
auto real_key = str.substr(key_start_pos, key_end_pos - key_start_pos);
|
||
Trim(real_key);
|
||
if (real_key == key) return true;
|
||
} else {
|
||
if ((key_start_pos == key_pos) && (key_end_pos == (key_pos + key.size())))
|
||
return true;
|
||
}
|
||
|
||
key_pos = str.find(key, key_end_pos + sep.length());
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* @brief 比较版本,如6.1.1 6.2.8
|
||
*
|
||
* @param[in] ver1 版本1
|
||
* @param[in] ver2 版本2
|
||
* @return int 返回1是大于,返回0是相等,返回-1是小于
|
||
*/
|
||
inline int CmpVersion(std::string_view ver1, std::string_view ver2) {
|
||
const auto& version1_detail =
|
||
SplitToVec<std::string_view>(ver1, ".", true, true);
|
||
const auto& version2_detail =
|
||
SplitToVec<std::string_view>(ver2, ".", true, true);
|
||
|
||
size_t idx = 0;
|
||
for (idx = 0; idx < version1_detail.size() && idx < version2_detail.size(); ++idx) {
|
||
int ver1_num = 0, ver2_num = 0;
|
||
auto [ptr1, ec1] = std::from_chars(version1_detail[idx].data(),
|
||
version1_detail[idx].data() + version1_detail[idx].size(),
|
||
ver1_num);
|
||
auto [ptr2, ec2] = std::from_chars(version2_detail[idx].data(),
|
||
version2_detail[idx].data() + version2_detail[idx].size(),
|
||
ver2_num);
|
||
if (ec1 != std::errc() || ec2 != std::errc() ||
|
||
ptr1 != version1_detail[idx].data() + version1_detail[idx].size() ||
|
||
ptr2 != version2_detail[idx].data() + version2_detail[idx].size()) {
|
||
throw std::runtime_error("Invalid version format");
|
||
}
|
||
if (ver1_num < ver2_num)
|
||
return -1;
|
||
if (ver1_num > ver2_num)
|
||
return 1;
|
||
}
|
||
if (idx == version1_detail.size() && idx == version2_detail.size()) {
|
||
return 0;
|
||
}
|
||
return version1_detail.size() > version2_detail.size() ? 1 : -1;
|
||
}
|
||
|
||
/**
|
||
* @brief 检查版本是否处于设定版本之间
|
||
* @note 需要保证start_ver<end_ver。若end_ver空,则设为999.9.9.9
|
||
* @param[in] ver 待检查版本
|
||
* @param[in] start_ver 开始版本
|
||
* @param[in] end_ver 结束版本
|
||
* @return true 在传入的版本之间
|
||
* @return false 不在传入的版本之间
|
||
*/
|
||
inline bool CheckVersionInside(std::string_view ver,
|
||
std::string_view start_ver,
|
||
std::string_view end_ver) {
|
||
return (CmpVersion(ver, start_ver.empty() ? "0.0.0.0" : start_ver) >= 0 &&
|
||
CmpVersion(ver, end_ver.empty() ? "999.9.9.9" : end_ver) <= 0);
|
||
}
|
||
|
||
/**
|
||
* @brief 将一个字符串中指定字符串ov换为字符串nv
|
||
*
|
||
* @param[inout] str 待替换字符串
|
||
* @param[in] ov 要被替换的子字符串
|
||
* @param[in] nv 要替换成的子字符串
|
||
* @return std::string& 替换后的字符串str的引用
|
||
*/
|
||
inline std::string& ReplaceString(std::string& str,
|
||
std::string_view ov,
|
||
std::string_view nv) {
|
||
if (str.empty() || ov.empty()) return str;
|
||
std::vector<size_t> vec_pos;
|
||
size_t pos = 0, old_len = ov.size(), new_len = nv.size();
|
||
while ((pos = str.find(ov, pos)) != std::string::npos) {
|
||
vec_pos.emplace_back(pos);
|
||
pos += old_len;
|
||
}
|
||
size_t& vec_len = pos = vec_pos.size();
|
||
if (vec_len) {
|
||
if (old_len == new_len) {
|
||
for (size_t ii = 0; ii < vec_len; ++ii)
|
||
memcpy(const_cast<char*>(str.c_str() + vec_pos[ii]), nv.data(),
|
||
new_len);
|
||
} else if (old_len > new_len) {
|
||
char* p = const_cast<char*>(str.c_str()) + vec_pos[0];
|
||
vec_pos.emplace_back(str.size());
|
||
for (size_t ii = 0; ii < vec_len; ++ii) {
|
||
memcpy(p, nv.data(), new_len);
|
||
p += new_len;
|
||
size_t cplen = vec_pos[ii + 1] - vec_pos[ii] - old_len;
|
||
memmove(p, str.c_str() + vec_pos[ii] + old_len, cplen);
|
||
p += cplen;
|
||
}
|
||
str.resize(p - str.c_str());
|
||
} else {
|
||
size_t diff = new_len - old_len;
|
||
vec_pos.emplace_back(str.size());
|
||
str.resize(str.size() + diff * vec_len);
|
||
char* p = const_cast<char*>(str.c_str()) + str.size();
|
||
for (size_t ii = vec_len - 1; ii < vec_len; --ii) {
|
||
size_t cplen = vec_pos[ii + 1] - vec_pos[ii] - old_len;
|
||
p -= cplen;
|
||
memmove(p, str.c_str() + vec_pos[ii] + old_len, cplen);
|
||
p -= new_len;
|
||
memcpy(p, nv.data(), new_len);
|
||
}
|
||
}
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/**
|
||
* @brief 判断字符串是否为数字和字母组成
|
||
*
|
||
* @param[in] str 待判断字符串
|
||
* @return true 全部为数字和字母组成
|
||
* @return false 不全为数字和字母组成
|
||
*/
|
||
inline bool IsAlnumStr(std::string_view str) {
|
||
if (str.length() == 0) return false;
|
||
for (const auto& c : str) {
|
||
if (!isalnum(c)) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 判断字符串是否为数字组成
|
||
*
|
||
* @param[in] str 待判断字符串
|
||
* @return true 全部为数字组成
|
||
* @return false 不全为数字组成
|
||
*/
|
||
inline bool IsDigitStr(std::string_view str) {
|
||
if (str.length() == 0) return false;
|
||
for (const auto& c : str) {
|
||
if (c > '9' || c < '0') return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取map中的key的集合
|
||
*
|
||
* @tparam KeyType map中key的类型
|
||
* @tparam ValType map中val的类型
|
||
* @param m 输入map
|
||
* @return std::set<KeyType> key的集合
|
||
*/
|
||
template <typename KeyType, typename ValType>
|
||
inline std::set<KeyType> GetMapKeys(const std::map<KeyType, ValType>& m) {
|
||
std::set<KeyType> re;
|
||
for (const auto& it : m) re.emplace(it.first);
|
||
return re;
|
||
}
|
||
|
||
/**
|
||
* @brief 画一个表格
|
||
*
|
||
* @param table 表格数据
|
||
* @param with_header 是否要画表头
|
||
* @return std::string 结果字符串,可以直接打印
|
||
*/
|
||
template <class StringType = std::string_view>
|
||
requires(std::is_same_v<StringType, std::string_view> ||
|
||
std::is_same_v<StringType, std::string>)
|
||
inline std::string DrawTable(
|
||
const std::vector<std::vector<StringType>>& table, bool with_header = true) {
|
||
size_t row = table.size();
|
||
if (row == 0) return "<empty table>";
|
||
|
||
std::vector<size_t> column_width;
|
||
|
||
for (auto& row_item : table) {
|
||
if (column_width.size() < row_item.size())
|
||
column_width.resize(row_item.size());
|
||
|
||
for (size_t ii = 0; ii < row_item.size(); ++ii)
|
||
column_width[ii] = std::max(column_width[ii], row_item[ii].size());
|
||
}
|
||
|
||
for (auto& item : column_width)
|
||
item += 2;
|
||
|
||
size_t column = column_width.size();
|
||
|
||
std::stringstream result;
|
||
|
||
// first line
|
||
for (size_t jj = 0; jj < column; ++jj) {
|
||
result << "+" << std::setfill('-') << std::setw(column_width[jj]) << "-";
|
||
}
|
||
result << "+\n";
|
||
|
||
for (size_t ii = 0; ii < row; ++ii) {
|
||
for (size_t jj = 0; jj < column; ++jj) {
|
||
std::string cur_data = " ";
|
||
|
||
if (table[ii].size() > jj)
|
||
cur_data += table[ii][jj];
|
||
|
||
result << "|" << std::left << std::setfill(' ') << std::setw(column_width[jj]) << cur_data;
|
||
}
|
||
result << "|\n";
|
||
|
||
// table header
|
||
if (with_header && ii == 0) {
|
||
for (size_t jj = 0; jj < column; ++jj) {
|
||
result << "+" << std::setfill('-') << std::setw(column_width[jj]) << "-";
|
||
}
|
||
result << "+\n";
|
||
}
|
||
}
|
||
|
||
// last line
|
||
for (size_t jj = 0; jj < column; ++jj) {
|
||
result << "+" << std::setfill('-') << std::setw(column_width[jj]) << "-";
|
||
}
|
||
result << "+\n";
|
||
|
||
return result.str();
|
||
}
|
||
|
||
/**
|
||
* @brief 将字符串中形如 ${XXX_ENV} 的部分替换为对应环境变量的值
|
||
*
|
||
* @param input
|
||
* @return std::string
|
||
*/
|
||
inline std::string ReplaceEnvVars(std::string_view input) {
|
||
std::regex pattern(R"(\$\{([^}]+)\})");
|
||
std::smatch match;
|
||
std::string result(input);
|
||
size_t cur_pos = 0;
|
||
|
||
while (std::regex_search(result.cbegin() + cur_pos, result.cend(), match, pattern)) {
|
||
std::string env_name = match[1].str();
|
||
const char* env_val = std::getenv(env_name.c_str());
|
||
if (env_val == nullptr) env_val = "";
|
||
|
||
result.replace(cur_pos + match.position(0), match.length(0), env_val);
|
||
|
||
cur_pos += (match.position(0) + strlen(env_val));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 字符转小写
|
||
*
|
||
* @param c 字符
|
||
* @return char 小写字符
|
||
*/
|
||
inline char CharToLower(char c) { return static_cast<char>(tolower(c)); }
|
||
|
||
/**
|
||
* @brief 字符转大写
|
||
*
|
||
* @param c 字符
|
||
* @return char 大写字符
|
||
*/
|
||
inline char CharToUpper(char c) { return static_cast<char>(toupper(c)); }
|
||
|
||
/**
|
||
* @brief 字符串转小写
|
||
*
|
||
* @param str 字符串
|
||
* @return std::string& 小写字符串
|
||
*/
|
||
inline std::string& StrToLower(std::string& str) {
|
||
std::transform(str.begin(), str.end(), str.begin(), CharToLower);
|
||
return str;
|
||
}
|
||
|
||
/**
|
||
* @brief 字符串转小写
|
||
*
|
||
* @param str 字符串
|
||
* @return std::string 小写字符串
|
||
*/
|
||
inline std::string StrToLower(std::string_view str) {
|
||
std::string result;
|
||
result.resize(str.size());
|
||
|
||
std::transform(str.begin(), str.end(), result.begin(), CharToLower);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 字符串转大写
|
||
*
|
||
* @param str 字符串
|
||
* @return std::string& 大写字符串
|
||
*/
|
||
inline std::string& StrToUpper(std::string& str) {
|
||
std::transform(str.begin(), str.end(), str.begin(), CharToUpper);
|
||
return str;
|
||
}
|
||
|
||
/**
|
||
* @brief 字符串转大写
|
||
*
|
||
* @param str 字符串
|
||
* @return std::string 大写字符串
|
||
*/
|
||
inline std::string StrToUpper(std::string_view str) {
|
||
std::string result;
|
||
result.resize(str.size());
|
||
|
||
std::transform(str.begin(), str.end(), result.begin(), CharToUpper);
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* @brief 比较两个字符串忽略大小写后是否相等
|
||
*
|
||
* @param str1
|
||
* @param str2
|
||
* @return true
|
||
* @return false
|
||
*/
|
||
inline bool CheckIEqual(std::string_view str1, std::string_view str2) {
|
||
if (str1.size() != str2.size()) return false;
|
||
|
||
return std::equal(str1.begin(), str1.end(), str2.begin(),
|
||
[](char a, char b) { return std::tolower(a) == std::tolower(b); });
|
||
}
|
||
|
||
/**
|
||
* @brief 字符串转为标题形式,单词首字符大写
|
||
*
|
||
* @param str 字符串
|
||
* @return std::string& 标题形式字符串
|
||
*/
|
||
inline std::string& StrToTitleCase(std::string& str) {
|
||
std::string::iterator it = str.begin();
|
||
*it = CharToUpper(*it);
|
||
for (; it != str.end() - 1; ++it) {
|
||
if (*it == ' ') {
|
||
*(it + 1) = CharToUpper(*(it + 1));
|
||
}
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/**
|
||
* @brief standardise a path string
|
||
*
|
||
* @param path
|
||
* @return std::string&
|
||
*/
|
||
inline std::string& StandardisePath(std::string& path) {
|
||
if (path.empty()) return (path = "/");
|
||
std::replace(path.begin(), path.end(), '\\', '/');
|
||
if (path[path.length() - 1] != '/') path += '/';
|
||
return path;
|
||
}
|
||
|
||
/**
|
||
* @brief judge if str start with pattern
|
||
*
|
||
* @param str
|
||
* @param pattern
|
||
* @param ignore_case if ignore the case
|
||
* @return true
|
||
* @return false
|
||
*/
|
||
inline bool StartsWith(std::string_view str,
|
||
std::string_view pattern,
|
||
bool ignore_case = false) {
|
||
const size_t str_len = str.length();
|
||
const size_t pattern_len = pattern.length();
|
||
if (str_len < pattern_len || pattern_len == 0) return false;
|
||
|
||
if (ignore_case) {
|
||
for (size_t i = 0; i < pattern_len; ++i) {
|
||
if (tolower(pattern[i]) != tolower(str[i])) return false;
|
||
}
|
||
return true;
|
||
}
|
||
for (size_t i = 0; i < pattern_len; ++i) {
|
||
if (pattern[i] != str[i]) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief judge if str end with pattern
|
||
*
|
||
* @param str
|
||
* @param pattern
|
||
* @param ignore_case if ignore the case
|
||
* @return true
|
||
* @return false
|
||
*/
|
||
inline bool EndsWith(std::string_view str,
|
||
std::string_view pattern,
|
||
bool ignore_case = false) {
|
||
const size_t str_len = str.length();
|
||
const size_t pattern_len = pattern.length();
|
||
if (str_len < pattern_len || pattern_len == 0) return false;
|
||
|
||
const size_t& begin_pos = str_len - pattern_len;
|
||
if (ignore_case) {
|
||
for (size_t i = 0; i < pattern_len; ++i) {
|
||
if (tolower(pattern[i]) != tolower(str[begin_pos + i])) return false;
|
||
}
|
||
return true;
|
||
}
|
||
for (size_t i = 0; i < pattern_len; i++) {
|
||
if (pattern[i] != str[begin_pos + i]) return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief fnv1a hash算法,64位
|
||
*
|
||
* @param data
|
||
* @param len
|
||
* @return uint64_t hash值
|
||
*/
|
||
inline uint64_t Hash64Fnv1a(const char* data, size_t len) {
|
||
constexpr uint64_t kPrime = 0x100000001b3;
|
||
uint64_t hash = 0xcbf29ce484222325;
|
||
for (size_t i = 0; i < len; ++i) {
|
||
hash = (hash ^ static_cast<uint8_t>(data[i])) * kPrime;
|
||
}
|
||
return hash;
|
||
}
|
||
|
||
/**
|
||
* @brief fnv1a hash算法,32位
|
||
*
|
||
* @param data
|
||
* @param len
|
||
* @return uint32_t hash值
|
||
*/
|
||
inline uint32_t Hash32Fnv1a(const char* data, size_t len) {
|
||
constexpr uint32_t kPrime = 0x1000193;
|
||
uint32_t hash = 0x811c9dc5;
|
||
for (size_t i = 0; i < len; ++i) {
|
||
hash = (hash ^ static_cast<uint8_t>(data[i])) * kPrime;
|
||
}
|
||
return hash;
|
||
}
|
||
|
||
template <typename T>
|
||
inline std::string SSToString(const T& obj) {
|
||
std::stringstream ss;
|
||
ss << obj;
|
||
return ss.str();
|
||
}
|
||
|
||
struct StringHash {
|
||
using hash_type = std::hash<std::string_view>;
|
||
using is_transparent = void;
|
||
|
||
std::size_t operator()(const char* str) const { return hash_type{}(str); }
|
||
std::size_t operator()(std::string_view str) const { return hash_type{}(str); }
|
||
std::size_t operator()(std::string const& str) const { return hash_type{}(str); }
|
||
};
|
||
|
||
} // namespace aimrt::common::util
|