Log Refactor
This commit is contained in:
+274
-28
@@ -37,26 +37,50 @@ type spinner struct {
|
||||
text string
|
||||
msg []string
|
||||
painted bool
|
||||
writer io.Writer
|
||||
enabled bool
|
||||
}
|
||||
|
||||
var spin = &spinner{
|
||||
msg: []string{"-", "\\", "|", "/"},
|
||||
func newSpinner() *spinner {
|
||||
return &spinner{
|
||||
msg: []string{"-", "\\", "|", "/"},
|
||||
}
|
||||
}
|
||||
|
||||
var spin = newSpinner()
|
||||
|
||||
func (s *spinner) configure(writer io.Writer, enabled bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.writer = writer
|
||||
s.enabled = enabled
|
||||
}
|
||||
|
||||
func (s *spinner) start(text string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.enabled || s.writer == nil {
|
||||
s.on = false
|
||||
s.painted = false
|
||||
s.text = text
|
||||
return
|
||||
}
|
||||
s.on = true
|
||||
s.text = text
|
||||
go s.run(text)
|
||||
go s.run()
|
||||
}
|
||||
|
||||
func (s *spinner) stop() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if !s.enabled || s.writer == nil {
|
||||
s.on = false
|
||||
s.painted = false
|
||||
return
|
||||
}
|
||||
s.on = false
|
||||
if s.painted {
|
||||
fmt.Fprint(os.Stderr, "\r\033[K\n")
|
||||
fmt.Fprint(s.writer, "\r\033[K")
|
||||
s.painted = false
|
||||
}
|
||||
}
|
||||
@@ -64,23 +88,28 @@ func (s *spinner) stop() {
|
||||
func (s *spinner) linebreak() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.on && s.painted {
|
||||
fmt.Fprint(os.Stderr, "\r\033[K\n")
|
||||
if s.enabled && s.on && s.painted {
|
||||
fmt.Fprint(s.writer, "\r\033[K")
|
||||
s.painted = false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *spinner) run(text string) {
|
||||
func (s *spinner) update(text string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.text = text
|
||||
}
|
||||
|
||||
func (s *spinner) run() {
|
||||
i := 0
|
||||
for {
|
||||
s.mu.Lock()
|
||||
if !s.on {
|
||||
s.mu.Unlock()
|
||||
fmt.Fprintf(os.Stderr, "\r")
|
||||
return
|
||||
}
|
||||
msg := s.msg[i%len(s.msg)]
|
||||
fmt.Fprintf(os.Stderr, "\r[%s] %s", msg, text)
|
||||
fmt.Fprintf(s.writer, "\r[%s] %s", msg, s.text)
|
||||
s.painted = true
|
||||
s.mu.Unlock()
|
||||
i++
|
||||
@@ -88,6 +117,18 @@ func (s *spinner) run(text string) {
|
||||
}
|
||||
}
|
||||
|
||||
func isInteractiveTTY(w io.Writer) bool {
|
||||
file, ok := w.(*os.File)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return (info.Mode() & os.ModeCharDevice) != 0
|
||||
}
|
||||
|
||||
var commands = []command{
|
||||
{
|
||||
name: "build",
|
||||
@@ -184,6 +225,7 @@ func Run(args []string) (int, error) {
|
||||
default:
|
||||
for _, cmd := range commands {
|
||||
if cmd.name == args[0] {
|
||||
spin.configure(ctx.stderr, isInteractiveTTY(ctx.stderr) && strings.TrimSpace(os.Getenv("CI")) == "")
|
||||
spin.start("running " + cmd.name)
|
||||
if err := cmd.run(ctx); err != nil {
|
||||
spin.stop()
|
||||
@@ -263,6 +305,212 @@ type buildHAKOptions struct {
|
||||
filteredArchives []string
|
||||
sourceManifest string
|
||||
planOnly bool
|
||||
logLevel logLevel
|
||||
}
|
||||
|
||||
type logLevel int
|
||||
|
||||
const (
|
||||
logLevelNormal logLevel = iota
|
||||
logLevelQuiet
|
||||
logLevelVerbose
|
||||
logLevelDebug
|
||||
)
|
||||
|
||||
type buildHAKConsole struct {
|
||||
stdout io.Writer
|
||||
projectRoot string
|
||||
projectName string
|
||||
planOnly bool
|
||||
level logLevel
|
||||
spinnerEnabled bool
|
||||
}
|
||||
|
||||
func newBuildHAKConsole(ctx context, p *project.Project, opts buildHAKOptions) *buildHAKConsole {
|
||||
return &buildHAKConsole{
|
||||
stdout: ctx.stdout,
|
||||
projectRoot: p.Root,
|
||||
projectName: p.Config.Module.Name,
|
||||
planOnly: opts.planOnly,
|
||||
level: opts.logLevel,
|
||||
spinnerEnabled: isInteractiveTTY(ctx.stderr) && strings.TrimSpace(os.Getenv("CI")) == "" && opts.logLevel == logLevelNormal,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) progress(message string) {
|
||||
if phase := c.phaseLabel(message); phase != "" {
|
||||
spin.update("Build HAKs: " + phase)
|
||||
}
|
||||
if c.level != logLevelDebug {
|
||||
return
|
||||
}
|
||||
spin.linebreak()
|
||||
fmt.Fprintf(c.stdout, "[debug] %s\n", message)
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) phaseLabel(message string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(message, "Validating project"):
|
||||
return "validating project"
|
||||
case strings.HasPrefix(message, "Collecting asset resources"):
|
||||
return "collecting asset resources"
|
||||
case strings.HasPrefix(message, "Planning HAK chunks"):
|
||||
return "planning HAK chunks"
|
||||
case strings.HasPrefix(message, "Resolving module HAK order"):
|
||||
return "resolving module HAK order"
|
||||
case strings.HasPrefix(message, "Reusing unchanged generated HAKs"):
|
||||
return "reusing unchanged HAKs"
|
||||
case strings.HasPrefix(message, "Reusing HAK "), strings.HasPrefix(message, "Writing HAK "):
|
||||
return "writing HAK archives"
|
||||
case strings.HasPrefix(message, "Cleaning stale generated HAKs"):
|
||||
return "cleaning stale generated HAKs"
|
||||
case strings.HasPrefix(message, "Cleaning previous generated HAKs"):
|
||||
return "cleaning previous generated HAKs"
|
||||
case strings.HasPrefix(message, "Writing HAK manifest"):
|
||||
return "writing HAK manifest"
|
||||
case strings.HasPrefix(message, "Writing autogen manifest "):
|
||||
return "writing autogen manifests"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) emitResult(result pipeline.BuildResult) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Build HAKs ----------")
|
||||
if c.level != logLevelQuiet {
|
||||
c.emitCreditsSummary(result)
|
||||
c.emitCoreSteps(result)
|
||||
}
|
||||
c.emitFinalSummary(result)
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) emitCreditsSummary(result pipeline.BuildResult) {
|
||||
summary := result.CreditsSummary
|
||||
if len(summary.ArtifactPaths) == 0 {
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(c.stdout, "Refreshing credits cache")
|
||||
if len(summary.ScannedDirs) > 0 {
|
||||
fmt.Fprintf(c.stdout, " scanned: %s\n", strings.Join(summary.ScannedDirs, ", "))
|
||||
}
|
||||
fmt.Fprintf(c.stdout, " tracks: %d\n", summary.TrackCount)
|
||||
if len(summary.GeneratedPaths) > 0 {
|
||||
for _, path := range summary.GeneratedPaths {
|
||||
fmt.Fprintf(c.stdout, " output: %s\n", c.relPath(path))
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case summary.ChangedFiles == 0:
|
||||
fmt.Fprintln(c.stdout, " status: unchanged")
|
||||
default:
|
||||
fmt.Fprintf(c.stdout, " updated: %d artifact(s)\n", summary.ChangedFiles)
|
||||
}
|
||||
if summary.TrackCount > 0 {
|
||||
if c.level >= logLevelVerbose {
|
||||
fmt.Fprintln(c.stdout, " mappings:")
|
||||
for _, mapping := range summary.Mappings {
|
||||
fmt.Fprintf(c.stdout, " %s -> %s\n", mapping.Source, mapping.Output)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(c.stdout, " mapped: %d music file(s); use --verbose to list mappings\n", summary.TrackCount)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(c.stdout)
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) emitCoreSteps(result pipeline.BuildResult) {
|
||||
fmt.Fprintln(c.stdout, "Validating project")
|
||||
fmt.Fprintln(c.stdout, "Collecting asset resources")
|
||||
if len(result.AutogenManifestPaths) > 0 {
|
||||
fmt.Fprintln(c.stdout, "Writing autogen manifests")
|
||||
for _, path := range result.AutogenManifestPaths {
|
||||
fmt.Fprintf(c.stdout, " %s\n", filepath.Base(path))
|
||||
}
|
||||
fmt.Fprintln(c.stdout)
|
||||
}
|
||||
fmt.Fprintln(c.stdout, "Planning HAK chunks")
|
||||
fmt.Fprintln(c.stdout, "Resolving module HAK order")
|
||||
if !c.planOnly {
|
||||
fmt.Fprintln(c.stdout, "Reusing unchanged HAKs")
|
||||
fmt.Fprintf(c.stdout, " reused: %d / %d\n", result.HAKSummary.Reused, result.HAKSummary.Total)
|
||||
fmt.Fprintf(c.stdout, " written: %d / %d\n", result.HAKSummary.Written, result.HAKSummary.Total)
|
||||
fmt.Fprintf(c.stdout, " assets: %s\n", formatCount(result.HAKAssets))
|
||||
if c.level >= logLevelVerbose {
|
||||
for _, action := range result.HAKSummary.Actions {
|
||||
verb := "wrote"
|
||||
if action.Reused {
|
||||
verb = "reused"
|
||||
}
|
||||
fmt.Fprintf(c.stdout, " %s: %s (%s assets)\n", verb, action.Name, formatCount(action.AssetCount))
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(c.stdout)
|
||||
fmt.Fprintln(c.stdout, "Cleaning stale generated HAKs")
|
||||
} else {
|
||||
fmt.Fprintf(c.stdout, "Planned HAK archives: %d\n", result.HAKSummary.Total)
|
||||
}
|
||||
fmt.Fprintln(c.stdout, "Writing HAK manifest")
|
||||
fmt.Fprintln(c.stdout)
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) emitFinalSummary(result pipeline.BuildResult) {
|
||||
fmt.Fprintln(c.stdout, "Summary ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
if c.planOnly {
|
||||
fmt.Fprintf(c.stdout, "planned archives: %d\n", result.HAKSummary.Total)
|
||||
} else {
|
||||
fmt.Fprintf(c.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
fmt.Fprintf(c.stdout, "hak assets: %s\n", formatCount(result.HAKAssets))
|
||||
}
|
||||
if result.Manifest != "" {
|
||||
fmt.Fprintf(c.stdout, "manifest: %s\n", c.relPath(result.Manifest))
|
||||
}
|
||||
if len(result.CreditsArtifactPaths) > 0 {
|
||||
fmt.Fprintf(c.stdout, "credits artifacts: %d\n", len(result.CreditsArtifactPaths))
|
||||
}
|
||||
if len(result.AutogenManifestPaths) > 0 {
|
||||
fmt.Fprintf(c.stdout, "autogen manifests: %d\n", len(result.AutogenManifestPaths))
|
||||
}
|
||||
status := "complete"
|
||||
if c.planOnly {
|
||||
status = "planned"
|
||||
}
|
||||
fmt.Fprintf(c.stdout, "status: %s\n", status)
|
||||
}
|
||||
|
||||
func (c *buildHAKConsole) relPath(path string) string {
|
||||
if path == "" {
|
||||
return path
|
||||
}
|
||||
rel, err := filepath.Rel(c.projectRoot, path)
|
||||
if err != nil {
|
||||
return filepath.ToSlash(path)
|
||||
}
|
||||
return filepath.ToSlash(rel)
|
||||
}
|
||||
|
||||
func formatCount(value int) string {
|
||||
raw := strconv.Itoa(value)
|
||||
if value < 1000 && value > -1000 {
|
||||
return raw
|
||||
}
|
||||
sign := ""
|
||||
if strings.HasPrefix(raw, "-") {
|
||||
sign = "-"
|
||||
raw = strings.TrimPrefix(raw, "-")
|
||||
}
|
||||
parts := make([]string, 0, (len(raw)+2)/3)
|
||||
for len(raw) > 3 {
|
||||
parts = append(parts, raw[len(raw)-3:])
|
||||
raw = raw[:len(raw)-3]
|
||||
}
|
||||
parts = append(parts, raw)
|
||||
slices := make([]string, 0, len(parts))
|
||||
for index := len(parts) - 1; index >= 0; index-- {
|
||||
slices = append(slices, parts[index])
|
||||
}
|
||||
return sign + strings.Join(slices, ",")
|
||||
}
|
||||
|
||||
func runBuildHAKs(ctx context) error {
|
||||
@@ -280,16 +528,15 @@ func runBuildHAKs(ctx context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
progress := func(message string) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintf(ctx.stdout, "[build-haks] %s\n", message)
|
||||
}
|
||||
console := newBuildHAKConsole(ctx, p, opts)
|
||||
spin.configure(ctx.stderr, console.spinnerEnabled)
|
||||
spin.update("Build HAKs: starting")
|
||||
var result pipeline.BuildResult
|
||||
if opts.planOnly {
|
||||
result, err = pipeline.PlanHAKsWithProgress(p, progress)
|
||||
result, err = pipeline.PlanHAKsWithProgress(p, console.progress)
|
||||
} else {
|
||||
result, err = pipeline.BuildHAKsWithOptions(p, pipeline.BuildHAKOptions{
|
||||
Progress: progress,
|
||||
Progress: console.progress,
|
||||
ArchiveNames: opts.filteredArchives,
|
||||
SourceManifestPath: opts.sourceManifest,
|
||||
})
|
||||
@@ -298,18 +545,7 @@ func runBuildHAKs(ctx context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.stdout, "project: %s\n", p.Config.Module.Name)
|
||||
fmt.Fprintf(ctx.stdout, "hak archives: %d\n", len(result.HAKPaths))
|
||||
fmt.Fprintf(ctx.stdout, "hak assets: %d\n", result.HAKAssets)
|
||||
if result.Manifest != "" {
|
||||
fmt.Fprintf(ctx.stdout, "hak manifest: %s\n", result.Manifest)
|
||||
}
|
||||
if len(result.CreditsArtifactPaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "credits artifacts: %d\n", len(result.CreditsArtifactPaths))
|
||||
}
|
||||
if len(result.AutogenManifestPaths) > 0 {
|
||||
fmt.Fprintf(ctx.stdout, "autogen manifests: %d\n", len(result.AutogenManifestPaths))
|
||||
}
|
||||
console.emitResult(result)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -323,7 +559,7 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
arg := args[index]
|
||||
switch arg {
|
||||
case "-h", "--help":
|
||||
return opts, errors.New("usage: build-haks [--hak <hak-name> ...] [--archive <archive-name> ...] [--source-manifest <path>] [--plan-only]")
|
||||
return opts, errors.New("usage: build-haks [--hak <hak-name> ...] [--archive <archive-name> ...] [--source-manifest <path>] [--plan-only] [--quiet|--verbose|--debug]")
|
||||
case "--hak":
|
||||
index++
|
||||
if index >= len(args) {
|
||||
@@ -338,6 +574,12 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
opts.filteredArchives = append(opts.filteredArchives, args[index])
|
||||
case "--plan-only":
|
||||
opts.planOnly = true
|
||||
case "--quiet":
|
||||
opts.logLevel = logLevelQuiet
|
||||
case "--verbose":
|
||||
opts.logLevel = logLevelVerbose
|
||||
case "--debug":
|
||||
opts.logLevel = logLevelDebug
|
||||
case "--source-manifest":
|
||||
index++
|
||||
if index >= len(args) {
|
||||
@@ -357,6 +599,10 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
opts.sourceManifest = value
|
||||
continue
|
||||
}
|
||||
if arg == "--quiet=true" {
|
||||
opts.logLevel = logLevelQuiet
|
||||
continue
|
||||
}
|
||||
return opts, fmt.Errorf("unknown build-haks argument %q", arg)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user