Qué es
PyMini es un intérprete de un subconjunto de Python, escrito en Swift y metido dentro del teclado. Cada panel y cada acción tuya corre sobre él. Lee el Python que ya conoces, y el mismo lenguaje sirve tanto si un script dibuja una interfaz como si reescribe una palabra.
# an action: text in, text out
def transform(text):
return text.upper()
# a panel: a view, and what a tap does
def view(state):
return button("Shout", "shout")
def on_action(action, state):
insert(context()["word"].upper())
return statePor qué usarlo
Creamos PyMini porque Clink necesitaba que cualquiera pudiera programar su propio teclado con scripts, y nada de lo que encontramos cabía dentro de uno. Pero no tiene nada atado a los teclados, así que ahora es de código abierto y un paquete de Swift independiente, para cualquier app que quiera dejar que sus usuarios escriban un poco de Python.
No es CPython, ni lo lleva dentro. Una extensión de teclado recibe una fracción de la memoria de una app, y el binario y el montón de CPython se gastarían ese presupuesto antes de que tu script corriera. PyMini son unos cientos de kilobytes de Swift, sin dependencias binarias.
Úsalo donde tus usuarios quieran su propia lógica: transformaciones de texto en una app de escritura, reglas en una app de tareas o de presupuestos, fórmulas, automatizaciones, mods de juegos o un sistema de plugins para una app con comunidad. Mucha gente ya sabe algo de Python, así que no hay un lenguaje nuevo que enseñar, y un script es texto plano que puedes guardar, sincronizar y compartir como cualquier otro ajuste.
import PyMini
let py = PyRuntime(modules: ["math", "json", "re"])
// your app, as a Python function
py.define("shout") { call in
try call.argument(0, as: String.self).uppercased()
}
// a script one of your users wrote
try py.load("""
def tidy(text):
return shout(" ".join(text.split()))
""")
try py.call("tidy", " hello there ") // HELLO THERE| Llamada | Qué hace |
|---|---|
import PyMini | Sin dependencias y sin nada que incluir a mano. Añade el paquete de Swift y funciona en iOS 15, macOS 12, tvOS 15, watchOS 8 y visionOS 1 o posteriores. |
py.define("shout") { call in ... } | Las funciones de tu app se convierten en funciones y módulos de Python. Los argumentos llegan como tipos de Swift, y lo que devuelves vuelve al script como un valor de Python. |
PyLimits(steps: 200_000) | Cada llamada corre con un presupuesto de pasos, y agotarlo es un error que ningún script puede capturar. Un while True descuidado termina con un mensaje, no con una app congelada. |
PyRuntime(modules: ["math"]) | Los scripts no tienen archivos, ni red, ni procesos. Solo llegan a las funciones que defines y a los módulos estándar que dejas activados. |
py.call(hook: "on_open") | Llama a un script cuando pase algo en tu app. Un hook que el script no definió se omite, así que todos los hooks pueden ser opcionales. |
catch let error as PyError | Los errores vuelven con el nombre de la excepción de Python y el número de línea, listos para enseñárselos a quien escribió el script. |
import sys | sys.version da el nombre del intérprete y la versión, y el comando pymini imprime esa misma línea. Un script puede contar qué versión lleva la app que lo rodea. |
El código fuente, la referencia del lenguaje y las pruebas están en GitHub.
Qué garantiza
No hay sistema de archivos, ni red, ni proceso al que un script pueda llegar: el aislamiento es la forma del intérprete, no una lista de cosas que rechaza. import es la única puerta hacia fuera, y da a una lista corta de espacios de nombres que solo calculan. Todo lo demás cuesta pasos. Cada expresión, cada vuelta de un bucle, cada paso de una expresión regular y cada línea de JSON gasta uno de 2.000.000, y las cadenas y las listas tienen un tope al crecer.
Importable en un panel compartido
jsonmathrandomresystime
El presupuesto no se puede capturar. Una cláusula except atrapa un KeyError, pero no el aborto que termina un bucle desbocado, porque atraparlo es justo la forma en que un script desbocado seguiría corriendo.
Registro de cambios
Cada versión de PyMini, de la más reciente a la más antigua, y lo que añadió.
PyMini 2.0
La primera versión sabía poner una palabra en mayúsculas y colocar una rejilla de botones. La segunda es un lenguaje en el que puedes escribir un programa pequeño, y un panel que responde a un toque en vez de llevar uno fijo.
| Ejemplo | Qué cambió |
|---|---|
[word for word in words if word] | Comprensiones, lambdas, tuplas, conjuntos y try / except / finally / raise. |
f"{total:,.2f}" | Conversiones de f-string y especificaciones de formato completas, compartidas con str.format y format(). |
f"""Hello {name}""" | Cadenas con triples comillas, cadenas raw y f-strings que ocupan varias líneas. |
import math, random, time, json, re | Cinco espacios de nombres y ninguno más, todos ellos puros. |
re.sub(r"\s+", " ", text) | Expresiones regulares sobre el motor propio de Clink, que gasta el mismo presupuesto de pasos. |
on_action(action, value, state) | Los paneles atienden sus propios toques, guardan estado de verdad y tienen once controles más. |
replace(len(word), word.upper()) | Los scripts pueden pedirle al teclado que actúe, y leer lo que rodea al cursor. |
Current sentence | Las acciones leen cualquier porción del campo, no solo la palabra y lo que la precede. |
import re
def initial():
return {"words": []}
def view(state):
top = sorted(set(state["words"]), key=lambda w: -len(w))[:3]
return vstack([text(f"{len(state['words']):,} words", mono=True)]
+ [row(w, detail=f"{len(w)}") for w in top])
def on_open(state):
state["words"] = re.findall(r"\w+", context()["before"])
return statePyMini 1.0
La primera versión. Una acción convertía una cadena en otra, y un panel era un puñado de controles cuyos botones escribían un texto fijo o fijaban un valor.
| Ejemplo | Qué cambió |
|---|---|
def transform(text): | int, float, str, bool, None, list y dict, con if, while, for y def con argumentos por defecto y por nombre. |
f"{n} words" | f-strings sin especificaciones de formato, e indexación y cortes con índices negativos. |
sorted(text.split()) | 24 funciones integradas y los métodos habituales de str, list y dict. |
button("+", set={"count": 1}) | Texto, botones, campos, pilas y una rejilla. Un botón podía escribir texto o cambiar el estado, y nada más. |
while True: | Ningún import, un presupuesto de pasos en cada script y topes a lo que pueden crecer las cadenas y las listas. |
Por dónde seguir
El lenguaje completo, el vocabulario de los paneles, y un constructor que escribe ambos sin que teclees una línea.
Acciones ›Paneles ›Plugins ›Creador ›