Skip to content

Commit

Permalink
Merge pull request #7 from gongo/namewidth
Browse files Browse the repository at this point in the history
Align output with filename
  • Loading branch information
gongo committed Feb 7, 2017
2 parents 2abe4bb + 2173396 commit d59f333
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 30 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -3,8 +3,9 @@ go:
- 1.5

install:
- go get github.com/ActiveState/tail
- go get github.com/hpcloud/tail
- go get github.com/mattn/go-colorable
- go get github.com/mattn/go-runewidth
- go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls
- if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
Expand Down
31 changes: 25 additions & 6 deletions tailer.go
Expand Up @@ -6,8 +6,11 @@ import (
"os"
"path/filepath"

"strings"

"github.com/hpcloud/tail"
"github.com/mattn/go-colorable"
"github.com/mattn/go-runewidth"
)

var (
Expand All @@ -21,7 +24,7 @@ var (
type Tailer struct {
*tail.Tail
colorCode int
maxWidth int
padding string
}

//NewTailers creates slice of Tailers from file names.
Expand Down Expand Up @@ -54,17 +57,26 @@ func newTailer(filename string, colorCode int, maxWidth int) (*Tailer, error) {
return nil, err
}

dispNameLength := displayFilenameLength(filename)

return &Tailer{
Tail: t,
colorCode: colorCode,
maxWidth: maxWidth,
padding: strings.Repeat(" ", maxWidth-dispNameLength),
}, nil
}

//Do formats, colors and writes to stdout appended lines when they happen, exiting on write error
func (t Tailer) Do() {
for line := range t.Lines {
_, err := fmt.Fprintf(colorableOutput, "\x1b[%dm%*s\x1b[0m: %s\n", t.colorCode, t.maxWidth, t.name(), line.Text)
_, err := fmt.Fprintf(
colorableOutput,
"\x1b[%dm%s%s\x1b[0m: %s\n",
t.colorCode,
t.padding,
t.name(),
line.Text,
)
if err != nil {
return
}
Expand All @@ -82,10 +94,17 @@ func getColorCode(index int) int {
func maximumNameLength(filenames []string) int {
max := 0
for _, name := range filenames {
base := filepath.Base(name)
if len(base) > max {
max = len(base)
if current := displayFilenameLength(name); current > max {
max = current
}
}
return max
}

func displayFilename(filename string) string {
return filepath.Base(filename)
}

func displayFilenameLength(filename string) int {
return runewidth.StringWidth(displayFilename(filename))
}
94 changes: 71 additions & 23 deletions tailer_test.go
Expand Up @@ -6,12 +6,16 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
"time"
)

var (
defaultOutput = colorableOutput
ansiColorRegex = regexp.MustCompile("^\x1b\\[[0-9]+m(.*)\x1b\\[0m: (.*)$")
defaultOutput = colorableOutput
)

func TestNewTailers(t *testing.T) {
Expand All @@ -30,10 +34,6 @@ func TestNewTailers(t *testing.T) {
t.Fatalf("Incorrect: tailers count expect(%d) actual(%d)", len(names), len(tailers))
}

if tailers[0].maxWidth != 19 { // len("very_very_long_base")
t.Fatalf("Incorrect: maximum name length: expect(%d) actual(%d)", 19, tailers[0].maxWidth)
}

if tailers[0].colorCode != ansiColorCodes[0] {
t.Fatal("Incorrect color code at 1st file")
}
Expand All @@ -47,44 +47,92 @@ func TestNewTailers(t *testing.T) {
}
}

func TestTailerDo(t *testing.T) {
func TestTailerDoForLinesAreNiceAligned(t *testing.T) {
// Stubbing output
output := new(bytes.Buffer)
colorableOutput = output
defer revertDefault()

// Create test file
testfile, err := ioutil.TempFile(os.TempDir(), "TestTailerDo")
dir, err := ioutil.TempDir("", "ninetail")
if err != nil {
t.Fatal(err)
}
defer os.Remove(testfile.Name())
defer os.RemoveAll(dir)

tailer, err := newTailer(testfile.Name(), 31, 10)
if err != nil {
t.Fatal(err)
bases := []string{
"test_tailer_do.log",
"short.txt",
"世界一かわいいよ.log",
}
files := make([]*os.File, len(bases))
for i, name := range bases {
// Create test file
filename := filepath.Join(dir, name)
f, err := os.Create(filename)
if err != nil {
t.Fatal(err)
}
files[i] = f
}

go tailer.Do()
var wg sync.WaitGroup
tailers := NewTailers(getFilenames(files))
for _, t := range tailers {
wg.Add(1)
go func(t *Tailer) {
t.Do()
wg.Done()
}(t)
}

// ^^;)
interval := time.Tick(100 * time.Millisecond)
<-interval
testfile.WriteString("foobar\n")
fmt.Fprintf(files[0], "foobar\n")
<-interval
fmt.Fprintf(files[2], "その作業安全ですかと言える社風\n")
<-interval
fmt.Fprintf(files[1], "PPAP = Perfect PHP As PHP\n")
<-interval
tailer.Stop()

expect := fmt.Sprintf(
"\x1b[%dm%*s\x1b[0m: foobar\n",
31, // eq 2nd args on newTailer()
10, // eq 3rd args on newTailer()
filepath.Base(testfile.Name()),
)
for _, t := range tailers {
t.Stop()
}
wg.Wait()

expect := ` test_tailer_do.log: foobar
世界一かわいいよ.log: その作業安全ですかと言える社風
short.txt: PPAP = Perfect PHP As PHP
`
actual := stripAnsiColorCode(output.String())

if expect != output.String() {
t.Fatal("Incorrect display")
if expect != actual {
t.Fatal("Incorrect align")
}
}

func revertDefault() {
colorableOutput = defaultOutput
}

func getFilenames(files []*os.File) []string {
filenames := make([]string, len(files))
for i, file := range files {
filenames[i] = file.Name()
}
return filenames
}

func stripAnsiColorCode(text string) string {
stripped := ""

for _, line := range strings.Split(text, "\n") {
s := ansiColorRegex.FindStringSubmatch(line)

if s != nil { // nil is empty line
stripped += s[1] + ": " + s[2] + "\n"
}
}

return stripped
}

0 comments on commit d59f333

Please sign in to comment.