diff --git a/glass/src/lib/native/cpp/support/DataLogReaderThread.cpp b/glass/src/lib/native/cpp/support/DataLogReaderThread.cpp index dc68b8968ba..b9fdb445631 100644 --- a/glass/src/lib/native/cpp/support/DataLogReaderThread.cpp +++ b/glass/src/lib/native/cpp/support/DataLogReaderThread.cpp @@ -96,25 +96,23 @@ void DataLogReaderThread::ReadMain() { if (data.empty()) { continue; } - if (wpi::starts_with(name, "NT:")) { - name = wpi::drop_front(name, 3); + if (auto strippedName = wpi::remove_prefix(name, "NT:")) { + name = *strippedName; } - if (wpi::starts_with(name, "/.schema/struct:")) { - auto typeStr = wpi::drop_front(name, 16); + if (auto typeStr = wpi::remove_prefix(name, "/.schema/struct:")) { std::string_view schema{reinterpret_cast(data.data()), data.size()}; std::string err; - auto desc = m_structDb.Add(typeStr, schema, &err); + auto desc = m_structDb.Add(*typeStr, schema, &err); if (!desc) { wpi::print("could not decode struct '{}' schema '{}': {}\n", name, schema, err); } - } else if (wpi::starts_with(name, "/.schema/proto:")) { + } else if (auto filename = wpi::remove_prefix(name, "/.schema/proto:")) { // protobuf descriptor handling - auto filename = wpi::drop_front(name, 15); - if (!m_protoDb.Add(filename, data)) { + if (!m_protoDb.Add(*filename, data)) { wpi::print("could not decode protobuf '{}' filename '{}'\n", name, - filename); + *filename); } } } diff --git a/glass/src/libnt/native/cpp/NTField2D.cpp b/glass/src/libnt/native/cpp/NTField2D.cpp index 745e9e2b54c..1fd87377570 100644 --- a/glass/src/libnt/native/cpp/NTField2D.cpp +++ b/glass/src/libnt/native/cpp/NTField2D.cpp @@ -126,7 +126,7 @@ void NTField2DModel::Update() { for (auto&& event : m_poller.ReadQueue()) { if (auto info = event.GetTopicInfo()) { // handle publish/unpublish - auto name = wpi::drop_front(info->name, m_path.size()); + auto name = wpi::remove_prefix(info->name, m_path).value_or(""); if (name.empty() || name[0] == '.') { continue; } @@ -198,7 +198,9 @@ void NTField2DModel::ForEachFieldObject( func) { for (auto&& obj : m_objects) { if (obj->Exists()) { - func(*obj, wpi::drop_front(obj->GetName(), m_path.size())); + if (auto name = wpi::remove_prefix(obj->GetName(), m_path)) { + func(*obj, *name); + } } } } diff --git a/glass/src/libnt/native/cpp/NTMechanism2D.cpp b/glass/src/libnt/native/cpp/NTMechanism2D.cpp index 32ed276fe3a..3954936d2ca 100644 --- a/glass/src/libnt/native/cpp/NTMechanism2D.cpp +++ b/glass/src/libnt/native/cpp/NTMechanism2D.cpp @@ -256,7 +256,7 @@ NTMechanism2DModel::~NTMechanism2DModel() = default; void NTMechanism2DModel::Update() { for (auto&& event : m_poller.ReadQueue()) { if (auto info = event.GetTopicInfo()) { - auto name = wpi::drop_front(info->name, m_path.size()); + auto name = wpi::remove_prefix(info->name, m_path).value_or(""); if (name.empty() || name[0] == '.') { continue; } @@ -307,7 +307,7 @@ void NTMechanism2DModel::Update() { } } else { auto fullName = nt::Topic{valueData->topic}.GetName(); - auto name = wpi::drop_front(fullName, m_path.size()); + auto name = wpi::remove_prefix(fullName, m_path).value_or(""); if (name.empty() || name[0] == '.') { continue; } diff --git a/glass/src/libnt/native/cpp/NetworkTables.cpp b/glass/src/libnt/native/cpp/NetworkTables.cpp index 34ca96b3f26..0f249ca340a 100644 --- a/glass/src/libnt/native/cpp/NetworkTables.cpp +++ b/glass/src/libnt/native/cpp/NetworkTables.cpp @@ -727,11 +727,12 @@ void NetworkTablesModel::ValueSource::UpdateFromValue( mpack_reader_init_data(&r, value.GetRaw()); UpdateMsgpackValueSource(model, this, r, name, value.last_change()); mpack_reader_destroy(&r); - } else if (wpi::starts_with(typeStr, "struct:")) { - auto structName = wpi::drop_front(typeStr, 7); - bool isArray = structName.ends_with("[]"); + } else if (auto structNameOpt = wpi::remove_prefix(typeStr, "struct:")) { + auto structName = *structNameOpt; + auto withoutArray = wpi::remove_suffix(structName, "[]"); + bool isArray = withoutArray.has_value(); if (isArray) { - structName = wpi::drop_back(structName, 2); + structName = *withoutArray; } auto desc = model.m_structDb.Find(structName); if (desc && desc->IsValid()) { @@ -762,8 +763,8 @@ void NetworkTablesModel::ValueSource::UpdateFromValue( } else { valueChildren.clear(); } - } else if (wpi::starts_with(typeStr, "proto:")) { - auto msg = model.m_protoDb.Find(wpi::drop_front(typeStr, 6)); + } else if (auto filename = wpi::remove_prefix(typeStr, "proto:")) { + auto msg = model.m_protoDb.Find(*filename); if (msg) { msg->Clear(); auto raw = value.GetRaw(); @@ -808,13 +809,15 @@ void NetworkTablesModel::Update() { m_server.publishers.clear(); } else if (info->name == "$serversub") { m_server.subscribers.clear(); - } else if (wpi::starts_with(info->name, "$clientpub$")) { - auto it = m_clients.find(wpi::drop_front(info->name, 11)); + } else if (auto client = + wpi::remove_prefix(info->name, "$clientpub$")) { + auto it = m_clients.find(*client); if (it != m_clients.end()) { it->second.publishers.clear(); } - } else if (wpi::starts_with(info->name, "$clientsub$")) { - auto it = m_clients.find(wpi::drop_front(info->name, 11)); + } else if (auto client = + wpi::remove_prefix(info->name, "$clientsub$")) { + auto it = m_clients.find(*client); if (it != m_clients.end()) { it->second.subscribers.clear(); } @@ -854,27 +857,29 @@ void NetworkTablesModel::Update() { m_server.UpdatePublishers(entry->value.GetRaw()); } else if (entry->info.name == "$serversub") { m_server.UpdateSubscribers(entry->value.GetRaw()); - } else if (wpi::starts_with(entry->info.name, "$clientpub$")) { - auto it = m_clients.find(wpi::drop_front(entry->info.name, 11)); + } else if (auto client = + wpi::remove_prefix(entry->info.name, "$clientpub$")) { + auto it = m_clients.find(*client); if (it != m_clients.end()) { it->second.UpdatePublishers(entry->value.GetRaw()); } - } else if (wpi::starts_with(entry->info.name, "$clientsub$")) { - auto it = m_clients.find(wpi::drop_front(entry->info.name, 11)); + } else if (auto client = + wpi::remove_prefix(entry->info.name, "$clientsub$")) { + auto it = m_clients.find(*client); if (it != m_clients.end()) { it->second.UpdateSubscribers(entry->value.GetRaw()); } } - } else if (entry->value.IsRaw() && - wpi::starts_with(entry->info.name, "/.schema/struct:") && + } else if (auto typeStr = + wpi::remove_prefix(entry->info.name, "/.schema/struct:"); + entry->value.IsRaw() && typeStr && entry->info.type_str == "structschema") { // struct schema handling - auto typeStr = wpi::drop_front(entry->info.name, 16); std::string_view schema{ reinterpret_cast(entry->value.GetRaw().data()), entry->value.GetRaw().size()}; std::string err; - auto desc = m_structDb.Add(typeStr, schema, &err); + auto desc = m_structDb.Add(*typeStr, schema, &err); if (!desc) { wpi::print("could not decode struct '{}' schema '{}': {}\n", entry->info.name, schema, err); @@ -884,25 +889,23 @@ void NetworkTablesModel::Update() { if (!entryPair.second) { continue; } - std::string_view ts = entryPair.second->info.type_str; - if (!wpi::starts_with(ts, "struct:")) { - continue; - } - ts = wpi::drop_front(ts, 7); - if (ts == typeStr || (wpi::ends_with(ts, "[]") && - wpi::drop_back(ts, 2) == typeStr)) { - entryPair.second->UpdateFromValue(*this); + if (auto ts = wpi::remove_prefix(entryPair.second->info.type_str, + "struct:")) { + if (*ts == *typeStr || + wpi::remove_suffix(*ts, "[]").value_or(*ts) == *typeStr) { + entryPair.second->UpdateFromValue(*this); + } } } } - } else if (entry->value.IsRaw() && - wpi::starts_with(entry->info.name, "/.schema/proto:") && + } else if (auto filename = + wpi::remove_prefix(entry->info.name, "/.schema/proto:"); + entry->value.IsRaw() && filename && entry->info.type_str == "proto:FileDescriptorProto") { // protobuf descriptor handling - auto filename = wpi::drop_front(entry->info.name, 15); - if (!m_protoDb.Add(filename, entry->value.GetRaw())) { + if (!m_protoDb.Add(*filename, entry->value.GetRaw())) { wpi::print("could not decode protobuf '{}' filename '{}'\n", - entry->info.name, filename); + entry->info.name, *filename); } else { // loop over all protobuf entries and update (conservatively) for (auto&& entryPair : m_entries) { @@ -1080,18 +1083,18 @@ void NetworkTablesModel::UpdateClients(std::span data) { } static bool GetHeadingTypeString(std::string_view* ts) { - if (wpi::starts_with(*ts, "proto:")) { - *ts = wpi::drop_front(*ts, 6); + if (auto withoutProto = wpi::remove_prefix(*ts, "proto:")) { + *ts = *withoutProto; auto lastdot = ts->rfind('.'); if (lastdot != std::string_view::npos) { *ts = wpi::substr(*ts, lastdot + 1); } - if (wpi::starts_with(*ts, "Protobuf")) { - *ts = wpi::drop_front(*ts, 8); + if (auto withoutProtobuf = wpi::remove_prefix(*ts, "Protobuf")) { + *ts = *withoutProtobuf; } return true; - } else if (wpi::starts_with(*ts, "struct:")) { - *ts = wpi::drop_front(*ts, 7); + } else if (auto withoutStruct = wpi::remove_prefix(*ts, "struct:")) { + *ts = *withoutStruct; return true; } return false; diff --git a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp index 61bac02789f..5c8c6f56467 100644 --- a/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp +++ b/glass/src/libnt/native/cpp/NetworkTablesProvider.cpp @@ -157,7 +157,8 @@ void NetworkTablesProvider::Update() { } auto topicName = nt::GetTopicName(valueData->topic); - auto tableName = wpi::drop_back(topicName, 6); + auto tableName = + wpi::remove_suffix(topicName, "/.type").value_or(topicName); GetOrCreateView(builderIt->second, nt::Topic{valueData->topic}, tableName); @@ -196,9 +197,8 @@ void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) { if (!window) { return; } - if (wpi::starts_with(entry->name, "/SmartDashboard/")) { - window->SetDefaultName( - fmt::format("{} (SmartDashboard)", wpi::drop_front(entry->name, 16))); + if (auto name = wpi::remove_prefix(entry->name, "/SmartDashboard/")) { + window->SetDefaultName(fmt::format("{} (SmartDashboard)", *name)); } entry->window = window; diff --git a/ntcore/src/main/native/cpp/LocalStorage.cpp b/ntcore/src/main/native/cpp/LocalStorage.cpp index 7d1c3e4dc0d..02815156d12 100644 --- a/ntcore/src/main/native/cpp/LocalStorage.cpp +++ b/ntcore/src/main/native/cpp/LocalStorage.cpp @@ -53,10 +53,11 @@ int LocalStorage::DataLoggerData::Start(TopicData* topic, int64_t time) { } else if (typeStr == "int[]") { typeStr = "int64[]"; } - return log.Start(fmt::format("{}{}", logPrefix, - wpi::drop_front(topic->name, prefix.size())), - typeStr, DataLoggerEntry::MakeMetadata(topic->propertiesStr), - time); + return log.Start( + fmt::format( + "{}{}", logPrefix, + wpi::remove_prefix(topic->name, prefix).value_or(topic->name)), + typeStr, DataLoggerEntry::MakeMetadata(topic->propertiesStr), time); } void LocalStorage::DataLoggerEntry::Append(const Value& v) { diff --git a/ntcore/src/main/native/cpp/NetworkServer.cpp b/ntcore/src/main/native/cpp/NetworkServer.cpp index 26dd9408297..04f2c052bdb 100644 --- a/ntcore/src/main/native/cpp/NetworkServer.cpp +++ b/ntcore/src/main/native/cpp/NetworkServer.cpp @@ -224,8 +224,8 @@ void NetworkServer::ServerConnection4::ProcessWsUpgrade() { wpi::SmallString<128> nameBuf; std::string_view name; bool err = false; - if (wpi::starts_with(path, "/nt/")) { - name = wpi::UnescapeURI(wpi::drop_front(path, 4), nameBuf, &err); + if (auto uri = wpi::remove_prefix(path, "/nt/")) { + name = wpi::UnescapeURI(*uri, nameBuf, &err); } if (err || name.empty()) { INFO("invalid path '{}' (from {}), must match /nt/[clientId], closing", diff --git a/ntcore/src/main/native/cpp/net/WireDecoder.cpp b/ntcore/src/main/native/cpp/net/WireDecoder.cpp index 536a62fb0c8..48bf5ad4130 100644 --- a/ntcore/src/main/native/cpp/net/WireDecoder.cpp +++ b/ntcore/src/main/native/cpp/net/WireDecoder.cpp @@ -567,7 +567,6 @@ bool nt::net::WireDecodeBinary(std::span* in, int64_t* outId, outValue->SetServerTime(time); outValue->SetTime(time == 0 ? 0 : time + localTimeOffset); // update input range - *in = wpi::drop_front(*in, - in->size() - mpack_reader_remaining(&reader, nullptr)); + *in = wpi::take_back(*in, mpack_reader_remaining(&reader, nullptr)); return true; } diff --git a/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp b/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp index d2b860ea976..3567b720096 100644 --- a/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp +++ b/simulation/halsim_ws_server/src/main/native/cpp/HALSimHttpConnection.cpp @@ -169,14 +169,13 @@ void HALSimHttpConnection::ProcessRequest() { !wpi::contains(path, "..") && !wpi::contains(path, "//")) { // convert to fs native representation fs::path nativePath; - if (wpi::starts_with(path, "/user/")) { - nativePath = - fs::path{m_server->GetWebrootSys()} / - fs::path{wpi::drop_front(path, 6), fs::path::format::generic_format}; + if (auto userPath = wpi::remove_prefix(path, "/user/")) { + nativePath = fs::path{m_server->GetWebrootSys()} / + fs::path{*userPath, fs::path::format::generic_format}; } else { nativePath = fs::path{m_server->GetWebrootSys()} / - fs::path{wpi::drop_front(path, 1), fs::path::format::generic_format}; + fs::path{wpi::drop_front(path), fs::path::format::generic_format}; } if (fs::is_directory(nativePath)) { diff --git a/wpiutil/src/main/native/thirdparty/llvm/cpp/llvm/StringExtras.cpp b/wpiutil/src/main/native/thirdparty/llvm/cpp/llvm/StringExtras.cpp index 930772488ec..f9b42944740 100644 --- a/wpiutil/src/main/native/thirdparty/llvm/cpp/llvm/StringExtras.cpp +++ b/wpiutil/src/main/native/thirdparty/llvm/cpp/llvm/StringExtras.cpp @@ -278,7 +278,7 @@ bool wpi::detail::ConsumeSignedInteger( } // Get the positive part of the value. - std::string_view str2 = wpi::drop_front(str, 1); + std::string_view str2 = wpi::drop_front(str); if (wpi::detail::ConsumeUnsignedInteger(str2, radix, ullVal) || // Reject values so large they'd overflow as negative signed, but allow // "-0". This negates the unsigned so that the negative isn't undefined diff --git a/wpiutil/src/main/native/thirdparty/llvm/include/wpi/StringExtras.h b/wpiutil/src/main/native/thirdparty/llvm/include/wpi/StringExtras.h index e07576ef9f9..0edee8532b1 100644 --- a/wpiutil/src/main/native/thirdparty/llvm/include/wpi/StringExtras.h +++ b/wpiutil/src/main/native/thirdparty/llvm/include/wpi/StringExtras.h @@ -357,6 +357,32 @@ inline bool contains_lower(std::string_view str, const char* other) noexcept { return find_lower(str, other) != std::string_view::npos; } +/** + * Return an optional containing @p str but with @p prefix removed if the string + * starts with the prefix. If the string does not start with the prefix, return + * an empty optional. + */ +constexpr std::optional remove_prefix(std::string_view str, std::string_view prefix) noexcept { + if (str.starts_with(prefix)) { + str.remove_prefix(prefix.size()); + return str; + } + return std::nullopt; +} + +/** + * Return an optional containing @p str but with @p suffix removed if the + * string ends with the suffix. If the string does not end with the suffix, + * return an empty optional. + */ +constexpr std::optional remove_suffix(std::string_view str, std::string_view suffix) noexcept { + if (str.ends_with(suffix)) { + str.remove_suffix(suffix.size()); + return str; + } + return std::nullopt; +} + /** * Return a string_view equal to @p str but with the first @p n elements * dropped. diff --git a/wpiutil/src/test/native/cpp/StringExtrasTest.cpp b/wpiutil/src/test/native/cpp/StringExtrasTest.cpp new file mode 100644 index 00000000000..ac61dba0279 --- /dev/null +++ b/wpiutil/src/test/native/cpp/StringExtrasTest.cpp @@ -0,0 +1,35 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include + +#include "wpi/StringExtras.h" + +TEST(StringExtrasTest, RemovePrefix) { + std::string_view original = "wpilib"; + auto modified = wpi::remove_prefix(original, "wpi"); + EXPECT_EQ(original, "wpilib"); + EXPECT_EQ(modified, std::optional{"lib"}); +} + +TEST(StringExtrasTest, RemoveSuffix) { + std::string_view original = "wpilib"; + auto modified = wpi::remove_suffix(original, "lib"); + EXPECT_EQ(original, "wpilib"); + EXPECT_EQ(modified, std::optional{"wpi"}); +} + +TEST(StringExtrasTest, RemovePrefixNoMatch) { + std::string_view original = "wpilib"; + auto modified = wpi::remove_prefix(original, "foo"); + EXPECT_EQ(original, "wpilib"); + EXPECT_EQ(modified, std::nullopt); +} + +TEST(StringExtrasTest, RemoveSuffixNoMatch) { + std::string_view original = "wpilib"; + auto modified = wpi::remove_suffix(original, "foo"); + EXPECT_EQ(original, "wpilib"); + EXPECT_EQ(modified, std::nullopt); +}