File: bitdump.go
   1 /*
   2 The MIT License (MIT)
   3 
   4 Copyright (c) 2026 pacman64
   5 
   6 Permission is hereby granted, free of charge, to any person obtaining a copy of
   7 this software and associated documentation files (the "Software"), to deal
   8 in the Software without restriction, including without limitation the rights to
   9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  10 of the Software, and to permit persons to whom the Software is furnished to do
  11 so, subject to the following conditions:
  12 
  13 The above copyright notice and this permission notice shall be included in all
  14 copies or substantial portions of the Software.
  15 
  16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 SOFTWARE.
  23 */
  24 
  25 /*
  26 To compile a smaller-sized command-line app, you can use the `go` command as
  27 follows:
  28 
  29 go build -ldflags "-s -w" -trimpath bitdump.go
  30 */
  31 
  32 package main
  33 
  34 import (
  35     "bufio"
  36     "errors"
  37     "io"
  38     "os"
  39     "strconv"
  40 )
  41 
  42 const info = `
  43 bitdump [options...] [filenames...]
  44 
  45 
  46 Show all bits for all input bytes, starting each output line with the
  47 leading byte's offset.
  48 
  49 All (optional) leading options start with either single or double-dash:
  50 
  51     -h, -help                  show this help message
  52     -no-offset, -no-offsets    don't start lines with current byte offsets
  53     -tab, -tabs                separate items with tabs instead of spaces
  54 `
  55 
  56 const itemsPerLine = 8
  57 
  58 func main() {
  59     emitOffsets := true
  60     separator := byte(' ')
  61     args := os.Args[1:]
  62 
  63     for len(args) > 0 {
  64         switch args[0] {
  65         case `-h`, `--h`, `-help`, `--help`:
  66             os.Stdout.WriteString(info[1:])
  67             return
  68 
  69         case `-no-offset`, `--no-offset`, `-no-offsets`, `--no-offsets`:
  70             emitOffsets = false
  71             args = args[1:]
  72             continue
  73 
  74         case `-tab`, `--tab`, `-tabs`, `--tabs`:
  75             separator = '\t'
  76             args = args[1:]
  77             continue
  78         }
  79 
  80         break
  81     }
  82 
  83     if len(args) > 0 && args[0] == `--` {
  84         args = args[1:]
  85     }
  86 
  87     var p params
  88     p.emitOffset = emitNoOffset
  89     if emitOffsets {
  90         p.emitOffset = emitDecimalOffset
  91     }
  92     p.separator = separator
  93 
  94     if err := run(os.Stdout, p, args); err != nil && err != io.EOF {
  95         os.Stderr.WriteString(err.Error())
  96         os.Stderr.WriteString("\n")
  97         os.Exit(1)
  98         return
  99     }
 100 }
 101 
 102 type params struct {
 103     offset     *int64
 104     emitOffset func(w *bufio.Writer, offset int64, sep byte)
 105     separator  byte
 106 }
 107 
 108 func run(w io.Writer, p params, args []string) error {
 109     offset := int64(0)
 110     p.offset = &offset
 111     bw := bufio.NewWriter(w)
 112     defer func() {
 113         if offset%itemsPerLine == 0 && offset > 0 {
 114             bw.WriteByte('\n')
 115         }
 116         bw.Flush()
 117     }()
 118 
 119     if len(args) == 0 {
 120         return bitdump(bw, os.Stdin, p)
 121     }
 122 
 123     for _, name := range args {
 124         if err := handleFile(bw, name, p); err != nil {
 125             return err
 126         }
 127     }
 128     return nil
 129 }
 130 
 131 func handleFile(w *bufio.Writer, name string, p params) error {
 132     if name == `` || name == `-` {
 133         return bitdump(w, os.Stdin, p)
 134     }
 135 
 136     f, err := os.Open(name)
 137     if err != nil {
 138         return errors.New(`can't read from file named "` + name + `"`)
 139     }
 140     defer f.Close()
 141 
 142     return bitdump(w, f, p)
 143 }
 144 
 145 func bitdump(w *bufio.Writer, r io.Reader, p params) error {
 146     var buf [32 * 1024]byte
 147     defer w.Flush()
 148 
 149     for {
 150         n, err := r.Read(buf[:])
 151         if n < 1 && err == io.EOF {
 152             return nil
 153         }
 154 
 155         if err != nil && err != io.EOF {
 156             return err
 157         }
 158 
 159         chunk := buf[:n]
 160 
 161         for len(chunk) >= itemsPerLine {
 162             if err := emitChunk(w, chunk[:itemsPerLine], p); err != nil {
 163                 return err
 164             }
 165 
 166             chunk = chunk[itemsPerLine:]
 167             *p.offset += itemsPerLine
 168         }
 169 
 170         if len(chunk) > 0 {
 171             if err := emitChunk(w, chunk, p); err != nil {
 172                 return err
 173             }
 174 
 175             *p.offset += int64(len(chunk))
 176         }
 177     }
 178 }
 179 
 180 func emitDecimalOffset(w *bufio.Writer, offset int64, sep byte) {
 181     const pad = `00000000`
 182     var str [24]byte
 183 
 184     s := strconv.AppendInt(str[:0], offset, 10)
 185     // pad small offsets with leading zeros
 186     if len(s) < len(pad) {
 187         w.WriteString(pad[len(s):])
 188     }
 189     w.Write(s)
 190     w.WriteByte(sep)
 191 }
 192 
 193 func emitNoOffset(w *bufio.Writer, offset int64, sep byte) {
 194     // deliberately does nothing
 195 }
 196 
 197 func emitChunk(w *bufio.Writer, chunk []byte, p params) error {
 198     p.emitOffset(w, *p.offset, p.separator)
 199     for i, b := range chunk {
 200         if i > 0 {
 201             w.WriteByte(p.separator)
 202         }
 203         w.WriteString(bits[b])
 204     }
 205 
 206     if err := w.WriteByte('\n'); err != nil {
 207         // assume a write error is the consequence of stdout
 208         // being closed, perhaps by another app along a pipe
 209         return io.EOF
 210     }
 211     return nil
 212 }
 213 
 214 /*
 215 tlp = '(f"{bin(v)[2:]:>08}" for v in range(256))' | lineup 6 |
 216 gsub '\t' '`, `' | tlp 'f"\t`{l}`,"'
 217 */
 218 var bits = [256]string{
 219     `00000000`, `00000001`, `00000010`, `00000011`, `00000100`, `00000101`,
 220     `00000110`, `00000111`, `00001000`, `00001001`, `00001010`, `00001011`,
 221     `00001100`, `00001101`, `00001110`, `00001111`, `00010000`, `00010001`,
 222     `00010010`, `00010011`, `00010100`, `00010101`, `00010110`, `00010111`,
 223     `00011000`, `00011001`, `00011010`, `00011011`, `00011100`, `00011101`,
 224     `00011110`, `00011111`, `00100000`, `00100001`, `00100010`, `00100011`,
 225     `00100100`, `00100101`, `00100110`, `00100111`, `00101000`, `00101001`,
 226     `00101010`, `00101011`, `00101100`, `00101101`, `00101110`, `00101111`,
 227     `00110000`, `00110001`, `00110010`, `00110011`, `00110100`, `00110101`,
 228     `00110110`, `00110111`, `00111000`, `00111001`, `00111010`, `00111011`,
 229     `00111100`, `00111101`, `00111110`, `00111111`, `01000000`, `01000001`,
 230     `01000010`, `01000011`, `01000100`, `01000101`, `01000110`, `01000111`,
 231     `01001000`, `01001001`, `01001010`, `01001011`, `01001100`, `01001101`,
 232     `01001110`, `01001111`, `01010000`, `01010001`, `01010010`, `01010011`,
 233     `01010100`, `01010101`, `01010110`, `01010111`, `01011000`, `01011001`,
 234     `01011010`, `01011011`, `01011100`, `01011101`, `01011110`, `01011111`,
 235     `01100000`, `01100001`, `01100010`, `01100011`, `01100100`, `01100101`,
 236     `01100110`, `01100111`, `01101000`, `01101001`, `01101010`, `01101011`,
 237     `01101100`, `01101101`, `01101110`, `01101111`, `01110000`, `01110001`,
 238     `01110010`, `01110011`, `01110100`, `01110101`, `01110110`, `01110111`,
 239     `01111000`, `01111001`, `01111010`, `01111011`, `01111100`, `01111101`,
 240     `01111110`, `01111111`, `10000000`, `10000001`, `10000010`, `10000011`,
 241     `10000100`, `10000101`, `10000110`, `10000111`, `10001000`, `10001001`,
 242     `10001010`, `10001011`, `10001100`, `10001101`, `10001110`, `10001111`,
 243     `10010000`, `10010001`, `10010010`, `10010011`, `10010100`, `10010101`,
 244     `10010110`, `10010111`, `10011000`, `10011001`, `10011010`, `10011011`,
 245     `10011100`, `10011101`, `10011110`, `10011111`, `10100000`, `10100001`,
 246     `10100010`, `10100011`, `10100100`, `10100101`, `10100110`, `10100111`,
 247     `10101000`, `10101001`, `10101010`, `10101011`, `10101100`, `10101101`,
 248     `10101110`, `10101111`, `10110000`, `10110001`, `10110010`, `10110011`,
 249     `10110100`, `10110101`, `10110110`, `10110111`, `10111000`, `10111001`,
 250     `10111010`, `10111011`, `10111100`, `10111101`, `10111110`, `10111111`,
 251     `11000000`, `11000001`, `11000010`, `11000011`, `11000100`, `11000101`,
 252     `11000110`, `11000111`, `11001000`, `11001001`, `11001010`, `11001011`,
 253     `11001100`, `11001101`, `11001110`, `11001111`, `11010000`, `11010001`,
 254     `11010010`, `11010011`, `11010100`, `11010101`, `11010110`, `11010111`,
 255     `11011000`, `11011001`, `11011010`, `11011011`, `11011100`, `11011101`,
 256     `11011110`, `11011111`, `11100000`, `11100001`, `11100010`, `11100011`,
 257     `11100100`, `11100101`, `11100110`, `11100111`, `11101000`, `11101001`,
 258     `11101010`, `11101011`, `11101100`, `11101101`, `11101110`, `11101111`,
 259     `11110000`, `11110001`, `11110010`, `11110011`, `11110100`, `11110101`,
 260     `11110110`, `11110111`, `11111000`, `11111001`, `11111010`, `11111011`,
 261     `11111100`, `11111101`, `11111110`, `11111111`,
 262 }