Ir al contenido

Stringable

La clase Stringable es un envoltorio fluido e inmutable sobre el tipo str nativo de Python. Proporciona una amplia colección de métodos encadenables para operaciones comunes con cadenas — búsqueda, reemplazo, transformación de mayúsculas/minúsculas, codificación, validación y más — sin mutar el valor original. Al extender str, puede usarse en cualquier lugar donde se acepte una cadena regular, agregando la conveniencia expresiva de nivel framework.

from orionis.support.strings.stringable import Stringable
s = Stringable("Hello World")

Cada método que retorna texto produce una nueva instancia de Stringable, lo que permite encadenar llamadas de forma fluida:

result = Stringable(" hello world ").trim().title().finish("!")
# "Hello World!"

Retorna la porción de la cadena después de la primera (o última) aparición de un delimitador.

Stringable("foo/bar/baz").after("/") # "bar/baz"
Stringable("foo/bar/baz").afterLast("/") # "baz"

Si el delimitador no se encuentra, se retorna la cadena original.

Retorna la porción de la cadena antes de la primera (o última) aparición de un delimitador.

Stringable("foo/bar/baz").before("/") # "foo"
Stringable("foo/bar/baz").beforeLast("/") # "foo/bar"

Extrae el texto entre dos delimitadores. between utiliza la primera aparición del delimitador de inicio y la primera aparición del delimitador de fin después de este. betweenFirst se comporta de forma idéntica y se ofrece por legibilidad.

Stringable("[hello]").between("[", "]") # "hello"
Stringable("[a][b]").betweenFirst("[", "]") # "a"

Retorna un Stringable vacío cuando alguno de los delimitadores no se encuentra.

Retorna una subcadena a partir de una posición dada, opcionalmente limitada a una longitud específica.

Stringable("Hello World").substr(6) # "World"
Stringable("Hello World").substr(0, 5) # "Hello"

Toma caracteres desde el inicio (positivo) o el final (negativo) de la cadena.

Stringable("hello world").take(5) # "hello"
Stringable("hello world").take(-5) # "world"

Extrae un fragmento alrededor de la primera aparición de una frase, con radio y texto de omisión configurables.

text = Stringable("The quick brown fox jumps over the lazy dog")
text.excerpt("fox", {"radius": 5, "omission": "..."})
# "...brown fox jumps..."

Retorna None si la frase no se encuentra.

Verifica si la cadena contiene una o más subcadenas. Acepta una cadena individual o un iterable, con flag opcional de búsqueda sin distinción de mayúsculas.

Stringable("Hello World").contains("World") # True
Stringable("Hello World").contains("world", ignore_case=True) # True
Stringable("Hello World").contains(["foo", "World"]) # True

Retorna True solo cuando todas las agujas de la lista están presentes.

Stringable("hello world foo").containsAll(["hello", "world", "foo"]) # True
Stringable("hello world").containsAll(["hello", "xyz"]) # False

El inverso de contains.

Stringable("hello world").doesntContain("xyz") # True
Stringable("hello.py").endsWith(".py") # True
Stringable("hello.py").endsWith([".py", ".txt"]) # True
Stringable("hello world").startsWith("hello") # True
Stringable("hello").doesntStartWith("world") # True
Stringable("hello.py").doesntEndWith(".txt") # True

Comparación de igualdad estricta.

Stringable("hello").exactly("hello") # True
Stringable("hello").exactly("Hello") # False

Encuentra el índice de la primera aparición de una subcadena, opcionalmente comenzando desde un desplazamiento. Retorna False si no se encuentra.

Stringable("hello world").position("world") # 6
Stringable("hello world").position("xyz") # False

Métodos auxiliares para expresiones regulares:

Stringable("order-1234").match(r"\d+") # "1234"
Stringable("a1b2c3").matchAll(r"\d") # ["1", "2", "3"]
Stringable("hello").isMatch(r"^h.*o$") # True
Stringable("hello").test(r"^h.*o$") # True (alias)

Coincidencia basada en comodines (* y ?), con modo sin distinción de mayúsculas opcional.

Stringable("hello world").isPattern("hello*") # True
Stringable("Hello World").isPattern("hello*", ignore_case=True) # True

Reemplaza subcadenas con nuevos valores. Soporta listas paralelas para multi-reemplazo y modo sin distinción de mayúsculas.

Stringable("Hello World").replace("World", "Python") # "Hello Python"
Stringable("Hello World").replace("world", "Python", case_sensitive=False) # "Hello Python"
Stringable("a b c").replace(["a", "b"], ["x", "y"]) # "x y c"

