mirror of
https://github.com/docker/compose.git
synced 2025-07-22 21:24:38 +02:00
compact TUI to monitor layers download progress
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
593c4263f3
commit
24ff098252
6
go.mod
6
go.mod
@ -86,8 +86,8 @@ require (
|
|||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.15.12 // indirect
|
github.com/klauspost/compress v1.15.12 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
@ -132,7 +132,7 @@ require (
|
|||||||
golang.org/x/crypto v0.2.0 // indirect
|
golang.org/x/crypto v0.2.0 // indirect
|
||||||
golang.org/x/net v0.4.0 // indirect
|
golang.org/x/net v0.4.0 // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
|
||||||
golang.org/x/sys v0.4.0 // indirect
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
golang.org/x/term v0.3.0 // indirect
|
golang.org/x/term v0.3.0 // indirect
|
||||||
golang.org/x/text v0.6.0 // indirect
|
golang.org/x/text v0.6.0 // indirect
|
||||||
golang.org/x/time v0.1.0 // indirect
|
golang.org/x/time v0.1.0 // indirect
|
||||||
|
13
go.sum
13
go.sum
@ -516,12 +516,12 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK
|
|||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
@ -1001,7 +1001,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -1021,8 +1020,8 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
@ -336,23 +336,53 @@ func isServiceImageToBuild(service types.ServiceConfig, services []types.Service
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PreparingPhase = "Preparing"
|
||||||
|
WaitingPhase = "Waiting"
|
||||||
|
PullingFsPhase = "Pulling fs layer"
|
||||||
|
DownloadingPhase = "Downloading"
|
||||||
|
DownloadCompletePhase = "Download complete"
|
||||||
|
ExtractingPhase = "Extracting"
|
||||||
|
VerifyingChecksumPhase = "Verifying Checksum"
|
||||||
|
AlreadyExistsPhase = "Already exists"
|
||||||
|
PullCompletePhase = "Pull complete"
|
||||||
|
)
|
||||||
|
|
||||||
func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
|
func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.Writer) {
|
||||||
if jm.ID == "" || jm.Progress == nil {
|
if jm.ID == "" || jm.Progress == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
text string
|
text string
|
||||||
status = progress.Working
|
total int64
|
||||||
|
percent int
|
||||||
|
current int64
|
||||||
|
status = progress.Working
|
||||||
)
|
)
|
||||||
|
|
||||||
text = jm.Progress.String()
|
text = jm.Progress.String()
|
||||||
|
|
||||||
if jm.Status == "Pull complete" ||
|
switch jm.Status {
|
||||||
jm.Status == "Already exists" ||
|
case PreparingPhase, WaitingPhase, PullingFsPhase:
|
||||||
strings.Contains(jm.Status, "Image is up to date") ||
|
percent = 0
|
||||||
|
case DownloadingPhase, ExtractingPhase, VerifyingChecksumPhase:
|
||||||
|
if jm.Progress != nil {
|
||||||
|
current = jm.Progress.Current
|
||||||
|
total = jm.Progress.Total
|
||||||
|
if jm.Progress.Total > 0 {
|
||||||
|
percent = int(jm.Progress.Current * 100 / jm.Progress.Total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case DownloadCompletePhase, AlreadyExistsPhase, PullCompletePhase:
|
||||||
|
status = progress.Done
|
||||||
|
percent = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(jm.Status, "Image is up to date") ||
|
||||||
strings.Contains(jm.Status, "Downloaded newer image") {
|
strings.Contains(jm.Status, "Downloaded newer image") {
|
||||||
status = progress.Done
|
status = progress.Done
|
||||||
|
percent = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
if jm.Error != nil {
|
if jm.Error != nil {
|
||||||
@ -363,6 +393,9 @@ func toPullProgressEvent(parent string, jm jsonmessage.JSONMessage, w progress.W
|
|||||||
w.Event(progress.Event{
|
w.Event(progress.Event{
|
||||||
ID: jm.ID,
|
ID: jm.ID,
|
||||||
ParentID: parent,
|
ParentID: parent,
|
||||||
|
Current: current,
|
||||||
|
Total: total,
|
||||||
|
Percent: percent,
|
||||||
Text: jm.Status,
|
Text: jm.Status,
|
||||||
Status: status,
|
Status: status,
|
||||||
StatusText: text,
|
StatusText: text,
|
||||||
|
@ -139,11 +139,15 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, w progress.W
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
text string
|
text string
|
||||||
status = progress.Working
|
status = progress.Working
|
||||||
|
total int64
|
||||||
|
current int64
|
||||||
|
percent int
|
||||||
)
|
)
|
||||||
if jm.Status == "Pull complete" || jm.Status == "Already exists" {
|
if jm.Status == "Pushed" || jm.Status == "Already exists" {
|
||||||
status = progress.Done
|
status = progress.Done
|
||||||
|
percent = 100
|
||||||
}
|
}
|
||||||
if jm.Error != nil {
|
if jm.Error != nil {
|
||||||
status = progress.Error
|
status = progress.Error
|
||||||
@ -151,11 +155,22 @@ func toPushProgressEvent(prefix string, jm jsonmessage.JSONMessage, w progress.W
|
|||||||
}
|
}
|
||||||
if jm.Progress != nil {
|
if jm.Progress != nil {
|
||||||
text = jm.Progress.String()
|
text = jm.Progress.String()
|
||||||
|
if jm.Progress.Total != 0 {
|
||||||
|
current = jm.Progress.Current
|
||||||
|
total = jm.Progress.Total
|
||||||
|
if jm.Progress.Total > 0 {
|
||||||
|
percent = int(jm.Progress.Current * 100 / jm.Progress.Total)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Event(progress.Event{
|
w.Event(progress.Event{
|
||||||
ID: fmt.Sprintf("Pushing %s: %s", prefix, jm.ID),
|
ID: fmt.Sprintf("Pushing %s: %s", prefix, jm.ID),
|
||||||
Text: jm.Status,
|
Text: jm.Status,
|
||||||
Status: status,
|
Status: status,
|
||||||
|
Current: current,
|
||||||
|
Total: total,
|
||||||
|
Percent: percent,
|
||||||
StatusText: text,
|
StatusText: text,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,20 +16,37 @@
|
|||||||
|
|
||||||
package progress
|
package progress
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/morikuni/aec"
|
||||||
|
)
|
||||||
|
|
||||||
// EventStatus indicates the status of an action
|
// EventStatus indicates the status of an action
|
||||||
type EventStatus int
|
type EventStatus int
|
||||||
|
|
||||||
|
func (s EventStatus) color() aec.ANSI {
|
||||||
|
switch s {
|
||||||
|
case Done:
|
||||||
|
return aec.GreenF
|
||||||
|
case Warning:
|
||||||
|
return aec.YellowF.With(aec.Bold)
|
||||||
|
case Error:
|
||||||
|
return aec.RedF.With(aec.Bold)
|
||||||
|
default:
|
||||||
|
return aec.DefaultF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Working means that the current task is working
|
// Working means that the current task is working
|
||||||
Working EventStatus = iota
|
Working EventStatus = iota
|
||||||
// Done means that the current task is done
|
// Done means that the current task is done
|
||||||
Done
|
Done
|
||||||
// Error means that the current task has errored
|
|
||||||
Error
|
|
||||||
// Warning means that the current task has warning
|
// Warning means that the current task has warning
|
||||||
Warning
|
Warning
|
||||||
|
// Error means that the current task has errored
|
||||||
|
Error
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event represents a progress event.
|
// Event represents a progress event.
|
||||||
@ -39,7 +56,10 @@ type Event struct {
|
|||||||
Text string
|
Text string
|
||||||
Status EventStatus
|
Status EventStatus
|
||||||
StatusText string
|
StatusText string
|
||||||
|
Current int64
|
||||||
|
Percent int
|
||||||
|
|
||||||
|
Total int64
|
||||||
startTime time.Time
|
startTime time.Time
|
||||||
endTime time.Time
|
endTime time.Time
|
||||||
spinner *spinner
|
spinner *spinner
|
||||||
@ -148,3 +168,22 @@ func (e *Event) stop() {
|
|||||||
e.endTime = time.Now()
|
e.endTime = time.Now()
|
||||||
e.spinner.Stop()
|
e.spinner.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
spinnerDone = aec.Apply("✔", aec.GreenF)
|
||||||
|
spinnerWarning = aec.Apply("!", aec.YellowF)
|
||||||
|
spinnerError = aec.Apply("✘", aec.RedF)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *Event) Spinner() any {
|
||||||
|
switch e.Status {
|
||||||
|
case Done:
|
||||||
|
return spinnerDone
|
||||||
|
case Warning:
|
||||||
|
return spinnerWarning
|
||||||
|
case Error:
|
||||||
|
return spinnerError
|
||||||
|
default:
|
||||||
|
return e.spinner.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -29,19 +28,21 @@ import (
|
|||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
|
|
||||||
"github.com/buger/goterm"
|
"github.com/buger/goterm"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/morikuni/aec"
|
"github.com/morikuni/aec"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ttyWriter struct {
|
type ttyWriter struct {
|
||||||
out io.Writer
|
out io.Writer
|
||||||
events map[string]Event
|
events map[string]Event
|
||||||
eventIDs []string
|
eventIDs []string
|
||||||
repeated bool
|
repeated bool
|
||||||
numLines int
|
numLines int
|
||||||
done chan bool
|
done chan bool
|
||||||
mtx *sync.Mutex
|
mtx *sync.Mutex
|
||||||
tailEvents []string
|
tailEvents []string
|
||||||
dryRun bool
|
dryRun bool
|
||||||
|
skipChildEvents bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *ttyWriter) Start(ctx context.Context) error {
|
func (w *ttyWriter) Start(ctx context.Context) error {
|
||||||
@ -85,6 +86,9 @@ func (w *ttyWriter) Event(e Event) {
|
|||||||
last.Status = e.Status
|
last.Status = e.Status
|
||||||
last.Text = e.Text
|
last.Text = e.Text
|
||||||
last.StatusText = e.StatusText
|
last.StatusText = e.StatusText
|
||||||
|
last.Total = e.Total
|
||||||
|
last.Current = e.Current
|
||||||
|
last.Percent = e.Percent
|
||||||
// allow set/unset of parent, but not swapping otherwise prompt is flickering
|
// allow set/unset of parent, but not swapping otherwise prompt is flickering
|
||||||
if last.ParentID == "" || e.ParentID == "" {
|
if last.ParentID == "" || e.ParentID == "" {
|
||||||
last.ParentID = e.ParentID
|
last.ParentID = e.ParentID
|
||||||
@ -163,9 +167,8 @@ func (w *ttyWriter) print() { //nolint:gocyclo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
skipChildEvents := false
|
|
||||||
if len(w.eventIDs) > goterm.Height()-2 {
|
if len(w.eventIDs) > goterm.Height()-2 {
|
||||||
skipChildEvents = true
|
w.skipChildEvents = true
|
||||||
}
|
}
|
||||||
numLines := 0
|
numLines := 0
|
||||||
for _, v := range w.eventIDs {
|
for _, v := range w.eventIDs {
|
||||||
@ -173,16 +176,16 @@ func (w *ttyWriter) print() { //nolint:gocyclo
|
|||||||
if event.ParentID != "" {
|
if event.ParentID != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
line := lineText(event, "", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
|
line := w.lineText(event, "", terminalWidth, statusPadding, w.dryRun)
|
||||||
fmt.Fprint(w.out, line)
|
fmt.Fprint(w.out, line)
|
||||||
numLines++
|
numLines++
|
||||||
for _, v := range w.eventIDs {
|
for _, v := range w.eventIDs {
|
||||||
ev := w.events[v]
|
ev := w.events[v]
|
||||||
if ev.ParentID == event.ID {
|
if ev.ParentID == event.ID {
|
||||||
if skipChildEvents {
|
if w.skipChildEvents {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
line := lineText(ev, " ", terminalWidth, statusPadding, runtime.GOOS != "windows", w.dryRun)
|
line := w.lineText(ev, " ", terminalWidth, statusPadding, w.dryRun)
|
||||||
fmt.Fprint(w.out, line)
|
fmt.Fprint(w.out, line)
|
||||||
numLines++
|
numLines++
|
||||||
}
|
}
|
||||||
@ -197,7 +200,7 @@ func (w *ttyWriter) print() { //nolint:gocyclo
|
|||||||
w.numLines = numLines
|
w.numLines = numLines
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineText(event Event, pad string, terminalWidth, statusPadding int, color bool, dryRun bool) string {
|
func (w *ttyWriter) lineText(event Event, pad string, terminalWidth, statusPadding int, dryRun bool) string {
|
||||||
endTime := time.Now()
|
endTime := time.Now()
|
||||||
if event.Status != Working {
|
if event.Status != Working {
|
||||||
endTime = event.startTime
|
endTime = event.startTime
|
||||||
@ -207,12 +210,38 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
|||||||
}
|
}
|
||||||
prefix := ""
|
prefix := ""
|
||||||
if dryRun {
|
if dryRun {
|
||||||
prefix = api.DRYRUN_PREFIX
|
prefix = aec.Apply(api.DRYRUN_PREFIX, aec.CyanF)
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed := endTime.Sub(event.startTime).Seconds()
|
elapsed := endTime.Sub(event.startTime).Seconds()
|
||||||
|
|
||||||
textLen := len(fmt.Sprintf("%s %s", event.ID, event.Text))
|
var (
|
||||||
|
total int64
|
||||||
|
current int64
|
||||||
|
completion []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, v := range w.eventIDs {
|
||||||
|
ev := w.events[v]
|
||||||
|
if ev.ParentID == event.ID {
|
||||||
|
total += ev.Total
|
||||||
|
current += ev.Current
|
||||||
|
completion = append(completion, percentChars[(len(percentChars)-1)*ev.Percent/100])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var txt string
|
||||||
|
if len(completion) > 0 {
|
||||||
|
txt = fmt.Sprintf("%s %s [%s] %7s/%-7s %s",
|
||||||
|
event.ID,
|
||||||
|
aec.Apply(fmt.Sprintf("%d layers", len(completion)), aec.YellowF),
|
||||||
|
aec.Apply(strings.Join(completion, ""), aec.GreenF, aec.Bold),
|
||||||
|
units.HumanSize(float64(current)), units.HumanSize(float64(total)),
|
||||||
|
event.Text)
|
||||||
|
} else {
|
||||||
|
txt = fmt.Sprintf("%s %s", event.ID, event.Text)
|
||||||
|
}
|
||||||
|
textLen := len(txt)
|
||||||
padding := statusPadding - textLen
|
padding := statusPadding - textLen
|
||||||
if padding < 0 {
|
if padding < 0 {
|
||||||
padding = 0
|
padding = 0
|
||||||
@ -225,31 +254,16 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
|||||||
if maxStatusLen > 0 && len(status) > maxStatusLen {
|
if maxStatusLen > 0 && len(status) > maxStatusLen {
|
||||||
status = status[:maxStatusLen] + "..."
|
status = status[:maxStatusLen] + "..."
|
||||||
}
|
}
|
||||||
text := fmt.Sprintf("%s %s%s %s %s%s %s",
|
text := fmt.Sprintf("%s %s%s %s%s %s",
|
||||||
pad,
|
pad,
|
||||||
event.spinner.String(),
|
event.Spinner(),
|
||||||
prefix,
|
prefix,
|
||||||
event.ID,
|
txt,
|
||||||
event.Text,
|
|
||||||
strings.Repeat(" ", padding),
|
strings.Repeat(" ", padding),
|
||||||
status,
|
aec.Apply(status, event.Status.color()),
|
||||||
)
|
)
|
||||||
timer := fmt.Sprintf("%.1fs\n", elapsed)
|
timer := fmt.Sprintf("%.1fs ", elapsed)
|
||||||
o := align(text, timer, terminalWidth)
|
o := align(text, aec.Apply(timer, aec.BlueF), terminalWidth)
|
||||||
|
|
||||||
if color {
|
|
||||||
color := aec.WhiteF
|
|
||||||
if event.Status == Done {
|
|
||||||
color = aec.BlueF
|
|
||||||
}
|
|
||||||
if event.Status == Error {
|
|
||||||
color = aec.RedF
|
|
||||||
}
|
|
||||||
if event.Status == Warning {
|
|
||||||
color = aec.YellowF
|
|
||||||
}
|
|
||||||
return aec.Apply(o, color)
|
|
||||||
}
|
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
@ -257,7 +271,7 @@ func lineText(event Event, pad string, terminalWidth, statusPadding int, color b
|
|||||||
func numDone(events map[string]Event) int {
|
func numDone(events map[string]Event) int {
|
||||||
i := 0
|
i := 0
|
||||||
for _, e := range events {
|
for _, e := range events {
|
||||||
if e.Status == Done {
|
if e.Status != Working {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -265,5 +279,32 @@ func numDone(events map[string]Event) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func align(l, r string, w int) string {
|
func align(l, r string, w int) string {
|
||||||
return fmt.Sprintf("%-[2]*[1]s %[3]s", l, w-len(r)-1, r)
|
ll := lenAnsi(l)
|
||||||
|
lr := lenAnsi(r)
|
||||||
|
pad := strings.Repeat(" ", w-ll-lr)
|
||||||
|
return fmt.Sprintf("%s%s%s\n", l, pad, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lenAnsi count of user-perceived characters in ANSI string.
|
||||||
|
func lenAnsi(s string) int {
|
||||||
|
length := 0
|
||||||
|
ansiCode := false
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '\x1b' {
|
||||||
|
ansiCode = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ansiCode && r == 'm' {
|
||||||
|
ansiCode = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ansiCode {
|
||||||
|
length++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return length
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
percentChars = strings.Split("⠀⡀⣀⣄⣤⣦⣶⣷⣿", "")
|
||||||
|
)
|
||||||
|
@ -41,23 +41,20 @@ func TestLineText(t *testing.T) {
|
|||||||
|
|
||||||
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
|
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
|
||||||
|
|
||||||
out := lineText(ev, "", 50, lineWidth, true, false)
|
out := tty().lineText(ev, "", 50, lineWidth, false)
|
||||||
assert.Equal(t, out, "\x1b[37m . id Text Status 0.0s\n\x1b[0m")
|
assert.Equal(t, out, " . id Text \x1b[39mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||||
|
|
||||||
out = lineText(ev, "", 50, lineWidth, false, false)
|
|
||||||
assert.Equal(t, out, " . id Text Status 0.0s\n")
|
|
||||||
|
|
||||||
ev.Status = Done
|
ev.Status = Done
|
||||||
out = lineText(ev, "", 50, lineWidth, true, false)
|
out = tty().lineText(ev, "", 50, lineWidth, false)
|
||||||
assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m")
|
assert.Equal(t, out, " \x1b[32m✔\x1b[0m id Text \x1b[32mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||||
|
|
||||||
ev.Status = Error
|
ev.Status = Error
|
||||||
out = lineText(ev, "", 50, lineWidth, true, false)
|
out = tty().lineText(ev, "", 50, lineWidth, false)
|
||||||
assert.Equal(t, out, "\x1b[31m . id Text Status 0.0s\n\x1b[0m")
|
assert.Equal(t, out, " \x1b[31m✘\x1b[0m id Text \x1b[31m\x1b[1mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||||
|
|
||||||
ev.Status = Warning
|
ev.Status = Warning
|
||||||
out = lineText(ev, "", 50, lineWidth, true, false)
|
out = tty().lineText(ev, "", 50, lineWidth, false)
|
||||||
assert.Equal(t, out, "\x1b[33m . id Text Status 0.0s\n\x1b[0m")
|
assert.Equal(t, out, " \x1b[33m!\x1b[0m id Text \x1b[33m\x1b[1mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLineTextSingleEvent(t *testing.T) {
|
func TestLineTextSingleEvent(t *testing.T) {
|
||||||
@ -75,8 +72,8 @@ func TestLineTextSingleEvent(t *testing.T) {
|
|||||||
|
|
||||||
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
|
lineWidth := len(fmt.Sprintf("%s %s", ev.ID, ev.Text))
|
||||||
|
|
||||||
out := lineText(ev, "", 50, lineWidth, true, false)
|
out := tty().lineText(ev, "", 50, lineWidth, false)
|
||||||
assert.Equal(t, out, "\x1b[34m . id Text Status 0.0s\n\x1b[0m")
|
assert.Equal(t, out, " \x1b[32m✔\x1b[0m id Text \x1b[32mStatus\x1b[0m \x1b[34m0.0s \x1b[0m\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestErrorEvent(t *testing.T) {
|
func TestErrorEvent(t *testing.T) {
|
||||||
@ -136,3 +133,13 @@ func TestWarningEvent(t *testing.T) {
|
|||||||
assert.Assert(t, ok)
|
assert.Assert(t, ok)
|
||||||
assert.Assert(t, event.endTime.After(time.Now().Add(-10*time.Second)))
|
assert.Assert(t, event.endTime.After(time.Now().Add(-10*time.Second)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tty() *ttyWriter {
|
||||||
|
tty := &ttyWriter{
|
||||||
|
eventIDs: []string{},
|
||||||
|
events: map[string]Event{},
|
||||||
|
done: make(chan bool),
|
||||||
|
mtx: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
return tty
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user