Aller au contenu

É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 : 0
droits qui diffèrent : 0

Puis exercé pour de vrai, à travers la session filtrée :

title : Dune | genre fiction
copy : champ calculé -> 'C-001 - A3'
loan : state out
out -> check_in : returned
returned -> check_in : aucune transition

Le 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

DomaineKM
Modèles, champs, relations, champs calculésoui, 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 lignesoui
Workflow, actions, automatismes, tâches planifiéesoui
Traductions, données installées et de démonstrationoui
Menus, états PDF, extension d’un autre modulepas encore (lots K3 à K5)
Lire un document, appeler une IA, parler à un service, un vrai algorithmenon, 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 :

Fenêtre de terminal
kewos km check modules/library # reads the file and says what is wrong, with the line
kewos km fmt modules/library # lays it out canonically, in place
kewos km fmt --check modules/library # writes nothing, exits non-zero if a file would change
kewos km convert modules/library_yaml --out modules/library.km
kewos km show modules/library # the documents it lowers to, as JSON
kewos km complete modules/library/library.km --line 12 --column 8

fmt 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.