progress: refactor multireader and add status display

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
docker-18.09
Tonis Tiigi 2017-06-16 15:49:03 -07:00
parent 8e7d0904ce
commit 13f3a45b1d
15 changed files with 1310 additions and 127 deletions

View File

@ -2,11 +2,10 @@ package main
import ( import (
"context" "context"
"log"
"os" "os"
"github.com/davecgh/go-spew/spew"
"github.com/tonistiigi/buildkit_poc/client" "github.com/tonistiigi/buildkit_poc/client"
"github.com/tonistiigi/buildkit_poc/util/progress/progressui"
"github.com/urfave/cli" "github.com/urfave/cli"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
@ -31,15 +30,16 @@ func build(clicontext *cli.Context) error {
}) })
eg.Go(func() error { eg.Go(func() error {
for s := range ch { return progressui.DisplaySolveStatus(ctx, ch)
for _, v := range s.Vertexes { // for s := range ch {
log.Print(spew.Sdump(v)) // for _, v := range s.Vertexes {
} // log.Print(spew.Sdump(v))
for _, v := range s.Statuses { // }
log.Print(spew.Sdump(v)) // for _, v := range s.Statuses {
} // log.Print(spew.Sdump(v))
} // }
return nil // }
// return nil
}) })
return eg.Wait() return eg.Wait()

View File

