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