diff --git a/TABLE_IMPROVEMENT_PLAN.md b/TABLE_IMPROVEMENT_PLAN.md index 947f507..027d0fd 100644 --- a/TABLE_IMPROVEMENT_PLAN.md +++ b/TABLE_IMPROVEMENT_PLAN.md @@ -158,17 +158,7 @@ get_sync_status :: proc(result: SyncFlag, err_msg: string) -> string { - Use `context.temp_allocator` more effectively - Pre-allocate buffers for expected sizes -### Phase 4: JSON Output Separation - -#### 4.1 Unified JSON Rendering -```odin -render_json_rows :: proc(writer: io.Writer, rows: any, field_names: []string) -``` -- Create centralized JSON rendering helper -- Work with same structs as table rendering -- Use reflection or explicit field marshaling - -#### 4.2 Format-Agnostic Interface +### Phase 4: Format-Agnostic Interface - Commands generate data → renderers handle format - Table renderer focuses only on ASCII/Unicode output - Keep terminal detection in command layer diff --git a/TODOS.md b/TODOS.md index ec39e96..8cf5c7c 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,8 +1,6 @@ # TODOs -1. envr scan crashes when there are zero results. - -27. Commands are still leaking. +1. Commands are still leaking. 28. **db.odin** — Inconsistencies in how struct vs sqlite are named. @@ -26,21 +24,19 @@ 12. **cmd_sync.odin:80, cmd_list.odin:33** — `make([]string, 2)` for table rows never freed. Leaks per row. Defer to memory pass. -13. **cmd_list.odin** — Non-TTY branch builds `ListEntry` structs and marshals JSON separately. Now that `render_json_rows` (issue 1) accepts an `io.Writer` and uses `json.marshal`, unify both branches to use it. Note: will change JSON keys from `"directory"/"path"` to `"Directory"/"Path"`. +13. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`. -14. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`. +14. Add a text filter to the multi_select. -15. Add a text filter to the multi_select. +16. Add tests for untested commands. -17. Add tests for untested commands. +17. 2 scan tests silently skip when fd isn't installed, tests pass without actually testing anything. These should use #assert to be sure that fd is in path. -18. 2 scan tests silently skip when fd isn't installed, tests pass without actually testing anything. These should use #assert to be sure that fd is in path. +19. add --format -f flag to commands that draw tables. -20. add --format -f flag to commands that draw tables. +20. Replace `testing.expect` calls with `testing.expect_value` calls where appropriate. -21. Replace `testing.expect` calls with `testing.expect_value` calls where appropriate. - -22. Change struct field names from PascalCase to snake_case. +21. Change struct field names from PascalCase to snake_case. 23. procedures should be ordered by use, main at the top, then in the order they are called from main. diff --git a/table.odin b/table.odin index dd55290..2e967c5 100644 --- a/table.odin +++ b/table.odin @@ -1,6 +1,5 @@ package main -import "core:encoding/json" import "core:fmt" import "core:io" import "core:strings" @@ -96,22 +95,3 @@ render_table :: proc(w: io.Writer, headers: []string, rows: [][]string) { hline(w, &b, "\u2514", "\u2534", "\u2518", col_widths) } -render_json_rows :: proc(w: io.Writer, headers: []string, rows: [][]string) { - entries := make([dynamic]map[string]string, 0, len(rows), context.temp_allocator) - - for row in rows { - entry := make(map[string]string, len(headers), context.temp_allocator) - for i in 0 ..< len(headers) { - entry[headers[i]] = row[i] - } - append(&entries, entry) - } - - data, err := json.marshal(entries[:], allocator = context.temp_allocator) - if err != nil { - fmt.eprintf("Error marshaling JSON: %v\n", err) - return - } - fmt.wprintf(w, "%s", data, flush = false) -} - diff --git a/table_test.odin b/table_test.odin index 747ecc1..30a0331 100644 --- a/table_test.odin +++ b/table_test.odin @@ -1,109 +1,11 @@ #+test package main -import "core:encoding/json" import "core:fmt" import "core:strings" import "core:terminal/ansi" import "core:testing" -@(test) -test_render_json_rows_normal :: proc(t: ^testing.T) { - b: strings.Builder - strings.builder_init(&b) - defer strings.builder_destroy(&b) - - headers := []string{"name", "path"} - rows := [][]string{{"foo", "/home/user/.env"}, {"bar", "/home/user/project/.env"}} - - w := strings.to_writer(&b) - render_json_rows(w, headers, rows) - - output := strings.to_string(b) - - result: []map[string]string = --- - unmarshal_err := json.unmarshal_string(output, &result, allocator = context.temp_allocator) - testing.expect( - t, - unmarshal_err == nil, - fmt.tprintf("json unmarshal failed: %v\noutput was: %q", unmarshal_err, output), - ) - testing.expect(t, len(result) == 2, fmt.tprintf("expected 2 rows, got %d", len(result))) - testing.expect( - t, - result[0]["name"] == "foo", - fmt.tprintf("expected name=foo, got %q", result[0]["name"]), - ) - testing.expect(t, result[0]["path"] == "/home/user/.env") - testing.expect(t, result[1]["name"] == "bar") - testing.expect(t, result[1]["path"] == "/home/user/project/.env") -} - -@(test) -test_render_json_rows_special_chars :: proc(t: ^testing.T) { - b: strings.Builder - strings.builder_init(&b) - defer strings.builder_destroy(&b) - - headers := []string{"key", "value"} - rows := [][]string { - {"quote", `has "double quotes"`}, - {"backslash", `path\to\file`}, - {"newline", "line1\nline2"}, - {"mixed", `a "b" c\nd`}, - } - - w := strings.to_writer(&b) - render_json_rows(w, headers, rows) - - output := strings.to_string(b) - - result: []map[string]string = --- - unmarshal_err := json.unmarshal( - transmute([]byte)output, - &result, - allocator = context.temp_allocator, - ) - testing.expect( - t, - unmarshal_err == nil, - fmt.tprintf("json unmarshal failed: %v\noutput was: %q", unmarshal_err, output), - ) - testing.expect(t, len(result) == 4) - testing.expect( - t, - result[0]["value"] == `has "double quotes"`, - fmt.tprintf("got %q", result[0]["value"]), - ) - testing.expect(t, result[1]["value"] == `path\to\file`) - testing.expect(t, result[2]["value"] == "line1\nline2") - testing.expect(t, result[3]["value"] == `a "b" c\nd`) -} - -@(test) -test_render_json_rows_empty :: proc(t: ^testing.T) { - b: strings.Builder - strings.builder_init(&b) - defer strings.builder_destroy(&b) - - headers := []string{"name"} - rows: [][]string - - w := strings.to_writer(&b) - render_json_rows(w, headers, rows) - - output := strings.to_string(b) - - result: []map[string]string = --- - unmarshal_err := json.unmarshal_string(output, &result, allocator = context.temp_allocator) - testing.expect( - t, - unmarshal_err == nil, - fmt.tprintf("json unmarshal failed: %v\noutput was: %q", unmarshal_err, output), - ) - testing.expect(t, len(result) == 0) -} - @(test) test_render_table_normal :: proc(t: ^testing.T) { b: strings.Builder