đŸ Archived View for heyplzlookat.me âș articles âș mehari-0-3.gmi captured on 2024-08-18 at 17:25:26. Gemini links have been rewritten to link to archived content
âŹ ïž Previous capture (2024-03-21)
-=-=-=-=-=-=-
La nouvelle mise-Ă -jour Ă©vĂšnement
Mehari 0.3 est sorti et aujourdâhui nous allons discuter de la prochaine version : Mehari 0.4 (nom de code: prostate lovers)
Pour ceux qui ont cliqué pour la version 0.3, le changelog complet est disponible ci-dessous :
https://github.com/Psi-Prod/Mehari/releases/tag/0.3
NâhĂ©sitez pas Ă faire vos remarques sur lâAPI de Mehari en commentaire.
Pondre une API bien lissĂ©e est probablement ce que je prĂ©fĂšre faire en programmation (câest notamment pour cette raison que jâaime OCaml). Pour rendre dĂ©pendant des utilisateurs de ma seule volontĂ© et percer dans le OCaml jeu, il vaut mieux Ă©crire des bibliothĂšques aussi composables que possible. Si le meilleur des pythonista se mettait Ă Ă©crire une bibliothĂšque OCaml, il produirait assurĂ©ment quelque chose de convenable mais pas trĂšs agrĂ©able Ă utiliser. Et câest bien normal car il ne connaĂźt ni les idiomes du langage, ni son Ă©cosystĂšme.
Venons-en au point, je nâaime pas trop la façon dont les paramĂštres de route sont gĂ©rĂ©s dans Mehari. Je mâexplique :
let my_route = Mehari_lwt_unix.route "/echo/(.*)" ~regex:true (fun req -> Mehari.param req 1 |> Mehari.respond_text);
Ici, on définit une route qui match tous les chemins qui commencent par `echo` et on renvoie le reste du chemin en utilisant `Mehari.param`.
Normalement, un truc a dĂ» vous piquer les yeux. Les regex sont Ă©crites en clair au lieu dâutiliser un DSL pur OCaml typesafe ! Et oui, il existe une super librairie â sobrement nommĂ© `ocaml-re` (đż) â qui permet dâĂ©crire des regex de maniĂšre encore plus incomprĂ©hensible :
Re.(seq [ str "foo="; group (rep1 any) ]) <=> "foo=(.+)
https://github.com/ocaml/ocaml-re
On va donc remplacer le paramĂštre labellisĂ© boolĂ©en `regex` par quelque chose de plus puissant pour choisir si lâon souhaite :
Pour ce faire, deux options sâoffrent Ă nous :
Utiliser un GADT signifierait devoir prĂ©ciser un paramĂštre labellisĂ© pour chaque type de route, mĂȘme lorsquâon veut gĂ©rer une route triviale comme `/articles`, ce qui nâest pas le cas actuellement dans Mehari. En effet, placer un constructeur dâun GADT en tant quâargument par dĂ©faut conduit Ă fixer le type de ce dernier et interdit logiquement dâutiliser des constructeurs dâun type diffĂ©rent :
type _ t = Int : int t | String : string t let print (type a) ?(typ=Int) (x : a) = ⊠let _ = print ~typ:String "" ^^^^^^^^^ Error: This expression has type string t but an expression was expected of type int t Type string is not compatible with type int
Si vous ĂȘtes intĂ©ressĂ© par les GADT, jâen ai dĂ©jĂ parlĂ© moins briĂšvement dans cet article :
Dans les deux cas présentés, ce serait un breaking change. Mais qui utilise Mehari pour faire tourner son serveur Gemini, sérieusement ?
Maintenant que le sujet est sur la table, je voulais mâexpliquer concernant le choix de supporter directement des regex plutĂŽt que dâimposer notre propre systĂšme comme Dream le fait. Dâun cĂŽtĂ©, Dream est plus lisible avec ses paramĂštres nommĂ©s (`/foo/:bar`) mais dâun autre cĂŽtĂ©, les expressions rĂ©guliĂšres sont plus puissantes, bien que plus « austĂšres ». Ă cela sâajoute un argument non nĂ©gligeable : on nâavait pas envie de se faire chier.
En bidouillant un peu, je me suis aperçu que la sĂ©mantique de Mehari.param est assez dĂ©plorable. En effet, cette fonction lĂšve la mĂȘme exception si lâindex est nĂ©gatif et si la paramĂštre demandĂ© nâest pas prĂ©sent dans la route. Ci-dessous un extrait de la doc :
Mehari.param req n retrieves the n-th path parameter of req.
Raise Invalid_argument if n is not a positive integer
Raise Invalid_argument if path does not contain any parameters in which case the program is buggy.
Câest donc sĂ©mantiquement impossible de distinguer une mauvaise utilisation de la fonction dâune erreur de programmation, mĂȘme en matchant sur le string contenu dans le constructeur `Invalid_argument` !
try Mehari.param req 1 with Invalid_argument msg when String.starts_with ~prefix:"xxx" -> raise "JE SUIS COMPLĂTEMENT FOU đč"
On va donc prochainement lever `Not_found` lorsque la route ne contiendra aucun paramĂštre rĂ©cupĂ©rable. Enfin on va peut-ĂȘtre ajouter une fonction Ă©quivalente qui renverra soit un `string option` en levant `Invalid_argument`, soit un `(string, [ `NotFound | `NegativeInteger]) result`. Je ne sais pas encore quelle signature est la mieux, dites-moi.
AprĂšs mâavoir laissĂ© pour le moins perplexe pendant longtemps, jâai dĂ©couvert que le module Format Ă©tait une fesserie. Je me suis ainsi convaincu quâutiliser cette machinerie dans la fonction `Gemtext.to_string` pouvait potentiellement ĂȘtre une bonne idĂ©e.
Je me suis heureusement rendu compte quâutiliser Format a la fĂącheuse tendance de ne plus faire de la fonction `Gemtext.of_string` une bijection rĂ©ciproque de `Gemtext.to_string` (car Format implĂ©mente le soft-wrap des lignes trop longues).
Pour me pardonner de mon impertinence, voici une recette pour obtenir un rendu Gemtext lisible qui rend hommage au beau, au juste et au vrai :
let pp_line ppf = let open Format in function | Text t -> pp_print_text ppf t | Link { url; name } -> fprintf ppf "@[<hov 2>â %s%a@]" url (pp_print_option (fun ppf -> fprintf ppf "@ %a" pp_print_text)) name | Preformat { alt; text } -> fprintf ppf "@[<v>%a@ %s@]" (pp_print_option pp_print_text) alt text | Heading (h, t) -> let hchar = match h with `H1 -> "â " | `H2 -> "â Ą" | `H3 -> "â ą" in fprintf ppf "@[<hov 2>%s %a@]" hchar pp_print_text t | ListItem t -> fprintf ppf "@[<hov 2>â %a@]" pp_print_text t | Quote t -> let out_funs = Format.pp_get_formatter_out_functions ppf () in pp_set_formatter_out_functions ppf { out_funs with out_indent = (fun _ -> fprintf ppf "â ")}; fprintf ppf "@[<hov 0>â %a@]" pp_print_text t; Format.pp_set_formatter_out_functions ppf out_funs let pp = Format.pp_print_list ~pp_sep:Format.pp_force_newline pp_line let gemtext = Mehari.Gemtext.[ heading `H1 "Salut la compagnie"; newline; link "https://heyplzlookat.me/" ~name:"Un super site"; newline; heading `H2 "Passions"; newline; text "Liste des passions :"; list_item "le langage de prog OCaml"; list_item "Marx"; newline; quote "De trĂšs bonnes passions" ] let () = Format.set_margin 20; (* Max 20 caractĂšres par ligne *) Format.printf "%a%!" pp gemtext
ImprĂ©gnez-vous maintenant dâesthĂ©tique, misĂ©rables misomuses :
â Salut la compagnie â https://heyplzlookat.me/ Un super site â Ą Passions Liste des passions : â le langage de prog OCaml â Marx â De trĂšs bonnes â passions
Pour encore mieux intĂ©grer mehari-mirage, jâaimerais bien ajouter une fonction pour servir statiquement des fichiers depuis une expression de type `Mirage_kv.RO`.
Il faudrait que lâon factorise le module Gemtext de Mehari dans un paquet tierce car on lâutilise Ă©galement dans Razzia, notre client Gemini secret non publiĂ© sur Opam en raison du manque de motivation pour Ă©crire sa documentation (mais qui reste malgrĂ© tout utilisable). De plus, cela nous permettrait de pouvoir tester le parser convenablement.
https://github.com/Psi-Prod/Razzia
En y repensant, je me disais quâil faudrait pouvoir personnaliser le comportement en cas de not found avec un handler mĂȘme si câest techniquement rĂ©alisable avec ce hack :
Mehari_lwt_unix.router [ âŠ; Mehari_lwt_unix.route "/(.*)" ~regex:true (fun _ -> Mehari.response not_found "not found !") ]
Pas encore de commentaires