From e989b8830321140126d7ca104e3b34fa777e363f Mon Sep 17 00:00:00 2001 From: Spencer Brower Date: Thu, 11 Jun 2026 20:02:12 -0400 Subject: [PATCH] refactor: scaffolded odin project with CLI parser, version command, Go fallback --- .gitignore | 1 + cli.odin | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ cmd_deps.odin | 30 ++++++++++++++++ features.odin | 45 ++++++++++++++++++++++++ flake.lock | 24 ++++++------- flake.nix | 3 ++ main.odin | 56 ++++++++++++++++++++++++++++++ stubs.odin | 39 +++++++++++++++++++++ table.odin | 84 +++++++++++++++++++++++++++++++++++++++++++++ tty.odin | 7 ++++ version.odin | 13 +++++++ 11 files changed, 385 insertions(+), 12 deletions(-) create mode 100644 cli.odin create mode 100644 cmd_deps.odin create mode 100644 features.odin create mode 100644 main.odin create mode 100644 stubs.odin create mode 100644 table.odin create mode 100644 tty.odin create mode 100644 version.odin 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.lock b/flake.lock index 41fed86..9ecfd2a 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1768135262, - "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", + "lastModified": 1778716662, + "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", + "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", "type": "github" }, "original": { @@ -36,11 +36,11 @@ }, "nixpkgs-lib": { "locked": { - "lastModified": 1765674936, - "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", + "lastModified": 1777168982, + "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", "owner": "nix-community", "repo": "nixpkgs.lib", - "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", + "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", "type": "github" }, "original": { @@ -51,11 +51,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1768178648, - "narHash": "sha256-kz/F6mhESPvU1diB7tOM3nLcBfQe7GU7GQCymRlTi/s=", + "lastModified": 1781173989, + "narHash": "sha256-fnzKKPvS+oieI/pTzotA5tkoM47EB1NpaBcgk4R97hE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3fbab70c6e69c87ea2b6e48aa6629da2aa6a23b0", + "rev": "8c91a71d13451abc40eb9dae8910f972f979852f", "type": "github" }, "original": { @@ -80,11 +80,11 @@ ] }, "locked": { - "lastModified": 1768158989, - "narHash": "sha256-67vyT1+xClLldnumAzCTBvU0jLZ1YBcf4vANRWP3+Ak=", + "lastModified": 1780220602, + "narHash": "sha256-eynAfOmbmxJnkp7YewvCEbShNnnYJ9gLLqkzsYtBPeM=", "owner": "numtide", "repo": "treefmt-nix", - "rev": "e96d59dff5c0d7fddb9d113ba108f03c3ef99eca", + "rev": "db947814a175b7ca6ded66e21383d938df01c227", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b42c941..9843f44 100644 --- a/flake.nix +++ b/flake.nix @@ -97,6 +97,9 @@ gotools cobra-cli + unstable.odin + unstable.ols + # Build tools zip diff --git a/main.odin b/main.odin new file mode 100644 index 0000000..14a1361 --- /dev/null +++ b/main.odin @@ -0,0 +1,56 @@ +package main + +import "core:fmt" +import "core:os" + +GO_BINARY :: "./envr-go" + +main :: proc() { + 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) + } +}