1
1
mirror of https://github.com/wader/fq.git synced 2024-11-27 06:04:47 +03:00
fq/internal/hexdump/hexdump.go
Mattias Wadman 913f5780f4 columnwriter,dump: Add Column interface and refactor into BarColumn and MultiLineColumn
This removes bar column drawing responsility from already complicated dump code.

Start of dump code refactor that will enable configurable columns and proper column truncate/wrap.
2022-08-24 21:48:56 +02:00

209 lines
4.7 KiB
Go

package hexdump
import (
"io"
"strings"
"github.com/wader/fq/internal/columnwriter"
"github.com/wader/fq/internal/mathex"
"github.com/wader/fq/pkg/bitio"
)
type Dumper struct {
addrLen int
addrBase int
lineBytes int64
columnW *columnwriter.Writer
separatorsW io.Writer
startOffset int64
offset int64
hexFn func(b byte) string
asciiFn func(b byte) string
dumpHeaderFn func(s string) string
dumpAddrFn func(s string) string
column string
hasWrittenHeader bool
bitsBuf []byte
bitsBufN int64
}
// TODO: something more generic? bin, octal, arbitrary base?
// TODO: template for columns?
// TODO: merge with dump?
// TODO: replace addrLen with highest address and calc instead
// TODO: use dump options? config struct?
func New(w io.Writer, startOffset int64, addrLen int, addrBase int, lineBytes int,
hexFn func(b byte) string,
asciiFn func(b byte) string,
dumpHeaderFn func(s string) string,
dumpAddrFn func(s string) string,
column string) *Dumper {
cw := columnwriter.New(
w,
&columnwriter.MultiLineColumn{Width: addrLen},
columnwriter.BarColumn(column),
&columnwriter.MultiLineColumn{Width: lineBytes * 3},
columnwriter.BarColumn(column),
&columnwriter.MultiLineColumn{Width: lineBytes},
columnwriter.BarColumn(column),
)
return &Dumper{
addrLen: addrLen,
addrBase: addrBase,
lineBytes: int64(lineBytes),
columnW: cw,
separatorsW: io.MultiWriter(cw.Columns[1], cw.Columns[3], cw.Columns[5]),
startOffset: startOffset,
offset: startOffset - startOffset%int64(lineBytes),
hexFn: hexFn,
asciiFn: asciiFn,
dumpHeaderFn: dumpHeaderFn,
dumpAddrFn: dumpAddrFn,
column: column,
hasWrittenHeader: false,
bitsBuf: make([]byte, 1),
}
}
func (d *Dumper) flush() error {
if _, err := d.columnW.Columns[0].Write([]byte(
d.dumpAddrFn(mathex.PadFormatInt(((d.offset-1)/d.lineBytes)*d.lineBytes, d.addrBase, true, d.addrLen)))); err != nil {
return err
}
if _, err := d.separatorsW.Write([]byte(d.column)); err != nil {
return err
}
if err := d.columnW.Flush(); err != nil {
return err
}
return nil
}
func (d *Dumper) WriteBits(p []byte, nBits int64) (n int64, err error) {
pos := int64(0)
rBits := nBits
if d.bitsBufN > 0 {
r := mathex.Min(8-d.bitsBufN, nBits)
v := bitio.Read64(p, 0, r)
bitio.Write64(v, r, d.bitsBuf, d.bitsBufN)
d.bitsBufN += r
if d.bitsBufN < 8 {
return nBits, nil
}
if n, err := d.Write(d.bitsBuf); err != nil {
return int64(n) * 8, err
}
pos = r
rBits -= r
}
for rBits >= 8 {
b := [1]byte{0}
b[0] = byte(bitio.Read64(p, pos, 8))
if n, err := d.Write(b[:]); err != nil {
return int64(n) * 8, err
}
pos += 8
rBits -= 8
}
if rBits > 0 {
d.bitsBuf[0] = byte(bitio.Read64(p, pos, rBits)) << (8 - rBits)
d.bitsBufN = rBits
} else {
d.bitsBufN = 0
}
return nBits, nil
}
func (d *Dumper) Write(p []byte) (n int, err error) {
if !d.hasWrittenHeader {
if _, err := d.separatorsW.Write([]byte(d.column)); err != nil {
return 0, err
}
for i := int64(0); i < d.lineBytes; i++ {
headerSB := &strings.Builder{}
if _, err := headerSB.Write([]byte(mathex.PadFormatInt(i, d.addrBase, false, 2))); err != nil {
return 0, err
}
if i < d.lineBytes-1 {
if _, err := headerSB.Write([]byte(" ")); err != nil {
return 0, err
}
}
if _, err := d.columnW.Columns[2].Write([]byte(d.dumpHeaderFn(headerSB.String()))); err != nil {
return 0, err
}
}
if err := d.columnW.Flush(); err != nil {
return 0, err
}
d.hasWrittenHeader = true
}
if d.offset < d.startOffset {
r := int(d.startOffset - d.offset)
if _, err := d.columnW.Columns[2].Write([]byte(strings.Repeat(" ", r))); err != nil {
return n, err
}
if _, err := d.columnW.Columns[4].Write([]byte(strings.Repeat(" ", r))); err != nil {
return n, err
}
d.offset = d.startOffset
}
for len(p) > 0 {
cl := d.lineBytes - d.offset%d.lineBytes
if cl == 0 {
cl = d.lineBytes
}
if cl > int64(len(p)) {
cl = int64(len(p))
}
ps := p[0:cl]
for _, b := range ps {
d.offset++
if _, err := d.columnW.Columns[2].Write([]byte(d.hexFn(b))); err != nil {
return n, err
}
if d.offset%d.lineBytes != 0 {
if _, err := d.columnW.Columns[2].Write([]byte(" ")); err != nil {
return n, err
}
}
if _, err := d.columnW.Columns[4].Write([]byte(d.asciiFn(b))); err != nil {
return n, err
}
n++
}
if d.offset%d.lineBytes == 0 {
if err := d.flush(); err != nil {
return n, err
}
}
p = p[cl:]
}
return n, nil
}
func (d *Dumper) Close() error {
if d.offset == 0 || d.offset%d.lineBytes != 0 {
return d.flush()
}
return nil
}