Source code for triad.utils.schema

from typing import Iterable, List, Tuple

from triad.constants import TRIAD_VAR_QUOTE

from .assertion import assert_or_throw
from .string import validate_triad_var_name

[docs]def unquote_name(name: str, quote: str = TRIAD_VAR_QUOTE) -> str: """If the input is quoted, then get the inner string, otherwise do nothing. :param name: the name string :param quote: the quote char, defaults to ` :return: the value without ` """ if validate_triad_var_name(name): return name if len(name) >= 2 and name[0] == name[-1] == quote: return name[1:-1].replace(quote + quote, quote) name = name.strip() assert_or_throw( len(name) > 0, ValueError("empty string is invalid"), ) return name
[docs]def quote_name(name: str, quote: str = TRIAD_VAR_QUOTE) -> str: """Add quote ` for strings that are not a valid triad var name. :param name: the name string :param quote: the quote char, defaults to ` :return: the quoted(if necessary) string """ if validate_triad_var_name(name): return name return quote + name.replace(quote, quote + quote) + quote
[docs]def move_to_unquoted(expr: str, p: int, quote=TRIAD_VAR_QUOTE) -> int: """When ``p`` is on a quote, find the position next to the end of the quoted part :param expr: the original string :param p: the current position of ``expr``, and it should be a quote :param quote: the quote character :raises SyntaxError: if there is an open quote detected :return: the position next to the end of the quoted part """ e = p + 1 le = len(expr) while e < le: if expr[e] == quote: if e + 1 < le and expr[e + 1] == quote: # escape (``) e = e + 2 else: return e + 1 else: e += 1 raise SyntaxError(f"{expr} contains open quote {quote}")
[docs]def split_quoted_string( s: str, quote=TRIAD_VAR_QUOTE ) -> Iterable[Tuple[bool, int, int]]: """Split ``s`` to a sequence of quoted and unquoted parts. :param s: the original string :param quote: the quote character :yield: the tuple in the format of ``is_quoted, start, end`` """ b, e = 0, 0 le = len(s) while e < le: if s[e] == quote: if e > b: yield False, b, e b = e e = move_to_unquoted(s, e, quote=quote) yield True, b, e b = e else: e += 1 if b < le: yield False, b, le
[docs]def safe_search_out_of_quote( s: str, chars: str, quote=TRIAD_VAR_QUOTE ) -> Iterable[Tuple[int, str]]: """Search for chars out of the quoted parts :param s: the original string :param chars: the charaters to find :param quote: the quote character :yield: the tuple in format of ``position, char`` """ for rg in split_quoted_string(s, quote=quote): if not rg[0]: for i in range(rg[1], rg[2]): if s[i] in chars: yield i, s[i]
[docs]def safe_replace_out_of_quote( s: str, find: str, replace: str, quote=TRIAD_VAR_QUOTE ) -> str: """Replace strings out of the quoted part :param s: the original string :param find: the string to find :param replace: the string used to replace :param quote: the quote character :return: the string with the replacements """ return "".join( s[rg[1] : rg[2]] if rg[0] else s[rg[1] : rg[2]].replace(find, replace) for rg in split_quoted_string(s, quote=quote) )
[docs]def safe_split_out_of_quote( s: str, sep_chars: str, max_split: int = -1, quote: str = TRIAD_VAR_QUOTE, ) -> List[str]: b = 0 if max_split == 0 or len(s) == 0: return [s] res: List[str] = [] for p, _ in safe_search_out_of_quote(s, sep_chars, quote=quote): res.append(s[b:p]) b = p + 1 if len(res) == max_split: break res.append(s[b:]) return res
[docs]def safe_split_and_unquote( s: str, sep_char: str = ",", quote: str = TRIAD_VAR_QUOTE, on_unquoted_empty: str = "keep", ) -> List[str]: """Split the string and unquote every part .. admonition:: Examples ``" a , ` b ` , c "`` => ``["a", " b ","c"]`` :param s: the original string :param sep_char: the split character, defaults to "," :param quote: the quote character :param on_unquoted_empty: can be ``keep``, ``ignore`` or ``throw``, defaults to "keep" :raises ValueError: if there are empty but unquoted parts and ``on_unquoted_empty`` is ``throw`` :return: the unquoted parts. """ res: List[str] = [] for _s in safe_split_out_of_quote(s, sep_char, quote=quote): s = _s.strip() if s == "": if on_unquoted_empty == "keep": res.append(s) elif on_unquoted_empty == "ignore": continue else: raise ValueError(f"empty is not allowed in {s}") else: res.append(unquote_name(s, quote=quote)) return res