column formatting pkg and cmd
diff --git a/colwriter/Readme b/colwriter/Readme
new file mode 100644
index 0000000..1c1f4e6
--- /dev/null
+++ b/colwriter/Readme
@@ -0,0 +1,5 @@
+Package colwriter provides a write filter that formats
+input lines in multiple columns.
+
+The package is a straightforward translation from
+/src/cmd/draw/mc.c in Plan 9 from User Space.
diff --git a/colwriter/column.go b/colwriter/column.go
new file mode 100644
index 0000000..c03f43a
--- /dev/null
+++ b/colwriter/column.go
@@ -0,0 +1,146 @@
+// Package colwriter provides a write filter that formats
+// input lines in multiple columns.
+//
+// The package is a straightforward translation from
+// /src/cmd/draw/mc.c in Plan 9 from User Space.
+package colwriter
+
+import (
+ "bytes"
+ "io"
+)
+
+const (
+ tab = 4
+)
+
+const (
+ // Print each input line ending in a colon ':' separately.
+ BreakOnColon uint = 1 << iota
+)
+
+// A Writer is a filter that arranges input lines in as many columns as will
+// fit in its width. Tab '\t' chars in the input are translated to sequences
+// of spaces ending at multiples of 4 positions.
+//
+// If BreakOnColon is set, each input line ending in a colon ':' is written
+// separately.
+//
+// The Writer assumes that all Unicode code points have the same width; this
+// may not be true in some fonts.
+type Writer struct {
+ w io.Writer
+ buf []byte
+ width int
+ flag uint
+}
+
+// NewWriter allocates and initializes a new Writer writing to w.
+// Parameter width controls the total number of characters on each line
+// across all columns.
+func NewWriter(w io.Writer, width int, flag uint) *Writer {
+ return &Writer{
+ w: w,
+ width: width,
+ flag: flag,
+ }
+}
+
+// Write writes p to the writer w. The only errors returned are ones
+// encountered while writing to the underlying output stream.
+func (w *Writer) Write(p []byte) (n int, err error) {
+ var linelen int
+ var lastWasColon bool
+ for i, c := range p {
+ w.buf = append(w.buf, c)
+ linelen++
+ if c == '\t' {
+ w.buf[len(w.buf)-1] = ' '
+ for linelen%tab != 0 {
+ w.buf = append(w.buf, ' ')
+ linelen++
+ }
+ }
+ if w.flag&BreakOnColon != 0 && c == ':' {
+ lastWasColon = true
+ } else if lastWasColon {
+ if c == '\n' {
+ pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'})
+ if pos < 0 {
+ pos = 0
+ }
+ line := w.buf[pos:]
+ w.buf = w.buf[:pos]
+ if err = w.columnate(); err != nil {
+ if len(line) < i {
+ return i - len(line), err
+ }
+ return 0, err
+ }
+ if n, err := w.w.Write(line); err != nil {
+ if r := len(line) - n; r < i {
+ return i - r, err
+ }
+ return 0, err
+ }
+ }
+ lastWasColon = false
+ }
+ if c == '\n' {
+ linelen = 0
+ }
+ }
+ return len(p), nil
+}
+
+// Flush should be called after the last call to Write to ensure that any data
+// buffered in the Writer is written to output.
+func (w *Writer) Flush() error {
+ return w.columnate()
+}
+
+func (w *Writer) columnate() error {
+ words := bytes.Split(w.buf, []byte{'\n'})
+ w.buf = nil
+ if len(words[len(words)-1]) == 0 {
+ words = words[:len(words)-1]
+ }
+ maxwidth := 0
+ for _, wd := range words {
+ if len(wd) > maxwidth {
+ maxwidth = len(wd)
+ }
+ }
+ maxwidth++ // space char
+ wordsPerLine := w.width / maxwidth
+ if wordsPerLine <= 0 {
+ wordsPerLine = 1
+ }
+ nlines := (len(words) + wordsPerLine - 1) / wordsPerLine
+ for i := 0; i < nlines; i++ {
+ col := 0
+ endcol := 0
+ for j := i; j < len(words); j += nlines {
+ endcol += maxwidth
+ _, err := w.w.Write(words[j])
+ if err != nil {
+ return err
+ }
+ col += len(words[j])
+ if j+nlines < len(words) {
+ for col < endcol {
+ _, err := w.w.Write([]byte{' '})
+ if err != nil {
+ return err
+ }
+ col++
+ }
+ }
+ }
+ _, err := w.w.Write([]byte{'\n'})
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/colwriter/column_test.go b/colwriter/column_test.go
new file mode 100644
index 0000000..c60b691
--- /dev/null
+++ b/colwriter/column_test.go
@@ -0,0 +1,101 @@
+package colwriter
+
+import (
+ "bytes"
+ "testing"
+)
+
+var s = `
+.git
+.gitignore
+.godir
+Procfile:
+README.md
+api.go
+apps.go
+auth.go
+darwin.go
+data.go
+dyno.go:
+env.go
+git.go
+help.go
+hkdist
+linux.go
+ls.go
+main.go
+plugin.go
+run.go
+scale.go
+ssh.go
+tail.go
+term
+unix.go
+update.go
+version.go
+windows.go
+`[1:]
+
+var de = `
+.git README.md darwin.go git.go ls.go scale.go unix.go
+.gitignore api.go data.go help.go main.go ssh.go update.go
+.godir apps.go dyno.go: hkdist plugin.go tail.go version.go
+Procfile: auth.go env.go linux.go run.go term windows.go
+`[1:]
+
+var ce = `
+.git .gitignore .godir
+
+Procfile:
+README.md api.go apps.go auth.go darwin.go data.go
+
+dyno.go:
+env.go hkdist main.go scale.go term version.go
+git.go linux.go plugin.go ssh.go unix.go windows.go
+help.go ls.go run.go tail.go update.go
+`[1:]
+
+func TestColumns(t *testing.T) {
+ b := new(bytes.Buffer)
+ w := NewWriter(b, 80, 0)
+ if _, err := w.Write([]byte(s)); err != nil {
+ t.Error(err)
+ }
+ if err := w.Flush(); err != nil {
+ t.Error(err)
+ }
+ g := string(b.Bytes())
+ if de != g {
+ t.Log("\n" + de)
+ t.Log("\n" + g)
+ t.Errorf("%q != %q", de, g)
+ }
+}
+
+func TestColumnsColon(t *testing.T) {
+ b := new(bytes.Buffer)
+ w := NewWriter(b, 80, BreakOnColon)
+ if _, err := w.Write([]byte(s)); err != nil {
+ t.Error(err)
+ }
+ if err := w.Flush(); err != nil {
+ t.Error(err)
+ }
+ g := string(b.Bytes())
+ if ce != g {
+ t.Log("\n" + ce)
+ t.Log("\n" + g)
+ t.Errorf("%q != %q", ce, g)
+ }
+}
+
+func TestColWriterFlushEmpty(t *testing.T) {
+ b := new(bytes.Buffer)
+ w := NewWriter(b, 80, 0)
+ if err := w.Flush(); err != nil {
+ t.Error(err)
+ }
+ if g := b.Bytes(); len(g) != 0 {
+ t.Errorf("expected empty output, got %#v", g)
+ }
+}
diff --git a/mc/Readme b/mc/Readme
new file mode 100644
index 0000000..519ddc0
--- /dev/null
+++ b/mc/Readme
@@ -0,0 +1,9 @@
+Command mc prints in multiple columns.
+
+ Usage: mc [-] [-N] [file...]
+
+Mc splits the input into as many columns as will fit in N
+print positions. If the output is a tty, the default N is
+the number of characters in a terminal line; otherwise the
+default N is 80. Under option - each input line ending in
+a colon ':' is printed separately.
diff --git a/mc/mc.go b/mc/mc.go
new file mode 100644
index 0000000..ab823d7
--- /dev/null
+++ b/mc/mc.go
@@ -0,0 +1,61 @@
+// Command mc prints in multiple columns.
+//
+// Usage: mc [-] [-N] [file...]
+//
+// Mc splits the input into as many columns as will fit in N
+// print positions. If the output is a tty, the default N is
+// the number of characters in a terminal line; otherwise the
+// default N is 80. Under option - each input line ending in
+// a colon ':' is printed separately.
+package main
+
+import (
+ "github.com/kr/pty"
+ "github.com/kr/text/colwriter"
+ "io"
+ "log"
+ "os"
+ "strconv"
+)
+
+func main() {
+ var width int
+ var flag uint
+ args := os.Args[1:]
+ for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' {
+ if len(args[0]) > 1 {
+ width, _ = strconv.Atoi(args[0][1:])
+ } else {
+ flag |= colwriter.BreakOnColon
+ }
+ args = args[1:]
+ }
+ if width < 1 {
+ _, width, _ = pty.Getsize(os.Stdout)
+ }
+ if width < 1 {
+ width = 80
+ }
+
+ w := colwriter.NewWriter(os.Stdout, width, 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 *colwriter.Writer, r io.Reader) {
+ if _, err := io.Copy(w, r); err != nil {
+ log.Println(err)
+ }
+ if err := w.Flush(); err != nil {
+ log.Println(err)
+ }
+}