diff --git a/TODOS.md b/TODOS.md index 64ce93d..6082509 100644 --- a/TODOS.md +++ b/TODOS.md @@ -32,8 +32,6 @@ Note: These todos can wait until all the subcommands have been ported. 35. **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))`. -39. Lots of memory leaks to fix. - ## LOW 15. **db.odin:115** — `json.unmarshal_string` error not checked. Malformed JSON silently produces empty/partial data. diff --git a/cli.odin b/cli.odin index fbf6ef1..a55e6c8 100644 --- a/cli.odin +++ b/cli.odin @@ -3,7 +3,6 @@ package main import "core:bufio" import "core:fmt" import "core:io" -import "core:mem" import "core:os" import "core:strings" @@ -13,6 +12,9 @@ Command :: struct { flags: map[string]string, bool_set: map[string]bool, config_path: string, + out_buf: ^bufio.Writer, + out: io.Writer, + err: io.Writer, } CommandInfo :: struct { @@ -28,7 +30,10 @@ 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.", + `The init command generates your initial config and saves it to +~/.envr/config in JSON format.\n\nDuring setup, you will be prompted to select one or more ssh keys with which to +encrypt your databse. **Make 100% sure** that you have **a remote copy** of this +key somewhere, otherwise your data could be lost forever.`, {}, }, {"scan", "envr scan", "Find and select .env files for backup", "", {}}, @@ -60,15 +65,23 @@ delete_command :: proc(cmd: ^Command) { delete(cmd.args) delete(cmd.flags) delete(cmd.bool_set) - // delete(cmd.config_path) + bufio.writer_destroy(cmd.out_buf) + free(cmd.out_buf) } // Caller is responsible for calling delete_command(cmd). // FIXME: Works in kinda a wonky and awkward way. -parse_args :: proc(args: []string) -> (cmd: Command, ok: bool) { +parse_args :: proc(args: []string, out: io.Stream, err: io.Stream) -> (cmd: Command, ok: bool) { + { + cmd.out_buf = new(bufio.Writer) + bufio.writer_init(cmd.out_buf, out) + cmd.out = bufio.writer_to_writer(cmd.out_buf) + cmd.err = err + } + if len(args) < 2 || args[1] == "--help" || args[1] == "-h" { - print_usage() - return Command{}, false + write_usage(cmd.out) + return cmd, false } cmd.name = args[1] @@ -117,8 +130,8 @@ parse_args :: proc(args: []string) -> (cmd: Command, ok: bool) { } if has_flag(&cmd, "help") { - print_command_help(cmd.name) - return Command{}, false + print_command_help(&cmd) + return cmd, false } return cmd, true @@ -177,18 +190,12 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool { return true } -print_command_help :: proc(name: string) { - 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) +print_command_help :: proc(cmd: ^Command) { + ok := write_command_help(cmd.name, cmd.out) if !ok { - fmt.printf("Unknown command: %s\n", name) - print_usage() + fmt.wprintf(cmd.err, "Unknown command: %s\n", cmd.name) + write_usage(cmd.out) } - bufio.writer_flush(&bw) } // TODO: command args should be shown in usage. @@ -263,13 +270,3 @@ Use "envr [command] --help" for more information about a command. ) } -// TODO: Look at usages,might want to pass a writer -print_usage :: proc() { - bw: bufio.Writer - bufio.writer_init(&bw, io.to_writer(os.to_writer(os.stdout)), mem.DEFAULT_PAGE_SIZE) - defer bufio.writer_destroy(&bw) - defer bufio.writer_flush(&bw) - - write_usage(bufio.writer_to_writer(&bw)) -} - diff --git a/cli_test.odin b/cli_test.odin index ccad4c0..bcad5e7 100644 --- a/cli_test.odin +++ b/cli_test.odin @@ -2,6 +2,7 @@ package main +import "core:bufio" import "core:fmt" import "core:strings" import "core:testing" @@ -189,9 +190,35 @@ test_has_flag_empty_command :: proc(t: ^testing.T) { testing.expect(t, !has_flag(&cmd, "anything"), "empty command should have no flags") } +test_parse_args :: proc( + args: []string, +) -> ( + cmd: Command, + ok: bool, + out_text: string, + err_text: string, +) { + out_b: strings.Builder + strings.builder_init(&out_b) + defer strings.builder_destroy(&out_b) + err_b: strings.Builder + strings.builder_init(&err_b) + defer strings.builder_destroy(&err_b) + + cmd, ok = parse_args(args, strings.to_stream(&out_b), strings.to_stream(&err_b)) + + if ok { + bufio.writer_flush(cmd.out_buf) + out_text = strings.to_string(out_b) + err_text = strings.to_string(err_b) + } + + return +} + @(test) test_parse_args_bare_command :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "list"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "list"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -204,10 +231,9 @@ test_parse_args_bare_command :: proc(t: ^testing.T) { @(test) test_parse_args_positional :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "backup", "/project/.env"}) - testing.expect(t, ok, "should succeed") - if !ok do return + cmd, ok, _, _ := test_parse_args([]string{"envr", "backup", "/project/.env"}) defer delete_command(&cmd) + testing.expect(t, ok, "should succeed") testing.expect(t, cmd.name == "backup") testing.expect(t, len(cmd.args) == 1) @@ -216,7 +242,7 @@ test_parse_args_positional :: proc(t: ^testing.T) { @(test) test_parse_args_long_flag_with_value :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "sync", "--config", "x.json"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "sync", "--config", "x.json"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -226,7 +252,7 @@ test_parse_args_long_flag_with_value :: proc(t: ^testing.T) { @(test) test_parse_args_short_flag_with_value :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "sync", "-c", "x.json"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "sync", "-c", "x.json"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -236,7 +262,7 @@ test_parse_args_short_flag_with_value :: proc(t: ^testing.T) { @(test) test_parse_args_long_bool_flag :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "init", "--force"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "init", "--force"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -246,7 +272,7 @@ test_parse_args_long_bool_flag :: proc(t: ^testing.T) { @(test) test_parse_args_short_bool_flag :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "version", "-l"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "version", "-l"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -256,7 +282,7 @@ test_parse_args_short_bool_flag :: proc(t: ^testing.T) { @(test) test_parse_args_multiple_positionals :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "backup", "a", "b"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "backup", "a", "b"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -268,7 +294,7 @@ test_parse_args_multiple_positionals :: proc(t: ^testing.T) { @(test) test_parse_args_mixed_flags_and_positionals :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "backup", "/project/.env", "--force"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "backup", "/project/.env", "--force"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -280,16 +306,16 @@ test_parse_args_mixed_flags_and_positionals :: proc(t: ^testing.T) { @(test) test_parse_args_no_args :: proc(t: ^testing.T) { - _, ok := parse_args([]string{"envr"}) + cmd, ok, _, _ := test_parse_args([]string{"envr"}) + defer delete_command(&cmd) testing.expect(t, !ok, "no args should return false") } @(test) test_parse_args_flag_then_positional_then_flag :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "backup", "a.env", "--force", "--verbose"}) - testing.expect(t, ok, "should succeed") - if !ok do return + cmd, ok, _, _ := test_parse_args([]string{"envr", "backup", "a.env", "--force", "--verbose"}) defer delete_command(&cmd) + testing.expect(t, ok, "should succeed") testing.expect(t, cmd.bool_set["force"] == true) testing.expect(t, cmd.bool_set["verbose"] == true) @@ -299,7 +325,9 @@ test_parse_args_flag_then_positional_then_flag :: proc(t: ^testing.T) { @(test) test_parse_args_config_file_long_flag :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "list", "--config-file", "/custom/config.json"}) + cmd, ok, _, _ := test_parse_args( + []string{"envr", "list", "--config-file", "/custom/config.json"}, + ) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -313,7 +341,7 @@ test_parse_args_config_file_long_flag :: proc(t: ^testing.T) { @(test) test_parse_args_config_file_short_flag :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "list", "-c", "/custom/config.json"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-c", "/custom/config.json"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) @@ -327,7 +355,7 @@ test_parse_args_config_file_short_flag :: proc(t: ^testing.T) { @(test) test_parse_args_config_file_defaults :: proc(t: ^testing.T) { - cmd, ok := parse_args([]string{"envr", "list"}) + cmd, ok, _, _ := test_parse_args([]string{"envr", "list"}) testing.expect(t, ok, "should succeed") if !ok do return defer delete_command(&cmd) diff --git a/cmd_backup.odin b/cmd_backup.odin index a91e24c..7939ebe 100644 --- a/cmd_backup.odin +++ b/cmd_backup.odin @@ -5,13 +5,13 @@ import "core:strings" cmd_backup :: proc(cmd: ^Command) { if len(cmd.args) != 1 { - print_command_help("backup") + print_command_help(cmd) return } path := cmd.args[0] if len(strings.trim_space(path)) == 0 { - fmt.println("Error: No path provided") + fmt.wprintln(cmd.err, "Error: No path provided", flush = false) return } @@ -30,6 +30,6 @@ cmd_backup :: proc(cmd: ^Command) { return } - fmt.printf("Saved %s into the database\n", path) + fmt.wprintf(cmd.out, "Saved %s into the database\n", path, flush = false) } diff --git a/cmd_check.odin b/cmd_check.odin index 028e440..1dd0f94 100644 --- a/cmd_check.odin +++ b/cmd_check.odin @@ -13,7 +13,7 @@ cmd_check :: proc(cmd: ^Command) { } else { cwd, cwd_err := os.get_working_directory(context.temp_allocator) if cwd_err != nil { - fmt.printf("Error getting current directory: %v\n", cwd_err) + fmt.wprintf(cmd.err, "Error getting current directory: %v\n", cwd_err, flush = false) return } check_path = cwd @@ -25,7 +25,7 @@ cmd_check :: proc(cmd: ^Command) { } else { resolved, abs_err := filepath.abs(check_path) if abs_err != nil { - fmt.printf("Error getting absolute path: %v\n", abs_err) + fmt.wprintf(cmd.err, "Error getting absolute path: %v\n", abs_err, flush = false) return } abs_path = resolved @@ -43,15 +43,17 @@ cmd_check :: proc(cmd: ^Command) { if is_dir { if cant_scan(feats) { - fmt.println( + fmt.wprintln( + cmd.err, "Error: please install fd to use the check command (https://github.com/sharkdp/fd)", + flush = false, ) return } scanned, scan_ok := scan_path(abs_path, db.cfg) if !scan_ok { - fmt.println("Error scanning directory for .env files") + fmt.wprintln(cmd.err, "Error scanning directory for .env files", flush = false) return } files_in_path = scanned @@ -68,16 +70,15 @@ cmd_check :: proc(cmd: ^Command) { if len(not_backed) == 0 { if len(files_in_path) == 0 { - fmt.println("No .env files found in the specified directory.") + fmt.wprintln(cmd.out, "No .env files found in the specified directory.", flush = false) } else { - fmt.println("✓ All .env files in the directory are backed up.") + fmt.wprintln(cmd.out, "✓ All .env files in the directory are backed up.", flush = false) } } else { - fmt.printf("Found %d .env file(s) that are not backed up:\n", len(not_backed)) + fmt.wprintf(cmd.out, "Found %d .env file(s) that are not backed up:\n", len(not_backed), flush = false) for file in not_backed { - fmt.printf(" %s\n", file) + fmt.wprintf(cmd.out, " %s\n", file, flush = false) } - fmt.println("\nRun 'envr sync' to back up these files.") + fmt.wprintln(cmd.out, "\nRun 'envr sync' to back up these files.", flush = false) } } - diff --git a/cmd_deps.odin b/cmd_deps.odin index 8ae3bfd..9680d96 100644 --- a/cmd_deps.odin +++ b/cmd_deps.odin @@ -1,6 +1,6 @@ package main -import "core:io" +import "core:fmt" import "core:os" import "core:terminal" @@ -24,12 +24,10 @@ cmd_deps :: proc(cmd: ^Command) { } if terminal.is_terminal(os.stdout) { - w := io.to_writer(os.to_writer(os.stdout)) - render_table(w, headers, rows[:]) + render_table(cmd.out, headers, rows[:]) } else { - w := io.to_writer(os.to_writer(os.stdout)) - render_json_rows(w, headers, rows[:]) - io.write_string(w, "\n") + render_json_rows(cmd.out, headers, rows[:]) + fmt.wprint(cmd.out, "\n", flush = false) } } diff --git a/cmd_edit_config.odin b/cmd_edit_config.odin index 948a3ce..32eac36 100644 --- a/cmd_edit_config.odin +++ b/cmd_edit_config.odin @@ -6,7 +6,7 @@ import "core:os" cmd_edit_config :: proc(cmd: ^Command) { editor := os.get_env("EDITOR", context.allocator) if len(editor) == 0 { - fmt.println("Error: $EDITOR environment variable is not set") + fmt.wprintln(cmd.err, "Error: $EDITOR environment variable is not set", flush = false) return } @@ -14,7 +14,12 @@ cmd_edit_config :: proc(cmd: ^Command) { _, stat_err := os.stat(config_path, context.allocator) if stat_err != nil { - fmt.printf("Config file does not exist at %s. Run 'envr init' first.\n", config_path) + fmt.wprintf( + cmd.err, + "Config file does not exist at %s. Run 'envr init' first.\n", + config_path, + flush = false, + ) return } @@ -28,13 +33,13 @@ cmd_edit_config :: proc(cmd: ^Command) { p, start_err := os.process_start(desc) if start_err != nil { - fmt.printf("Error running editor: %v\n", start_err) + fmt.wprintf(cmd.err, "Error running editor: %v\n", start_err, flush = false) return } state, wait_err := os.process_wait(p) if wait_err != nil { - fmt.printf("Error waiting for editor: %v\n", wait_err) + fmt.wprintf(cmd.err, "Error waiting for editor: %v\n", wait_err, flush = false) return } if state.exit_code != 0 { diff --git a/cmd_init.odin b/cmd_init.odin index 8eef822..305af6e 100644 --- a/cmd_init.odin +++ b/cmd_init.odin @@ -5,13 +5,15 @@ import "core:fmt" cmd_init :: proc(cmd: ^Command) { force := has_flag(cmd, "force") || has_flag(cmd, "f") - fmt.println(cmd.config_path) + fmt.wprintln(cmd.out, cmd.config_path, flush = false) _, cfg_exists := load_config(cmd.config_path) if cfg_exists && !force { - fmt.println( + fmt.wprintln( + cmd.out, `You have already initialized envr. Run again with the --force flag if you want to reinitialize.`, + flush = false, ) return } @@ -22,15 +24,15 @@ Run again with the --force flag if you want to reinitialize.`, } if len(keys) == 0 { - fmt.println(`No ssh-ed25519 keys found in ~/.ssh -Generate one with: ssh-keygen -t ed25519`) + fmt.wprintln(cmd.err, `No ssh-ed25519 keys found in ~/.ssh +Generate one with: ssh-keygen -t ed25519`, flush = false) return } selected, result := multi_select("Select SSH private keys:", keys[:]) defer delete(selected) if result == .Cancel { - fmt.println("\x1b[2mCancelled.\x1b[0m") + fmt.wprintln(cmd.out, "\x1b[2mCancelled.\x1b[0m", flush = false) return } @@ -42,7 +44,7 @@ Generate one with: ssh-keygen -t ed25519`) } if len(selected_paths) == 0 { - fmt.println("No SSH keys selected - Config not created") + fmt.wprintln(cmd.err, "No SSH keys selected - Config not created", flush = false) return } @@ -51,9 +53,10 @@ Generate one with: ssh-keygen -t ed25519`) return } - fmt.printf( + fmt.wprintf( + cmd.out, "Config initialized with %d SSH key(s). You are ready to use envr.\n", len(selected_paths), + flush = false, ) } - diff --git a/cmd_list.odin b/cmd_list.odin index c263763..10fb49f 100644 --- a/cmd_list.odin +++ b/cmd_list.odin @@ -2,7 +2,6 @@ package main import "core:encoding/json" import "core:fmt" -import "core:io" import "core:os" import "core:path/filepath" import "core:strings" @@ -41,8 +40,7 @@ cmd_list :: proc(cmd: ^Command) { append(&table_rows, row_slice) } - w := io.to_writer(os.to_writer(os.stdout)) - render_table(w, headers, table_rows[:]) + render_table(cmd.out, headers, table_rows[:]) } else { // TODO: Should we instead print full entries here? entries: [dynamic]ListEntry @@ -59,10 +57,10 @@ cmd_list :: proc(cmd: ^Command) { data, marshal_err := json.marshal(entries[:], allocator = context.temp_allocator) if marshal_err != nil { - fmt.printf("Error marshaling JSON: %v\n", marshal_err) + fmt.wprintf(cmd.err, "Error marshaling JSON: %v\n", marshal_err, flush = false) return } - fmt.println(string(data)) + fmt.wprintln(cmd.out, string(data), flush = false) } } diff --git a/cmd_nushell_completion.odin b/cmd_nushell_completion.odin index df29172..9da7034 100644 --- a/cmd_nushell_completion.odin +++ b/cmd_nushell_completion.odin @@ -5,7 +5,6 @@ import "core:fmt" COMPLETION_SCRIPT: string : string(#load("mod.nu")) cmd_nushell_completion :: proc(cmd: ^Command) { - // TODO: Use buffered writer? - fmt.print(COMPLETION_SCRIPT) + fmt.wprint(cmd.out, COMPLETION_SCRIPT, flush = false) } diff --git a/cmd_remove.odin b/cmd_remove.odin index 8027723..7657cde 100644 --- a/cmd_remove.odin +++ b/cmd_remove.odin @@ -6,13 +6,13 @@ import "core:strings" cmd_remove :: proc(cmd: ^Command) { if len(cmd.args) != 1 { - print_command_help("remove") + print_command_help(cmd) return } path := cmd.args[0] if len(strings.trim_space(path)) == 0 { - fmt.println("Error: No path provided") + fmt.wprintln(cmd.err, "Error: No path provided", flush = false) return } @@ -23,7 +23,7 @@ cmd_remove :: proc(cmd: ^Command) { } else { resolved, abs_err := filepath.abs(path) if abs_err != nil { - fmt.printf("Error getting absolute path: %v\n", abs_err) + fmt.wprintf(cmd.err, "Error getting absolute path: %v\n", abs_err, flush = false) return } abs_path = resolved @@ -39,6 +39,6 @@ cmd_remove :: proc(cmd: ^Command) { return } - fmt.printf("Removed %s from the database\n", abs_path) + fmt.wprintf(cmd.out, "Removed %s from the database\n", abs_path, flush = false) } diff --git a/cmd_restore.odin b/cmd_restore.odin index 2e0355d..01e98b4 100644 --- a/cmd_restore.odin +++ b/cmd_restore.odin @@ -7,13 +7,13 @@ import "core:strings" cmd_restore :: proc(cmd: ^Command) { if len(cmd.args) != 1 { - print_command_help("restore") + print_command_help(cmd) return } path := cmd.args[0] if len(strings.trim_space(path)) == 0 { - fmt.println("Error: No path provided") + fmt.wprintln(cmd.err, "Error: No path provided", flush = false) return } @@ -24,7 +24,7 @@ cmd_restore :: proc(cmd: ^Command) { } else { resolved, abs_err := filepath.abs(path) if abs_err != nil { - fmt.printf("Error getting absolute path: %v\n", abs_err) + fmt.wprintf(cmd.err, "Error getting absolute path: %v\n", abs_err, flush = false) return } abs_path = resolved @@ -46,10 +46,10 @@ cmd_restore :: proc(cmd: ^Command) { write_err := os.write_entire_file(file.Path, file.contents) if write_err != nil { - fmt.printf("Error writing file: %v\n", write_err) + fmt.wprintf(cmd.err, "Error writing file: %v\n", write_err, flush = false) return } - fmt.printf("Restored %s\n", file.Path) + fmt.wprintf(cmd.out, "Restored %s\n", file.Path, flush = false) } diff --git a/cmd_scan.odin b/cmd_scan.odin index a9e2566..4ef5ed6 100644 --- a/cmd_scan.odin +++ b/cmd_scan.odin @@ -8,8 +8,10 @@ import "core:terminal" cmd_scan :: proc(cmd: ^Command) { feats := check_features() if cant_scan(feats) { - fmt.println( + fmt.wprintln( + cmd.err, "Error: please install fd to use the scan command (https://github.com/sharkdp/fd)", + flush = false, ) return } @@ -22,7 +24,11 @@ cmd_scan :: proc(cmd: ^Command) { search_dirs := search_paths(db.cfg) if len(search_dirs) == 0 { - fmt.println("No search paths configured. Please run `envr init -f` or edit your config.") + fmt.wprintln( + cmd.err, + "No search paths configured. Please run `envr init -f` or edit your config.", + flush = false, + ) return } @@ -31,7 +37,7 @@ cmd_scan :: proc(cmd: ^Command) { for dir in search_dirs { found, scan_ok := scan_path(dir, db.cfg) if !scan_ok { - fmt.printf("Error scanning %s\n", dir) + fmt.wprintf(cmd.err, "Error scanning %s\n", dir, flush = false) continue } for f in found { @@ -47,24 +53,29 @@ cmd_scan :: proc(cmd: ^Command) { files := find_unbacked(all_files[:], db_files[:]) if len(files) == 0 { - fmt.println("No .env files found to add.") + fmt.wprintln(cmd.out, "No .env files found to add.", flush = false) return } if !terminal.is_terminal(os.stdout) { output, marshal_err := json.marshal(files[:]) if marshal_err != nil { - fmt.printf("Error marshaling files to JSON: %v\n", marshal_err) + fmt.wprintf( + cmd.err, + "Error marshaling files to JSON: %v\n", + marshal_err, + flush = false, + ) return } - fmt.println(string(output)) + fmt.wprintln(cmd.out, string(output), flush = false) return } selected, result := multi_select("Select .env files to backup:", files[:]) defer delete(selected) if result == .Cancel { - fmt.println("\x1b[2mCancelled.\x1b[0m") + fmt.wprintln(cmd.out, "\x1b[2mCancelled.\x1b[0m", flush = false) return } @@ -75,20 +86,25 @@ cmd_scan :: proc(cmd: ^Command) { } env_file, ok := new_env_file(files[i]) if !ok { - fmt.printf("Error reading %s\n", files[i]) + fmt.wprintf(cmd.err, "Error reading %s\n", files[i], flush = false) continue } if !db_insert(&db, env_file) { - fmt.printf("Error adding %s\n", files[i]) + fmt.wprintf(cmd.err, "Error adding %s\n", files[i], flush = false) continue } added_count += 1 } if added_count > 0 { - fmt.printf("\x1b[1;32mSuccessfully added %d file(s) to backup.\x1b[0m\n", added_count) + fmt.wprintf( + cmd.out, + "\x1b[1;32mSuccessfully added %d file(s) to backup.\x1b[0m\n", + added_count, + flush = false, + ) } else { - fmt.println("\x1b[2mNo files were added.\x1b[0m") + fmt.wprintln(cmd.out, "\x1b[2mNo files were added.\x1b[0m", flush = false) } } diff --git a/cmd_sync.odin b/cmd_sync.odin index 0d33c92..f388055 100644 --- a/cmd_sync.odin +++ b/cmd_sync.odin @@ -2,7 +2,6 @@ package main import "core:encoding/json" import "core:fmt" -import "core:io" import "core:os" import "core:strings" import "core:terminal" @@ -84,15 +83,14 @@ cmd_sync :: proc(cmd: ^Command) { append(&table_rows, row_slice) } - w := io.to_writer(os.to_writer(os.stdout)) - render_table(w, headers, table_rows[:]) + render_table(cmd.out, headers, table_rows[:]) } else { data, marshal_err := json.marshal(results[:]) if marshal_err != nil { - fmt.printf("Error marshaling JSON: %v\n", marshal_err) + fmt.wprintf(cmd.err, "Error marshaling JSON: %v\n", marshal_err, flush = false) return } - fmt.println(string(data)) + fmt.wprintln(cmd.out, string(data), flush = false) } } diff --git a/cmd_version.odin b/cmd_version.odin index b21b520..cd71211 100644 --- a/cmd_version.odin +++ b/cmd_version.odin @@ -5,6 +5,6 @@ import "core:fmt" VERSION :: #load("version.txt", string) cmd_version :: proc(cmd: ^Command) { - fmt.println(VERSION) + fmt.wprintln(cmd.out, VERSION, flush = false) } diff --git a/db.odin b/db.odin index 8674b05..abe743c 100644 --- a/db.odin +++ b/db.odin @@ -51,7 +51,6 @@ delete_envfile :: proc(f: ^EnvFile) { delete(f.contents) } -// TODO: Leak? make_temp_path :: proc() -> string { ts := time.time_to_unix(time.now()) b: strings.Builder diff --git a/main.odin b/main.odin index 9d5add7..d2709e4 100644 --- a/main.odin +++ b/main.odin @@ -1,10 +1,12 @@ package main +import "core:bufio" import "core:fmt" import "core:os" main :: proc() { - cmd, ok := parse_args(os.args) + 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 { return } @@ -35,10 +37,9 @@ main :: proc() { case "nushell-completion": cmd_nushell_completion(&cmd) case: - fmt.printf("Unknown command: %s\n", cmd.name) - print_usage() + fmt.wprintf(cmd.err, "Unknown command: %s\n", cmd.name) + write_usage(cmd.out) os.exit(1) } } - diff --git a/main.odin.bak b/main.odin.bak deleted file mode 100644 index afdfbcb..0000000 --- a/main.odin.bak +++ /dev/null @@ -1,44 +0,0 @@ -package main - -import "core:fmt" -import "core:os" - -main :: proc() { - cmd, ok := parse_args() - if !ok { - return - } - - switch cmd.name { - case "init": - cmd_init(&cmd) - case "version": - cmd_version(&cmd) - case "deps": - cmd_deps(&cmd) - case "list": - cmd_list(&cmd) - case "backup", "add": - cmd_backup(&cmd) - case "remove": - cmd_remove(&cmd) - case "restore": - cmd_restore(&cmd) - case "edit-config": - cmd_edit_config(&cmd) - case "check": - cmd_check(&cmd) - case "scan": - cmd_scan(&cmd) - case "sync": - cmd_sync(&cmd) - case "nushell-completion": - cmd_nushell_completion(&cmd) - case: - fmt.printf("Unknown command: %s\n", cmd.name) - print_usage() - os.exit(1) - } -} - -