author | Alberto Bertogli
<albertito@blitiri.com.ar> 2023-08-04 22:44:43 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2023-08-05 00:07:16 UTC |
parent | 514357d400600af4d66bec2fbed24f9103c1c3a8 |
go.mod | +2 | -1 |
go.sum | +4 | -1 |
summer.go | +10 | -3 |
test/access.t | +0 | -4 |
test/basic.t | +0 | -10 |
test/dryrun_sqlite.t | +0 | -7 |
test/dryrun_xattr.t | +0 | -7 |
test/help.t | +6 | -0 |
test/singlefile.t | +0 | -1 |
test/sqlite.t | +0 | -4 |
test/tty.t | +15 | -0 |
ui.go | +16 | -10 |
diff --git a/go.mod b/go.mod index b097087..84d4fc4 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/mattn/go-sqlite3 v1.14.16 github.com/pkg/xattr v0.4.9 + golang.org/x/term v0.11.0 ) -require golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f // indirect +require golang.org/x/sys v0.11.0 // indirect diff --git a/go.sum b/go.sum index 2e4ed8c..9bdf4a4 100644 --- a/go.sum +++ b/go.sum @@ -2,5 +2,8 @@ github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwp github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= -golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f h1:8w7RhxzTVgUzw/AH/9mUV5q0vMgy40SQRursCcfmkCw= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= diff --git a/summer.go b/summer.go index 4df19ef..a55696f 100644 --- a/summer.go +++ b/summer.go @@ -9,6 +9,8 @@ import ( "os" "path/filepath" "syscall" + + "golang.org/x/term" ) const usage = `# summer 🌞 🏖 @@ -40,6 +42,7 @@ Flags: var ( dbPath = flag.String("db", "", "database to read from/write to") oneFilesystem = flag.Bool("x", false, "don't cross filesystem boundaries") + forceTTY = flag.Bool("forcetty", false, "force TTY output") ) var options = struct { @@ -48,6 +51,9 @@ var options = struct { // Do not cross filesystem boundaries. oneFilesystem bool + + // Whether output is a TTY. + isTTY bool }{} func Usage() { @@ -62,6 +68,7 @@ func main() { flag.Parse() options.oneFilesystem = *oneFilesystem + options.isTTY = *forceTTY || term.IsTerminal(int(os.Stdout.Fd())) op := flag.Arg(0) root := flag.Arg(1) @@ -150,7 +157,7 @@ func getDeviceForPath(path string) uint64 { func generate(root string) error { rootDev := getDeviceForPath(root) - p := NewProgress() + p := NewProgress(options.isTTY) defer p.Stop() fn := func(path string, d fs.DirEntry, err error) error { @@ -195,7 +202,7 @@ func generate(root string) error { func verify(root string) error { rootDev := getDeviceForPath(root) - p := NewProgress() + p := NewProgress(options.isTTY) defer p.Stop() fn := func(path string, d fs.DirEntry, err error) error { @@ -251,7 +258,7 @@ func verify(root string) error { func update(root string) error { rootDev := getDeviceForPath(root) - p := NewProgress() + p := NewProgress(options.isTTY) defer p.Stop() fn := func(path string, d fs.DirEntry, err error) error { diff --git a/test/access.t b/test/access.t index 426ac8f..f72a125 100644 --- a/test/access.t +++ b/test/access.t @@ -9,15 +9,12 @@ interfere. $ echo marola > root/hola $ summer -db=db.sqlite3 generate root/ - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 2 new, 0 corrupted $ summer -db=db.sqlite3 verify root/ - \r (no-eol) (esc) 0s: 2 matched, 0 modified, 0 new, 0 corrupted $ chmod 0000 root/empty $ summer -db=db.sqlite3 verify root/ - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 0 new, 0 corrupted open root/empty: permission denied [1] @@ -26,7 +23,6 @@ Test behaviour when the root does not exist. This exercises some different code paths, because the root is special. $ summer verify doesnotexist - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 0 new, 0 corrupted lstat doesnotexist: no such file or directory [1] diff --git a/test/basic.t b/test/basic.t index dd65c48..0adf197 100644 --- a/test/basic.t +++ b/test/basic.t @@ -10,11 +10,9 @@ Generate test data. Generate and verify. $ summer generate . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 2 new, 0 corrupted $ summer verify . - \r (no-eol) (esc) 0s: 2 matched, 0 modified, 0 new, 0 corrupted Check handling of new and updated files. @@ -22,13 +20,10 @@ Check handling of new and updated files. $ echo trova > nueva $ touch empty $ summer verify . - \r (no-eol) (esc) 0s: 1 matched, 1 modified, 1 new, 0 corrupted $ summer update . - \r (no-eol) (esc) 0s: 1 matched, 1 modified, 1 new, 0 corrupted $ summer verify . - \r (no-eol) (esc) 0s: 3 matched, 0 modified, 0 new, 0 corrupted Corrupt a file by changing its contents without changing the mtime. @@ -36,13 +31,11 @@ Corrupt a file by changing its contents without changing the mtime. $ OLD_MTIME=`stat -c "%y" hola` $ echo sospechoso >> hola $ summer verify . - \r (no-eol) (esc) 0s: 2 matched, 1 modified, 0 new, 0 corrupted $ touch --date="$OLD_MTIME" hola $ summer verify . "hola": FILE CORRUPTED - expected:239059f6, got:916db13f - \r (no-eol) (esc) 0s: 2 matched, 0 modified, 0 new, 1 corrupted detected 1 corrupted files [1] @@ -52,7 +45,6 @@ it. $ summer update . "hola": FILE CORRUPTED - expected:239059f6, got:916db13f - \r (no-eol) (esc) 0s: 2 matched, 0 modified, 0 new, 1 corrupted detected 1 corrupted files [1] @@ -61,10 +53,8 @@ Editing the file makes us ignore the previous checksum. $ touch hola $ summer update . - \r (no-eol) (esc) 0s: 2 matched, 1 modified, 0 new, 0 corrupted $ summer verify . - \r (no-eol) (esc) 0s: 3 matched, 0 modified, 0 new, 0 corrupted Check verbose and quiet. diff --git a/test/dryrun_sqlite.t b/test/dryrun_sqlite.t index 63dfbbe..c563f9c 100644 --- a/test/dryrun_sqlite.t +++ b/test/dryrun_sqlite.t @@ -10,21 +10,17 @@ Generate test data. Generate and verify. $ summer -n -db=db.sqlite3 generate . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 3 new, 0 corrupted $ summer -n -db=db.sqlite3 verify . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 3 new, 0 corrupted $ summer -n -db=db.sqlite3 verify . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 3 new, 0 corrupted Now write data for real, so we can test modification. $ summer -db=db.sqlite3 generate . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 3 new, 0 corrupted Check handling of new and updated files. @@ -32,11 +28,8 @@ Check handling of new and updated files. $ echo trova > nueva $ touch empty $ summer -n -db=db.sqlite3 verify . - \r (no-eol) (esc) 0s: 1 matched, 2 modified, 1 new, 0 corrupted $ summer -n -db=db.sqlite3 update . - \r (no-eol) (esc) 0s: 1 matched, 2 modified, 1 new, 0 corrupted $ summer -n -db=db.sqlite3 verify . - \r (no-eol) (esc) 0s: 1 matched, 2 modified, 1 new, 0 corrupted diff --git a/test/dryrun_xattr.t b/test/dryrun_xattr.t index 30a42ed..99195ef 100644 --- a/test/dryrun_xattr.t +++ b/test/dryrun_xattr.t @@ -10,21 +10,17 @@ Generate test data. Generate and verify. $ summer -n generate . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 2 new, 0 corrupted $ summer -n verify . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 2 new, 0 corrupted $ summer -n verify . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 2 new, 0 corrupted Now write data for real, so we can test modification. $ summer generate . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 2 new, 0 corrupted Check handling of new and updated files. @@ -32,11 +28,8 @@ Check handling of new and updated files. $ echo trova > nueva $ touch empty $ summer -n verify . - \r (no-eol) (esc) 0s: 1 matched, 1 modified, 1 new, 0 corrupted $ summer -n update . - \r (no-eol) (esc) 0s: 1 matched, 1 modified, 1 new, 0 corrupted $ summer -n verify . - \r (no-eol) (esc) 0s: 1 matched, 1 modified, 1 new, 0 corrupted diff --git a/test/help.t b/test/help.t index a14fefa..d3b4b5a 100644 --- a/test/help.t +++ b/test/help.t @@ -31,6 +31,8 @@ No arguments. Flags: -db string \tdatabase to read from/write to (esc) + -forcetty + \tforce TTY output (esc) -n\tdry-run mode (do not write anything) (esc) -q\tquiet mode (esc) -v\tverbose mode (list each file) (esc) @@ -67,6 +69,8 @@ Too few arguments. Flags: -db string \tdatabase to read from/write to (esc) + -forcetty + \tforce TTY output (esc) -n\tdry-run mode (do not write anything) (esc) -q\tquiet mode (esc) -v\tverbose mode (list each file) (esc) @@ -103,6 +107,8 @@ No valid path (the argument is given, but it is empty). Flags: -db string \tdatabase to read from/write to (esc) + -forcetty + \tforce TTY output (esc) -n\tdry-run mode (do not write anything) (esc) -q\tquiet mode (esc) -v\tverbose mode (list each file) (esc) diff --git a/test/singlefile.t b/test/singlefile.t index 62d15fd..018b2b3 100644 --- a/test/singlefile.t +++ b/test/singlefile.t @@ -14,7 +14,6 @@ Test that summer works fine when given a file instead of a directory. 0s: 1 matched, 0 modified, 1 new, 0 corrupted $ summer update ./hola - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 1 new, 0 corrupted $ summer -v verify . diff --git a/test/sqlite.t b/test/sqlite.t index 056f317..1bf30b1 100644 --- a/test/sqlite.t +++ b/test/sqlite.t @@ -7,13 +7,10 @@ This is enough to exercise the backend. $ echo marola > hola $ summer -db=db.sqlite3 generate . - \r (no-eol) (esc) 0s: 0 matched, 0 modified, 3 new, 0 corrupted $ summer -db=db.sqlite3 verify . - \r (no-eol) (esc) 0s: 2 matched, 1 modified, 0 new, 0 corrupted $ summer -db=db.sqlite3 update . - \r (no-eol) (esc) 0s: 2 matched, 1 modified, 0 new, 0 corrupted Check that the root path doesn't confuse us. @@ -27,7 +24,6 @@ Check that the root path doesn't confuse us. Force a write error to check it is appropriately handled. $ summer "-db=file:db.sqlite3?mode=ro" update . - \r (no-eol) (esc) 0s: 0 matched, 1 modified, 0 new, 0 corrupted attempt to write a readonly database [1] diff --git a/test/tty.t b/test/tty.t new file mode 100644 index 0000000..8803e3d --- /dev/null +++ b/test/tty.t @@ -0,0 +1,15 @@ +Test output to a TTY. + +summer will auto-detect if stdout is a tty or not, and change some of the +output accordingly. In this test framework, stdout is not a TTY, so all other +tests use that codepath. + +In this test we force tty output, and check the output is as expected. + + $ alias summer="$TESTDIR/../summer" + + $ touch file1 file2 file3 + + $ summer -forcetty -n generate . + \r (no-eol) (esc) + 0s: 0 matched, 0 modified, 3 new, 0 corrupted diff --git a/ui.go b/ui.go index e31eabb..1b37192 100644 --- a/ui.go +++ b/ui.go @@ -48,6 +48,7 @@ func PrintVersion() { type Progress struct { start time.Time + isTTY bool wg sync.WaitGroup mu sync.Mutex @@ -57,10 +58,11 @@ type Progress struct { done chan bool } -func NewProgress() *Progress { +func NewProgress(isTTY bool) *Progress { p := &Progress{ start: time.Now(), done: make(chan bool), + isTTY: isTTY, } p.wg.Add(1) go p.periodicPrint() @@ -85,26 +87,30 @@ func (p *Progress) periodicPrint() { for { select { case <-p.done: - p.print() - if !*verbose { - fmt.Printf("\n") - } + p.print(true) return case <-ticker.C: - p.print() + if p.isTTY { + p.print(false) + } } } } -func (p *Progress) print() { +func (p *Progress) print(last bool) { p.mu.Lock() defer p.mu.Unlock() - // Usually we just overwrite the previous line. - // But when verbose, just print them. prefix := "\r" suffix := "" - if *verbose { + if last { + suffix = "\n" + } + + // Usually we just overwrite the previous line. + // But when verbose, just print one after the other. + // For non-TTY, never overwrite. + if *verbose || !p.isTTY { prefix = "" suffix = "\n" }