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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

hexdump.gno

3.13 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/nym-moultest123/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 }