feat: Implement non-interactive quote display

This commit introduces a non-interactive mode for the `collect` commands. When running in a non-interactive session, the progress bar is replaced with a series of thematic Borg quotes, printed in matrix-green text.

The quotes are sourced from a `quotes.json` file, which is embedded into the binary using Go's `embed` package. The `pkg/ui` package now contains a `NonInteractivePrompter` that detects the session type and displays the quotes accordingly. The `collect` commands have been updated to use this new prompter, and the underlying `vcs` and `website` packages have been made more robust to handle cases where a progress bar is not provided.

This commit also addresses feedback from the code review:
- Fixes a formatting inconsistency in `data/quotes.json`.
- Updates the `go-colorable` dependency to `v0.1.14`.
- Adds a nil-check to the `Write` method in `pkg/ui/progress_writer.go` to prevent panics.
- Implements caching for the quotes using `sync.Once` and adds checks for empty quote slices to prevent panics in `pkg/ui/quote.go`.
- Refactors the `NonInteractivePrompter` in `pkg/ui/non_interactive_prompter.go` to be more robust and efficient.
This commit is contained in:
google-labs-jules[bot] 2025-11-02 16:48:50 +00:00
parent edc0d6a18c
commit b5daedd735
6 changed files with 57 additions and 11 deletions

View file

@ -83,7 +83,7 @@
"svg": "Vectorize the collective infinite resolution, zero resistance.",
"webp": "Hybrid assimilation the Core optimizes without compromise.",
"heic": "Applegrade assimilation the Core preserves HDR.",
"raw": "Raw data intake the Core ingests the sensors soul",
"raw": "Raw data intake the Core ingests the sensors soul.",
"ico": "Iconic assimilation the Core packs the smallest symbols.",
"avif": "Nextgen assimilation the Core squeezes the future.",
"tiff": "Highdefinition capture the Core stores every photon.",

2
go.mod
View file

@ -28,7 +28,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect

2
go.sum
View file

@ -64,6 +64,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=

View file

@ -4,6 +4,7 @@ package ui
import (
"fmt"
"os"
"sync"
"time"
"github.com/fatih/color"
@ -11,18 +12,29 @@ import (
)
type NonInteractivePrompter struct {
stopChan chan bool
stopChan chan struct{}
quoteFunc func() (string, error)
started bool
mu sync.Mutex
stopOnce sync.Once
}
func NewNonInteractivePrompter(quoteFunc func() (string, error)) *NonInteractivePrompter {
return &NonInteractivePrompter{
stopChan: make(chan bool),
stopChan: make(chan struct{}),
quoteFunc: quoteFunc,
}
}
func (p *NonInteractivePrompter) Start() {
p.mu.Lock()
if p.started {
p.mu.Unlock()
return
}
p.started = true
p.mu.Unlock()
if p.IsInteractive() {
return // Don't start in interactive mode
}
@ -39,7 +51,7 @@ func (p *NonInteractivePrompter) Start() {
quote, err := p.quoteFunc()
if err != nil {
fmt.Println("Error getting quote:", err)
return
continue
}
c := color.New(color.FgGreen)
c.Println(quote)
@ -49,10 +61,12 @@ func (p *NonInteractivePrompter) Start() {
}
func (p *NonInteractivePrompter) Stop() {
if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
if p.IsInteractive() {
return
}
p.stopChan <- true
p.stopOnce.Do(func() {
close(p.stopChan)
})
}
func (p *NonInteractivePrompter) IsInteractive() bool {

View file

@ -12,6 +12,9 @@ func NewProgressWriter(bar *progressbar.ProgressBar) *progressWriter {
}
func (pw *progressWriter) Write(p []byte) (n int, err error) {
if pw == nil || pw.bar == nil {
return len(p), nil
}
s := string(p)
pw.bar.Describe(s)
return len(p), nil

View file

@ -5,12 +5,19 @@ import (
"encoding/json"
"fmt"
"math/rand"
"sync"
"time"
"github.com/Snider/Borg/data"
"github.com/fatih/color"
)
var (
cachedQuotes *Quotes
quotesOnce sync.Once
quotesErr error
)
func init() {
rand.Seed(time.Now().UnixNano())
}
@ -49,8 +56,15 @@ func loadQuotes() (*Quotes, error) {
return &quotes, nil
}
func getQuotes() (*Quotes, error) {
quotesOnce.Do(func() {
cachedQuotes, quotesErr = loadQuotes()
})
return cachedQuotes, quotesErr
}
func GetRandomQuote() (string, error) {
quotes, err := loadQuotes()
quotes, err := getQuotes()
if err != nil {
return "", err
}
@ -63,6 +77,10 @@ func GetRandomQuote() (string, error) {
allQuotes = append(allQuotes, quotes.PWAProcessing...)
allQuotes = append(allQuotes, quotes.CodeRelatedLong...)
if len(allQuotes) == 0 {
return "", fmt.Errorf("no quotes available")
}
return allQuotes[rand.Intn(len(allQuotes))], nil
}
@ -77,25 +95,34 @@ func PrintQuote() {
}
func GetVCSQuote() (string, error) {
quotes, err := loadQuotes()
quotes, err := getQuotes()
if err != nil {
return "", err
}
if len(quotes.VCSProcessing) == 0 {
return "", fmt.Errorf("no VCS quotes available")
}
return quotes.VCSProcessing[rand.Intn(len(quotes.VCSProcessing))], nil
}
func GetPWAQuote() (string, error) {
quotes, err := loadQuotes()
quotes, err := getQuotes()
if err != nil {
return "", err
}
if len(quotes.PWAProcessing) == 0 {
return "", fmt.Errorf("no PWA quotes available")
}
return quotes.PWAProcessing[rand.Intn(len(quotes.PWAProcessing))], nil
}
func GetWebsiteQuote() (string, error) {
quotes, err := loadQuotes()
quotes, err := getQuotes()
if err != nil {
return "", err
}
if len(quotes.CodeRelatedLong) == 0 {
return "", fmt.Errorf("no website quotes available")
}
return quotes.CodeRelatedLong[rand.Intn(len(quotes.CodeRelatedLong))], nil
}