Files
bargain-hardware/cmd/main.go
T
2026-03-11 09:01:45 +00:00

128 lines
2.7 KiB
Go

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{}{}
}