wip
diff --git a/flow/flow.go b/flow/flow.go new file mode 100644 index 0000000..a93b100 --- /dev/null +++ b/flow/flow.go
@@ -0,0 +1,51 @@ +// Command flow reflows text in paragraphs. +// +// Usage: flow [-w width] [-pre prefix] [file...] +package main + +import ( + "flag" + "github.com/kr/pty" + "github.com/kr/text/flowwriter" + "io" + "log" + "os" +) + +func main() { + var width int + var pre string + flag.IntVar(&width, "w", 0, "width") + flag.StringVar(&pre, "pre", " ", "preformatted prefix") + flag.Parse() + if width < 1 { + _, width, _ = pty.Getsize(os.Stdout) + } + if width < 1 { + width = 80 + } + + w := flowwriter.NewWriter(os.Stdout, width) + w.SetNoWrap(pre) + if flag.NArg() > 0 { + for _, s := range flag.Args() { + if f, err := os.Open(s); err == nil { + copyin(w, f) + f.Close() + } else { + log.Println(err) + } + } + } else { + copyin(w, os.Stdin) + } +} + +func copyin(w *flowwriter.Writer, r io.Reader) { + if _, err := io.Copy(w, r); err != nil { + log.Println(err) + } + if err := w.Flush(); err != nil { + log.Println(err) + } +}
diff --git a/flowwriter/flow.go b/flowwriter/flow.go new file mode 100755 index 0000000..c215d94 --- /dev/null +++ b/flowwriter/flow.go
@@ -0,0 +1,117 @@ +package flowwriter + +import ( + "bytes" + "github.com/kr/text/indentwriter" + "github.com/kr/text/wrapwriter" + "io" + "strings" + "unicode/utf8" +) + +type Writer struct { + width int + hang string + indent string + chars string // hang + indent + repl rune // last rune in indent + noWrap string + + sec [][]byte + buf []byte + pre0 []byte + pre []byte + + w io.Writer +} + +func NewWriter(w io.Writer, width int) *Writer { + nw := new(Writer) + nw.w = w + nw.width = width + nw.hang = "-*" + nw.indent = "> " + nw.initChars() + return nw +} + +func (w *Writer) SetHang(chars string) { + w.hang = chars + w.initChars() +} + +func (w *Writer) SetIndent(chars string) { + w.indent = chars + w.initChars() +} + +func (w *Writer) initChars() { + w.chars = w.hang + w.indent + w.repl, _ = utf8.DecodeLastRuneInString(w.indent) +} + +func (w *Writer) SetNoWrap(prefix string) { + w.noWrap = prefix +} + +func (w *Writer) Write(p []byte) (n int, err error) { + for i, c := range p { + w.buf = append(w.buf, c) + if c == '\n' { + body := bytes.TrimLeft(w.buf, w.chars) + pre := w.buf[:len(w.buf)-len(body)] + if !bytes.Equal(pre, w.pre) { + if err := w.flow(); err != nil { + return i - len(w.buf), err + } + w.pre0 = pre + w.pre = w.tr(pre) + } + w.sec = append(w.sec, body) + w.buf = nil + } + } + return len(p), nil +} + +func (w *Writer) Flush() error { + // TODO(kr): handle incomplete last line + return w.flow() +} + +func (w *Writer) tr(s []byte) []byte { + f := func(r rune) rune { + if strings.ContainsRune(w.hang, r) { + return w.repl + } + return r + } + return bytes.Map(f, s) +} + +func (w *Writer) flow() (err error) { + sec := w.sec + w.sec = nil + n := w.width - len(w.pre0) + dst := indentwriter.NewWriter(w.w, [][]byte{w.pre0, w.pre}) + if w.noWrap == "" || string(w.pre) != w.noWrap { + dst = wrapwriter.NewWriter(dst, n) + } + for _, s := range sec { + if _, err = dst.Write(s); err != nil { + return err + } + } + if f, ok := dst.(flusher); ok { + f.Flush() + } + return nil +} + +func Wrapper(w io.Writer) io.Writer { + return w +} + +type flusher interface { + Flush() error +}
diff --git a/flowwriter/flow_test.go b/flowwriter/flow_test.go new file mode 100644 index 0000000..ab1d4bf --- /dev/null +++ b/flowwriter/flow_test.go
@@ -0,0 +1,77 @@ +package flowwriter + +import ( + "bytes" + "testing" +) + +type Test struct { + n int + d string + w string +} + +var ts = []Test{ + {9, ` +a b c d e f + - a b c d + e f +a b c d e f +`, + ` +a b c d e +f + - a b c + d e f +a b c d e +f +`}, + + {9, ` +a b c d e f +> - a b c d +> e f +a b c d e f +`, + ` +a b c d e +f +> - a b c +> d e f +a b c d e +f +`}, + + {9, ` +a b c d e f +> - a b c d +a b c d e f +`, + ` +a b c d e +f +> - a b c +> d +a b c d e +f +`}, +} + +func TestIndent(t *testing.T) { + for _, ts := range ts { + b := new(bytes.Buffer) + w := NewWriter(b, ts.n, "-", "> ", "") + if _, err := w.Write([]byte(ts.d)); err != nil { + t.Error(err) + } + if err := w.Flush(); err != nil { + t.Error(err) + } + if g := b.String(); g != ts.w { + t.Logf("%q != %q", g, ts.w) + t.Log("\n" + g) + t.Log("\n" + ts.w) + t.Fail() + } + } +}
diff --git a/fulltest.go.waiting b/fulltest.go.waiting new file mode 100644 index 0000000..1eb14dd --- /dev/null +++ b/fulltest.go.waiting
@@ -0,0 +1,61 @@ +package text + +import ( + "bytes" + "testing" +) + +type Test struct { + n int + d string + w string +} + +ts := []Test{ + {9, ` +a b c d e f + - a b c d + e f +a b c d e f +`, + ` +a b c d e +f + - a b c + d e f +a b c d e +f +`}, + + {9, ` +a b c d e f +> - a b c d +> e f +a b c d e f +`, + ` +a b c d e +f +> - a b c +> d e f +a b c d e +f +`}, + + {9, ` +a b c d e f +> - a b c d +a b c d e f +`, + ` +a b c d e +f +> - a b c +> d +a b c d e +f +`}, +} + +func TestIndent(t *testing.T) { +}
diff --git a/tab/main.go b/tab/main.go new file mode 100644 index 0000000..f502e1c --- /dev/null +++ b/tab/main.go
@@ -0,0 +1,47 @@ +// Command tab translates tabs to spaces. +// +// Usage: tab [file...] +// +// Uses the elastic tabs algorithm. See tabwriter and original +// source. +package main + +import ( + "io" + "log" + "os" + "text/tabwriter" +) + +var ( + minw = 2 + tabw = 4 + npad = 2 + padc byte = ' ' + flag uint = 0 +) + +func main() { + args := os.Args[1:] + w := tabwriter.NewWriter(os.Stdout, minw, tabw, npad, padc, flag) + if len(args) > 0 { + for _, s := range args { + if f, err := os.Open(s); err == nil { + copyin(w, f) + } else { + log.Println(err) + } + } + } else { + copyin(w, os.Stdin) + } +} + +func copyin(w *tabwriter.Writer, r io.Reader) { + if _, err := io.Copy(w, r); err != nil { + log.Println(err) + } + if err := w.Flush(); err != nil { + log.Println(err) + } +}
diff --git a/wrapwriter/wrap.go b/wrapwriter/wrap.go new file mode 100755 index 0000000..47e0cab --- /dev/null +++ b/wrapwriter/wrap.go
@@ -0,0 +1,125 @@ +package wrapwriter + +import ( + "bytes" + "io" + "math" +) + +var ( + nl = []byte{'\n'} + sp = []byte{' '} +) + +type Writer struct { + width int + buf []byte + bol bool + w io.Writer +} + +func NewWriter(w io.Writer, width int) *Writer { + return &Writer{width: width, w: w, bol: true} +} + +func (w *Writer) Write(p []byte) (n int, err error) { + for _, c := range p { + w.buf = append(w.buf, c) + if c == '\n' && w.bol { + if err = w.wrap(); err != nil { + return 0, err + } + if _, err = w.w.Write(nl); err != nil { + return 0, err + } + } + w.bol = c == '\n' + } + return len(p), nil +} + +func (w *Writer) Flush() error { + return w.wrap() +} + +func (w *Writer) wrap() (err error) { + b := bytes.TrimSpace(w.buf) + w.buf = nil + if len(b) < 1 { + return nil + } + words := bytes.Split(bytes.Replace(b, nl, sp, -1), sp) + for _, line := range wrapWords(words, w.width) { + for i, word := range line { + if _, err = w.w.Write(word); err != nil { + return err + } + if i == len(line)-1 { + _, err = w.w.Write(nl) + } else { + _, err = w.w.Write(sp) + } + if err != nil { + return err + } + } + } + return nil +} + +// wrapWords is the low-level line-breaking algorithm, useful if you need more +// control over the details of the text wrapping process. For most uses, either +// Wrap or WrapBytes will be sufficient and more convenient. +// +// wrapWords splits a list of words into lines with minimal "raggedness", +// treating each byte as one unit, accounting for 1 unit between adjacent +// words on each line, and attempting to limit lines to lim units. Raggedness +// is the total error over all lines, where error is the square of the +// difference of the length of the line and lim. Too-long lines (which only +// happen when a single word is longer than lim units) have lim**2 penalty +// units added to the error. +// TODO(kr): count runes instead of bytes for width +func wrapWords(words [][]byte, lim int) [][][]byte { + pen := lim * lim + n := len(words) + length := make([][]int, n) + for i := 0; i < n; i++ { + length[i] = make([]int, n) + length[i][i] = len(words[i]) + for j := i + 1; j < n; j++ { + length[i][j] = length[i][j-1] + 1 + len(words[j]) + } + } + + nbrk := make([]int, n) + cost := make([]int, n) + for i := range cost { + cost[i] = math.MaxInt32 + } + for i := n - 1; i >= 0; i-- { + if length[i][n-1] <= lim { + cost[i] = 0 + nbrk[i] = n + } else { + for j := i + 1; j < n; j++ { + d := lim - length[i][j-1] + c := d*d + cost[j] + if length[i][j-1] > lim { + c += pen // too-long lines get a worse penalty + } + if c < cost[i] { + cost[i] = c + nbrk[i] = j + } + } + } + } + + var lines [][][]byte + i := 0 + for i < n { + lines = append(lines, words[i:nbrk[i]]) + i = nbrk[i] + } + return lines +}
diff --git a/wrapwriter/wrap_test.go b/wrapwriter/wrap_test.go new file mode 100644 index 0000000..cea2d0b --- /dev/null +++ b/wrapwriter/wrap_test.go
@@ -0,0 +1,41 @@ +package wrapwriter + +import ( + "bytes" + "testing" +) + +const text = "The quick brown fox jumps over the lazy dog." + +type Test struct { + n int + in string + exp string +} + +var ts = []Test{ + {24, text, "The quick brown fox\njumps over the lazy dog.\n"}, + {5, text, "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog.\n"}, + {500, text, "The quick brown fox jumps over the lazy dog.\n"}, + {9, "\na b c d e f\n", "\na b c d e\nf\n"}, + {9, "\na b c d e f\n\na b c d e f\n", "\na b c d e\nf\n\na b c d e\nf\n"}, +} + +func TestWrap(t *testing.T) { + for _, ts := range ts { + b := new(bytes.Buffer) + w := NewWriter(b, ts.n) + if _, err := w.Write([]byte(ts.in)); err != nil { + t.Error(err) + } + if err := w.Flush(); err != nil { + t.Error(err) + } + got := b.String() + if got != ts.exp { + t.Errorf("%q != %q", got, ts.exp) + t.Log(got) + t.Log(ts.exp) + } + } +}