Pacte écologique

Mode d'emploi de l'interpréteur de commandes CI

Dernière mise à jour : 18/06/2006 23:17 clib


Construction de l'interpréteur de commandes CI

1 - Construire un environnement de développement pour la bibliothèque CLIB

.../clib/ed/inc
.../clib/ed/src
.../clib/ed/lib

2 - Récupérer les fichiers

Dans .../inc :

- ansi.itm
- ascii.h
- assert.h
- bits.h
- ci.h
- ci_err.utm
- cnt.h
- cnt_err.itm
- fstr.h
- fstr_err.itm
- io.h
- pc_dbg.h
- str.h
- sys.h
- sysalloc.h
- tok.h
- tok_err.itm
- types.h

Dans .../src :

- ascii.c
- assert.c
- ci.c
- cnt.c
- fstr.c
- io.c
- str.c
- sysalloc.c
- tok.c

Les 9 fichiers sources servent éventuellement à générer la bibliothèque.

Nota : Bien sûr, on est pas obligé de générer la bibliothèque, on peut aussi ajouter ces fichiers au projet.

3 - Créer la bibliothèque

Le chemin des fichiers inclus est

.../clib

Le chemin des fichiers sources (.c) est

.../clib/ed/src

Le chemin de la sortie est

.../clib/ed/lib

La macro DBG_SYSALLOC doit être définie globalement. Typiquement (Borland C, gcc) :

... -DDBG_SYSALLOC

