Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 19 additions & 23 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,11 @@ func run(args []string) error {
// gather project symbols (enums, constants) so argument values that
// reference them resolve to a type just like literals do
table := symbols.New()
for _, file := range files {
src, err := os.ReadFile(file)
if err != nil {
return err
}
if err := progressEach("scanning", files, func(_ string, src []byte) error {
table.CollectSource(src)
return nil
}); err != nil {
return err
}

// build the inheritance table from the project and its vendor directory, so
Expand All @@ -73,23 +72,22 @@ func run(args []string) error {
if err != nil {
return err
}
for _, file := range append(append([]string{}, files...), vendorFiles...) {
src, err := os.ReadFile(file)
if err != nil {
return err
}
allFiles := append(append([]string{}, files...), vendorFiles...)
if err := progressEach("parsing", allFiles, func(_ string, src []byte) error {
inheritance.CollectSource(src)
return nil
}); err != nil {
return err
}

// 1. collect literal argument types across the whole project
fmt.Println("1. Collecting argument types...")
var records []collect.Record
for _, file := range files {
src, err := os.ReadFile(file)
if err != nil {
return err
}
if err := progressEach("collecting", files, func(_ string, src []byte) error {
records = append(records, collect.FromSource(src, table)...)
return nil
}); err != nil {
return err
}
fmt.Printf(" Found %d arg types\n\n", len(records))

Expand All @@ -102,29 +100,27 @@ func run(args []string) error {
fmt.Println("2. Adding types to parameters...")
}
added := 0
for _, file := range files {
src, err := os.ReadFile(file)
if err != nil {
return err
}

if err := progressEach("applying", files, func(file string, src []byte) error {
output, count, changed := apply.Source(src, types, table, inheritance)
if !changed {
continue
return nil
}

if dry {
if patch, ok := diff.Lines(file, string(src), output); ok {
fmt.Print(patch)
}
added += count
continue
return nil
}

if err := os.WriteFile(file, []byte(output), 0o644); err != nil {
return err
}
added += count
return nil
}); err != nil {
return err
}

if added == 0 {
Expand Down
73 changes: 73 additions & 0 deletions progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"fmt"
"os"
"strings"
)

// progressBarWidth is the number of cells in the rendered bar, matching the
// default width Symfony's progress bar uses.
const progressBarWidth = 28

// labelColumnWidth pads the phase label so the successive phases' bars line up
// in one column. It is the length of the widest label, "collecting".
const labelColumnWidth = 10

// stderrIsTerminal reports whether stderr is an interactive terminal. The live
// bar redraws in place with a carriage return, which only reads correctly on a
// terminal; a redirected or piped run stays plain.
var stderrIsTerminal = detectTerminal()

func detectTerminal() bool {
fileInfo, err := os.Stderr.Stat()
if err != nil {
return false
}
return fileInfo.Mode()&os.ModeCharDevice != 0
}

// renderProgressBar draws a rector-style bar keyed by phase label, e.g.
//
// collecting 1080/1659 [==============>-------------] 65%
//
// The leading carriage return rewrites the line in place on each tick.
func renderProgressBar(label string, done int, total int) string {
percent := 0
filled := 0
if total > 0 {
percent = done * 100 / total
filled = done * progressBarWidth / total
}

var bar string
if filled >= progressBarWidth {
bar = strings.Repeat("=", progressBarWidth)
} else {
bar = strings.Repeat("=", filled) + ">" + strings.Repeat("-", progressBarWidth-filled-1)
}
return fmt.Sprintf("\r%-*s %d/%d [%s] %3d%%", labelColumnWidth, label, done, total, bar, percent)
}

// progressEach reads each file and hands its source to fn, drawing a live
// progress bar labeled label on stderr while it goes. The bar is skipped on a
// non-terminal run so piped output stays clean.
func progressEach(label string, files []string, fn func(file string, src []byte) error) error {
total := len(files)
for index, file := range files {
src, err := os.ReadFile(file)
if err != nil {
return err
}
if err := fn(file, src); err != nil {
return err
}
if stderrIsTerminal {
fmt.Fprint(os.Stderr, renderProgressBar(label, index+1, total))
}
}
if total > 0 && stderrIsTerminal {
fmt.Fprintln(os.Stderr) // end the progress bar line
}
return nil
}
Loading