@@ -20,11 +20,13 @@ import (
2020 "context"
2121 "fmt"
2222 "io"
23+ "iter"
2324 "strings"
2425 "sync"
2526 "time"
2627
2728 "github.com/buger/goterm"
29+ "github.com/docker/compose/v5/pkg/utils"
2830 "github.com/docker/go-units"
2931 "github.com/morikuni/aec"
3032
@@ -60,7 +62,8 @@ type ttyWriter struct {
6062
6163type task struct {
6264 ID string
63- parentID string
65+ parent string // the resource this task receives updates from - other parents will be ignored
66+ parents utils.Set [string ] // all resources to depend on this task
6467 startTime time.Time
6568 endTime time.Time
6669 text string
@@ -72,6 +75,64 @@ type task struct {
7275 spinner * Spinner
7376}
7477
78+ func newTask (e api.Resource ) task {
79+ t := task {
80+ ID : e .ID ,
81+ parents : utils .NewSet [string ](),
82+ startTime : time .Now (),
83+ text : e .Text ,
84+ details : e .Details ,
85+ status : e .Status ,
86+ current : e .Current ,
87+ percent : e .Percent ,
88+ total : e .Total ,
89+ spinner : NewSpinner (),
90+ }
91+ if e .ParentID != "" {
92+ t .parent = e .ParentID
93+ t .parents .Add (e .ParentID )
94+ }
95+ if e .Status == api .Done || e .Status == api .Error {
96+ t .stop ()
97+ }
98+ return t
99+ }
100+
101+ // update adjusts task state based on last received event
102+ func (t * task ) update (e api.Resource ) {
103+ if e .ParentID != "" {
104+ t .parents .Add (e .ParentID )
105+ // we may receive same event from distinct parents (typically: images sharing layers)
106+ // to avoid status to flicker, only accept updates from our first declared parent
107+ if t .parent != e .ParentID {
108+ return
109+ }
110+ }
111+
112+ // update task based on received event
113+ switch e .Status {
114+ case api .Done , api .Error , api .Warning :
115+ if t .status != e .Status {
116+ t .stop ()
117+ }
118+ case api .Working :
119+ t .hasMore ()
120+ }
121+ t .status = e .Status
122+ t .text = e .Text
123+ t .details = e .Details
124+ // progress can only go up
125+ if e .Total > t .total {
126+ t .total = e .Total
127+ }
128+ if e .Current > t .current {
129+ t .current = e .Current
130+ }
131+ if e .Percent > t .percent {
132+ t .percent = e .Percent
133+ }
134+ }
135+
75136func (t * task ) stop () {
76137 t .endTime = time .Now ()
77138 t .spinner .Stop ()
@@ -81,6 +142,15 @@ func (t *task) hasMore() {
81142 t .spinner .Restart ()
82143}
83144
145+ func (t * task ) Completed () bool {
146+ switch t .status {
147+ case api .Done , api .Error , api .Warning :
148+ return true
149+ default :
150+ return false
151+ }
152+ }
153+
84154func (w * ttyWriter ) Start (ctx context.Context , operation string ) {
85155 w .ticker = time .NewTicker (100 * time .Millisecond )
86156 w .operation = operation
@@ -137,48 +207,10 @@ func (w *ttyWriter) event(e api.Resource) {
137207 }
138208
139209 if last , ok := w .tasks [e .ID ]; ok {
140- switch e .Status {
141- case api .Done , api .Error , api .Warning :
142- if last .status != e .Status {
143- last .stop ()
144- }
145- case api .Working :
146- last .hasMore ()
147- }
148- last .status = e .Status
149- last .text = e .Text
150- last .details = e .Details
151- // progress can only go up
152- if e .Total > last .total {
153- last .total = e .Total
154- }
155- if e .Current > last .current {
156- last .current = e .Current
157- }
158- if e .Percent > last .percent {
159- last .percent = e .Percent
160- }
161- // allow set/unset of parent, but not swapping otherwise prompt is flickering
162- if last .parentID == "" || e .ParentID == "" {
163- last .parentID = e .ParentID
164- }
210+ last .update (e )
165211 w .tasks [e .ID ] = last
166212 } else {
167- t := task {
168- ID : e .ID ,
169- parentID : e .ParentID ,
170- startTime : time .Now (),
171- text : e .Text ,
172- details : e .Details ,
173- status : e .Status ,
174- current : e .Current ,
175- percent : e .Percent ,
176- total : e .Total ,
177- spinner : NewSpinner (),
178- }
179- if e .Status == api .Done || e .Status == api .Error {
180- t .stop ()
181- }
213+ t := newTask (e )
182214 w .tasks [e .ID ] = t
183215 w .ids = append (w .ids , e .ID )
184216 }
@@ -205,6 +237,28 @@ func (w *ttyWriter) printEvent(e api.Resource) {
205237 _ , _ = fmt .Fprintf (w .out , "%s %s %s\n " , e .ID , color (e .Text ), e .Details )
206238}
207239
240+ func (w * ttyWriter ) parentTasks () iter.Seq [task ] {
241+ return func (yield func (task ) bool ) {
242+ for _ , id := range w .ids { // iterate on ids to enforce a consistent order
243+ t := w .tasks [id ]
244+ if len (t .parents ) == 0 {
245+ yield (t )
246+ }
247+ }
248+ }
249+ }
250+
251+ func (w * ttyWriter ) childrenTasks (parent string ) iter.Seq [task ] {
252+ return func (yield func (task ) bool ) {
253+ for _ , id := range w .ids { // iterate on ids to enforce a consistent order
254+ t := w .tasks [id ]
255+ if t .parents .Has (parent ) {
256+ yield (t )
257+ }
258+ }
259+ }
260+ }
261+
208262func (w * ttyWriter ) print () {
209263 w .mtx .Lock ()
210264 defer w .mtx .Unlock ()
@@ -233,20 +287,25 @@ func (w *ttyWriter) print() {
233287 var statusPadding int
234288 for _ , t := range w .tasks {
235289 l := len (t .ID )
236- if t . parentID == "" && statusPadding < l {
290+ if len ( t . parents ) == 0 && statusPadding < l {
237291 statusPadding = l
238292 }
239293 }
240294
295+ skipChildEvents := len (w .tasks ) > goterm .Height ()- 2
241296 numLines := 0
242- for _ , id := range w .ids { // iterate on ids to enforce a consistent order
243- t := w .tasks [id ]
244- if t .parentID != "" {
245- continue
246- }
297+ for t := range w .parentTasks () {
247298 line := w .lineText (t , "" , terminalWidth , statusPadding , w .dryRun )
248299 _ , _ = fmt .Fprint (w .out , line )
249300 numLines ++
301+ if skipChildEvents {
302+ continue
303+ }
304+ for child := range w .childrenTasks (t .ID ) {
305+ line := w .lineText (child , " " , terminalWidth , statusPadding - 2 , w .dryRun )
306+ _ , _ = fmt .Fprint (w .out , line )
307+ numLines ++
308+ }
250309 }
251310 for i := numLines ; i < w .numLines ; i ++ {
252311 if numLines < goterm .Height ()- 2 {
@@ -281,18 +340,15 @@ func (w *ttyWriter) lineText(t task, pad string, terminalWidth, statusPadding in
281340
282341 // only show the aggregated progress while the root operation is in-progress
283342 if parent := t ; parent .status == api .Working {
284- for _ , id := range w .ids {
285- child := w .tasks [id ]
286- if child .parentID == parent .ID {
287- if child .status == api .Working && child .total == 0 {
288- // we don't have totals available for all the child events
289- // so don't show the total progress yet
290- hideDetails = true
291- }
292- total += child .total
293- current += child .current
294- completion = append (completion , percentChars [(len (percentChars )- 1 )* child .percent / 100 ])
343+ for child := range w .childrenTasks (parent .ID ) {
344+ if child .status == api .Working && child .total == 0 {
345+ // we don't have totals available for all the child events
346+ // so don't show the total progress yet
347+ hideDetails = true
295348 }
349+ total += child .total
350+ current += child .current
351+ completion = append (completion , percentChars [(len (percentChars )- 1 )* child .percent / 100 ])
296352 }
297353 }
298354
0 commit comments