Générer la bibliothèque (Les détails dépendent de l'implémentation).

4 - Créer une application de test

Celle-ci est composée de 3 fichiers. Le programme principal main.c et un ensemble de fonctions appelées par le shell (ainsi que le fichier d'interface qui va avec).

/* main.c */
#include <stdio.h>
#include <stdlib.h>

#include "ed/inc/sysalloc.h"
#include "ed/inc/sys.h"
#include "ed/inc/io.h"

#include "app.h"

/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private variables =================================================== */
/* private functions =================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int cb_out (void *p_user, char const *s)
{
   int err = 0;
   FILE *fp = p_user;

   if (fp != NULL)
   {
      fprintf (fp, "%s", s);
      fflush (fp);
   }
   else
   {
      err = 1;
   }
   return err;
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static void print_prompt (void)
{
   printf ("\n> ");
   fflush (stdout);
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static void print_err (ci_err_e err)
{
   printf ("CI.ERR: %s\n", ci_serr (err));
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static void test (void)
{
   ci_s ci;

   static ci_cfg_s const a_cfg[] =
      {
         {"date", date_man, date_cde},
         {"time", time_man, time_cde},
      };

   char s_user[] = "User";

   ci_err_e err = ci_init (&ci, a_cfg, NELEM (a_cfg));

   if (err == CI_OK)
   {
      ci_install_out (&ci, cb_out, stdout);
      {
         int end = 0;
         while (!end)
         {
            print_prompt ();
            {
               char *s_line = get_line ();
               if (s_line != NULL)
               {
                  err = ci_in (&ci, s_line, s_user);
                  end = err == CI_ERR_QUIT;
                  FREE (s_line);
                  if (err && !end)
                  {
                     print_err (err);
                  }
               }
               else
               {
                  end = 1;
               }
            }
         }
      }
   }
   else
   {
      print_err (err);
   }
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
static int main_ (void)
{
   test ();

   return 0;
}

/* entry points ======================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int main (void)
{
   int ret;
   static char Trace[1 << 8];

   SYS_INIT (Trace, OFF);

   ret = main_ ();

   sys_mem_trace ();

   return ret;
}
/* app.h */
#ifndef H_ED_APP_20050311102737
#define H_ED_APP_20050311102737

/* app.h */

#ifdef __cplusplus
extern "C"
{
#endif

#include "ed/inc/ci.h"

/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* internal public data ================================================ */
/* internal public functions =========================================== */
/* entry points ======================================================== */

/* commands */
   ci_cde_f date_cde;
   ci_cde_f time_cde;

/* public data ========================================================= */

/* helps */
   extern char const date_man[];
   extern char const time_man[];

#ifdef __cplusplus
}
#endif

#endif                          /* guard */

/* Guards added by GUARD (c) ED 2000-2005 Feb 07 2005 Ver. 1.7 */
/* app.c */
/* app.c */

#include "app.h"

#include <stdio.h>
#include <time.h>

/* macros ============================================================== */
/* constants =========================================================== */
/* types =============================================================== */
/* structures ========================================================== */
/* private data ======================================================== */
/* private functions =================================================== */
/* internal public data ================================================ */
/* internal public functions =========================================== */
/* entry points ======================================================== */

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int date_cde (int argc, char const **argv, void *p_user)
{
   int ret = 0;

   if (argc == 1)
   {
      time_t now = time (NULL);
      struct tm tm = *localtime (&now);
      char s[64];
      char const *s_user = p_user ? p_user : "";

      strftime (s, sizeof s, "%A %d-%m-%Y (week %W)", &tm);
      printf ("[%s] today is %s\n", s_user, s);
   }
   else
   {
      printf ("date change ...\n");
   }

   return ret;
}

/* ---------------------------------------------------------------------
   --------------------------------------------------------------------- */
int time_cde (int argc, char const **argv, void *p_user)
{
   int ret = 0;

   if (argc == 1)
   {
      time_t now = time (NULL);
      struct tm tm = *localtime (&now);
      char s[16];
      char const *s_user = p_user ? p_user : "";

      strftime (s, sizeof s, "%H:%M:%S", &tm);
      printf (" %s\n", s);
      printf ("[%s] it's now %s\n", s_user, s);
   }
   else
   {
      printf ("time change ...\n");
   }


   return ret;
}

/* public data ========================================================= */

char const date_man[] =
{
   "[yyyy[ mm[ dd]]]\n"
};

char const time_man[] =
{
   "[hh[ mm[ ss]]]\n"
};

La macro DBG_SYSALLOC doit être définie globalement.

Une fois lancé, ce code produit :

SYSALLOC Overload (10 rec)
SYSALLOC Successful initialization: 10 records available

>

On peut ensuite passer des commandes :

SYSALLOC Overload (10 rec)
SYSALLOC Successful initialization: 10 records available

> help
command list
 help      quit      date      time

> date
[User] today is Friday 16-06-2006 (week 24)

> time
 11:18:56
[User] it's now 11:18:56

> quit
SYSALLOC min=4294967295 max=4294967295 delta=0
SYSALLOC All-matched
SYSALLOC Released Memory

Press ENTER to continue.


Utilisation de l'interpréteur de commandes CI

Fonctionnement

Un interpréteur de commande est un mécanisme qui exécute une action en fonction d'une commande fournie sous forme textuelle. La ligne de commande est une chaine de caractères qui peut comporter la commande suivie ou non de paramètres séparés par un espace.

"commande"
"commande parametre ..."

L'action est implémentée par un appel de fonction au format classique et universel :

int action (int argc, char **argv);

Le principe est de définir une liste de commandes composée

   static ci_cfg_s const a_cfg[] =
      {
         {"date", date_man, date_cde},
         {"time", time_man, time_cde},
      };

Le système a 2 commandes internes préétablies : "quit" et "help".

Restrictions

Les commandes préétablies ne sont pas configurables.

Construire une application pas à pas

Le code minimum

Ce code minimum montre comment on construit l'interpreteur de commande avec ses 2 commandes intégrées 'help' et 'quit'. Dans un premier temps, il suffit de créer une structure ci_s et de l'initialiser avec ci_init() :

/* ci_01.c */
#include "ed/inc/ci.h"

#include <stdio.h>

/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
   if (err != CI_OK)\
   {\
      fprintf (stderr,\
              "ERR : %s(%d) at %s:%d\n"\
              , ci_serr(err)\
              , err\
              , __FILE__\
              , __LINE__\
              );\
   }\
}\
while (0)

int main (void)
{
   ci_s ci;
   ci_err_e err;

   err = ci_init (&, NULL, 0);
   PERR(err);

   return 0;
}

Ce n'est pas très spectaculaire, mais ça fonctionne. Comme il n'y a pas de boucle, le programme s'arrête tout de suite. Normal.

On ajoute maintenant une application minimaliste comprenant :

/* ci_02.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */

#include <stdio.h>

/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
   if (err != CI_OK)\
   {\
      fprintf (stderr,\
              "ERR : %s(%d) at %s:%d\n"\
              , ci_serr(err)\
              , err\
              , __FILE__\
              , __LINE__\
              );\
   }\
}\
while (0)

