progress: refactor multireader and add status display
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>docker-18.09
parent
8e7d0904ce
commit
13f3a45b1d
|
@ -2,11 +2,10 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/tonistiigi/buildkit_poc/client"
|
||||
"github.com/tonistiigi/buildkit_poc/util/progress/progressui"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
@ -31,15 +30,16 @@ func build(clicontext *cli.Context) error {
|
|||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
for s := range ch {
|
||||
for _, v := range s.Vertexes {
|
||||
log.Print(spew.Sdump(v))
|
||||
}
|
||||
for _, v := range s.Statuses {
|
||||
log.Print(spew.Sdump(v))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return progressui.DisplaySolveStatus(ctx, ch)
|
||||
// for s := range ch {
|
||||
// for _, v := range s.Vertexes {
|
||||
// log.Print(spew.Sdump(v))
|
||||
// }
|
||||
// for _, v := range s.Statuses {
|
||||
// log.Print(spew.Sdump(v))
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
})
|
||||
|
||||
return eg.Wait()
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
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: "/"})
|
||||
mod2 := mod1.Run(llb.Meta{Args: []string{"/bin/sh", "-c", "echo foo > /bar"}, Cwd: "/"})
|
||||
alpine := llb.Image("docker.io/library/alpine:latest")
|
||||
|
|
|
@ -2,6 +2,7 @@ package solver
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -93,19 +94,17 @@ func (j *job) pipe(ctx context.Context, ch chan *client.SolveStatus) error {
|
|||
for {
|
||||
p, err := pr.Read(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
ss := &client.SolveStatus{}
|
||||
for _, p := range p {
|
||||
switch v := p.Sys.(type) {
|
||||
case client.Vertex:
|
||||
ss := &client.SolveStatus{Vertexes: []*client.Vertex{&v}}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case ch <- ss:
|
||||
}
|
||||
ss.Vertexes = append(ss.Vertexes, &v)
|
||||
|
||||
case progress.Status:
|
||||
i := strings.Index(p.ID, ".")
|
||||
vs := &client.VertexStatus{
|
||||
|
@ -118,7 +117,9 @@ func (j *job) pipe(ctx context.Context, ch chan *client.SolveStatus) error {
|
|||
Started: v.Started,
|
||||
Completed: v.Completed,
|
||||
}
|
||||
ss := &client.SolveStatus{Statuses: []*client.VertexStatus{vs}}
|
||||
ss.Statuses = append(ss.Statuses, vs)
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
|
@ -126,7 +127,6 @@ func (j *job) pipe(ctx context.Context, ch chan *client.SolveStatus) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func walk(op *opVertex) chan *opVertex {
|
||||
cache := make(map[digest.Digest]struct{})
|
||||
|
|
|
@ -233,11 +233,7 @@ func (g *opVertex) name() string {
|
|||
case *pb.Op_Source:
|
||||
return op.Source.Identifier
|
||||
case *pb.Op_Exec:
|
||||
name := strings.Join(op.Exec.Meta.Args, " ")
|
||||
if len(name) > 22 { // TODO: const
|
||||
name = name[:20] + "..."
|
||||
}
|
||||
return name
|
||||
return strings.Join(op.Exec.Meta.Args, " ")
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package progress
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -54,9 +55,18 @@ func (mr *MultiReader) handle() error {
|
|||
for {
|
||||
p, err := mr.main.Read(context.TODO())
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
mr.mu.Lock()
|
||||
for w := range mr.writers {
|
||||
w.Done()
|
||||
}
|
||||
mr.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
mr.mu.Lock()
|
||||
for _, p := range p {
|
||||
for w, c := range mr.writers {
|
||||
if p == nil {
|
||||
c()
|
||||
|
@ -64,9 +74,7 @@ func (mr *MultiReader) handle() error {
|
|||
w.write(*p)
|
||||
}
|
||||
}
|
||||
}
|
||||
mr.mu.Unlock()
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,9 @@ package progress
|
|||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -35,13 +36,12 @@ type ProgressWriter interface {
|
|||
}
|
||||
|
||||
type ProgressReader interface {
|
||||
Read(context.Context) (*Progress, error)
|
||||
Read(context.Context) ([]*Progress, error)
|
||||
}
|
||||
|
||||
type Progress struct {
|
||||
ID string
|
||||
Timestamp time.Time
|
||||
Done bool
|
||||
Sys interface{}
|
||||
}
|
||||
|
||||
|
@ -58,27 +58,37 @@ type progressReader struct {
|
|||
ctx context.Context
|
||||
cond *sync.Cond
|
||||
mu sync.Mutex
|
||||
handles []*streamHandle
|
||||
writers map[*progressWriter]struct{}
|
||||
dirty map[string]*Progress
|
||||
}
|
||||
|
||||
type streamHandle struct {
|
||||
pw *progressWriter
|
||||
lastP *Progress
|
||||
}
|
||||
// type progressState struct {
|
||||
// mu sync.Mutex
|
||||
//
|
||||
// }
|
||||
//
|
||||
// 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) {
|
||||
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) {
|
||||
func (pr *progressReader) Read(ctx context.Context) ([]*Progress, error) {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
go func() {
|
||||
|
@ -96,27 +106,29 @@ func (pr *progressReader) Read(ctx context.Context) (*Progress, error) {
|
|||
return nil, ctx.Err()
|
||||
default:
|
||||
}
|
||||
open := false
|
||||
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.
|
||||
p, ok := sh.next()
|
||||
if ok {
|
||||
pr.mu.Unlock()
|
||||
return p, nil
|
||||
}
|
||||
if sh.lastP == nil || !sh.lastP.Done {
|
||||
open = true
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-pr.ctx.Done():
|
||||
dmap := pr.dirty
|
||||
open := len(pr.writers) > 0
|
||||
if len(dmap) == 0 {
|
||||
if !open {
|
||||
pr.mu.Unlock()
|
||||
return nil, nil
|
||||
return nil, io.EOF
|
||||
}
|
||||
pr.cond.Wait()
|
||||
default:
|
||||
pr.cond.Wait()
|
||||
continue
|
||||
}
|
||||
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():
|
||||
return
|
||||
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())
|
||||
pr := &progressReader{
|
||||
ctx: ctx,
|
||||
writers: make(map[*progressWriter]struct{}),
|
||||
dirty: make(map[string]*Progress),
|
||||
}
|
||||
pr.cond = sync.NewCond(&pr.mu)
|
||||
go func() {
|
||||
|
@ -166,12 +180,8 @@ func newWriter(pw *progressWriter, name string) *progressWriter {
|
|||
|
||||
type progressWriter struct {
|
||||
id string
|
||||
lastP atomic.Value
|
||||
done bool
|
||||
reader *progressReader
|
||||
|
||||
byKey map[string]atomic.Value
|
||||
items []atomic.Value
|
||||
}
|
||||
|
||||
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 {
|
||||
if p.Done {
|
||||
pw.done = true
|
||||
}
|
||||
pw.lastP.Store(&p)
|
||||
pw.reader.mu.Lock()
|
||||
pw.reader.dirty[pw.id+"."+p.ID] = &p
|
||||
pw.reader.mu.Unlock()
|
||||
pw.reader.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pw *progressWriter) Done() error {
|
||||
var p Progress
|
||||
lastP := pw.lastP.Load().(*Progress)
|
||||
p.ID = pw.id
|
||||
p.Timestamp = time.Now()
|
||||
if lastP != nil {
|
||||
p = *lastP
|
||||
if p.Done {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
p.Sys = lastP.Sys
|
||||
}
|
||||
p.Done = true
|
||||
pw.reader.mu.Lock()
|
||||
delete(pw.reader.writers, pw)
|
||||
pw.reader.mu.Unlock()
|
||||
pw.reader.cond.Broadcast()
|
||||
pw.done = true
|
||||
return pw.write(p)
|
||||
return nil
|
||||
}
|
||||
|
||||
type noOpWriter struct{}
|
||||
|
|
|
@ -3,6 +3,7 @@ package progress
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -32,7 +33,6 @@ func TestProgress(t *testing.T) {
|
|||
|
||||
assert.True(t, len(trace.items) > 5)
|
||||
assert.True(t, len(trace.items) <= 7)
|
||||
assert.Equal(t, trace.items[len(trace.items)-1].Done, true)
|
||||
}
|
||||
|
||||
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) <= 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) {
|
||||
|
@ -115,18 +108,18 @@ func reduceCalc(ctx context.Context, total int) (int, error) {
|
|||
}
|
||||
|
||||
type trace struct {
|
||||
items []Progress
|
||||
items []*Progress
|
||||
}
|
||||
|
||||
func saveProgress(ctx context.Context, pr ProgressReader, t *trace) error {
|
||||
for {
|
||||
p, err := pr.Read(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
t.items = append(t.items, *p)
|
||||
return err
|
||||
}
|
||||
t.items = append(t.items, p...)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -27,3 +27,4 @@ google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
|
|||
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
|
||||
|
||||
github.com/urfave/cli d70f47eeca3afd795160003bc6e28b001d60c67c
|
||||
github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b
|
|
@ -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.
|
|
@ -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)
|
||||
|
||||
|
|
@ -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")
|
||||
}
|
|
@ -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, ""))
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue