feat: strbuf features (#2821)

* feat: strbuf is always zero-terminated

* feat: tr_strbuf::append() now takes any number of args

* feat: tr_strbuf handles zero-terminated string inputs

* feat: add tr_strbuf::ends_with()

* feat: add tr_strbuf::starts_with()

* refactor: replace buildPath() with join()
This commit is contained in:
Charles Kerr 2022-03-25 16:16:26 -05:00 committed by GitHub
parent 9451446e6d
commit 1051160b99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 201 additions and 54 deletions

View File

@ -14,7 +14,7 @@
* but falls back to heap allocation when necessary.
* Useful for building temp strings without heap allocation.
*
* `fmt::basic_memory_buffer` is final, so aggregate intead
* `fmt::basic_memory_buffer` is final, so aggregate instead
* of subclassing ¯\_()_/¯
*/
template<typename T, size_t N>
@ -35,10 +35,10 @@ public:
return *this;
}
template<typename ContiguousRange>
tr_strbuf(ContiguousRange const& in)
template<typename... Args>
tr_strbuf(Args const&... args)
{
buffer_.append(in);
append(args...);
}
[[nodiscard]] constexpr auto begin()
@ -61,82 +61,175 @@ public:
return buffer_.end();
}
[[nodiscard]] T& operator[](size_t pos)
[[nodiscard]] constexpr auto& operator[](size_t pos)
{
return buffer_[pos];
}
[[nodiscard]] constexpr T const& operator[](size_t pos) const
[[nodiscard]] constexpr auto const& operator[](size_t pos) const
{
return buffer_[pos];
}
[[nodiscard]] auto size() const
[[nodiscard]] constexpr auto size() const
{
return buffer_.size();
}
[[nodiscard]] bool empty() const
[[nodiscard]] constexpr bool empty() const
{
return size() == 0;
}
[[nodiscard]] auto* data()
[[nodiscard]] constexpr auto* data()
{
return buffer_.data();
}
[[nodiscard]] auto const* data() const
[[nodiscard]] constexpr auto const* data() const
{
return buffer_.data();
}
[[nodiscard]] constexpr auto const* c_str() const
{
return data();
}
[[nodiscard]] constexpr auto sv() const
{
return std::basic_string_view<T>{ data(), size() };
}
///
auto clear()
[[nodiscard]] constexpr bool ends_with(T const& x) const
{
return buffer_.clear();
}
auto resize(size_t n)
{
return buffer_.resize(n);
}
auto push_back(T const& value)
{
return buffer_.push_back(value);
auto const n = size();
return n != 0 && data()[n - 1] == x;
}
template<typename ContiguousRange>
auto append(ContiguousRange const& range)
[[nodiscard]] constexpr bool ends_with(ContiguousRange const& x) const
{
return buffer_.append(std::data(range), std::data(range) + std::size(range));
auto const x_len = std::size(x);
auto const len = size();
return len >= x_len && this->sv().substr(len - x_len) == x;
}
[[nodiscard]] constexpr bool ends_with(T const* x) const
{
return x != nullptr && ends_with(std::basic_string_view<T>(x));
}
///
[[nodiscard]] constexpr bool starts_with(T const& x) const
{
return !empty() && *data() == x;
}
template<typename ContiguousRange>
auto& operator+=(ContiguousRange const& range)
[[nodiscard]] constexpr bool starts_with(ContiguousRange const& x) const
{
append(range);
auto const x_len = std::size(x);
return size() >= x_len && this->sv().substr(0, x_len) == x;
}
[[nodiscard]] constexpr bool starts_with(T const* x) const
{
return x != nullptr && starts_with(std::basic_string_view<T>(x));
}
///
void clear()
{
buffer_.clear();
ensure_sz();
}
void resize(size_t n)
{
buffer_.resize(n);
ensure_sz();
}
///
void append(T const& value)
{
buffer_.push_back(value);
ensure_sz();
}
template<typename ContiguousRange>
void append(ContiguousRange const& args)
{
buffer_.append(std::data(args), std::data(args) + std::size(args));
ensure_sz();
}
void append(T const* sz_value)
{
if (sz_value != nullptr)
{
append(std::basic_string_view<T>{ sz_value });
}
}
template<typename... Args>
void append(Args const&... args)
{
(append(args), ...);
}
template<typename Arg>
auto& operator+=(Arg const& arg)
{
append(arg);
return *this;
}
template<typename ContiguousRange>
auto& operator=(ContiguousRange const& range)
///
template<typename... Args>
void assign(Args const&... args)
{
clear();
append(range);
append(args...);
}
template<typename Arg>
auto& operator=(Arg const& arg)
{
assign(arg);
return *this;
}
template<typename... ContiguousRange>
void buildPath(ContiguousRange const&... args)
///
template<typename... Args>
void join(T delim, Args const&... args)
{
buffer_.reserve(sizeof...(args) + (std::size(args) + ...));
((append(args), push_back('/')), ...);
((append(args), append(delim)), ...);
resize(size() - 1);
}
template<typename ContiguousRange, typename... Args>
void join(ContiguousRange const& delim, Args const&... args)
{
((append(args), append(delim)), ...);
resize(size() - std::size(delim));
}
template<typename... Args>
void join(T const* sz_delim, Args const&... args)
{
join(std::basic_string_view<T>{ sz_delim }, args...);
}
private:
/**
* Ensure that the buffer's string is zero-terminated, e.g. for
* external APIs that require char* strings.
@ -147,20 +240,9 @@ public:
void ensure_sz()
{
auto const n = size();
buffer_.try_reserve(n + 1);
buffer_.reserve(n + 1);
buffer_[n] = '\0';
}
auto const* c_str()
{
ensure_sz();
return data();
}
[[nodiscard]] constexpr auto sv() const
{
return std::string_view{ data(), size() };
}
};
/**

View File

@ -36,11 +36,17 @@ TEST_F(StrbufTest, assign)
EXPECT_EQ(Value, buf.sv());
}
TEST_F(StrbufTest, buildPath)
TEST_F(StrbufTest, cStr)
{
auto buf = tr_pathbuf{};
buf.buildPath("foo"sv, "bar"sv, "baz"sv);
EXPECT_EQ("foo/bar/baz", buf.sv());
static char const* const Value = "Hello, World!";
auto buf = tr_pathbuf{ Value };
EXPECT_STREQ(Value, buf.c_str());
EXPECT_EQ(strlen(Value), std::size(buf));
buf = tr_pathbuf{ "H", Value + 1 };
EXPECT_STREQ(Value, buf.c_str());
EXPECT_EQ(strlen(Value), std::size(buf));
}
TEST_F(StrbufTest, clear)
@ -67,6 +73,12 @@ TEST_F(StrbufTest, constructorAssign)
auto buf = tr_pathbuf{ Value };
EXPECT_EQ(Value, buf.sv());
buf = tr_pathbuf{ Value.substr(7, 5), Value.substr(5, 2), Value.substr(0, 5), Value.substr(12, 1) };
EXPECT_EQ("World, Hello!"sv, buf.sv());
buf = tr_pathbuf{ "Hello, ", "World!" };
EXPECT_EQ(Value, buf.sv());
}
TEST_F(StrbufTest, heap)
@ -124,10 +136,63 @@ TEST_F(StrbufTest, iterators)
}
}
TEST_F(StrbufTest, sz)
TEST_F(StrbufTest, join)
{
static char const* const Value = "Hello, World!";
auto buf = tr_pathbuf{ std::string_view{ Value } };
EXPECT_STREQ(Value, buf.c_str());
EXPECT_EQ(strlen(Value), std::size(buf));
auto buf = tr_pathbuf{};
buf.clear();
buf.join(' ', 'A', "short", "phrase"sv);
EXPECT_EQ("A short phrase"sv, buf.sv());
buf.clear();
buf.join(" ", 'A', "short", "phrase"sv);
EXPECT_EQ("A short phrase"sv, buf.sv());
buf.clear();
buf.join("--"sv, 'A', "short", "phrase"sv);
EXPECT_EQ("A--short--phrase"sv, buf.sv());
}
TEST_F(StrbufTest, startsWith)
{
auto const buf = tr_pathbuf{ "/hello/world" };
EXPECT_TRUE(buf.starts_with('/'));
EXPECT_TRUE(buf.starts_with("/"));
EXPECT_TRUE(buf.starts_with("/"sv));
EXPECT_TRUE(buf.starts_with("/hello"));
EXPECT_TRUE(buf.starts_with("/hello"sv));
EXPECT_TRUE(buf.starts_with("/hello/world"));
EXPECT_TRUE(buf.starts_with("/hello/world"sv));
EXPECT_FALSE(buf.starts_with('g'));
EXPECT_FALSE(buf.starts_with("g"));
EXPECT_FALSE(buf.starts_with("g"sv));
EXPECT_FALSE(buf.starts_with("ghello"));
EXPECT_FALSE(buf.starts_with("ghello"sv));
EXPECT_FALSE(buf.starts_with("/hellg"));
EXPECT_FALSE(buf.starts_with("/hellg"sv));
EXPECT_FALSE(buf.starts_with("/hellg/world"));
EXPECT_FALSE(buf.starts_with("/hellg/world"sv));
}
TEST_F(StrbufTest, endsWith)
{
auto const buf = tr_pathbuf{ "/hello/world" };
EXPECT_TRUE(buf.ends_with('d'));
EXPECT_TRUE(buf.ends_with("d"));
EXPECT_TRUE(buf.ends_with("d"sv));
EXPECT_TRUE(buf.ends_with("world"));
EXPECT_TRUE(buf.ends_with("world"sv));
EXPECT_TRUE(buf.ends_with("/hello/world"));
EXPECT_TRUE(buf.ends_with("/hello/world"sv));
EXPECT_FALSE(buf.ends_with('g'));
EXPECT_FALSE(buf.ends_with("g"));
EXPECT_FALSE(buf.ends_with("g"sv));
EXPECT_FALSE(buf.ends_with("gorld"));
EXPECT_FALSE(buf.ends_with("gorld"sv));
EXPECT_FALSE(buf.ends_with("worlg"));
EXPECT_FALSE(buf.ends_with("worlg"sv));
EXPECT_FALSE(buf.ends_with("/hellg/world"));
EXPECT_FALSE(buf.ends_with("/hellg/world"sv));
}