mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 18:48:33 -04:00
Compare commits
8 Commits
427a67dcb4
...
2229affe69
| Author | SHA1 | Date | |
|---|---|---|---|
| 2229affe69 | |||
| 6394c42d8b | |||
| 8e00c78f12 | |||
| 8d2c5403e8 | |||
| 7c7d3d5c23 | |||
| 6151a5efaf | |||
| fbb1f86945 | |||
| 0c5aa74256 |
268
TABLE_IMPROVEMENT_PLAN.md
Normal file
268
TABLE_IMPROVEMENT_PLAN.md
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
# Table Rendering Memory Optimization Plan
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This plan outlines improvements to eliminate excessive memory allocations and copies in the Odin table rendering system. The current implementation makes 10+ allocations per row, while the Zig equivalent makes zero allocations for rendering. This optimization will reduce memory usage, improve performance, and align with the project's efficiency goals.
|
||||||
|
|
||||||
|
## Current State Analysis
|
||||||
|
|
||||||
|
### Zig Version (Reference Implementation)
|
||||||
|
- **Allocations**: 1 (data only)
|
||||||
|
- **Data copies**: 0
|
||||||
|
- **String allocation**: 0
|
||||||
|
- **Column widths**: Stack array
|
||||||
|
- **Output**: Direct to writer
|
||||||
|
|
||||||
|
### Odin Version (Current Implementation)
|
||||||
|
- **Allocations**: 10+ per row
|
||||||
|
- **Data copies**: Multiple per row
|
||||||
|
- **String allocation**: 2+ per row (concatenate + slice)
|
||||||
|
- **Column widths**: Heap allocated
|
||||||
|
- **Output**: Builder → stdout
|
||||||
|
|
||||||
|
### Current Issues Identified
|
||||||
|
|
||||||
|
1. **Table Infrastructure** (`table.odin`)
|
||||||
|
- Uses `strings.Builder` which allocates per-line memory
|
||||||
|
- Heap-allocated `[dynamic]int` for column widths
|
||||||
|
- Multiple `strings.concatenate()` calls creating new strings
|
||||||
|
|
||||||
|
2. **Command Implementations**
|
||||||
|
- `cmd_list`: Creates intermediate `[]string` slices per row, allocates new strings via `strings.concatenate()`
|
||||||
|
- `cmd_sync`: Creates `SyncEntry` structs with cloned strings, allocates dynamic arrays
|
||||||
|
- `cmd_deps`: Allocates dynamic rows array unnecessarily
|
||||||
|
|
||||||
|
3. **Memory Pattern**
|
||||||
|
- Each command allocates `[][]string` for table data
|
||||||
|
- Manual struct-to-row transformation creates copies
|
||||||
|
- Duplicate code across all table-using commands
|
||||||
|
|
||||||
|
## Proposed Solutions
|
||||||
|
|
||||||
|
### Phase 1: Core Table Infrastructure Overhaul
|
||||||
|
|
||||||
|
#### 1.1 Direct Writer-Based Rendering
|
||||||
|
**Current:**
|
||||||
|
```odin
|
||||||
|
b: strings.Builder
|
||||||
|
strings.builder_init(&b)
|
||||||
|
// ... build table in builder
|
||||||
|
fmt.println(strings.to_string(b))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed:**
|
||||||
|
```odin
|
||||||
|
render_table :: proc(writer: io.Writer, headers: []string, rows: [][]string)
|
||||||
|
```
|
||||||
|
- Replace `strings.Builder` with `io.Writer` output
|
||||||
|
- Eliminate intermediate string allocations
|
||||||
|
- Write table components directly to output stream
|
||||||
|
|
||||||
|
#### 1.2 Stack-Based Column Widths
|
||||||
|
**Current:**
|
||||||
|
```odin
|
||||||
|
col_widths := make([dynamic]int, 0, len(headers))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed:**
|
||||||
|
- Use fixed stack arrays for reasonable column counts
|
||||||
|
- Implement small buffer optimization (SBO) for variable column counts
|
||||||
|
- Only allocate for tables exceeding threshold (e.g., 16 columns)
|
||||||
|
|
||||||
|
#### 1.3 Zero-Copy String Handling
|
||||||
|
**Current:**
|
||||||
|
```odin
|
||||||
|
dir_str := strings.concatenate({row.Dir, "/"}, context.temp_allocator)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed:**
|
||||||
|
- Replace `strings.concatenate()` with string slicing
|
||||||
|
- Work directly with `EnvFile.Path` and `EnvFile.Dir` fields
|
||||||
|
- Use `filepath.base()` and `filepath.dir()` without allocation where possible
|
||||||
|
|
||||||
|
### Phase 2: Generic Table Interface
|
||||||
|
|
||||||
|
#### 2.1 Field-Based Table Renderer
|
||||||
|
```odin
|
||||||
|
Table_Field :: struct {
|
||||||
|
name: string,
|
||||||
|
value: string, // String view, no allocation
|
||||||
|
alignment: Alignment,
|
||||||
|
}
|
||||||
|
|
||||||
|
Table_Config :: struct {
|
||||||
|
writer: io.Writer,
|
||||||
|
fields: []Table_Field,
|
||||||
|
col_widths: []int,
|
||||||
|
}
|
||||||
|
|
||||||
|
render_row :: proc(cfg: Table_Config, row_data: any)
|
||||||
|
```
|
||||||
|
- Accept struct fields directly without intermediate arrays
|
||||||
|
- Support field selection (show only specific fields)
|
||||||
|
- Alignment options (left/center/right)
|
||||||
|
|
||||||
|
#### 2.2 Field Extraction Procs
|
||||||
|
- Generate field extraction helpers for each struct type
|
||||||
|
- Avoid string allocation by returning string views
|
||||||
|
- Cache computed values (like formatted status strings)
|
||||||
|
|
||||||
|
#### 2.3 Streaming Table Processing
|
||||||
|
- Process rows one at a time without collecting all rows
|
||||||
|
- Reduce peak memory usage from O(N × strings) to O(table_structure)
|
||||||
|
- Enable early termination if needed
|
||||||
|
|
||||||
|
### Phase 3: Command-Specific Optimizations
|
||||||
|
|
||||||
|
#### 3.1 Eliminate Intermediate Structs
|
||||||
|
**Current (cmd_sync):**
|
||||||
|
```odin
|
||||||
|
for &file in files {
|
||||||
|
// ... processing
|
||||||
|
path_str, _ := strings.clone(file.Path)
|
||||||
|
status_str, _ := strings.clone(status)
|
||||||
|
append(&results, SyncEntry{Path = path_str, Status = status_str})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed:**
|
||||||
|
```odin
|
||||||
|
for &file in files {
|
||||||
|
result, err_msg := db_sync(&db, &file)
|
||||||
|
// Direct rendering with zero-copy
|
||||||
|
render_sync_row(writer, file, result, err_msg)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- `cmd_sync`: Work directly with `EnvFile` + `SyncFlagEnum`
|
||||||
|
- `cmd_list`: Use `EnvFile` fields directly, no `ListEntry`
|
||||||
|
- Generate table content on-the-fly
|
||||||
|
|
||||||
|
#### 3.2 In-Place Status Computation
|
||||||
|
```odin
|
||||||
|
get_sync_status :: proc(result: SyncFlag, err_msg: string) -> string {
|
||||||
|
switch {
|
||||||
|
case .Error in result: return if len(err_msg) > 0 then err_msg else "error"
|
||||||
|
case .BackedUp in result: return "Backed Up"
|
||||||
|
case .Restored in result: return "Restored"
|
||||||
|
case .DirUpdated in result: return "Moved"
|
||||||
|
case: return "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Compute status strings without allocation (use static lookup)
|
||||||
|
- Cache formatted status values if needed
|
||||||
|
- Reduce allocation count from N to 0 or 1
|
||||||
|
|
||||||
|
#### 3.3 Batch Processing
|
||||||
|
- Reduce allocation count by pooling small allocations
|
||||||
|
- 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
|
||||||
|
- Commands generate data → renderers handle format
|
||||||
|
- Table renderer focuses only on ASCII/Unicode output
|
||||||
|
- Keep terminal detection in command layer
|
||||||
|
|
||||||
|
## Expected Improvements
|
||||||
|
|
||||||
|
| Metric | Current | Target | Improvement |
|
||||||
|
|--------|---------|--------|-------------|
|
||||||
|
| **Allocations** | 10+ per row | 0-1 per table | 10x+ reduction |
|
||||||
|
| **Memory copies** | 2-3 per row | 0 | 100% reduction |
|
||||||
|
| **Peak memory** | O(N × strings) | O(table_structure) | Constant factor |
|
||||||
|
| **Throughput** | Baseline | 2-3x faster | Performance boost |
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### High-Priority Changes
|
||||||
|
1. Replace `strings.Builder` with direct `io.Writer` output
|
||||||
|
2. Convert column widths to stack-based allocation
|
||||||
|
3. Eliminate intermediate struct allocations in commands
|
||||||
|
|
||||||
|
### Medium-Priority Changes
|
||||||
|
1. Create generic field-based table interface
|
||||||
|
2. Implement streaming table processing
|
||||||
|
3. Centralize JSON rendering logic
|
||||||
|
|
||||||
|
### Low-Priority Changes
|
||||||
|
1. Add alignment options beyond left-aligned
|
||||||
|
2. Implement comprehensive field introspection
|
||||||
|
3. Add advanced table formatting features
|
||||||
|
|
||||||
|
## Tradeoff Questions
|
||||||
|
|
||||||
|
Before implementation begins, we need to resolve these architectural questions:
|
||||||
|
|
||||||
|
### 1. Generality vs. Performance
|
||||||
|
**Question:** Should we create a fully generic table renderer (similar to Zig's `Table(T)`) or focus on optimizing the current 3 use cases first?
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- **Generic approach**: Higher development cost, future-proof, may have some overhead
|
||||||
|
- **Specific optimization**: Faster implementation, maximum performance for current use cases, less flexible
|
||||||
|
|
||||||
|
**Recommendation:** Start with specific optimizations for current use cases, then generalize patterns that emerge.
|
||||||
|
|
||||||
|
### 2. Alignment Support
|
||||||
|
**Question:** Does the project need left/center/right alignment support, or is left-alignment sufficient?
|
||||||
|
|
||||||
|
**Context:** Zig supports alignment options, but current Odin implementation only left-aligns. Most CLI tables work fine with left alignment.
|
||||||
|
|
||||||
|
**Recommendation:** Start with left-alignment only, add alignment if specific use cases demand it.
|
||||||
|
|
||||||
|
### 3. API Compatibility
|
||||||
|
**Question:** Should we maintain the current `render_table()` API signature, or are breaking changes acceptable?
|
||||||
|
|
||||||
|
**Current API:**
|
||||||
|
```odin
|
||||||
|
render_table :: proc(headers: []string, rows: [][]string)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- **Maintain API**: Slower to implement, backward compatible, may need adapter layers
|
||||||
|
- **Break API**: Faster implementation, cleaner code, requires updates to all callers
|
||||||
|
|
||||||
|
**Recommendation:** Breaking changes are acceptable since this is an optimization-focused effort and callers are limited to 3 commands.
|
||||||
|
|
||||||
|
### 4. Odin Capabilities
|
||||||
|
**Question:** What runtime reflection or field introspection capabilities does Odin provide?
|
||||||
|
|
||||||
|
**Context:** Zig uses `@typeInfo()` and comptime field iteration. We need to understand Odin's equivalent capabilities to design the optimal solution.
|
||||||
|
|
||||||
|
**Recommendation:** Investigate Odin's runtime type information capabilities before finalizing the generic table interface design.
|
||||||
|
|
||||||
|
### 5. Testing Strategy
|
||||||
|
**Question:** Should we add comprehensive tests for new table rendering before optimizing commands, or optimize incrementally with tests added afterwards?
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- **Test-first**: More robust, catches regressions early, slower initial development
|
||||||
|
- **Optimize-first**: Faster development, may miss edge cases, requires retroactive testing
|
||||||
|
|
||||||
|
**Recommendation:** Hybrid approach - add basic tests for core infrastructure, then optimize incrementally with additional tests for each command.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Research Phase**: Investigate Odin's type system and reflection capabilities
|
||||||
|
2. **Prototype Phase**: Create minimal working prototype of zero-allocation table renderer
|
||||||
|
3. **Refactor Phase**: Incrementally update commands to use new infrastructure
|
||||||
|
4. **Test Phase**: Add comprehensive tests and verify memory improvements
|
||||||
|
5. **Benchmark Phase**: Measure performance improvements and memory usage
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- [ ] Zero allocations for table rendering (excluding initial data)
|
||||||
|
- [ ] Zero string copies in the happy path
|
||||||
|
- [ ] All 3 commands (`list`, `sync`, `deps`) use new infrastructure
|
||||||
|
- [ ] Performance improvement of 2x or more
|
||||||
|
- [ ] Memory usage reduction of 50% or more
|
||||||
|
- [ ] No regression in table formatting quality
|
||||||
|
- [ ] Backward compatibility with JSON output format
|
||||||
39
TODOS.md
39
TODOS.md
@@ -63,3 +63,42 @@ Note: These todos can wait until all the subcommands have been ported.
|
|||||||
28. 2 scan tests silently skip Low When fd isn't installed, tests pass without actually testing anything. These should use #assert to be sure that fd is in path.
|
28. 2 scan tests silently skip Low When fd isn't installed, tests pass without actually testing anything. These should use #assert to be sure that fd is in path.
|
||||||
|
|
||||||
38. Try to do all encryption / decryption in memory - only read / write encrypted data to disk.
|
38. Try to do all encryption / decryption in memory - only read / write encrypted data to disk.
|
||||||
|
|
||||||
|
## Double-check AI output
|
||||||
|
|
||||||
|
- [ ] cli.odin
|
||||||
|
- [ ] config.odin
|
||||||
|
- [ ] crypto.odin
|
||||||
|
- [ ] db.odin
|
||||||
|
- [ ] features.odin
|
||||||
|
- [ ] main.odin
|
||||||
|
- [ ] prompt.odin
|
||||||
|
- [ ] scan.odin
|
||||||
|
- [ ] sodium.odin
|
||||||
|
- [ ] ssh.odin
|
||||||
|
- [ ] table.odin
|
||||||
|
- [ ] cmd_backup.odin
|
||||||
|
- [ ] cmd_check.odin
|
||||||
|
- [ ] cmd_deps.odin
|
||||||
|
- [ ] cmd_edit_config.odin
|
||||||
|
- [ ] cmd_init.odin
|
||||||
|
- [ ] cmd_list.odin
|
||||||
|
- [ ] cmd_nushell_completion.odin
|
||||||
|
- [ ] cmd_remove.odin
|
||||||
|
- [ ] cmd_restore.odin
|
||||||
|
- [ ] cmd_scan.odin
|
||||||
|
- [ ] cmd_sync.odin
|
||||||
|
- [ ] cmd_version.odin
|
||||||
|
- [ ] sqlite/sqlite.odin
|
||||||
|
- [ ] cli_test.odin
|
||||||
|
- [ ] cmd_check_test.odin
|
||||||
|
- [ ] cmd_list_test.odin
|
||||||
|
- [ ] cmd_nushell_completion_test.odin
|
||||||
|
- [ ] config_test.odin
|
||||||
|
- [ ] crypto_test.odin
|
||||||
|
- [ ] db_integration_test.odin
|
||||||
|
- [ ] db_test.odin
|
||||||
|
- [ ] features_test.odin
|
||||||
|
- [ ] scan_test.odin
|
||||||
|
- [ ] ssh_test.odin
|
||||||
|
- [ ] table_test.odin
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:io"
|
||||||
|
import "core:os"
|
||||||
|
import "core:terminal"
|
||||||
|
|
||||||
cmd_deps :: proc(cmd: ^Command) {
|
cmd_deps :: proc(cmd: ^Command) {
|
||||||
feats := check_features()
|
feats := check_features()
|
||||||
|
|
||||||
@@ -18,6 +23,12 @@ cmd_deps :: proc(cmd: ^Command) {
|
|||||||
append(&rows, []string{"fd", "\u2717 Missing"})
|
append(&rows, []string{"fd", "\u2717 Missing"})
|
||||||
}
|
}
|
||||||
|
|
||||||
render_table(headers, rows[:])
|
if terminal.is_terminal(os.stdout) {
|
||||||
|
render_table(headers, rows[:])
|
||||||
|
} else {
|
||||||
|
w := io.to_writer(os.to_writer(os.stdout))
|
||||||
|
render_json_rows(w, headers, rows[:])
|
||||||
|
io.write_string(w, "\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
109
quash
109
quash
@@ -1,109 +0,0 @@
|
|||||||
[1m[38;5;2m@[0m [1m[38;5;13muk[38;5;8mspssxz[39m [38;5;3mspencer.brower@proton.me[39m [38;5;14m2026-06-12 16:45:22[39m [38;5;10mdefault@[39m [38;5;12m54[38;5;8m8fe7ec[39m[0m
|
|
||||||
│ [1m[38;5;3m(no description set)[39m[0m
|
|
||||||
○ [1m[38;5;5msu[0m[38;5;8mwmwvkl[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 16:40:25[39m [38;5;5modin[39m [1m[38;5;4ma1e93[0m[38;5;8m345[39m
|
|
||||||
│ ci: Updated github action.
|
|
||||||
○ [1m[38;5;5mtq[0m[38;5;8mpkpmus[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 16:35:39[39m [1m[38;5;4mee[0m[38;5;8md36089[39m
|
|
||||||
│ feat: Removed go code.
|
|
||||||
○ [1m[38;5;5myzz[0m[38;5;8mzmznw[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 16:35:34[39m [1m[38;5;4m75[0m[38;5;8mb77845[39m
|
|
||||||
│ build: Converted Makefile and flake package.
|
|
||||||
○ [1m[38;5;5mkv[0m[38;5;8mtmxpyn[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 15:54:44[39m [1m[38;5;4m4ec[0m[38;5;8m2b22b[39m
|
|
||||||
│ refactor: removed `is_tty`.
|
|
||||||
○ [1m[38;5;5mpo[0m[38;5;8muwppuo[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 15:48:12[39m [1m[38;5;4m027[0m[38;5;8m6db76[39m
|
|
||||||
│ refactor: Switched from age to libsodium.
|
|
||||||
○ [1m[38;5;5mtx[0m[38;5;8moxnuzl[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 15:36:10[39m [1m[38;5;4ma0[0m[38;5;8me2c995[39m
|
|
||||||
│ docs: Updated TODOs.
|
|
||||||
○ [1m[38;5;5mzv[0m[38;5;8mrkmqpk[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 15:01:50[39m [1m[38;5;4md0[0m[38;5;8mdc93ab[39m
|
|
||||||
│ feat(odin): Migrated nushell-completion command to go.
|
|
||||||
○ [1m[38;5;5mzp[0m[38;5;8mmvtmzx[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 14:50:42[39m [1m[38;5;4m91[0m[38;5;8mada61c[39m
|
|
||||||
│ feat: Added tests.
|
|
||||||
○ [1m[38;5;5mvs[0m[38;5;8mqmlvlq[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 14:17:56[39m [1m[38;5;4m9b[0m[38;5;8m395677[39m
|
|
||||||
│ fix: Fixed the rest of the (tested) leaks.
|
|
||||||
○ [1m[38;5;5mrw[0m[38;5;8mzttsll[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 13:37:09[39m [1m[38;5;4m43d[0m[38;5;8md8aca[39m
|
|
||||||
│ perf: Improved writer performance.
|
|
||||||
○ [1m[38;5;5mro[0m[38;5;8mvqumvz[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 13:25:50[39m [1m[38;5;4mdb[0m[38;5;8m1b863e[39m
|
|
||||||
│ fix: fixing leaks.
|
|
||||||
○ [1m[38;5;5mqu[0m[38;5;8mqsmwmx[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 10:45:43[39m [1m[38;5;4me9[0m[38;5;8m660501[39m
|
|
||||||
│ fix: Added proper help text to all commands.
|
|
||||||
○ [1m[38;5;5muu[0m[38;5;8mpootzn[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 10:28:41[39m [1m[38;5;4m76[0m[38;5;8m29dd2c[39m
|
|
||||||
│ fix: Got rid of go fallback code.
|
|
||||||
○ [1m[38;5;5msv[0m[38;5;8mkzoqxq[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 10:22:21[39m [1m[38;5;4m7c[0m[38;5;8m7ddf46[39m
|
|
||||||
│ fix: Fixed memory leaks in `find_binary`.
|
|
||||||
○ [1m[38;5;5myzv[0m[38;5;8mwlzvq[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 10:22:21[39m [1m[38;5;4ma1e94[0m[38;5;8m5a6[39m
|
|
||||||
│ feat(odin): Ported init command.
|
|
||||||
○ [1m[38;5;5myk[0m[38;5;8mlwuqrm[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 09:12:55[39m [1m[38;5;4m0a[0m[38;5;8m332adf[39m
|
|
||||||
│ feat(odin): Ported scan command.
|
|
||||||
○ [1m[38;5;5munkt[0m[38;5;8mymmr[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 08:27:14[39m [1m[38;5;4m4e1[0m[38;5;8me3590[39m
|
|
||||||
│ feat(odin): port check command to odin.
|
|
||||||
○ [1m[38;5;5moy[0m[38;5;8mllntvp[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-12 08:02:08[39m [1m[38;5;4m82[0m[38;5;8mbec68b[39m
|
|
||||||
│ fix: Fixing AI oopsies.
|
|
||||||
○ [1m[38;5;5ml[0m[38;5;8mowokuok[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:26:59[39m [1m[38;5;4m2c[0m[38;5;8mb6067a[39m
|
|
||||||
│ feat(odin): ported edit-config command to odin.
|
|
||||||
○ [1m[38;5;5mvl[0m[38;5;8mssoopk[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:25:11[39m [1m[38;5;4m36[0m[38;5;8m68df57[39m
|
|
||||||
│ feat(odin): ported restore command to odin.
|
|
||||||
○ [1m[38;5;5mtu[0m[38;5;8mnwtypr[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:21:59[39m [1m[38;5;4md2[0m[38;5;8m127e47[39m
|
|
||||||
│ feat(odin): Ported remove command.
|
|
||||||
○ [1m[38;5;5mnr[0m[38;5;8mnpskps[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:17:52[39m [1m[38;5;4mcb[0m[38;5;8m7db967[39m
|
|
||||||
│ feat(odin): Added long text and --help flags.
|
|
||||||
○ [1m[38;5;5msw[0m[38;5;8mwzkunx[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:14:11[39m [1m[38;5;4mc9[0m[38;5;8m2155a1[39m
|
|
||||||
│ feat(odin): ported backup command.
|
|
||||||
○ [1m[38;5;5mts[0m[38;5;8mnurnzr[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:05:39[39m [1m[38;5;4mb1[0m[38;5;8md24161[39m
|
|
||||||
│ feat(odin): ported list command.
|
|
||||||
○ [1m[38;5;5mvw[0m[38;5;8molkxsl[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 21:05:33[39m [1m[38;5;4m40[0m[38;5;8mf0b3c3[39m
|
|
||||||
│ feat(odin): ported deps command, added utilities (features, tty, table).
|
|
||||||
○ [1m[38;5;5mrqr[0m[38;5;8mrlqlk[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 20:34:53[39m [1m[38;5;4md8[0m[38;5;8m4e43d0[39m
|
|
||||||
│ odin: scaffold project with CLI parser, version command, Go fallback
|
|
||||||
○ [1m[38;5;5mznn[0m[38;5;8mskorn[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 20:08:27[39m [1m[38;5;4m28[0m[38;5;8mf96df4[39m
|
|
||||||
│ feat: Started odin setup.
|
|
||||||
│ ○ [1m[38;5;5mry[0m[38;5;8mkmnnwl[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-11 20:00:08[39m [38;5;5mzig[39m [1m[38;5;4m42[0m[38;5;8mc01a08[39m
|
|
||||||
│ │ feat: init command.
|
|
||||||
│ ○ [1m[38;5;5mzt[0m[38;5;8mntvnnw[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-09 11:01:15[39m [1m[38;5;4md3[0m[38;5;8meb4e84[39m
|
|
||||||
│ │ fix: Fixed issue with buffer size.
|
|
||||||
│ ○ [1m[38;5;5mpq[0m[38;5;8mzlpytk[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-09 09:50:38[39m [1m[38;5;4m6ac[0m[38;5;8md1f9d[39m
|
|
||||||
│ │ refactor: Moved deps into `root.zig`.
|
|
||||||
│ ○ [1m[38;5;5msl[0m[38;5;8mkwsoqy[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-06-09 09:41:13[39m [1m[38;5;4m68[0m[38;5;8m1931fb[39m
|
|
||||||
│ │ feat: Added table viewer.
|
|
||||||
│ ○ [1m[38;5;5mqk[0m[38;5;8mmlntsm[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-05-27 19:30:19[39m [1m[38;5;4macb[0m[38;5;8mda090[39m
|
|
||||||
│ │ feat: list cmd.
|
|
||||||
│ ○ [1m[38;5;5mvx[0m[38;5;8mnsyxqp[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-05-27 18:27:21[39m [1m[38;5;4mfc[0m[38;5;8m8474d7[39m
|
|
||||||
│ │ feat: Restore db from file.
|
|
||||||
│ ○ [1m[38;5;5muo[0m[38;5;8mowvkxx[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-05-03 12:45:43[39m [1m[38;5;4m8f[0m[38;5;8m2c2419[39m
|
|
||||||
│ │ feat(config): Added data path.
|
|
||||||
│ ○ [1m[38;5;5mqr[0m[38;5;8mkuztko[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-05-01 10:30:12[39m [1m[38;5;4m3e[0m[38;5;8m6c1752[39m
|
|
||||||
│ │ feat: accept config in Db
|
|
||||||
│ ○ [1m[38;5;5mvr[0m[38;5;8mxoyzlo[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-30 22:37:31[39m [1m[38;5;4mfd[0m[38;5;8m0f8bba[39m
|
|
||||||
│ │ feat(age): accept multiple recipients.
|
|
||||||
│ ○ [1m[38;5;5mrqu[0m[38;5;8mvonut[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-30 21:03:38[39m [1m[38;5;4m65[0m[38;5;8m571393[39m
|
|
||||||
│ │ feat: Implemented basic db operation.
|
|
||||||
│ ○ [1m[38;5;5mnw[0m[38;5;8mzoqvoq[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-29 16:35:38[39m [1m[38;5;4me5[0m[38;5;8m286527[39m
|
|
||||||
│ │ feat: Created own age wrapper.
|
|
||||||
│ ○ [1m[38;5;5mrl[0m[38;5;8mtyxtqr[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-28 17:49:04[39m [1m[38;5;4m02c[0m[38;5;8me5e46[39m
|
|
||||||
│ │ feat: Added age-ffi.
|
|
||||||
│ ○ [1m[38;5;5mkr[0m[38;5;8mzuylpu[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-26 17:29:37[39m [1m[38;5;4ma13[0m[38;5;8m264c8[39m
|
|
||||||
│ │ feat: zig-sqlite.
|
|
||||||
│ ○ [1m[38;5;5mnq[0m[38;5;8mlotzkk[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-24 11:19:31[39m [1m[38;5;4m79[0m[38;5;8m9d95a4[39m
|
|
||||||
│ │ feat: added Config parsing.
|
|
||||||
│ ○ [1m[38;5;5mnp[0m[38;5;8mvzptmw[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-23 16:53:47[39m [1m[38;5;4m21[0m[38;5;8m7bb413[39m
|
|
||||||
│ │ feat(comma): Added help method.
|
|
||||||
│ ○ [1m[38;5;5mrr[0m[38;5;8mlywnkm[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-21 19:42:02[39m [1m[38;5;4ma5[0m[38;5;8m47409e[39m
|
|
||||||
│ │ docs: Added AI Disclaimer to README.md.
|
|
||||||
│ ○ [1m[38;5;5mpl[0m[38;5;8mqqwlws[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-21 19:34:09[39m [1m[38;5;4m53[0m[38;5;8mcf22bc[39m
|
|
||||||
│ │ feat: Added help output for commands.
|
|
||||||
│ ○ [1m[38;5;5mznp[0m[38;5;8mvknpm[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-21 18:13:35[39m [1m[38;5;4mae[0m[38;5;8m445459[39m
|
|
||||||
│ │ feat(comma): Added enum value for unknown commands.
|
|
||||||
│ ○ [1m[38;5;5mzq[0m[38;5;8mpvlvms[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-21 18:02:58[39m [1m[38;5;4mbd[0m[38;5;8m2a5455[39m
|
|
||||||
│ │ feat: Migrated `deps` command.
|
|
||||||
│ ○ [1m[38;5;5mw[0m[38;5;8mqslwyqo[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-20 17:08:26[39m [1m[38;5;4m8a[0m[38;5;8m503ced[39m
|
|
||||||
│ │ refactor: Broke comma into a separate package.
|
|
||||||
│ ○ [1m[38;5;5mtr[0m[38;5;8mqurnkq[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-20 16:14:43[39m [1m[38;5;4m33[0m[38;5;8mb0063c[39m
|
|
||||||
│ │ feat: Added command structure.
|
|
||||||
│ │ ○ [1m[38;5;5msp[0m[38;5;8mllvvwm[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-20 10:15:48[39m [38;5;2menvr-zig@[39m [1m[38;5;4mac9[0m[38;5;8m4b33e[39m
|
|
||||||
│ ├─╯ [38;5;2m(empty)[39m [38;5;2m(no description set)[39m
|
|
||||||
│ ○ [1m[38;5;5mol[0m[38;5;8mwurpsw[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-18 16:28:30[39m [1m[38;5;4m43b[0m[38;5;8m03e0a[39m
|
|
||||||
│ │ wip: feat: Migrated version command to zig.
|
|
||||||
│ ○ [1m[38;5;5mm[0m[38;5;8mnqunpro[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-17 16:41:45[39m [1m[38;5;4mce[0m[38;5;8m135e9c[39m
|
|
||||||
│ │ feat: Created zig wrapper.
|
|
||||||
│ ○ [1m[38;5;5munkr[0m[38;5;8mrvon[39m [38;5;3mspencer.brower@proton.me[39m [38;5;6m2026-04-17 15:49:00[39m [1m[38;5;4m6a6[0m[38;5;8m11150[39m
|
|
||||||
├─╯ feat: Added zig config.
|
|
||||||
[1m[38;5;14m◆[0m [1m[38;5;5mps[0m[38;5;8mmotwus[39m [38;5;3m6729162+sbrow@users.noreply.github.com[39m [38;5;6m2026-01-12 15:42:05[39m [38;5;5mgo main[39m [38;5;5mv0.2.1[39m [1m[38;5;4mc6[0m[38;5;8md03088[39m
|
|
||||||
│ chore(main): release 0.2.1
|
|
||||||
~
|
|
||||||
@@ -3,18 +3,9 @@ package main
|
|||||||
import "core:encoding/json"
|
import "core:encoding/json"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:io"
|
import "core:io"
|
||||||
import "core:os"
|
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:terminal"
|
|
||||||
|
|
||||||
render_table :: proc(headers: []string, rows: [][]string) {
|
render_table :: proc(headers: []string, rows: [][]string) {
|
||||||
if !terminal.is_terminal(os.stdout) {
|
|
||||||
w := io.to_writer(os.to_writer(os.stdout))
|
|
||||||
render_json_rows(w, headers, rows)
|
|
||||||
io.write_string(w, "\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
col_widths := make([dynamic]int, 0, len(headers))
|
col_widths := make([dynamic]int, 0, len(headers))
|
||||||
for i in 0 ..< len(headers) {
|
for i in 0 ..< len(headers) {
|
||||||
append(&col_widths, strings.rune_count(headers[i]))
|
append(&col_widths, strings.rune_count(headers[i]))
|
||||||
|
|||||||
Reference in New Issue
Block a user