NoteTank V1.4.0 - Charme et efficacité du mode console

Auteur: Joseph Colineau
Date: 27 janvier 2004

Contents
1. Introduction
        1.1. Origine
        1.2. Que fait le logiciel ?
2. Structure du logiciel
        2.1. Boucle principale
        2.2. Règles
        2.3. Fonctions
        2.4. gestion du fichier de données
        2.5. gestion des fichiers de données
        2.6. fonctions de recherche
        2.7. accès à l'interpréteur Rebol
        2.8. aide
        2.9. Personnalisation
        2.10. Divers
3. Evolutions
4. Conclusion

1. Introduction

En avez-vous eu assez de ces multiples clics qui sont nécessaires pour atteindre une information en environnement bureautique. Le règne du tout graphique ralentit sérieusement l'accès à l'information: annuaire téléphonique en ligne, prise de notes facile, mais nécessitant de lancer une appli, information disponible sur la base de données intranet, mais passage par tous les clicks de lancement du browser. Sans compter toutes les tâches spécifiques et différentes pour chacun, liées à son activité professionnelle quotidienne. Rebol peut vous simplifier la vie !

1.1. Origine

L'origine de ce projet sans ambition est d'avoir sous la main un moyen simple et rapide de remplacement de ces multiples post-it qui ornent mon bureau. Avoir sur mon écran une fenêtre ouverte en permanence pour écrire en vrac ce qui me passe par la tête, et le retrouver facilement. D'où le nom du logiciel : "NoteTank" ou réservoir à notes. Je me suis vite aperçu qu'avec Rebol il était simple de faire plus, et de personnaliser facilement, comme vous le verrez plus loin. La difficulté, maintenant, est de ne pas multiplier à l'infini les fonctions, afin de conserver un outil simple et efficace.

1.2. Que fait le logiciel ?

Le tout en mode console, gràce à l'utilisation d'un petit nombre de commandes simples (et à personnaliser). Par exemple:

- le texte de ma note : prise d'une note dans le fichier de stockage.

? tex : recherche et affichage de toutes les notes qui comportent les caractères "tex".

tel dur : recherche dans mon annuaire téléphonique de toutes les personnes dont le nom comporte les caractères "dur".

R do anamonitor.r : exécution de l'expression Rebol "do anamonitor.r".

> word : traduction d'un mot.

aide : affichage des commandes disponibles.

200 * 300 : expression Rebol

script1 : lancement du script script1.r


2. Structure du logiciel

2.1. Boucle principale

Après un chargement du fichier de la "base de données", le programme se met en attente d'entrées, qu'il traite à chaque retour chariot

if not exists? to-file rep-files [make-dir to-file rep-files]
dbase: load-db fichier-dbase
forever [
    in-str: ask ">> "
    if error? try [input-parse in-str] [print "?"]
]
La fonction d'interprétation des entrées est input-parse. Elle est basée sur le "parsing" de la phrase soumise à l'entrée par l'utilisateur. On va successivement analyser cette phrase au regard des règles que l'on a spécifiées. Deux cas sont prévus:

if parse blk [["bye"|"quit"|"exit"] to end][print "bye ..."  save-db dbase fichier-dbase ]
if parse blk [["noter"|"note"|"-"] copy aa to end][noter aa]
voici le code de la fonction de parsing. Le test de sortie du fichier est effectué d'abord. Ensuite, on passe en revue dans une boucle l'ensemble des règles. Comme nous le verrons plus loin, les règles sount définies dans un bloc de blocs. Chaque règle est constituée d'un bloc de trois blocs: le second (regle/2) est la règle de parsing (par exemple [["noter"|"note"|"-"] copy aa to end]), et le troisième (regle/3) le code à exécuter si la règle est vérifiée ([noter aa exit])

input-parse: func [str /local blk lstr aa] [
    blk: copy str
    if parse blk [["bye" | "quit" | "exit"] to end] [print "bye ..." save-db dbase fichier-dbase break]
    foreach regle regles [
        if parse blk regle/2 regle/3]
    print "?"
    i-rec: 0
]
En fait, on teste l'entrée en cherchant successivement

