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".
This commit is contained in:
Oliver Jakoubek 2026-03-05 10:56:18 +01:00
commit 37ad950241
3 changed files with 191 additions and 10 deletions

73
AGENTS.md Normal file
View file

@ -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 <id> # View issue details
br update <id> --status in_progress # Claim work
br close <id> # 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 <issue> <depends-on>` 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 <id>`) 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

22
main.go
View file

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

106
main_test.go Normal file
View file

@ -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)
}
}