xref: /aosp_15_r20/build/soong/ui/terminal/format.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// 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 {
26	colorize bool
27	format   string
28	quiet    bool
29	start    time.Time
30}
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.
36func newFormatter(colorize bool, format string, quiet bool) formatter {
37	return formatter{
38		colorize: colorize,
39		format:   format,
40		quiet:    quiet,
41		start:    time.Now(),
42	}
43}
44
45func (s formatter) message(level status.MsgLevel, message string) string {
46	if level >= status.ErrorLvl {
47		return fmt.Sprintf("%s %s", s.failedString(), message)
48	} 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
56func 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}
63func (s formatter) progress(counts status.Counts) string {
64	if s.format == "" {
65		output := fmt.Sprintf("[%3d%% %d/%d", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
66
67		if !counts.EstimatedTime.IsZero() {
68			output += fmt.Sprintf(" %s remaining", remainingTimeString(counts.EstimatedTime))
69		}
70		output += "] "
71		return output
72	}
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())
111		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			}
118		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 == "" {
132			ret = fmt.Sprintf("%s %s\n%s", s.failedString(), targets, result.Output)
133		} else {
134			ret = fmt.Sprintf("%s %s\n%s\n%s", s.failedString(), targets, result.Command, result.Output)
135		}
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}
146
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}
154