Initialement j’utilisais Cobra sur mon projet GoStamp, qui est mon outil CLI (Command Line Interface) qui permet de compresser, ajouter des filigranes, gérer les licences et modifier les métadonnées de gros batchs d’images, mais j’y reviendrai probablement dans un article dédié.
Cobra donc, est une librairie qui permet de créer des CLI, c’est une des librairies les plus connues et un standard en golang pour ce type d’application.
Mon problème avec Cobra c’est la taille de cette librairie (une fois compilée) et les allocations mémoire masquées assez lourdes (Notamment à cause de la gestion des flags avec des clés textuelles).
Pour mon cas d’usage, en réécrivant une librairie, j’ai choisi de passer les valeurs de flags avec des pointeurs directs plutôt qu’avec une map intermédiaire, cela permet de limiter les réallocations.
Aussi j’ai fait des choix d’architecture pour minimiser la taille de la librairie, notamment dans le parsing des flags qui utilise une machine à états pour vérifier si un flag a été détecté précédemment ou si c’est un nouveau flag.
Un autre choix d’architecture pour cette librairie : limiter les types (statiques dans la librairie) à 6 types principaux qui répondent à tous les cas d’usages principaux pour ce genre d’outil :
Cobra ou d’autres parseurs, pour des raisons qui leur sont propres (certainement de la facilité ou pour rester conforme au reste de la librairie), ont fait le choix de stocker les short flags (-p ou -a) sous forme de string, ce qui oblige le garbage collector de Go à travailler.
De mon côté j’ai choisi de garder simplement le caractère en entier stocké sur la pile (ne passe pas par le garbage collector).
L’API est pensée pour être la plus fluide et explicite possible. Tu déclares ta commande, tu lies tes variables directement via leurs pointeurs dans le init(), et c’est tout. Zéro intermédiaire textuel.
package cmd
import (
"fmt"
"github.com/RoyS122/LiteCLI"
)
var (
outputPath string
quality int
verbose bool
)
var ProcessCmd = &litecli.Command{
Name: "process",
Short: "Compresse et traite un lot d'images",
Run: func(cmd *litecli.Command, args []string) {
// Tes flags sont déjà typés et injectés ici !
fmt.Printf("Traitement du dossier : %v (Qualité : %d%%)\n", args, quality)
},
}
func init() {
// Le binding direct par pointeur : propre, safe et sans allocation
ProcessCmd.StringVarP(&outputPath, "output", 'o', "./dist")
ProcessCmd.IntVarP(&quality, "quality", 'q', 80)
ProcessCmd.BoolVarP(&verbose, "verbose", 'v', false)
}
Après mon travail ce week-end sur cette petite librairie j’ai testé la différence entre mon binaire avec cobra et mon binaire sans :
Le binaire avec cobra pèse 6 Mo (cobra représente une petite partie de ça car j’ai les libs d’images qui pèsent assez lourd).
Le binaire sans cobra pèse 5.1 Mo ce qui représente une diminution de ~ 15 %.
0 dépendance pour mon projet GoStamp.
Pour la petite histoire, j’ai développé une grosse partie de cette librairie en stream sur Twitch. C’est toujours un exercice sympa de partager ce genre de session de dev brut, de bloquer sur des machines à états, et de peaufiner les optimisations en direct avec le chat.
Si tu veux suivre les prochains streams ou voir l’envers du décor de mes projets (comme le dev de GoStamp), n’hésite pas à venir faire un tour sur ma chaîne Twitch et tu peux aller voir le replay du stream ici.
Une fois terminée, car oui je l’ai terminée, boosté après avoir regardé la conférence de Eskil Steenberg sur la manière de terminer ses logiciels, et j’ai fait en sorte comme mis en avant dans cette conférence que d’autres puissent construire par-dessus ma librairie (comme j’avais construit mon app sur Cobra).
Pour cela j’ai publié le tout sur github sur un repos en suivant les prérequis pour créer un package Go compatible avec les imports natifs de go (go get etc).
Tu peux retrouver ma librairie sur le site de package de golang et sur github. N’hésite pas à aller voir et tester si tu as des projets qui nécessitent une interface en ligne de commande.