int main (void)
{
   ci_s ci;
   ci_err_e err;

   /* initialisation  */
   err = ci_init (&, NULL, 0);
   PERR(err);

   /* boucle applicative */
   {
      int end = 0;
      do
      {
         /* prompt */
         printf ("> ");
         fflush (stdout);
         /* lecture d'une ligne */
         {
            char *line = get_line();
            if (line != NULL)
            {
               /* passage de la ligne à l'interpreteur de commande */
               err = ci_in(&ci, line, NULL);
               PERR(err);

               /* detection de la fin */
               end = err == CI_ERR_QUIT;
            }
            free (line), line = NULL;
         }
      }
      while (!end);
   }
   return 0;
}

avec un petit exemple d'exécution :

> sdsdsd
> help
> quit
ERR : quit command(5) at ci_02.c:49

Press ENTER to continue.

On constate que la commande inconnue ne provoque pas de reaction, de même que la commande d'aide (help). C'est du au fait que le module CI n'a pas, en interne, de fonction de sortie. C'est à l'utilisateur de fournir cette fonction au module. Ceci permet de répondre à tous les cas (la sortie ne se fait pas forcément sur stdout, elle peut se faire dans un port série, un socket etc.).

Nous allons donc compléter le code avec une fonction de sortie sur stdout :

/* ci_03.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */

#include <stdio.h>

/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
   if (err != CI_OK)\
   {\
      fprintf (stderr,\
              "ERR : %s(%d) at %s:%d\n"\
              , ci_serr(err)\
              , err\
              , __FILE__\
              , __LINE__\
              );\
   }\
}\
while (0)

/* definition d'une fonction de sortie de type ci_out_f */
static int ci_out_cb (void *p_user, char const *s)
{
   int ret = 0;
   printf ("%s", s);
   fflush (stdout);
   return ret;
}

int main (void)
{
   ci_s ci;
   ci_err_e err;

   /* initialisation  */
   err = ci_init (&ci, NULL, 0);
   PERR(err);

   /* installation de la fonction de sortie */
   err = ci_install_out (&, ci_out_cb, NULL);
   PERR(err);

   /* boucle applicative */
   {
      int end = 0;
      do
      {
         printf ("> ");
         fflush (stdout);

         {
            char *line = get_line();
            if (line != NULL)
            {
               err = ci_in(&ci, line, NULL);
               PERR(err);
               end = err == CI_ERR_QUIT;
            }
            free (line), line = NULL;
         }
      }
      while (!end);
   }
   return 0;
}

avec un petit exemple d'exécution :

> sdsdsd
> help
command list
 help      quit
> quit
ERR : quit command(5) at ci_03.c:58

Press ENTER to continue.

C'est terminé pour la partie 'minimale'. Nous allons ensuite aborder pas à pas le cas d'une application réelle.

Une application réelle

Nous allons définir une commande qui montre la version de l'application. Si on ajoute un paramètre 'lib', elle montre en plus la version des bibliothèques :

Commande

   ver[ lib]

Paramètres

   lib : optionnel

Comportement

   Affiche la version de l'application
   Le paramètre 'lib' provoque en plus l'affichage des bibliothèques.

Dans un premier temps, création d'une chaine "mode d'emploi" ou "aide" qui reprend l'essentiel de la spécification de la commande...

/* definition du mode d'emploi de la commande 'ver'.*/
static char const help_ver[] =
   "ver[ lib]\n"
   ;

puis création de la fonction de traitement. Un peu de code est placé dedans pour montrer le comportement des paramètres.

/* definition d'une fonction de traitement de commande de type ci_cde_f
   pour traiter la commande 'ver'
*/
static int cde_ver_cb (int argc, char const **argv, void *p_user)
{
   int ret = 0;

   /* quelques lignes pour monter le comportement des parametres (debug) */
   printf ("argc = %d\n", argc);
   {
      int i;
      for (i = 0; i < argc; i++)
      {
         printf ("argv[%d] = '%s'\n", i, argv[i]);
      }
   }

   return ret;
}

Enfin, integration de ces deux éléments dans le tableau de configuration. Son adresse et son nombre d'elements sont alors passés à ci_init() à la place de NULL et 0 :

/* ci_04.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include "ed/inc/sys.h" /* NELEM */

#include <stdio.h>
#include <string.h>

/* version de l'application */
#define VER "0.4"

/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
   if (err != CI_OK)\
   {\
      fprintf (stderr,\
              "ERR : %s(%d) at %s:%d\n"\
              , ci_serr(err)\
              , err\
              , __FILE__\
              , __LINE__\
              );\
   }\
}\
while (0)

/* definition du mode d'emploi de la commande 'ver'.
   La premiere ligne affiche automatiquement le nom de la commande.
   On peut ajouter les parametres et une ou des lignes
   ligne supplementaires pour illustrer...
*/
static char const help_ver[] =
   "[lib]\n"
   "Donne la version de l'application\n"
   "lib : liste aussi les versions des bibliotheques\n"
   ;

