diff --git a/cli.odin b/cli.odin index 27da44b..218a598 100644 --- a/cli.odin +++ b/cli.odin @@ -1,6 +1,9 @@ package main +import "core:bufio" import "core:fmt" +import "core:io" +import "core:mem" import "core:os" import "core:strings" @@ -19,10 +22,14 @@ CommandInfo :: struct { aliases: []string, } -COMMANDS := []CommandInfo{ - {"init", "envr init", "Set up envr", +COMMANDS := []CommandInfo { + { + "init", + "envr init", + "Set up envr", "The init command generates your initial config and saves it to\n~/.envr/config in JSON format.\n\nDuring setup, you will be prompted to select one or more ssh keys with which to\nencrypt your databse. **Make 100% sure** that you have **a remote copy** of this\nkey somewhere, otherwise your data could be lost forever.", - {}}, + {}, + }, {"scan", "envr scan", "Find and select .env files for backup", "", {}}, {"sync", "envr sync", "Update or restore your env backups", "", {}}, {"backup", "envr backup ", "Import a .env file into envr", "", {"add"}}, @@ -30,9 +37,13 @@ COMMANDS := []CommandInfo{ {"list", "envr list", "View your tracked files", "", {}}, {"remove", "envr remove ", "Remove a .env file from your database", "", {}}, {"check", "envr check [path]", "Check if files are backed up", "", {}}, - {"deps", "envr deps", "Check for missing binaries", + { + "deps", + "envr deps", + "Check for missing binaries", "envr relies on external binaries for certain functionality.\n\nThe check command reports on which binaries are available and which are not.", - {}}, + {}, + }, {"version", "envr version", "Show envr's version", "", {}}, {"edit-config", "envr edit-config", "Edit your config with your default editor", "", {}}, } @@ -60,8 +71,8 @@ parse_args :: proc() -> (cmd: Command, ok: bool) { arg := args[i] if strings.starts_with(arg, "--") { key := arg[2:] - if i+1 < len(args) && !strings.starts_with(args[i+1], "-") { - cmd.flags[key] = args[i+1] + if i + 1 < len(args) && !strings.starts_with(args[i + 1], "-") { + cmd.flags[key] = args[i + 1] i += 2 } else { cmd.bool_set[key] = true @@ -69,8 +80,8 @@ parse_args :: proc() -> (cmd: Command, ok: bool) { } } else if strings.starts_with(arg, "-") && len(arg) == 2 { key_slice := arg[1:2] - if i+1 < len(args) && !strings.starts_with(args[i+1], "-") { - cmd.flags[key_slice] = args[i+1] + if i + 1 < len(args) && !strings.starts_with(args[i + 1], "-") { + cmd.flags[key_slice] = args[i + 1] i += 2 } else { cmd.bool_set[key_slice] = true @@ -113,45 +124,43 @@ find_command :: proc(name: string) -> (CommandInfo, bool) { return CommandInfo{}, false } -command_help_text :: proc(name: string) -> (string, bool) { +write_command_help :: proc(name: string, w: io.Writer) -> bool { info, found := find_command(name) if !found { - return "", false + return false } - b: strings.Builder - strings.builder_init(&b) - - fmt.sbprintf(&b, "Usage: %s [flags]\n\n", info.usage) - fmt.sbprintf(&b, "%s\n", info.short) + fmt.wprintf(w, "Usage: %s [flags]\n\n", info.usage, flush = false) + fmt.wprintf(w, "%s\n", info.short, flush = false) if len(info.aliases) > 0 { - fmt.sbprintf(&b, "\nAliases:\n %s", info.name) + fmt.wprintf(w, "\nAliases:\n %s", info.name, flush = false) for a in info.aliases { - fmt.sbprintf(&b, ", %s", a) + fmt.wprintf(w, ", %s", a, flush = false) } - fmt.sbprintf(&b, "\n") + fmt.wprintf(w, "\n", flush = false) } if len(info.long) > 0 { - fmt.sbprintf(&b, "\n%s\n", info.long) + fmt.wprintf(w, "\n%s\n", info.long, flush = false) } - fmt.sbprintf(&b, "\nFlags:\n -h, --help help for %s\n", info.name) - - s := strings.clone(strings.to_string(b)) - strings.builder_destroy(&b) - return s, true + fmt.wprintf(w, "\nFlags:\n -h, --help help for %s\n", info.name, flush = false) + return true } print_command_help :: proc(name: string) { - text, ok := command_help_text(name) + bw: bufio.Writer + bufio.writer_init(&bw, io.to_writer(os.to_writer(os.stdout)), mem.DEFAULT_PAGE_SIZE) + defer bufio.writer_destroy(&bw) + + w := bufio.writer_to_writer(&bw) + ok := write_command_help(name, w) if !ok { fmt.printf("Unknown command: %s\n", name) print_usage() - return } - fmt.println(text) + bufio.writer_flush(&bw) } usage_text :: proc() -> string { @@ -159,7 +168,10 @@ usage_text :: proc() -> string { strings.builder_init(&b) fmt.sbprintf(&b, "envr keeps your .env synced to a local, age encrypted database.\n") - fmt.sbprintf(&b, "Is a safe and easy way to gather all your .env files in one place where they can\n") + fmt.sbprintf( + &b, + "Is a safe and easy way to gather all your .env files in one place where they can\n", + ) fmt.sbprintf(&b, "easily be backed by another tool such as restic or git.\n") fmt.sbprintf(&b, "\n") fmt.sbprintf(&b, "All your data is stored in ~/data.age\n") @@ -184,7 +196,10 @@ usage_text :: proc() -> string { fmt.sbprintf(&b, "\n") fmt.sbprintf(&b, "> envr sync\n") fmt.sbprintf(&b, "\n") - fmt.sbprintf(&b, "5. If you lose a repository, after re-cloning the repo into the same path it was\n") + fmt.sbprintf( + &b, + "5. If you lose a repository, after re-cloning the repo into the same path it was\n", + ) fmt.sbprintf(&b, "at before, restore your backup with:\n") fmt.sbprintf(&b, "\n") fmt.sbprintf(&b, "> envr restore ~//.env\n") @@ -203,7 +218,7 @@ usage_text :: proc() -> string { name_len := len(b.buf) - name_start padding := 20 - name_len if padding > 0 { - for _ in 0.. string { print_usage :: proc() { fmt.print(usage_text()) } + diff --git a/cli_test.odin b/cli_test.odin index b952850..08bd91a 100644 --- a/cli_test.odin +++ b/cli_test.odin @@ -34,11 +34,7 @@ test_usage_text_contains_steps :: proc(t: ^testing.T) { testing.expect(t, strings.contains(text, "4."), "missing step 4") testing.expect(t, strings.contains(text, "5."), "missing step 5") testing.expect(t, strings.contains(text, "> envr sync\n"), "step 4 missing 'envr sync'") - testing.expect( - t, - strings.contains(text, "> envr restore"), - "step 5 missing 'envr restore'", - ) + testing.expect(t, strings.contains(text, "> envr restore"), "step 5 missing 'envr restore'") } @(test) @@ -47,42 +43,37 @@ test_usage_text_contains_flags_and_help_hint :: proc(t: ^testing.T) { testing.expect(t, strings.contains(text, "Flags:"), "missing Flags section") testing.expect(t, strings.contains(text, "--help"), "missing --help flag") - testing.expect( - t, - strings.contains(text, "Use \"envr [command] --help\""), - "missing help hint", - ) + testing.expect(t, strings.contains(text, "Use \"envr [command] --help\""), "missing help hint") } @(test) test_command_help_backup :: proc(t: ^testing.T) { - text, ok := command_help_text("backup") - testing.expect(t, ok, "command_help_text(\"backup\") returned false") + b: strings.Builder + strings.builder_init(&b) + defer strings.builder_destroy(&b) + ok := write_command_help("backup", strings.to_writer(&b)) + testing.expect(t, ok, "write_command_help(\"backup\") returned false") + + text := strings.to_string(b) testing.expect(t, strings.contains(text, "Usage:"), "missing Usage line") - testing.expect( - t, - strings.contains(text, "envr backup "), - "missing usage pattern", - ) - testing.expect( - t, - strings.contains(text, "Aliases:"), - "missing Aliases section", - ) + testing.expect(t, strings.contains(text, "envr backup "), "missing usage pattern") + testing.expect(t, strings.contains(text, "Aliases:"), "missing Aliases section") testing.expect(t, strings.contains(text, "add"), "missing 'add' alias") testing.expect(t, strings.contains(text, "Flags:"), "missing Flags section") - testing.expect( - t, - strings.contains(text, "--help"), - "missing --help in flags", - ) + testing.expect(t, strings.contains(text, "--help"), "missing --help in flags") } @(test) test_command_help_add_alias :: proc(t: ^testing.T) { - text, ok := command_help_text("add") - testing.expect(t, ok, "command_help_text(\"add\") returned false") + b: strings.Builder + strings.builder_init(&b) + defer strings.builder_destroy(&b) + + ok := write_command_help("add", strings.to_writer(&b)) + testing.expect(t, ok, "write_command_help(\"add\") returned false") + + text := strings.to_string(b) testing.expect( t, strings.contains(text, "envr backup "), @@ -93,34 +84,43 @@ test_command_help_add_alias :: proc(t: ^testing.T) { @(test) test_command_help_init_no_aliases :: proc(t: ^testing.T) { - text, ok := command_help_text("init") - testing.expect(t, ok, "command_help_text(\"init\") returned false") + b: strings.Builder + strings.builder_init(&b) + defer strings.builder_destroy(&b) + ok := write_command_help("init", strings.to_writer(&b)) + testing.expect(t, ok, "write_command_help(\"init\") returned false") + + text := strings.to_string(b) testing.expect(t, strings.contains(text, "Usage:"), "missing Usage line") - testing.expect( - t, - !strings.contains(text, "Aliases:"), - "init should not have Aliases section", - ) + testing.expect(t, !strings.contains(text, "Aliases:"), "init should not have Aliases section") testing.expect(t, strings.contains(text, "Flags:"), "missing Flags section") - testing.expect( - t, - strings.contains(text, "help for init"), - "missing 'help for init'", - ) + testing.expect(t, strings.contains(text, "help for init"), "missing 'help for init'") } @(test) test_command_help_unknown :: proc(t: ^testing.T) { - text, ok := command_help_text("nonexistent") - testing.expect(t, !ok, "command_help_text(\"nonexistent\") should return false") + b: strings.Builder + strings.builder_init(&b) + defer strings.builder_destroy(&b) + + ok := write_command_help("nonexistent", strings.to_writer(&b)) + testing.expect(t, !ok, "write_command_help(\"nonexistent\") should return false") + + text := strings.to_string(b) testing.expect(t, len(text) == 0, "text should be empty for unknown command") } @(test) test_command_help_version :: proc(t: ^testing.T) { - text, ok := command_help_text("version") - testing.expect(t, ok, "command_help_text(\"version\") returned false") + b: strings.Builder + strings.builder_init(&b) + defer strings.builder_destroy(&b) + + ok := write_command_help("version", strings.to_writer(&b)) + testing.expect(t, ok, "write_command_help(\"version\") returned false") + + text := strings.to_string(b) testing.expect(t, strings.contains(text, "Usage:"), "missing Usage line") testing.expect( t, @@ -128,3 +128,4 @@ test_command_help_version :: proc(t: ^testing.T) { "version should not have Aliases section", ) } + diff --git a/cmd_check.odin b/cmd_check.odin index 30311c9..5294da8 100644 --- a/cmd_check.odin +++ b/cmd_check.odin @@ -6,74 +6,79 @@ import "core:path/filepath" import "core:strings" cmd_check :: proc(cmd: ^Command) { - check_path: string - if len(cmd.args) > 0 { - check_path = cmd.args[0] - } else { - cwd, cwd_err := os.get_working_directory(context.allocator) - if cwd_err != nil { - fmt.printf("Error getting current directory: %v\n", cwd_err) - return - } - check_path = cwd - } + feats := check_features() - abs_path: string - if filepath.is_abs(check_path) { - abs_path = check_path - } else { - resolved, abs_err := filepath.abs(check_path) - if abs_err != nil { - fmt.printf("Error getting absolute path: %v\n", abs_err) - return - } - abs_path = resolved - } + check_path: string + if len(cmd.args) > 0 { + check_path = cmd.args[0] + } else { + cwd, cwd_err := os.get_working_directory(context.allocator) + if cwd_err != nil { + fmt.printf("Error getting current directory: %v\n", cwd_err) + return + } + check_path = cwd + } - db, db_ok := db_open() - if !db_ok { - return - } - defer db_close(&db) + abs_path: string + if filepath.is_abs(check_path) { + abs_path = check_path + } else { + resolved, abs_err := filepath.abs(check_path) + if abs_err != nil { + fmt.printf("Error getting absolute path: %v\n", abs_err) + return + } + abs_path = resolved + } - is_dir := os.is_directory(abs_path) + db, db_ok := db_open() + if !db_ok { + return + } + defer db_close(&db) - files_in_path: [dynamic]string + is_dir := os.is_directory(abs_path) - if is_dir { - if !can_scan() { - fmt.println("Error: please install fd to use the check command (https://github.com/sharkdp/fd)") - return - } + files_in_path: [dynamic]string - scanned, scan_ok := scan_path(abs_path, db.cfg) - if !scan_ok { - fmt.println("Error scanning directory for .env files") - return - } - files_in_path = scanned - } else { - append(&files_in_path, abs_path) - } + if is_dir { + if cant_scan(feats) { + fmt.println( + "Error: please install fd to use the check command (https://github.com/sharkdp/fd)", + ) + return + } - db_files, list_ok := db_list(&db) - if !list_ok { - return - } + scanned, scan_ok := scan_path(abs_path, db.cfg) + if !scan_ok { + fmt.println("Error scanning directory for .env files") + return + } + files_in_path = scanned + } else { + append(&files_in_path, abs_path) + } - not_backed := find_unbacked(files_in_path[:], db_files[:]) + db_files, list_ok := db_list(&db) + if !list_ok { + return + } - if len(not_backed) == 0 { - if len(files_in_path) == 0 { - fmt.println("No .env files found in the specified directory.") - } else { - fmt.println("✓ All .env files in the directory are backed up.") - } - } else { - fmt.printf("Found %d .env file(s) that are not backed up:\n", len(not_backed)) - for file in not_backed { - fmt.printf(" %s\n", file) - } - fmt.println("\nRun 'envr sync' to back up these files.") - } + not_backed := find_unbacked(files_in_path[:], db_files[:]) + + if len(not_backed) == 0 { + if len(files_in_path) == 0 { + fmt.println("No .env files found in the specified directory.") + } else { + fmt.println("✓ All .env files in the directory are backed up.") + } + } else { + fmt.printf("Found %d .env file(s) that are not backed up:\n", len(not_backed)) + for file in not_backed { + fmt.printf(" %s\n", file) + } + fmt.println("\nRun 'envr sync' to back up these files.") + } } + diff --git a/cmd_check_test.odin b/cmd_check_test.odin index 10b7a3c..21c6b35 100644 --- a/cmd_check_test.odin +++ b/cmd_check_test.odin @@ -5,39 +5,44 @@ import "core:testing" @(test) test_find_unbacked_finds_missing :: proc(t: ^testing.T) { - local := []string{"/a/.env", "/b/.env", "/c/.env"} - db := []EnvFile{{Path = "/a/.env"}, {Path = "/b/.env"}} + local := []string{"/a/.env", "/b/.env", "/c/.env"} + db := []EnvFile{{Path = "/a/.env"}, {Path = "/b/.env"}} - result := find_unbacked(local, db[:]) - testing.expect(t, len(result) == 1, fmt.aprintf("expected 1 unbacked, got %d", len(result))) - if len(result) > 0 { - testing.expect(t, result[0] == "/c/.env", fmt.aprintf("expected /c/.env, got %s", result[0])) - } + result := find_unbacked(local, db[:]) + testing.expect(t, len(result) == 1, fmt.aprintf("expected 1 unbacked, got %d", len(result))) + if len(result) > 0 { + testing.expect( + t, + result[0] == "/c/.env", + fmt.aprintf("expected /c/.env, got %s", result[0]), + ) + } } @(test) test_find_unbacked_all_backed :: proc(t: ^testing.T) { - local := []string{"/a/.env", "/b/.env"} - db := []EnvFile{{Path = "/a/.env"}, {Path = "/b/.env"}} + local := []string{"/a/.env", "/b/.env"} + db := []EnvFile{{Path = "/a/.env"}, {Path = "/b/.env"}} - result := find_unbacked(local, db[:]) - testing.expect(t, len(result) == 0, fmt.aprintf("expected 0 unbacked, got %d", len(result))) + result := find_unbacked(local, db[:]) + testing.expect(t, len(result) == 0, fmt.aprintf("expected 0 unbacked, got %d", len(result))) } @(test) test_find_unbacked_no_local :: proc(t: ^testing.T) { - local: []string - db := []EnvFile{{Path = "/a/.env"}} + local: []string + db := []EnvFile{{Path = "/a/.env"}} - result := find_unbacked(local, db[:]) - testing.expect(t, len(result) == 0, fmt.aprintf("expected 0 unbacked, got %d", len(result))) + result := find_unbacked(local, db[:]) + testing.expect(t, len(result) == 0, fmt.aprintf("expected 0 unbacked, got %d", len(result))) } @(test) test_find_unbacked_none_backed :: proc(t: ^testing.T) { - local := []string{"/a/.env", "/b/.env"} - db: []EnvFile + local := []string{"/a/.env", "/b/.env"} + db: []EnvFile - result := find_unbacked(local, db[:]) - testing.expect(t, len(result) == 2, fmt.aprintf("expected 2 unbacked, got %d", len(result))) + result := find_unbacked(local, db[:]) + testing.expect(t, len(result) == 2, fmt.aprintf("expected 2 unbacked, got %d", len(result))) } + diff --git a/cmd_scan.odin b/cmd_scan.odin index 02dd56c..987a96a 100644 --- a/cmd_scan.odin +++ b/cmd_scan.odin @@ -4,7 +4,8 @@ import "core:encoding/json" import "core:fmt" cmd_scan :: proc(cmd: ^Command) { - if !can_scan() { + feats := check_features() + if cant_scan(feats) { fmt.println( "Error: please install fd to use the scan command (https://github.com/sharkdp/fd)", ) diff --git a/features_test.odin b/features_test.odin index feaaf39..c48fd97 100644 --- a/features_test.odin +++ b/features_test.odin @@ -6,8 +6,8 @@ import "core:testing" @(test) test_find_binary_exists :: proc(t: ^testing.T) { - path := os.get_env("PATH", context.allocator) - paths := strings.split(path, ":") + path := os.get_env("PATH", context.temp_allocator) + paths := strings.split(path, ":", context.temp_allocator) result := find_binary(paths, "sh") testing.expect(t, result != "", "sh should be found on PATH") @@ -15,7 +15,7 @@ test_find_binary_exists :: proc(t: ^testing.T) { @(test) test_find_binary_not_exists :: proc(t: ^testing.T) { - old_path := os.get_env("PATH", context.allocator) + old_path := os.get_env("PATH", context.temp_allocator) defer { if old_path != "" { os.set_env("PATH", old_path) @@ -24,8 +24,8 @@ test_find_binary_not_exists :: proc(t: ^testing.T) { os.set_env("PATH", "/tmp/envr-nope") - path := os.get_env("PATH", context.allocator) - paths := strings.split(path, ":") + path := os.get_env("PATH", context.temp_allocator) + paths := strings.split(path, ":", context.temp_allocator) result := find_binary(paths, "no_such_binary_xyz") diff --git a/scan.odin b/scan.odin index 8c99930..53a60a0 100644 --- a/scan.odin +++ b/scan.odin @@ -2,23 +2,49 @@ package main import "core:fmt" import "core:os" -import "core:path/filepath" import "core:strings" import "core:sync" fd_counter: sync.Atomic_Mutex fd_seq: int -next_fd_tmp_path :: proc() -> string { - sync.atomic_mutex_lock(&fd_counter) - n := fd_seq - fd_seq += 1 - sync.atomic_mutex_unlock(&fd_counter) - return fmt.aprintf("/tmp/envr-fd-%d-%d", os.get_pid(), n) +// Caller is responsible for freeing paths +scan_path :: proc(search_path: string, cfg: Config) -> (paths: [dynamic]string, ok: bool) { + if is_tty() { + fmt.printf("Searching for all files in \"%s\"...\n", search_path) + } + all_files, all_ok := run_fd(build_fd_args(search_path, cfg, true)) + if !all_ok { + return + } + + if is_tty() { + fmt.printf("Search for unignored fies in \"%s\"...\n", search_path) + } + unignored_files, unignored_ok := run_fd(build_fd_args(search_path, cfg, false)) + if !unignored_ok { + return + } + + unignored_set := make(map[string]bool, len(unignored_files), context.temp_allocator) + for file in unignored_files { + unignored_set[file] = true + } + + for file in all_files { + if !(file in unignored_set) { + append(&paths, file) + } + } + + ok = true + return } +@(private = "file") build_fd_args :: proc(search_path: string, cfg: Config, include_ignored: bool) -> []string { - args := make([dynamic]string, 0, 3 + 2 * len(cfg.ScanConfig.Exclude) + 2) + args_len := 3 + 2 * len(cfg.ScanConfig.Exclude) + 2 + args := make([dynamic]string, 0, args_len, context.temp_allocator) append(&args, "fd") append(&args, "-a") append(&args, cfg.ScanConfig.Matcher) @@ -38,7 +64,7 @@ build_fd_args :: proc(search_path: string, cfg: Config, include_ignored: bool) - return args[:] } -run_fd :: proc(args: []string) -> (lines: [dynamic]string, ok: bool) { +run_fd :: proc(args: []string) -> (lines: []string, ok: bool) { tmp_path := next_fd_tmp_path() tmp_file, tmp_err := os.open(tmp_path, os.O_CREATE | os.O_WRONLY | os.O_TRUNC) if tmp_err != nil { @@ -64,7 +90,7 @@ run_fd :: proc(args: []string) -> (lines: [dynamic]string, ok: bool) { return } - data, read_err := os.read_entire_file_from_path(tmp_path, context.allocator) + data, read_err := os.read_entire_file_from_path(tmp_path, context.temp_allocator) os.remove(tmp_path) if read_err != nil { return @@ -77,69 +103,44 @@ run_fd :: proc(args: []string) -> (lines: [dynamic]string, ok: bool) { return } - raw_lines := strings.split(output, "\n") + raw_lines := strings.split(output, "\n", context.temp_allocator) + result := make([dynamic]string, 0, len(raw_lines), context.temp_allocator) for line in raw_lines { - trimmed, _ := strings.clone(strings.trim_space(line)) + trimmed := strings.trim_space(line) if len(trimmed) > 0 { - append(&lines, trimmed) + append(&result, trimmed) } } - ok = true - return + return result[:], true } -scan_path :: proc(search_path: string, cfg: Config) -> (paths: [dynamic]string, ok: bool) { - if is_tty() { - fmt.printf("Searching for all files in \"%s\"...\n", search_path) - } - all_args := build_fd_args(search_path, cfg, true) - all_files, all_ok := run_fd(all_args) - if !all_ok { - return - } - - if is_tty() { - fmt.printf("Search for unignored fies in \"%s\"...\n", search_path) - } - unignored_args := build_fd_args(search_path, cfg, false) - unignored_files, unignored_ok := run_fd(unignored_args) - if !unignored_ok { - return - } - - unignored_set: map[string]bool - for file in unignored_files { - unignored_set[file] = true - } - - for file in all_files { - if !(file in unignored_set) { - append(&paths, file) - } - } - - ok = true - return +@(private = "file") +next_fd_tmp_path :: proc() -> string { + sync.atomic_mutex_lock(&fd_counter) + n := fd_seq + fd_seq += 1 + sync.atomic_mutex_unlock(&fd_counter) + return fmt.aprintf("/tmp/envr-fd-%d-%d", os.get_pid(), n, allocator = context.temp_allocator) } -can_scan :: proc() -> bool { - feats := check_features() - return Feature.Fd in feats +cant_scan :: proc(feats: AvailableFeatures) -> bool { + return Feature.Fd not_in feats } -find_unbacked :: proc(local_files: []string, db_files: []EnvFile) -> [dynamic]string { - backed_set: map[string]bool +find_unbacked :: proc(local_files: []string, db_files: []EnvFile) -> []string { + // Lives until the end of the function + backed_set := make(map[string]bool, len(db_files), context.temp_allocator) for file in db_files { backed_set[file.Path] = true } - unbacked: [dynamic]string + unbacked := make([dynamic]string, 0, len(db_files) / 2, context.temp_allocator) for file in local_files { if !(file in backed_set) { append(&unbacked, file) } } - return unbacked + return unbacked[:] } diff --git a/scan_test.odin b/scan_test.odin index 41bab06..49880bb 100644 --- a/scan_test.odin +++ b/scan_test.odin @@ -7,88 +7,81 @@ import "core:testing" @(test) test_scan_path_finds_gitignored_env_files :: proc(t: ^testing.T) { - if !can_scan() { - return - } + feats := check_features() + testing.expect(t, cant_scan(feats) == false) - base := fmt.aprintf("/tmp/envr-scan-test-%d", os.get_pid()) - os.mkdir_all(base) - defer os.remove_all(base) + base := fmt.aprintf("/tmp/envr-scan-test-%d", os.get_pid()) + os.mkdir_all(base) + defer os.remove_all(base) - git_init := os.Process_Desc{ - command = []string{"git", "-c", "advice.defaultBranchName=false", "init"}, - working_dir = base, - stdout = os.stderr, - stderr = os.stderr, - } - p, err := os.process_start(git_init) - if err != nil { - return - } - _, wait_err := os.process_wait(p) - if wait_err != nil { - return - } + git_init := os.Process_Desc { + command = []string{"git", "-c", "advice.defaultBranchName=false", "init"}, + working_dir = base, + stdout = os.stderr, + stderr = os.stderr, + } + p, err := os.process_start(git_init) + if err != nil { + return + } + _, wait_err := os.process_wait(p) + if wait_err != nil { + return + } - gitignore_path := fmt.aprintf("%s/.gitignore", base) - _ = os.write_entire_file(gitignore_path, ".env*\n") + gitignore_path := fmt.aprintf("%s/.gitignore", base) + _ = os.write_entire_file(gitignore_path, ".env*\n") - _ = os.write_entire_file(fmt.aprintf("%s/.env", base), "SECRET=1") - _ = os.write_entire_file(fmt.aprintf("%s/.env.testing", base), "TEST=1") - _ = os.write_entire_file(fmt.aprintf("%s/config.yaml", base), "key: value") + _ = os.write_entire_file(fmt.aprintf("%s/.env", base), "SECRET=1") + _ = os.write_entire_file(fmt.aprintf("%s/.env.testing", base), "TEST=1") + _ = os.write_entire_file(fmt.aprintf("%s/config.yaml", base), "key: value") - cfg := Config{ - ScanConfig = ScanConfig{ - Matcher = "\\.env", - Exclude = []string{}, - Include = []string{}, - }, - } + cfg := Config { + ScanConfig = ScanConfig{Matcher = "\\.env", Exclude = []string{}, Include = []string{}}, + } - results, ok := scan_path(base, cfg) - testing.expect(t, ok, "scan_path should succeed") + results, ok := scan_path(base, cfg) + defer delete(results) + testing.expect(t, ok, "scan_path should succeed") - found_env := false - found_testing := false - found_config := false + found_env := false + found_testing := false + found_config := false - for path in results { - _, filename := filepath.split(path) - if filename == ".env" { - found_env = true - } - if filename == ".env.testing" { - found_testing = true - } - if filename == "config.yaml" { - found_config = true - } - } + for path in results { + _, filename := filepath.split(path) + if filename == ".env" { + found_env = true + } + if filename == ".env.testing" { + found_testing = true + } + if filename == "config.yaml" { + found_config = true + } + } - testing.expect(t, found_env, "should find .env (gitignored)") - testing.expect(t, found_testing, "should find .env.testing (gitignored)") - testing.expect(t, !found_config, "should NOT find config.yaml (not gitignored)") + testing.expect(t, found_env, "should find .env (gitignored)") + testing.expect(t, found_testing, "should find .env.testing (gitignored)") + testing.expect(t, !found_config, "should NOT find config.yaml (not gitignored)") } @(test) test_scan_path_empty_dir :: proc(t: ^testing.T) { - if !can_scan() { - return - } + feats := check_features() + testing.expect(t, cant_scan(feats) == false) - base := fmt.aprintf("/tmp/envr-scan-empty-%d", os.get_pid()) - os.mkdir_all(base) - defer os.remove_all(base) + base := fmt.aprintf("/tmp/envr-scan-empty-%d", os.get_pid()) + os.mkdir_all(base) + defer os.remove_all(base) - cfg := Config{ - ScanConfig = ScanConfig{ - Matcher = "\\.env", - Exclude = []string{}, - Include = []string{}, - }, - } + cfg := Config { + ScanConfig = ScanConfig{Matcher = "\\.env", Exclude = []string{}, Include = []string{}}, + } - results, ok := scan_path(base, cfg) - testing.expect(t, ok, "scan_path should succeed") - testing.expect(t, len(results) == 0, fmt.aprintf("expected 0 results, got %d", len(results))) + results, ok := scan_path(base, cfg) + defer delete(results) + testing.expect(t, ok, "scan_path should succeed") + testing.expect(t, len(results) == 0, fmt.aprintf("expected 0 results, got %d", len(results))) } + diff --git a/table.odin b/table.odin index bafddde..5fab02b 100644 --- a/table.odin +++ b/table.odin @@ -15,11 +15,11 @@ render_table :: proc(headers: []string, rows: [][]string) { } col_widths := make([dynamic]int, 0, len(headers)) - for i in 0.. col_widths[i] { col_widths[i] = w @@ -34,11 +34,11 @@ render_table :: proc(headers: []string, rows: [][]string) { hline :: proc(b: ^strings.Builder, left, mid, right: string, widths: [dynamic]int) { strings.write_string(b, left) - for i in 0..