blob: c7c48a9c36ff87bc9e9ed17a96e0ee285c7744e0 [file] [log] [blame]
/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Microsoft Corporation
*
* -=- Robust Distributed System Nucleus (rDSN) -=-
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <absl/strings/ascii.h>
#include <openssl/md5.h>
#include <stdio.h>
#include <strings.h>
#include <algorithm>
#include <cstring>
#include <sstream> // IWYU pragma: keep
#include <utility>
#include "utils/fmt_logging.h"
#include "utils/strings.h"
namespace dsn {
namespace utils {
#define CHECK_NULL_PTR(lhs, rhs) \
do { \
if (lhs == nullptr) { \
return rhs == nullptr; \
} \
if (rhs == nullptr) { \
return false; \
} \
} while (0)
bool equals(const char *lhs, const char *rhs)
{
CHECK_NULL_PTR(lhs, rhs);
return std::strcmp(lhs, rhs) == 0;
}
bool equals(const char *lhs, const char *rhs, size_t n)
{
CHECK_NULL_PTR(lhs, rhs);
return std::strncmp(lhs, rhs, n) == 0;
}
bool iequals(const char *lhs, const char *rhs)
{
CHECK_NULL_PTR(lhs, rhs);
return ::strcasecmp(lhs, rhs) == 0;
}
bool iequals(const char *lhs, const char *rhs, size_t n)
{
CHECK_NULL_PTR(lhs, rhs);
return ::strncasecmp(lhs, rhs, n) == 0;
}
bool iequals(const std::string &lhs, const char *rhs)
{
if (rhs == nullptr) {
return false;
}
return ::strcasecmp(lhs.c_str(), rhs) == 0;
}
bool iequals(const std::string &lhs, const char *rhs, size_t n)
{
if (rhs == nullptr) {
return false;
}
return ::strncasecmp(lhs.c_str(), rhs, n) == 0;
}
bool iequals(const char *lhs, const std::string &rhs)
{
if (lhs == nullptr) {
return false;
}
return ::strcasecmp(lhs, rhs.c_str()) == 0;
}
bool iequals(const char *lhs, const std::string &rhs, size_t n)
{
if (lhs == nullptr) {
return false;
}
return ::strncasecmp(lhs, rhs.c_str(), n) == 0;
}
bool mequals(const void *lhs, const void *rhs, size_t n)
{
CHECK_NULL_PTR(lhs, rhs);
return std::memcmp(lhs, rhs, n) == 0;
}
#undef CHECK_NULL_PTR
std::string get_last_component(const std::string &input, const char splitters[])
{
int index = -1;
const char *s = splitters;
while (*s != 0) {
auto pos = input.find_last_of(*s);
if (pos != std::string::npos && (static_cast<int>(pos) > index))
index = static_cast<int>(pos);
s++;
}
if (index != -1)
return input.substr(index + 1);
else
return input;
}
namespace {
// The states while scan each split.
enum class split_args_state : int
{
kSplitBeginning, // The initial state while starting to scan a split
kSplitLeadingSpaces, // While meeting leading spaces, if any
kSplitToken, // While running into token (after leading spaces)
};
const std::string kLeadingSpaces = " \t";
const std::string kTrailingSpaces = " \t\r\n";
inline bool is_leading_space(char ch)
{
return std::any_of(
kLeadingSpaces.begin(), kLeadingSpaces.end(), [ch](char space) { return ch == space; });
}
inline bool is_trailing_space(char ch)
{
return std::any_of(
kTrailingSpaces.begin(), kTrailingSpaces.end(), [ch](char space) { return ch == space; });
}
// Skip trailing spaces and find the end of token.
const char *find_token_end(const char *token_begin, const char *end)
{
CHECK(token_begin < end, "");
for (; end > token_begin && is_trailing_space(*(end - 1)); --end) {
}
return end;
}
// Append new element to sequence containers, such as std::vector and std::list.
struct SequenceInserter
{
// The new element is constructed through variadic template and appended at the end
// of the sequence container.
template <typename SequenceContainer, typename... Args>
void emplace(SequenceContainer &container, Args &&... args) const
{
container.emplace_back(std::forward<Args>(args)...);
}
};
// Insert new element to associative containers, such as std::unordered_set and std::set.
struct AssociativeInserter
{
// The new element is constructed through variadic template and inserted into the associative
// container.
template <typename AssociativeContainer, typename... Args>
void emplace(AssociativeContainer &container, Args &&... args) const
{
container.emplace(std::forward<Args>(args)...);
}
};
// Split the `input` string by the only character `separator` into tokens. Leading and trailing
// spaces of each token will be stripped. Once the token is empty, or become empty after
// stripping, an empty string will be added into `output` if `keep_place_holder` is enabled.
//
// `inserter` provides the only interface for all types of containers. By `inserter`, all tokens
// will be collected to each type of container: for sequence containers, such as std::vector and
// std::list, tokens will be "appended"; for associative containers, such as std::unordered_set
// and std::set, tokens will be "inserted".
template <typename Inserter, typename Container>
void split(const char *input,
char separator,
bool keep_place_holder,
const Inserter &inserter,
Container &output)
{
CHECK_NOTNULL(input, "");
output.clear();
auto state = split_args_state::kSplitBeginning;
const char *token_begin = nullptr;
for (auto p = input;; ++p) {
if (*p == separator || *p == '\0') {
switch (state) {
case split_args_state::kSplitBeginning:
case split_args_state::kSplitLeadingSpaces:
if (keep_place_holder) {
// Will add an empty string as output, since all characters
// in this split are spaces.
inserter.emplace(output);
}
break;
case split_args_state::kSplitToken: {
auto token_end = find_token_end(token_begin, p);
CHECK(token_begin <= token_end, "");
if (token_begin == token_end && !keep_place_holder) {
// Current token is empty, and place holder is not needed.
break;
}
inserter.emplace(output, token_begin, token_end);
} break;
default:
break;
}
if (*p == '\0') {
// The whole string has been scanned, just break from the loop.
break;
}
// Current token has been scanned, continue next split.
state = split_args_state::kSplitBeginning;
continue;
}
// Current scanned character is not `separator`.
switch (state) {
case split_args_state::kSplitBeginning:
if (is_leading_space(*p)) {
state = split_args_state::kSplitLeadingSpaces;
} else {
state = split_args_state::kSplitToken;
token_begin = p;
}
break;
case split_args_state::kSplitLeadingSpaces:
if (!is_leading_space(*p)) {
// Any character that is not leading space will be considered as
// the beginning of a token. Whether all of the scanned characters
// belong to the token will be decided while the `separator` is
// found.
state = split_args_state::kSplitToken;
token_begin = p;
}
break;
default:
break;
}
}
}
template <typename Container>
inline void split_to_sequence_container(const char *input,
char separator,
bool keep_place_holder,
Container &output)
{
split(input, separator, keep_place_holder, SequenceInserter(), output);
}
template <typename Container>
inline void split_to_associative_container(const char *input,
char separator,
bool keep_place_holder,
Container &output)
{
split(input, separator, keep_place_holder, AssociativeInserter(), output);
}
} // anonymous namespace
void split_args(const char *input,
std::vector<std::string> &output,
char separator,
bool keep_place_holder)
{
split_to_sequence_container(input, separator, keep_place_holder, output);
}
void split_args(const char *input,
std::list<std::string> &output,
char separator,
bool keep_place_holder)
{
split_to_sequence_container(input, separator, keep_place_holder, output);
}
void split_args(const char *input,
std::unordered_set<std::string> &output,
char separator,
bool keep_place_holder)
{
split_to_associative_container(input, separator, keep_place_holder, output);
}
bool parse_kv_map(const char *args,
/*out*/ std::map<std::string, std::string> &kv_map,
char item_splitter,
char kv_splitter,
bool allow_dup_key)
{
kv_map.clear();
std::vector<std::string> splits;
split_args(args, splits, item_splitter);
for (std::string &i : splits) {
if (i.empty())
continue;
size_t pos = i.find(kv_splitter);
if (pos == std::string::npos) {
return false;
}
std::string key = i.substr(0, pos);
std::string value = i.substr(pos + 1);
if (!allow_dup_key && kv_map.find(key) != kv_map.end()) {
return false;
}
kv_map[key] = value;
}
return true;
}
void kv_map_to_stream(const std::map<std::string, std::string> &kv_map,
/*out*/ std::ostream &oss,
char item_splitter,
char kv_splitter)
{
int i = 0;
for (auto &kv : kv_map) {
if (i > 0)
oss << item_splitter;
oss << kv.first << kv_splitter << kv.second;
i++;
}
}
std::string kv_map_to_string(const std::map<std::string, std::string> &kv_map,
char item_splitter,
char kv_splitter)
{
std::ostringstream oss;
kv_map_to_stream(kv_map, oss, item_splitter, kv_splitter);
return oss.str();
}
std::string
replace_string(std::string subject, const std::string &search, const std::string &replace)
{
size_t pos = 0;
while ((pos = subject.find(search, pos)) != std::string::npos) {
subject.replace(pos, search.length(), replace);
pos += replace.length();
}
return subject;
}
char *trim_string(char *s)
{
while (*s != '\0' && is_leading_space(*s)) {
s++;
}
char *r = s;
s += strlen(s);
while (s >= r && (*s == '\0' || is_trailing_space(*s))) {
*s = '\0';
s--;
}
return r;
}
std::string string_md5(const char *buffer, unsigned length)
{
unsigned char out[MD5_DIGEST_LENGTH];
MD5_CTX c;
MD5_Init(&c);
int offset = 0;
while (offset < length) {
int block = length - offset;
if (block > 4096)
block = 4096;
MD5_Update(&c, buffer, block);
offset += block;
buffer += block;
}
MD5_Final(out, &c);
char str[MD5_DIGEST_LENGTH * 2 + 1];
str[MD5_DIGEST_LENGTH * 2] = 0;
for (int n = 0; n < MD5_DIGEST_LENGTH; n++)
sprintf(str + n + n, "%02x", out[n]);
std::string result;
result.assign(str);
return result;
}
std::string find_string_prefix(const std::string &input, char separator)
{
auto current = input.find(separator);
if (current == 0 || current == std::string::npos) {
return std::string();
}
return input.substr(0, current);
}
bool has_space(const std::string &str)
{
// Use absl::ascii_isspace() instead of std::isspace(), which could not be used as
// the predicate directly, since it might be implemented as a macro, and its parameter
// must be declared as unsigned. Thus, to use std::isspace(), we have to wrap it into
// a lambda expression.
return std::any_of(str.begin(), str.end(), absl::ascii_isspace);
}
} // namespace utils
} // namespace dsn