blob: 241a1ddf74b5fe1c6067be3e2a4c0f2d0fe541e6 [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 {
26 format string
27 quiet bool
28 start time.Time
29}
30
31// newFormatter returns a formatter for formatting output to
32// the terminal in a format similar to Ninja.
33// format takes nearly all the same options as NINJA_STATUS.
34// %c is currently unsupported.
Jeongik Chab6d5ff52023-12-19 05:44:26 +000035func newFormatter(format string, quiet bool) formatter {
Colin Crossce525352019-06-08 18:58:00 -070036 return formatter{
37 format: format,
38 quiet: quiet,
39 start: time.Now(),
40 }
41}
42
43func (s formatter) message(level status.MsgLevel, message string) string {
44 if level >= status.ErrorLvl {
45 return fmt.Sprintf("FAILED: %s", message)
46 } else if level > status.StatusLvl {
47 return fmt.Sprintf("%s%s", level.Prefix(), message)
48 } else if level == status.StatusLvl {
49 return message
50 }
51 return ""
52}
53
Jeongik Cha3622b342023-11-27 11:00:52 +090054func remainingTimeString(t time.Time) string {
55 now := time.Now()
56 if t.After(now) {
57 return t.Sub(now).Round(time.Duration(time.Second)).String()
58 }
59 return time.Duration(0).Round(time.Duration(time.Second)).String()
60}
Colin Crossce525352019-06-08 18:58:00 -070061func (s formatter) progress(counts status.Counts) string {
62 if s.format == "" {
Jeongik Cha3622b342023-11-27 11:00:52 +090063 output := fmt.Sprintf("[%3d%% %d/%d", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
Jeongik Chab6d5ff52023-12-19 05:44:26 +000064
65 if !counts.EstimatedTime.IsZero() {
Jeongik Cha3622b342023-11-27 11:00:52 +090066 output += fmt.Sprintf(" %s remaining", remainingTimeString(counts.EstimatedTime))
67 }
68 output += "] "
69 return output
Colin Crossce525352019-06-08 18:58:00 -070070 }
71
72 buf := &strings.Builder{}
73 for i := 0; i < len(s.format); i++ {
74 c := s.format[i]
75 if c != '%' {
76 buf.WriteByte(c)
77 continue
78 }
79
80 i = i + 1
81 if i == len(s.format) {
82 buf.WriteByte(c)
83 break
84 }
85
86 c = s.format[i]
87 switch c {
88 case '%':
89 buf.WriteByte(c)
90 case 's':
91 fmt.Fprintf(buf, "%d", counts.StartedActions)
92 case 't':
93 fmt.Fprintf(buf, "%d", counts.TotalActions)
94 case 'r':
95 fmt.Fprintf(buf, "%d", counts.RunningActions)
96 case 'u':
97 fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
98 case 'f':
99 fmt.Fprintf(buf, "%d", counts.FinishedActions)
100 case 'o':
101 fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
102 case 'c':
103 // TODO: implement?
104 buf.WriteRune('?')
105 case 'p':
106 fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
107 case 'e':
108 fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
Jeongik Cha3622b342023-11-27 11:00:52 +0900109 case 'l':
110 if counts.EstimatedTime.IsZero() {
111 // No esitimated data
112 buf.WriteRune('?')
113 } else {
114 fmt.Fprintf(buf, "%s", remainingTimeString(counts.EstimatedTime))
115 }
Colin Crossce525352019-06-08 18:58:00 -0700116 default:
117 buf.WriteString("unknown placeholder '")
118 buf.WriteByte(c)
119 buf.WriteString("'")
120 }
121 }
122 return buf.String()
123}
124
125func (s formatter) result(result status.ActionResult) string {
126 var ret string
127 if result.Error != nil {
128 targets := strings.Join(result.Outputs, " ")
129 if s.quiet || result.Command == "" {
130 ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
131 } else {
132 ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
133 }
134 } else if result.Output != "" {
135 ret = result.Output
136 }
137
138 if len(ret) > 0 && ret[len(ret)-1] != '\n' {
139 ret += "\n"
140 }
141
142 return ret
143}