blob: 01f8b0d13ee7ab127a0b4145293fd58eac4ececf [file] [log] [blame]
Colin Crossce525352019-06-08 18:58:00 -07001// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package terminal
16
17import (
18 "fmt"
19 "strings"
20 "time"
21
22 "android/soong/ui/status"
23)
24
25type formatter struct {
cherokeeMetaf7119b52024-07-11 16:52:18 -070026 colorize bool
27 format string
28 quiet bool
29 start time.Time
Colin Crossce525352019-06-08 18:58:00 -070030}
31
32// newFormatter returns a formatter for formatting output to
33// the terminal in a format similar to Ninja.
34// format takes nearly all the same options as NINJA_STATUS.
35// %c is currently unsupported.
cherokeeMetaf7119b52024-07-11 16:52:18 -070036func newFormatter(colorize bool, format string, quiet bool) formatter {
Colin Crossce525352019-06-08 18:58:00 -070037 return formatter{
cherokeeMetaf7119b52024-07-11 16:52:18 -070038 colorize: colorize,
39 format: format,
40 quiet: quiet,
41 start: time.Now(),
Colin Crossce525352019-06-08 18:58:00 -070042 }
43}
44
45func (s formatter) message(level status.MsgLevel, message string) string {
46 if level >= status.ErrorLvl {
cherokeeMetaf7119b52024-07-11 16:52:18 -070047 return fmt.Sprintf("%s %s", s.failedString(), message)
Colin Crossce525352019-06-08 18:58:00 -070048 } else if level > status.StatusLvl {
49 return fmt.Sprintf("%s%s", level.Prefix(), message)
50 } else if level == status.StatusLvl {
51 return message
52 }
53 return ""
54}
55
Jeongik Cha3622b342023-11-27 11:00:52 +090056func remainingTimeString(t time.Time) string {
57 now := time.Now()
58 if t.After(now) {
59 return t.Sub(now).Round(time.Duration(time.Second)).String()
60 }
61 return time.Duration(0).Round(time.Duration(time.Second)).String()
62}
Colin Crossce525352019-06-08 18:58:00 -070063func (s formatter) progress(counts status.Counts) string {
64 if s.format == "" {
Jeongik Cha3622b342023-11-27 11:00:52 +090065 output := fmt.Sprintf("[%3d%% %d/%d", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
Jeongik Chab6d5ff52023-12-19 05:44:26 +000066
67 if !counts.EstimatedTime.IsZero() {
Jeongik Cha3622b342023-11-27 11:00:52 +090068 output += fmt.Sprintf(" %s remaining", remainingTimeString(counts.EstimatedTime))
69 }
70 output += "] "
71 return output
Colin Crossce525352019-06-08 18:58:00 -070072 }
73
74 buf := &strings.Builder{}
75 for i := 0; i < len(s.format); i++ {
76 c := s.format[i]
77 if c != '%' {
78 buf.WriteByte(c)
79 continue
80 }
81
82 i = i + 1
83 if i == len(s.format) {
84 buf.WriteByte(c)
85 break
86 }
87
88 c = s.format[i]
89 switch c {
90 case '%':
91 buf.WriteByte(c)
92 case 's':
93 fmt.Fprintf(buf, "%d", counts.StartedActions)
94 case 't':
95 fmt.Fprintf(buf, "%d", counts.TotalActions)
96 case 'r':
97 fmt.Fprintf(buf, "%d", counts.RunningActions)
98 case 'u':
99 fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
100 case 'f':
101 fmt.Fprintf(buf, "%d", counts.FinishedActions)
102 case 'o':
103 fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
104 case 'c':
105 // TODO: implement?
106 buf.WriteRune('?')
107 case 'p':
108 fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
109 case 'e':
110 fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
Jeongik Cha3622b342023-11-27 11:00:52 +0900111 case 'l':
112 if counts.EstimatedTime.IsZero() {
113 // No esitimated data
114 buf.WriteRune('?')
115 } else {
116 fmt.Fprintf(buf, "%s", remainingTimeString(counts.EstimatedTime))
117 }
Colin Crossce525352019-06-08 18:58:00 -0700118 default:
119 buf.WriteString("unknown placeholder '")
120 buf.WriteByte(c)
121 buf.WriteString("'")
122 }
123 }
124 return buf.String()
125}
126
127func (s formatter) result(result status.ActionResult) string {
128 var ret string
129 if result.Error != nil {
130 targets := strings.Join(result.Outputs, " ")
131 if s.quiet || result.Command == "" {
cherokeeMetaf7119b52024-07-11 16:52:18 -0700132 ret = fmt.Sprintf("%s %s\n%s", s.failedString(), targets, result.Output)
Colin Crossce525352019-06-08 18:58:00 -0700133 } else {
cherokeeMetaf7119b52024-07-11 16:52:18 -0700134 ret = fmt.Sprintf("%s %s\n%s\n%s", s.failedString(), targets, result.Command, result.Output)
Colin Crossce525352019-06-08 18:58:00 -0700135 }
136 } else if result.Output != "" {
137 ret = result.Output
138 }
139
140 if len(ret) > 0 && ret[len(ret)-1] != '\n' {
141 ret += "\n"
142 }
143
144 return ret
145}
cherokeeMetaf7119b52024-07-11 16:52:18 -0700146
147func (s formatter) failedString() string {
148 failed := "FAILED:"
149 if s.colorize {
150 failed = ansi.red() + ansi.bold() + failed + ansi.regular()
151 }
152 return failed
153}