input-parse: func [str /local blk lstr aa] [
    blk: copy str
    ; test fin programme
    if parse blk [["bye" | "quit" | "exit"] to end] [print "bye ..." save-db dbase fichier-dbase break]
    ; test règles NoteTank
    foreach regle regles [
        if parse blk regle/2 regle/3]
    ; test scripts
    if error? try [do to-file rejoin [rep-scripts blk ".r"] print "ok" exit][]
    ; test expressions Rebol ... à peaufiner    
    if error? try [ 
        print do-clean blk 
        exit][]
    ; commande non comprise 
    print "?"
    i-rec: 0
]

2.2. Règles

Les règles sont rassemblées dans un bloc unique, et ont toutes la même structure:

[ ["fonction réalisée par la règle   "]  [ règle de parsing ]  [ code a exécuter si la règle est vérifiée ] ]
L' intérèt de cette structure est qu'elle est extensible autant qu'on veut. De plus, elle est autodocumentée, le bloc 1 définissant l'usage de la règle. La fonction d'aide se contente de lister le bloc de règles.

Voici la liste des règles introduites dans la version actuelle du logiciel. Libre à vous de la compléter ou de la modifier:

regles: [
    [["noter dans le fichier     "] [["noter" | "note" | "-"] copy aa to end] [noter aa exit]]
    [["sauver et quitter        "] [["bye" | "quit" | "exit"] to end] [print "bye ..." save-db dbase fichier-dbase exit]]
    [["changer de fichier       "] [["cf" | "changer"] copy aa to end] [save-db dbase fichier-dbase dbase: change-db aa exit]]
    [["lister les fichiers      "] [["???" | "fichiers"] to end] [print read to-file rep-files exit]]
    [["fichier et dernier enreg "] [["??" | "fichier"] to end] [print [fichier-dbase "  " last dbase] exit]]
    [["chercher                 "] [["?" | "chercher" | "trouver"] copy aa to end] [chercher aa exit]]
    [["chercher un n° de tél      "] [["tel" | "téléphone"] copy aa to end] [chercher-tel aa fichier-tel exit]]
    [["chercher une adresse      "] [["adr" | "adresse"] copy aa to end] [chercher-adresse aa fichier-adr exit]]
    [["traduire                     "] [[">" | "traduire"] copy aa to end] [traduire aa fichier-trad exit]]
    [["afficher la date     "] [["jour" | "date"] to end] [print now/date exit]]
    [["afficher l'heure         "] [["heure"] to end] [print now/time exit]]
    [["expression Rebol       "] [["=" | "R "] copy aa to end] [if error? try [do reform aa] [print "expression mal formulée"] exit]]
    [["imprimer l'aide          "] [["aide" | "help"] to end] [lister-regles exit]] ;[print aide-txt exit]]
    [["lister le fichier        "] ["lister" copy aa to end] [print-db dbase exit]]
    [["effacer un enregistr "] ["effacer" copy aa to end] [effacer i-rec exit]]
    [["restaurer un effacement"] ["restaurer"] [dbase: copy dbase-old exit]]
    [["version du logiciel      "] ["version"] [print ["NoteTank, version" system/script/header/version] exit]]
    [["backup du fichier de notes  "] [["backup"] to end] [fdb: copy fichier-dbase change find fdb ".txt" ".bak" save-db dbase fdb exit]]
]

2.3. Fonctions

Elles sont très simples et ne nécessitent pas de commentaires. Si vous voulez utiliser les fonctions de recherche dans un fichier d'adresse, ou de téléphone, vous devrez probablement réécrire les fonctions correspondantes, qui dépendent évidemment de la structure de vos fichiers. Pour ma part, j'utilise des fichiers au format csv (fichiers textes avec séparateur point-virgule, très faciles à parser). Beaucoup de programmes proposent ce format d'exportation des données. La fonction de recherche sur Internet (google) n'est bien sûr utilisable que si vous être connectés, et que votre rebol est bien configuré.

2.4. gestion du fichier de données

