"""Text built-in functions."""
from __future__ import annotations
from typing import Any
from vimsheet.formula.functions.registry import register
ERR = "#ERR"
TYPE_ERR = "#TYPE"
NA = "#N/A"
[docs]
@register("CONCAT", "CONCATENATE", desc="Join strings together")
def fn_concat(*args: Any) -> Any:
return "".join(str(a) if a is not None else "" for a in args)
[docs]
@register("LEN", desc="Length of string")
def fn_len(s: Any) -> Any:
return len(str(s)) if s is not None else 0
[docs]
@register("UPPER", desc="Convert to uppercase")
def fn_upper(s: Any) -> Any:
return str(s).upper() if s is not None else ""
[docs]
@register("LOWER", desc="Convert to lowercase")
def fn_lower(s: Any) -> Any:
return str(s).lower() if s is not None else ""
[docs]
@register("PROPER", desc="Title case each word")
def fn_proper(s: Any) -> Any:
return str(s).title() if s is not None else ""
[docs]
@register("TRIM", desc="Remove leading/trailing spaces")
def fn_trim(s: Any) -> Any:
return str(s).strip() if s is not None else ""
[docs]
@register("LEFT", desc="First N chars. =LEFT('hello',2)->'he'")
def fn_left(s: Any, n: Any = 1) -> Any:
return str(s)[: int(n)] if s is not None else ""
[docs]
@register("RIGHT", desc="Last N chars")
def fn_right(s: Any, n: Any = 1) -> Any:
return str(s)[-int(n) :] if s is not None else ""
[docs]
@register("MID", desc="Chars from middle")
def fn_mid(s: Any, start: Any, n: Any) -> Any:
try:
st = int(start) - 1 # 1-based
return str(s)[st : st + int(n)] if s is not None else ""
except (TypeError, ValueError):
return TYPE_ERR
[docs]
@register("FIND", desc="Position of substring")
def fn_find(find: Any, s: Any, start: Any = 1) -> Any:
try:
idx = str(s).find(str(find), int(start) - 1)
return idx + 1 if idx >= 0 else NA
except (TypeError, ValueError):
return TYPE_ERR
[docs]
@register("REPLACE", desc="Replace chars at position")
def fn_replace(s: Any, start: Any, n: Any, new: Any) -> Any:
try:
sv = str(s) if s is not None else ""
st = int(start) - 1
return sv[:st] + str(new) + sv[st + int(n) :]
except (TypeError, ValueError):
return TYPE_ERR
[docs]
@register("SUBSTITUTE", desc="Replace substring")
def fn_substitute(s: Any, old: Any, new: Any, occurrence: Any = None) -> Any:
sv = str(s) if s is not None else ""
ov, nv = str(old), str(new)
if occurrence is None:
return sv.replace(ov, nv)
try:
n = int(occurrence)
count = 0
idx = 0
while True:
found = sv.find(ov, idx)
if found == -1:
break
count += 1
if count == n:
return sv[:found] + nv + sv[found + len(ov) :]
idx = found + len(ov)
return sv
except (TypeError, ValueError):
return TYPE_ERR
[docs]
@register("TEXT", desc="Format number as text")
def fn_text(val: Any, fmt: Any) -> Any:
try:
return format(val, str(fmt))
except Exception:
return str(val)
[docs]
@register("VALUE", desc="Convert text to number")
def fn_value(s: Any) -> Any:
sv = str(s).strip() if s is not None else ""
try:
return int(sv)
except ValueError:
pass
try:
return float(sv)
except ValueError:
return TYPE_ERR
[docs]
@register("REPT", "REPEAT", desc="Repeat string N times")
def fn_rept(s: Any, n: Any) -> Any:
try:
return str(s) * int(n)
except (TypeError, ValueError):
return TYPE_ERR
[docs]
@register("EXACT", desc="True if strings match")
def fn_exact(a: Any, b: Any) -> Any:
return str(a) == str(b)
[docs]
@register("CHAR", desc="Char from ASCII code")
def fn_char(n: Any) -> Any:
try:
return chr(int(n))
except (TypeError, ValueError, OverflowError):
return ERR
[docs]
@register("CODE", desc="ASCII code of first char")
def fn_code(s: Any) -> Any:
sv = str(s) if s is not None else ""
if not sv:
return ERR
return ord(sv[0])