package main import ( "fmt" "log" "os" "bargain-scraper/internal/exporter" "bargain-scraper/internal/models" "bargain-scraper/internal/scraper" "bargain-scraper/internal/tui" tea "github.com/charmbracelet/bubbletea" ) func main() { log.SetFlags(0) log.SetOutput(os.Stderr) if len(os.Args) > 1 && os.Args[1] == "--help" { printHelp() return } app := newApp() if err := app.run(); err != nil { log.Fatalf("Error: %v", err) } } func printHelp() { fmt.Println("Bargain Hardware Storage Scraper") fmt.Println("") fmt.Println("Usage:") fmt.Println(" bargain-scraper Start the interactive TUI") fmt.Println(" bargain-scraper --help Show this help message") fmt.Println("") fmt.Println("In the TUI:") fmt.Println(" ↑/↓ or j/k Navigate") fmt.Println(" Enter Select/scrape category") fmt.Println(" / Start search") fmt.Println(" E Export data") fmt.Println(" R Rescrape current category") fmt.Println(" Esc Go back") } type app struct { model *tui.Model scraper *scraper.Scraper exp *exporter.Exporter progressChan chan scraper.ScrapeProgress doneChan chan struct{} } func newApp() *app { return &app{ model: tui.New(), exp: exporter.New(), progressChan: make(chan scraper.ScrapeProgress, 100), doneChan: make(chan struct{}), } } func (a *app) run() error { s, err := scraper.New() if err != nil { return fmt.Errorf("failed to create scraper: %w", err) } defer s.Close() a.scraper = s a.model.ScrapeCallback = func(category models.Category) { go a.scrapeCategory(category) } a.model.ExportCallback = func(format string) error { switch format { case "json": return a.exp.ExportToJSON(a.model.GetFilteredProducts(), "") case "csv": return a.exp.ExportToCSV(a.model.GetFilteredProducts(), "") case "print": fmt.Print(a.exp.PrintToConsole(a.model.GetFilteredProducts())) return nil } return nil } p := tea.NewProgram(a.model, tea.WithAltScreen()) go a.handleProgress(p) if _, err := p.Run(); err != nil { return fmt.Errorf("error running TUI: %w", err) } return nil } func (a *app) handleProgress(p *tea.Program) { for { select { case progress := <-a.progressChan: a.model.SetProgress(progress) p.Send(progress) case <-a.doneChan: p.Send(ScrapingDoneMsg{}) return } } } type ScrapingDoneMsg struct{} func (a *app) scrapeCategory(category models.Category) { products, err := a.scraper.ScrapeCategory(a.model.GetContext(), category, a.progressChan) a.model.SetScraping(false) if err != nil { a.model.SetError(err) } a.model.SetProducts(products) a.doneChan <- struct{}{} }