/* definition d'une fonction de traitement de commande de type ci_cde_f
   pour traiter la commande 'ver'
*/
static int cde_ver_cb (int argc, char const **argv, void *p_user)
{
   int ret = 0;

   /* quelques lignes pour montrer le comportement des parametres (debug) */
   printf ("argc = %d\n", argc);
   {
      int i;
      for (i = 0; i < argc; i++)
      {
         printf ("argv[%d] = '%s'\n", i, argv[i]);
      }
   }

   return ret;
}

/* definition d'une fonction de sortie de type ci_out_f */
static int ci_out_cb (void *p_user, char const *s)
{
   int ret = 0;
   printf ("%s", s);
   fflush (stdout);
   return ret;
}

int main (void)
{
   ci_s ci;
   ci_err_e err;

   /* tableau de structure de configuration (pour le moment, un seule commande) */
   static ci_cfg_s const a_cfg[] =
      {
         {"ver", help_ver, cde_ver_cb},
      };

   /* initialisation  */
   err = ci_init (&ci, a_cfg, NELEM(a_cfg));
   PERR(err);

   err = ci_install_out (&ci, ci_out_cb, NULL);
   PERR(err);

   /* boucle applicative */
   {
      int end = 0;
      do
      {
         printf ("> ");
         fflush (stdout);

         {
            char *line = get_line();
            if (line != NULL)
            {
               err = ci_in(&ci, line, NULL);
               PERR(err);
               end = err == CI_ERR_QUIT;
            }
            free (line), line = NULL;
         }
      }
      while (!end)
         ;
   }
   return 0;
}

Nous pouvons constater que la commande est maintenant prise en compte dans la liste

> help
command list
 help      quit      ver
>

Nous pouvons aussi demander une aide spécifique :

> help ver

USAGE ver [lib]
Donne la version de l'application
lib : liste aussi les versions des bibliotheques
>

Voici le résultat de quelques essais de commande 'ver' et de paramètres'

> ver
argc = 1
argv[0] = 'ver'
> ver xx
argc = 2
argv[0] = 'ver'
argv[1] = 'xx'
> ver xx yy
argc = 3
argv[0] = 'ver'
argv[1] = 'xx'
argv[2] = 'yy'
>

D'autre part, maintenant qu'un fichier de configuration est installé, le module réagit de façon plus explicite à certaines erreurs :

> xxx
ERR : unknown command(8) at ci_04.c:95
>
ERR : no command(6) at ci_04.c:95
>

Nous allons maintenant modifier la fonction de traitement pour qu'elle fasse exactement ce qu'on attend d'elle :

/* ci_05.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include "ed/inc/sys.h" /* NELEM */

#include <stdio.h>
#include <string.h>

/* version de l'application */
#define VER "0.5"

/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
   if (err != CI_OK)\
   {\
      fprintf (stderr,\
              "ERR : %s(%d) at %s:%d\n"\
              , ci_serr(err)\
              , err\
              , __FILE__\
              , __LINE__\
              );\
   }\
}\
while (0)

/* definition du mode d'emploi de la commande 'ver'.
   La premiere ligne affiche automatiquement le nom de la commande.
   On peut ajouter les parametres et une ou des lignes
   ligne supplementaires pour illustrer...
*/
static char const help_ver[] =
   "[lib]\n"
   "Donne la version de l'application\n"
   "lib : liste aussi les versions des bibliotheques\n"
   ;

/* definition d'une fonction de traitement de commande de type ci_cde_f
   pour traiter la commande 'ver'
*/
static int cde_ver_cb (int argc, char const **argv, void *p_user)
{
   int ret = 0;

   /* afficher la version de l'application  */
   printf ("VER = %s\n", VER);

   /* si il y a un parametre */
   if (argc > 1)
   {
      /* et que ce parametre est 'lib' */
      if (strcmp (argv[1], "lib") == 0)
      {
         printf (" bibliotheques :\n");
         printf (" %-20s%s\n", ci_sid(), ci_sver());
         printf (" %-20s%s\n", io_sid(), io_sver());
      }
   }

   return ret;
}

/* definition d'une fonction de sortie de type ci_out_f */
static int ci_out_cb (void *p_user, char const *s)
{
   int ret = 0;
   printf ("%s", s);
   fflush (stdout);
   return ret;
}

