Added some basic HTTP and ip address parsing.
This commit is contained in:
parent
03c899f17e
commit
35e7131780
2
LibConf
2
LibConf
@ -7,6 +7,8 @@ mijin_sources = Split("""
|
||||
source/mijin/debug/symbol_info.cpp
|
||||
source/mijin/io/process.cpp
|
||||
source/mijin/io/stream.cpp
|
||||
source/mijin/net/http.cpp
|
||||
source/mijin/net/socket.cpp
|
||||
source/mijin/util/os.cpp
|
||||
source/mijin/types/name.cpp
|
||||
source/mijin/virtual_filesystem/filesystem.cpp
|
||||
|
1
SModule
1
SModule
@ -10,6 +10,7 @@ mijin_sources = Split("""
|
||||
source/mijin/debug/symbol_info.cpp
|
||||
source/mijin/io/process.cpp
|
||||
source/mijin/io/stream.cpp
|
||||
source/mijin/net/http.cpp
|
||||
source/mijin/net/socket.cpp
|
||||
source/mijin/util/os.cpp
|
||||
source/mijin/types/name.cpp
|
||||
|
@ -36,20 +36,12 @@ void Stream::flush() {}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
(void) buffer;
|
||||
(void) options;
|
||||
(void) outBytesRead;
|
||||
|
||||
MIJIN_ASSERT(!getFeatures().async || !getFeatures().read, "Stream advertises async read, but doesn't implement it.");
|
||||
co_return StreamError::NOT_SUPPORTED;
|
||||
co_return readRaw(buffer, options, outBytesRead);
|
||||
}
|
||||
|
||||
mijin::Task<StreamError> Stream::c_writeRaw(std::span<const std::uint8_t> buffer)
|
||||
{
|
||||
(void) buffer;
|
||||
|
||||
MIJIN_ASSERT(!getFeatures().async || !getFeatures().write, "Stream advertises async write, but doesn't implement it.");
|
||||
co_return StreamError::NOT_SUPPORTED;
|
||||
co_return writeRaw(buffer);
|
||||
}
|
||||
|
||||
StreamError Stream::readBinaryString(std::string& outString)
|
||||
|
@ -68,6 +68,7 @@ enum class [[nodiscard]] StreamError
|
||||
IO_ERROR = 1,
|
||||
NOT_SUPPORTED = 2,
|
||||
CONNECTION_CLOSED = 3,
|
||||
PROTOCOL_ERROR = 4,
|
||||
UNKNOWN_ERROR = -1
|
||||
};
|
||||
|
||||
@ -430,6 +431,8 @@ inline const char* errorName(StreamError error) noexcept
|
||||
return "not supported";
|
||||
case StreamError::CONNECTION_CLOSED:
|
||||
return "connection closed";
|
||||
case StreamError::PROTOCOL_ERROR:
|
||||
return "protocol error";
|
||||
case StreamError::UNKNOWN_ERROR:
|
||||
return "unknown error";
|
||||
}
|
||||
|
169
source/mijin/net/http.cpp
Normal file
169
source/mijin/net/http.cpp
Normal file
@ -0,0 +1,169 @@
|
||||
|
||||
#include "./http.hpp"
|
||||
|
||||
#include "../util/iterators.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
#define MIJIN_HTTP_WRITE(text) \
|
||||
do \
|
||||
{ \
|
||||
if (const StreamError error = co_await base_->c_writeText(text); error != StreamError::SUCCESS) \
|
||||
{ \
|
||||
co_return error; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
#define MIJIN_HTTP_CHECKREAD(read) \
|
||||
do \
|
||||
{ \
|
||||
if (const StreamError error = co_await read; error != StreamError::SUCCESS) \
|
||||
{ \
|
||||
co_return error; \
|
||||
} \
|
||||
} while(false)
|
||||
|
||||
#define MIJIN_HTTP_READLINE(text) MIJIN_HTTP_CHECKREAD(base_->c_readLine(text)); text = trim(text)
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
namespace
|
||||
{
|
||||
inline constexpr std::size_t CONTENT_LENGTH_LIMIT = 100 << 20; // 100MiB
|
||||
bool parseHTTPVersion(std::string_view version, HTTPVersion& outVersion) noexcept
|
||||
{
|
||||
std::vector<std::string_view> parts = split(version, ".");
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return toNumber(parts[0], outVersion.major) && toNumber(parts[1], outVersion.minor);
|
||||
}
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> HTTPStream::c_request(HTTPRequest request) noexcept
|
||||
{
|
||||
if (const StreamError error = co_await c_writeRequest(request); error != StreamError::SUCCESS)
|
||||
{
|
||||
co_return error;
|
||||
}
|
||||
co_return co_await c_readResponse();
|
||||
}
|
||||
|
||||
Task<StreamError> HTTPStream::c_writeRequest(const mijin::HTTPRequest& request) noexcept
|
||||
{
|
||||
std::map<std::string, std::string> moreHeaders;
|
||||
if (!request.body.empty())
|
||||
{
|
||||
auto itLength = request.headers.find("content-length");
|
||||
if (itLength == request.headers.end())
|
||||
{
|
||||
moreHeaders.emplace("content-length", std::to_string(request.body.size()));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::size_t headerValue = 0;
|
||||
if (!toNumber(itLength->second, headerValue) || headerValue != request.body.size())
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MIJIN_HTTP_WRITE(request.method);
|
||||
MIJIN_HTTP_WRITE(" ");
|
||||
MIJIN_HTTP_WRITE(request.address);
|
||||
MIJIN_HTTP_WRITE(" HTTP/1.0\n");
|
||||
for (const auto& [key, value] : moreHeaders)
|
||||
{
|
||||
MIJIN_HTTP_WRITE(key);
|
||||
MIJIN_HTTP_WRITE(": ");
|
||||
MIJIN_HTTP_WRITE(value);
|
||||
MIJIN_HTTP_WRITE("\n");
|
||||
}
|
||||
for (const auto& [key, value] : request.headers)
|
||||
{
|
||||
MIJIN_HTTP_WRITE(key);
|
||||
MIJIN_HTTP_WRITE(": ");
|
||||
MIJIN_HTTP_WRITE(value);
|
||||
MIJIN_HTTP_WRITE("\n");
|
||||
}
|
||||
|
||||
MIJIN_HTTP_WRITE("\n");
|
||||
if (!request.body.empty())
|
||||
{
|
||||
MIJIN_HTTP_WRITE(request.body);
|
||||
}
|
||||
|
||||
co_return StreamError::SUCCESS;
|
||||
}
|
||||
|
||||
Task<StreamResult<HTTPResponse>> HTTPStream::c_readResponse() noexcept
|
||||
{
|
||||
std::string line;
|
||||
MIJIN_HTTP_READLINE(line);
|
||||
|
||||
std::vector<std::string_view> parts = split(line, " ", {.limitParts = 3});
|
||||
if (parts.size() != 3)
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
if (!parts[0].starts_with("HTTP/"))
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
HTTPResponse response;
|
||||
if (!parseHTTPVersion(parts[0].substr(5), response.version)
|
||||
|| !toNumber(parts[1], response.status))
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
response.statusMessage = parts[2];
|
||||
|
||||
decltype(response.headers)::iterator lastHeader;
|
||||
while (true)
|
||||
{
|
||||
MIJIN_HTTP_READLINE(line);
|
||||
if (line.empty()) {
|
||||
break;
|
||||
}
|
||||
if (line[0] == ' ' || line[0] == '\t')
|
||||
{
|
||||
// continuation
|
||||
if (lastHeader == response.headers.end())
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
lastHeader->second.push_back(' ');
|
||||
lastHeader->second.append(trim(line));
|
||||
}
|
||||
parts = split(line, ":", {.limitParts = 2});
|
||||
if (parts.size() != 2)
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
lastHeader = response.headers.emplace(toLower(trim(parts[0])), trim(parts[1]));
|
||||
}
|
||||
|
||||
auto itContentLength = response.headers.find("content-length");
|
||||
if (itContentLength != response.headers.end())
|
||||
{
|
||||
std::size_t contentLength = 0;
|
||||
if (!toNumber(itContentLength->second, contentLength))
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
if (contentLength > CONTENT_LENGTH_LIMIT)
|
||||
{
|
||||
co_return StreamError::PROTOCOL_ERROR;
|
||||
}
|
||||
response.content.resize(contentLength);
|
||||
MIJIN_HTTP_CHECKREAD(base_->c_readRaw(response.content));
|
||||
}
|
||||
|
||||
co_return response;
|
||||
}
|
||||
}
|
||||
|
||||
#undef MIJIN_HTTP_WRITE
|
||||
#undef MIJIN_HTTP_READLINE
|
55
source/mijin/net/http.hpp
Normal file
55
source/mijin/net/http.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(MIJIN_NET_HTTP_HPP_INCLUDED)
|
||||
#define MIJIN_NET_HTTP_HPP_INCLUDED 1
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "../io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
{
|
||||
struct HTTPVersion
|
||||
{
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
};
|
||||
|
||||
struct HTTPRequest
|
||||
{
|
||||
std::string address;
|
||||
std::string method = "GET";
|
||||
std::multimap<std::string, std::string> headers;
|
||||
std::string body;
|
||||
|
||||
explicit HTTPRequest(std::string address_) noexcept : address(std::move(address_)) {}
|
||||
};
|
||||
|
||||
struct HTTPResponse
|
||||
{
|
||||
HTTPVersion version;
|
||||
unsigned status;
|
||||
std::string statusMessage;
|
||||
std::multimap<std::string, std::string> headers;
|
||||
std::string content;
|
||||
};
|
||||
|
||||
class HTTPStream
|
||||
{
|
||||
private:
|
||||
Stream* base_;
|
||||
public:
|
||||
HTTPStream(Stream& base) noexcept : base_(&base)
|
||||
{
|
||||
MIJIN_ASSERT(base_ != nullptr, "Invalid parameter for base.");
|
||||
}
|
||||
Task<StreamResult<HTTPResponse>> c_request(HTTPRequest request) noexcept;
|
||||
private:
|
||||
Task<StreamError> c_writeRequest(const HTTPRequest& request) noexcept;
|
||||
Task<StreamResult<HTTPResponse>> c_readResponse() noexcept;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // !defined(MIJIN_NET_HTTP_HPP_INCLUDED)
|
@ -1,13 +1,17 @@
|
||||
|
||||
#include "./socket.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../detect.hpp"
|
||||
#include "../util/string.hpp"
|
||||
|
||||
#if MIJIN_TARGET_OS == MIJIN_OS_LINUX
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include "../util/variant.hpp"
|
||||
#endif
|
||||
|
||||
namespace mijin
|
||||
@ -51,6 +55,77 @@ int readFlags(const ReadOptions& options)
|
||||
}
|
||||
}
|
||||
|
||||
Optional<IPv4Address> IPv4Address::fromString(std::string_view stringView) noexcept
|
||||
{
|
||||
std::vector<std::string_view> parts = split(stringView, ".", {.limitParts = 4});
|
||||
if (parts.size() != 4) {
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
IPv4Address address;
|
||||
for (int idx = 0; idx < 4; ++idx)
|
||||
{
|
||||
if (!toNumber(parts[idx], address.octets[idx]))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
Optional<IPv6Address> IPv6Address::fromString(std::string_view stringView) noexcept
|
||||
{
|
||||
// very specific edge case
|
||||
if (stringView.contains(":::"))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
std::vector<std::string_view> parts = split(stringView, "::", {.ignoreEmpty = false});
|
||||
if (parts.size() > 2)
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
if (parts.size() == 1)
|
||||
{
|
||||
parts.emplace_back("");
|
||||
}
|
||||
|
||||
std::vector<std::string_view> partsLeft = split(parts[0], ":");
|
||||
std::vector<std::string_view> partsRight = split(parts[1], ":");
|
||||
|
||||
std::erase_if(partsLeft, std::mem_fn(&std::string_view::empty));
|
||||
std::erase_if(partsRight, std::mem_fn(&std::string_view::empty));
|
||||
|
||||
if (partsLeft.size() + partsRight.size() > 8)
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
IPv6Address address;
|
||||
unsigned hextet = 0;
|
||||
for (std::string_view part : partsLeft)
|
||||
{
|
||||
if (!toNumber(part, address.hextets[hextet], /* base = */ 16))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
++hextet;
|
||||
}
|
||||
for (; hextet < (8 - partsRight.size()); ++hextet)
|
||||
{
|
||||
address.hextets[hextet] = 0;
|
||||
}
|
||||
for (std::string_view part : partsRight)
|
||||
{
|
||||
if (!toNumber(part, address.hextets[hextet], /* base = */ 16))
|
||||
{
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
++hextet;
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
StreamError TCPStream::readRaw(std::span<std::uint8_t> buffer, const ReadOptions& options, std::size_t* outBytesRead)
|
||||
{
|
||||
MIJIN_ASSERT(isOpen(), "Socket is not open.");
|
||||
@ -174,7 +249,7 @@ StreamFeatures TCPStream::getFeatures()
|
||||
};
|
||||
}
|
||||
|
||||
StreamError TCPStream::open(const char* address, std::uint16_t port) noexcept
|
||||
StreamError TCPStream::open(ip_address_t address, std::uint16_t port) noexcept
|
||||
{
|
||||
MIJIN_ASSERT(!isOpen(), "Socket is already open.");
|
||||
|
||||
@ -183,13 +258,31 @@ StreamError TCPStream::open(const char* address, std::uint16_t port) noexcept
|
||||
{
|
||||
return translateErrno();
|
||||
}
|
||||
sockaddr_in connectAddress =
|
||||
{
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr = {inet_addr(address)}
|
||||
};
|
||||
if (connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in)) < 0)
|
||||
|
||||
const bool connected = std::visit(Visitor{
|
||||
[&](const IPv4Address& address4)
|
||||
{
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
#error "TODO: swap byte orderof thre address"
|
||||
#endif
|
||||
sockaddr_in connectAddress =
|
||||
{
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
.sin_addr = {.s_addr = std::bit_cast<in_addr_t>(address4)}
|
||||
};
|
||||
return connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in)) == 0;
|
||||
},
|
||||
[&](const IPv6Address& address6) {
|
||||
sockaddr_in6 connectAddress =
|
||||
{
|
||||
.sin6_family = AF_INET,
|
||||
.sin6_port = htons(port),
|
||||
.sin6_addr = std::bit_cast<in6_addr>(address6)
|
||||
};
|
||||
return connect(handle_, reinterpret_cast<sockaddr*>(&connectAddress), sizeof(sockaddr_in6)) == 0;}
|
||||
}, address);
|
||||
if (!connected)
|
||||
{
|
||||
::close(handle_);
|
||||
handle_ = -1;
|
||||
|
@ -4,7 +4,10 @@
|
||||
#if !defined(MIJIN_NET_SOCKET_HPP_INCLUDED)
|
||||
#define MIJIN_NET_SOCKET_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <variant>
|
||||
#include "../async/coroutine.hpp"
|
||||
#include "../container/optional.hpp"
|
||||
#include "../io/stream.hpp"
|
||||
|
||||
namespace mijin
|
||||
@ -14,6 +17,41 @@ namespace mijin
|
||||
// public types
|
||||
//
|
||||
|
||||
struct IPv4Address
|
||||
{
|
||||
std::array<std::uint8_t, 4> octets;
|
||||
|
||||
auto operator<=>(const IPv4Address&) const noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
static Optional<IPv4Address> fromString(std::string_view stringView) noexcept;
|
||||
};
|
||||
|
||||
struct IPv6Address
|
||||
{
|
||||
std::array<std::uint16_t, 8> hextets;
|
||||
|
||||
auto operator<=>(const IPv6Address&) const noexcept = default;
|
||||
|
||||
[[nodiscard]]
|
||||
static Optional<IPv6Address> fromString(std::string_view stringView) noexcept;
|
||||
};
|
||||
using ip_address_t = std::variant<IPv4Address, IPv6Address>;
|
||||
|
||||
[[nodiscard]]
|
||||
inline Optional<ip_address_t> ipAddressFromString(std::string_view stringView) noexcept
|
||||
{
|
||||
if (Optional<IPv4Address> ipv4Address = IPv4Address::fromString(stringView); !ipv4Address.empty())
|
||||
{
|
||||
return ip_address_t(*ipv4Address);
|
||||
}
|
||||
if (Optional<IPv6Address> ipv6Address = IPv6Address::fromString(stringView); !ipv6Address.empty())
|
||||
{
|
||||
return ip_address_t(*ipv6Address);
|
||||
}
|
||||
return NULL_OPTIONAL;
|
||||
}
|
||||
|
||||
class Socket
|
||||
{
|
||||
protected:
|
||||
@ -61,7 +99,7 @@ public:
|
||||
bool isAtEnd() override;
|
||||
StreamFeatures getFeatures() override;
|
||||
|
||||
StreamError open(const char* address, std::uint16_t port) noexcept;
|
||||
StreamError open(ip_address_t address, std::uint16_t port) noexcept;
|
||||
void close() noexcept;
|
||||
[[nodiscard]] bool isOpen() const noexcept { return handle_ >= 0; }
|
||||
private:
|
||||
@ -77,7 +115,15 @@ private:
|
||||
public:
|
||||
TCPStream& getStream() noexcept override;
|
||||
|
||||
StreamError open(const char* address, std::uint16_t port) noexcept { return stream_.open(address, port); }
|
||||
StreamError open(ip_address_t address, std::uint16_t port) noexcept { return stream_.open(address, port); }
|
||||
StreamError open(std::string_view addressText, std::uint16_t port) noexcept
|
||||
{
|
||||
if (Optional<ip_address_t> address = ipAddressFromString(addressText); !address.empty())
|
||||
{
|
||||
return open(*address, port);
|
||||
}
|
||||
return StreamError::UNKNOWN_ERROR;
|
||||
}
|
||||
void close() noexcept { stream_.close(); }
|
||||
[[nodiscard]] bool isOpen() const noexcept { return stream_.isOpen(); }
|
||||
|
||||
|
@ -4,8 +4,11 @@
|
||||
#if !defined(MIJIN_UTIL_STRING_HPP_INCLUDED)
|
||||
#define MIJIN_UTIL_STRING_HPP_INCLUDED 1
|
||||
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@ -24,6 +27,13 @@ namespace mijin
|
||||
// public constants
|
||||
//
|
||||
|
||||
//
|
||||
// public traits
|
||||
//
|
||||
|
||||
template<typename TString>
|
||||
using char_type_t = decltype(std::string_view(std::declval<TString>()))::value_type;
|
||||
|
||||
//
|
||||
// public types
|
||||
//
|
||||
@ -151,8 +161,35 @@ bool equalsIgnoreCaseImpl(std::basic_string_view<TChar, TTraitsA> stringA, std::
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimPrefixImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim)
|
||||
{
|
||||
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
|
||||
return stringView;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimSuffixImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim)
|
||||
{
|
||||
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
|
||||
return stringView;
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimImpl(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim)
|
||||
{
|
||||
return trimPrefixImpl(trimSuffixImpl(stringView, charsToTrim), charsToTrim);
|
||||
}
|
||||
|
||||
template<typename TChar>
|
||||
static const TChar SPACE = TChar(' ');
|
||||
static const std::array DEFAULT_TRIM_CHARS_DATA = {TChar(' '), TChar('\t'), TChar('\r'), TChar('\n')};
|
||||
|
||||
template<typename TChar>
|
||||
static const std::basic_string_view<TChar, std::char_traits<TChar>> DEFAULT_TRIM_CHARS
|
||||
= {DEFAULT_TRIM_CHARS_DATA<TChar>.begin(), DEFAULT_TRIM_CHARS_DATA<TChar>.end()};
|
||||
}
|
||||
|
||||
template<typename TLeft, typename TRight>
|
||||
@ -162,27 +199,46 @@ template<typename TLeft, typename TRight>
|
||||
std::basic_string_view(std::forward<TRight>(separator)), options);
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimPrefix(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
|
||||
template<typename TString, typename TChars>
|
||||
[[nodiscard]]
|
||||
auto trimPrefix(TString&& string, TChars&& chars)
|
||||
{
|
||||
stringView.remove_prefix(std::min(stringView.find_first_not_of(charsToTrim), stringView.size()));
|
||||
return stringView;
|
||||
return detail::trimPrefixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trimSuffix(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
|
||||
template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trimPrefix(TString&& string)
|
||||
{
|
||||
stringView.remove_suffix(stringView.size() - std::min(stringView.find_last_not_of(charsToTrim) + 1, stringView.size()));
|
||||
return stringView;
|
||||
return trimPrefix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits>
|
||||
std::basic_string_view<TChar, TTraits> trim(std::basic_string_view<TChar, TTraits> stringView,
|
||||
std::basic_string_view<TChar, TTraits> charsToTrim = {&detail::SPACE<TChar>, &detail::SPACE<TChar> + 1})
|
||||
template<typename TString, typename TChars>
|
||||
[[nodiscard]]
|
||||
auto trimSuffix(TString&& string, TChars&& chars)
|
||||
{
|
||||
return trimPrefix(trimSuffix(stringView, charsToTrim), charsToTrim);
|
||||
return detail::trimSuffixImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||
}
|
||||
|
||||
template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trimSuffix(TString&& string)
|
||||
{
|
||||
return trimSuffix(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TString, typename TChars>
|
||||
[[nodiscard]]
|
||||
auto trim(TString&& string, TChars&& chars)
|
||||
{
|
||||
return detail::trimImpl(std::string_view(std::forward<TString>(string)), std::string_view(std::forward<TChars>(chars)));
|
||||
}
|
||||
|
||||
template<typename TString>
|
||||
[[nodiscard]]
|
||||
auto trim(TString&& string)
|
||||
{
|
||||
return trim(string, detail::DEFAULT_TRIM_CHARS<char_type_t<TString>>);
|
||||
}
|
||||
|
||||
template<typename TLeft, typename TRight>
|
||||
@ -191,6 +247,51 @@ template<typename TLeft, typename TRight>
|
||||
return detail::equalsIgnoreCaseImpl(std::string_view(left), std::string_view(right));
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
void makeLower(std::basic_string<TChar, TTraits, TAllocator>& string)
|
||||
{
|
||||
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
|
||||
{
|
||||
return std::tolower<TChar>(chr, locale);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename TChar, typename TTraits, typename TAllocator>
|
||||
void makeUpper(std::basic_string<TChar, TTraits, TAllocator>& string)
|
||||
{
|
||||
std::transform(string.begin(), string.end(), string.begin(), [locale = std::locale()](TChar chr)
|
||||
{
|
||||
return std::toupper<TChar>(chr, locale);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
[[nodiscard]]
|
||||
auto toLower(TArgs&&... args)
|
||||
{
|
||||
std::basic_string string(std::forward<TArgs>(args)...);
|
||||
makeLower(string);
|
||||
return string;
|
||||
}
|
||||
|
||||
template<typename... TArgs>
|
||||
[[nodiscard]]
|
||||
auto toUpper(TArgs&&... args)
|
||||
{
|
||||
std::basic_string string(std::forward<TArgs>(args)...);
|
||||
makeUpper(string);
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
template<typename TNumber>
|
||||
[[nodiscard]]
|
||||
bool toNumber(std::string_view stringView, TNumber& outNumber, int base = 10) noexcept
|
||||
{
|
||||
const std::from_chars_result res = std::from_chars(&*stringView.begin(), &*stringView.end(), outNumber, base);
|
||||
return res.ec == std::errc{} && res.ptr == &*stringView.end();
|
||||
}
|
||||
|
||||
namespace pipe
|
||||
{
|
||||
struct Join
|
||||
|
Loading…
x
Reference in New Issue
Block a user