Skip to content

Référence de Syntaxe

biface edited this page Oct 10, 2026 · 1 revision

Fichier de configuration des commandes

English | Français

Version : 1.0
Dernière mise à jour : 2026-10-10
Format : YAML ou JSON


Table des Matières

Voir aussi : Directives du REPL · Guide des plugins


Vue d'ensemble

Le fichier de configuration définit toutes les commandes CLI et le comportement REPL pour les applications construites avec dynamic-cli.

Fonctionnalités clés :

  • Définir des commandes sans écrire de code
  • Support des arguments positionnels et des options
  • Validation et vérification de type automatiques
  • Extensible avec des handlers personnalisés

Formats supportés :

  • YAML (.yaml, .yml) - Recommandé pour la lisibilité
  • JSON (.json) - Compatible avec les outils existants

Format de fichier

Exemple YAML

metadata:
  version: "1.0.0"
  prompt: "monapp"
  prompt_suffix: " > "

global_options:
  - name: "verbose"
    # ...

commands:
  - name: "init"
    # ...

Exemple JSON

{
  "metadata": {
    "version": "1.0.0",
    "prompt": "monapp",
    "prompt_suffix": " > "
  },
  "global_options": [
    {
      "name": "verbose"
    }
  ],
  "commands": [
    {
      "name": "init"
    }
  ]
}

Note : Les exemples YAML sont utilisés dans ce document pour la lisibilité.


Structure racine

Le fichier de configuration comporte trois sections principales :

metadata:          # Métadonnées de l'application
  # ...

global_options:    # Options disponibles pour TOUTES les commandes
  # ...

commands:          # Liste des commandes disponibles
  # ...

directives:        # Facultatif : redéfinition des directives du REPL
  # ...

metadata et commands sont obligatoires. global_options est facultative et vaut par défaut une liste vide. directives est facultative : voir Directives du REPL.


Section metadata

Informations au niveau de l'application.

Structure

metadata:
  version: string        # Obligatoire - Version de la configuration
  prompt: string         # Obligatoire - Texte du prompt REPL
  prompt_suffix: string  # Facultatif - Suffixe après le prompt (défaut : " > ")

Champs

