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

Module CI de la bibliothèque CLIB

Emmanuel Delahaye

Le module CI de la bibliothèque CLIB est un interpréteur de commande basique (Command Interpretor) ♪

- Titre : Mode d'emploi de l'interpréteur de commandes CI

- Auteur : Emmanuel Delahaye

- Parution : 18 juin 2006

- Mise à jour : 7 mars 2013

- Licence : Copyright ® 2008 Emmanuel Delahaye. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Construction de l'interpréteur de commandes CI

- I-A. Construire un environnement de développement pour la bibliothèque CLIB

- I-B. Récupérer les fichiers

- I-C. Créer la bibliothèque

- I-D. Créer une application de test

- II. Utilisation de l'interpréteur de commandes CI

- II-A. Fonctionnement

- II-B. Restrictions

- II-C. Construire une application pas à pas

- II-C-1. Le code minimum

- II-C-2. Une application réelle

I - Construction de l'interpréteur de commandes CI

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

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

I-B - 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 neuf fichiers sources servent éventuellement à générer la bibliothèque.

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

I-C - 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).

I-D - Créer une application de test

Celle-ci est composée de trois 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.

II - Utilisation de l'interpréteur de commandes CI

II-A - 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 chaîne 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 :

- du nom de la commande en minuscules ;

- d'une chaîne d'aide ;

- du nom de fonction associée.

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

Le système a deux commandes internes préétablies : « quit » et « help ».

II-B - Restrictions

Les commandes préétablies ne sont pas configurables.

II-C - Construire une application pas à pas

II-C-1 - Le code minimum

Ce code minimum montre comment on construit l'interpréteur de commande avec ses deux 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 une boucle applicative incluant :

- l'affichage d'un 'prompt' ;

- une lecture de ligne (saisie de la commande) ;

- un appel à la fonction de traitement de la commande.

/* 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'interpréteur 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 réaction, de même que la commande d'aide (help). C'est dû 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.

II-C-2 - 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 chaîne « mode d'emploi » ou « aide » qui reprend l'essentiel de la spécification de la commande…

/* définition 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.

/* définition 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 paramètres (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, intégration de ces deux éléments dans le tableau de configuration. Son adresse et son nombre d'éléments 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)

/* définition du mode d'emploi de la commande 'ver'.
La première ligne affiche automatiquement le nom de la commande.
On peut ajouter les paramètres et une ou des lignes
supplémentaires pour illustrer...
*/
static char const help_ver[] =
"[lib]\n"
"Donne la version de l'application\n"
"lib : liste aussi les versions des bibliothèques\n"
;

/* définition 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 paramètres (debug) */
printf ("argc = %d\n", argc);
{
int i;
for (i = 0; i < argc; i++)
{
printf ("argv[%d] = '%s'\n", i, argv[i]);
}
}

return ret;
}

/* définition 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 bibliothèques
>

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)

/* définition du mode d'emploi de la commande 'ver'.
La première ligne affiche automatiquement le nom de la commande.
On peut ajouter les paramètres et une ou des lignes
supplémentaires pour illustrer...
*/
static char const help_ver[] =
"[lib]\n"
"Donne la version de l'application\n"
"lib : liste aussi les versions des bibliothèques\n"
;

/* définition 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 paramètre */
if (argc > 1)
{
/* et que ce paramètre est 'lib' */
if (strcmp (argv[1], "lib") == 0)
{
printf (" bibliothèques :\n");
printf (" %-20s%s\n", ci_sid(), ci_sver());
printf (" %-20s%s\n", io_sid(), io_sver());
}
}

return ret;
}

/* définition 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, une 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
>

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

/* 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)

/* définition du mode d'emploi de la commande 'ver'.
La première ligne affiche automatiquement le nom de la commande.
On peut ajouter les paramètres et une ou des lignes
supplémentaires pour illustrer...
*/
static char const help_ver[] =
"[lib]\n"
"Donne la version de l'application\n"
"lib : liste aussi les versions des bibliothèques\n"
;

/* définition 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 paramètre */
if (argc > 1)
{
/* et que ce paramètre est 'lib' */
if (strcmp (argv[1], "lib") == 0)
{
printf (" bibliothèques :\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;
}

/* définition 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
>