FiveTech Support Forums

FiveWin / Harbour / xBase community
Board index mod_harbour Mini Debug Toolkit (Harbour)
Posts: 6984
Joined: Fri Oct 07, 2005 07:07 PM
Mini Debug Toolkit (Harbour)
Posted: Sun Oct 26, 2025 05:39 PM
Mini Debug Toolkit (Harbour)

Purpose: Fast, robust console helpers for debugging and peeking into DBF data — no external deps.

ValToCharEx( u, lWithHeader ) → cText

Converts any value to a readable string, optionally prefixed with a header showing file, line, and proc name.

Params

u — any type (C,N,D,L,A,H,M,U,…)

lWithHeader (Logical, default .T.) — include debug header

Return
Readable string. Hashes (H) are JSON-encoded; empty {} are normalized to {=>}.

Usage

? ValToCharEx( hReq )
? ValToCharEx( SomeArray(), .F. )

Browser( nMax )

Ultra-simple DBF browser: prints up to nMax records from the current work area using ?.
Fixed column width, text/memo truncated, exceptions handled.

Param: nMax (default 20)

Tip (pause):

WAIT "Press any key..."


Example

USE kunden NEW
Browser(100)

BrowseFit( nMax, aOnlyFields )

Prettier console view with auto-fit: derives column widths from DBStruct(), auto-fits to MaxCol(), right-aligns numbers, left-aligns text, paging with Inkey(0) (ESC to quit). Header reprinted on each page.

Params

nMax (default 50)

aOnlyFields (array of field names) — optional whitelist

Features

Shrinks C/M columns first until total width fits the terminal

Page height based on MaxRow()

Examples

BrowseFit() // 50 rows, all fields
BrowseFit(200, {"ZIMMERNR","AUSSTATTUN","BETTEN","BALKON"})

Notes & Best Practices

Active work area: Both browsers read from the currently selected alias (USED() check).

Safe by default: No file I/O — ideal for live debugging in services.

Performance: Limit nMax and use aOnlyFields for large tables.

Error info: Browser() prints short exception info (Description/Operation/SubCode/OsCode).
FUNCTION ValToCharEx( u, lWithHeader )
    LOCAL cType := ValType( u )
    LOCAL cResult, cHdr := ""
    LOCAL nLine := ProcLine( 1 )
    LOCAL cFile := ProcFile( 1 )
    LOCAL cProc := ProcName( 1 )
 
    IF ValType( lWithHeader ) != "L"
       lWithHeader := .T.   // oder .F., wie du willst
    ENDIF
 
    IF lWithHeader
       // robust: falls Infos mal NIL/0 sind
       cHdr := hb_StrFormat( "[%s:%d %s] ", ;
                IIF( Empty(cFile), "?", cFile ), ;
                IIF( nLine == NIL, 0, nLine ), ;
                IIF( Empty(cProc), "?", cProc ) )
    ENDIF
 
    DO CASE
    CASE cType == "C" .OR. cType == "M"
       cResult := u
    CASE cType == "D"
       cResult := DToC( u )
    CASE cType == "L"
       cResult := If( u, ".T.", ".F." )
    CASE cType == "N"
       cResult := AllTrim( Str( u ) )
    CASE cType == "A"
       cResult := hb_ValToExp( u )
    CASE cType == "H"
       cResult := hb_jsonEncode( u, .T. )
       IF Left( cResult, 2 ) == "{}"
          cResult := StrTran( cResult, "{}", "{=>}" )
       ENDIF
    CASE cType == "U"
       cResult := "nil"
    OTHERWISE
       cResult := "type not supported yet in function ValToChar()"
    ENDCASE
 
 RETURN cHdr + cResult
 


