Catégorie : Programmation     Tags :      

    Retrouvez cet article dans : Linux Magazine 89

    La dissection de GLib nous amène ce mois-ci à étudier le code de l’analyseur de ligne de commande. Nous alternons un peu avec les structures de données vues dans les numéros précédents. GLib n’est pas qu’une boite d’outils dédiés aux structures de données !

    Introduction

    Historiquement, l’analyse de la ligne de commande était réalisée avec les fonctions du C, d’où une multitude de formats possibles. Certains utilisaient des tirets pour indiquer un argument, d’autres un slash, d’autres encore rien du tout. Souvent, pour ne pas dire presque toujours, les arguments devaient être indiqués dans l’ordre défini par le programme, ne laissant aucune souplesse à l’utilisateur.
    Pour faciliter la tâche aux programmeurs, le jeu de fonctions dont getopt() est la plus connue est apparu. La façon d’indiquer les arguments s’est normée : les options étaient indiquées sous la forme d’un tiret suivi d’une lettre. Ceci n’était pas très causant et tout le monde n’adopta pas ce jeu de fonctions. GNU a tenté d’améliorer les choses avec getopt_long() qui permet d’utiliser les options longues, celles commençant par un double tiret. Cette fonction n’est cependant pas vraiment plus souple que la getopt() originale.
    Par ailleurs, certains développèrent une bibliothèque dédiée à l’analyse de la ligne de commande, popt, utilisée entre autres par le logiciel de gestion de paquets rpm. L’analyseur de Glib se veut en fait un outil plus simple pouvant remplacer popt.

    Remarque :

    Cet article est basé sur la version 2.12.4 de Glib.

    Fonctionnalités

    Jetons un coup d’œil à la documentation de GLib et de ce fameux analyseur de ligne de commande. Nous y trouvons trois structures de données : GOptionContext, GOptionGroup et GOptionEntry. Vu leurs noms, nous pouvons déjà deviner que nous allons décrire chacune de nos options dans des GOptionEntry, que nous allons grouper par GOptionGroup, et que nous allons insérer le tout dans un seul et unique GOptionContext.
    Nous allons donc créer un jeu d’options principales et un autre jeu d’option secondaire :

    01 #include <stdio.h>
    02 #include <stdlib.h>
    03 #include <string.h>
    04
    05 #include <glib.h>
    06
    07 static gboolean o_debug=FALSE;
    08 static gboolean o_verbose=FALSE;
    09 static gint o_nb=1;
    10 static gchar *o_chaine=NULL;
    11 static gchar *o_fichier=NULL;
    12
    13 static GOptionEntry mes_options_principales[] =
    14 {
    15         { «debug», 'd', 0, G_OPTION_ARG_NONE, &o_debug, «Activer le mode debug», NULL },
    16         { «verbose», 'v', 0, G_OPTION_ARG_NONE, &o_verbose, «Activer le mode blabla», NULL },
    17         { “nb”, 'n', 0, G_OPTION_ARG_INT, &o_nb, “Un nombre”, “N” },
    18         { “chaine”, 0, 0, G_OPTION_ARG_STRING, &o_chaine, “Une chaîne de caractères», «plouf» },
    19         { NULL }
    20 };
    21
    22 static GOptionEntry mes_options_secondaires[] =
    23 {
    24         { «fichier», 'f', 0, G_OPTION_ARG_FILENAME, &o_fichier, «Un nom de fichier», NULL },
    25         { NULL }
    26 };
    27

    Il s’agira ensuite de les déclarer :

    28 int main(int argc, char**argv) {
    29
    30         GError *error = NULL;
    31         GOptionContext *context;
    32         GOptionGroup *groupe_secondaire;
    33
    34 /* Création du contexte et ajout des entrées principales */
    35         context = g_option_context_new («Programme de test d'analyse de ligne de commande»);
    36         g_option_context_add_main_entries (context, mes_options_principales, NULL);
    37
    38 /* Création d'un groupe et ajout des entrées secondaires dans ce groupe */
    39         groupe_secondaire = g_option_group_new(«fichiers», «Gestion des fichiers», «Options dédiées aux fichiers», NULL, NULL);
    40         g_option_group_add_entries(groupe_secondaire, mes_options_secondaires);
    41
    42 /* Ajout du groupe dans le contexte */
    43         g_option_context_add_group (context, groupe_secondaire);
    44

    Analysez maintenant argc et argv :

    45 /* Analyse de la ligne de commande */
    46         g_option_context_parse (context, &argc, &argv, &error);
    47
    48 /* Affichage de choses et d'autres... */
    49         printf(«debug=%s verbose=%s nb=%d chaine='%s'\n», o_debug?»TRUE»:»FALSE»,o_verbose?»TRUE»:»FALSE», o_nb, o_chaine);
    50         printf(“fichier='%s'\n”, o_fichier);
    51

    Un peu de nettoyage avant de finir ne fait jamais de mal !

    52 /* Nettoyage */
    53         g_option_context_free(context);
    54
    55 /* Au revoir, et merci pour le poisson ! */
    56         exit(EXIT_SUCCESS);
    57 }

    Compilez et exécutez ce programme :

     $ gcc `pkg-config --cflags --libs glib-2.0` test.c -o t
    $ ./t
    debug=FALSE verbose=FALSE nb=1 chaine='(null)'
    fichier='(null)'
    $ ./t --help Usage:
      t [OPTION...] Programme de test d'analyse de ligne de commande
    Help Options:
      -?, --help            Show help options
      --help-all            Show all help options
      --help-fichiers       Options d?di?es aux fichiers
    Application Options:
      -d, --debug           Activer le mode debug
      -v, --verbose         Activer le mode blabla
      -n, --nb=N            Un nombre
      --chaine=plouf        Une cha?ne de caract?res
    $ ./t --help-all Usage:
      t [OPTION...] Programme de test d'analyse de ligne de commande
    Help Options:
      -?, --help            Show help options
      --help-all            Show all help options
      --help-fichiers       Options d?di?es aux fichiers
    Gestion des fichiers
      -f, --fichier         Un nom de fichier
    Application Options:
      -d, --debug           Activer le mode debug
      -v, --verbose         Activer le mode blabla
      -n, --nb=N            Un nombre
      --chaine=plouf        Une cha?ne de caract?res
    $ ./t --debug -n 3 --fichier='/bin/ls'
    debug=TRUE verbose=FALSE nb=3 chaine='(null)'
    fichier='/bin/ls'

    Les structures

    Attaquons les choses sérieuses. Que contiennent ces fameuses structures ? Ouvrez les fichiers glib/goptions.c et glib/goption.h.

    GOptionContext

    01 typedef struct _GOptionContext GOptionContext;
    01 struct _GOptionContext
    02 {
    03   GList           *groups;
    04
    05   gchar           *parameter_string;
    06   gchar           *summary;
    07   gchar           *description;
    08
    09   GTranslateFunc   translate_func;
    10   GDestroyNotify   translate_notify;
    11   gpointer           translate_data;
    12
    13   guint            help_enabled   : 1;
    14   guint            ignore_unknown : 1;
    15
    16   GOptionGroup    *main_group;
    17
    18   /* We keep a list of change so we can revert them */
    19   GList           *changes;
    20
    21   /* We also keep track of all argv elements
    22    * that should be NULLed or modified.
    23    */
    24   GList           *pending_nulls;
    25 };

    Nous ne pouvons pas encore comprendre les tenants et aboutissants de cette structure. Remarquez cependant que nous avons ligne 16 notre groupe principal et ligne 3 une liste de groupes supplémentaires. La suite est éclairée par la lecture de g_option_context_new() :

    01 GOptionContext *
    02 g_option_context_new (const gchar *parameter_string)
    03
    04 {
    05   GOptionContext *context;
    06
    07   context = g_new0 (GOptionContext, 1);
    08
    09   context->parameter_string = g_strdup (parameter_string);
    10   context->help_enabled = TRUE;
    11   context->ignore_unknown = FALSE;
    12
    13   return context;
    14 }

    Cette fonction ne fait qu’allouer de la mémoire (ligne 7) et initialiser trois champs (lignes 9 à 11). Passons à la suite. Nous reviendrons à cette structure plus loin.

    GOptionGroup

    01 typedef struct _GOptionGroup   GOptionGroup;
    01 struct _GOptionGroup
    02 {
    03   gchar           *name;
    04   gchar           *description;
    05   gchar           *help_description;
    06
    07   GDestroyNotify   destroy_notify;
    08   gpointer         user_data;
    09
    10   GTranslateFunc   translate_func;
    11   GDestroyNotify   translate_notify;
    12   gpointer           translate_data;
    13
    14   GOptionEntry    *entries;
    15   gint             n_entries;
    16
    17   GOptionParseFunc pre_parse_func;
    18   GOptionParseFunc post_parse_func;
    19   GOptionErrorFunc error_func;
    20 };

    Cette structure n’est pas beaucoup plus claire que la précédente. Remarquez cependant les premiers champs descriptifs (lignes 3 à 5) ainsi que l’emplacement où nous indiquerons nos entrées (lignes 14 et 15). Peut-être g_option_group_new() nous éclairera-t-il un peu plus ?

    01 GOptionGroup *
    02 g_option_group_new (const gchar    *name,
    03                     const gchar    *description,
    04                     const gchar    *help_description,
    05                     gpointer        user_data,
    06                     GDestroyNotify  destroy)
    07
    08 {
    09   GOptionGroup *group;
    10
    11   group = g_new0 (GOptionGroup, 1);
    12   group->name = g_strdup (name);
    13   group->description = g_strdup (description);
    14   group->help_description = g_strdup (help_description);
    15   group->user_data = user_data;
    16   group->destroy_notify = destroy;
    17
    18   return group;
    19 }

    Cela ne s’améliore pas : nous allouons de la mémoire pour le GOptionGroup et y insérons une copie des arguments.

    GOptionEntry

    La structure GOptionEntry nous en dira-t-elle plus ?

    01 typedef struct _GOptionEntry   GOptionEntry;
    01 struct _GOptionEntry
    02 {
    03   const gchar *long_name;
    04   gchar        short_name;
    05   gint         flags;
    06
    07   GOptionArg   arg;
    08   gpointer     arg_data;
    09
    10   const gchar *description;
    11   const gchar *arg_description;
    12 };

    Voilà, nous y sommes. Pour chaque argument, c’est ici que nous stockons les informations qui lui sont associées. Le GOptionArg est juste une énumération :

    01 typedef enum
    02 {
    03   G_OPTION_ARG_NONE,
    04   G_OPTION_ARG_STRING,
    05   G_OPTION_ARG_INT,
    06   G_OPTION_ARG_CALLBACK,
    07   G_OPTION_ARG_FILENAME,
    08   G_OPTION_ARG_STRING_ARRAY,
    09   G_OPTION_ARG_FILENAME_ARRAY,
    10   G_OPTION_ARG_DOUBLE,
    11   G_OPTION_ARG_INT64
    12 } GOptionArg;

    Il indique vraisemblablement le type de l’argument attendu.

    La création du contexte

    g_option_context_add_main_entries()

    Dans notre programme du début, après avoir utilisé g_option_context_new(), nous avons fait appel à g_option_context_add_main_entries() :

    01 void
    02 g_option_context_add_main_entries (GOptionContext      *context,
    03                                    const GOptionEntry  *entries,
    04                                    const gchar         *translation_domain)
    05 {
    06   g_return_if_fail (entries != NULL);
    07
    08   if (!context->main_group)
    09     context->main_group = g_option_group_new (NULL, NULL, NULL, NULL, NULL);
    10
    11   g_option_group_add_entries (context->main_group, entries);
    12   g_option_group_set_translation_domain (context->main_group, translation_domain);
    13 }

    Cette fonction n’est finalement qu’une fonction d’appoint pour créer le groupe principal s’il n’existe pas déjà (lignes 8 et 9) et appeler g_option_group_add_entries() et g_option_group_set_translation_domain() (lignes 11 et 12). C’est exactement ce que nous avons fait pour notre groupe d’options secondaire à ceci près que l’internationalisation est prise en compte avec le domaine de traduction (ligne 12).

    g_option_group_add_entries()

    Ce qui nous intéresse donc, finalement, c’est g_option_group_add_entries() :

    01 g_option_group_add_entries (GOptionGroup       *group,
    02                             const GOptionEntry *entries)
    03 {
    04   gint i, n_entries;
    05
    06   g_return_if_fail (entries != NULL);
    07
    08   for (n_entries = 0; entries[n_entries].long_name != NULL; n_entries++) ;
    09
    10   group->entries = g_renew (GOptionEntry, group->entries, group->n_entries + n_entries);
    11
    12   memcpy (group->entries + group->n_entries, entries, sizeof (GOptionEntry) * n_entries);
    13
    14   for (i = group->n_entries; i < group->n_entries + n_entries; i++)
    15     {
    16       gchar c = group->entries[i].short_name;
    17
    18       if (c)
    19         {
    20           if (c == '-' || !g_ascii_isprint (c))
    21             {
    22               g_warning (G_STRLOC»: ignoring invalid short option '%c' (%d)», c, c);
    23               group->entries[i].short_name = 0;
    24             }
    25         }
    26     }
    27
    28   group->n_entries += n_entries;
    29 }

    Ligne 8, cette boucle sans contenu est un classique pour compter le nombre d’éléments d’une structure, ici de entries. La variable n_entries y est incrémentée jusqu’au dernier élément.
    Ligne 10, la zone des entrées est agrandie de n_entries fois la taille d’un GOptionEntry. Le champ entries de cette structure correspond donc à un tableau. Vous devinez ligne 28 que la taille de ce tableau est group->n_entries et c’est pourquoi il est incrémenté de n_entries à cette ligne.
    Ligne 12, le tableau des entrées indiqué en argument est concaténé aux entrées existantes grâce à un memcpy().
    Cela devrait suffire, mais lignes 14 à 26, nous avons droit à une boucle de vérification. En effet, pour chaque entrée, l’option courte est testée. Elle ne doit pas valoir '-', ce qui signifierait -- et correspondrait à la fin des options. Elle doit par contre valoir un caractère faisant partie des caractères imprimables (fonction g_ascii_isprint()). Si ce test ligne 20 était positif, un message d’avertissement serait envoyé au développeur (ligne 22). Celui-ci devrait corriger l’option afin que ce message n’apparaisse pas à l’utilisateur final. L’option est de plus désactivée ligne 23.

    g_option_group_set_translation_domain()

    Intéressons-nous à cette fonction :

    01 void
    02 g_option_group_set_translation_domain (GOptionGroup *group,
    03                                        const gchar  *domain)
    04 {
    05   g_return_if_fail (group != NULL);
    06
    07   g_option_group_set_translate_func (group,
    08                        (GTranslateFunc)dgettext_swapped,
    09                        g_strdup (domain),
    10                        g_free);
    11 }

    Nous avons droit à nouveau, comme dans les articles précédents, à un jeu de piste !

    01 void
    02 g_option_group_set_translate_func (GOptionGroup   *group,
    03                        GTranslateFunc  func,
    04                        gpointer        data,
    05                        GDestroyNotify  destroy_notify)
    06 {
    07   g_return_if_fail (group != NULL);
    08
    09   if (group->translate_notify)
    10     group->translate_notify (group->translate_data);
    11
    12   group->translate_func = func;
    13   group->translate_data = data;
    14   group->translate_notify = destroy_notify;
    15 }

    Cette fonction se contente de changer les fonctions et données de traduction. Si une fonction précédente avait été indiquée pour notifier quelqu’un du changement de fonction dans group->translate_notify, elle est appelée ligne 9. La nouvelle est indiquée ligne 14 après avoir changé les champs nécessaires lignes 12 et 13 pour la nouvelle fonction et la donnée utilisateur.
    g_option_context_add_group()
    Nous avons vu comment créer un groupe et, dans le cas du groupe principal, l’intégrer dans le contexte. Il ne nous reste plus qu’à étudier comment prendre également en compte des groupes secondaires. C’est le rôle de g_option_context_add_group() :

    01 void
    02 g_option_context_add_group (GOptionContext *context,
    03                             GOptionGroup   *group)
    04 {
    05   GList *list;
    06
    07   g_return_if_fail (context != NULL);
    08   g_return_if_fail (group != NULL);
    09   g_return_if_fail (group->name != NULL);
    10   g_return_if_fail (group->description != NULL);
    11   g_return_if_fail (group->help_description != NULL);
    12
    13   for (list = context->groups; list; list = list->next)
    14     {
    15       GOptionGroup *g = (GOptionGroup *)list->data;
    16
    17       if ((group->name == NULL && g->name == NULL) ||
    18           (group->name && g->name && strcmp (group->name, g->name) == 0))
    19         g_warning («A group named \»%s\» is already” “part of this GOptionContext»,
    20                    group->name);
    21     }
    22
    23   context->groups = g_list_append (context->groups, group);
    24 }

    Les groupes sont stockés dans le contexte en tant que liste chaînée. Nous l’avions déjà supposé en voyant le type du champ groups dans un GOptionContext. Nous en avons la preuve ici : notre groupe est inséré aux autres avec g_list_append() ligne 23. Le code précédent, lignes 13 à 21 est une petite vérification : tous les groupes existants sont parcourus (boucle ligne 13) et leur nom est comparé à celui du groupe que nous voulons ajouter (test lignes 17 et 18). Si tel était le cas, un petit message d’avertissement est produit lignes 19 et 20.

    L’analyse de la ligne de commande

    Nous voici arrivés à ce qui nous intéresse le plus : l’analyse de la ligne de commande. Malheureusement, les lignes de code sont trop nombreuses pour être reproduites ici. Nous allons donc devoir nous limiter à certaines parties. Nous ne montrerons donc en particulier pas les parties répétitives, ni celles ne présentant pas grand intérêt comme les tests des arguments ou les initialisations basiques.

    01 gboolean
    02 g_option_context_parse (GOptionContext   *context,
    03                         gint             *argc,
    04                         gchar          ***argv,
    05                         GError          **error)
    06 {

    Ce qui suit détermine le nom du programme et fait appel à g_set_prgname(). Puis une boucle parcourt la liste des groupes (context->groups) et y exécute, pour chacun, la fonction pre_parse_func() qui lui est associée (par le champ du même nom) si celle-ci est définie. Cette fonction est également exécutée pour le groupe principal context->main_group si elle est définie. Un code similaire se trouve plus loin pour la gestion des erreurs. Nous arrivons à la ligne 48 qui démarre l’analyse des arguments.

    48   if (argc && argv)
    49     {
    50       gboolean stop_parsing = FALSE;
    51       gboolean has_unknown = FALSE;
    52       gint separator_pos = 0;
    53
    54       for (i = 1; i < *argc; i++)
    55         {
    56           gchar *arg, *dash;
    57           gboolean parsed = FALSE;
    58
    59           if ((*argv)[i][0] == ‚-' && (*argv)[i][1] != ‚\0' && !stop_parsing)
    60             {
    61               if ((*argv)[i][1] == ‚-')
    62                 {
    63                   /* -- option */
    64

    Pour chaque argument, nous testons s’il commence par un tiret (boucle ligne 54 et test ligne 59). S’il n’y a pas de problème, nous avons deux cas à distinguer : les options courtes et les options longues. Nous allons voir les options longues et faire l’impasse sur les options courtes, le code étant relativement le même. En cas de problème, ce n’est pas vraiment un problème. Nous avons seulement affaire à un argument non référencé qui sera traité lignes 234 à 240.

    65                   arg = (*argv)[i] + 2;
    66
    67                   /* '--' terminates list of arguments */
    68                   if (*arg == 0)
    69                     {
    70                       separator_pos = i;
    71                       stop_parsing = TRUE;
    72                       continue;
    73                     }
    74

    Ligne 65, nous faisons pointer arg sur le nom de l’option. Si celui-ci est nul, l’option était -- qui signifie que nous devons arrêter l’analyse. C’est l’objet de la ligne 71.

    75                   /* Handle help options */
    76                   if (context->help_enabled)
    77                     {
    78                       if (strcmp (arg, «help») == 0)
    79                         print_help (context, TRUE, NULL);
    80                       else if (strcmp (arg, «help-all») == 0)
    81                         print_help (context, FALSE, NULL);
    82                       else if (strncmp (arg, «help-», 5) == 0)
    83                         {
    84                           GList *list;
    85
    86                           list = context->groups;
    87
    88                           while (list)
    89                             {
    90                               GOptionGroup *group = list->data;
    91
    92                               if (strcmp (arg + 5, group->name) == 0)
    93                                 print_help (context, FALSE, group);
    94
    95                               list = list->next;
    96                             }
    97                         }
    98                     }
    99

    Il est possible de désactiver l’aide en mettant FALSE dans context->help_enabled. C’est d’ailleurs l’objet de la fonction g_option_context_set_help_enabled() dont nous ne parlerons pas plus ici. Si elle est activée (test ligne 76), nous devons tester si l’option est un dérivé de help. Vous faites ici connaissance avec la fonction print_help() dont nous avons toutes les utilisations possibles lignes 79, 81 et 93. Son premier argument indique le contexte. Le deuxième indique si nous montrons l’aide du groupe principal. Le dernier correspond à un groupe dont il faut afficher l’aide. Vous verrez plus loin que la fonction print_help() se termine par un appel à exit().

    100                   if (context->main_group &&
    101                       !parse_long_option (context, context->main_group, &i, arg,
    102                             FALSE, argc, argv, error, &parsed))
    103                     goto fail;
    104
    105                   if (parsed)
    106                     continue;
    107
    108                   /* Try the groups */
    109                   list = context->groups;
    110                   while (list)
    111                     {
    112                       GOptionGroup *group = list->data;
    113
    114                       if (!parse_long_option (context, group, &i, arg,
    115                             FALSE, argc, argv, error, &parsed))
    116                         goto fail;
    117
    118                       if (parsed)
    119                         break;
    120
    121                       list = list->next;
    122                     }
    123
    124                   if (parsed)
    125                     continue;
    126

    Pour chaque groupe, et tant que nous n’avons pas trouvé l’entrée correspondante, nous cherchons si elle y est avec parse_long_option(). Nous commençons avec le groupe principal ligne 101. Si elle y est (test ligne 105), nous passons à la suite (ligne 106). Sinon, nous attaquons la liste des groupes secondaires (boucle ligne 110) dont nous sortons ligne 119 si nous avons trouvé. Lignes 124 et 125, nous passons à la suite si l’option était dans un groupe.

    127             /* Now look for --<group>-<option> */
    128             dash = strchr (arg, '-');
    129             if (dash)
    130               {
    131                 /* Try the groups */
    132                 list = context->groups;
    133                 while (list)
    134                   {
    135                     GOptionGroup *group = list->data;
    136
    137                     if (strncmp (group->name, arg, ash - arg) == 0)
    138                       {
    139                         if (!parse_long_option (context, group, &i, dash + 1,
    140                              TRUE, argc, argv, error, &parsed))
    141                           goto fail;
    142
    143                         if (parsed)
    144                           break;
    145                       }
    146
    147                     list = list->next;
    148                   }
    149               }
    150

    Vous découvrez ici qu’il est également possible de faire précéder le nom de l’option par le nom du groupe correspondant, avec un tiret séparateur. Si ce tiret existe (test ligne 128), nous recherchons le groupe portant le nom correspondant (boucle ligne 133 et test ligne 137). Pour ce groupe, nous faisons appel à la fonction parse_long_option() (ligne 139) comme précédemment.

    151                   if (context->ignore_unknown)
    152                     continue;
    153                 }
    154               else
    155                 { /* short option */

    Si les options inconnues sont ignorées, nous itérons de force (ligne 152). Sinon, nous trouverons du code dédié aux options inconnues plus loin. Nous passons aux options courtes, mais ce code ressemblant au précédent, nous allons utiliser le sécateur jusqu’à la ligne 217. Vous avez ci-dessous le code correspondant à une option non analysée. Si context->ignore_unknown est nul, nous générons une erreur et, comble de l’horreur, exécutons un goto !

    218                 }
    219
    220               if (!parsed)
    221                 has_unknown = TRUE;
    222
    223               if (!parsed && !context->ignore_unknown)
    224                 {
    225                   g_set_error (error,
    226                             G_OPTION_ERROR,
    				G_OPTION_ERROR_UNKNOWN_OPTION,
    227                                    _(«Unknown option %s»), (*argv)[i]);
    228                   goto fail;
    229                 }

    Ce goto est utilisé à bon escient. Les langages plus évolués que le C proposent un mécanisme qui, en soulevant une erreur, nous envoie directement et de façon inconditionnelle à une portion de code dédiée à la gestion des erreurs. C’est le cas avec try et catch en java par exemple. Ici, la gestion de l’erreur s’effectue au label fail.

    230             }
    231          else
    232            {
    233              /* Collect remaining args */
    234              if (context->main_group &&
    235                 !parse_remaining_arg (context, context->main_group, &i,
    236                                      argc, argv, error, &parsed))
    237                goto fail;
    238
    239              if (!parsed && (has_unknown || (*argv)[i][0] == '-'))
    240                separator_pos = 0;
    241            }
    242        }

    Nous avons ci-dessus la gestion des arguments au-delà de l’option -- ou des arguments non précédés par un tiret. Ils correspondent à la condition négative du test ligne 59.
    Nous avons ci-dessous, où plutôt nous avions avant que l’auteur n’efface les lignes, du code pour exécuter les fonctions de post-analyse, de la même façon que nous avions au début les fonctions de pré-analyse de la ligne de commande. Elles ne présentent pas d’intérêt. Vous trouverez un code similaire plus loin pour la gestion des erreurs. Nous allons directement à la fin où les arguments sont une nouvelle fois analysés afin de supprimer ceux qui ont été traités. Ne restent donc que ceux dont l’analyseur n’a su que faire. Par ailleurs, si context->ignore_unknown est vrai, ce code n’a pas lieu d’être exécuté.

    272   if (argc && argv)
    273     {
    274       free_pending_nulls (context, TRUE);
    275
    276       for (i = 1; i < *argc; i++)
    277         {
    278           for (k = i; k < *argc; k++)
    279             if ((*argv)[k] != NULL)
    280               break;
    281
    282           if (k > i)
    283             {
    284               k -= i;
    285               for (j = i + k; j < *argc; j++)
    286                 {
    287                   (*argv)[j-k] = (*argv)[j];
    288                   (*argv)[j] = NULL;
    289                 }
    290               *argc -= k;
    291             }
    292         }
    293     }
    294
    295   return TRUE;
    296

    Nous arrivons enfin à la gestion des erreurs et au label fail. Ce code est similaire à ceux qui font appel aux fonctions de pré-analyse au début et aux fonctions de post-analyse au milieu de la fonction g_option_context_parse(). Au lieu d’utiliser les champs pre_parse_func() ou post_parse_func(), il s’agit ici de error_func(). Pour chaque groupe (boucle ligne 301), nous testons si une telle fonction existe (ligne 305) et, le cas échéant, nous l’exécutons (ligne 306). Cette démarche est également effectuée pour le groupe principal (test ligne 312 et exécution ligne 313).

    297  fail:
    298
    299   /* Call error hooks */
    300   list = context->groups;
    301   while (list)
    302     {
    303       GOptionGroup *group = list->data;
    304
    305       if (group->error_func)
    306         (* group->error_func) (context, group,
    307                                group->user_data, error);
    308
    309       list = list->next;
    310     }
    311
    312   if (context->main_group && context->main_group->error_func)
    313     (* context->main_group->error_func) (context, context->main_group,
    314        context->main_group->user_data, error);
    315
    316   free_changes_list (context, TRUE);
    317   free_pending_nulls (context, FALSE);
    318
    319   return FALSE;
    320 }

    Un peu de nettoyage lignes 316 et 317 et nous voici avec quelques fonctions qui nous restent sur les bras.

    parse_long_option()

    Commençons avec cette fonction. Les curieux iront également voir parse_short_option() dont le code est proche de celle-ci. Juste avant, voyons une macro, NO_ARG dont nous allons avoir besoin très vite :

    01 #define NO_ARG(entry) ((entry)->arg == G_OPTION_ARG_NONE ||       \
    02                        ((entry)->arg == G_OPTION_ARG_CALLBACK &&  \
    03                         ((entry)->flags & G_OPTION_FLAG_NO_ARG)))

    Cette macro teste si l’option est censée prendre un argument ou non. Nous la retrouvons dans le code suivant, ligne 22.

    Cette fonction n’est rien d’autre qu’une boucle (ligne 14) qui recherche la bonne option, ou plutôt l’entrée du groupe dont le nom (long, puisque nous ne nous occupons ici que des options longues) de l’option correspond à celle en cours d’analyse.

    01 static gboolean
    02 parse_long_option (GOptionContext *context,
    03                    GOptionGroup   *group,
    04                    gint           *index,
    05                    gchar          *arg,
    06                    gboolean        aliased,
    07                    gint           *argc,
    08                    gchar        ***argv,
    09                    GError        **error,
    10                    gboolean       *parsed)
    11 {
    12   gint j;
    13
    14   for (j = 0; j < group->n_entries; j++)
    15  {
    16   if (*index >= *argc)
    17     return TRUE;
    18
    19   if (aliased && (group->entries[j].flags & G_OPTION_FLAG_NOALIAS))
    20      continue;
    21

    Nous commençons par tester le cas d’un simple drapeau, soit d’une option au format --option sans argument supplémentaire. Le test suivant vérifie l’égalité entre l’entrée courante et l’option courante ainsi que le fait que l’option ne prenne pas d’argument. Si tel est le cas, nous exécutons parse_arg() dont nous verrons le code plus loin.

    22       if (NO_ARG (&group->entries[j]) &&
    23           strcmp (arg, group->entries[j].long_name) == 0)
    24         {
    25           gchar *option_name;
    26
    27           option_name = g_strconcat («--», group->entries[j].long_name, NULL);
    28           parse_arg (context, group, &group->entries[j],
    29                      NULL, option_name, error);
    30           g_free(option_name);
    31
    32           add_pending_null (context, &((*argv)[*index]), NULL);
    33           *parsed = TRUE;
    34         }
    35       else
    36         {

    Nous n’avons pas affaire à une simple option, mais à une qui prend un argument. Nous testons si celui-ci a été indiqué sous la forme --option=argument ou --option argument ligne 39. Sinon, l’option n’est pas analysée (fin du bloc d’exécution ligne 105).

    37    gint len = strlen (group->entries[j].long_name);
    38
    39    if (strncmp (arg, group->entries[j].long_name, len) == 0 &&
    40        (arg[len] == '=' || arg[len] == 0))
    41      {
    42        gchar *value = NULL;
    43        gchar *option_name;
    44
    45        add_pending_null (context, &((*argv)[*index]), NULL);
    46        option_name = g_strconcat («--», group->entries[j].long_name, NULL);
    47

    Ci-dessus, nous avons obtenu le nom de l’option. Ci-après, nous allons chercher à obtenir sa valeur. Le cas --option=argument est simple et traité en deux lignes (48 et 49). Nous entrons ensuite dans divers autres cas que nous n’allons pas détailler :

    • ligne 50 : nous ne sommes pas encore au dernier argument. Nous traitons le cas où l’argument est facultatif (test ligne 52). Si ce n’est pas le cas, nous avons la valeur ligne 54. S’il est effectivement facultatif, il faut vérifier l’argument suivant : commence-t-il par un tiret (test ligne 60) ? Si oui, nous analysons l’option directement ligne 63 et quittons ligne 67. Sinon, nous avons affaire à la valeur qui a été donnée sur la ligne de commande ;
    • ligne 77 : nous sommes au dernier argument, mais pour cette option, la valeur est facultative (nous sommes donc dans le cas où cette valeur n’a pas été indiquée). La fonction parse_arg() est appelée avec NULL en guise de quatrième argument, la valeur ligne 67. Sinon, nous avons affaire à la valeur qui a été donnée sur la ligne de commande. Ce code est identique aux lignes 62 à 67 ;
    • ligne 87 : tous les autres cas sont des cas d’erreur : il manque un argument. La fonction retourne FALSE après avoir appelé g_set_error().
    48               if (arg[len] == '=')
    49                 value = arg + len + 1;
    50               else if (*index < *argc - 1)
    51                 {
    52                   if (!(group->entries[j].flags & G_OPTION_FLAG_OPTIONAL_ARG))
    53                     {
    54                       value = (*argv)[*index + 1];
    55                       add_pending_null (context, &((*argv)[*index + 1]), NULL);
    56                       (*index)++;
    57                     }
    58                   else
    59                     {
    60                       if ((*argv)[*index + 1][0] == '-')
    61                         {
    62                           gboolean retval;
    63                           retval = parse_arg (context, group, &group->entries[j],
    64                                    NULL, option_name, error);
    65                             *parsed = TRUE;
    66                           g_free (option_name);
    67                              return retval;
    68                         }
    69                       else
    70                         {
    71                           value = (*argv)[*index + 1];
    72                           add_pending_null (context, &((*argv)[*index + 1]), NULL);
    73                           (*index)++;
    74                         }
    75                     }
    76                 }
    77               else if (*index >= *argc - 1 &&
    78                        group->entries[j].flags & G_OPTION_FLAG_OPTIONAL_ARG)
    79                 {
    [ code identique aux lignes 62 à 67 ]
    86                 }
    87               else
    [ code de gestion d'erreur ]

    Lorsque nous arrivons ici, nous avons une option nommée option_name et sa valeur value. Analysons cela avec parse_arg() avant de faire le ménage et de retourner d’où nous venons :

    96             if (!parse_arg (context, group, &group->entries[j],
    97                             value, option_name, error))
    98               {
    99                 g_free (option_name);
    100                 return FALSE;
    101               }
    102
    103             g_free (option_name);
    104             *parsed = TRUE;
    105           }
    106       }
    107   }
    108
    109  return TRUE;
    110 }

    parse_remaining_arg()

    Lorsque nous sommes au-delà de l’option -- ou que nous avons un argument non précédé par un tiret, nous avons vu plus haut que nous devions appeler parse_remaining_arg() :

    static gboolean
    parse_remaining_arg (GOptionContext *context,
    		     GOptionGroup   *group,
    		     gint           *index,
    		     gint           *argc,
    		     gchar        ***argv,
    		     GError        **error,
    		     gboolean       *parsed)
    {
      gint j;
    
      for (j = 0; j < group->n_entries; j++)
        {
          if (*index >= *argc)
    	return TRUE;
    
          if (group->entries[j].long_name[0])
    	continue;
    
          g_return_val_if_fail (group->entries[j].arg == G_OPTION_ARG_STRING_ARRAY ||
    group->entries[j].arg == G_OPTION_ARG_FILENAME_ARRAY, FALSE);
    
          add_pending_null (context, &((*argv)[*index]), NULL);
    
          if (!parse_arg (context, group, &group->entries[j], (*argv)[*index], «», error))
    	return FALSE;
    
          *parsed = TRUE;
          return TRUE;
        }
    
      return TRUE;
    }

    parse_arg()

    Nous avons vu des appels à cette fonction qui, comme son nom l’indique, analyse un argument. En voici le code :

    01 static gboolean
    02 parse_arg (GOptionContext *context,
    03            GOptionGroup   *group,
    04            GOptionEntry   *entry,
    05            const gchar    *value,
    06            const gchar    *option_name,
    07            GError        **error)
    08
    09 {
    10   Change *change;
    11
    12   g_assert (value || OPTIONAL_ARG (entry) || NO_ARG (entry));
    13
    14   switch (entry->arg)
    15     {
    16     case G_OPTION_ARG_NONE:
    17       {
    18         change = get_change (context, G_OPTION_ARG_NONE,
    19                              entry->arg_data);
    20
    21         *(gboolean *)entry->arg_data = !(entry->flags & G_OPTION_FLAG_REVERSE);
    22          break;
    23       }
    24     case G_OPTION_ARG_STRING:
    25       {
    26         gchar *data;
    27
    28         data = g_locale_to_utf8 (value, -1, NULL, NULL, error);
    29
    30         if (!data)
    31           return FALSE;
    32
    33         change = get_change (context, G_OPTION_ARG_STRING,
    34                              entry->arg_data);
    35         g_free (change->allocated.str);
    36
    37         change->prev.str = *(gchar **)entry->arg_data;
    38         change->allocated.str = data;
    39
    40         *(gchar **)entry->arg_data = data;
    41         break;
    42       }
    43     case G_OPTION_ARG_STRING_ARRAY:
    [...]
    75     case G_OPTION_ARG_FILENAME:
    [...]
    98     case G_OPTION_ARG_FILENAME_ARRAY:
    [...]
    133     case G_OPTION_ARG_INT:
    134       {
    135         gint data;
    136
    137         if (!parse_int (option_name, value,
    138                          &data,
    139                         error))
    140           return FALSE;
    141
    142         change = get_change (context, G_OPTION_ARG_INT,
    143                              entry->arg_data);
    144         change->prev.integer = *(gint *)entry->arg_data;
    145         *(gint *)entry->arg_data = data;
    146         break;
    147       }
    148     case G_OPTION_ARG_CALLBACK:
    [...]
    180     case G_OPTION_ARG_DOUBLE:
    [...]
    197     case G_OPTION_ARG_INT64:
    [...]
    214     default:
    215       g_assert_not_reached ();
    216     }
    217
    218   return TRUE;
    219 }

    Le code se répétant plus ou moins, nous avons éliminé le code correspondant à la plupart des cas. Étudions néanmoins le cas d’une chaîne de caractères, puis celui d’un entier. Nous avons d’abord une pré-analyse via une transformation en UTF8 (ligne 28) pour une chaîne, alors que pour un entier, nous appelons parse_int() (ligne 137). Puis la fonction get_change() se charge de créer le nécessaire pour sauvegarder la valeur initiale de l’argument. La sauvegarde a lieu lignes 37 et 38 pour une chaîne, et ligne 144 pour un entier. Puis nous modifions la ligne entry correspondant à notre argument dans le tableau des options, lignes 40 ou 145.
    Normalement, le cas par défaut ne devrait jamais survenir. Aussi, plutôt que de ne rien mettre, les développeurs préfèrent en général provoquer une erreur si cela arrivait, ce qui est le cas ligne 215.

    Voyons encore la fonction get_change() dont nous avons parlé précédemment :

    01 static Change *
    02 get_change (GOptionContext *context,
    03             GOptionArg      arg_type,
    04             gpointer        arg_data)
    05 {
    06   GList *list;
    07   Change *change = NULL;
    08
    09   for (list = context->changes; list != NULL; list = list->next)
    10     {
    11       change = list->data;
    12
    13       if (change->arg_data == arg_data)
    14         goto found;
    15     }
    16
    17   change = g_new0 (Change, 1);
    18   change->arg_type = arg_type;
    19   change->arg_data = arg_data;
    20
    21   context->changes = g_list_prepend (context->changes, change);
    22
    23  found:
    24
    25   return change;
    26 }

    Cette fonction recherche tout simplement si une sauvegarde a déjà eu lieu. Le cas échant, vous voyez alors un goto ligne 14 qui n’est pas moins propre que le return change que vous auriez pu lire à la place. Sinon, un nouveau changement est créé ligne 17 et pré-rempli lignes 18 et 19. Ce changement est ajouté au début de la liste des changements ligne 21.
    L’intérêt de sauvegarder les valeurs précédentes est de pouvoir les restaurer. Si vous faites une recherche sur « changes » dans glib/goption.c, c’est dans get_change() que vous trouverez le plus d’occurrences évidemment, mais vous en trouvez également dans free_changes_list(). Le second argument de cette fonction est explicite : il s’appelle revert. Il s’agit donc bien d’un retour en arrière. Vous pouvez maintenant mieux comprendre la fin du code de g_option_context_parse() que nous avons vu précédemment : lors d’un échec, nous avions vu un appel à goto fail. A la suite du label fail, vous trouvez ligne 316 l’appel à free_changes_list().

    print_help()

    Enfin, nous arrivons à la fonction print_help() qui affiche l’aide. Mais avant de lire son code, voyez la macro TRANSLATE qui est utilisée plusieurs fois dans print_help() :

    01 #define TRANSLATE(group, str) (((group)->translate_func ? (* (group)->translate_func) ((str), (group)->translate_data) : (str)))

    S’il existe une fonction de traduction définie pour le groupe indiqué, elle est utilisée pour traduire str. Sinon, c’est str elle-même qui est utilisée.
    Voici maintenant la tant attendue print_help(). Ce code est assez long, mais comme peu de choses sont redondantes, nous allons le voir en entier :

    01 static void
    02 print_help (GOptionContext *context,
    03             gboolean        main_help,
    04             GOptionGroup   *group)
    05 {
    06   GList *list;
    07   gint max_length, len;
    08   gint i;
    09   GOptionEntry *entry;
    10   GHashTable *shadow_map;
    11   gboolean seen[256];
    12   const gchar *rest_description;
    13

    Les options restantes et le résumé

    Le code suivant analyse les options restantes. Nous découvrons cette fonctionnalité en lisant le code, et c’est en recherchant arg_description dans glib/goption.c puis G_OPTION_REMAINING dans ce même fichier et ensuite dans la documentation officielle que nous avons l’explication. Si dans vos entrées vous en définissez une de type G_OPTION_ARG_STRING_ARRAY ou G_OPTION_ARG_FILENAME_ARRAY, et que vous mettez G_OPTION_REMAINING en guise d’option longue (la chaîne correspondante est une chaîne vide : #define G_OPTION_REMAINING «»), alors sur la ligne votre_programme [OPTION...]... vous trouverez la description de ces options restantes. L’affichage de cette ligne a lieu lignes 29 à 34 et la recherche de la bonne description se trouve dans la boucle lignes 18 à 26.
    Le résumé est affiché ligne 37 s’il existe (test ligne précédente).

    14   rest_description = NULL;
    15   if (context->main_group)
    16     {
    17
    18       for (i = 0; i < context->main_group->n_entries; i++)
    19         {
    20           entry = &context->main_group->entries[i];
    21           if (entry->long_name[0] == 0)
    22             {
    23               rest_description = TRANSLATE (context->main_group, entry->arg_description);
    24               break;
    25             }
    26         }
    27     }
    28
    29   g_print («%s\n  %s %s%s%s%s%s\n\n»,
    30            _(«Usage:»), g_get_prgname(), _(«[OPTION...]»),
    31            rest_description ? « « : «»,
    32            rest_description ? rest_description : «»,
    33            context->parameter_string ? « « : «»,
    34            context->parameter_string ? TRANSLATE (context, context->parameter_string) : «»);
    35
    36   if (context->summary)
    37     g_print («%s\n\n», TRANSLATE (context, context->summary));
    38

    Résolution des conflits

    Nous allons maintenant résoudre les conflits, à savoir les options portant le même nom long et celles le même nom court. Pour cela, nous effectuons une première passe lignes 42 à 56. Pour les options longues, nous nous contentons de les insérer dans une table de hachage. Pour les courtes, nous vérifions que nous ne les avons pas déjà vues, ce qui signifierait que l’entrée correspondante dans le tableau seen[] serait vraie (test ligne 51). Si tel est le cas, l’option est purement et simplement désactivée (ligne 52).

    39   memset (seen, 0, sizeof (gboolean) * 256);
    40   shadow_map = g_hash_table_new (g_str_hash, g_str_equal);
    41
    42   if (context->main_group)
    43     {
    44       for (i = 0; i < context->main_group->n_entries; i++)
    45         {
    46           entry = &context->main_group->entries[i];
    47           g_hash_table_insert (shadow_map,
    48                                (gpointer)entry->long_name,
    49                                entry);
    50
    51           if (seen[(guchar)entry->short_name])
    52             entry->short_name = 0;
    53           else
    54             seen[(guchar)entry->short_name] = TRUE;
    55         }
    56     }
    57

    Puis nous recommençons la même chose pour les groupes d’options. Mais est-ce vraiment le même code ? Pas tout à fait. En effet, il apparaît un test supplémentaire, si l’option longue a déjà été rencontrée. Dans ce cas, pas question de la désactiver. Mais nous la remplaçons par elle-même précédée du nom de son groupe (test lignes 65 et 66 ; remplacement ligne 67). Sinon, nous insérons l’option dans la table de hachage comme ci-dessus. Notez que le test n’a lieu que si vous n’avez pas une entrée flanquée du drapeau G_OPTION_FLAG_NOALIAS qui sert justement à cela.
    Le principe est le même pour les options courtes, lignes 71 à 75. A la fin, la table de hachage ne nous sert plus (pas plus d’ailleurs que le tableau seen[]) et elle est détruite ligne 80.

    58   list = context->groups;
    59   while (list != NULL)
    60     {
    61       GOptionGroup *group = list->data;
    62       for (i = 0; i < group->n_entries; i++)
    63         {
    64           entry = &group->entries[i];
    65           if (g_hash_table_lookup (shadow_map, entry->long_name) &&
    66               !(entry->flags && G_OPTION_FLAG_NOALIAS))
    67             entry->long_name = g_strdup_printf («%s-%s», group->name, entry->long_name);
    68           else
    69             g_hash_table_insert (shadow_map, (gpointer)entry->long_name, entry);
    70
    71           if (seen[(guchar)entry->short_name] &&
    72               !(entry->flags && G_OPTION_FLAG_NOALIAS))
    73             entry->short_name = 0;
    74           else
    75             seen[(guchar)entry->short_name] = TRUE;
    76         }
    77       list = list->next;
    78     }
    79
    80   g_hash_table_destroy (shadow_map);
    81

    Calcul de taille

    Le code suivant calcule la taille nécessaire pour afficher la colonne de gauche, celle contenant les noms des options. Le résultat arrive dans max_length et indique la taille maximale d’une option. A la fin, ligne 114, 4 octets sont ajoutés pour constituer un peu d’espace entre l’option et son descriptif.

    82   list = context->groups;
    83
    84   max_length = g_utf8_strlen («-?, --help», -1);
    85
    86   if (list)
    87     {
    88       len = g_utf8_strlen («--help-all», -1);
    89       max_length = MAX (max_length, len);
    90     }
    91
    92   if (context->main_group)
    93     {
    94       len = calculate_max_length (context->main_group);
    95       max_length = MAX (max_length, len);
    96     }
    97
    98   while (list != NULL)
    99     {
    100       GOptionGroup *group = list->data;
    101
    102       /* First, we check the --help-<groupname> options */
    103       len = g_utf8_strlen («--help-», -1) + g_utf8_strlen (group->name, -1);
    104       max_length = MAX (max_length, len);
    105
    106       /* Then we go through the entries */
    107       len = calculate_max_length (group);
    108       max_length = MAX (max_length, len);
    109
    110       list = list->next;
    111     }
    112
    113   /* Add a bit of padding */
    114   max_length += 4;
    115

    L’affichage des options

    Remarquez les trois morceaux de code. A moins qu’un groupe n’ait été indiqué sur la ligne de commande avec --help-<group>, le code lignes 116 à 140 est exécuté pour afficher de l’aide sur l’aide, en particulier la liste des groupes et les fameuses options --help-<group>.

    116   if (!group)
    117     {
    118       list = context->groups;
    119
    120       g_print («%s\n  -%c, --%-*s %s\n»,
    121                _(«Help Options:»), '?', max_length - 4, «help»,
    122                _(«Show help options»));
    123
    124       /* We only want --help-all when there are groups */
    125       if (list)
    126         g_print («  --%-*s %s\n», max_length, «help-all»,
    127                  _(«Show all help options»));
    128
    129       while (list)
    130         {
    131           GOptionGroup *group = list->data;
    132
    133           g_print («  --help-%-*s %s\n», max_length - 5, group->name,
    134                    TRANSLATE (group, group->help_description));
    135
    136           list = list->next;
    137         }
    138
    139       g_print (“\n”);
    140     }
    141

    Puis, c’est le tour de deux cas, si un groupe a été demandé explicitement (lignes 142 à 150), et sinon, si nous n’avons pas de groupe principal, tous les groupes (lignes 151 à 170).

    142   if (group)
    143     {
    144       /* Print a certain group */
    145
    146       g_print («%s\n», TRANSLATE (group, group->description));
    147       for (i = 0; i < group->n_entries; i++)
    148         print_entry (group, max_length, &group->entries[i]);
    149       g_print («\n»);
    150     }
    151   else if (!main_help)
    152     {
    153       /* Print all groups */
    154
    155       list = context->groups;
    156
    157       while (list)
    158         {
    159           GOptionGroup *group = list->data;
    160
    161           g_print («%s\n», group->description);
    162
    163           for (i = 0; i < group->n_entries; i++)
    164             if (!(group->entries[i].flags & G_OPTION_FLAG_IN_MAIN))
    165               print_entry (group, max_length, &group->entries[i]);
    166
    167           g_print («\n»);
    168           list = list->next;
    169         }
    170     }
    171

    Enfin, si nous avons un groupe principal, ou qu’un groupe n’a pas été indiqué spécifiquement, nous affichons le groupe principal (lignes 179 à 182) suivi des autres options (lignes 184 à 194).

    172   /* Print application options if --help or --help-all has been specified */
    173   if (main_help || !group)
    174     {
    175       list = context->groups;
    176
    177       g_print («%s\n», _(«Application Options:»));
    178
    179       if (context->main_group)
    180         for (i = 0; i < context->main_group->n_entries; i++)
    181           print_entry (context->main_group, max_length,
    182                        &context->main_group->entries[i]);
    183
    184       while (list != NULL)
    185         {
    186           GOptionGroup *group = list->data;
    187
    188           /* Print main entries from other groups */
    189           for (i = 0; i < group->n_entries; i++)
    190             if (group->entries[i].flags & G_OPTION_FLAG_IN_MAIN)
    191               print_entry (group, max_length, &group->entries[i]);
    192
    193           list = list->next;
    194         }
    195
    196       g_print (“\n”);
    197     }
    198

    A la fin, avant de quitter, si une description existe dans le contexte, nous l’affichons.

    199   if (context->description)
    200     g_print (“%s\n”, TRANSLATE (context, context->description));
    201
    202   exit (0);
    203 }

    g_option_context_set_description()

    Une telle description peut être indiquée avec g_option_context_set_description() :

    01 void
    02 g_option_context_set_description (GOptionContext *context,
    03                             const gchar    *description)
    04 {
    05   g_return_if_fail (context != NULL);
    06
    07   g_free (context->description);
    08   context->description = g_strdup (description);
    09 }

    Cette fonction sert principalement à indiquer un message à afficher en fin de l’aide, et il est souhaitable d’y indiquer un site web où trouver plus d’informations sur le sujet, ainsi qu’une adresse électronique pour soumettre les bugs éventuels.

    Conclusion

    Cet article très long vous aura permis de voir comment une gestion complète des options peut se faire. Les programmeurs utilisent généralement une simple série de printf() dans leur code alors qu’avec de bonnes fonctions, vous pouvez arriver à un résultat plus abouti.

    En l’occurrence, le système que propose GLib permet d’avoir un affichage de l’aide toujours à jour, car il fonctionne sur la base d’un référencement. Si vous n’avez pas indiqué l’option au système, il ne peut en tenir compte pour l’analyse de la ligne de commande.

    Au contraire, si l’option est connue, non seulement elle pourra être analysée, mais, en plus, elle fera partie de l’aide. Enfin, GLib propose une notion de groupes d’options qui n’est pas négligeable, en particulier pour tous ceux qui programment des greffons. Ceux-ci ajoutent des options facultatives, dont la présence dépend de si le greffon est chargé ou non. Maintenant que vous avez pris connaissance de ce code, servez-vous-en !

    Le mois prochain : les arbres binaires balancés de type GTree...

    Référence :

    Le site de GTK+ : http://www.gtk.org/

    Retrouvez cet article dans : Linux Magazine 89

    Posté par Yves Mettier (Yves Mettier) | Signature : Yves Mettier | Article paru dans

    Laissez une réponse

    Vous devez avoir ouvert une session pour écrire un commentaire.

    • Il y a actuellement

    • 861 articles/billets en ligne.