diff --git a/.beads/.gitignore b/.beads/.gitignore deleted file mode 100644 index f32e807..0000000 --- a/.beads/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -# Database -*.db -*.db-shm -*.db-wal - -# Lock files -*.lock - -# Temporary -last-touched -*.tmp diff --git a/.beads/config.yaml b/.beads/config.yaml deleted file mode 100644 index c3abc97..0000000 --- a/.beads/config.yaml +++ /dev/null @@ -1 +0,0 @@ -issue_prefix: csv diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl deleted file mode 100644 index ed93a6d..0000000 --- a/.beads/issues.jsonl +++ /dev/null @@ -1,2 +0,0 @@ -{"id":"bd-3rr","title":"Fix -o flag: output filename not parsed correctly","description":"## Bug Report\n\nUsing the `-o` flag to specify an output filename causes errors. Instead of using the provided filename, the tool attempts to open/save `.xlsx` (empty basename) and prints memory addresses.\n\n## Reproduction\n\n```\ncsv2excel.exe -enc=windows1252 -o=test.xlsx .\\WDK-SiWo-20260103.csv\n```\n\n## Actual Output\n\n```\n0x3eb5d9d181b0\n0x3eb5d9d181c0\n0x3eb5d9d181d0\nFehler beim Öffnen .xlsx: open .xlsx: The system cannot find the file specified.\n✓ .\\WDK-SiWo-20260103.csv → Reiter \"WDK-SiWo-20260103\" (5324 Zeilen)\nFehler beim Speichern: unsupported workbook file format\n```\n\n## Analysis\n\n- Memory addresses being printed suggest a pointer/value is being passed where a string is expected (e.g. `fmt.Println(&flag)` instead of `fmt.Println(*flag)`)\n- The output path resolves to `.xlsx` instead of `test.xlsx`, meaning the flag value is not read correctly\n- The `unsupported workbook file format` error is a consequence of trying to open a non-existent file as a workbook\n\n## Acceptance Criteria\n- [ ] `csv2excel -o=test.xlsx input.csv` writes output to `test.xlsx`\n- [ ] No memory addresses are printed to stdout/stderr\n- [ ] Error message is shown if the output path is invalid/unwritable\n- [ ] Tests written and passing","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-03-05T09:37:26.921804683Z","created_by":"oli","updated_at":"2026-03-05T12:10:00.000000000Z","closed_at":"2026-03-05T12:10:00.000000000Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0} -{"id":"bd-bxt","title":"Add --version flag to CLI","description":"## Description\n\nAdd a `--version` flag to the `csv2excel` CLI that outputs version information including the version tag and build timestamp.\n\n## Expected Output\n\n```\ncsv2excel version v0.10.0-1-g3eb502a built 2026-02-19T14:30:13Z\n```\n\nThe version string should be injected at build time using Go linker flags (`-ldflags`), so that `git describe` and a build timestamp are embedded into the binary.\n\n## Implementation Notes\n\n- Define `version` and `buildDate` variables in `main.go` (or a dedicated `version.go`)\n- Inject values via `-ldflags \"-X main.version=... -X main.buildDate=...\"` during `mage Build`\n- Handle the `--version` / `-v` flag and print the version line, then exit 0\n\n## Acceptance Criteria\n- [ ] `csv2excel --version` prints a line matching `csv2excel version built `\n- [ ] Version and build date are injected at build time via ldflags in the Mage build targets\n- [ ] Running `csv2excel --version` exits with code 0\n- [ ] Default (unset) values produce a sensible fallback (e.g. `dev` / `unknown`)\n- [ ] Tests written and passing (if applicable)","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-03-05T09:22:40.538253362Z","created_by":"oli","updated_at":"2026-03-05T12:10:00.000000000Z","closed_at":"2026-03-05T12:10:00.000000000Z","close_reason":"done","source_repo":".","compaction_level":0,"original_size":0} diff --git a/.beads/metadata.json b/.beads/metadata.json deleted file mode 100644 index c787975..0000000 --- a/.beads/metadata.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "database": "beads.db", - "jsonl_export": "issues.jsonl" -} \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f416..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/csv2excel.iml b/.idea/csv2excel.iml deleted file mode 100644 index 5e764c4..0000000 --- a/.idea/csv2excel.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml deleted file mode 100644 index d7202f0..0000000 --- a/.idea/go.imports.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 1b6b2e4..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index bea7dbe..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,73 +0,0 @@ -# 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/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 0b4595e..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [v0.1.1] - 2026-03-05 - -### Fixed - -- `-o` flag value was read as a pointer instead of a string, causing memory addresses to be printed and the output path to resolve to `.xlsx` - -## [v0.1.0] - 2026-03-05 - -### Added - -- Initial release: CLI tool to convert one or more CSV files into a single Excel (.xlsx) workbook -- Each CSV file becomes a separate worksheet named after the source filename -- Auto-detection of CSV delimiter (`,`, `;`, `\t`) based on first-line analysis -- Support for UTF-8 and Windows-1252 encoding (`-enc` flag) -- Configurable output file path (`-o` flag) -- Tolerant CSV parsing with `LazyQuotes` for malformed files -- Build system via [Mage](https://magefile.org/) with targets for Linux, Windows, and install -- Version info injected at build time via git tags (`ldflags`) - -### Documentation - -- README with installation instructions, flag reference, and usage examples diff --git a/magefiles/magefile.go b/magefiles/magefile.go index 4cbb769..d9357e8 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -7,7 +7,6 @@ import ( "os" "path/filepath" "runtime" - "time" "github.com/magefile/mage/mg" "github.com/magefile/mage/sh" @@ -24,9 +23,12 @@ func ldflags() (string, error) { if err != nil { commit = "none" } - buildDate := time.Now().UTC().Format(time.RFC3339) + buildDate, err := sh.Output("date", "-u", "+%Y-%m-%dT%H:%M:%SZ") + if err != nil { + buildDate = "unknown" + } return fmt.Sprintf( - `-X code.beautifulmachines.dev/jakoubek/csv2excel/internal/version.Version=%s -X code.beautifulmachines.dev/jakoubek/csv2excel/internal/version.Commit=%s -X code.beautifulmachines.dev/jakoubek/csv2excel/internal/version.BuildDate=%s`, + `-X csv2excel/internal/version.Version=%s -X csv2excel/internal/version.Commit=%s -X csv2excel/internal/version.BuildDate=%s`, version, commit, buildDate, ), nil } @@ -75,7 +77,6 @@ func BuildWindows() error { // Install installs the binary to $GOBIN or $GOPATH/bin. func Install() error { - mg.Deps(Build) fmt.Println("Installing", binaryName, "...") flags, err := ldflags() if err != nil { diff --git a/main.go b/main.go index 7461ccc..7e98611 100644 --- a/main.go +++ b/main.go @@ -12,57 +12,27 @@ import ( "github.com/xuri/excelize/v2" "golang.org/x/text/encoding/charmap" "golang.org/x/text/transform" - - "code.beautifulmachines.dev/jakoubek/csv2excel/internal/version" ) func main() { sep := flag.String("sep", "auto", "Trennzeichen: auto, ',', ';', '\\t'") enc := flag.String("enc", "utf8", "Encoding: utf8, windows1252") out := flag.String("o", "output.xlsx", "Ausgabedatei") - showVersion := flag.Bool("version", false, "Version anzeigen") flag.Parse() - if *showVersion { - fmt.Printf("csv2excel %s (commit %s, built %s)\n", version.Version, version.Commit, version.BuildDate) - os.Exit(0) - } - - // PowerShell kann "-o=testdatei.xlsx" in "-o=testdatei" + ".xlsx" splitten - if filepath.Ext(*out) == "" { - *out += ".xlsx" - } - files := flag.Args() if len(files) == 0 { - fmt.Fprintln(os.Stderr, "Verwendung: csv2excel [flags] datei1.csv datei2.csv ...") - fmt.Fprintln(os.Stderr, "Tipp: In PowerShell Leerzeichen statt = verwenden: -o ausgabe.xlsx") + fmt.Fprintln(os.Stderr, "Verwendung: csv2xlsx [flags] datei1.csv datei2.csv ...") os.Exit(1) } - if err := run(files, *out, *sep, *enc); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} + fmt.Println(sep) + fmt.Println(enc) + fmt.Println(out) -func run(files []string, out, sep, enc string) error { xlsx := excelize.NewFile() firstSheet := true - var csvFiles []string - for _, f := range files { - if strings.EqualFold(filepath.Ext(f), ".xlsx") { - fmt.Fprintf(os.Stderr, "Warnung: %s übersprungen – Excel-Datei als Input? Meintest du: -o=%s\n", f, f) - continue - } - csvFiles = append(csvFiles, f) - } - files = csvFiles - if len(files) == 0 { - return fmt.Errorf("keine CSV-Dateien zum Verarbeiten") - } - for _, path := range files { sheetName := strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) @@ -79,11 +49,11 @@ func run(files []string, out, sep, enc string) error { 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 @@ -115,11 +85,12 @@ func run(files []string, out, sep, enc string) error { fmt.Printf("✓ %s → Reiter \"%s\" (%d Zeilen)\n", path, sheetName, row-1) } - if err := xlsx.SaveAs(out); err != nil { - return fmt.Errorf("Fehler beim Speichern: %w", err) + if err := xlsx.SaveAs(*out); err != nil { + fmt.Fprintf(os.Stderr, "Fehler beim Speichern: %v\n", err) + os.Exit(1) } - fmt.Printf("✅ Gespeichert: %s\n", out) - return nil + fmt.Printf("✅ Gespeichert: %s\n", *out) + } func detectDelimiter(path, hint string) rune { diff --git a/main_test.go b/main_test.go deleted file mode 100644 index b95978e..0000000 --- a/main_test.go +++ /dev/null @@ -1,106 +0,0 @@ -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) - } -}