jeudi 29 mars 2012

Debug file en Clipper

Introduction
Pouvoir disposer d'un outil de debugging minimal peut revêtir une importance capitale.
C'est encore plus vrai lorsque l'on maintient un vieux logiciel en Clipper (re-compilé avec Harbour + Visual Studio C++ 2008).

Comme je compile le soft en ligne de commande, je n'ai pas cette chance de pouvoir disposer d'un déboggeur ARFF!
Je voulais donc utiliser d'un fichier de déboggage et d'une fonction DMSG (Debug Message) me permettant de laisser des traces dans ce fichier de trace. Fichier bien utile car il permet de capturer des informations sur le fonctionnement du programme.

Clipper à prévu le coup avec les commandes "SET ALTERNATE TO" permettant de rediriger la sortie de la console vers un fichier. Idéal pour laisser une trace :-)
Malheureusement pour moi, cette fonctionnalité est déjà utilisé à d'autres fins... a moi donc d'utiliser un autre procédé pour y arriver.

FT_FUSE - manipulation de fichier
Il existe heureusement les fonction FT_FUSE et consort!
Voici l'implémentation de la procédure DMSG, une fonction qui écrit des message dans un fichier "debug.log".
Cette fonction utilise également la variable globale dbgLineCount pour incrémenter le numéro de ligne.
#include "fileio.ch" 

...

&& DMSG - Debugging Message 
&&  line counter for DMSG procedure
PUBLIC dbgLineCount 
dbgLineCount = 0   

...

*****************
PROCEDURE DMSG
*****************
*  Write a debug.log file by inserting records on the top of the file
*  If the file is not present, nothing is written into it
*     see FT_USE   @ http://www.ousob.com/ng/nanfor/ng2319d.php
*     see FILE I/O @ http://www.itk.ru/clip-doc.en/categfileio.html
*     beautiful sample @ http://www.groupsrv.com/computers/about324430.html   
*     
*  27/03/2012 - WARNING function is buggy and may turns into infinite loop !
*  29/03/2012 - Fixed :-)
  parameters sMsg   

  private hFile
  private sToWrite

  dbgLineCount = dbgLineCount + 1 && Line counter
    
  dbgFilename = "c:\stock\debug.log"
    
  ft_fselect(1) 
  hFile := fopen(dbgFilename, FO_READWRITE + FO_SHARED)
  hFile := ft_fuse(dbgFilename)      
  if hFile < 0
    return
  endif

  * Data to write in the Debug File (place CR/LF at the end of file)  
  sToWrite = Str(dbgLineCount) + " " + TIME() + " " + sMsg + chr(13) + chr(10)    
  
  * Write a buffer
  fwrite(hFile,sToWrite)   

  * close file
  FT_FUSE()
  
return

L'appel se résume à:

DO DMSG WITH "L477:replace FAMI with TheResult"
Note: le "DO" n'est normalement pas nécessaire, il passe les arguments par référence.

Et le contenu du fichier debug ressemble à ceci:
         3 11:35:05 ------------------- STARTING STOCK ------------------------
         4 11:35:12 0000, main menu exit, SET PRINT OFF
         5 11:35:15 SAYA050 : Say Article - ENTER
         6 11:35:15 before call xaFami(Fami) - validation of fami
         7 11:35:15 L477:replace FAMI with TheResult
         8 11:35:15 after call xaFami(Fami)
         9 11:35:15 SAYA050 : Say Article - LEAVE
        10 11:35:18 BEFORE SUIVANT - will exec skip

