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 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} 61func (s formatter) progress(counts status.Counts) string { 62 if s.format == "" { 63 output := fmt.Sprintf("[%3d%% %d/%d", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions) 64 65 if !counts.EstimatedTime.IsZero() { 66 output += fmt.Sprintf(" %s remaining", remainingTimeString(counts.EstimatedTime)) 67 } 68 output += "] " 69 return output 70 } 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()) 109 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 } 116 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} 144