refactor(config): Switched property names to camel_case.

This commit is contained in:
2026-06-22 09:20:07 -04:00
parent 29415da692
commit 63d00a1f55
11 changed files with 96 additions and 90 deletions

View File

@@ -52,6 +52,8 @@
26. Adopt `core:log` across `db.odin`, `crypto.odin`, `config.odin`, `ssh.odin` — replace ~30 scattered `fmt.printf("Error ...")` calls with leveled logging for consistent stderr routing and source locations.
27. "Encryption failed" in tests.
## Double-check AI output
- [ ] cli.odin
@@ -60,7 +62,7 @@
- [x] cmd_backup.odin
- [x] cmd_check.odin
- [ ] cmd_check_test.odin
- [ ] cmd_edit_config.odin
- [x] cmd_edit_config.odin
- [x] cmd_init.odin
- [x] cmd_list.odin
- [ ] cmd_list_test.odin

View File

@@ -41,6 +41,8 @@ cmd_edit_config :: proc(cmd: ^Command) {
fmt.wprintf(cmd.err, "Error waiting for editor: %v\n", wait_err, flush = false)
return
}
// TODO: Should we call exit inside of commands?
if state.exit_code != 0 {
os.exit(int(state.exit_code))
}

View File

@@ -8,21 +8,21 @@ import "core:strings"
import "findr"
Config :: struct {
keys: [dynamic]SshKeyPair `json:"keys"`,
scan_config: ScanConfig `json:"scan"`,
config_path: string `json:"-"`,
}
SshKeyPair :: struct {
Private: string `json:"private"`,
Public: string `json:"public"`,
private: string `json:"private"`,
public: string `json:"public"`,
}
ScanConfig :: struct {
Matcher: string `json:"matcher"`,
Exclude: [dynamic]string `json:"exclude"`,
Include: [dynamic]string `json:"include"`,
}
Config :: struct {
Keys: [dynamic]SshKeyPair `json:"keys"`,
ScanConfig: ScanConfig `json:"scan"`,
config_path: string `json:"-"`,
matcher: string `json:"matcher"`,
exclude: [dynamic]string `json:"exclude"`,
include: [dynamic]string `json:"include"`,
}
load_config :: proc(config_path: string, allocator := context.allocator) -> (Config, bool) {
@@ -53,23 +53,23 @@ default_config_path :: proc(home: string, allocator := context.allocator) -> str
}
delete_config :: proc(cfg: ^Config, allocator := context.allocator) {
for key in cfg.Keys {
delete(key.Private, allocator)
delete(key.Public, allocator)
for key in cfg.keys {
delete(key.private, allocator)
delete(key.public, allocator)
}
delete(cfg.Keys)
delete(cfg.keys)
delete(cfg.ScanConfig.Matcher, allocator)
delete(cfg.scan_config.matcher, allocator)
for exclude in cfg.ScanConfig.Exclude {
for exclude in cfg.scan_config.exclude {
delete(exclude, allocator)
}
delete(cfg.ScanConfig.Exclude)
delete(cfg.scan_config.exclude)
for include in cfg.ScanConfig.Include {
for include in cfg.scan_config.include {
delete(include, allocator)
}
delete(cfg.ScanConfig.Include)
delete(cfg.scan_config.include)
}
save_config :: proc(cfg: Config, force: bool = false) -> bool {
@@ -123,7 +123,7 @@ new_config :: proc(
// TODO: Is this bad?
priv_key := strings.clone(priv)
pub, _ := strings.concatenate([]string{priv_key, ".pub"})
append(&keys, SshKeyPair{Private = priv_key, Public = pub})
append(&keys, SshKeyPair{private = priv_key, public = pub})
}
exclude := make([dynamic]string, 0, 4)
@@ -136,12 +136,12 @@ new_config :: proc(
append(&include, strings.clone("~"))
scan_cfg := ScanConfig {
Matcher = strings.clone("\\.env"),
Exclude = exclude,
Include = include,
matcher = strings.clone("\\.env"),
exclude = exclude,
include = include,
}
return Config{Keys = keys, ScanConfig = scan_cfg, config_path = cfg_path}
return Config{keys = keys, scan_config = scan_cfg, config_path = cfg_path}
}
find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) {
@@ -209,7 +209,7 @@ search_paths :: proc(cfg: Config, allocator := context.allocator) -> [dynamic]st
// TODO: handle error
home, _ := os.user_home_dir(context.temp_allocator)
paths, _ := new_clone(cfg.ScanConfig.Include, allocator)
paths, _ := new_clone(cfg.scan_config.include, allocator)
for &include in paths {
// TODO: Do we need to manually expand ~/ in odin?

View File

@@ -16,11 +16,11 @@ test_new_config_single_key :: proc(t: ^testing.T) {
cfg := new_config(paths)
defer delete_config(&cfg)
testing.expect(t, len(cfg.Keys) == 1, "should have 1 key")
testing.expect(t, cfg.Keys[0].Private == "/home/user/.ssh/id_ed25519", "Private path mismatch")
testing.expect(t, len(cfg.keys) == 1, "should have 1 key")
testing.expect(t, cfg.keys[0].private == "/home/user/.ssh/id_ed25519", "Private path mismatch")
testing.expect(
t,
cfg.Keys[0].Public == "/home/user/.ssh/id_ed25519.pub",
cfg.keys[0].public == "/home/user/.ssh/id_ed25519.pub",
"Public path mismatch",
)
}
@@ -31,9 +31,9 @@ test_new_config_multiple_keys :: proc(t: ^testing.T) {
cfg := new_config(paths)
defer delete_config(&cfg)
testing.expect(t, len(cfg.Keys) == 2, "should have 2 keys")
testing.expect(t, cfg.Keys[0].Private == "/home/user/.ssh/id_ed25519")
testing.expect(t, cfg.Keys[1].Private == "/home/user/.ssh/id_rsa")
testing.expect(t, len(cfg.keys) == 2, "should have 2 keys")
testing.expect(t, cfg.keys[0].private == "/home/user/.ssh/id_ed25519")
testing.expect(t, cfg.keys[1].private == "/home/user/.ssh/id_rsa")
}
@(test)
@@ -42,7 +42,7 @@ test_new_config_empty_keys :: proc(t: ^testing.T) {
cfg := new_config(paths)
defer delete_config(&cfg)
testing.expect(t, len(cfg.Keys) == 0, "should have 0 keys")
testing.expect(t, len(cfg.keys) == 0, "should have 0 keys")
}
@(test)
@@ -51,10 +51,10 @@ test_new_config_scan_defaults :: proc(t: ^testing.T) {
cfg := new_config(paths)
defer delete_config(&cfg)
testing.expect(t, cfg.ScanConfig.Matcher == "\\.env", "matcher should be \\.env")
testing.expect(t, len(cfg.ScanConfig.Exclude) == 4, "should have 4 exclude patterns")
testing.expect(t, len(cfg.ScanConfig.Include) == 1, "should have 1 include path")
testing.expect(t, cfg.ScanConfig.Include[0] == "~", "include should be ~")
testing.expect(t, cfg.scan_config.matcher == "\\.env", "matcher should be \\.env")
testing.expect(t, len(cfg.scan_config.exclude) == 4, "should have 4 exclude patterns")
testing.expect(t, len(cfg.scan_config.include) == 1, "should have 1 include path")
testing.expect(t, cfg.scan_config.include[0] == "~", "include should be ~")
}
@(test)
@@ -65,7 +65,7 @@ test_new_config_exclude_patterns :: proc(t: ^testing.T) {
expected := []string{"*\\.envrc", "\\.local/", "node_modules", "vendor"}
for i in 0 ..< len(expected) {
testing.expect(t, cfg.ScanConfig.Exclude[i] == expected[i])
testing.expect(t, cfg.scan_config.exclude[i] == expected[i])
}
}
@@ -88,13 +88,13 @@ test_save_load_config_roundtrip :: proc(t: ^testing.T) {
if !ok do return
defer delete_config(&loaded)
testing.expect(t, len(loaded.Keys) == 1, "should have 1 key")
testing.expect(t, loaded.Keys[0].Private == "/home/user/.ssh/id_ed25519")
testing.expect(t, loaded.Keys[0].Public == "/home/user/.ssh/id_ed25519.pub")
testing.expect(t, loaded.ScanConfig.Matcher == "\\.env")
testing.expect(t, len(loaded.ScanConfig.Exclude) == 4)
testing.expect(t, len(loaded.ScanConfig.Include) == 1)
testing.expect(t, loaded.ScanConfig.Include[0] == "~")
testing.expect(t, len(loaded.keys) == 1, "should have 1 key")
testing.expect(t, loaded.keys[0].private == "/home/user/.ssh/id_ed25519")
testing.expect(t, loaded.keys[0].public == "/home/user/.ssh/id_ed25519.pub")
testing.expect(t, loaded.scan_config.matcher == "\\.env")
testing.expect(t, len(loaded.scan_config.exclude) == 4)
testing.expect(t, len(loaded.scan_config.include) == 1)
testing.expect(t, loaded.scan_config.include[0] == "~")
}
@(test)
@@ -143,10 +143,10 @@ test_save_config_force_overwrites :: proc(t: ^testing.T) {
if !ok do return
defer delete_config(&loaded)
testing.expect(t, len(loaded.Keys) == 1, "should have 1 key")
testing.expect(t, len(loaded.keys) == 1, "should have 1 key")
testing.expect(
t,
loaded.Keys[0].Private == "/home/user/.ssh/key2",
loaded.keys[0].private == "/home/user/.ssh/key2",
"should be the overwritten key",
)
}
@@ -186,10 +186,10 @@ test_search_paths_expands_tilde :: proc(t: ^testing.T) {
os.set_env("HOME", "/tmp/envr-fake-home-search")
cfg := Config {
ScanConfig = ScanConfig{Include = make([dynamic]string, 0, 1)},
scan_config = ScanConfig{include = make([dynamic]string, 0, 1)},
}
append(&cfg.ScanConfig.Include, "~")
defer delete(cfg.ScanConfig.Include)
append(&cfg.scan_config.include, "~")
defer delete(cfg.scan_config.include)
paths := search_paths(cfg, context.temp_allocator)

View File

@@ -283,16 +283,16 @@ ssh_to_x25519 :: proc(
pairs := make([]X25519Keypair, len(keys), allocator)
for i in 0 ..< len(keys) {
ssh_kp, parse_ok := parse_ssh_private_key(keys[i].Private)
ssh_kp, parse_ok := parse_ssh_private_key(keys[i].private)
if !parse_ok {
fmt.printf("Error: failed to parse SSH private key: %s\n", keys[i].Private)
fmt.printf("Error: failed to parse SSH private key: %s\n", keys[i].private)
delete(pairs)
return pairs, false
}
ssh_pub, pub_ok := parse_ssh_public_key(keys[i].Public)
ssh_pub, pub_ok := parse_ssh_public_key(keys[i].public)
if !pub_ok {
fmt.printf("Error: failed to parse SSH public key: %s\n", keys[i].Public)
fmt.printf("Error: failed to parse SSH public key: %s\n", keys[i].public)
delete(pairs)
return pairs, false
}

View File

@@ -10,7 +10,7 @@ CRYPTO_TEST_KEY_DIR :: "fixtures" + os.Path_Separator_String + "keys"
make_test_key_pair :: proc(name: string) -> SshKeyPair {
priv := fmt.tprintf("%s/%s", CRYPTO_TEST_KEY_DIR, name)
pub := fmt.tprintf("%s/%s.pub", CRYPTO_TEST_KEY_DIR, name)
return SshKeyPair{Private = priv, Public = pub}
return SshKeyPair{private = priv, public = pub}
}
@(test)

View File

@@ -111,7 +111,7 @@ db_restore_from_encrypted :: proc(db: ^Db, data_path: string) -> bool {
}
// TODO: Use context.temp_allocator
plaintext, dec_ok := decrypt(encrypted_data, db.cfg.Keys[:])
plaintext, dec_ok := decrypt(encrypted_data, db.cfg.keys[:])
if !dec_ok {
fmt.println("Error: decryption failed")
return false
@@ -166,7 +166,7 @@ db_close :: proc(db: ^Db) {
sqlite_data := data[:sz]
// TODO: PAss allocator chain
encrypted, enc_ok := encrypt(sqlite_data, db.cfg.Keys[:])
encrypted, enc_ok := encrypt(sqlite_data, db.cfg.keys[:])
if !enc_ok {
fmt.println("Error: encryption failed")
return

View File

@@ -20,7 +20,7 @@ fixture_key :: proc() -> SshKeyPair {
[]string{FIXTURES, "/keys/insecure-test-key.pub"},
context.temp_allocator,
)
return SshKeyPair{Private = priv, Public = pub}
return SshKeyPair{private = priv, public = pub}
}
fixture_db_path :: proc() -> string {
@@ -30,9 +30,9 @@ fixture_db_path :: proc() -> string {
fixture_config :: proc() -> Config {
cfg := Config {
Keys = make([dynamic]SshKeyPair, 0, 1),
keys = make([dynamic]SshKeyPair, 0, 1),
}
append(&cfg.Keys, fixture_key())
append(&cfg.keys, fixture_key())
return cfg
}
@@ -40,7 +40,7 @@ fixture_config :: proc() -> Config {
test_encrypt_decrypt_sqlite_roundtrip :: proc(t: ^testing.T) {
cfg := fixture_config()
defer {
delete(cfg.Keys)
delete(cfg.keys)
}
db_path := fixture_db_path()
@@ -51,7 +51,7 @@ test_encrypt_decrypt_sqlite_roundtrip :: proc(t: ^testing.T) {
}
defer delete(sqlite_data)
encrypted, enc_ok := encrypt(sqlite_data, cfg.Keys[:])
encrypted, enc_ok := encrypt(sqlite_data, cfg.keys[:])
testing.expect(t, enc_ok, "encryption should succeed")
if !enc_ok {
return
@@ -64,7 +64,7 @@ test_encrypt_decrypt_sqlite_roundtrip :: proc(t: ^testing.T) {
testing.expect(t, encrypted[2] == u8('V'), "magic byte 2")
testing.expect(t, encrypted[3] == u8('R'), "magic byte 3")
plaintext, dec_ok := decrypt(encrypted, cfg.Keys[:])
plaintext, dec_ok := decrypt(encrypted, cfg.keys[:])
testing.expect(t, dec_ok, "decryption should succeed")
if !dec_ok {
return
@@ -93,7 +93,7 @@ test_encrypt_decrypt_sqlite_roundtrip :: proc(t: ^testing.T) {
test_encrypt_write_read_decrypt :: proc(t: ^testing.T) {
cfg := fixture_config()
defer {
delete(cfg.Keys)
delete(cfg.keys)
}
db_path := fixture_db_path()
@@ -104,7 +104,7 @@ test_encrypt_write_read_decrypt :: proc(t: ^testing.T) {
}
defer delete(sqlite_data)
encrypted, enc_ok := encrypt(sqlite_data, cfg.Keys[:])
encrypted, enc_ok := encrypt(sqlite_data, cfg.keys[:])
testing.expect(t, enc_ok, "encryption should succeed")
if !enc_ok {
return
@@ -126,7 +126,7 @@ test_encrypt_write_read_decrypt :: proc(t: ^testing.T) {
}
defer delete(read_back)
plaintext, dec_ok := decrypt(read_back, cfg.Keys[:])
plaintext, dec_ok := decrypt(read_back, cfg.keys[:])
testing.expect(t, dec_ok, "decryption after write/read should succeed")
if !dec_ok {
return
@@ -140,7 +140,7 @@ test_encrypt_write_read_decrypt :: proc(t: ^testing.T) {
test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
cfg := fixture_config()
defer {
delete(cfg.Keys)
delete(cfg.keys)
}
db_path := fixture_db_path()
@@ -151,14 +151,14 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
}
defer delete(sqlite_data)
encrypted, enc_ok := encrypt(sqlite_data, cfg.Keys[:])
encrypted, enc_ok := encrypt(sqlite_data, cfg.keys[:])
testing.expect(t, enc_ok, "encryption should succeed")
if !enc_ok {
return
}
defer delete(encrypted)
plaintext, dec_ok := decrypt(encrypted, cfg.Keys[:])
plaintext, dec_ok := decrypt(encrypted, cfg.keys[:])
testing.expect(t, dec_ok, "decryption should succeed")
if !dec_ok {
return
@@ -206,7 +206,7 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
@(test)
test_full_db_cycle :: proc(t: ^testing.T) {
cfg := fixture_config()
defer delete(cfg.Keys)
defer delete(cfg.keys)
db_path := fixture_db_path()
original_data, read_err := os.read_entire_file_from_path(db_path, context.allocator)
@@ -216,7 +216,7 @@ test_full_db_cycle :: proc(t: ^testing.T) {
}
defer delete(original_data)
encrypted, enc_ok := encrypt(original_data, cfg.Keys[:])
encrypted, enc_ok := encrypt(original_data, cfg.keys[:])
testing.expect(t, enc_ok, "first encryption should succeed")
if !enc_ok {
return
@@ -241,21 +241,21 @@ test_full_db_cycle :: proc(t: ^testing.T) {
}
defer delete(read_back)
plaintext, dec_ok := decrypt(read_back, cfg.Keys[:])
plaintext, dec_ok := decrypt(read_back, cfg.keys[:])
testing.expect(t, dec_ok, "decryption should succeed")
if !dec_ok {
return
}
defer delete(plaintext)
encrypted2, enc2_ok := encrypt(plaintext, cfg.Keys[:])
encrypted2, enc2_ok := encrypt(plaintext, cfg.keys[:])
testing.expect(t, enc2_ok, "re-encryption should succeed")
if !enc2_ok {
return
}
defer delete(encrypted2)
plaintext2, dec2_ok := decrypt(encrypted2, cfg.Keys[:])
plaintext2, dec2_ok := decrypt(encrypted2, cfg.keys[:])
testing.expect(t, dec2_ok, "second decryption should succeed")
if !dec2_ok {
return
@@ -282,13 +282,13 @@ test_full_db_cycle :: proc(t: ^testing.T) {
test_ssh_key_parse_from_fixtures :: proc(t: ^testing.T) {
key := fixture_key()
priv_kp, priv_ok := parse_ssh_private_key(key.Private)
priv_kp, priv_ok := parse_ssh_private_key(key.private)
testing.expect(t, priv_ok, "should parse private key from fixtures")
if !priv_ok {
return
}
pub_key, pub_ok := parse_ssh_public_key(key.Public)
pub_key, pub_ok := parse_ssh_public_key(key.public)
testing.expect(t, pub_ok, "should parse public key from fixtures")
if !pub_ok {
return
@@ -311,20 +311,20 @@ test_ssh_key_parse_from_fixtures :: proc(t: ^testing.T) {
test_config_load_with_fixture_key :: proc(t: ^testing.T) {
cfg := fixture_config()
defer {
delete(cfg.Keys)
delete(cfg.keys)
}
testing.expect(t, len(cfg.Keys) == 1, "should have 1 key")
testing.expect(t, len(cfg.keys) == 1, "should have 1 key")
key := cfg.Keys[0]
key := cfg.keys[0]
testing.expectf(t, len(key.Private) > 0, "private key path should not be empty")
testing.expectf(t, len(key.Public) > 0, "public key path should not be empty")
testing.expectf(t, len(key.private) > 0, "private key path should not be empty")
testing.expectf(t, len(key.public) > 0, "public key path should not be empty")
_, priv_ok := parse_ssh_private_key(key.Private)
_, priv_ok := parse_ssh_private_key(key.private)
testing.expect(t, priv_ok, "should parse private key using config paths")
if !priv_ok {
fmt.printf(" private key path was: '%s'\n", key.Private)
fmt.printf(" private key path was: '%s'\n", key.private)
}
}

View File

@@ -537,8 +537,8 @@ test_db_sync_moved :: proc(t: ^testing.T) {
testing.expect(t, ok, "failed to create test db")
defer db_close(&db)
db.cfg.ScanConfig.Include = make([dynamic]string, 0, 1, context.temp_allocator)
append(&db.cfg.ScanConfig.Include, search_root)
db.cfg.scan_config.include = make([dynamic]string, 0, 1, context.temp_allocator)
append(&db.cfg.scan_config.include, search_root)
f := make_test_env_file(
"/old/nonexistent/path/.env",

View File

@@ -7,8 +7,8 @@ import "findr"
// Caller is responsible for freeing paths
scan_path :: proc(search_path: string, cfg: Config) -> (paths: [dynamic]string, ok: bool) {
opts := findr.WalkOptions {
pattern = cfg.ScanConfig.Matcher,
excludes = cfg.ScanConfig.Exclude[:],
pattern = cfg.scan_config.matcher,
excludes = cfg.scan_config.exclude[:],
}
findr.walk({search_path}, &paths, opts, os.get_processor_core_count())
ok = true
@@ -29,3 +29,4 @@ find_unbacked :: proc(local_files: []string, db_files: []EnvFile) -> []string {
}
return unbacked[:]
}

View File

@@ -35,7 +35,7 @@ test_scan_path_finds_gitignored_env_files :: proc(t: ^testing.T) {
_ = os.write_entire_file(fmt.tprintf("%s/config.yaml", base), "key: value")
cfg := Config {
ScanConfig = ScanConfig{Matcher = "\\.env"},
scan_config = ScanConfig{matcher = "\\.env"},
}
results, ok := scan_path(base, cfg)
@@ -76,7 +76,7 @@ test_scan_path_empty_dir :: proc(t: ^testing.T) {
defer os.remove_all(base)
cfg := Config {
ScanConfig = ScanConfig{Matcher = "\\.env"},
scan_config = ScanConfig{matcher = "\\.env"},
}
results, ok := scan_path(base, cfg)
@@ -84,3 +84,4 @@ test_scan_path_empty_dir :: proc(t: ^testing.T) {
testing.expect(t, ok, "scan_path should succeed")
testing.expect(t, len(results) == 0, fmt.tprintf("expected 0 results, got %d", len(results)))
}