/* Super simpler DBF-Browser: nur Ausgabe mit "?"
   Aufrufbeispiele:
      Browser()        // 20 Sätze
      Browser(100)     // 100 Sätze
*/
FUNCTION Browser( nMax )
    LOCAL nLimit := IIF( nMax == NIL .OR. nMax <= 0, 20, nMax )
    LOCAL nOld   := SELECT()
    LOCAL aStru, nFlds, nShown, n, cLine, uVal, cType
 
    IF !USED()
       ? "Browser(): Kein Workarea aktiv."
       RETURN NIL
    ENDIF
 
    aStru  := DBStruct()
    nFlds  := Len( aStru )
    nShown := 0
 
    // Kopf
    ? "Alias:", ALIAS(), "  Records:", LastRec()
 
    // Feldnamenzeile
    cLine := ""
    FOR n := 1 TO nFlds
       cLine += PadR( aStru[n,1], 12 ) + " "
    NEXT
    ? cLine
 
    BEGIN SEQUENCE
       GO TOP
       DO WHILE !Eof() .AND. nShown < nLimit
          cLine := ""
          FOR n := 1 TO nFlds
             uVal  := FieldGet( n )
             cType := aStru[n,2]   // "C", "N", "D", "L", "M", ...
             DO CASE
             CASE cType == "C"
                cLine += PadR( Left( AllTrim( IIF( uVal == NIL, "", uVal ) ), 40 ), 12 ) + " "
             CASE cType == "N"
                cLine += PadR( LTrim( Str( IIF( uVal == NIL, 0, uVal ) ) ), 12 ) + " "
             CASE cType == "D"
                cLine += PadR( IIF( Empty( uVal ), "", DToC( uVal ) ), 12 ) + " "
             CASE cType == "L"
                cLine += PadR( IIF( uVal, ".T.", ".F." ), 12 ) + " "
             CASE cType == "M"
                cLine += PadR( Left( AllTrim( IIF( uVal == NIL, "", uVal ) ), 40 ), 12 ) + " "
             OTHERWISE
                cLine += PadR( Left( AllTrim( hb_ValToExp( uVal, .F. ) ), 40 ), 12 ) + " "
             ENDCASE
          NEXT
        //  ? valtocharex(cLine)
        //  WAIT "Weiter mit Taste..."   // gleiches Verhalten
          ? cLine

          nShown++
          SKIP

       



       ENDDO
    RECOVER USING oErr
       ? "Browser(): Fehler:", oErr:Description, "Operation:", oErr:Operation, ;
         "SubCode:", oErr:SubCode, "OsCode:", oErr:OsCode
    END SEQUENCE
 
    SELECT ( nOld )




 RETURN NIL




 /* BrowseFit(): hübschere Konsolen-Ansicht mit Auto-Fit auf MaxCol()
   Aufruf:
      BrowseFit()                 // 50 Zeilen, alle Felder
      BrowseFit(100)              // 100 Zeilen
      BrowseFit(, {"ZIMMERNR","AUSSTATTUN","BETTEN","BALKON"})  // Feld-Whitelist
*/
FUNCTION BrowseFit( nMax, aOnlyFields )
    LOCAL nLimit := IIF( nMax == NIL .OR. nMax <= 0, 50, nMax )
    LOCAL nOld := SELECT()
    LOCAL aStru, aCols := {}, n, cName, cType, nLen, nDec
    LOCAL nTotalWidth := 0, nMaxCol := MaxCol(), nMaxRow := MaxRow()
    LOCAL nShown := 0, nPageRows := Max( nMaxRow - 4, 10 )
    LOCAL cHdr1 := "", cHdr2 := "", cSep := "", cLine, uVal, nKey
 
    IF !USED()
       ? "BrowseFit(): Kein Workarea aktiv."
       RETURN NIL
    ENDIF
 
    // 1) Struktur lesen + gewünschte Felder filtern
    aStru := DBStruct()
    FOR n := 1 TO Len( aStru )
       cName := aStru[n,1]
       IF Empty( aOnlyFields ) .OR. ASCAN( aOnlyFields, {|x| Upper(x)==Upper(cName)} ) > 0
          cType := aStru[n,2]
          nLen  := aStru[n,3]
          nDec  := aStru[n,4]
          // sinnvolle Mindest-/Höchstbreite je Typ
          nLen  := IIF( cType $ "CM", Min( Max( Len(cName), 10 ), 40 ), ;
                  IIF( cType == "D", Max( Len(cName), 10 ), ;
                  IIF( cType == "L", Max( Len(cName), 3 ), ;
                       Max( Len(cName), Min( nLen + IIF(nDec>0,1,0), 14 ) ) ) ) )
          AAdd( aCols, { cName, cType, nLen, nDec } )
       ENDIF
    NEXT
    IF Empty( aCols )
       ? "BrowseFit(): Keine passenden Felder."
       RETURN NIL
    ENDIF
 
    // 2) Falls Gesamtbreite > Terminalbreite, kürzen wir weiche Felder zuerst (M/C)
    nTotalWidth := _bf_TotalWidth( aCols )
    DO WHILE nTotalWidth > nMaxCol
       // suche längstes C/M-Feld > 10 und kürze um 1
       FOR n := 1 TO Len( aCols )
          IF ( aCols[n,2] $ "CM" ) .AND. aCols[n,3] > 10
             aCols[n,3]--
             EXIT
          ENDIF
       NEXT
       // wenn nichts mehr zu kürzen war, kürzen wir längstes Feld > 6
       IF _bf_TotalWidth( aCols ) == nTotalWidth
          n := _bf_IndexMaxWidth( aCols )
          IF aCols[n,3] > 6
             aCols[n,3]--
          ELSE
             EXIT
          ENDIF
       ENDIF
       nTotalWidth := _bf_TotalWidth( aCols )
    ENDDO
 
    // 3) Header bauen
    cHdr1 := "Alias: " + ALIAS() + "  Records: " + LTrim(Str(LastRec()))
    ? cHdr1
    cHdr2 := ""
    cSep  := ""
    FOR n := 1 TO Len( aCols )
       cHdr2 += PadC( aCols[n,1], aCols[n,3] ) + IIF( n < Len(aCols), " ", "" )
       cSep  += Replicate( "-", aCols[n,3] )   + IIF( n < Len(aCols), " ", "" )
    NEXT
    ? cHdr2
    ? cSep
 
    // 4) Datensätze
    GO TOP
    DO WHILE !Eof() .AND. nShown < nLimit
       cLine := ""
       FOR n := 1 TO Len( aCols )
          uVal := FieldGet( FieldPos( aCols[n,1] ) )
          cLine += _bf_Format( uVal, aCols[n,2], aCols[n,3], aCols[n,4] )
          IF n < Len( aCols ); cLine += " "; ENDIF
       NEXT
       ? cLine
       nShown++
       IF nShown % nPageRows == 0 .AND. ( !Eof() .AND. nShown < nLimit )
          ?? " -- mehr -- (ESC beendet)"
          nKey := Inkey(0)
          ? 
          IF nKey == 27
             EXIT
          ENDIF
          // Kopf neu zeichnen
          ? cHdr2
          ? cSep
       ENDIF
       SKIP
    ENDDO
 
    SELECT ( nOld )
 RETURN NIL
 
 // ---- Helpers --------------------------------------------------------------
 
 STATIC FUNCTION _bf_TotalWidth( aCols )
    LOCAL n, w := 0
    FOR n := 1 TO Len( aCols )
       w += aCols[n,3]
       IF n < Len( aCols ); w++ ; ENDIF
    NEXT
 RETURN w
 
 STATIC FUNCTION _bf_IndexMaxWidth( aCols )
    LOCAL i := 1, n := 2
    FOR n := 2 TO Len( aCols )
       IF aCols[n,3] > aCols[i,3]
          i := n
       ENDIF
    NEXT
 RETURN i
 
 STATIC FUNCTION _bf_Format( uVal, cType, nLen, nDec )
    LOCAL c := ""
    DO CASE
    CASE cType == "C"
       c := PadR( Left( AllTrim( IIF(uVal==NIL,"",uVal) ), nLen ), nLen )
    CASE cType == "M"
       c := PadR( Left( AllTrim( IIF(uVal==NIL,"",uVal) ), nLen ), nLen )
    CASE cType == "N"
       c := PadL( IIF( uVal==NIL, "", LTrim(Str(uVal, nLen, Min(nDec, Max(nLen-2,0))) ) ), nLen )
    CASE cType == "D"
       c := PadR( IIF( Empty(uVal), "", DToC(uVal) ), nLen )
    CASE cType == "L"
       c := PadL( IIF(uVal, "T", "F"), nLen )
    OTHERWISE
       c := PadR( Left( AllTrim( hb_ValToExp(uVal,.F.) ), nLen ), nLen )
    ENDCASE
 RETURN c

Continue the discussion