Skip to content

Commit 937670a

Browse files
committed
feat(pin): add non-interactive mode for piped output environments
1 parent 8134468 commit 937670a

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- ✨ Ability to update the spinner message dynamically
1616
- ⚙️ No external dependencies – uses only the Go standard library
1717
- 🚀 Compatible with Go 1.11 and later
18+
- ⏹ Automatically disables animations in non-interactive (piped) environments to prevent output corruption
1819

1920
## Installation
2021

@@ -35,6 +36,10 @@ defer cancel()
3536
p.Stop("Done!")
3637
```
3738

39+
## Non-interactive Behavior
40+
41+
When the spinner detects that `stdout` is not connected to an interactive terminal (for example, when output is piped), it disables animations and prints only the final message. This prevents ANSI escape codes from cluttering the output.
42+
3843
## Examples
3944

4045
### Basic Progress Indicator

pin.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ package pin
3939
import (
4040
"context"
4141
"fmt"
42+
"os"
4243
"sync"
4344
"time"
4445
)
@@ -194,7 +195,7 @@ func New(message string, opts ...Option) *Pin {
194195
p := &Pin{
195196
frames: defaultFrames,
196197
message: message,
197-
stopChan: make(chan struct{}),
198+
stopChan: make(chan struct{}, 1),
198199
spinnerColor: ColorDefault,
199200
textColor: ColorDefault,
200201
doneSymbol: '✓',
@@ -219,6 +220,16 @@ func (p *Pin) Start(ctx context.Context) context.CancelFunc {
219220
if p.isRunning {
220221
return func() {}
221222
}
223+
224+
if !isTerminal(os.Stdout) {
225+
p.isRunning = true
226+
go func() {
227+
<-ctx.Done()
228+
p.isRunning = false
229+
}()
230+
return func() {}
231+
}
232+
222233
p.isRunning = true
223234

224235
ctx, cancel := context.WithCancel(ctx)
@@ -284,6 +295,13 @@ func (p *Pin) Stop(message ...string) {
284295
if !p.isRunning {
285296
return
286297
}
298+
299+
if !isTerminal(os.Stdout) {
300+
if len(message) > 0 {
301+
fmt.Println(message[0])
302+
}
303+
return
304+
}
287305
p.isRunning = false
288306
p.stopChan <- struct{}{}
289307

@@ -366,3 +384,18 @@ func (c Color) getColorCode() string {
366384
return ""
367385
}
368386
}
387+
388+
// isTerminal checks if the provided file descriptor is a terminal.
389+
func isTerminal(f *os.File) bool {
390+
if ForceInteractive {
391+
return true
392+
}
393+
fi, err := f.Stat()
394+
if err != nil {
395+
return false
396+
}
397+
398+
return (fi.Mode() & os.ModeCharDevice) != 0
399+
}
400+
401+
var ForceInteractive bool

pin_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import (
1313
"github.com/yarlson/pin"
1414
)
1515

16+
func init() {
17+
pin.ForceInteractive = true
18+
}
19+
1620
var (
1721
stdoutMu sync.Mutex
1822
)

0 commit comments

Comments
 (0)