Reemplaza solo la primera o última aparición.

Stringable("aaa").replaceFirst("a", "b") # "baa"
Stringable("aaa").replaceLast("a", "b") # "aab"

Reemplaza una subcadena solo si aparece como prefijo o sufijo.

Stringable("helloWorld").replaceStart("hello", "hi") # "hiWorld"
Stringable("helloWorld").replaceEnd("World", "Python") # "helloPython"

Reemplaza apariciones de una cadena de búsqueda una a una con elementos sucesivos de una lista.

Stringable("? ? ?").replaceArray("?", ["a", "b", "c"]) # "a b c"

Reemplazo basado en regex con una cadena o un callable.

Stringable("hello123world").replaceMatches(r"\d+", "NUM")
# "helloNUMworld"
Stringable("hello").replaceMatches(r"[aeiou]", lambda m: m.group(0).upper())
# "hEllO"

Elimina subcadenas completamente.

Stringable("hello world").remove("l") # "heo word"
Stringable("Hello World").remove("hello", case_sensitive=False) # " World"
Stringable("hello world").remove(["hello", " "]) # "world"

Reemplaza múltiples palabras clave usando un diccionario de mapeo.

Stringable("I love cats").swap({"cats": "dogs"}) # "I love dogs"
Stringable("HELLO World").lower() # "hello world"
Stringable("hello World").upper() # "HELLO WORLD"

Invierte las mayúsculas/minúsculas de cada carácter.

Stringable("Hello World").swapCase() # "hELLO wORLD"

Convierte entre convenciones de nomenclatura comunes.

Stringable("hello_world").camel() # "helloWorld"
Stringable("helloWorld").kebab() # "hello-world"
Stringable("helloWorld").snake() # "hello_world"
Stringable("helloWorld").snake(".") # "hello.world"
Stringable("hello_world").studly() # "HelloWorld"
Stringable("hello_world").pascal() # "HelloWorld" (alias de studly)
Stringable("hello world").title() # "Hello World"
Stringable("hello world").headline() # "Hello World"
Stringable("the quick brown fox").apa()
# "The Quick Brown Fox" — Estilo APA: palabras cortas en minúsculas excepto primera/última

Convierte a mayúscula o minúscula solo el primer carácter.

Stringable("hello world").ucfirst() # "Hello world"
Stringable("Hello World").lcfirst() # "hello World"

Conversión basada en modo numérico:

ModoEfecto
None / 0casefold
1MAYÚSCULAS
2minúsculas
3Título
Stringable("HELLO").convertCase(2) # "hello"

Genera un slug amigable para URLs. Soporta un separador personalizado y un diccionario de reemplazo de caracteres.

Stringable("Hello World!").slug() # "hello-world"
Stringable("Hello World").slug("_") # "hello_world"
Stringable("user@example").slug() # "user-at-example"
Stringable(" hello ").trim() # "hello"
Stringable("--hello--").trim("-") # "hello"
Stringable(" hello ").ltrim() # "hello "
Stringable(" hello ").rtrim() # " hello"

Envoltorios estilo Python para lstrip / rstrip que retornan un Stringable.

Stringable("xxhello").lStrip("x") # "hello"
Stringable("helloxx").rStrip("x") # "hello"

Rellena la cadena para alcanzar una longitud total deseada.

Stringable("hi").padBoth(6) # " hi "
Stringable("hi").padLeft(5) # " hi"
Stringable("5").padLeft(3, "0") # "005"
Stringable("hi").padRight(5) # "hi "

Rellena con ceros a la izquierda.

Stringable("42").zFill(5) # "00042"

Colapsa espacios en blanco consecutivos en un solo espacio y recorta.

Stringable(" hello world ").squish() # "hello world"

Colapsa apariciones consecutivas de un carácter en una sola.

Stringable("hello world").deduplicate() # "hello world"
Stringable("aabbcc").deduplicate("b") # "aabcc"
Stringable("hello").append(" world", "!") # "hello world!"
Stringable("world").prepend("hello ", "dear ") # "hello dear world"

Agrega uno o más caracteres de nueva línea.

Stringable("hello").newLine() # "hello\n"
Stringable("hello").newLine(2) # "hello\n\n"

Asegura que la cadena termina (o comienza) con un valor dado — no lo duplica si ya está presente.

