Tutoriel HsIndex

cmdargs

Afin de faire interagir ce programme avec l’utilisateur, il est nécessaire de pouvoir lui passer des arguments via la ligne de commande. Pour cela j’utilise la bibliothèque cmdargs fournie de manière standard avec la plateforme de développement Haskell-plateform.

Méthodes

Je ne détaillerais pas dans le détail l’API et le fonctionnement de cette bibliothèque. Je dirais simplement que cette librairie possède deux méthodes de fonctionnement:

  • La méthode implicite : Simple et facile à prendre en main mais limité en terme d’options.

  • La méthode explicite : Plus complexe mais contenant plus d’options et de souplesses pour les options à passer en ligne de commande.

De manière a avoir un meilleur contrôle sur le résultat final, je préfère utiliser la méthode explicite de description des arguments.

Modes

Un programme utilisant cmdargs fonctionne en appelant un mode de fonctionnement auquel sont attachées des options spécifiques.

Un mode est appelé par un mot clef placé juste après l’appel du nom du programme:

prog mode1 -a -v
prog mode2 -b --option

Arguments en ligne de commande

Choix des langues

Pour chaque langue correspondra un mode de fonctionnement auquel s’ajoutera un mode pour pouvoir librement décrire son propre langage.

les différents modes de fonctionnement seront:

hsindex english ...
hsindex french  ...
hsindex german ...
hsindex russian ...

hsindex custom  ...

Options

Pour tous les modes. les arguments suivants seront obligatoires:

  • --input (ou -i en version courte) Pour le fichier d’index à traiter.

Ces options seront facultatives

  • --style (ou -s en version courte) Pour ajouter un fichier de style pour la génération de l’index.

  • --output (ou -o en version courte) Pour définir le fichier de sortie (Un nom de fichier par défaut est définie pour chaque mode).

Implémentation

Définition des modes

Les modes sont définis par le type de données MyModes qui contient les constructeur associé à chaque mode. Pour chaque constructeur on définis les différents champs qui seront utilisés par les options de la ligne de commande pour transmettre les valeurs au programme.

On définit donc

  • 4 constructeurs (un pour chaque langue à générer)

  • 1 constructeur pour une langue définit par l’utilisateur

  • 2 modes supplémentaires pour afficher les informations de version et l’aide.

    Note : Ces modes seront appelés via des options (-V et -h) et non avec des mots clefs.

data MyModes = 
    IndexRussian { fileIn    :: FilePath       -- ^ The input file
                 , fileOut   :: FilePath       -- ^ The output file
                 , fileStyle :: Maybe FilePath -- ^ The style file
                 }
  | IndexFrench  { fileIn    :: FilePath
                 , fileOut   :: FilePath
                 , fileStyle :: Maybe FilePath
                 } 
  | IndexGerman  { fileIn    :: FilePath
                 , fileOut   :: FilePath
                 , fileStyle :: Maybe FilePath
                 } 
  | IndexEnglish { fileIn    :: FilePath
                 , fileOut   :: FilePath
                 , fileStyle :: Maybe FilePath
                 } 
  | IndexCustom  { fileIn    :: FilePath
                 , fileOut   :: FilePath
                 , fileStyle :: Maybe FilePath
                 , fileDef   :: FilePath
                 } 
  | ArgHelp 
  | ArgVersion

Construction des modes

Les argument de chaque langue seront gérés avec une fonction dédiée.

modeGenIndexFrench :: Mode MyModes

On utilise pour cela la fonction mode qui prend comme argument

  1. Le mot clef associé au mode.

  2. Les valeurs initiales du mode.

  3. Un descriptif du mode (Affiché dans l’aide).

  4. Une fonction pour gérer les arguments sans noms (Ces types d’arguments ne seront pas utilisés ici).

  5. Une liste de fonction permettant de gérer les options qui peuvent être utilisé avec ce mode.

Pour chaque option, on utilise la fonction flagReq qui définit une option qui impose à l’utilisateur de fournir une valeur.

Cette fonction prend comme argument

  1. Une liste de nom d’option ("input" pour la version longue et "i" pour la version courte).

  2. Une fonction permettant de capturer la valeur requise.

  3. Un complément d’information sur le type de valeur attendu (Affiché dans l’aide).

  4. Les détails sur l’option (Affiché dans l’aide).

modeGenIndexFrench :: Mode MyModes
modeGenIndexFrench = mode "french" initialOptsIndexFrench description unnamedArg convertFlags
  where
    description = "Generate a French index"
    unnamedArg  = Arg {argValue = updateUnnamed, argType = "", argRequire = False}
      where updateUnnamed str opts = Left ("Error unknown argument : " ++ str)
    convertFlags =
      [ flagReq ["input", "i"]  setInputFile     "<File>" "Input file"
      , flagReq ["output", "o"] setOutpuFileFile "<File>" "Output file"
      , flagReq ["style", "s"]  setStyleFileFile "<File>" "Style file"
      ]

Capture des valeurs (noms de fichiers)

Les fonctions permettant de capturer les valeurs sont de type :

setInputFile :: String -> a -> Either String a

Ce qui signifie que ces fonction prennent comme paramètres :

  1. La valeur telle qu’elle est renseignée dans la ligne de commande sous forme de chaîne de caractères.

  2. Le type de mode (ici MyModes)

  3. Un choix Either entre

    • Une chaîne de caractère à retourner comme message d’erreur si la valeur n’est pas correcte.

    • Le constructeur du type MyModes mis à jour en prenant en compte la valeur de l’option.

Ici, les valeurs à renseigner sont de simples chaînes de caractères contenant le chemin d’accès aux fichiers d’entrée, de sortie et de styles. Il n’y a donc pas besoin de faire des test particuliers sur les chaînes. On retourne donc systématiquement Right (Valeur correcte) avec une mise à jour de MyModes

setInputFile str opts = Right $ opts { fileIn = str }

Assemblage des différents modes

On assemble les différents modes en créant un mode globale dont le mot clef sera le nom du programme et le constructeur par défaut ArgHelp pour lancer l’aide par défaut.

La liste des modes créés précédemment sera passé comme argument (mods) et on ajoute des fonctions permettant de capturer les options associées à l’aide et aux informations de version.

La définition de modeGroupModes en utilisant toGroup permet d’ajouter la détection des argument supplémentaires helpFlag et versionFlag. Ces fonctions sont créées avec flagNone qui a le même rôle que flagReq a ceci près que l’option ne nécessite pas qu’on lui fournisse une valeur.

const est la fonction constante de type a -> b -> a et sert à définir une fonction qui renvoie systématiquement la même valeur.

modesCLI mods = (modes "hsindex" ArgHelp "" mods) { modeGroupFlags = toGroup [helpFlag, versionFlag] }
  where
    helpFlag    = flagNone ["help", "h", "?"] (const ArgHelp) "Help message"
    versionFlag = flagNone ["version", "V"] (const ArgVersion) "Version informations"