Outils Golo — explication
De quoi s'agit-il ?
Un menu Golo dont les commandes viennent d'un fichier TOML, chacune exécutée là où l'outil l'a demandé — un popup, une fenêtre de terminal ou une fenêtre d'édition — et les fichiers ouverts relus ensuite. Cette page explique pourquoi chacun de ces choix est ce qu'il est.
Pourquoi la sortie a trois destinations, et un popup par défaut
La première version de ce mécanisme, dans Turbo Go, mettait chaque commande dans une fenêtre de terminal, et c'était le mauvais défaut pour la plupart d'entre elles.
Un terminal est la bonne réponse quand le programme est interactif ou long : golo main.golo sur un script qui lit le clavier avec readln doit pouvoir recevoir une réponse, et un script qui sert du HTTP avec httpServe doit pouvoir être interrompu par Ctrl-C. Rien de cela n'est vrai de golo --test, qui imprime son rapport et se termine. Lui donner une fenêtre entière — qu'il faut ensuite fermer, sur un bureau où les fenêtres se chevauchent et sont numérotées — c'est plus de cérémonie que le résultat ne mérite.
Un popup est la bonne réponse pour une commande qu'on lance, qu'on lit et qu'on écarte. Il est modal, ce qui est un vrai coût et est nommé dans le guide : un gogolo build dont vous n'attendiez pas la lenteur — il lance le compilateur Go — retient l'éditeur jusqu'à ce qu'il finisse ou que vous appuyiez sur Échap. Ce coût a été accepté à dessein, parce que l'alternative — un dialogue qui surgit trois secondes plus tard — avale ce qu'on était en train de taper à l'instant où il arrive.
Le popup s'ouvre donc immédiatement et se remplit. Vous voyez la progression, rien ne vous surprend, et Échap le ferme et arrête la commande, ce qui est la seule façon d'interrompre quelque chose dont la sortie n'est pas dans un terminal.
Une fenêtre d'édition est la bonne réponse pour une sortie que vous allez parcourir : un long rapport de tests, ou le source Go que gogolo transpile imprime. C'est un tampon ordinaire, Ctrl-F le fouille et Save as le conserve. Elle est remplie une fois la commande terminée plutôt qu'au fil de l'eau, parce qu'un tampon qui grandit sous le curseur pendant qu'on le fouille est le contraire de ce pour quoi ce mode existe.
Aucune des trois ne convient à tout, c'est pourquoi output est dans le fichier plutôt que dans le code. Run est l'exemple travaillé : c'est la première commande du fichier de départ, elle dit terminal, et le commentaire à côté dit pourquoi. Debug aussi — le débogueur pas à pas lit le clavier — et REPL, qui n'est rien d'autre qu'un clavier.
Pourquoi Run vient en premier
Dans le fichier de départ de Turbo MoonBit le premier outil est moon check, parce que pour un langage compilé « est-ce correct ? » est la question posée le plus souvent et celle qui ne produit rien. Golo est un langage de script, et la question posée le plus souvent est « qu'est-ce que ça imprime ? ». La première ligne du menu Golo exécute donc le fichier, et la deuxième lance les tests.
Pourquoi une fenêtre de terminal est toujours là
L'éditeur en avait déjà une — un vrai pseudo-terminal avec un émulateur VT, construit pour les fenêtres F8 — si bien qu'output = "terminal" coûte un champ dans ses options et achète pour rien les couleurs, la pagination, Ctrl-C, la saisie clavier et le défilement arrière, parce que ce sont les mêmes mécanismes qu'utilise n'importe quel terminal. La sortie de GoloScript utilise ces couleurs : golo --test dessine des coches vertes, et la famille de builtins uiPrint dessine ce que le script a demandé.
La fenêtre reste après la fin de la commande, et c'est le but : la sortie est ce que vous avez demandé, et une fenêtre qui disparaîtrait avec elle serait inutile.
Pourquoi le code de sortie est toujours dans le titre
Un script qui se termine sans rien imprimer, ou golo --test sur un répertoire sans fichier de test, n'imprime rien du tout. Un popup au corps vide et au titre neutre ne se distingue pas d'un popup dont la commande n'a pas démarré, et le lecteur est laissé à deviner la seule chose qu'il voulait savoir.
Le titre porte donc le verdict — — ok ou — exit 1 — et un corps vide dit (no output) une fois la commande terminée. Pendant qu'elle tourne encore le corps reste vide, parce que « (no output) » est un verdict et qu'une commande en cours n'en a pas encore atteint.
Pourquoi les commandes sont dans un fichier
Huit commandes câblées dans l'éditeur auraient répondu à la demande. Elles auraient aussi été fausses en une semaine.
Chaque commande du fichier de départ passe par l'un des trois binaires de GoloScript — golo, gogolo ou wagolo — et aucune n'a donc besoin de quoi que ce soit au-delà de l'outillage lui-même. C'est un défaut défendable et ce n'est la réponse universelle de personne. Un projet à un seul point d'entrée veut golo main.golo sans qu'on lui demande quel script. Un projet livré en binaire natif veut gogolo build -o bin/app app.golo avec la sortie fixée. Un projet qui vise le navigateur veut wagolo build -target=js et jamais wasi. Un projet qui tourne sous Docker veut docker run … k33g/gololang. Rien de cela n'est connaissable d'ici, et tout cela est une ligne dans un fichier.
Les huit sont donc des défauts, pas du code : c'est le contenu du fichier de départ que Golo ▸ Create tools file écrit, et en changer une revient à éditer un fichier plutôt qu'à reconstruire un éditeur. Le fichier est lu à chaque ouverture du menu, pour la même raison que le menu Snippets : une modification doit prendre effet aussitôt, et le fichier est souvent ouvert dans la fenêtre derrière le menu.
Les commandes vont à sh -c — cmd.exe /S /C sous Windows — plutôt que d'être découpées en argv ici. Le fichier appartient à l'utilisateur, les tubes, les globs et && sont donc des fonctionnalités plutôt que des dangers, et une entrée peut être golo --test && gogolo build -o app main.golo. Découper un argv reviendrait à inventer des règles de citation pour une chaîne que quelqu'un a écrite à la main.
Pourquoi il n'y a pas de fichier d'outils au niveau utilisateur
Les snippets sont lus dans deux fichiers — le vôtre et celui du projet — parce que vos snippets sont vos habitudes et doivent vous suivre d'un projet à l'autre.
Les outils ne sont pas ainsi. Ils appartiennent à l'outillage propre d'un projet : un fichier d'outils global proposerait golo --test dans un dépôt qui n'a jamais entendu parler de Golo, et un projet qui ne fait qu'interpréter ses scripts aurait wagolo build dans son menu avec un TinyGo qu'il n'a jamais installé. Le fichier est par projet, et c'est toute la règle.
Pourquoi un outil peut nommer son propre menu
Un menu appelé Golo qui contient docker compose up est un mensonge sur ce qu'est le menu. Le premier fichier d'outils qu'on écrit dépasse Golo, parce que les commandes d'un projet ne portent pas toutes sur le langage dans lequel il est écrit : conteneurs, bases de données, déploiements, une cible de Makefile ajoutée en 2019.
Deux formes ont été envisagées. Un second menu fixe appelé Tools — tout ce qui est Golo dans Golo, tout le reste dans Tools — c'est une clé dans le format et aucun problème de nommage, mais cela ne fait que déplacer le mensonge : un menu Tools contenant docker compose up, psql et un script de déploiement est tout aussi indifférencié, et dès qu'il y a dix entrées personne n'en retrouve une. Et un second fichier, menus.toml, garde le fichier d'outils simple au prix de deux fichiers qui doivent s'accorder sur les outils existants.
Le menu est donc un nom libre sur l'outil, dans le seul fichier : menu = "Docker". Un nom que rien d'autre n'utilise crée le menu ; omettre la clé signifie Golo. Il n'y a pas de liste de noms autorisés, parce qu'une liste serait la liste des projets de quelqu'un d'autre.
Golo lui-même reste fixe sur la barre plutôt que de devenir un nom de plus venu du fichier. Golo ▸ Create tools file doit être atteignable dans un projet qui n'a aucun fichier d'outils — c'est exactement le projet qui en a besoin — et un menu qui n'existe qu'une fois le fichier là ne peut pas proposer de l'écrire.
Pourquoi la touche chaude n'est pas au fichier de la choisir
L'auteur d'un fichier d'outils ne peut pas savoir quelles lettres sont libres. Il voit File, Edit, Search, Run, Code, Options, Window, Snippets, Golo et Help sur la barre, mais seulement en comptant les soulignés, et un projet partagé entre plusieurs personnes dépendrait alors de ce que personne n'ajoute un menu qui entre en collision.
Les collisions ici sont silencieuses, et c'est ce qui rend la conception nécessaire. La barre répond au premier menu dont la touche correspond ; un second menu revendiquant la même lettre n'est pas une erreur et se dessine normalement — il ne s'ouvre simplement jamais. Ce piège s'est déjà refermé une fois dans cette famille : Snippets et Search voulaient tous deux S, Snippets était l'inatteignable, et tous les tests passaient. La correction fut alors de déplacer Snippets sur N à la main. Laisser un fichier nommer des menus en fait un danger permanent plutôt qu'une erreur ponctuelle, l'attribution est donc faite par l'éditeur : la première lettre du nom que rien d'autre ne revendique.
Les tildes écrits dans le nom sont honorés quand la lettre est libre, et remplacés sans bruit quand elle ne l'est pas. Refuser le fichier était l'alternative, et c'est pire : la collision dépend des menus existants, un fichier d'outils qui marchait casserait donc le jour où une version de l'éditeur ajoute un menu. Entre un menu sur une lettre que vous n'avez pas demandée et un menu que vous ne pouvez pas ouvrir, le premier est la moindre perte.
Quand toutes les lettres d'un nom sont prises, le menu n'a aucune touche chaude. F10, les flèches et la souris l'atteignent toujours, et l'alternative — prendre une lettre qui n'est pas dans le nom — mettrait un souligné sous rien.
Pourquoi la barre est reconstruite à partir d'un stat
Menu.OnOpen regarnit les entrées d'un menu juste avant qu'il se déroule, et c'est ainsi que les menus Golo et Snippets suivent leurs fichiers sans redémarrage. Cela ne peut pas aider ici : l'ensemble des menus fait partie de la barre, pas d'un menu en particulier, et ajouter menu = "Docker" au fichier doit mettre Docker sur la barre.
Lire et parser le fichier à chaque tour de la boucle d'événements le ferait, et ce serait aussi du travail pour rien à chaque frappe dans un fichier que personne n'a modifié. La barre porte donc la taille et la date de modification du fichier d'outils à partir duquel elle a été construite, et un stat par tour décide s'il faut la reconstruire. Éditer le fichier dans la fenêtre devant soi, l'enregistrer, et voir la barre changer, c'est le cas pour lequel c'est fait.
Pourquoi les fichiers ouverts sont relus, et seulement certains
Golo n'a pas de formateur, aucune commande de départ ne réécrit donc le fichier devant vous — mais golo new écrit un fichier dans le répertoire, gogolo build -keep-go laisse un .go à côté du script, et vos propres outils peuvent faire n'importe quoi. Sans rien de plus, l'éditeur resterait sur une copie périmée d'un fichier qu'une autre commande a changé, et le F2 suivant écrirait votre copie par-dessus le travail de la commande.
Quand une commande se termine, l'éditeur relit donc tous les fichiers ouverts. La partie intéressante est ceux qu'il refuse de toucher.
Un fichier avec des modifications non enregistrées est laissé tranquille, et la barre d'état dit combien ont été ignorés. Le recharger jetterait un travail que l'utilisateur n'a pas enregistré, ce qu'aucune commodité ne justifie. Et le conflit est réel : la commande et la modification non enregistrée ne sont pas d'accord sur ce que le fichier doit dire, et l'éditeur n'est pas en position de trancher. Le nommer et s'arrêter est l'issue honnête.
Deux décisions plus petites à l'intérieur :
- Le curseur reste où il était, ramené dans ce que le fichier contient désormais.
- L'historique d'annulation est jeté. Annuler au-delà d'un rechargement restaurerait un texte que le fichier n'a plus, ce qui est pire que ne pas pouvoir annuler du tout.
Pourquoi le rechargement se fait sur la boucle d'événements
La fin de la commande est remarquée sur la goroutine qui lit le terminal, laquelle n'a pas le droit de toucher un tampon ou le bureau. Elle pose donc un drapeau, et le rechargement s'exécute au début du tour suivant de la boucle d'événements.
C'est la quatrième chose dans la bibliothèque construite ainsi — l'annonce du serveur de langage, les redessins du terminal, l'échéance de l'enregistrement automatique, et maintenant ceci. La règle qu'elles partagent vaut d'être redite : le réveil peut se perdre, l'état ne doit pas se perdre. PostEvent laisse tomber ce qui n'entre pas dans sa file, tout ce qui dépend de l'arrivée d'un message est donc un bogue qui attend un moment d'affluence. Un drapeau que la boucle vérifie elle-même ne peut pas manquer.
Pourquoi une commande peut demander une valeur, et pourquoi entre doubles accolades
golo a besoin d'un script. golo new a besoin d'un nom de module et d'un nom de fichier. gogolo build a besoin d'un script et d'un chemin de sortie. wagolo build a besoin d'une cible en plus. Rien de cela ne peut vivre dans le fichier d'outils comme une chaîne fixe, parce que la réponse est différente chaque fois — et un outil qui ne peut pas demander est un outil qu'il faut éditer avant chaque usage, ce qui n'est pas un outil.
Un {{libellé}} dans une commande est donc une valeur que l'éditeur demande d'abord, dans une boîte titrée du nom de l'outil. Six des huit commandes Golo l'utilisent, et c'est délibéré : une fonctionnalité démontrée dans le fichier que tout le monde reçoit est une fonctionnalité que les gens trouvent, et une décrite seulement dans un commentaire ne l'est pas. C'est plus de champs que dans le fichier de départ d'aucun frère, et la raison est celle de Golo : sans manifeste il n'y a pas de moon run qui sache quoi lancer, chaque commande qui touche un fichier doit donc se faire dire lequel.
Les accolades simples étaient l'orthographe évidente et elles sont fausses. awk '{print $1}' et find . -exec rm {} + sont des choses ordinaires à mettre dans un fichier d'outils, et lire la première comme un champ transforme une commande qui marche en une boîte demandant « print $1 ». Les doubles accolades n'entrent en collision avec presque rien.
La valeur est citée par défaut, parce que l'alternative échoue en silence. Un chemin avec un espace, substitué brut, devient deux arguments et la commande se plaint d'un fichier qui n'existe pas. Citer fait marcher ce cas et rend l'autre — « mets ces trois options à la fin » — impossible, si bien que ... dans les accolades demande la valeur telle quelle. Deux comportements, tous deux documentés, plutôt qu'un seul qui a tort la moitié du temps.
Rien n'est mémorisé sur le disque. La boîte part de ce qui a été tapé la fois précédente, pour la session. L'écrire dans le répertoire propre du projet a été envisagé et rejeté : ce répertoire contient ce que le projet a décidé, et le script que quelqu'un a lancé en traquant un bogue n'en fait pas partie.
Un fichier qui ne peut pas être parsé est refusé à la lecture, pas quand l'outil est choisi. Un {{ non fermé qui atteint le shell est une commande qui échoue avec des accolades dedans, ce qui ne nomme ni l'outil ni le fichier ; refuser au chargement nomme les deux. C'est la même règle que suit déjà une valeur d'output inconnue.
Le dialogue est refusé quand il ne tient pas. Un outil qui demande plus de valeurs que le terminal n'a de lignes donnerait une boîte dont le bouton OK est sous le bas de l'écran — à laquelle on ne peut répondre que par Échap, qui annule. Dire « ceci demande douze valeurs et neuf tiennent » n'est pire que rien que si vous préférez le découvrir en essayant.
Liens avec le reste
- Chaque clé du fichier et chaque règle : Référence des outils Golo
- S'en servir : Lancer des commandes Golo depuis l'éditeur
- Les fenêtres qu'utilise
output = "terminal", et pourquoi ce sont de vrais terminaux : Fenêtres de terminal - L'autre menu construit à partir d'un fichier : Snippets
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
|