Stringable("path/to").finish("/") # "path/to/"
Stringable("path/to/").finish("/") # "path/to/"
Stringable("world").start("hello ") # "hello world"
Stringable("hello").wrap('"') # '"hello"'
Stringable("hello").wrap("[", "]") # "[hello]"
Stringable('"hello"').unwrap('"') # "hello"
Stringable("[hello]").unwrap("[", "]") # "hello"
Stringable("ab").repeat(3) # "ababab"
Stringable("ab").repeat(0) # ""
Stringable("hello").reverse() # "olleh"

Trunca la cadena a un número máximo de caracteres. Un flag opcional preserve_words evita cortar a mitad de palabra.

Stringable("hello world").limit(5) # "hello..."
Stringable("hello world").limit(5, " [more]") # "hello [more]"
Stringable("hello world foo").limit(8, "...", preserve_words=True) # truncamiento seguro por palabras

Limita a un número máximo de palabras.

Stringable("one two three four").words(2) # "one two..."

Enmascara una porción de la cadena con un carácter repetido.

Stringable("password").mask("*", 2, 4) # "pa****rd"
Stringable("hello").mask("*", 2) # "he***"
Stringable("hello").mask("*", -3) # "he***"

Divide por un delimitador literal.

Stringable("a,b,c").explode(",") # ["a", "b", "c"]

Divide por una expresión regular o por longitud de fragmento.

Stringable("a1b2c3").split(r"\d") # ["a", "b", "c", ""]
Stringable("abcdef").split(2) # ["ab", "cd", "ef"]

Divide en las fronteras de mayúsculas.

Stringable("helloWorld").ucsplit() # ["hello", "World"]
Stringable("hello").length() # 5
Stringable("hello world").wordCount() # 2

Cuenta apariciones no superpuestas de una subcadena, opcionalmente dentro de una ventana offset/length.

Stringable("banana").substrCount("an") # 2

Ajusta el texto a un ancho de línea especificado.

long_text = Stringable("This is a long sentence that needs wrapping")
long_text.wordWrap(20)
Stringable("hello").toBase64() # "aGVsbG8="
Stringable("aGVsbG8=").fromBase64() # "hello"

fromBase64 acepta un parámetro solo por nombre strict. Cuando strict=True, una entrada inválida lanza un RuntimeError en lugar de retornar una cadena vacía.

Métodos de hashing convenientes que retornan cadenas de resumen hexadecimal.

Stringable("hello").md5() # resumen hex de 32 caracteres
Stringable("hello").sha1() # resumen hex de 40 caracteres
Stringable("hello").sha256() # resumen hex de 64 caracteres

Aplica hash con cualquier algoritmo soportado por hashlib.

Stringable("hello").hash("sha256") # equivalente a .sha256()
Stringable("<b>Hello</b>").toHtmlString() # "&lt;b&gt;Hello&lt;/b&gt;"
Stringable("<p>Hello <b>World</b></p>").stripTags() # "Hello World"

Elimina o reemplaza caracteres no ASCII.

Stringable("café").ascii() # "cafe"
Stringable("café").transliterate("?", strict=True) # "caf?"

Cifra y descifra mediante la fachada Crypt del framework. Requiere que el servicio Encrypter esté registrado.

encrypted = Stringable("secret").encrypt()
decrypted = encrypted.decrypt()
Stringable("42").toInteger() # 42
Stringable("0xff").toInteger(16) # 255
Stringable("3.14").toFloat() # 3.14
Stringable("true").toBoolean() # True
Stringable("0").toBoolean() # False

Retorna el str plano subyacente.

Stringable("hello").value() # "hello" (tipo: str)

Retorna un str plano apto para codificación JSON.

Stringable("hello").jsonSerialize() # "hello"
Stringable("").isEmpty() # True
Stringable("hello").isNotEmpty() # True
MétodoRetorna True cuando…
isAlnum()Todos los caracteres son alfanuméricos
isAlpha()Todos los caracteres son alfabéticos
isDecimal()Todos los caracteres son dígitos decimales
isDigit()Todos los caracteres son dígitos
isIdentifier()Es un identificador Python válido
isLower()Todos los caracteres con casing están en minúsculas
isUpper()Todos los caracteres con casing están en mayúsculas
isNumeric()Todos los caracteres son numéricos
isPrintable()Todos los caracteres son imprimibles
isSpace()Solo caracteres de espacio en blanco
isTitle()Cadena en formato título
isAscii()Solo caracteres ASCII de 7 bits
Stringable('{"key": "value"}').isJson() # True
Stringable("not json").isJson() # False

Valida una URL, opcionalmente restringiendo a protocolos específicos.

