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	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.
35func newFormatter(format string, quiet bool) formatter {
36	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
54func (s formatter) progress(counts status.Counts) string {
55	if s.format == "" {
56		return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
57	}
58
59	buf := &strings.Builder{}
60	for i := 0; i < len(s.format); i++ {
61		c := s.format[i]
62		if c != '%' {
63			buf.WriteByte(c)
64			continue
65		}
66
67		i = i + 1
68		if i == len(s.format) {
69			buf.WriteByte(c)
70			break
71		}
72
73		c = s.format[i]
74		switch c {
75		case '%':
76			buf.WriteByte(c)
77		case 's':
78			fmt.Fprintf(buf, "%d", counts.StartedActions)
79		case 't':
80			fmt.Fprintf(buf, "%d", counts.TotalActions)
81		case 'r':
82			fmt.Fprintf(buf, "%d", counts.RunningActions)
83		case 'u':
84			fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
85		case 'f':
86			fmt.Fprintf(buf, "%d", counts.FinishedActions)
87		case 'o':
88			fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
89		case 'c':
90			// TODO: implement?
91			buf.WriteRune('?')
92		case 'p':
93			fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
94		case 'e':
95			fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
96		default:
97			buf.WriteString("unknown placeholder '")
98			buf.WriteByte(c)
99			buf.WriteString("'")
100		}
101	}
102	return buf.String()
103}
104
105func (s formatter) result(result status.ActionResult) string {
106	var ret string
107	if result.Error != nil {
108		targets := strings.Join(result.Outputs, " ")
109		if s.quiet || result.Command == "" {
110			ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
111		} else {
112			ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
113		}
114	} else if result.Output != "" {
115		ret = result.Output
116	}
117
118	if len(ret) > 0 && ret[len(ret)-1] != '\n' {
119		ret += "\n"
120	}
121
122	return ret
123}
124