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. Encrypt/decrypt the database in memory.
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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"`.
|
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)`.
|
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.
|
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
|
## Double-check AI output
|
||||||
|
|
||||||
- [ ] cli.odin
|
- [ ] cli.odin
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caller is responsible for calling delete_config()
|
||||||
new_config :: proc(
|
new_config :: proc(
|
||||||
private_key_paths: []string,
|
private_key_paths: []string,
|
||||||
cfg_path: string = "~/.envr/config.json",
|
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)
|
cpath := to_cstring(file.Path)
|
||||||
defer delete(cpath)
|
defer delete(cpath)
|
||||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
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))
|
cremotes := to_cstring(string(remotes_json))
|
||||||
defer delete(cremotes)
|
defer delete(cremotes)
|
||||||
rc = sqlite.bind_text(stmt, 2, cremotes, -1, nil)
|
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)
|
csha := to_cstring(file.Sha256)
|
||||||
defer delete(csha)
|
defer delete(csha)
|
||||||
rc = sqlite.bind_text(stmt, 3, csha, -1, nil)
|
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)
|
ccontents := to_cstring(file.contents)
|
||||||
defer delete(ccontents)
|
defer delete(ccontents)
|
||||||
rc = sqlite.bind_text(stmt, 4, ccontents, -1, nil)
|
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)
|
rc = sqlite.step(stmt)
|
||||||
if rc != sqlite.DONE {
|
if rc != sqlite.DONE {
|
||||||
@@ -418,6 +434,10 @@ db_fetch :: proc(d: ^Db, path: string, allocator := context.allocator) -> (EnvFi
|
|||||||
cpath := to_cstring(path, allocator)
|
cpath := to_cstring(path, allocator)
|
||||||
defer delete(cpath, allocator)
|
defer delete(cpath, allocator)
|
||||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
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)
|
rc = sqlite.step(stmt)
|
||||||
if rc == sqlite.DONE {
|
if rc == sqlite.DONE {
|
||||||
fmt.printf("No file found with path: %s\n", path)
|
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)
|
cpath := to_cstring(path)
|
||||||
defer delete(cpath)
|
defer delete(cpath)
|
||||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
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)
|
rc = sqlite.step(stmt)
|
||||||
if rc != sqlite.DONE {
|
if rc != sqlite.DONE {
|
||||||
fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(d.db))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import "core:fmt"
|
|||||||
import "core:os"
|
import "core:os"
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
|
defer free_all(context.temp_allocator)
|
||||||
|
|
||||||
cmd, ok := parse_args(os.args, os.to_writer(os.stdout), os.to_writer(os.stderr))
|
cmd, ok := parse_args(os.args, os.to_writer(os.stdout), os.to_writer(os.stderr))
|
||||||
defer bufio.writer_flush(cmd.out_buf)
|
defer bufio.writer_flush(cmd.out_buf)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
192
prompt.odin
192
prompt.odin
@@ -3,36 +3,9 @@ package main
|
|||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:sys/posix"
|
import "core:sys/posix"
|
||||||
|
|
||||||
Raw_State :: struct {
|
MultiSelect_Result :: enum {
|
||||||
original: posix.termios,
|
Confirm,
|
||||||
fd: posix.FD,
|
Cancel,
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Key :: enum {
|
Key :: enum {
|
||||||
@@ -44,71 +17,9 @@ Key :: enum {
|
|||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
read_key :: proc() -> Key {
|
Raw_State :: struct {
|
||||||
buf: [3]u8
|
original: posix.termios,
|
||||||
|
fd: posix.FD,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_VISIBLE :: 7
|
MAX_VISIBLE :: 7
|
||||||
@@ -125,7 +36,7 @@ multi_select :: proc(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selected = make([dynamic]bool, len(options))
|
selected = make([dynamic]bool, 0, len(options))
|
||||||
cursor: int = 0
|
cursor: int = 0
|
||||||
scroll_offset: int = 0
|
scroll_offset: int = 0
|
||||||
|
|
||||||
@@ -199,3 +110,92 @@ render_options :: proc(
|
|||||||
return end - scroll_offset
|
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