mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
chore: Completed todos.
This commit is contained in:
19
TODOS.md
19
TODOS.md
@@ -1,12 +1,7 @@
|
||||
# TODO
|
||||
# TODOs
|
||||
|
||||
Note: These todos can wait until all the subcommands have been ported.
|
||||
|
||||
## HIGH
|
||||
|
||||
1. **db.odin:380-383, 405, 446** — `sqlite.bind_text` return values overwritten but never checked. A failed bind means `sqlite.step` operates on unbound params.
|
||||
|
||||
## MEDIUM
|
||||
1. Encrypt/decrypt the database in memory.
|
||||
|
||||
2. **db.odin:324-327** — Map iteration (`remote_set`) is non-deterministic. Same file can produce different JSON on each backup, causing spurious DB diffs. Sort remotes before storing.
|
||||
|
||||
@@ -18,22 +13,14 @@ Note: These todos can wait until all the subcommands have been ported.
|
||||
|
||||
6. **cmd_restore.odin:44** — `os.mkdir_all` error silently discarded. Subsequent write failure will be confusing.
|
||||
|
||||
7. **cmd_edit_config.odin:27** — `$EDITOR` used as single binary name. Breaks for multi-word values like `"code -w"`. Needs `strings.fields()`.
|
||||
|
||||
8. **config.odin:178** — `search_paths` silently ignores `os.user_home_dir` error. If home is empty, `~` isn't expanded. Same class of bug as issue 3.
|
||||
|
||||
9. **prompt.odin:124** — `make([dynamic]bool, len(options))` creates N zero-initialized elements. Works because `false` is the default, but same footgun as original issue 1. Should be `make([dynamic]bool, 0, len(options))`.
|
||||
|
||||
## LOW
|
||||
|
||||
10. **db.odin:115** — `json.unmarshal_string` error not checked. Malformed JSON silently produces empty/partial data.
|
||||
|
||||
11. **db.odin:352-353** — `hex.encode` error ignored. `string(hex_bytes)` aliases the byte slice.
|
||||
|
||||
12. **cmd_sync.odin:80, cmd_list.odin:33, cmd_deps.odin:9** — `make([]string, 2)` for table rows never freed. Leaks per row. Defer to memory pass.
|
||||
|
||||
## REFACTOR
|
||||
|
||||
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"`.
|
||||
|
||||
14. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
||||
@@ -54,6 +41,8 @@ Note: These todos can wait until all the subcommands have been ported.
|
||||
|
||||
22. 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.
|
||||
|
||||
## Double-check AI output
|
||||
|
||||
- [ ] cli.odin
|
||||
|
||||
@@ -126,6 +126,7 @@ find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Caller is responsible for calling delete_config()
|
||||
new_config :: proc(
|
||||
private_key_paths: []string,
|
||||
cfg_path: string = "~/.envr/config.json",
|
||||
|
||||
24
db.odin
24
db.odin
@@ -382,18 +382,34 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
||||
cpath := to_cstring(file.Path)
|
||||
defer delete(cpath)
|
||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
||||
if rc != sqlite.OK {
|
||||
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db))
|
||||
return false
|
||||
}
|
||||
|
||||
cremotes := to_cstring(string(remotes_json))
|
||||
defer delete(cremotes)
|
||||
rc = sqlite.bind_text(stmt, 2, cremotes, -1, nil)
|
||||
if rc != sqlite.OK {
|
||||
fmt.printf("Error binding remotes: %s\n", sqlite.db_errmsg(d.db))
|
||||
return false
|
||||
}
|
||||
|
||||
csha := to_cstring(file.Sha256)
|
||||
defer delete(csha)
|
||||
rc = sqlite.bind_text(stmt, 3, csha, -1, nil)
|
||||
if rc != sqlite.OK {
|
||||
fmt.printf("Error binding sha256: %s\n", sqlite.db_errmsg(d.db))
|
||||
return false
|
||||
}
|
||||
|
||||
ccontents := to_cstring(file.contents)
|
||||
defer delete(ccontents)
|
||||
rc = sqlite.bind_text(stmt, 4, ccontents, -1, nil)
|
||||
if rc != sqlite.OK {
|
||||
fmt.printf("Error binding contents: %s\n", sqlite.db_errmsg(d.db))
|
||||
return false
|
||||
}
|
||||
|
||||
rc = sqlite.step(stmt)
|
||||
if rc != sqlite.DONE {
|
||||
@@ -418,6 +434,10 @@ db_fetch :: proc(d: ^Db, path: string, allocator := context.allocator) -> (EnvFi
|
||||
cpath := to_cstring(path, allocator)
|
||||
defer delete(cpath, allocator)
|
||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
||||
if rc != sqlite.OK {
|
||||
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db))
|
||||
return EnvFile{}, false
|
||||
}
|
||||
rc = sqlite.step(stmt)
|
||||
if rc == sqlite.DONE {
|
||||
fmt.printf("No file found with path: %s\n", path)
|
||||
@@ -459,6 +479,10 @@ db_delete :: proc(d: ^Db, path: string) -> bool {
|
||||
cpath := to_cstring(path)
|
||||
defer delete(cpath)
|
||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
||||
if rc != sqlite.OK {
|
||||
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db))
|
||||
return false
|
||||
}
|
||||
rc = sqlite.step(stmt)
|
||||
if rc != sqlite.DONE {
|
||||
fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(d.db))
|
||||
|
||||
@@ -5,6 +5,8 @@ import "core:fmt"
|
||||
import "core:os"
|
||||
|
||||
main :: proc() {
|
||||
defer free_all(context.temp_allocator)
|
||||
|
||||
cmd, ok := parse_args(os.args, os.to_writer(os.stdout), os.to_writer(os.stderr))
|
||||
defer bufio.writer_flush(cmd.out_buf)
|
||||
if !ok {
|
||||
|
||||
192
prompt.odin
192
prompt.odin
@@ -3,36 +3,9 @@ package main
|
||||
import "core:fmt"
|
||||
import "core:sys/posix"
|
||||
|
||||
Raw_State :: struct {
|
||||
original: posix.termios,
|
||||
fd: posix.FD,
|
||||
}
|
||||
|
||||
enable_raw_mode :: proc(fd: posix.FD) -> (Raw_State, bool) {
|
||||
state: Raw_State
|
||||
state.fd = fd
|
||||
|
||||
if posix.tcgetattr(fd, &state.original) != .OK {
|
||||
return state, false
|
||||
}
|
||||
|
||||
attr: posix.termios = state.original
|
||||
attr.c_lflag -= {.ICANON, .ECHO, .ISIG, .IEXTEN}
|
||||
attr.c_iflag -= {.IXON, .ICRNL, .BRKINT, .INPCK, .ISTRIP}
|
||||
attr.c_oflag -= {.OPOST}
|
||||
attr.c_cflag += {.CS8}
|
||||
attr.c_cc[.VMIN] = 1
|
||||
attr.c_cc[.VTIME] = 0
|
||||
|
||||
if posix.tcsetattr(fd, .TCSAFLUSH, &attr) != .OK {
|
||||
return state, false
|
||||
}
|
||||
|
||||
return state, true
|
||||
}
|
||||
|
||||
disable_raw_mode :: proc(state: ^Raw_State) {
|
||||
posix.tcsetattr(state.fd, .TCSAFLUSH, &state.original)
|
||||
MultiSelect_Result :: enum {
|
||||
Confirm,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
Key :: enum {
|
||||
@@ -44,71 +17,9 @@ Key :: enum {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
read_key :: proc() -> Key {
|
||||
buf: [3]u8
|
||||
|
||||
n := posix.read(posix.STDIN_FILENO, &buf[0], 1)
|
||||
if n <= 0 {
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
switch buf[0] {
|
||||
case ' ':
|
||||
return .Space
|
||||
case '\n', '\r':
|
||||
return .Enter
|
||||
case 0x03:
|
||||
return .Escape
|
||||
case 0x1b:
|
||||
tv: posix.timeval
|
||||
tv.tv_sec = 0
|
||||
tv.tv_usec = posix.suseconds_t(100000)
|
||||
|
||||
set: posix.fd_set
|
||||
posix.FD_ZERO(&set)
|
||||
posix.FD_SET(posix.STDIN_FILENO, &set)
|
||||
|
||||
ready := posix.select(1, &set, nil, nil, &tv)
|
||||
if ready <= 0 {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
n2 := posix.read(posix.STDIN_FILENO, &buf[1], 1)
|
||||
if n2 <= 0 || buf[1] != '[' {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
posix.FD_ZERO(&set)
|
||||
posix.FD_SET(posix.STDIN_FILENO, &set)
|
||||
tv.tv_sec = 0
|
||||
tv.tv_usec = posix.suseconds_t(100000)
|
||||
|
||||
ready = posix.select(1, &set, nil, nil, &tv)
|
||||
if ready <= 0 {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
n3 := posix.read(posix.STDIN_FILENO, &buf[2], 1)
|
||||
if n3 <= 0 {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
switch buf[2] {
|
||||
case 'A':
|
||||
return .Up
|
||||
case 'B':
|
||||
return .Down
|
||||
case:
|
||||
return .Escape
|
||||
}
|
||||
case:
|
||||
return .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
MultiSelect_Result :: enum {
|
||||
Confirm,
|
||||
Cancel,
|
||||
Raw_State :: struct {
|
||||
original: posix.termios,
|
||||
fd: posix.FD,
|
||||
}
|
||||
|
||||
MAX_VISIBLE :: 7
|
||||
@@ -125,7 +36,7 @@ multi_select :: proc(
|
||||
return
|
||||
}
|
||||
|
||||
selected = make([dynamic]bool, len(options))
|
||||
selected = make([dynamic]bool, 0, len(options))
|
||||
cursor: int = 0
|
||||
scroll_offset: int = 0
|
||||
|
||||
@@ -199,3 +110,92 @@ render_options :: proc(
|
||||
return end - scroll_offset
|
||||
}
|
||||
|
||||
enable_raw_mode :: proc(fd: posix.FD) -> (Raw_State, bool) {
|
||||
state: Raw_State
|
||||
state.fd = fd
|
||||
|
||||
if posix.tcgetattr(fd, &state.original) != .OK {
|
||||
return state, false
|
||||
}
|
||||
|
||||
attr: posix.termios = state.original
|
||||
attr.c_lflag -= {.ICANON, .ECHO, .ISIG, .IEXTEN}
|
||||
attr.c_iflag -= {.IXON, .ICRNL, .BRKINT, .INPCK, .ISTRIP}
|
||||
attr.c_oflag -= {.OPOST}
|
||||
attr.c_cflag += {.CS8}
|
||||
attr.c_cc[.VMIN] = 1
|
||||
attr.c_cc[.VTIME] = 0
|
||||
|
||||
if posix.tcsetattr(fd, .TCSAFLUSH, &attr) != .OK {
|
||||
return state, false
|
||||
}
|
||||
|
||||
return state, true
|
||||
}
|
||||
|
||||
disable_raw_mode :: proc(state: ^Raw_State) {
|
||||
posix.tcsetattr(state.fd, .TCSAFLUSH, &state.original)
|
||||
}
|
||||
|
||||
read_key :: proc() -> Key {
|
||||
buf: [3]u8
|
||||
|
||||
n := posix.read(posix.STDIN_FILENO, &buf[0], 1)
|
||||
if n <= 0 {
|
||||
return .Unknown
|
||||
}
|
||||
|
||||
switch buf[0] {
|
||||
case ' ':
|
||||
return .Space
|
||||
case '\n', '\r':
|
||||
return .Enter
|
||||
case 0x03:
|
||||
return .Escape
|
||||
case 0x1b:
|
||||
tv: posix.timeval
|
||||
tv.tv_sec = 0
|
||||
tv.tv_usec = posix.suseconds_t(100000)
|
||||
|
||||
set: posix.fd_set
|
||||
posix.FD_ZERO(&set)
|
||||
posix.FD_SET(posix.STDIN_FILENO, &set)
|
||||
|
||||
ready := posix.select(1, &set, nil, nil, &tv)
|
||||
if ready <= 0 {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
n2 := posix.read(posix.STDIN_FILENO, &buf[1], 1)
|
||||
if n2 <= 0 || buf[1] != '[' {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
posix.FD_ZERO(&set)
|
||||
posix.FD_SET(posix.STDIN_FILENO, &set)
|
||||
tv.tv_sec = 0
|
||||
tv.tv_usec = posix.suseconds_t(100000)
|
||||
|
||||
ready = posix.select(1, &set, nil, nil, &tv)
|
||||
if ready <= 0 {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
n3 := posix.read(posix.STDIN_FILENO, &buf[2], 1)
|
||||
if n3 <= 0 {
|
||||
return .Escape
|
||||
}
|
||||
|
||||
switch buf[2] {
|
||||
case 'A':
|
||||
return .Up
|
||||
case 'B':
|
||||
return .Down
|
||||
case:
|
||||
return .Escape
|
||||
}
|
||||
case:
|
||||
return .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user