Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

hexdump.gno

3.12 Kb · 113 lines
  1// Package hexdump renders bytes in the classic xxd/hexdump -C layout, as a
  2// pure, reusable package.
  3//
  4// The format is the familiar one: an 8-digit hex offset, then 16 bytes as hex
  5// in two 8-byte groups separated by an extra space, then the same bytes as
  6// ASCII between pipes with non-printables shown as ".". Short final lines are
  7// padded so the ASCII column stays aligned — the whole point of the layout.
  8//
  9// Useful on chain for exactly the reason it is useful off it: when a value is
 10// not what you expected, the bytes tell you why. Encoding bugs, stray NULs,
 11// UTF-8 that is not what it claims, trailing whitespace — all invisible in a
 12// rendered string and obvious in a dump.
 13//
 14// A live demo of this package is at
 15// [r/moul/x/daily/hexdumpdemo](/r/moul/x/daily/hexdumpdemo/v0).
 16package hexdump
 17
 18import "strings"
 19
 20// MaxBytes bounds a dump so gas stays predictable. Input beyond this is
 21// truncated and reported by Dump's second return value.
 22const MaxBytes = 4096
 23
 24// BytesPerLine is the classic 16.
 25const BytesPerLine = 16
 26
 27const hexDigits = "0123456789abcdef"
 28
 29// Dump renders b in the xxd -C layout. It returns the dump and the number of
 30// bytes rendered, which is less than len(b) when the input exceeds MaxBytes.
 31func Dump(b []byte) (string, int) {
 32	n := len(b)
 33	if n > MaxBytes {
 34		n = MaxBytes
 35	}
 36	if n == 0 {
 37		return "", 0
 38	}
 39
 40	var sb strings.Builder
 41	for off := 0; off < n; off += BytesPerLine {
 42		end := off + BytesPerLine
 43		if end > n {
 44			end = n
 45		}
 46		sb.WriteString(Line(off, b[off:end]))
 47		sb.WriteString("\n")
 48	}
 49	return sb.String(), n
 50}
 51
 52// DumpString is Dump over a string's bytes.
 53func DumpString(s string) (string, int) { return Dump([]byte(s)) }
 54
 55// Line renders one line: offset, hex columns, ASCII gutter. chunk must hold at
 56// most BytesPerLine bytes; a shorter chunk is padded so columns stay aligned.
 57func Line(offset int, chunk []byte) string {
 58	var sb strings.Builder
 59	sb.WriteString(Offset(offset))
 60	sb.WriteString("  ")
 61
 62	for i := 0; i < BytesPerLine; i++ {
 63		if i == BytesPerLine/2 {
 64			sb.WriteString(" ") // the classic gap between the two 8-byte groups
 65		}
 66		if i < len(chunk) {
 67			sb.WriteString(Hex(chunk[i]))
 68		} else {
 69			sb.WriteString("  ") // pad so the ASCII gutter never shifts
 70		}
 71		sb.WriteString(" ")
 72	}
 73
 74	sb.WriteString(" |")
 75	sb.WriteString(ASCII(chunk))
 76	sb.WriteString("|")
 77	return sb.String()
 78}
 79
 80// Offset formats an 8-digit lowercase hex offset.
 81func Offset(n int) string {
 82	if n < 0 {
 83		n = 0
 84	}
 85	out := make([]byte, 8)
 86	for i := 7; i >= 0; i-- {
 87		out[i] = hexDigits[n&0xf]
 88		n >>= 4
 89	}
 90	return string(out)
 91}
 92
 93// Hex formats one byte as two lowercase hex digits.
 94func Hex(b byte) string {
 95	return string([]byte{hexDigits[b>>4], hexDigits[b&0xf]})
 96}
 97
 98// ASCII renders the printable-ASCII view of chunk: bytes outside 0x20..0x7e
 99// become ".". It is NOT padded — Line handles alignment.
100func ASCII(chunk []byte) string {
101	out := make([]byte, len(chunk))
102	for i, c := range chunk {
103		if c >= 0x20 && c <= 0x7e {
104			out[i] = c
105		} else {
106			out[i] = '.'
107		}
108	}
109	return string(out)
110}
111
112// Printable reports whether c renders as itself rather than as ".".
113func Printable(c byte) bool { return c >= 0x20 && c <= 0x7e }