Ce que c'est
PyMini est un interpréteur d'un sous-ensemble de Python, écrit en Swift et intégré au clavier. Chacun de vos panneaux et chacune de vos actions tourne dessus. Il lit le Python que vous connaissez déjà, et le même langage sert qu'un script dessine une interface ou réécrive un mot.
# 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 statePourquoi l'utiliser
Nous avons créé PyMini parce que Clink avait besoin d'un moyen pour chacun de programmer son propre clavier, et rien de ce que nous avons trouvé ne tenait dans un clavier. Pourtant rien en lui n'est lié aux claviers : il est donc désormais open source, sous forme de package Swift indépendant, pour toute app qui veut laisser ses utilisateurs écrire un peu de Python.
Ce n'est pas CPython, et il ne l'embarque pas. Une extension de clavier reçoit une fraction de la mémoire d'une app, et le binaire et le tas de CPython dépenseraient ce budget avant même que votre script tourne. PyMini, c'est quelques centaines de kilo-octets de Swift, sans aucune dépendance binaire.
Il a sa place partout où vos utilisateurs veulent leur propre logique : transformations de texte dans une app d'écriture, règles dans une app de tâches ou de budget, formules, automatisations, mods de jeux, ou système de plugins pour une app qui a sa communauté. Beaucoup de gens connaissent déjà un peu de Python, il n'y a donc pas de nouveau langage à enseigner, et un script est du texte brut que vous pouvez stocker, synchroniser et partager comme n'importe quel réglage.
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| Appel | Ce qu’il fait |
|---|---|
import PyMini | Aucune dépendance, rien à embarquer à la main. Ajoutez le package Swift et il fonctionne sur iOS 15, macOS 12, tvOS 15, watchOS 8 et visionOS 1 ou version ultérieure. |
py.define("shout") { call in ... } | Les fonctionnalités de votre app deviennent des fonctions et des modules Python. Les arguments arrivent en types Swift, et ce que vous renvoyez revient au script sous forme de valeur Python. |
PyLimits(steps: 200_000) | Chaque appel tourne avec un budget de pas, et l'épuiser est une erreur qu'aucun script ne peut rattraper. Un while True maladroit se termine par un message, pas par une app figée. |
PyRuntime(modules: ["math"]) | Les scripts n'ont ni fichiers, ni réseau, ni processus. Ils n'atteignent que les fonctions que vous définissez et les modules standard que vous laissez activés. |
py.call(hook: "on_open") | Appelez un script quand quelque chose se passe dans votre app. Un hook que le script n'a pas défini est simplement ignoré, donc chaque hook peut être facultatif. |
catch let error as PyError | Les erreurs reviennent avec le nom de l'exception Python et un numéro de ligne, prêtes à être montrées à la personne qui a écrit le script. |
import sys | sys.version donne le nom de l'interpréteur et la version, et la commande pymini affiche la même ligne. Un script peut ainsi dire quelle version embarque l'app qui l'exécute. |
Le code source, la référence du langage et les tests sont sur GitHub.
Ce qu'il garantit
Aucun système de fichiers, aucun réseau, aucun processus n'est accessible depuis un script : le bac à sable est la forme de l'interpréteur, pas une liste de choses qu'il refuse. import est la seule porte de sortie, et elle donne sur une courte liste d'espaces de noms qui ne font que calculer. Tout le reste coûte des pas. Chaque expression, chaque tour de boucle, chaque pas d'une expression régulière et chaque ligne de JSON en dépense un sur 2 000 000, et les chaînes comme les listes sont plafonnées à mesure qu'elles grossissent.
Importable dans un panneau partagé
jsonmathrandomresystime
Le budget ne se rattrape pas. Une clause except attrape une KeyError, mais pas l'abandon qui met fin à une boucle emballée : l'attraper serait précisément le moyen pour un script emballé de continuer à tourner.
Journal des modifications
Chaque version de PyMini, de la plus récente à la plus ancienne, et ce qu'elle a ajouté.
PyMini 2.0
La première version savait mettre un mot en majuscules et poser une grille de boutons. La seconde est un langage dans lequel écrire un petit programme, et un panneau qui répond à une touche au lieu d'en porter une figée.
| Exemple | Ce qui a changé |
|---|---|
[word for word in words if word] | Compréhensions, lambdas, tuples, ensembles, et try / except / finally / raise. |
f"{total:,.2f}" | Conversions de f-string et spécifications de format complètes, partagées avec str.format et format(). |
f"""Hello {name}""" | Chaînes entre triples guillemets, chaînes brutes, et f-strings sur plusieurs lignes. |
import math, random, time, json, re | Cinq espaces de noms, et aucun autre, tous purs. |
re.sub(r"\s+", " ", text) | Des expressions régulières sur le moteur de Clink, qui dépense le même budget de pas. |
on_action(action, value, state) | Les panneaux traitent eux-mêmes leurs touches, gardent un véritable état, et comptent onze contrôles de plus. |
replace(len(word), word.upper()) | Les scripts peuvent demander au clavier d'agir, et lire ce qui entoure le curseur. |
Current sentence | Les actions lisent n'importe quelle portion du champ, pas seulement le mot et ce qui le précède. |
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 première version. Une action transformait une chaîne en une autre, et un panneau se résumait à quelques contrôles dont les boutons tapaient un texte fixe ou réglaient une valeur.
| Exemple | Ce qui a changé |
|---|---|
def transform(text): | int, float, str, bool, None, list et dict, avec if, while, for, et def avec arguments par défaut et nommés. |
f"{n} words" | Des f-strings sans spécification de format, l'indexation et le découpage avec indices négatifs. |
sorted(text.split()) | 24 fonctions intégrées et les méthodes courantes de str, list et dict. |
button("+", set={"count": 1}) | Du texte, des boutons, des champs, des piles et une grille. Un bouton pouvait taper du texte ou modifier l'état, rien de plus. |
while True: | Aucun import, un budget de pas pour chaque script, et des plafonds sur la taille que peuvent atteindre chaînes et listes. |
Où aller ensuite
Le langage au complet, le vocabulaire des panneaux, et un atelier qui écrit les deux sans que vous tapiez une ligne.
Actions ›Panneaux ›Plugins ›Créateur ›