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