Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/reflex-base/news/5417.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`StringVar` now includes `lstrip` and `rstrip` methods. The `strip` method now accepts an optional `chars` argument for consistency with Python’s str API.
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,56 @@ export const pyOr = (a, b) => (isTrue(a) ? a : b());
*/
export const pyAnd = (a, b) => (isTrue(a) ? b() : a);

/***
* Python-semantics str.lstrip: remove leading characters in the given set.
* @param {string} s The string to strip.
* @param {string?} chars Characters to remove; null/undefined strips whitespace.
* @returns {string} The stripped string.
*/
export const pyLstrip = (s, chars) => {
if (chars == null) return s.trimStart();
const charSet = new Set(chars);
let start = 0;
while (start < s.length) {
const cp = String.fromCodePoint(s.codePointAt(start));
if (!charSet.has(cp)) break;
start += cp.length;
}
return s.slice(start);
};

/***
* Python-semantics str.rstrip: remove trailing characters in the given set.
* @param {string} s The string to strip.
* @param {string?} chars Characters to remove; null/undefined strips whitespace.
* @returns {string} The stripped string.
*/
export const pyRstrip = (s, chars) => {
if (chars == null) return s.trimEnd();
const charSet = new Set(chars);
let end = s.length;
while (end > 0) {
// step back over a full code point (surrogate pairs are 2 units wide)
let cp = s[end - 1];
if (end > 1) {
const pair = String.fromCodePoint(s.codePointAt(end - 2));
if (pair.length === 2) cp = pair;
}
if (!charSet.has(cp)) break;
end -= cp.length;
}
return s.slice(0, end);
};

/***
* Python-semantics str.strip: remove leading and trailing characters in the given set.
* @param {string} s The string to strip.
* @param {string?} chars Characters to remove; null/undefined strips whitespace.
* @returns {string} The stripped string.
*/
export const pyStrip = (s, chars) =>
chars == null ? s.trim() : pyRstrip(pyLstrip(s, chars), chars);