l'ensemble des enregistrements est stocké dans le bloc dbase les fonction d'accès au fichier de données sont: noter,chercher, effacer, restaurer:

noter: func [str /local record] [
    record: copy []
    append/only record now
    append/only record trim str
    append/only dbase record
    save-db dbase fichier-dbase
    i-rec: length? dbase]
chercher: func [str /local ir] [
    dbase: head dbase
    i-rec: 0
    ir: 0
    foreach record dbase [
        ir: ir + 1
        if found? find (to-string record/2) (trim str) [i-rec: ir print record/2]
    ]
]
effacer: func [indice] [
    if indice > 0 [
        print rejoin ["effacement " dbase/:indice]
        dbase-old: copy dbase
        dbase: at dbase indice
        remove dbase
        dbase: head dbase
        save-db dbase fichier-dbase
        i-rec: 0
    ]
]

2.5. gestion des fichiers de données

Par défaut, les données sont enregistrées dans le fichier notes.txt. Il est possible de définir d'autres fichiers. Il suffit d'utiliser la commande cf nom-fichier. Si nom-fichier existe, il sera ouvert, et toutes les opérations d'accès seront faites sur ce nouveau fichier. Sinon, il sera créé. La liste des fichiers de données déjà créés est donnée par ???. Le nom du fichier actuellement utilisé, ainsi que le dernier enregistrement de ce fichier son donnés par ??. Les fichiers de données sont rassemblés dans le sous-répertoire /files.

2.6. fonctions de recherche

Des fonctions de recherche dans des fichiers de contacts sont faciles à réaliser. En voici un exemple, adaptable à vos propres fichiers, en fonction de leur structure. On notera que ces fonctions impriment tous les strings comportant la séquence alphanumérique présentée comme requête. Il est donc possible de taper une partie seulement du mot recherché.

chercher-tel: func [str doc] [
{fichier de type texte}
    base: read/lines to-file doc
    foreach record base [
        if found? find (to-string record) (trim str) [rec: parse record none print [rec/1 rec/2 "  " rec/3 rec/4 rec/5]]
    ]
]
chercher-adresse: func [str doc] [
    {pour fichier au format csv 
    format fichier: Numéro;Nom;Prénom;Adresse1;CodePostal;Ville;Adresse2;CodePostal2;Ville2;
    TéléphoneDomicile;TéléphoneBureau;Notes}
    base: read/lines to-file doc
    base: next base
    foreach record base [
        if found? find (to-string record) (trim str) [rec: parse/all record ";" print [rec/1 rec/2 "  " rec/3 rec/4 rec/5 rec/6 rec/7 rec/8 rec/9 rec/10 rec/11 rec/12]]
    ]
]
De même, il est simple d'intégrer un "traducteur". Il suffit de disposer (on en trouve sur internet) d'un fichier texte comportant sur chaque ligne le mot dans la langue d'origine, et le même mot traduit.

traduire: func [str doc] [
base: read/lines to-file doc
foreach record base [
    if found? find (to-string record) (trim str) [print record]
]
]
La recherche est plus efficace si l'on s'affranchit des accents dans les comparaisons de strings.

sans-accent: func [str /local str1][
    str1: copy str
    substitutions:  ["é" "e" "è" "e" "à" "a" "ê" "e" "ë" "e" "ç" "c" "ô" "o" "ù" "u"]
    foreach [search value] substitutions [
            replace/all str1 search value
        ]
        str1
]
La fonction de recherche devient:

chercher: func [str /local ir] [
    dbase: head dbase
    i-rec: 0
    ir: 0 
    foreach record dbase [
        ir: ir + 1
        if found? find (sans-accent to-string record/2) (sans-accent trim str) [i-rec: ir print record/2]
    ]
]

2.7. accès à l'interpréteur Rebol

Il est utile aussi d'avoir accès à l'interpréteur Rebol, ne serait-ce que pour faire un calcul simple, ou lancer un script Rebol. C'est très simple à faire, il n'est même pas nécessaire d'appeler une fonction spécifique, "do" suffit. Voici la règle introduite

[["expression Rebol     "] [["=" | "R "] copy aa to end] [if error? try [do reform aa] [print "expression mal formulée"] exit]]
On l'utilise de cette manière:

