mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
test: commands now accept stdout/stderr fields.
This commit is contained in:
2
TODOS.md
2
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.
|
||||
|
||||
53
cli.odin
53
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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
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, _, _ := test_parse_args([]string{"envr", "list"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "backup", "/project/.env"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
if !ok do return
|
||||
defer delete_command(&cmd)
|
||||
defer delete_command(&cmd)
|
||||
testing.expect(t, ok, "should succeed")
|
||||
|
||||
testing.expect(t, cmd.name == "backup")
|
||||
testing.expect(t, len(cmd.args) == 1)
|
||||
testing.expect(t, cmd.args[0] == "/project/.env")
|
||||
@@ -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, _, _ := test_parse_args([]string{"envr", "sync", "--config", "x.json"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "sync", "-c", "x.json"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "init", "--force"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "version", "-l"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "backup", "a", "b"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "backup", "/project/.env", "--force"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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) {
|
||||
cmd, ok, _, _ := test_parse_args([]string{"envr"})
|
||||
testing.expect(t, !ok, "no args should return false")
|
||||
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, _, _ := test_parse_args([]string{"envr", "backup", "a.env", "--force", "--verbose"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
if !ok do return
|
||||
defer delete_command(&cmd)
|
||||
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)
|
||||
testing.expect(t, len(cmd.args) == 1)
|
||||
@@ -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, _, _ := test_parse_args(
|
||||
testing.expect(t, ok, "should succeed")
|
||||
[]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, _, _ := test_parse_args([]string{"envr", "list", "-c", "/custom/config.json"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
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, _, _ := test_parse_args([]string{"envr", "list"})
|
||||
testing.expect(t, ok, "should succeed")
|
||||
testing.expect(t, ok, "should succeed")
|
||||
if !ok do return
|
||||
defer delete_command(&cmd)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
1
db.odin
1
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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user