En savoir plus sur la manipulation de fichier en Clipper
Vous trouverez des informations sur les opérations fichier dans les références suivantes
J'ai aussi déniché l'excellent article "Comparing input to database fields" posté par Mack Barss.
Je me permet de reprendre le contenu ci-dessous (car il est trop précieux pour prendre le risque qu'il soit perdu!)
//*
//* privsmnu.prg source code
//*
#include "fileio.ch"

//* function load temporary privilege dbf
Function Prv_qProcess
local iEmpnum, iCcde, iUsrid, iSysid
local iPrecust, iSecpack, iUsrpriv

local prvEmpnum, prvCcde, prvUsrid, prvSysid
local prvPrecust, privSecpack, prvUsrpriv

local cntmsg, qUsr, dUsr, mycnt := 0
local mrt, mrb, mcl, mcr, msg_scrn
local rFilename, tmpFilename


tmpFileName := popupdir('C:\IAREVAL\INPUT\*.txt','D','Select Compare file',;
"w+/b,w+/r",.t.)


rFilename := "C:\IAREVAL\INPUT\" + tmpFilename
gFilename := "C:\IAREVAL\REPORTS\GAPFILE.TXT"
mFilename := "C:\IAREVAL\REPORTS\INMASTER.TXT"


if varlen(rFileName) < 20
return
end


ft_fselect(1)
rfile := ft_fuse(rFilename)

ft_fselect(2)
gFile := fcreate(gFilename,FO_READWRITE)
gFile := fopen(gFilename, FO_READWRITE + FO_SHARED)
gFile := ft_fuse(gFilename)

ft_fselect(3)
mFile := fcreate(mFilename,FO_READWRITE)
mFile := fopen(mFilename, FO_READWRITE + FO_SHARED)
mFile := ft_fuse(mFilename)

qUsr := ft_dispmsg( { { "Press [Y] to continue with", ;
"process. Press any other ", ;
"key to abort the process. " }, ;
{ "r/w", "r/w", "r/w" } } , ;
"Y" )

if qUsr = .t.

use PRIVQDBF index PRIVQDBF shared new

plswait(.t.,"Processing compare...")

ft_fselect(1)
rFile := ft_fuse(rFilename)
ft_fgotop()

do while ! ft_feof()

* read line
rline = ft_freadln(rFile)
mycnt = mycnt + 1

iEmpnum := substr(rline, 1, 6)
iCcde := substr(rline, 18, 3)
iUsrid := substr(rline, 28, 20)
iSysid := substr(rline, 49, 1Cool *** probablement iSysid := substr(rline, 49, 18)
iPrecust := substr(rline, 68, 20)
iSecpack := substr(rline, 89, 16)
iUsrpriv := substr(rline,106, 45)
iRevFile := substr(rline,152, 44)

select PRIVQDBF
dbgotop()

do while !eof()
BEGIN SEQUENCE
if iEmpnum = Empnum .and. iCcde = Ccde .and. iUsrid = Usrid ;
.and. iSysid = Sysid .and. iSecPack = Secpack .and. iUsrpriv =
Usrpriv

//* write dbf record to txt file *//
tmplne := empnum + ccde + usrid + sysid + precust + secpack +
usrpriv + revfile
fwrite(mfile,tmplne)
BREAK
else
forget()
skip
endif
End
end

//* record not found - write to gap file *//
tmplne := iEmpnum + iCcde + iUsrid + iSysid + iPrecust + iSecpack +
iUsrpriv + iRevfile
fwrite(gfile,tmplne)

tmplne := ""
ft_fselect(1)
ft_fskip()
enddo

close all
plswait(.f.)
msg_scrn = savescreen(0,0,Maxrow(),79)
msgcolor = "n/w,n/w,,,n/w"
setcolor(msgcolor)

mrt := maxrow()/2-2
mrb := maxrow()/2+1
mcl := maxcol()/2-9
mcr := maxcol()/2+9
clearbox(mrt,mcl,mrb,mcr,'Single')
@mrt+1,mcl+1 say " # Records read:"
@mrt+2,mcl+3 say " ==> " + ltrim(str(mycnt))

inkey(20)
setcolor(defColor)
restscreen(0,0,Maxrow(),79,msg_scrn)
endif
return nil

*: Eof: privsmnu.prg 

jeudi 22 mars 2012

Guide de référence du langage Clipper

Si un jour vous avez besoin de référence sur la programmation clipper, allez donc jeter un œil ici.

Simple et efficace.

BeagleBone, une plateforme de prototypage Linux.

28/03/2012: Le produit est disponible à la vente sur MCHobby.be :-)
22/03/2012: Article initialement paru sur Arduino Notepad, je pense qu'il intéresserait aussi les développeurs. Apparition prochaine de ce produit sur MCHobby.
 
Pour ceux qui s'intéressent à la programmation des micro-contrôleurs et un peu au monde Unix, je viens de faire la découverte de BeagleBoard.

Carte BeagleBone


C'est une carte de prototypage à mis chemin entre la plateforme Arduino et un PC Linux.
C'est une métaphore un peu radicale mais c'est exactement ce à quoi cela ressemble.
BeagleBone se présente comme une plateforme totalement indépendante... même pas besoin d'installer un environnement de développement et de la documentation, tout se trouve déjà sur la carte BeagleBone (qui dispose quand même de 2Go).

J'ai eu l'occasion de visionner la vidéo de Matt Richardson à propos de BeagleBone et je dois bien admettre être tombé sous le charme.

Cette petite vidéo montre comment mettre le pied à l'étrier avec BeagleBone, comment commencer à adresser les sorties (avec une LED) et même comment utiliser Python comme langage de programmation!

BeagleBone est une plateforme très excitante pour MCHobby.
MCHobby fonctionne entièrement sous Linux et forcement, l'approche de BeagleBone retient notre attention.
BeagleBone dispose d'un accès Internet (prise Ethernet) et sait faire un wget!

Python est aussi un langage que j'ai personnellement pratiqué par le passé (voir les articles sur Developer Notepad) et forcement c'est encore un point de plus en faveur de BeagleBone.

Il n'est pas nécessaire de connaître Python pour jouer avec BeagleBone.  Vous pourrez trouvez toute l'information nécessaire sur le Net (la communauté BeagleBone est aussi active que celle d'Arduino).

Ressources
Pour plus d'information, je vous recommande les liens suivants:
Je vais en commander un ce Week-End pour jeter un œil dessus 

mercredi 21 mars 2012

Gestionnaire de Version sous Windows

Aujourd'hui, je cherchais un gestionnaire de version sous Windows, libre de préférence.
Je n'ai pas de grands besoins, juste celui de pouvoir revenir en arrière sur un développement en Clipper sous Win7 64 bit.

Je n'ai donc pas besoin d'un canon pour tuer une mouche.


Bien malheureusement, il m'a fallut presque une demi-journée pour trouver un produit approprié à mes besoins tout en restant simple d'emploi (et mise en œuvre facile).

C'est qu'en fin de compte, entre produit inutilisable, usine à gaz et soft devenu payant, il ne reste plus grand chose de praticable (facile à prendre en mains et prêt à l'emploi en quelques minutes).

J'ai fini par trouver mon bonheur dans le couple Tortoise + SubVersion (pour Windows).
Avec un excellent manuel de mise en place disponible sur Developpez.Com (merci Eric Reboisson!)

Besoin d'une solution de gestion de version pour vos codes sources, commencez par cette option.

mardi 20 mars 2012

Retourner plusieurs valeurs depuis une fonction - retourner un @HashTable

Introduction
Ce n'est pas la première fois que j'essaye de retourner un HashTable ou un Array dans une fonction.
Et à chaque fois, je n'arrive pas à retrouver le contenu et les méthodes de l'objet.

Je dois avouer être resté circonspect face à ce problème en me demandant pourquoi PowerShell modifiait mon typage au point de ne pas pouvoir récupérer un HashTable via une fonction.

La réponse réside dans le fait qu'une fonction sait retourner plusieurs résultats!
Et que ces résultats peuvent être compilés dans un Array!

Retourner plusieurs résultats dans une fonction
Voici un petit bout de code issu du blog de Matrin Zugec.
La fonction retourne simplement plusieurs valeurs.
Function Get-MultiValue () {
  [hashtable]$Return = @{}
  $Return.Success = $True
  $Return.PercentComplete = 100
  $Return.ReturnTime = $(Get-Date)
  $Return.Username = "Martin Zugec"
  Return $Return
}

L'appel et la réutilisation des valeurs se font comme suit:
$Var = Get-MultiValue
If ($Var.Success) {
  $Var.UserName
  $Var.ReturnTime
}

Super comme truc!

Retourner plusieurs résultats, un comportement par défaut des fonctions
C'est en parcourant ce code et en essayant de le comprendre (en relation avec cet article de Stack-Overflow) que je comprends que le comportement par défaut d'une fonction est de retourner plusieurs valeurs.
Ainsi donc, lorsque qu'une commande affiche quelque-chose à l'écran (par exemple Write-Output) ou que la fonction évalue un objet (par son appel direct, ce qui provoque généralement son affichage), ces derniers éléments sont considérés comme des résultats de la fonction. Ces résultats accompagnerons aussi la valeur retournée avec la commande return.

Ca a l'air compliqué comme ça mais si vous rencontrez des problèmes, pensez simplement à tester la longueur de la variable et a éventuellement itérer le table qu'elle pourrait contenir contient.

En exemple pratique, voici la fonction loadShrWsArticles qui charge le contenu d'un fichier de donnée dans un HashTable.
Cette fonction est censée retourner le HashTable. La HashTable stocke une liste d'objets créés à l'aide de la  fonction CreateShrWsArticleDic (cette dernière crée des dictionnaire d'objet).

L'intérêt réside surtout dans l'utilisation de la fonction (décrite plus bas)

function CreateShrWsArticleDic{
 return @{ "artCode" = ""; "artEan" = ""; "artLabel" = ""; "CBStatus"="" } 
    # Source = File WSys/OutData/WSys-Articles-shrink.dat 
    #    26070-10      -> artCode
    #    8712734085995 -> artEan
    #    4 POLES MALES -> artLabel
    #    0             -> CB Status code
    #    OK            -> CB Status (as text)
    
}

# ------------------------------
#   Load the WSys-Articles-shrink.dat
# ------------------------------
# Returns a dictionnary of ShrWsArticles objects
#
function loadShrWsArticles {
    $sFilename = "C:\Dev\GGTools\WSys\OutData\WSys-Articles-shrink.dat"
    write-progress -activity $("Loading " + (split-path $sfilename -Leaf) ) -status "Openning file" -percentcomplete 0
    Write-Output $("Loading "+$sfilename)
    $hashArticles = @{} # HashTable to store the objects
    $f = Get-Content -Encoding String $sFilename
    # $f[0] -> SourceFile : xxx
    # $f[1] -> CreationDate : yyyyMMdd hh-mm-ss
    $iMax = $f.Length
    $iPos = 2
    while( $iPos -lt $iMax ) {
        # Write Status
     if ( ($iPos % 25) -eq 0 ) {
            write-progress -activity $("Loading " + (split-path $sfilename -Leaf) ) -status ("Progression :"+$iPos+"/"+$iMax ) -percentcomplete $([Math]::Round($iPos/$iMax*100))
     }        
     $a = CreateShrWsArticleDic
     $a.artCode = $f[$iPos]
     $a.artEan = $f[$iPos+1] 
     $a.artLabel = $f[$iPos+2]
        $a.CBStatus = $f[$iPos+3]
        # $a.CBStatusText = $f[$iPos+4]
        
     # Next record 
     $iPos = $iPos + 5
     
     # Add to Hash Table (used simplified key)
        [string]$key = $a.artCode 
        $key = $key.Replace( " ", "" ).Replace( ",", "." ).ToUpper() 
        
     if( $hashArticles.ContainsKey( $key ) ){
      Write-Host "  loadShrWsArticles - hashArticles " $a.artCode " already registered"      
     }
     else {
      $hashArticles.Add( $key, $a )
     }
    }
    write-progress -activity $("Loading " + (split-path $sfilename -Leaf) ) -status "Loaded!" -completed
    Write-Output $("   " +$hashArticles.Count +" items loaded")
    
    return $hashArticles
}

En faisant l'appel
$test = loadShrWsArticles
Je m'attendais à pouvoir retrouver un des éléments de mon dictionnaire.
La ligne $test.Item("B1061403-50").artCode devait logiquement fournir un résultat.
Et pourtant, je reçois obstinément l'erreur:
L'appel de la méthode a échoué parce que [System.Object[]] ne contient pas de méthode nommée « Item ».
Au niveau de ligne : 1 Caractère : 11
+ $test.Item <<<< ("B1061403-50").artCode
    + CategoryInfo          : InvalidOperation: (Item:String) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound

Les choses deviennent claires lorsque l'on comprends que la fonction a retourné un tableau!
Mon HashTable est un des éléments de ce tableau.
> $test.Length
retourne 3, il y a 3 éléments dans mon résultat (en vérifiant chacun d'eux, je retrouve mon HashTable)

> $test[0]
Loading C:\Dev\GGTools\WSys\OutData\WSys-Articles-shrink.dat

> $test[1]
   1916 items loaded

> $test[2]
 Name                           Value                                                                                                                                                                                   
----                           -----                                                                                                                                                                                   
BF632/10A-100                  {artCode, artLabel, artEan, CBStatus}                                                                                                                                                   
MWP6-10                        {artCode, artLabel, artEan, CBStatus}                                                                                                                                                   
1000AROUGE                     {artCode, artLabel, artEan, CBStatus}   
...
...


J'ai retrouvé mon HashTable à la deuxième position du tableau!
Notez que les instructions Write-Output exécutés dans la fonction  font également partie du résultat de la fonction.Je vais enfin pouvoir retrouver mes objets :-) YES!!!

> $monHash = $test[2]
> $monHash.Item("B1061403-50").artCode

 B1061403-50

Cette fois-ci, plus d'erreur :-)

jeudi 8 mars 2012

Imprimante HP cm1415fn et Ubuntu

Parce qu'une telle information est toujours utile à partager avec des Linuxiens.

J'ai fais l'acquisition d'une imprimante Laser Couleur HP cm1415fn pour l'utiliser sur le réseau domestique (uniquement des machines Ubuntu 10.10).
Là encore, HP fait la différence avec ses concurrent.
En 10 minutes, elle fonctionnait sur notre petit réseau maison.

Ce qui a de génial, c'est que cette imprimante dispose d'une interface réseau... (donc pas besoin de l'attacher à une machine et d'avoir impérativement ce PC en route pour faire des impressions :-)
J'ai configurer l'imprimante en IP Fixe (par exemple 192.168.1.205) avec une adresse n'interférant pas avec le DHCP du routeur Belgacom (il dépasse rarement 192.168.1.15).

L'utilisation en IP fixe est bien pratique pour l'installation de l'imprimante sur les autres PC Ubuntu.
Il suffit de connaître l'IP de l'imprimante et cela devient un vrai jeu d'enfant :-)
Utiliser une IP Fixe pour l'imprimante
simplifie son installation sous Ubuntu
 

Il faut juste savoir qu'il faut sélectionner le modèle "Color LasetJet cp1514n" (oui, 1514!) pour l'installation du pilote.

Une particularité intéressante de cette imprimante est qu'elle dispose d'une cartouche noir. Il est donc possible de faire des impressions noir et blanc sans consommer les toners couleurs. Pratique pour faire des économies :-)
Il faut juste penser à demander une impression en dégradé de gris (volet "avancé" dans la boîte de dialogue des impressions).
Pouvoir imprimer en dégrader de gris permet
d'économiser les toners couleurs
Je ne me suis pas attardé sur les fonctionnalités de scanning, certains articles laisseraient à penser que cela ne fonctionne pas (à vous de voir si ce modèle vous intéresse).