diff --git a/.gitignore b/.gitignore index c7c932e..5c3e42d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ man # build artifacts builds envr +envr-go result diff --git a/cli.odin b/cli.odin new file mode 100644 index 0000000..576dd90 --- /dev/null +++ b/cli.odin @@ -0,0 +1,95 @@ +package main + +import "core:fmt" +import "core:os" +import "core:strings" + +Command :: struct { + name: string, + args: [dynamic]string, + flags: map[string]string, + bool_set: map[string]bool, +} + +IMPLEMENTED_COMMANDS := []string{ + "version", +} + +parse_args :: proc() -> (cmd: Command, ok: bool) { + args := os.args + if len(args) < 2 { + print_usage() + return Command{}, false + } + + cmd.name = args[1] + cmd.args = make([dynamic]string) + cmd.flags = make(map[string]string) + cmd.bool_set = make(map[string]bool) + + i := 2 + for i < len(args) { + 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] + i += 2 + } else { + cmd.bool_set[key] = true + i += 1 + } + } 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] + i += 2 + } else { + cmd.bool_set[key_slice] = true + i += 1 + } + } else { + append(&cmd.args, arg) + i += 1 + } + } + + return cmd, true +} + +is_implemented :: proc(name: string) -> bool { + for c in IMPLEMENTED_COMMANDS { + if c == name { + return true + } + } + return false +} + +has_flag :: proc(cmd: ^Command, name: string) -> bool { + _, ok := cmd.flags[name] + if ok { + return true + } + _, ok2 := cmd.bool_set[name] + return ok2 +} + +print_usage :: proc() { + fmt.println("envr - Manage your .env files.") + fmt.println("") + fmt.println("Usage: envr [args]") + fmt.println("") + fmt.println("Commands:") + fmt.println(" init Set up envr") + fmt.println(" scan Find and select .env files for backup") + fmt.println(" sync Update or restore your env backups") + fmt.println(" backup Import a .env file into envr") + fmt.println(" restore Restore a .env file from the database") + fmt.println(" list View your tracked files") + fmt.println(" remove Remove a .env file from your database") + fmt.println(" check [path] Check if files are backed up") + fmt.println(" deps Check for missing binaries") + fmt.println(" version Show envr's version") + fmt.println(" edit-config Edit your config with your default editor") +} diff --git a/cmd_deps.odin b/cmd_deps.odin new file mode 100644 index 0000000..b73afe8 --- /dev/null +++ b/cmd_deps.odin @@ -0,0 +1,30 @@ +package main + +import "core:fmt" + +cmd_deps :: proc(cmd: ^Command) { + feats := check_features() + + headers := []string{"Feature", "Status"} + rows: [dynamic][]string + + if .Git in feats { + append(&rows, []string{"Git", "\u2713 Available"}) + } else { + append(&rows, []string{"Git", "\u2717 Missing"}) + } + + if .Fd in feats { + append(&rows, []string{"fd", "\u2713 Available"}) + } else { + append(&rows, []string{"fd", "\u2717 Missing"}) + } + + if .Age in feats { + append(&rows, []string{"age", "\u2713 Available"}) + } else { + append(&rows, []string{"age", "\u2717 Missing"}) + } + + render_table(headers, rows[:]) +} diff --git a/features.odin b/features.odin new file mode 100644 index 0000000..db2a3d0 --- /dev/null +++ b/features.odin @@ -0,0 +1,45 @@ +package main + +import "core:os" +import "core:strings" + +Feature :: enum { + Git, + Fd, + Age, +} + +AvailableFeatures :: bit_set[Feature] + +check_features :: proc() -> AvailableFeatures { + feats: AvailableFeatures + + if find_binary("git") != "" { + feats += {.Git} + } + if find_binary("fd") != "" { + feats += {.Fd} + } + if find_binary("age") != "" { + feats += {.Age} + } + + return feats +} + +find_binary :: proc(name: string) -> string { + path_env := os.get_env("PATH", context.allocator) + paths := strings.split(path_env, ":") + for p in paths { + candidate := strings.join({strings.trim_right(p, "/"), name}, "/") + _, err := os.stat(candidate, context.allocator) + if err == nil { + return candidate + } + } + return "" +} + +has_feature :: proc(feats: AvailableFeatures, f: Feature) -> bool { + return f in feats +} diff --git a/flake.nix b/flake.nix index 8cd3fa1..9843f44 100644 --- a/flake.nix +++ b/flake.nix @@ -97,8 +97,8 @@ gotools cobra-cli - odin - ols + unstable.odin + unstable.ols # Build tools zip diff --git a/main.odin b/main.odin index 77c321f..14a1361 100644 --- a/main.odin +++ b/main.odin @@ -1,8 +1,56 @@ package main import "core:fmt" +import "core:os" + +GO_BINARY :: "./envr-go" main :: proc() { - fmt.println("Hello!") + cmd, ok := parse_args() + if !ok { + return + } + + if !is_implemented(cmd.name) { + fallback_to_go() + return + } + + switch cmd.name { + case "version": + cmd_version(&cmd) + case: + fmt.printf("Unknown command: %s\n", cmd.name) + print_usage() + os.exit(1) + } } +fallback_to_go :: proc() { + args := make([dynamic]string) + append(&args, "./envr-go") + for i in 1.. col_widths[i] { + col_widths[i] = len(r[i]) + } + } + } + + b: strings.Builder + strings.builder_init(&b) + defer strings.builder_destroy(&b) + defer delete(col_widths) + + hline :: proc(b: ^strings.Builder, left, mid, right: string, widths: [dynamic]int) { + strings.write_string(b, left) + for i in 0.. 0 { + fmt.print(",") + } + fmt.print("{") + for j in 0.. 0 { + fmt.print(",") + } + fmt.printf("\"%s\":\"%s\"", headers[j], rows[i][j]) + } + fmt.print("}") + } + fmt.println("]") +} diff --git a/tty.odin b/tty.odin new file mode 100644 index 0000000..61282a5 --- /dev/null +++ b/tty.odin @@ -0,0 +1,7 @@ +package main + +import "core:sys/posix" + +is_tty :: proc() -> bool { + return bool(posix.isatty(1)) +} diff --git a/version.odin b/version.odin new file mode 100644 index 0000000..2045e99 --- /dev/null +++ b/version.odin @@ -0,0 +1,13 @@ +package main + +import "core:fmt" + +VERSION :: "0.2.0" + +cmd_version :: proc(cmd: ^Command) { + if has_flag(cmd, "long") || has_flag(cmd, "l") { + fmt.printf("envr version %s\n", VERSION) + } else { + fmt.println(VERSION) + } +}