Merge branch 'text_sensor_filters_no_alloc' into integration

This commit is contained in:
J. Nick Koston
2025-12-07 13:21:47 -06:00
6 changed files with 22 additions and 45 deletions

View File

@@ -19,7 +19,6 @@ from esphome.cpp_generator import ( # noqa: F401
RawExpression,
RawStatement,
Statement,
StringRefLiteral,
StructInitializer,
TemplateArguments,
add,

View File

@@ -86,12 +86,12 @@ async def to_lower_filter_to_code(config, filter_id):
@FILTER_REGISTRY.register("append", AppendFilter, cv.string)
async def append_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, cg.StringRefLiteral(config))
return cg.new_Pvariable(filter_id, config)
@FILTER_REGISTRY.register("prepend", PrependFilter, cv.string)
async def prepend_filter_to_code(config, filter_id):
return cg.new_Pvariable(filter_id, cg.StringRefLiteral(config))
return cg.new_Pvariable(filter_id, config)
def validate_mapping(value):
@@ -114,8 +114,8 @@ async def substitute_filter_to_code(config, filter_id):
substitutions = [
cg.StructInitializer(
cg.MockObj("Substitution", "esphome::text_sensor::"),
("from", cg.StringRefLiteral(conf[CONF_FROM])),
("to", cg.StringRefLiteral(conf[CONF_TO])),
("from", conf[CONF_FROM]),
("to", conf[CONF_TO]),
)
for conf in config
]
@@ -127,8 +127,8 @@ async def map_filter_to_code(config, filter_id):
mappings = [
cg.StructInitializer(
cg.MockObj("Substitution", "esphome::text_sensor::"),
("from", cg.StringRefLiteral(conf[CONF_FROM])),
("to", cg.StringRefLiteral(conf[CONF_TO])),
("from", conf[CONF_FROM]),
("to", conf[CONF_TO]),
)
for conf in config
]

View File

@@ -57,13 +57,13 @@ optional<std::string> ToLowerFilter::new_value(std::string value) {
// Append
optional<std::string> AppendFilter::new_value(std::string value) {
value += this->suffix_;
value.append(this->suffix_);
return value;
}
// Prepend
optional<std::string> PrependFilter::new_value(std::string value) {
value.insert(0, this->prefix_.c_str(), this->prefix_.size());
value.insert(0, this->prefix_);
return value;
}
@@ -73,13 +73,15 @@ SubstituteFilter::SubstituteFilter(const std::initializer_list<Substitution> &su
optional<std::string> SubstituteFilter::new_value(std::string value) {
for (const auto &sub : this->substitutions_) {
// Compute lengths once per substitution (strlen is fast, called infrequently)
const size_t from_len = strlen(sub.from);
const size_t to_len = strlen(sub.to);
std::size_t pos = 0;
// Use c_str()/size() to avoid temporary std::string allocation from implicit conversion
while ((pos = value.find(sub.from.c_str(), pos, sub.from.size())) != std::string::npos) {
value.replace(pos, sub.from.size(), sub.to.c_str(), sub.to.size());
while ((pos = value.find(sub.from, pos, from_len)) != std::string::npos) {
value.replace(pos, from_len, sub.to, to_len);
// Advance past the replacement to avoid infinite loop when
// the replacement contains the search pattern (e.g., f -> foo)
pos += sub.to.size();
pos += to_len;
}
}
return value;
@@ -90,8 +92,8 @@ MapFilter::MapFilter(const std::initializer_list<Substitution> &mappings) : mapp
optional<std::string> MapFilter::new_value(std::string value) {
for (const auto &mapping : this->mappings_) {
if (mapping.from == value) {
value.assign(mapping.to.c_str(), mapping.to.size());
if (value == mapping.from) {
value.assign(mapping.to);
return value;
}
}

View File

@@ -2,7 +2,6 @@
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/core/string_ref.h"
namespace esphome {
namespace text_sensor {
@@ -93,26 +92,26 @@ class ToLowerFilter : public Filter {
/// A simple filter that adds a string to the end of another string
class AppendFilter : public Filter {
public:
explicit AppendFilter(StringRef suffix) : suffix_(suffix) {}
explicit AppendFilter(const char *suffix) : suffix_(suffix) {}
optional<std::string> new_value(std::string value) override;
protected:
StringRef suffix_;
const char *suffix_;
};
/// A simple filter that adds a string to the start of another string
class PrependFilter : public Filter {
public:
explicit PrependFilter(StringRef prefix) : prefix_(prefix) {}
explicit PrependFilter(const char *prefix) : prefix_(prefix) {}
optional<std::string> new_value(std::string value) override;
protected:
StringRef prefix_;
const char *prefix_;
};
struct Substitution {
StringRef from;
StringRef to;
const char *from;
const char *to;
};
/// A simple filter that replaces a substring with another substring

View File

@@ -243,23 +243,6 @@ class LogStringLiteral(Literal):
return f"LOG_STR({cpp_string_escape(self.string)})"
class StringRefLiteral(Literal):
"""A StringRef literal using from_lit() for compile-time string references.
Uses StringRef::from_lit() which stores pointer + length (8 bytes) instead of
std::string (24-32 bytes + heap allocation). The string data stays in flash.
"""
__slots__ = ("string",)
def __init__(self, string: str) -> None:
super().__init__()
self.string = string
def __str__(self) -> str:
return f"StringRef::from_lit({cpp_string_escape(self.string)})"
class IntLiteral(Literal):
__slots__ = ("i",)

View File

@@ -248,12 +248,6 @@ class TestLiterals:
(cg.FloatLiteral(4.2), "4.2f"),
(cg.FloatLiteral(1.23456789), "1.23456789f"),
(cg.FloatLiteral(math.nan), "NAN"),
(cg.StringRefLiteral("foo"), 'StringRef::from_lit("foo")'),
(cg.StringRefLiteral(""), 'StringRef::from_lit("")'),
(
cg.StringRefLiteral('with "quotes"'),
'StringRef::from_lit("with \\042quotes\\042")',
),
),
)
def test_str__simple(self, target: cg.Literal, expected: str):