Skip to content

Instantly share code, notes, and snippets.

@dchapes

dchapes/go.mod Secret

Last active June 5, 2019 22:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dchapes/9d795a04e471319abbc5ff016afbbee9 to your computer and use it in GitHub Desktop.
Save dchapes/9d795a04e471319abbc5ff016afbbee9 to your computer and use it in GitHub Desktop.
codereview.stackexchange.com/questions/219972
module swab
go 1.12
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
)
func main() {
log.SetPrefix("swab: ")
log.SetFlags(0)
infile := flag.String("in", "", "input `path`, blank for stdin")
outfile := flag.String("out", "", "output `path`, blank for stdout")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Usage: %s [options]\n", os.Args[0],
)
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() > 0 {
flag.Usage()
os.Exit(2) // To match the exit code flag.Parse uses.
}
var src io.ReadCloser = os.Stdin
var dst io.WriteCloser = os.Stdout
if *infile != "" {
f, err := os.Open(*infile)
if err != nil {
log.Fatal(err)
}
src = f
}
// Closing the input isn't strictly required in main
// nor for stdio, but it's a good habit. No need to
// check any error; we rely on Read reporting errors of interest.
defer src.Close()
if *outfile != "" {
f, err := os.Create(*outfile)
if err != nil {
log.Fatal(err)
}
dst = f
}
if _, err := io.Copy(dst, NewSwabReader(src)); err != nil {
// Not this calls os.Exit so no defers get run
// and we don't close the output either, not
// an issue from main.
log.Fatal(err)
}
if err := dst.Close(); err != nil {
log.Fatal(err)
}
}
type SwabReader struct {
r io.Reader
b byte // extra byte, not yet swapped
haveByte bool // true if b is valid
err error
}
// NewSwabReader returns an io.Reader that reads from r
// swapping adjacent bytes. The trailing odd byte, if any,
// is left as-is.
func NewSwabReader(r io.Reader) *SwabReader {
return &SwabReader{r: r}
}
func (sr *SwabReader) Read(p []byte) (n int, err error) {
if len(p) == 0 || sr.err != nil {
return 0, sr.err
}
i := 0
if sr.haveByte {
// Copy in the previous saved byte.
p[0] = sr.b
i = 1
//sr.haveByte = false // not strictly required
}
n, sr.err = sr.r.Read(p[i:])
n += i
p = p[:n]
for i := 1; i < len(p); i += 2 {
p[i-1], p[i] = p[i], p[i-1]
}
// Remove and save any non-swapped trailing odd byte.
if sr.err == nil {
if sr.haveByte = (n&1 != 0); sr.haveByte {
n--
sr.b = p[n]
//p = p[:n] // not strictly required
}
}
return n, sr.err
}
package main
import (
"io"
"strings"
"testing"
"testing/iotest"
)
var readFilters = []struct {
name string
fn func(io.Reader) io.Reader
}{
{"", nil},
{"DataErrReader", iotest.DataErrReader},
{"HalfReader", iotest.HalfReader},
{"OneByteReader", iotest.OneByteReader},
//{"TimeoutReader", iotest.TimeoutReader},
}
func TestSwab(t *testing.T) {
const sz = 32<<10 + 1
cases := []struct{ in, out string }{
{"", ""},
{"a", "a"},
{"ab", "ba"},
{"abc", "bac"},
{"abcd", "badc"},
{strings.Repeat("\x01\x80", sz) + "x",
strings.Repeat("\x80\x01", sz) + "x"},
}
var dst strings.Builder
var r io.Reader
for _, rf := range readFilters {
for _, tc := range cases {
r = strings.NewReader(tc.in)
if rf.fn != nil {
r = rf.fn(r)
}
dst.Reset()
//t.Logf("swabbing %s %.16q", rf.name, tc.in)
//r = iotest.NewReadLogger("<<< src", r)
n, err := io.Copy(&dst, NewSwabReader(r))
if err != nil {
t.Errorf("swab on %s %q failed: %v",
rf.name, tc.in, err,
)
continue
}
if want := int64(len(tc.out)); n != want {
t.Errorf("swab on %s %q returned n=%d, want %d",
rf.name, tc.in, n, want,
)
}
if got := dst.String(); got != tc.out {
t.Errorf("swab on %s %q\n\tgave %q\n\twant %q",
rf.name, tc.in, got, tc.out,
)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment