Move smart and dumb terminals into separate implementations

Support for smart and dumb terminals are implemented in writer.go,
which makes dumb terminals much more complicated than necessary.
Move smart and dumb terminals into two separate implementations
of StatusOutput, with common code moved into a shared formatter
class.

Test: not yet
Change-Id: I59bbdae479f138b46cd0f03092720a3303e8f0fe
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 2445c5b..481c511 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -15,21 +15,9 @@
 package terminal
 
 import (
-	"fmt"
-	"strings"
-	"time"
-
 	"android/soong/ui/status"
 )
 
-type statusOutput struct {
-	writer Writer
-	format string
-
-	start time.Time
-	quiet bool
-}
-
 // NewStatusOutput returns a StatusOutput that represents the
 // current build status similarly to Ninja's built-in terminal
 // output.
@@ -37,109 +25,11 @@
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
 func NewStatusOutput(w Writer, statusFormat string, quietBuild bool) status.StatusOutput {
-	return &statusOutput{
-		writer: w,
-		format: statusFormat,
+	formatter := newFormatter(statusFormat, quietBuild)
 
-		start: time.Now(),
-		quiet: quietBuild,
-	}
-}
-
-func (s *statusOutput) Message(level status.MsgLevel, message string) {
-	if level >= status.ErrorLvl {
-		s.writer.Print(fmt.Sprintf("FAILED: %s", message))
-	} else if level > status.StatusLvl {
-		s.writer.Print(fmt.Sprintf("%s%s", level.Prefix(), message))
-	} else if level == status.StatusLvl {
-		s.writer.StatusLine(message)
-	}
-}
-
-func (s *statusOutput) StartAction(action *status.Action, counts status.Counts) {
-	if !s.writer.isSmartTerminal() {
-		return
-	}
-
-	str := action.Description
-	if str == "" {
-		str = action.Command
-	}
-
-	s.writer.StatusLine(s.progress(counts) + str)
-}
-
-func (s *statusOutput) FinishAction(result status.ActionResult, counts status.Counts) {
-	str := result.Description
-	if str == "" {
-		str = result.Command
-	}
-
-	progress := s.progress(counts) + str
-
-	if result.Error != nil {
-		targets := strings.Join(result.Outputs, " ")
-		if s.quiet || result.Command == "" {
-			s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s", targets, result.Output))
-		} else {
-			s.writer.StatusAndMessage(progress, fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output))
-		}
-	} else if result.Output != "" {
-		s.writer.StatusAndMessage(progress, result.Output)
+	if w.isSmartTerminal() {
+		return NewSmartStatusOutput(w, formatter)
 	} else {
-		s.writer.StatusLine(progress)
+		return NewDumbStatusOutput(w, formatter)
 	}
 }
-
-func (s *statusOutput) Flush() {}
-
-func (s *statusOutput) progress(counts status.Counts) string {
-	if s.format == "" {
-		return fmt.Sprintf("[%3d%% %d/%d] ", 100*counts.FinishedActions/counts.TotalActions, counts.FinishedActions, counts.TotalActions)
-	}
-
-	buf := &strings.Builder{}
-	for i := 0; i < len(s.format); i++ {
-		c := s.format[i]
-		if c != '%' {
-			buf.WriteByte(c)
-			continue
-		}
-
-		i = i + 1
-		if i == len(s.format) {
-			buf.WriteByte(c)
-			break
-		}
-
-		c = s.format[i]
-		switch c {
-		case '%':
-			buf.WriteByte(c)
-		case 's':
-			fmt.Fprintf(buf, "%d", counts.StartedActions)
-		case 't':
-			fmt.Fprintf(buf, "%d", counts.TotalActions)
-		case 'r':
-			fmt.Fprintf(buf, "%d", counts.RunningActions)
-		case 'u':
-			fmt.Fprintf(buf, "%d", counts.TotalActions-counts.StartedActions)
-		case 'f':
-			fmt.Fprintf(buf, "%d", counts.FinishedActions)
-		case 'o':
-			fmt.Fprintf(buf, "%.1f", float64(counts.FinishedActions)/time.Since(s.start).Seconds())
-		case 'c':
-			// TODO: implement?
-			buf.WriteRune('?')
-		case 'p':
-			fmt.Fprintf(buf, "%3d%%", 100*counts.FinishedActions/counts.TotalActions)
-		case 'e':
-			fmt.Fprintf(buf, "%.3f", time.Since(s.start).Seconds())
-		default:
-			buf.WriteString("unknown placeholder '")
-			buf.WriteByte(c)
-			buf.WriteString("'")
-		}
-	}
-	return buf.String()
-}