Stringable("https://example.com").isUrl() # True
Stringable("ftp://example.com").isUrl(protocols=["ftp"]) # True
Stringable("550e8400-e29b-41d4-a716-446655440000").isUuid() # True
Stringable("550e8400-e29b-41d4-a716-446655440000").isUuid(4) # True (versión 4)
Stringable("01ARZ3NDEKTSV4RRFFQ69G5FAV").isUlid() # True

Reglas básicas de pluralización del inglés.

Stringable("cat").plural() # "cats"
Stringable("cat").plural(1) # "cat"
Stringable("baby").plural() # "babies"
Stringable("bus").plural() # "buses"
Stringable("cat").plural(3, prepend_count=True) # "3 cats"
Stringable("cats").singular() # "cat"
Stringable("babies").singular() # "baby"

Pluraliza la última palabra de una cadena en StudlyCase o PascalCase.

Stringable("BlogPost").pluralStudly() # "BlogPosts"
Stringable("UserProfile").pluralPascal() # "UserProfiles"

Analiza una cadena estilo Class@method en sus componentes.

Stringable("MyClass@myMethod").parseCallback() # ["MyClass", "myMethod"]
Stringable("MyClass").parseCallback("handle") # ["MyClass", "handle"]
Stringable("MyClass").parseCallback() # ["MyClass", None]
Stringable("/home/user/file.txt").basename() # "file.txt"
Stringable("/home/user/file.txt").basename(".txt") # "file"
Stringable("/home/user/file.txt").dirname() # "/home/user"
Stringable("hello").offsetExists(2) # True
Stringable("hello").offsetExists(99) # False
Stringable("hello").offsetGet(1) # "e"
Stringable("hello").charAt(0) # "h"
Stringable("hello").charAt(99) # False

Stringable también soporta indexación y slicing estándar:

Stringable("hello")[1] # Stringable("e")
Stringable("hello")[1:4] # Stringable("ell")

Elimina todos los caracteres no numéricos.

Stringable("phone: +1-234-567").numbers() # "1234567"

Extrae valores usando un formato simplificado estilo sscanf con los marcadores %s, %d y %f.

Stringable("John 30 5.9").scan("%s %d %f") # ["John", "30", "5.9"]

Reemplaza texto dentro de un rango específico de la cadena usando offset y longitud.

Stringable("hello world").substrReplace("Python", 6, 5) # "hello Python"

Todos los métodos when* aceptan un callback y un default opcional. El callback recibe el Stringable actual y su valor de retorno se convierte en la nueva cadena. Si la condición es False y no se proporciona default, se retorna la cadena original sin cambios.

Ejecuta un callback condicionalmente basado en una condición booleana o callable.

Stringable("hello").when(True, lambda s: s.upper()) # "HELLO"
Stringable("hello").when(False, lambda s: s.upper()) # "hello"
Stringable("hello").when(lambda s: s.isNotEmpty(), lambda s: s.upper()) # "HELLO"
Stringable("hello world").whenContains("world", lambda s: s.upper())
# "HELLO WORLD"
Stringable("").whenEmpty(lambda s: Stringable("default")) # "default"
Stringable("hello").whenNotEmpty(lambda s: s.upper()) # "HELLO"
Stringable("hello world").whenStartsWith("hello", lambda s: s.upper())
# "HELLO WORLD"
Stringable("hello.py").whenEndsWith(".py", lambda s: s.upper())
# "HELLO.PY"
Stringable("hello").whenExactly("hello", lambda s: s.upper()) # "HELLO"
Stringable("hello").whenNotExactly("world", lambda s: s.upper()) # "HELLO"

whenTest / whenIs / whenIsAscii / whenIsUuid / whenIsUlid

Sección titulada «whenTest / whenIs / whenIsAscii / whenIsUuid / whenIsUlid»
Stringable("hello123").whenTest(r"\d+", lambda s: s.upper()) # "HELLO123"

Pasa la cadena a través de un callback y retorna el resultado.

Stringable("hello").pipe(lambda s: s.upper()) # "HELLO"

Ejecuta un callback de efecto secundario sin modificar la cadena. Útil para logging o depuración dentro de una cadena de llamadas.

Stringable("hello").tap(lambda s: print(s)).upper() # imprime "hello", retorna "HELLO"

Convierte la cadena en un objeto datetime usando el formato especificado (por defecto %Y-%m-%d). El datetime resultante incluye zona horaria usando la zona local del framework.

Stringable("2026-04-01").toDate() # datetime(2026, 4, 1, ...)
Stringable("01/04/2026").toDate("%d/%m/%Y") # datetime(2026, 4, 1, ...)

Lanza ValueError si la cadena no coincide con el formato.