/**
* Get the value from a ref.
* @param ref The ref to get the value from.
Expand Down
110 changes: 104 additions & 6 deletions packages/reflex-base/src/reflex_base/vars/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
from typing_extensions import TypeVar as TypingExtensionsTypeVar

from reflex_base import constants
from reflex_base.constants.base import REFLEX_VAR_OPENING_TAG
from reflex_base.constants.base import REFLEX_VAR_OPENING_TAG, Dirs
from reflex_base.utils import types
from reflex_base.utils.exceptions import VarTypeError
from reflex_base.utils.imports import ImportDict, ImportVar
from reflex_base.utils.types import GenericType, get_origin

from .base import (
Expand Down Expand Up @@ -674,6 +675,20 @@ def lower(self) -> StringVar:
"""
return string_lower_operation(self)

def lstrip(self, chars: StringVar | str | None = None) -> StringVar:
"""Left strip the string.

Args:
chars: Characters to remove from the left side. If None, strip whitespace.

Returns:
The string lstrip operation.
"""
if chars is not None and not isinstance(chars, (StringVar, str)):
raise_unsupported_operand_types("lstrip", (type(self), type(chars)))

return string_lstrip_operation(self, chars)

def upper(self) -> StringVar:
"""Convert the string to uppercase.

Expand All @@ -698,13 +713,19 @@ def capitalize(self) -> StringVar:
"""
return string_capitalize_operation(self)

def strip(self) -> StringVar:
def strip(self, chars: StringVar | str | None = None) -> StringVar:
"""Strip the string.

Args:
chars: Characters to remove from both ends. If None, strip whitespace.

Returns:
The string strip operation.
"""
return string_strip_operation(self)
if chars is not None and not isinstance(chars, (StringVar, str)):
raise_unsupported_operand_types("strip", (type(self), type(chars)))

return string_strip_operation(self, chars)

def reversed(self) -> StringVar:
"""Reverse the string.
Expand All @@ -714,6 +735,20 @@ def reversed(self) -> StringVar:
"""
return self.split().reverse().join()

def rstrip(self, chars: StringVar | str | None = None) -> StringVar:
"""Right strip the string.

Args:
chars: Characters to remove from the right side. If None, strip whitespace.

Returns:
The string rstrip operation.
"""
if chars is not None and not isinstance(chars, (StringVar, str)):
raise_unsupported_operand_types("rstrip", (type(self), type(chars)))

return string_rstrip_operation(self, chars)

def contains(
self, other: StringVar | str, field: StringVar | str | None = None
) -> BooleanVar:
Expand Down Expand Up @@ -971,17 +1006,80 @@ def string_capitalize_operation(string: StringVar[Any]):
)


_PY_STRIP_IMPORT: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="pyStrip")],
}

_PY_LSTRIP_IMPORT: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="pyLstrip")],
}

_PY_RSTRIP_IMPORT: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="pyRstrip")],
}


@var_operation
def string_strip_operation(string: StringVar[Any]):
"""Strip a string.
def string_strip_operation(
string: StringVar[Any],
chars: StringVar[Any] | str | None = None,
):
"""Strip whitespace or the given characters from both ends of a string.

Args:
string: The string to strip.
chars: The set of characters to remove. If None, strip whitespace.

Returns:
The stripped string.
"""
return var_operation_return(js_expression=f"{string}.trim()", var_type=str)
return var_operation_return(
js_expression=f"pyStrip({string}, {chars})",
var_type=str,
var_data=VarData(imports=_PY_STRIP_IMPORT),
)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Comment thread
greptile-apps[bot] marked this conversation as resolved.


@var_operation
def string_lstrip_operation(
string: StringVar[Any],
chars: StringVar[Any] | str | None = None,
):
"""Strip whitespace or the given characters from the start of a string.

Args:
string: The string to strip.
chars: The set of characters to remove. If None, strip whitespace.

Returns:
The stripped string.
"""
return var_operation_return(
js_expression=f"pyLstrip({string}, {chars})",
var_type=str,
var_data=VarData(imports=_PY_LSTRIP_IMPORT),
)


@var_operation
def string_rstrip_operation(
string: StringVar[Any],
chars: StringVar[Any] | str | None = None,
):
"""Strip whitespace or the given characters from the end of a string.

Args:
string: The string to strip.
chars: The set of characters to remove. If None, strip whitespace.

Returns:
The stripped string.
"""
return var_operation_return(
js_expression=f"pyRstrip({string}, {chars})",
var_type=str,
var_data=VarData(imports=_PY_RSTRIP_IMPORT),
)


@var_operation
Expand Down
30 changes: 30 additions & 0 deletions tests/integration/test_var_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class VarOperationState(rx.State):
str_var2: rx.Field[str] = rx.field("second")
str_var3: rx.Field[str] = rx.field("ThIrD")
str_var4: rx.Field[str] = rx.field("a long string")
str_var5: rx.Field[str] = rx.field(" spaced ")
str_var6: rx.Field[str] = rx.field("-^[a]^-stripped-^[a]^-")
strip_chars: rx.Field[str] = rx.field("-^[]a")
dict1: rx.Field[dict[int, int]] = rx.field({1: 2})
dict2: rx.Field[dict[int, int]] = rx.field({3: 4})
html_str: rx.Field[str] = rx.field("<div>hello</div>")
Expand Down Expand Up @@ -565,6 +568,24 @@ def index():
rx.text(VarOperationState.str_var3.lower(), id="str_lower"),
rx.text(VarOperationState.str_var3.upper(), id="str_upper"),
rx.text(VarOperationState.str_var4.split(" ").to_string(), id="str_split"),
rx.text(VarOperationState.str_var5.strip().to_string(), id="str_strip"),
rx.text(VarOperationState.str_var5.lstrip().to_string(), id="str_lstrip"),
rx.text(VarOperationState.str_var5.rstrip().to_string(), id="str_rstrip"),
rx.text(VarOperationState.str_var6.strip("-^[]a"), id="str_strip_chars"),
rx.text(VarOperationState.str_var6.lstrip("-^[]a"), id="str_lstrip_chars"),
rx.text(VarOperationState.str_var6.rstrip("-^[]a"), id="str_rstrip_chars"),
rx.text(
VarOperationState.str_var6.strip(VarOperationState.strip_chars),
id="str_strip_chars_var",
),
rx.text(
VarOperationState.str_var6.lstrip(VarOperationState.strip_chars),
id="str_lstrip_chars_var",
),
rx.text(
VarOperationState.str_var6.rstrip(VarOperationState.strip_chars),
id="str_rstrip_chars_var",
),
rx.text(VarOperationState.list3.join(""), id="list_join"),
rx.text(VarOperationState.list3.join(","), id="list_join_comma"),
# Index from an op var
Expand Down Expand Up @@ -938,6 +959,15 @@ def test_var_operations(driver, var_operations: AppHarness):
("str_lower", "third"),
("str_upper", "THIRD"),
("str_split", '["a","long","string"]'),
("str_strip", '"spaced"'),
("str_lstrip", '"spaced "'),
("str_rstrip", '" spaced"'),
("str_strip_chars", "stripped"),
("str_lstrip_chars", "stripped-^[a]^-"),
("str_rstrip_chars", "-^[a]^-stripped"),
("str_strip_chars_var", "stripped"),
("str_lstrip_chars_var", "stripped-^[a]^-"),
("str_rstrip_chars_var", "-^[a]^-stripped"),
# str, int
("str_mult_int", "firstfirstfirstfirstfirst"),
("str_and_int", "5"),
Expand Down
9 changes: 8 additions & 1 deletion tests/units/test_var.py
Original file line number Diff line number Diff line change
Expand Up @@ -1004,8 +1004,15 @@ def test_string_operations():

assert str(basic_string.length()) == '"Hello, World!".split("").length'
assert str(basic_string.lower()) == '"Hello, World!".toLowerCase()'
assert str(basic_string.lstrip()) == 'pyLstrip("Hello, World!", null)'
assert str(basic_string.upper()) == '"Hello, World!".toUpperCase()'
assert str(basic_string.strip()) == '"Hello, World!".trim()'
assert str(basic_string.strip()) == 'pyStrip("Hello, World!", null)'
assert str(basic_string.rstrip()) == 'pyRstrip("Hello, World!", null)'
assert str(basic_string.lstrip("!H")) == 'pyLstrip("Hello, World!", "!H")'
assert str(basic_string.strip("!H")) == 'pyStrip("Hello, World!", "!H")'
assert str(basic_string.rstrip("!H")) == 'pyRstrip("Hello, World!", "!H")'
chars_var = Var(_js_expr="state.chars").to(str)
assert str(basic_string.strip(chars_var)) == 'pyStrip("Hello, World!", state.chars)'
assert str(basic_string.contains("World")) == '"Hello, World!".includes("World")'
assert (
str(basic_string.split(" ").join(",")) == '"Hello, World!".split(" ").join(",")'
Expand Down
Loading