Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 124 additions & 26 deletions internal/cmd/wl_browse.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/doltserver"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/wasteland"
"github.com/steveyegge/gastown/internal/workspace"
)

Expand All @@ -27,10 +28,10 @@ var wlBrowseCmd = &cobra.Command{
Short: "Browse wanted items on the commons board",
Args: cobra.NoArgs,
RunE: runWLBrowse,
Long: `Browse the Wasteland wanted board (hop/wl-commons).
Long: `Browse the Wasteland wanted board.

Uses the clone-then-discard pattern: clones the commons database to a
temporary directory, queries it, then deletes the clone.
Uses the local fork if available (set by gt wl join), otherwise falls back
to cloning the upstream commons temporarily.

EXAMPLES:
gt wl browse # All open wanted items
Expand All @@ -54,39 +55,78 @@ func init() {
}

func runWLBrowse(cmd *cobra.Command, args []string) error {
if _, err := workspace.FindFromCwdOrError(); err != nil {
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}

doltPath, err := exec.LookPath("dolt")
if err != nil {
return fmt.Errorf("dolt not found in PATH — install from https://docs.dolthub.com/introduction/installation")
}
// Fast path: query through the Dolt server if the database is registered.
dbName := wasteland.ResolveDBName(townRoot)
if doltserver.DatabaseExists(townRoot, dbName) {
query := buildBrowseQuery(BrowseFilter{
Status: wlBrowseStatus,
Project: wlBrowseProject,
Type: wlBrowseType,
Priority: wlBrowsePriority,
Limit: wlBrowseLimit,
})
serverQuery := fmt.Sprintf("USE %s; %s", dbName, query)

if wlBrowseJSON {
output, err := doltserver.QueryJSON(townRoot, serverQuery)
if err != nil {
return err
}
fmt.Print(output)
return nil
}

tmpDir, err := os.MkdirTemp("", "wl-browse-*")
if err != nil {
return fmt.Errorf("creating temp directory: %w", err)
}
defer os.RemoveAll(tmpDir)
output, err := doltserver.QueryCSV(townRoot, serverQuery)
if err != nil {
return err
}
rows := wlParseCSV(output)
if len(rows) <= 1 {
fmt.Println("No wanted items found matching your filters.")
return nil
}

commonsOrg := "hop"
commonsDB := "wl-commons"
cloneDir := filepath.Join(tmpDir, commonsDB)
tbl := style.NewTable(
style.Column{Name: "ID", Width: 12},
style.Column{Name: "TITLE", Width: 40},
style.Column{Name: "PROJECT", Width: 12},
style.Column{Name: "TYPE", Width: 10},
style.Column{Name: "PRI", Width: 4, Align: style.AlignRight},
style.Column{Name: "POSTED BY", Width: 16},
style.Column{Name: "STATUS", Width: 10},
style.Column{Name: "EFFORT", Width: 8},
)

for _, row := range rows[1:] {
if len(row) < 8 {
continue
}
pri := wlFormatPriority(row[4])
tbl.AddRow(row[0], row[1], row[2], row[3], pri, row[5], row[6], row[7])
}

remote := fmt.Sprintf("%s/%s", commonsOrg, commonsDB)
if !wlBrowseJSON {
fmt.Printf("Cloning %s...\n", style.Bold.Render(remote))
fmt.Printf("Wanted items (%d):\n\n", len(rows)-1)
fmt.Print(tbl.Render())
return nil
}

cloneCmd := exec.Command(doltPath, "clone", remote, cloneDir)
if !wlBrowseJSON {
cloneCmd.Stderr = os.Stderr
// Fallback: read from local filesystem clone.
doltPath, err := exec.LookPath("dolt")
if err != nil {
return fmt.Errorf("dolt not found in PATH — install from https://docs.dolthub.com/introduction/installation")
}
if err := cloneCmd.Run(); err != nil {
return fmt.Errorf("cloning %s: %w\nEnsure the database exists on DoltHub: https://www.dolthub.com/%s", remote, err, remote)

cloneDir, tmpDir, err := resolveWLCommonsBrowse(townRoot, doltPath)
if err != nil {
return err
}
if !wlBrowseJSON {
fmt.Printf("%s Cloned successfully\n\n", style.Bold.Render("✓"))
if tmpDir != "" {
defer os.RemoveAll(tmpDir)
}

query := buildBrowseQuery(BrowseFilter{
Expand All @@ -108,6 +148,64 @@ func runWLBrowse(cmd *cobra.Command, args []string) error {
return renderWLBrowseTable(doltPath, cloneDir, query)
}

// resolveWLCommonsBrowse finds the local wl-commons clone directory for browsing.
// Returns (cloneDir, tmpDir, err). If tmpDir is non-empty, caller must
// defer os.RemoveAll(tmpDir) — a temporary clone was created.
func resolveWLCommonsBrowse(townRoot, doltPath string) (cloneDir, tmpDir string, err error) {
// Try wasteland config (set by gt wl join).
if cfg, cfgErr := wasteland.LoadConfig(townRoot); cfgErr == nil && cfg.LocalDir != "" {
if _, statErr := os.Stat(filepath.Join(cfg.LocalDir, ".dolt")); statErr == nil {
return cfg.LocalDir, "", nil
}
}

// Try standard location: .wasteland/hop/wl-commons.
stdPath := wasteland.LocalCloneDir(townRoot, "hop", "wl-commons")
if _, statErr := os.Stat(filepath.Join(stdPath, ".dolt")); statErr == nil {
return stdPath, "", nil
}

// Try common fallback locations.
if forkDir := findWLCommonsFork(townRoot); forkDir != "" {
return forkDir, "", nil
}

// No local clone — do a one-time clone-then-discard.
// Read upstream from config, or default to hop/wl-commons.
commonsOrg := "hop"
commonsDB := "wl-commons"
if cfg, cfgErr := wasteland.LoadConfig(townRoot); cfgErr == nil && cfg.Upstream != "" {
if o, d, parseErr := wasteland.ParseUpstream(cfg.Upstream); parseErr == nil {
commonsOrg = o
commonsDB = d
}
}

tmpDir, err = os.MkdirTemp("", "wl-browse-*")
if err != nil {
return "", "", fmt.Errorf("creating temp directory: %w", err)
}

cloneDir = filepath.Join(tmpDir, commonsDB)
remote := fmt.Sprintf("%s/%s", commonsOrg, commonsDB)
if !wlBrowseJSON {
fmt.Printf("Cloning %s...\n", style.Bold.Render(remote))
}

cloneCmd := exec.Command(doltPath, "clone", remote, cloneDir)
if !wlBrowseJSON {
cloneCmd.Stderr = os.Stderr
}
if cloneErr := cloneCmd.Run(); cloneErr != nil {
os.RemoveAll(tmpDir)
return "", "", fmt.Errorf("cloning %s: %w\nEnsure the database exists on DoltHub: https://www.dolthub.com/%s", remote, cloneErr, remote)
}
if !wlBrowseJSON {
fmt.Printf("%s Cloned successfully\n\n", style.Bold.Render("✓"))
}
return cloneDir, tmpDir, nil
}

// BrowseFilter holds filter parameters for building a browse query.
type BrowseFilter struct {
Status string
Expand Down
7 changes: 4 additions & 3 deletions internal/cmd/wl_charsheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ func runWlCharsheet(cmd *cobra.Command, args []string) error {
handle = wlCfg.RigHandle
}

if !doltserver.DatabaseExists(townRoot, doltserver.WLCommonsDB) {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", doltserver.WLCommonsDB)
dbName := wasteland.ResolveDBName(townRoot)
if !doltserver.DatabaseExists(townRoot, dbName) {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", dbName)
}

store := doltserver.NewWLCommons(townRoot)
store := doltserver.NewWLCommonsWithDB(townRoot, dbName)
sheet, err := doltserver.AssembleCharacterSheet(store, handle)
if err != nil {
return fmt.Errorf("assembling character sheet: %w", err)
Expand Down
5 changes: 3 additions & 2 deletions internal/cmd/wl_claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ func runWlClaim(cmd *cobra.Command, args []string) error {
}
rigHandle := wlCfg.RigHandle

dbName := wasteland.ResolveDBName(townRoot)
var item *doltserver.WantedItem
if !doltserver.DatabaseExists(townRoot, doltserver.WLCommonsDB) {
if !doltserver.DatabaseExists(townRoot, dbName) {
// Fallback for wl-commons clone-based workspaces (join creates .wasteland clone).
if wlCfg.LocalDir == "" {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", doltserver.WLCommonsDB)
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", dbName)
}
if err := claimWantedInLocalClone(wlCfg.LocalDir, wantedID, rigHandle); err != nil {
return err
Expand Down
5 changes: 3 additions & 2 deletions internal/cmd/wl_done.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ func runWlDone(cmd *cobra.Command, args []string) error {

completionID := generateCompletionID(wantedID, rigHandle)

if !doltserver.DatabaseExists(townRoot, doltserver.WLCommonsDB) {
dbName := wasteland.ResolveDBName(townRoot)
if !doltserver.DatabaseExists(townRoot, dbName) {
// Fallback for wl-commons clone-based workspaces (join creates .wasteland clone).
if wlCfg.LocalDir == "" {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", doltserver.WLCommonsDB)
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", dbName)
}
if err := submitDoneInLocalClone(wlCfg.LocalDir, wantedID, rigHandle, wlDoneEvidence, completionID); err != nil {
return err
Expand Down
8 changes: 5 additions & 3 deletions internal/cmd/wl_scorekeeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/spf13/cobra"
"github.com/steveyegge/gastown/internal/doltserver"
"github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/wasteland"
"github.com/steveyegge/gastown/internal/workspace"
)

Expand Down Expand Up @@ -49,11 +50,12 @@ func runWlScorekeeper(cmd *cobra.Command, args []string) error {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}

if !doltserver.DatabaseExists(townRoot, doltserver.WLCommonsDB) {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", doltserver.WLCommonsDB)
dbName := wasteland.ResolveDBName(townRoot)
if !doltserver.DatabaseExists(townRoot, dbName) {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", dbName)
}

store := doltserver.NewWLCommons(townRoot)
store := doltserver.NewWLCommonsWithDB(townRoot, dbName)
return runScorekeeperWithStore(store)
}

Expand Down
21 changes: 19 additions & 2 deletions internal/cmd/wl_show.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ func runWLShow(cmd *cobra.Command, args []string) error {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}

// Fast path: query through the Dolt server if the database is registered.
dbName := wasteland.ResolveDBName(townRoot)
if doltserver.DatabaseExists(townRoot, dbName) {
store := doltserver.NewWLCommonsWithDB(townRoot, dbName)
return showWanted(store, wantedID, wlShowJSON)
}

// Fallback: read from local filesystem clone.
doltPath, err := exec.LookPath("dolt")
if err != nil {
return fmt.Errorf("dolt not found in PATH — install from https://docs.dolthub.com/introduction/installation")
Expand Down Expand Up @@ -103,13 +111,22 @@ func resolveWLCommonsClone(townRoot, doltPath string) (cloneDir, tmpDir string,
}

// No local clone — do a one-time clone-then-discard, like browse.
// Read upstream from config, or default to hop/wl-commons.
remote := "hop/wl-commons"
if cfg, cfgErr := wasteland.LoadConfig(townRoot); cfgErr == nil && cfg.Upstream != "" {
remote = cfg.Upstream
}
fmt.Fprintf(os.Stderr, "No local wl-commons clone found. Cloning temporarily.\nRun 'gt wl sync' to keep a persistent local copy.\n\n")
tmpDir, err = os.MkdirTemp("", "wl-show-*")
if err != nil {
return "", "", fmt.Errorf("creating temp directory: %w", err)
}
remote := "hop/wl-commons"
cloneDir = filepath.Join(tmpDir, "wl-commons")
parts := strings.SplitN(remote, "/", 2)
dbName := "wl-commons"
if len(parts) == 2 {
dbName = parts[1]
}
cloneDir = filepath.Join(tmpDir, dbName)
fmt.Printf("Cloning %s...\n", style.Bold.Render(remote))
cloneCmd := exec.Command(doltPath, "clone", remote, cloneDir)
cloneCmd.Stderr = os.Stderr
Expand Down
5 changes: 3 additions & 2 deletions internal/cmd/wl_stamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,10 @@ func runWlStamp(cmd *cobra.Command, args []string) error {
StampIndex: -1, // will be computed below
}

if !doltserver.DatabaseExists(townRoot, doltserver.WLCommonsDB) {
dbName := wasteland.ResolveDBName(townRoot)
if !doltserver.DatabaseExists(townRoot, dbName) {
if wlCfg.LocalDir == "" {
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", doltserver.WLCommonsDB)
return fmt.Errorf("database %q not found\nJoin a wasteland first with: gt wl join <org/db>", dbName)
}
return insertStampInLocalClone(wlCfg.LocalDir, stamp)
}
Expand Down
Loading