= print 1 + 2

ou encore

R call "notepad", ou

R browse my-url

Dans cette version du logiciel, il est aussi possible de taper directement l'expression comme on le ferait dans une console Rebol. Cependant, la gestion d'erreur n'est que partiellement traitée, et si l'expression n'est pas comprise, aucun message d'erreur ne s'affiche.

10 * 6.55

call "notepad"

browse "my-url"

2.8. aide

Enfin, une fonction d'aide liste l'ensemble des règles déclarées

lister-regles: func [] [
    print "----------------"
    print "commandes                       mots-clés"
    print ""
    foreach regle regles [
        print [regle/1 tab parse form regle/2/1 "|"]
    ]
    print "----------------"
]

2.9. Personnalisation

Comme nous l'avons vu il est très facile d'introduire ses propres règles, en respectant la structure définie.

Il est également possible de modifier les règles actuelles, par exemple pour introduire de nouveaux mots de commande, ou changer leurs synonymes.

On fera attention à la manière d'introduire les mots de commande. Par exemple, si l'on introduit "R" , la règle sera exécutée dès que l'on introduit un mot de commande commençant par R. Si l'on veut n'exécuter la règle R que lorsque l'on a introduit le mot R seul, penser à introduire un espace après R: "R ".

Afin d'ajouter facilement ses propres règles sans avoir à modifier le programme principal, il suffit de créer un fichier à en-tête Rebol[], comportant un bloc de règles nommé regles1, et éventuellement des fonctions utilisées par ces commandes supplémentaires. Le programme teste la présence de ce fichier, et ajoute les règles personnalisées aux règles prédéfinies:

fichier-regles: %mes-regles.r
if exists? fichier-regles [do fichier-regles append regles regles1]
Par ailleurs, on peut créer des fonctions complexes, à l'aide de scripts, déposés dans le répertoire "scripts/", que l'on appellera simplement en tapant le nom du script (sans le suffixe ".r")

2.10. Divers

On n'a pas intégré ici de fonctions de messagerie électronique, mais celles-ci seraient simples à créer, avec, par exemple appel à un fichier d'adresses électroniques.

On pourra ici remarquer que, pour quelqu'un qui maîtrise Rebol, il est simple d'écrire directement la plupart des commandes présentées ci-dessus. On pourrait même les intégrer au fichier user.r, afin de les avoir sous la main dans la console Rebol. Pourquoi pas ? Cependant, rappelons que la motivation première de ce travail était d'avoir la possibilité de prendre en vrac des notes, et de les retrouver simplement. Un utilitaire spécifique semble un bon choix pour celà.


3. Evolutions

Le logiciel présenté ici reste volontairement simple, afin de pouvoir être adapté et remanié en fonction des contraintes de chacun, afin de conserver également son caractère pédagogique. C'est pourquoi, en particulier, on ne l'a pas surchargé par de la gestion d'erreur. On pourra facilement l'enrichir avec ses propres fichiers de données: dictionnaires, listes de traduction, répertoires d'adresses, de numéros de téléphone, de format plus ou moins exotique...

Une autre approche pourrait être la création d'un exécutable autonome (ce qui pourrait constituer un moyen de promouvoir Rebol discrètement...). Dans ce cas, il est bon de prévoir d'enregistrer la liste des règles dans un fichier séparé, ce qui permet une personnalisation, ou des adaptations, sans remettre en cause l'exécutable.

On pourrait imaginer une gestion dynamique des règles (possibilité de modification en cours d'exécution), ce qui n'est pas compliqué, mais il faudrait prévoir d'intégrer également des fonctions spécifiques associées avec de nouvelles règles, par exemple pour s'adapter à des formats de fichiers de données particuliers.


4. Conclusion

Rebol se prête bien au développement et à la personnalisation de ce type d'utilitaire. Les fonctions de parsing sont un moyen puissant pour créer son langage de commande. Il faudra cependant veiller à ne pas le surcharger par un nombre trop grand de mots de commande, pour lui conserver simplicité et efficacité.