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