mirror of
https://github.com/wader/fq.git
synced 2024-12-03 13:46:37 +03:00
913f5780f4
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.
209 lines
4.7 KiB
Go
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
|
|
}
|