int main (void)
{
   ci_s ci;
   ci_err_e err;

   /* tableau de structure de configuration (pour le moment, un seule commande) */
   static ci_cfg_s const a_cfg[] =
      {
         {"ver", help_ver, cde_ver_cb},
      };

   /* initialisation  */
   err = ci_init (&ci, a_cfg, NELEM(a_cfg));
   PERR(err);

   err = ci_install_out (&ci, ci_out_cb, NULL);
   PERR(err);

   /* boucle applicative */
   {
      int end = 0;
      do
      {
         printf ("> ");
         fflush (stdout);

         {
            char *line = get_line();
            if (line != NULL)
            {
               err = ci_in(&ci, line, NULL);
               PERR(err);
               end = err == CI_ERR_QUIT;
            }
            free (line), line = NULL;
         }
      }
      while (!end)
         ;
   }
   return 0;
}

Ce qui donne :

> ver
VER = 0.5
>

et :

> ver lib
VER = 0.5
 bibliotheques :
 CI                  1.4
 IO (c) ED 2003-2005 1.2
>

Nota : la cas du paramètre inconnu n'est pas signalé. Une modification triviale est faisable. Il suffit d'ajouter le traitement adéquant dans la bonne branche. On en profite pour modifier le compte rendu en 1 pour signifier 'erreur de parametre'.

/* ci_06.c */
#include "ed/inc/ci.h"
#include "ed/inc/io.h"
#include "ed/inc/sysalloc.h" /* free */
#include "ed/inc/sys.h" /* NELEM */

#include <stdio.h>
#include <string.h>

/* version de l'application */
#define VER "0.6"

/* macro permettant l'affichage des erreurs */
#define PERR(e)\
do\
{\
   if (err != CI_OK)\
   {\
      fprintf (stderr,\
              "ERR : %s(%d) at %s:%d\n"\
              , ci_serr(err)\
              , err\
              , __FILE__\
              , __LINE__\
              );\
   }\
}\
while (0)

/* definition du mode d'emploi de la commande 'ver'.
   La premiere ligne affiche automatiquement le nom de la commande.
   On peut ajouter les parametres et une ou des lignes
   ligne supplementaires pour illustrer...
*/
static char const help_ver[] =
   "[lib]\n"
   "Donne la version de l'application\n"
   "lib : liste aussi les versions des bibliotheques\n"
   ;

/* definition d'une fonction de traitement de commande de type ci_cde_f
   pour traiter la commande 'ver'
*/
static int cde_ver_cb (int argc, char const **argv, void *p_user)
{
   int ret = 0;

   /* afficher la version de l'application  */
   printf ("VER = %s\n", VER);

   /* si il y a un parametre */
   if (argc > 1)
   {
      /* et que ce parametre est 'lib' */
      if (strcmp (argv[1], "lib") == 0)
      {
         printf (" bibliotheques :\n");
         printf (" %-20s%s\n", ci_sid(), ci_sver());
         printf (" %-20s%s\n", io_sid(), io_sver());
      }
      else
      {
         printf ("parametre inconnu\n");
         ret = 1;
      }
   }

   return ret;
}

/* definition d'une fonction de sortie de type ci_out_f */
static int ci_out_cb (void *p_user, char const *s)
{
   int ret = 0;
   printf ("%s", s);
   fflush (stdout);
   return ret;
}

int main (void)
{
   ci_s ci;
   ci_err_e err;

   /* tableau de structure de configuration (pour le moment, un seule commande) */
   static ci_cfg_s const a_cfg[] =
      {
         {"ver", help_ver, cde_ver_cb},
      };

   /* initialisation  */
   err = ci_init (&ci, a_cfg, NELEM(a_cfg));
   PERR(err);

   err = ci_install_out (&ci, ci_out_cb, NULL);
   PERR(err);

   /* boucle applicative */
   {
      int end = 0;
      do
      {
         printf ("> ");
         fflush (stdout);

         {
            char *line = get_line();
            if (line != NULL)
            {
               err = ci_in(&ci, line, NULL);
               PERR(err);
               end = err == CI_ERR_QUIT;
            }
            free (line), line = NULL;
         }
      }
      while (!end)
         ;
   }
   return 0;
}

Ce qui donne :

> ver xxx
VER = 0.6
parametre inconnu
ERR : command parameter error(7) at ci_06.c:111
>

Valid XHTML 1.0! Valid CSS! Get Firefox!  Use OpenOffice.org Club d'entraide des développeurs francophones Code::Blocks