Champ Type Obligatoire Description
version string ✅ Oui Version du fichier de configuration (versioning sémantique recommandé)
prompt string ✅ Oui Texte affiché en mode REPL (ex : "monapp", "rpn")
prompt_suffix string ❌ Non Texte après le prompt (défaut : " > " ; typiquement " > " ou "$ "). Depuis la v0.9.0 (#67), c'est bien cette valeur que ReplInterface affiche réellement — une version antérieure ignorait ce champ et affichait toujours " > ".

Exemple

metadata:
  version: "1.0.0"
  prompt: "rpn"
  prompt_suffix: " > "

Affichage REPL :

rpn > _

Continuation multi-lignes (REPL)

Depuis la v0.9.0 (DD-027).

En mode REPL interactif, une ligne se terminant par un \ n'est pas exécutée immédiatement : le marqueur est retiré, la ligne est mise en tampon, le prompt bascule sur un prompt de continuation dérivé de prompt/prompt_suffix ci-dessus, et une nouvelle ligne est lue. La première ligne qui ne se termine pas par \ complète la commande — chaque fragment mis en tampon et cette dernière ligne sont joints par un unique espace puis exécutés une seule fois, à l'image de la convention Unix classique du shell.

Ceci est indépendant du Chaînage de commandes (DD-026) : le chaînage enchaîne plusieurs commandes complètes en une seule invocation ; cette fonctionnalité permet aux options d'une seule commande de s'étaler sur plusieurs lignes du REPL. Les deux se combinent sans se gêner mutuellement.

rpn > push \
...> 0
  • Seul un \ en fin de ligne déclenche l'accumulation — aucun autre terminateur explicite (ex : ;) n'est reconnu.
  • La ligne reconstruite (fragments joints par un unique espace, marqueurs \ retirés) est ce qui atteint l'historique du REPL — jamais un fragment brut partiel.
  • Un Ctrl+C pendant l'accumulation vide le tampon et revient au prompt normal, comme l'abandon d'une ligne continuée dans un shell.

Dérivation du prompt de continuation : le segment correspondant au nom de l'application dans prompt est remplacé par ..., et prompt_suffix est ajouté après avoir retiré ses espaces de tête — pour se lire comme une marque continue accolée au séparateur plutôt que de laisser un espace parasite :

prompt prompt_suffix Prompt principal Prompt de continuation (défaut)
rpn " > " (défaut) rpn > ...>
rpn " $ " rpn $ ...$

Le segment de base (... ci-dessus) peut être remplacé côté Rust via CliBuilder::prompt_multiline() — il n'existe aucun champ YAML pour cela, car c'est un détail d'affichage du REPL interactif, pas une partie du schéma de commandes. Une surcharge ne remplace que la base ; prompt_suffix reste ajouté, mais sans retirer ses espaces de tête cette fois — la surcharge est le choix d'espacement de l'appelant :

CliBuilder::new()
    .prompt("rpn")
    .prompt_multiline("_ _ _")
    // prompt_suffix == " $ " → prompt de continuation : "_ _ _ $ "

Limitation connue — pas de politique abandon/continuation configurable : une commande signalée comme complète (pas de \ final) mais à qui il manque encore un argument ou une option obligatoire échoue immédiatement avec l'erreur exacte MissingArgument/MissingOption d'aujourd'hui — il n'y a pas de boucle de re-sollicitation. Délibéré, pas un oubli : le mode CLI en une fois (CliInterface::run()), run_script() et :load n'ont aucun opérateur en direct à qui redemander, donc ils abandonnent toujours sur une commande incomplète ; le REPL garde le même comportement d'abandon, pour une parité CLI/REPL. Non planifié pour évoluer ; à revoir seulement si un besoin concret se présente.

Portée : s'applique uniquement à ReplInterface::run() (REPL interactif). En mode CLI en une fois, le shell appelant a déjà fusionné toute ligne continuée par \ avant que dynamic-cli ne voie son argv — rien à implémenter dans ce cas. run_script() et la directive :load du REPL ne sont pas concernés : chaque ligne est exécutée indépendamment, et un \ en fin de ligne de script est lu comme n'importe quel autre jeton.


Options globales

Options qui sont disponibles pour TOUTES les commandes de l'application.

Structure

global_options:
  - name: string              # Obligatoire - Identifiant unique
    short: string             # Optionnel - Caractère unique (ex : "v")
    long: string              # Optionnel - Forme longue (ex : "verbose")
    option_type: ArgumentType # Obligatoire - Type de données
    required: boolean         # Optionnel - Doit être fourni ? (défaut : false)
    default: string           # Optionnel - Valeur par défaut si non fourni
    description: string       # Obligatoire - Texte d'aide
    choices: [string]         # Optionnel - Liste des valeurs valides

Champs

Champ Type Obligatoire Description
name string ✅ Oui Identifiant interne (utilisé dans les handlers)
short string ⬜ Non Forme courte du flag (caractère unique, ex : "v" pour -v)
long string ⬜ Non Forme longue du flag (ex : "verbose" pour --verbose)
option_type string ✅ Oui Un parmi : string, integer, float, bool, path
required boolean ⬜ Non Si true, doit être fourni par l'utilisateur (défaut : false)
default string ⬜ Non Valeur par défaut (comme chaîne, sera parsée selon le type)
description string ✅ Oui Texte d'aide pour l'utilisateur
choices array ⬜ Non Restreindre les valeurs à une liste spécifique

Exemples

Flag verbose :

- name: "verbose"
  short: "v"
  long: "verbose"
  option_type: "bool"
  required: false
  default: "false"
  description: "Activer la sortie verbeuse"

Utilisation :

monapp -v commande
monapp --verbose commande

Répertoire de sortie :

- name: "output"
  short: "o"
  long: "output"
  option_type: "path"
  required: false
  description: "Répertoire de sortie pour les résultats"

Utilisation :

monapp -o /tmp/resultats commande
monapp --output ./sortie commande

Sélection de format :

- name: "format"
  short: "f"
  long: "format"
  option_type: "string"
  required: false
  default: "text"
  description: "Format de sortie"
  choices: ["text", "json", "yaml"]

Utilisation :

monapp --format json commande
monapp -f yaml commande

Notes

  • Au moins un de short ou long doit être fourni
  • Les deux peuvent être fournis pour une flexibilité maximale
  • Les options globales sont passées à tous les handlers de commandes

Section commands

Liste des commandes disponibles dans l'application.

Structure

commands:
  - name: string                    # Obligatoire - Nom de la commande
    aliases: [string]               # Optionnel - Noms alternatifs (défaut : [])
    description: string             # Obligatoire - Texte d'aide
    required: boolean               # Optionnel - Handler obligatoire ? (défaut : false)
    arguments: [ArgumentDefinition] # Optionnel - Arguments positionnels (défaut : [])
    options: [OptionDefinition]     # Optionnel - Options spécifiques à la commande (défaut : [])
    implementation: string          # Obligatoire - Nom de la fonction handler

Champs

Champ Type Obligatoire Description
name string ✅ Oui Nom principal de la commande (pas d'espaces, minuscules recommandées)
aliases array ❌ Non Noms alternatifs (ex : ["quitter", "q"] pour exit ; défaut : [])
description string ✅ Oui Texte d'aide pour l'utilisateur
required boolean ❌ Non Si true, un handler doit être enregistré pour cette commande, sinon la construction de l'application échoue (défaut : false)
arguments array ❌ Non Liste des arguments positionnels (défaut : [])
options array ❌ Non Options spécifiques à la commande (défaut : [])
implementation string ✅ Oui Identifiant pour le handler de commande (référencé dans le code)

Exemple

commands:
  - name: "input"
    aliases: ["charger", "ouvrir"]
    description: "Charger un fichier de configuration"
    required: true
    arguments:
      - name: "fichier"
        arg_type: "path"
        required: true
        description: "Chemin vers le fichier d'entrée"
        validation:
          - must_exist: true
          - extensions: [".yaml", ".json"]
    options: []
    implementation: "load_config"

Utilisation :

monapp input config.yaml
monapp charger config.yaml
monapp ouvrir config.json

Définition de commande

Détail complet d'une commande individuelle.

Structure complète

- name: "nom-commande"
  aliases: ["alt1", "alt2"]
  description: "Ce que fait cette commande"
  required: false
  
  arguments:
    - name: "arg1"
      arg_type: "string"
      required: true
      description: "Premier argument"
      validation: []
    
    - name: "arg2"
      arg_type: "integer"
      required: false
      description: "Deuxième argument optionnel"
      validation:
        - min: 1
          max: 100
  
  options:
    - name: "option1"
      short: "o"
      long: "option"
      option_type: "bool"
      required: false
      default: "false"
      description: "Activer l'option"
      choices: []
  
  implementation: "nom_handler"
  continue_on_failure: false
  requires_success: false

Détails des champs

name (chaîne de caractères, obligatoire) :

  • Identifiant principal de la commande
  • Utilisé en CLI : monapp <nom>
  • Convention : minuscules, pas d'espaces, utiliser des traits d'union pour les mots multiples (ex : lancer-simulation)
  • Ne peut pas commencer par :, qui signale les directives du REPL

aliases (liste de chaînes de caractères, optionnel, défaut []) :

  • Noms alternatifs pour la même commande
  • Exemple : aliases: ["quitter", "q", "sortir"]
  • Tous les alias invoquent le même handler
  • Comme name, un alias ne peut pas commencer par :

description (chaîne de caractères, obligatoire) :

  • Texte d'aide pour l'utilisateur
  • Affiché dans la sortie de la commande help
  • Devrait être concis mais clair (une phrase recommandée)

required (booléen, optionnel, défaut false) :

  • true : un handler doit être enregistré pour cette commande ; sinon, la construction de l'application échoue
  • false : la commande est ignorée à la construction si aucun handler n'est enregistré pour elle
  • Ne dit rien de l'exécution de la commande : la lancer ou non revient à l'utilisateur

arguments (liste, optionnel, défaut []) :

  • Liste des arguments positionnels
  • L'ordre compte (parsés de gauche à droite)
  • Voir section Arguments pour les détails

options (liste, optionnel, défaut []) :

  • Flags/options spécifiques à la commande
  • Indépendantes de global_options
  • Voir section Options pour les détails

implementation (chaîne de caractères, obligatoire) :

  • Identifiant utilisé dans le code Rust pour lier au handler
  • Convention : snake_case
  • Exemple : "load_config" correspond à LoadConfigHandler

continue_on_failure (booléen, optionnel, défaut false) :

  • Gouverne le chaînage de commandes (v0.8.0, DD-026) — sans effet quand cette commande n'appartient pas à une chaîne.
  • false (défaut) : si cette commande échoue, la chaîne s'arrête immédiatement — identique au comportement actuel d'une commande seule, appliqué par segment.
  • true : si cette commande échoue, l'erreur avec position dans la chaîne est signalée et l'exécution se poursuit au segment suivant. Destiné aux étapes réellement optionnelles (ex : un export secondaire non critique).

requires_success (booléen, optionnel, défaut false) :

  • Fait aussi partie du chaînage de commandes (DD-026). Sans rapport avec required ci-dessus — required est un contrôle au démarrage (« un handler doit être enregistré »), requires_success est une garde de chaîne évaluée à chaque invocation, au moment du dispatch.
  • false (défaut) : cette commande s'exécute indépendamment d'un échec antérieur dans la chaîne (sans effet sauf si une commande antérieure a continue_on_failure: true).
  • true : si une commande antérieure de la chaîne a déjà échoué (quelle que soit la valeur de continue_on_failure de cet échec), cette commande est ignorée — non exécutée, non comptée comme un échec supplémentaire.

Sync vs async n'est pas une question de configuration (ref. #8). Ce champ est agnostique quant à la nature sync ou async du handler qui s'y rattache — il n'existe pas de drapeau async: true, et il n'y en aura pas. Le choix entre exécution synchrone ou asynchrone se fait entièrement côté Rust, selon la méthode appelée lors du câblage du handler :

// Handler synchrone
.register_sync_handler("load_config", Box::new(LoadConfigHandler))
// Handler asynchrone — même convention de nom, même YAML
.register_async_handler("fetch_remote", Box::new(FetchRemoteHandler))

CliBuilder::build() résout chaque nom implementation selon le type effectivement enregistré. Ajouter un drapeau au niveau de la config créerait une seconde source de vérité susceptible de diverger de l'enregistrement Rust réel — seul le câblage Rust fait autorité, et c'est voulu.

Chaînage de commandes

Depuis la v0.8.0 (DD-026).

Une seule invocation CLI (ou une ligne de run_script()) peut chaîner plusieurs commandes : une fois l'arité déclarée d'une commande épuisée, le jeton nu suivant est vérifié auprès du registre des commandes — s'il correspond à un nom de commande (ou un alias), il démarre la commande suivante de la chaîne ; sinon, la ligne est rejetée exactement comme une commande seule trop longue l'a toujours été. Il n'existe aucun jeton séparateur (;, &&, |) — le chaînage résulte uniquement de l'épuisement de l'arité, ce n'est pas une syntaxe distincte à activer. continue_on_failure et requires_success, décrits ci-dessus, gouvernent le comportement de chaque commande au sein d'une chaîne.

Règle de segmentation

Pour chaque commande de la séquence :

  1. Ses options (y compris les occurrences d'options répétables, DD-024) et ses arguments positionnels sont consommés jusqu'à son arité déclarée.
  2. Le jeton suivant est recherché dans le registre des commandes.
    • S'il correspond à un nom de commande ou un alias → démarre le segment suivant.
    • Sinon → la même erreur « trop d'arguments » qu'une commande seule non chaînée aurait levée — aucun changement de comportement pour une commande seule réellement trop longue.

Contrôle de cohérence côté handler

Depuis la v0.9.0 (DD-028).

continue_on_failure est fixé en YAML, indépendamment du code du handler — rien ne relie les deux par défaut, donc un handler écrit en supposant que ses échecs arrêtent toujours la chaîne pourrait se retrouver configuré silencieusement avec continue_on_failure: true (ou l'inverse). Il n'y a aucun nouveau champ YAML pour cela : c'est un contrôle de cohérence côté Rust, posé sur le champ existant, vérifié une seule fois, à l'enregistrement, avant même que l'application ne s'exécute.

L'auteur d'un handler peut déclarer une attente en surchargeant expected_fault_tolerance() sur CommandHandler ou AsyncCommandHandler (les deux valent None par défaut — aucune opinion, aucun contrôle) :

impl CommandHandler for SolveCommand {
    fn execute(&self, context: &mut dyn ExecutionContext, args: &ParsedArgs) -> Result<()> {
        // ...
    }

    fn expected_fault_tolerance(&self) -> Option<bool> {
        Some(false) // ce handler suppose qu'il arrête toujours la chaîne
    }
}

CommandRegistry::register_sync/register_async comparent cette valeur (quand elle vaut Some) au continue_on_failure réellement configuré pour la commande, et rejettent l'enregistrement avec RegistryError::FaultToleranceMismatch en cas de contradiction — détectant une mauvaise configuration YAML au démarrage plutôt qu'au premier usage dans une chaîne. Un handler qui ne surcharge jamais la méthode (le défaut, None) n'est absolument pas concerné, quelle que soit la valeur de continue_on_failure.

Ceci ne vérifie pas la logique interne du handler — que son execute() retourne bien Err(...) quand il le devrait. Cela reste hors de portée de toute vérification au niveau de la signature, couvert par les tests propres de l'application.

Rapport d'erreur et d'omission

  • Une commande en échec est signalée ainsi : Error in command {n}/{total} ('{name}'): <sortie d'erreur habituelle> — la position dans la chaîne est ajoutée en préfixe ; le reste du message est inchangé par rapport à l'échec d'une commande seule.
  • Une commande ignorée (via requires_success) est signalée ainsi : Skipped: command {n}/{total} ('{name}') — a preceding command failed.
  • Le code de sortie du processus reflète toujours le premier échec rencontré dans la chaîne — l'échec « déclencheur » — même si une commande ultérieure avec continue_on_failure: true échoue aussi, ou qu'une commande en aval est ignorée en conséquence.

Limitation connue

Si la ligne de commande fournit un jeton de plus que l'arité déclarée d'une commande, et que ce jeton excédentaire correspond par ailleurs à un nom de commande enregistré, il est silencieusement lu comme le début du segment suivant plutôt que de lever une erreur — la segmentation ne peut pas distinguer « une valeur excédentaire isolée » de « la commande suivante » une fois l'arité épuisée. Aucun échappement de type -- n'existe pour forcer une valeur à être traitée comme positionnelle plutôt que comme une frontière de commande. Non planifié pour correction ; à revoir seulement si un besoin concret se présente.

Portée

  • CliInterface::run() (CLI en une fois) et run_script() héritent automatiquement du chaînage — les deux passent par le même chemin de résolution interne.
  • La directive :load du REPL ne chaîne pas : chaque ligne chargée passe toujours par le chemin mono-commande propre au REPL, inchangé.

Exemple

commands:
  - name: "configure"
    description: "Enregistre une source d'entrée"
    aliases: []
    required: false
    arguments:
      - name: "path"
        arg_type: "path"
        required: true
        description: "Fichier source"
        validation: []
    options: []
    implementation: "configure_handler"

  - name: "solve"
    description: "Exécute une fois toutes les sources configurées"
    aliases: []
    required: false
    arguments: []
    options: []
    implementation: "solve_handler"
    requires_success: true

Utilisation :

monapp configure model.yml configure scenario.yml solve
# "configure" s'exécute deux fois, puis "solve" — seulement si les deux configure ont réussi

Arguments

Arguments positionnels pour les commandes.

Structure

arguments:
  - name: string              # Obligatoire - Identifiant
    arg_type: ArgumentType    # Obligatoire - Type de données
    required: boolean         # Obligatoire - Doit être fourni ?
    description: string       # Obligatoire - Texte d'aide
    validation: [Rule]        # Optionnel - Règles de validation (défaut : [])

Champs

Champ Type Obligatoire Description
name chaîne ✅ Oui Identifiant interne de l'argument
arg_type chaîne ✅ Oui Un parmi : string, integer, float, bool, path
required booléen ✅ Oui Si true, l'utilisateur doit fournir cet argument
description chaîne ✅ Oui Texte d'aide pour l'utilisateur
validation liste ❌ Non Règles de validation (défaut : [])

Exemples

Argument chemin de fichier :

- name: "fichier"
  arg_type: "path"
  required: true
  description: "Chemin du fichier d'entrée"
  validation:
    - must_exist: true
    - extensions: [".yaml", ".json", ".yml"]

Utilisation : monapp charger config.yaml

Plage numérique :

- name: "iterations"
  arg_type: "integer"
  required: true
  description: "Nombre d'itérations"
  validation:
    - min: 1
      max: 10000

Utilisation : monapp simuler 5000

Chaîne optionnelle :

- name: "nom-sortie"
  arg_type: "string"
  required: false
  description: "Nom du fichier de sortie"
  validation: []

Utilisation :

monapp exporter resultat.csv    # Fournit le nom
monapp exporter                  # Utilise la valeur par défaut

L'ordre compte

Les arguments sont parsés dans l'ordre de gauche à droite :

arguments:
  - name: "source"      # Premier positionnel
    arg_type: "path"
    required: true
  
  - name: "destination" # Deuxième positionnel
    arg_type: "path"
    required: true

Utilisation : monapp copier source.txt dest.txt


Options

Flags et options spécifiques aux commandes.

Structure

options:
  - name: string              # Obligatoire - Identifiant
    short: string             # Optionnel - Caractère unique
    long: string              # Optionnel - Forme longue
    option_type: ArgumentType # Obligatoire - Type de données
    required: boolean         # Optionnel - Doit être fourni ? (défaut : false)
    default: string           # Optionnel - Valeur par défaut
    description: string       # Obligatoire - Texte d'aide
    choices: [string]         # Optionnel - Valeurs valides

Champs

Identiques aux champs des Options globales.

Exemples

Flag booléen :

- name: "recursif"
  short: "r"
  long: "recursif"
  option_type: "bool"
  required: false
  default: "false"
  description: "Traiter les répertoires récursivement"

Utilisation :

monapp traiter -r /chemin
monapp traiter --recursif /chemin

Option entière :

- name: "threads"
  short: "t"
  long: "threads"
  option_type: "integer"
  required: false
  default: "4"
  description: "Nombre de threads parallèles"

Utilisation :

monapp simuler --threads 8
monapp simuler -t 8
monapp simuler              # Utilise la valeur par défaut : 4

Restriction par choix :

- name: "niveau"
  short: "n"
  long: "niveau"
  option_type: "string"
  required: false
  default: "info"
  description: "Niveau de journalisation"
  choices: ["debug", "info", "warn", "error"]

Utilisation :

monapp lancer --niveau debug
monapp lancer -n warn
monapp lancer                   # Utilise la valeur par défaut : info
monapp lancer -n invalide       # ERREUR : choix invalide

Options répétables

Depuis la v0.6.0 (DD-024).

Une option répétable peut apparaître plusieurs fois sur la même ligne de commande. Chaque occurrence commence par un discriminant — un jeton nu immédiatement après le drapeau, tiré de choices — suivi de zéro ou plusieurs sous-paramètres clé=valeur. dynamic-cli accumule chaque occurrence dans l'ordre de la ligne de commande ; le handler les reçoit toutes.

Structure

options:
  - name: string                          # Requis
    long: string                          # Optionnel
    short: string                         # Optionnel
    option_type: ArgumentType             # Requis
    required: boolean                     # Optionnel (défaut : false)
    description: string                   # Requis
    choices: [string]                     # Requis si repeatable — discriminants valides
    repeatable: true                      # Requis pour activer ce comportement
    option_parameters:                    # Requis — une entrée par discriminant
      <discriminant>:
        - name: string
          arg_type: ArgumentType
          required: boolean
          description: string
          validation: []                  # Optionnel
          secure: false                   # Optionnel

Règles

  • choices doit être non vide et liste les discriminants valides.
  • Les clés de option_parameters doivent correspondre exactement à choices — aucune clé orpheline, aucun discriminant non déclaré. Le chargement de la configuration échoue sinon.
  • Chaque entrée option_parameters[discriminant] est une ArgumentDefinition classique (mêmes champs que les arguments: positionnels), mais seuls name / arg_type / required / description / validation / secure ont un sens — l'ordre n'a aucun effet puisque les sous-paramètres sont associés par clé, pas par position.
  • default n'est pas autorisé quand repeatable: true : l'absence de l'option signifie déjà zéro occurrence, une valeur par défaut implicite serait ambiguë.
  • option_parameters sur une option avec repeatable: false (ou absent) est une erreur de configuration.
  • Deux occurrences du même discriminant avec des paires clé=valeur différentes sont toutes deux conservées. Deux occurrences strictement identiques (même discriminant, mêmes paires clé=valeur) sont rejetées — c'est le seul chevauchement que le framework peut détecter sans connaissance du domaine ; les occurrences partiellement chevauchantes (même file, resolution différente, par exemple) sont acceptées et laissées à la charge du handler.

Exemple

- name: "output"
  long: "output"
  option_type: "string"
  required: false
  description: "Écrit les résultats sous une ou plusieurs formes de sortie"
  choices: ["csv", "plot"]
  repeatable: true
  option_parameters:
    csv:
      - name: "file"
        arg_type: "path"
        required: true
        description: "Fichier CSV de destination"
      - name: "resolution"
        arg_type: "integer"
        required: false
        description: "Résolution du pas de temps"
    plot:
      - name: "file"
        arg_type: "path"
        required: true
        description: "Fichier image de destination"

Utilisation :

monapp export --output csv file=resultats.csv resolution=100
monapp export --output csv file=resultats.csv --output plot file=graphe.png
monapp export --output csv file=a.csv --output csv file=b.csv   # les deux conservées — `file` différent
monapp export --output csv file=a.csv --output csv file=a.csv   # ERREUR : DuplicateOptionOccurrence
monapp export --output json file=out.json                       # ERREUR : UnknownDiscriminant
monapp export --output csv                                      # ERREUR : MissingRequiredOptionParameter (file)
monapp export --output csv file=a.csv depth=3                   # ERREUR : UnknownOptionParameter (depth)

Côté handler

CommandHandler::execute (et validate) reçoivent désormais une valeur ParsedArgs (voir la documentation du module executor) plutôt qu'une simple HashMap<String, String>, précisément pour pouvoir représenter les occurrences d'une option répétable aux côtés des options scalaires :

let occurrences = args.get_repeated("output").unwrap_or(&[]);
for occ in occurrences {
    match occ.discriminant.as_str() {
        "csv" => { /* occ.params.get("file"), occ.params.get("resolution") */ }
        "plot" => { /* occ.params.get("file") */ }
        _ => unreachable!(), // la validation de config a déjà rejeté les autres discriminants
    }
}

Voir CHANGELOG.md (v0.6.0) pour le chemin de migration complet HashMap<String, String> → ParsedArgs des handlers existants.


Types d'arguments

Types de données supportés pour les arguments et options.

Types disponibles

Type Description Exemples de valeurs Type Rust
string Valeur texte "bonjour", "config.yaml" String
integer Nombre entier 42, -10, 0 i64
float Nombre décimal 3.14, -0.5, 1.0 f64
bool Flag booléen true, false bool
path Chemin fichier/répertoire "./fichier.txt", "/tmp" PathBuf

Parsing des types

string :

  • Accepte n'importe quel texte
  • Pas de parsing requis
  • Exemple : nom: "Jean Dupont"

integer :

  • Doit être un entier valide
  • Support des valeurs négatives
  • Plage : -9,223,372,036,854,775,808 à 9,223,372,036,854,775,807
  • Exemple : compte: 42

float :

  • Doit être un nombre décimal valide
  • Support de la notation scientifique
  • Exemple : valeur: 3.14 ou valeur: 1.5e-3

bool :

  • Pour les flags : présence = true, absence = false
  • Pour les valeurs : "true", "false", "yes", "no", "1", "0"
  • Insensible à la casse
  • Exemple : --verbose (true) vs pas de flag (false)

path :

  • Chemin de fichier ou répertoire
  • Peut être relatif ou absolu
  • La validation peut vérifier l'existence
  • Exemple : fichier: "./donnees/entree.csv"

Règles de validation

Contraintes appliquées aux arguments et options.

Règles disponibles

1. Existence fichier/répertoire

validation:
  - must_exist: true

S'applique à : type path
Effet : Vérifie si le fichier/répertoire existe avant l'exécution
Erreur si : Le chemin n'existe pas

2. Extension de fichier

validation:
  - extensions: [".txt", ".csv", ".json"]

S'applique à : type path (fichiers uniquement)
Effet : Valide que le fichier a l'une des extensions spécifiées
Erreur si : L'extension du fichier n'est pas dans la liste

3. Plage numérique

validation:
  - min: 1
    max: 100

S'applique à : types integer, float
Effet : La valeur doit être dans la plage (inclusive)
Champs :

  • min (optionnel) : Valeur minimale
  • max (optionnel) : Valeur maximale
  • Les deux peuvent être omis pour des bornes unilatérales

Exemples :

# Seulement minimum
- min: 0

# Seulement maximum
- max: 100

# Les deux
- min: 1
  max: 10

Combinaison de règles

Plusieurs règles peuvent être appliquées à un seul argument :

validation:
  - must_exist: true
  - extensions: [".yaml", ".json"]

Effet : Le fichier doit exister ET avoir l'extension .yaml ou .json

Pas de validation

Utiliser un tableau vide si aucune validation n'est nécessaire :

validation: []

Exemple complet

Fichier de configuration complet pour une application de traitement de données.

# ═══════════════════════════════════════════════════════════
# Configuration CLI - Traitement de Données
# ═══════════════════════════════════════════════════════════

metadata:
  version: "1.0.0"
  prompt: "dataproc"
  prompt_suffix: " > "

# ───────────────────────────────────────────────────────────
# OPTIONS GLOBALES (disponibles pour toutes les commandes)
# ───────────────────────────────────────────────────────────
global_options:
  - name: "verbose"
    short: "v"
    long: "verbose"
    option_type: "bool"
    required: false
    default: "false"
    description: "Activer la sortie verbeuse"
  
  - name: "sortie-rep"
    short: "o"
    long: "sortie"
    option_type: "path"
    required: false
    description: "Répertoire de sortie pour les résultats"
  
  - name: "format"
    short: "f"
    long: "format"
    option_type: "string"
    required: false
    default: "text"
    description: "Format de sortie"
    choices: ["text", "json", "yaml"]

# ───────────────────────────────────────────────────────────
# COMMANDES
# ───────────────────────────────────────────────────────────
commands:
  
  # ═════════════════════════════════════════════════════════
  # Commande : charger
  # ═════════════════════════════════════════════════════════
  - name: "charger"
    aliases: ["ouvrir", "entree"]
    description: "Charger un fichier de données"
    required: true
    
    arguments:
      - name: "fichier"
        arg_type: "path"
        required: true
        description: "Chemin vers le fichier de données"
        validation:
          - must_exist: true
          - extensions: [".csv", ".json", ".yaml"]
    
    options: []
    
    implementation: "load_data"
  
  # ═════════════════════════════════════════════════════════
  # Commande : traiter
  # ═════════════════════════════════════════════════════════
  - name: "traiter"
    aliases: ["lancer"]
    description: "Traiter les données chargées"
    required: false
    
    arguments: []
    
    options:
      - name: "threads"
        short: "t"
        long: "threads"
        option_type: "integer"
        required: false
        default: "4"
        description: "Nombre de threads parallèles"
        choices: []
      
      - name: "taille-lot"
        short: "b"
        long: "lot"
        option_type: "integer"
        required: false
        default: "1000"
        description: "Taille du lot pour le traitement"
        choices: []
    
    implementation: "process_data"
  
  # ═════════════════════════════════════════════════════════
  # Commande : exporter
  # ═════════════════════════════════════════════════════════
  - name: "exporter"
    aliases: ["sauver"]
    description: "Exporter les résultats vers un fichier"
    required: false
    
    arguments:
      - name: "fichier-sortie"
        arg_type: "path"
        required: true
        description: "Chemin du fichier de sortie"
        validation: []
    
    options:
      - name: "compresser"
        short: "c"
        long: "compresser"
        option_type: "bool"
        required: false
        default: "false"
        description: "Compresser le fichier de sortie"
        choices: []
    
    implementation: "export_results"
  
  # ═════════════════════════════════════════════════════════
  # Commande : aide
  # ═════════════════════════════════════════════════════════
  - name: "aide"
    aliases: ["h", "?"]
    description: "Afficher l'aide"
    required: false
    
    arguments:
      - name: "commande"
        arg_type: "string"
        required: false
        description: "Commande pour laquelle obtenir de l'aide"
        validation: []
    
    options: []
    
    implementation: "show_help"
  
  # ═════════════════════════════════════════════════════════
  # Commande : quitter
  # ═════════════════════════════════════════════════════════
  - name: "quitter"
    aliases: ["sortir", "q"]
    description: "Quitter l'application"
    required: false
    
    arguments: []
    options: []
    
    implementation: "exit_app"

Exemples d'utilisation

Mode CLI :

# Avec options globales
dataproc --verbose charger donnees.csv
dataproc -v -o ./resultats traiter --threads 8
dataproc --format json exporter sortie.json

# Sans options globales
dataproc charger donnees.csv
dataproc traiter --threads 8 --lot 2000
dataproc exporter sortie.csv --compresser

Mode REPL :

dataproc > charger donnees.csv
Données chargées avec succès.

dataproc > traiter --threads 8
Traitement... Terminé.

dataproc > exporter resultats.csv
Résultats exportés vers resultats.csv

dataproc > aide exporter
Commande : exporter
Description : Exporter les résultats vers un fichier
...

dataproc > quitter
Au revoir !

Bonnes pratiques

1. Conventions de nommage

Commandes :

  • Utiliser les minuscules
  • Utiliser des traits d'union pour les mots multiples : lancer-simulation (pas lancerSimulation ou lancer_simulation)
  • Garder les noms concis mais descriptifs
  • Fournir des alias significatifs : ["quitter", "q", "sortir"]

Arguments/Options :

  • Utiliser les minuscules avec traits d'union : rep-sortie (pas repSortie)
  • Être cohérent dans toute l'application
  • Utiliser judicieusement les formes courtes : caractère unique, couramment reconnu (-v pour verbose)

2. Required vs Optional

Marquer comme required: true quand :

  • La commande DOIT être exécutée pour le workflow de l'application
  • L'argument DOIT être fourni pour que la commande fonctionne
  • L'option est essentielle (rare - considérer si ce devrait être un argument à la place)

Marquer comme required: false quand :

  • La commande est une fonctionnalité de commodité optionnelle
  • L'argument a un comportement par défaut sensé
  • L'option améliore mais ne bloque pas la fonctionnalité

3. Validation

Toujours valider quand :

  • Acceptant des chemins de fichiers (vérifier l'existence, les extensions)
  • Acceptant des valeurs numériques avec contraintes (utiliser des plages)
  • Acceptant des énumérations (utiliser choices)

Exemple :

# Bon : Validation complète
validation:
  - must_exist: true
  - extensions: [".csv", ".json"]
# Mauvais : Pas de validation pour entrée fichier
validation: []

4. Descriptions

Écrire des descriptions claires et concises :

  • ✅ Bon : "Charger un fichier de configuration"
  • ❌ Mauvais : "Charge des trucs"
  • ✅ Bon : "Nombre de threads parallèles (1-16)"
  • ❌ Mauvais : "threads"

5. Valeurs par défaut

Fournir des valeurs par défaut sensées pour les champs optionnels :

# Bon : Valeur par défaut sensée
- name: "threads"
  default: "4"
  
# Mauvais : Requis mais devrait avoir une valeur par défaut
- name: "threads"
  required: true  # L'utilisateur doit toujours spécifier

6. Options globales vs options de commande

Utiliser global_options pour :

  • Flags verbose/debug
  • Sélection de format de sortie
  • Chemins de fichiers de configuration
  • Niveaux de journalisation

Utiliser options de commande pour :

  • Comportement spécifique à la commande
  • Paramètres qui n'ont de sens que pour une commande

7. Alias

Fournir des alias utiles :

# Bon : Raccourcis courants
aliases: ["quitter", "q", "sortir"]
# Bon : Noms alternatifs
aliases: ["charger", "ouvrir", "entree"]
# À éviter : Confus ou trop nombreux
aliases: ["a", "b", "c", "xyz123"]

8. Noms d'implémentation

Utiliser un nommage clair et cohérent :

# Bon : Objectif clair
implementation: "load_config"
implementation: "run_simulation"
# Mauvais : Vague
implementation: "handler1"
implementation: "do_stuff"

9. Organisation des fichiers

Pour les grandes applications, considérer :

  • Un fichier de configuration par mode (cli.yaml, repl.yaml)
  • Commandes partagées dans un fichier inclus séparé
  • Surcharges spécifiques à l'environnement (dev.yaml, prod.yaml)

10. Documentation

Commenter les sections complexes :

# ═══════════════════════════════════════════════════════════
# COMMANDES DE SIMULATION
# Ces commandes contrôlent le moteur de simulation numérique
# ═══════════════════════════════════════════════════════════
commands:
  - name: "simuler"
    # Les utilisateurs avancés peuvent surcharger le pas de temps
    options:
      - name: "dt"
        description: "Pas de temps en secondes (experts uniquement)"

Équivalent JSON

L'exemple complet ci-dessus au format JSON :

{
  "metadata": {
    "version": "1.0.0",
    "prompt": "dataproc",
    "prompt_suffix": " > "
  },
  "global_options": [
    {
      "name": "verbose",
      "short": "v",
      "long": "verbose",
      "option_type": "bool",
      "required": false,
      "default": "false",
      "description": "Activer la sortie verbeuse"
    }
  ],
  "commands": [
    {
      "name": "charger",
      "aliases": ["ouvrir", "entree"],
      "description": "Charger un fichier de données",
      "required": true,
      "arguments": [
        {
          "name": "fichier",
          "arg_type": "path",
          "required": true,
          "description": "Chemin vers le fichier de données",
          "validation": [
            {"must_exist": true},
            {"extensions": [".csv", ".json"]}
          ]
        }
      ],
      "options": [],
      "implementation": "load_data"
    }
  ]
}

Résumé

Cette référence couvre tous les éléments de syntaxe des fichiers de configuration dynamic-cli :

✅ Metadata - Informations sur l'application
✅ Options globales - Options pour toutes les commandes
✅ Commandes - Définitions de commandes individuelles
✅ Arguments - Paramètres positionnels
✅ Options - Flags et paramètres nommés
✅ Types - Spécifications de types de données
✅ Validation - Règles de contraintes
✅ Exemples - Utilisation réelle
✅ Bonnes pratiques - Recommandations

Pour les détails d'implémentation, voir la documentation de l'API dynamic-cli.


Version : 1.0
Framework : dynamic-cli
Date : 2026-01-11

Clone this wiki locally