@ -7,7 +7,7 @@ import (
) )
func main() { func main() {
busybox := llb.Image("docker.io/library/busybox:latest") busybox := llb.Image("docker.io/library/redis:latest")
mod1 := busybox.Run(llb.Meta{Args: []string{"/bin/sleep", "1"}, Cwd: "/"}) mod1 := busybox.Run(llb.Meta{Args: []string{"/bin/sleep", "1"}, Cwd: "/"})
mod2 := mod1.Run(llb.Meta{Args: []string{"/bin/sh", "-c", "echo foo > /bar"}, Cwd: "/"}) mod2 := mod1.Run(llb.Meta{Args: []string{"/bin/sh", "-c", "echo foo > /bar"}, Cwd: "/"})
alpine := llb.Image("docker.io/library/alpine:latest") alpine := llb.Image("docker.io/library/alpine:latest")

View File

@ -2,6 +2,7 @@ package solver
import ( import (
"context" "context"
"io"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -93,19 +94,17 @@ func (j *job) pipe(ctx context.Context, ch chan *client.SolveStatus) error {
for { for {
p, err := pr.Read(ctx) p, err := pr.Read(ctx)
if err != nil { if err != nil {
return err if err == io.EOF {
}
if p == nil {
return nil return nil
} }
return err
}
ss := &client.SolveStatus{}
for _, p := range p {
switch v := p.Sys.(type) { switch v := p.Sys.(type) {
case client.Vertex: case client.Vertex:
ss := &client.SolveStatus{Vertexes: []*client.Vertex{&v}} ss.Vertexes = append(ss.Vertexes, &v)
select {
case <-ctx.Done():
return ctx.Err()
case ch <- ss:
}
case progress.Status: case progress.Status:
i := strings.Index(p.ID, ".") i := strings.Index(p.ID, ".")
vs := &client.VertexStatus{ vs := &client.VertexStatus{
@ -118,14 +117,15 @@ func (j *job) pipe(ctx context.Context, ch chan *client.SolveStatus) error {
Started: v.Started, Started: v.Started,
Completed: v.Completed, Completed: v.Completed,
} }
ss := &client.SolveStatus{Statuses: []*client.VertexStatus{vs}} ss.Statuses = append(ss.Statuses, vs)
}
}
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
case ch <- ss: case ch <- ss:
} }
} }
}
} }
func walk(op *opVertex) chan *opVertex { func walk(op *opVertex) chan *opVertex {

View File

@ -233,11 +233,7 @@ func (g *opVertex) name() string {
case *pb.Op_Source: case *pb.Op_Source:
return op.Source.Identifier return op.Source.Identifier
case *pb.Op_Exec: case *pb.Op_Exec:
name := strings.Join(op.Exec.Meta.Args, " ") return strings.Join(op.Exec.Meta.Args, " ")
if len(name) > 22 { // TODO: const
name = name[:20] + "..."
}
return name
default: default:
return "unknown" return "unknown"
} }

View File

@ -2,6 +2,7 @@ package progress
import ( import (
"context" "context"
"io"
"sync" "sync"
) )
@ -54,9 +55,18 @@ func (mr *MultiReader) handle() error {
for { for {
p, err := mr.main.Read(context.TODO()) p, err := mr.main.Read(context.TODO())
if err != nil { if err != nil {
if err == io.EOF {
mr.mu.Lock()
for w := range mr.writers {
w.Done()
}
mr.mu.Unlock()
return nil
}
return err return err
} }
mr.mu.Lock() mr.mu.Lock()
for _, p := range p {
for w, c := range mr.writers { for w, c := range mr.writers {
if p == nil { if p == nil {
c() c()
@ -64,9 +74,7 @@ func (mr *MultiReader) handle() error {
w.write(*p) w.write(*p)
} }
} }
mr.mu.Unlock()
if p == nil {
return nil
} }
mr.mu.Unlock()
} }
} }

View File

@ -2,8 +2,9 @@ package progress
import ( import (
"context" "context"
"io"
"sort"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -35,13 +36,12 @@ type ProgressWriter interface {
} }
type ProgressReader interface { type ProgressReader interface {
Read(context.Context) (*Progress, error) Read(context.Context) ([]*Progress, error)
} }
type Progress struct { type Progress struct {
ID string ID string
Timestamp time.Time Timestamp time.Time
Done bool
Sys interface{} Sys interface{}
} }
@ -58,27 +58,37 @@ type progressReader struct {
ctx context.Context ctx context.Context
cond *sync.Cond cond *sync.Cond
mu sync.Mutex mu sync.Mutex
handles []*streamHandle writers map[*progressWriter]struct{}
dirty map[string]*Progress
} }
type streamHandle struct { // type progressState struct {
pw *progressWriter // mu sync.Mutex
lastP *Progress //
} // }
//
// type streamHandle struct {
// pw *progressWriter
// lastP *Progress
// }
//
// func (sh *streamHandle) next() (*Progress, bool) {
// lasti := sh.pw.lastP.Load()
// if lasti != nil {
// last := lasti.(*Progress)
// if last != sh.lastP {
// sh.lastP = last
// return last, true
// }
// }
// return nil, false
// }
//
// func (pr *progressReader) write(id string, p *Progress) {
//
// }
func (sh *streamHandle) next() (*Progress, bool) { func (pr *progressReader) Read(ctx context.Context) ([]*Progress, error) {
lasti := sh.pw.lastP.Load()
if lasti != nil {
last := lasti.(*Progress)
if last != sh.lastP {
sh.lastP = last
return last, true
}
}
return nil, false
}
func (pr *progressReader) Read(ctx context.Context) (*Progress, error) {
done := make(chan struct{}) done := make(chan struct{})
defer close(done) defer close(done)
go func() { go func() {
@ -96,27 +106,29 @@ func (pr *progressReader) Read(ctx context.Context) (*Progress, error) {
return nil, ctx.Err() return nil, ctx.Err()
default: default:
} }
open := false dmap := pr.dirty
for _, sh := range pr.handles { // could be more efficient but unlikely that this array will be very big, maybe random ordering? at least remove the completed handlers. open := len(pr.writers) > 0
p, ok := sh.next() if len(dmap) == 0 {
if ok {
pr.mu.Unlock()
return p, nil
}
if sh.lastP == nil || !sh.lastP.Done {
open = true
}
}
select {
case <-pr.ctx.Done():
if !open { if !open {
pr.mu.Unlock() pr.mu.Unlock()
return nil, nil return nil, io.EOF
} }
pr.cond.Wait() pr.cond.Wait()
default: continue
pr.cond.Wait()
} }
pr.dirty = make(map[string]*Progress)
pr.mu.Unlock()
out := make([]*Progress, 0, len(dmap))
for _, p := range dmap {
out = append(out, p)
}
sort.Slice(out, func(i, j int) bool {
return out[i].Timestamp.Before(out[j].Timestamp)
})
return out, nil
} }
} }
@ -128,7 +140,7 @@ func (pr *progressReader) append(pw *progressWriter) {
case <-pr.ctx.Done(): case <-pr.ctx.Done():
return return
default: default:
pr.handles = append(pr.handles, &streamHandle{pw: pw}) pr.writers[pw] = struct{}{}
} }
} }
@ -136,6 +148,8 @@ func pipe() (*progressReader, *progressWriter, func()) {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
pr := &progressReader{ pr := &progressReader{
ctx: ctx, ctx: ctx,
writers: make(map[*progressWriter]struct{}),
dirty: make(map[string]*Progress),
} }
pr.cond = sync.NewCond(&pr.mu) pr.cond = sync.NewCond(&pr.mu)
go func() { go func() {
@ -166,12 +180,8 @@ func newWriter(pw *progressWriter, name string) *progressWriter {
type progressWriter struct { type progressWriter struct {
id string id string
lastP atomic.Value
done bool done bool
reader *progressReader reader *progressReader
byKey map[string]atomic.Value
items []atomic.Value
} }
func (pw *progressWriter) Write(s interface{}) error { func (pw *progressWriter) Write(s interface{}) error {
@ -186,30 +196,20 @@ func (pw *progressWriter) Write(s interface{}) error {
} }
func (pw *progressWriter) write(p Progress) error { func (pw *progressWriter) write(p Progress) error {
if p.Done { pw.reader.mu.Lock()
pw.done = true pw.reader.dirty[pw.id+"."+p.ID] = &p
} pw.reader.mu.Unlock()
pw.lastP.Store(&p)
pw.reader.cond.Broadcast() pw.reader.cond.Broadcast()
return nil return nil
} }
func (pw *progressWriter) Done() error { func (pw *progressWriter) Done() error {
var p Progress pw.reader.mu.Lock()
lastP := pw.lastP.Load().(*Progress) delete(pw.reader.writers, pw)
p.ID = pw.id pw.reader.mu.Unlock()
p.Timestamp = time.Now() pw.reader.cond.Broadcast()
if lastP != nil {
p = *lastP
if p.Done {
return nil
}
} else {
p.Sys = lastP.Sys
}
p.Done = true
pw.done = true pw.done = true
return pw.write(p) return nil
} }
type noOpWriter struct{} type noOpWriter struct{}

View File

@ -3,6 +3,7 @@ package progress
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"testing" "testing"
"time" "time"
@ -32,7 +33,6 @@ func TestProgress(t *testing.T) {
assert.True(t, len(trace.items) > 5) assert.True(t, len(trace.items) > 5)
assert.True(t, len(trace.items) <= 7) assert.True(t, len(trace.items) <= 7)
assert.Equal(t, trace.items[len(trace.items)-1].Done, true)
} }
func TestProgressNested(t *testing.T) { func TestProgressNested(t *testing.T) {
@ -53,13 +53,6 @@ func TestProgressNested(t *testing.T) {
assert.True(t, len(trace.items) > 9) // usually 14 assert.True(t, len(trace.items) > 9) // usually 14
assert.True(t, len(trace.items) <= 15) assert.True(t, len(trace.items) <= 15)
streams := 0
for _, t := range trace.items {
if t.Done {
streams++
}
}
assert.Equal(t, streams, 4)
} }
func calc(ctx context.Context, total int, name string) (int, error) { func calc(ctx context.Context, total int, name string) (int, error) {
@ -115,18 +108,18 @@ func reduceCalc(ctx context.Context, total int) (int, error) {
} }
type trace struct { type trace struct {
items []Progress items []*Progress
} }
func saveProgress(ctx context.Context, pr ProgressReader, t *trace) error { func saveProgress(ctx context.Context, pr ProgressReader, t *trace) error {
for { for {
p, err := pr.Read(ctx) p, err := pr.Read(ctx)
if err != nil { if err != nil {
return err if err == io.EOF {
}
if p == nil {
return nil return nil
} }
t.items = append(t.items, *p) return err
}
t.items = append(t.items, p...)
} }
} }

View File

@ -0,0 +1,200 @@
package progressui
import (
"context"
"fmt"
"time"
"github.com/containerd/console"
"github.com/docker/go-units"
"github.com/morikuni/aec"
digest "github.com/opencontainers/go-digest"
"github.com/tonistiigi/buildkit_poc/client"
)
func DisplaySolveStatus(ctx context.Context, ch chan *client.SolveStatus) error {
disp := &display{c: console.Current()}
t := newTrace()
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
var done bool
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
case ss, ok := <-ch:
if ok {
t.update(ss) // rate limit
} else {
done = true
}
}
disp.print(t.displayInfo(-1))
if done {
return nil
}
}
}
type displayInfo struct {
startTime time.Time
jobs []job
countTotal int
countCompleted int
}
type job struct {
startTime *time.Time
completedTime *time.Time
name string
}
type trace struct {
localTimeDiff time.Duration
vertexes []*vertex
byDigest map[digest.Digest]*vertex
}
type vertex struct {
*client.Vertex
statuses []*status
byID map[string]*status
}
type status struct {
*client.VertexStatus
}
func newTrace() *trace {
return &trace{
byDigest: make(map[digest.Digest]*vertex),
}
}
func (t *trace) update(s *client.SolveStatus) {
for _, v := range s.Vertexes {
prev, ok := t.byDigest[v.Digest]
if !ok {
t.byDigest[v.Digest] = &vertex{
byID: make(map[string]*status),
}
}
if v.Started != nil && (prev == nil || prev.Started == nil) {
if t.localTimeDiff == 0 {
t.localTimeDiff = time.Since(*v.Started)
}
t.vertexes = append(t.vertexes, t.byDigest[v.Digest])
}
t.byDigest[v.Digest].Vertex = v
}
for _, s := range s.Statuses {
v, ok := t.byDigest[s.Vertex]
if !ok {
continue // shouldn't happen
}
prev, ok := v.byID[s.ID]
if !ok {
v.byID[s.ID] = &status{VertexStatus: s}
}
if s.Started != nil && (prev == nil || prev.Started == nil) {
v.statuses = append(v.statuses, v.byID[s.ID])
}
v.byID[s.ID].VertexStatus = s
}
}
func (t *trace) displayInfo(maxRows int) (d displayInfo) {
d.startTime = time.Now()
if t.localTimeDiff != 0 {
d.startTime = (*t.vertexes[0].Started).Add(t.localTimeDiff)
}
d.countTotal = len(t.byDigest)
for _, v := range t.byDigest {
if v.Completed != nil {
d.countCompleted++
}
}
for _, v := range t.vertexes {
j := job{
startTime: addTime(v.Started, t.localTimeDiff),
completedTime: addTime(v.Completed, t.localTimeDiff),
name: v.Name,
}
d.jobs = append(d.jobs, j)
for _, s := range v.statuses {
j := job{
startTime: addTime(s.Started, t.localTimeDiff),
completedTime: addTime(s.Completed, t.localTimeDiff),
name: "=> " + s.ID,
}
if s.Total != 0 {
j.name += " " + units.HumanSize(float64(s.Current)) + " / " + units.HumanSize(float64(s.Current))
}
d.jobs = append(d.jobs, j)
}
}
return d
}
func addTime(tm *time.Time, d time.Duration) *time.Time {
if tm == nil {
return nil
}
t := (*tm).Add(d)
return &t
}
type display struct {
c console.Console
lineCount int
repeated bool
}
func (disp *display) print(d displayInfo) {
width := 60
size, err := disp.c.Size()
if err == nil {
width = int(size.Width)
}
_ = width
b := aec.EmptyBuilder
for i := 0; i <= disp.lineCount; i++ {
b = b.EraseLine(aec.EraseModes.All).Up(1)
}
if !disp.repeated {
b = b.Down(1)
}
b = b.EraseLine(aec.EraseModes.All)
disp.repeated = true
fmt.Print(b.Column(0).ANSI)
statusStr := ""
if d.countCompleted > 0 && d.countCompleted == d.countTotal {
statusStr = "FINISHED"
}
fmt.Printf("[+] Building %.1fs (%d/%d) %s\n", time.Since(d.startTime).Seconds(), d.countCompleted, d.countTotal, statusStr)
for _, j := range d.jobs {
endTime := time.Now()
if j.completedTime != nil {
endTime = *j.completedTime
}
dt := endTime.Sub(*j.startTime).Seconds()
if dt < 0.05 {
dt = 0
}
out := fmt.Sprintf(" => %s %.1fs\n", j.name, dt)
if j.completedTime != nil {
out = aec.Apply(out, aec.BlueF)
}
fmt.Print(out)
}
disp.lineCount = len(d.jobs)
}

View File

@ -27,3 +27,4 @@ google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
github.com/urfave/cli d70f47eeca3afd795160003bc6e28b001d60c67c github.com/urfave/cli d70f47eeca3afd795160003bc6e28b001d60c67c
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b

21
vendor/github.com/morikuni/aec/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Taihei Morikuni
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

178
vendor/github.com/morikuni/aec/README.md generated vendored Normal file
View File

@ -0,0 +1,178 @@
# aec
[![GoDoc](https://godoc.org/github.com/morikuni/aec?status.svg)](https://godoc.org/github.com/morikuni/aec)
Go wrapper for ANSI escape code.
## Install
```bash
go get github.com/morikuni/aec
```
## Features
ANSI escape codes depend on terminal environment.
Some of these features may not work.
Check supported Font-Style/Font-Color features with [checkansi](./checkansi).
[Wikipedia](https://en.wikipedia.org/wiki/ANSI_escape_code) for more detail.
### Cursor
- `Up(n)`
- `Down(n)`
- `Right(n)`
- `Left(n)`
- `NextLine(n)`
- `PreviousLine(n)`
- `Column(col)`
- `Position(row, col)`
- `Save`
- `Restore`
- `Hide`
- `Show`
- `Report`
### Erase
- `EraseDisplay(mode)`
- `EraseLine(mode)`
### Scroll
- `ScrollUp(n)`
- `ScrollDown(n)`
### Font Style
- `Bold`
- `Faint`
- `Italic`
- `Underline`
- `BlinkSlow`
- `BlinkRapid`
- `Inverse`
- `Conceal`
- `CrossOut`
- `Frame`
- `Encircle`
- `Overline`
### Font Color
Foreground color.
- `DefaultF`
- `BlackF`
- `RedF`
- `GreenF`
- `YellowF`
- `BlueF`
- `MagentaF`
- `CyanF`
- `WhiteF`
- `LightBlackF`
- `LightRedF`
- `LightGreenF`
- `LightYellowF`
- `LightBlueF`
- `LightMagentaF`
- `LightCyanF`
- `LightWhiteF`
- `Color3BitF(color)`
- `Color8BitF(color)`
- `FullColorF(r, g, b)`
Background color.
- `DefaultB`
- `BlackB`
- `RedB`
- `GreenB`
- `YellowB`
- `BlueB`
- `MagentaB`
- `CyanB`
- `WhiteB`
- `LightBlackB`
- `LightRedB`
- `LightGreenB`
- `LightYellowB`
- `LightBlueB`
- `LightMagentaB`
- `LightCyanB`
- `LightWhiteB`
- `Color3BitB(color)`
- `Color8BitB(color)`
- `FullColorB(r, g, b)`
### Color Converter
24bit RGB color to ANSI color.
- `NewRGB3Bit(r, g, b)`
- `NewRGB8Bit(r, g, b)`
### Builder
To mix these features.
```go
custom := aec.EmptyBuilder.Right(2).RGB8BitF(128, 255, 64).RedB().ANSI
custom.Apply("Hello World")
```
## Usage
1. Create ANSI by `aec.XXX().With(aec.YYY())` or `aec.EmptyBuilder.XXX().YYY().ANSI`
2. Print ANSI by `fmt.Print(ansi, "some string", aec.Reset)` or `fmt.Print(ansi.Apply("some string"))`
`aec.Reset` should be added when using font style or font color features.
## Example
Simple progressbar.
![sample](./sample.gif)
```go
package main
import (
"fmt"
"strings"
"time"
"github.com/morikuni/aec"
)
func main() {
const n = 20
builder := aec.EmptyBuilder
up2 := aec.Up(2)
col := aec.Column(n + 2)
bar := aec.Color8BitF(aec.NewRGB8Bit(64, 255, 64))
label := builder.LightRedF().Underline().With(col).Right(1).ANSI
// for up2
fmt.Println()
fmt.Println()
for i := 0; i <= n; i++ {
fmt.Print(up2)
fmt.Println(label.Apply(fmt.Sprint(i, "/", n)))
fmt.Print("[")
fmt.Print(bar.Apply(strings.Repeat("=", i)))
fmt.Println(col.Apply("]"))
time.Sleep(100 * time.Millisecond)
}
}
```
## License
[MIT](./LICENSE)

137
vendor/github.com/morikuni/aec/aec.go generated vendored Normal file
View File

@ -0,0 +1,137 @@
package aec
import "fmt"
// EraseMode is listed in a variable EraseModes.
type EraseMode uint
var (
// EraseModes is a list of EraseMode.
EraseModes struct {
// All erase all.
All EraseMode
// Head erase to head.
Head EraseMode
// Tail erase to tail.
Tail EraseMode
}
// Save saves the cursor position.
Save ANSI
// Restore restores the cursor position.
Restore ANSI
// Hide hides the cursor.
Hide ANSI
// Show shows the cursor.
Show ANSI
// Report reports the cursor position.
Report ANSI
)
// Up moves up the cursor.
func Up(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dA", n))
}
// Down moves down the cursor.
func Down(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dB", n))
}
// Right moves right the cursor.
func Right(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dC", n))
}
// Left moves left the cursor.
func Left(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dD", n))
}
// NextLine moves down the cursor to head of a line.
func NextLine(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dE", n))
}
// PreviousLine moves up the cursor to head of a line.
func PreviousLine(n uint) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dF", n))
}
// Column set the cursor position to a given column.
func Column(col uint) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dG", col))
}
// Position set the cursor position to a given absolute position.
func Position(row, col uint) ANSI {
return newAnsi(fmt.Sprintf(esc+"%d;%dH", row, col))
}
// EraseDisplay erases display by given EraseMode.
func EraseDisplay(m EraseMode) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dJ", m))
}
// EraseLine erases lines by given EraseMode.
func EraseLine(m EraseMode) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dK", m))
}
// ScrollUp scrolls up the page.
func ScrollUp(n int) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dS", n))
}
// ScrollDown scrolls down the page.
func ScrollDown(n int) ANSI {
if n == 0 {
return empty
}
return newAnsi(fmt.Sprintf(esc+"%dT", n))
}
func init() {
EraseModes = struct {
All EraseMode
Head EraseMode
Tail EraseMode
}{
Tail: 0,
Head: 1,
All: 2,
}
Save = newAnsi(esc + "s")
Restore = newAnsi(esc + "u")
Hide = newAnsi(esc + "?25l")
Show = newAnsi(esc + "?25h")
Report = newAnsi(esc + "6n")
}

59
vendor/github.com/morikuni/aec/ansi.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
package aec
import (
"fmt"
"strings"
)
const esc = "\x1b["
// Reset resets SGR effect.
const Reset string = "\x1b[0m"
var empty = newAnsi("")
// ANSI represents ANSI escape code.
type ANSI interface {
fmt.Stringer
// With adapts given ANSIs.
With(...ANSI) ANSI
// Apply wraps given string in ANSI.
Apply(string) string
}
type ansiImpl string
func newAnsi(s string) *ansiImpl {
r := ansiImpl(s)
return &r
}
func (a *ansiImpl) With(ansi ...ANSI) ANSI {
return concat(append([]ANSI{a}, ansi...))
}
func (a *ansiImpl) Apply(s string) string {
return a.String() + s + Reset
}
func (a *ansiImpl) String() string {
return string(*a)
}
// Apply wraps given string in ANSIs.
func Apply(s string, ansi ...ANSI) string {
if len(ansi) == 0 {
return s
}
return concat(ansi).Apply(s)
}
func concat(ansi []ANSI) ANSI {
strs := make([]string, 0, len(ansi))
for _, p := range ansi {
strs = append(strs, p.String())
}
return newAnsi(strings.Join(strs, ""))
}

388
vendor/github.com/morikuni/aec/builder.go generated vendored Normal file
View File

@ -0,0 +1,388 @@
package aec
// Builder is a lightweight syntax to construct customized ANSI.
type Builder struct {
ANSI ANSI
}
// EmptyBuilder is an initialized Builder.
var EmptyBuilder *Builder
// NewBuilder creates a Builder from existing ANSI.
func NewBuilder(a ...ANSI) *Builder {
return &Builder{concat(a)}
}
// With is a syntax for With.
func (builder *Builder) With(a ...ANSI) *Builder {
return NewBuilder(builder.ANSI.With(a...))
}
// Up is a syntax for Up.
func (builder *Builder) Up(n uint) *Builder {
return builder.With(Up(n))
}
// Down is a syntax for Down.
func (builder *Builder) Down(n uint) *Builder {
return builder.With(Down(n))
}
// Right is a syntax for Right.
func (builder *Builder) Right(n uint) *Builder {
return builder.With(Right(n))
}
// Left is a syntax for Left.
func (builder *Builder) Left(n uint) *Builder {
return builder.With(Left(n))
}
// NextLine is a syntax for NextLine.
func (builder *Builder) NextLine(n uint) *Builder {
return builder.With(NextLine(n))
}
// PreviousLine is a syntax for PreviousLine.
func (builder *Builder) PreviousLine(n uint) *Builder {
return builder.With(PreviousLine(n))
}
// Column is a syntax for Column.
func (builder *Builder) Column(col uint) *Builder {
return builder.With(Column(col))
}
// Position is a syntax for Position.
func (builder *Builder) Position(row, col uint) *Builder {
return builder.With(Position(row, col))
}
// EraseDisplay is a syntax for EraseDisplay.
func (builder *Builder) EraseDisplay(m EraseMode) *Builder {
return builder.With(EraseDisplay(m))
}
// EraseLine is a syntax for EraseLine.
func (builder *Builder) EraseLine(m EraseMode) *Builder {
return builder.With(EraseLine(m))
}
// ScrollUp is a syntax for ScrollUp.
func (builder *Builder) ScrollUp(n int) *Builder {
return builder.With(ScrollUp(n))
}
// ScrollDown is a syntax for ScrollDown.
func (builder *Builder) ScrollDown(n int) *Builder {
return builder.With(ScrollDown(n))
}
// Save is a syntax for Save.
func (builder *Builder) Save() *Builder {
return builder.With(Save)
}
// Restore is a syntax for Restore.
func (builder *Builder) Restore() *Builder {
return builder.With(Restore)
}
// Hide is a syntax for Hide.
func (builder *Builder) Hide() *Builder {
return builder.With(Hide)
}
// Show is a syntax for Show.
func (builder *Builder) Show() *Builder {
return builder.With(Show)
}
// Report is a syntax for Report.
func (builder *Builder) Report() *Builder {
return builder.With(Report)
}
// Bold is a syntax for Bold.
func (builder *Builder) Bold() *Builder {
return builder.With(Bold)
}
// Faint is a syntax for Faint.
func (builder *Builder) Faint() *Builder {
return builder.With(Faint)
}
// Italic is a syntax for Italic.
func (builder *Builder) Italic() *Builder {
return builder.With(Italic)
}
// Underline is a syntax for Underline.
func (builder *Builder) Underline() *Builder {
return builder.With(Underline)
}
// BlinkSlow is a syntax for BlinkSlow.
func (builder *Builder) BlinkSlow() *Builder {
return builder.With(BlinkSlow)
}
// BlinkRapid is a syntax for BlinkRapid.
func (builder *Builder) BlinkRapid() *Builder {
return builder.With(BlinkRapid)
}
// Inverse is a syntax for Inverse.
func (builder *Builder) Inverse() *Builder {
return builder.With(Inverse)
}
// Conceal is a syntax for Conceal.
func (builder *Builder) Conceal() *Builder {
return builder.With(Conceal)
}
// CrossOut is a syntax for CrossOut.
func (builder *Builder) CrossOut() *Builder {
return builder.With(CrossOut)
}
// BlackF is a syntax for BlackF.
func (builder *Builder) BlackF() *Builder {
return builder.With(BlackF)
}
// RedF is a syntax for RedF.
func (builder *Builder) RedF() *Builder {
return builder.With(RedF)
}
// GreenF is a syntax for GreenF.
func (builder *Builder) GreenF() *Builder {
return builder.With(GreenF)
}
// YellowF is a syntax for YellowF.
func (builder *Builder) YellowF() *Builder {
return builder.With(YellowF)
}
// BlueF is a syntax for BlueF.
func (builder *Builder) BlueF() *Builder {
return builder.With(BlueF)
}
// MagentaF is a syntax for MagentaF.
func (builder *Builder) MagentaF() *Builder {
return builder.With(MagentaF)
}
// CyanF is a syntax for CyanF.
func (builder *Builder) CyanF() *Builder {
return builder.With(CyanF)
}
// WhiteF is a syntax for WhiteF.
func (builder *Builder) WhiteF() *Builder {
return builder.With(WhiteF)
}
// DefaultF is a syntax for DefaultF.
func (builder *Builder) DefaultF() *Builder {
return builder.With(DefaultF)
}
// BlackB is a syntax for BlackB.
func (builder *Builder) BlackB() *Builder {
return builder.With(BlackB)
}
// RedB is a syntax for RedB.
func (builder *Builder) RedB() *Builder {
return builder.With(RedB)
}
// GreenB is a syntax for GreenB.
func (builder *Builder) GreenB() *Builder {
return builder.With(GreenB)
}
// YellowB is a syntax for YellowB.
func (builder *Builder) YellowB() *Builder {
return builder.With(YellowB)
}
// BlueB is a syntax for BlueB.
func (builder *Builder) BlueB() *Builder {
return builder.With(BlueB)
}
// MagentaB is a syntax for MagentaB.
func (builder *Builder) MagentaB() *Builder {
return builder.With(MagentaB)
}
// CyanB is a syntax for CyanB.
func (builder *Builder) CyanB() *Builder {
return builder.With(CyanB)
}
// WhiteB is a syntax for WhiteB.
func (builder *Builder) WhiteB() *Builder {
return builder.With(WhiteB)
}
// DefaultB is a syntax for DefaultB.
func (builder *Builder) DefaultB() *Builder {
return builder.With(DefaultB)
}
// Frame is a syntax for Frame.
func (builder *Builder) Frame() *Builder {
return builder.With(Frame)
}
// Encircle is a syntax for Encircle.
func (builder *Builder) Encircle() *Builder {
return builder.With(Encircle)
}
// Overline is a syntax for Overline.
func (builder *Builder) Overline() *Builder {
return builder.With(Overline)
}
// LightBlackF is a syntax for LightBlueF.
func (builder *Builder) LightBlackF() *Builder {
return builder.With(LightBlackF)
}
// LightRedF is a syntax for LightRedF.
func (builder *Builder) LightRedF() *Builder {
return builder.With(LightRedF)
}
// LightGreenF is a syntax for LightGreenF.
func (builder *Builder) LightGreenF() *Builder {
return builder.With(LightGreenF)
}
// LightYellowF is a syntax for LightYellowF.
func (builder *Builder) LightYellowF() *Builder {
return builder.With(LightYellowF)
}
// LightBlueF is a syntax for LightBlueF.
func (builder *Builder) LightBlueF() *Builder {
return builder.With(LightBlueF)
}
// LightMagentaF is a syntax for LightMagentaF.
func (builder *Builder) LightMagentaF() *Builder {
return builder.With(LightMagentaF)
}
// LightCyanF is a syntax for LightCyanF.
func (builder *Builder) LightCyanF() *Builder {
return builder.With(LightCyanF)
}
// LightWhiteF is a syntax for LightWhiteF.
func (builder *Builder) LightWhiteF() *Builder {
return builder.With(LightWhiteF)
}
// LightBlackB is a syntax for LightBlackB.
func (builder *Builder) LightBlackB() *Builder {
return builder.With(LightBlackB)
}
// LightRedB is a syntax for LightRedB.
func (builder *Builder) LightRedB() *Builder {
return builder.With(LightRedB)
}
// LightGreenB is a syntax for LightGreenB.
func (builder *Builder) LightGreenB() *Builder {
return builder.With(LightGreenB)
}
// LightYellowB is a syntax for LightYellowB.
func (builder *Builder) LightYellowB() *Builder {
return builder.With(LightYellowB)
}
// LightBlueB is a syntax for LightBlueB.
func (builder *Builder) LightBlueB() *Builder {
return builder.With(LightBlueB)
}
// LightMagentaB is a syntax for LightMagentaB.
func (builder *Builder) LightMagentaB() *Builder {
return builder.With(LightMagentaB)
}
// LightCyanB is a syntax for LightCyanB.
func (builder *Builder) LightCyanB() *Builder {
return builder.With(LightCyanB)
}
// LightWhiteB is a syntax for LightWhiteB.
func (builder *Builder) LightWhiteB() *Builder {
return builder.With(LightWhiteB)
}
// Color3BitF is a syntax for Color3BitF.
func (builder *Builder) Color3BitF(c RGB3Bit) *Builder {
return builder.With(Color3BitF(c))
}
// Color3BitB is a syntax for Color3BitB.
func (builder *Builder) Color3BitB(c RGB3Bit) *Builder {
return builder.With(Color3BitB(c))
}
// Color8BitF is a syntax for Color8BitF.
func (builder *Builder) Color8BitF(c RGB8Bit) *Builder {
return builder.With(Color8BitF(c))
}
// Color8BitB is a syntax for Color8BitB.
func (builder *Builder) Color8BitB(c RGB8Bit) *Builder {
return builder.With(Color8BitB(c))
}
// FullColorF is a syntax for FullColorF.
func (builder *Builder) FullColorF(r, g, b uint8) *Builder {
return builder.With(FullColorF(r, g, b))
}
// FullColorB is a syntax for FullColorB.
func (builder *Builder) FullColorB(r, g, b uint8) *Builder {
return builder.With(FullColorB(r, g, b))
}
// RGB3BitF is a syntax for Color3BitF with NewRGB3Bit.
func (builder *Builder) RGB3BitF(r, g, b uint8) *Builder {
return builder.Color3BitF(NewRGB3Bit(r, g, b))
}
// RGB3BitB is a syntax for Color3BitB with NewRGB3Bit.
func (builder *Builder) RGB3BitB(r, g, b uint8) *Builder {
return builder.Color3BitB(NewRGB3Bit(r, g, b))
}
// RGB8BitF is a syntax for Color8BitF with NewRGB8Bit.
func (builder *Builder) RGB8BitF(r, g, b uint8) *Builder {
return builder.Color8BitF(NewRGB8Bit(r, g, b))
}
// RGB8BitB is a syntax for Color8BitB with NewRGB8Bit.
func (builder *Builder) RGB8BitB(r, g, b uint8) *Builder {
return builder.Color8BitB(NewRGB8Bit(r, g, b))
}
func init() {
EmptyBuilder = &Builder{empty}
}

202
vendor/github.com/morikuni/aec/sgr.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
package aec
import (
"fmt"
)
// RGB3Bit is a 3bit RGB color.
type RGB3Bit uint8
// RGB8Bit is a 8bit RGB color.
type RGB8Bit uint8
func newSGR(n uint) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dm", n))
}
// NewRGB3Bit create a RGB3Bit from given RGB.
func NewRGB3Bit(r, g, b uint8) RGB3Bit {
return RGB3Bit((r >> 7) | ((g >> 6) & 0x2) | ((b >> 5) & 0x4))
}
// NewRGB8Bit create a RGB8Bit from given RGB.
func NewRGB8Bit(r, g, b uint8) RGB8Bit {
return RGB8Bit(16 + 36*(r/43) + 6*(g/43) + b/43)
}
// Color3BitF set the foreground color of text.
func Color3BitF(c RGB3Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dm", c+30))
}
// Color3BitB set the background color of text.
func Color3BitB(c RGB3Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"%dm", c+40))
}
// Color8BitF set the foreground color of text.
func Color8BitF(c RGB8Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"38;5;%dm", c))
}
// Color8BitB set the background color of text.
func Color8BitB(c RGB8Bit) ANSI {
return newAnsi(fmt.Sprintf(esc+"48;5;%dm", c))
}
// FullColorF set the foreground color of text.
func FullColorF(r, g, b uint8) ANSI {
return newAnsi(fmt.Sprintf(esc+"38;2;%d;%d;%dm", r, g, b))
}
// FullColorB set the foreground color of text.
func FullColorB(r, g, b uint8) ANSI {
return newAnsi(fmt.Sprintf(esc+"48;2;%d;%d;%dm", r, g, b))
}
// Style
var (
// Bold set the text style to bold or increased intensity.
Bold ANSI
// Faint set the text style to faint.
Faint ANSI
// Italic set the text style to italic.
Italic ANSI
// Underline set the text style to underline.
Underline ANSI
// BlinkSlow set the text style to slow blink.
BlinkSlow ANSI
// BlinkRapid set the text style to rapid blink.
BlinkRapid ANSI
// Inverse swap the foreground color and background color.
Inverse ANSI
// Conceal set the text style to conceal.
Conceal ANSI
// CrossOut set the text style to crossed out.
CrossOut ANSI
// Frame set the text style to framed.
Frame ANSI
// Encircle set the text style to encircled.
Encircle ANSI
// Overline set the text style to overlined.
Overline ANSI
)
// Foreground color of text.
var (
// DefaultF is the default color of foreground.
DefaultF ANSI
// Normal color
BlackF ANSI
RedF ANSI
GreenF ANSI
YellowF ANSI
BlueF ANSI
MagentaF ANSI
CyanF ANSI
WhiteF ANSI
// Light color
LightBlackF ANSI
LightRedF ANSI
LightGreenF ANSI
LightYellowF ANSI
LightBlueF ANSI
LightMagentaF ANSI
LightCyanF ANSI
LightWhiteF ANSI
)
// Background color of text.
var (
// DefaultB is the default color of background.
DefaultB ANSI
// Normal color
BlackB ANSI
RedB ANSI
GreenB ANSI
YellowB ANSI
BlueB ANSI
MagentaB ANSI
CyanB ANSI
WhiteB ANSI
// Light color
LightBlackB ANSI
LightRedB ANSI
LightGreenB ANSI
LightYellowB ANSI
LightBlueB ANSI
LightMagentaB ANSI
LightCyanB ANSI
LightWhiteB ANSI
)
func init() {
Bold = newSGR(1)
Faint = newSGR(2)
Italic = newSGR(3)
Underline = newSGR(4)
BlinkSlow = newSGR(5)
BlinkRapid = newSGR(6)
Inverse = newSGR(7)
Conceal = newSGR(8)
CrossOut = newSGR(9)
BlackF = newSGR(30)
RedF = newSGR(31)
GreenF = newSGR(32)
YellowF = newSGR(33)
BlueF = newSGR(34)
MagentaF = newSGR(35)
CyanF = newSGR(36)
WhiteF = newSGR(37)
DefaultF = newSGR(39)
BlackB = newSGR(40)
RedB = newSGR(41)
GreenB = newSGR(42)
YellowB = newSGR(43)
BlueB = newSGR(44)
MagentaB = newSGR(45)
CyanB = newSGR(46)
WhiteB = newSGR(47)
DefaultB = newSGR(49)
Frame = newSGR(51)
Encircle = newSGR(52)
Overline = newSGR(53)
LightBlackF = newSGR(90)
LightRedF = newSGR(91)
LightGreenF = newSGR(92)
LightYellowF = newSGR(93)
LightBlueF = newSGR(94)
LightMagentaF = newSGR(95)
LightCyanF = newSGR(96)
LightWhiteF = newSGR(97)
LightBlackB = newSGR(100)
LightRedB = newSGR(101)
LightGreenB = newSGR(102)
LightYellowB = newSGR(103)
LightBlueB = newSGR(104)
LightMagentaB = newSGR(105)
LightCyanB = newSGR(106)
LightWhiteB = newSGR(107)
}