Écrire en KM
Python demande de programmer. Le YAML demande d’accepter une arborescence de huit fichiers et une indentation qui compte. KM est une troisième porte : un module tient dans un seul fichier, qu’on lit de haut en bas.
Ce n’est pas un moteur. Un fichier .km est lu, traduit vers exactement les documents que le
chargeur YAML consomme déjà, et remis au même chargeur. Aucune sémantique nouvelle, aucune
sécurité nouvelle, aucun eval.
.kw ──▶ ┐ .yaml ──▶ ├──▶ les mêmes documents ──▶ les vraies fonctions du noyau .py ────┘Le module de bibliothèque, en entier
Voici le module des étapes 1 à 6 de ce parcours, dans sa forme KM. C’est le fichier réel : il s’installe, il crée les mêmes tables et il donne les mêmes écrans.
module library { title "Library" version 0.1.0 depends core}
model library_title "model.library_title" { taggable gallery cover
field name text required max 200 field author text required max 120 field isbn text unique max 17 field published_year number field genre choice fiction "Fiction" | science "Science" | history "History" | children "Children" | reference "Reference" default fiction field summary text field cover file extensions png, jpg, jpeg, webp, svg maxsize 5242880
search title from name subtitle from author}
model library_copy "model.library_copy" { field title_id link to library_title required field barcode text required unique max 32 field shelf text max 16 field condition choice new "New" | good "Good" | worn "Worn" | lost "Lost" default good field acquired_on date
compute name text max 64 stored = concat(barcode, ' - ', shelf)}
view library_title list { columns name, author, genre, published_year, isbn}
view library_title form { section section.work fields name, author, genre, published_year, summary section section.reference fields isbn, cover}
view library_loan timeline { start taken_on end due_on label member_id groups by state zoom week}
group library_reader "group.library_reader"group library_librarian "group.library_librarian"
access library_title { library_reader read library_librarian read, create, write, unlink kws_admin read, create, write, unlink}
workflow library_loan_lifecycle on library_loan { start out states out, returned, lost
transition check_in from out to returned transition declare_lost from out to lost}
translate fr { model.library_title "Catalogue" section.work "L'œuvre" workflow.check_in "Rendre"}Quatre modèles, douze écrans, quatre jeux de droits, un cycle de vie et deux langues, dans un fichier. C’est le seul argument qui compte face au YAML.
Ce que ça donne, et comment on le sait
Le module KM a été installé à côté de son jumeau Python, sur la même base :
tables identiques : oui (library_title, library_copy, library_member, library_loan)colonnes qui diffèrent : 0droits qui diffèrent : 0Puis exercé pour de vrai, à travers la session filtrée :
title : Dune | genre fictioncopy : champ calculé -> 'C-001 - A3'loan : state outout -> check_in : returnedreturned -> check_in : aucune transitionLe champ calculé mérite un regard. Le jumeau Python le produit avec une fonction ; ici
c’est l’expression concat(barcode, ' - ', shelf), et le résultat est le même. C’est tout
l’argument du format : les quelques pour cent de logique passent par le langage d’expressions
qui existe déjà, sans que KM ait à savoir programmer.
La grammaire en dix minutes
Un module, des modèles. Un bloc s’ouvre par un mot-clé et une accolade. Une instruction tient sur une ligne et s’arrête à la fin de la ligne : pas de tirets, pas d’indentation qui compte.
Un champ est une phrase. field code text required unique max 32 se lit. Les types portent
les noms qu’une personne dirait : text, number, money, date, yesno, choice,
file. Les relations sont des tournures : link to, has many … via, many to many,
count of.
Les noms du noyau restent valables. char, integer, selection fonctionnent aussi : le
format ajoute des mots, il n’en cache aucun.
Une longue liste continue à la ligne quand la coupure tombe sur | ou ,.
Les expressions ne sont pas réinventées. concat(a, b), count(lines), today(),
user.name : c’est le langage d’expressions du noyau, transporté tel quel.
Les erreurs sont le vrai livrable
Un langage se juge sur ses erreurs. Celui-ci nomme la ligne, montre la ligne fautive, pointe la colonne et propose le mot qui aurait marché :
line 6: field 'name': unknown type 'txt' field name txt ^ did you mean 'text'?Quand aucun mot n’est assez proche, il liste ceux qui existent au lieu de deviner. Un bloc du
langage qui n’est pas encore implémenté est refusé par son nom, avec son lot : « menu
fait partie du langage mais n’est pas encore implémenté — lot K3 ». Ni « mot-clé inconnu », qui
se lirait comme une faute de frappe, ni un silence, qui se lirait comme une fonctionnalité
livrée.
Ce que KM couvre, et ce qu’il ne couvre pas
| Domaine | KM |
|---|---|
| Modèles, champs, relations, champs calculés | oui, les 20 types du format déclaratif |
| Écrans (liste, formulaire, kanban, calendrier, frise, charge, croisé, tableau de bord) | oui |
| Groupes, droits par modèle, restrictions de champ, règles de lignes | oui |
| Workflow, actions, automatismes, tâches planifiées | oui |
| Traductions, données installées et de démonstration | oui |
| Menus, états PDF, extension d’un autre module | pas encore (lots K3 à K5) |
| Lire un document, appeler une IA, parler à un service, un vrai algorithme | non, et c’est voulu |
La dernière ligne n’est pas une lacune à combler. Un langage qui exprime un calcul arbitraire est un langage de programmation, et il serait moins bon que Python. La réponse prévue est une porte de sortie : le fichier nomme un traitement, il ne le contient pas, et le noyau le résout. Elle arrive au lot K8.
Les outils
Quatre commandes, toutes sur un fichier ou sur un dossier :
kewos km check modules/library # reads the file and says what is wrong, with the linekewos km fmt modules/library # lays it out canonically, in placekewos km fmt --check modules/library # writes nothing, exits non-zero if a file would changekewos km convert modules/library_yaml --out modules/library.kmkewos km show modules/library # the documents it lowers to, as JSONkewos km complete modules/library/library.km --line 12 --column 8fmt décide de la mise en page pour que personne n’ait à le faire : indentation par
profondeur, une colonne pour les noms de champs, une ligne vide entre les blocs. Formater
deux fois ne change rien, et les commentaires restent où vous les avez mis. Un fichier
que l’analyseur ne sait pas lire est rendu intact : c’est check qui vous dira pourquoi.
--check est la forme à mettre dans une intégration continue. Elle ne touche à rien et
nomme les fichiers qui ne sont pas à la bonne forme.
La porte de sortie
KM ne contient pas de code, et n’en contiendra jamais. Pour les quelques pour cent de logique qui demandent une vraie fonction, le fichier nomme un traitement au lieu de l’écrire :
handler invoice_loan in billing
action invoice on library_loan "action.invoice" { run invoice_loan}in billing désigne le fichier billing.py du dossier de ce module, et rien d’autre. Un
point, une barre oblique ou un .. sont refusés à la lecture : l’espace de noms atteignable
est le dossier du module. Un module écrit depuis Builder vit en base et n’a pas de dossier,
donc la porte n’existe pas pour lui.
Si le fichier ou la fonction manque, le module refuse de charger. C’est voulu : un bouton qui échoue le jour où quelqu’un l’utilise coûte plus cher qu’un module qui refuse de démarrer.
Et comme toutes les portes sont déclarées en tête du fichier, on voit d’un coup d’œil tous les endroits où du code s’exécute. Le prix à connaître : un module qui utilise la porte n’est plus « que de la donnée », il embarque du Python, et c’est la signature du paquet qui en répond. Voir l’ADR 0300.
Choisir
Rien ne vous oblige à choisir une fois pour toutes : les trois formats cohabitent dans la même installation, se voient par les événements et le registre, et se déploient de la même façon. Voir Python, YAML ou KM.