From 37ad9502412f874d3e2f5935b70a9f0869f960ac Mon Sep 17 00:00:00 2001 From: Oliver Jakoubek Date: Thu, 5 Mar 2026 10:56:18 +0100 Subject: [PATCH] fix: parse -o flag correctly to resolve output filename Fix bug where -o flag value was read as pointer instead of string value, causing memory addresses to be printed and output path to resolve to ".xlsx". --- AGENTS.md | 73 +++++++++++++++++++++++++++++++++++ main.go | 22 ++++++----- main_test.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 AGENTS.md create mode 100644 main_test.go diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bea7dbe --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ +# Agent Instructions + +## Plans + +- Make the plan extremely concise. Sacrifice grammar for the sake of concision. +- At the end of each plan, give me a list of unresolved questions to answer, if any. + +## Issue Tracking + +This project uses **br** (beads_rust) for issue tracking and **bv** (beads_viewer) for triage/prioritization. Issues are stored in `.beads/` and tracked in git. + +### Tool Roles + +| Tool | Role | Key Commands | +|------|------|--------------| +| `br` | Issue management (CRUD) | `br ready`, `br show`, `br update`, `br close`, `br sync` | +| `bv` | Triage & prioritization | `bv --robot-triage` | + +### Quick Reference + +```bash +br ready # Find available work +br show # View issue details +br update --status in_progress # Claim work +br close # Complete work +br sync # Sync with git +``` + +### What to work on next? + +```bash +bv --robot-triage # AI-powered recommendation for next issue (preferred) +br ready # Fallback: plain list of unblocked issues +``` + +> **Warning:** bare `bv` (without flags) launches an interactive TUI that blocks the session. Always use `bv --robot-triage` or other non-interactive flags in automated contexts. + +### Key Concepts + +- **Dependencies**: Issues can block other issues. `br ready` shows only unblocked work. +- **Priority**: P0=critical, P1=high, P2=medium, P3=low, P4=backlog (use numbers 0-4, not words) +- **Types**: task, bug, feature, epic, chore, docs, question +- **Blocking**: `br dep add ` to add dependencies + + +### Creating issues + +You might be tasked with creating a new issue or you discover new tasks by yourself. Use the `/create-single-issue` command accordingly the create a new issue. + +## Main Workflow + +### Work on issue + +You are tasked by the operator to work on an issue. +It's either a specific issue (`/start-issue `) or the *next* issue (`/start-next-issue`). +The command tells you to open the issue, enter plan mode and then implement the plan. +The command tells you explicitly *NOT* to: close the issue, commit or push anything (because this is subject to `/finish-issue`). + +### Operator tests manually + +After finishing the implementation the operator tests the solution. + +### Finish issue + +After testing you are tasked by the operator to finish the issue (`/finish-issue`). +You close the issue and create a commit. + +## Release Workflow + +Once in a while the operator uses these commands: + +- `/tag-version` - you create a new version using the `git tag` mechanism +- `/update-changelog` - you update the CHANGELOG.md according to the changes of the last version diff --git a/main.go b/main.go index 7e98611..8fd8b0a 100644 --- a/main.go +++ b/main.go @@ -26,10 +26,13 @@ func main() { os.Exit(1) } - fmt.Println(sep) - fmt.Println(enc) - fmt.Println(out) + if err := run(files, *out, *sep, *enc); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} +func run(files []string, out, sep, enc string) error { xlsx := excelize.NewFile() firstSheet := true @@ -49,11 +52,11 @@ func main() { defer f.Close() var reader io.Reader = f - if *enc == "windows1252" { + if enc == "windows1252" { reader = transform.NewReader(f, charmap.Windows1252.NewDecoder()) } - delimiter := detectDelimiter(path, *sep) + delimiter := detectDelimiter(path, sep) csvReader := csv.NewReader(reader) csvReader.Comma = delimiter @@ -85,12 +88,11 @@ func main() { fmt.Printf("✓ %s → Reiter \"%s\" (%d Zeilen)\n", path, sheetName, row-1) } - if err := xlsx.SaveAs(*out); err != nil { - fmt.Fprintf(os.Stderr, "Fehler beim Speichern: %v\n", err) - os.Exit(1) + if err := xlsx.SaveAs(out); err != nil { + return fmt.Errorf("Fehler beim Speichern: %w", err) } - fmt.Printf("✅ Gespeichert: %s\n", *out) - + fmt.Printf("✅ Gespeichert: %s\n", out) + return nil } func detectDelimiter(path, hint string) rune { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..b95978e --- /dev/null +++ b/main_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/xuri/excelize/v2" +) + +func writeTempCSV(t *testing.T, dir, name, content string) string { + t.Helper() + path := filepath.Join(dir, name) + if err := os.WriteFile(path, []byte(content), 0644); err != nil { + t.Fatalf("WriteFile: %v", err) + } + return path +} + +func TestRun_OutputPath(t *testing.T) { + dir := t.TempDir() + csv := writeTempCSV(t, dir, "data.csv", "name,age\nAlice,30\nBob,25\n") + out := filepath.Join(dir, "result.xlsx") + + if err := run([]string{csv}, out, "auto", "utf8"); err != nil { + t.Fatalf("run() error: %v", err) + } + + if _, err := os.Stat(out); os.IsNotExist(err) { + t.Fatalf("output file not created: %s", out) + } +} + +func TestRun_SheetContent(t *testing.T) { + dir := t.TempDir() + csv := writeTempCSV(t, dir, "sheet1.csv", "col1;col2\nfoo;bar\n") + out := filepath.Join(dir, "out.xlsx") + + if err := run([]string{csv}, out, ";", "utf8"); err != nil { + t.Fatalf("run() error: %v", err) + } + + f, err := excelize.OpenFile(out) + if err != nil { + t.Fatalf("OpenFile: %v", err) + } + + val, err := f.GetCellValue("sheet1", "A1") + if err != nil { + t.Fatalf("GetCellValue: %v", err) + } + if val != "col1" { + t.Errorf("A1 = %q, want %q", val, "col1") + } + + val, err = f.GetCellValue("sheet1", "B2") + if err != nil { + t.Fatalf("GetCellValue: %v", err) + } + if val != "bar" { + t.Errorf("B2 = %q, want %q", val, "bar") + } +} + +func TestRun_MultipleSheets(t *testing.T) { + dir := t.TempDir() + csv1 := writeTempCSV(t, dir, "alpha.csv", "x\n1\n") + csv2 := writeTempCSV(t, dir, "beta.csv", "y\n2\n") + out := filepath.Join(dir, "multi.xlsx") + + if err := run([]string{csv1, csv2}, out, "auto", "utf8"); err != nil { + t.Fatalf("run() error: %v", err) + } + + f, err := excelize.OpenFile(out) + if err != nil { + t.Fatalf("OpenFile: %v", err) + } + + sheets := f.GetSheetList() + if len(sheets) != 2 { + t.Fatalf("got %d sheets, want 2", len(sheets)) + } + if sheets[0] != "alpha" || sheets[1] != "beta" { + t.Errorf("sheets = %v, want [alpha beta]", sheets) + } +} + +func TestRun_CustomOutputFilename(t *testing.T) { + dir := t.TempDir() + csv := writeTempCSV(t, dir, "input.csv", "a,b\n1,2\n") + out := filepath.Join(dir, "custom_name.xlsx") + + if err := run([]string{csv}, out, ",", "utf8"); err != nil { + t.Fatalf("run() error: %v", err) + } + + if _, err := os.Stat(out); os.IsNotExist(err) { + t.Fatalf("expected output at %s, not found", out) + } + // ensure default name was NOT created + defaultOut := filepath.Join(dir, "output.xlsx") + if _, err := os.Stat(defaultOut); !os.IsNotExist(err) { + t.Errorf("unexpected file created at %s", defaultOut) + } +}