mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
refactor: scaffolded odin project with CLI parser, version command, Go fallback
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,4 +7,5 @@ man
|
||||
# build artifacts
|
||||
builds
|
||||
envr
|
||||
envr-go
|
||||
result
|
||||
|
||||
95
cli.odin
Normal file
95
cli.odin
Normal file
@@ -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 <command> [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 <path> Import a .env file into envr")
|
||||
fmt.println(" restore <path> Restore a .env file from the database")
|
||||
fmt.println(" list View your tracked files")
|
||||
fmt.println(" remove <path> 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")
|
||||
}
|
||||
30
cmd_deps.odin
Normal file
30
cmd_deps.odin
Normal file
@@ -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[:])
|
||||
}
|
||||
45
features.odin
Normal file
45
features.odin
Normal file
@@ -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
|
||||
}
|
||||
24
flake.lock
generated
24
flake.lock
generated
@@ -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": {
|
||||
|
||||
@@ -97,6 +97,9 @@
|
||||
gotools
|
||||
cobra-cli
|
||||
|
||||
unstable.odin
|
||||
unstable.ols
|
||||
|
||||
# Build tools
|
||||
zip
|
||||
|
||||
|
||||
56
main.odin
Normal file
56
main.odin
Normal file
@@ -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..<len(os.args) {
|
||||
append(&args, os.args[i])
|
||||
}
|
||||
|
||||
desc := os.Process_Desc{
|
||||
command = args[:],
|
||||
stdin = os.stdin,
|
||||
stdout = os.stdout,
|
||||
stderr = os.stderr,
|
||||
}
|
||||
|
||||
p, err1 := os.process_start(desc)
|
||||
if err1 != nil {
|
||||
fmt.printf("Error: failed to run envr-go: %v\n", err1)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
state, err2 := os.process_wait(p)
|
||||
if err2 != nil {
|
||||
fmt.printf("Error waiting for envr-go: %v\n", err2)
|
||||
os.exit(1)
|
||||
}
|
||||
|
||||
os.exit(int(state.exit_code))
|
||||
}
|
||||
39
stubs.odin
Normal file
39
stubs.odin
Normal file
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
|
||||
cmd_init :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: init")
|
||||
}
|
||||
|
||||
cmd_list :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: list")
|
||||
}
|
||||
|
||||
cmd_scan :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: scan")
|
||||
}
|
||||
|
||||
cmd_sync :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: sync")
|
||||
}
|
||||
|
||||
cmd_backup :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: backup")
|
||||
}
|
||||
|
||||
cmd_restore :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: restore")
|
||||
}
|
||||
|
||||
cmd_remove :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: remove")
|
||||
}
|
||||
|
||||
cmd_check :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: check")
|
||||
}
|
||||
|
||||
cmd_edit_config :: proc(cmd: ^Command) {
|
||||
fmt.println("TODO: edit-config")
|
||||
}
|
||||
84
table.odin
Normal file
84
table.odin
Normal file
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
render_table :: proc(headers: []string, rows: [][]string) {
|
||||
if !is_tty() {
|
||||
render_json_rows(headers, rows)
|
||||
return
|
||||
}
|
||||
|
||||
col_widths := make([dynamic]int, len(headers))
|
||||
for i in 0..<len(headers) {
|
||||
append(&col_widths, len(headers[i]))
|
||||
}
|
||||
for r in rows {
|
||||
for i in 0..<len(r) {
|
||||
if i < len(col_widths) && len(r[i]) > 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..<len(widths) {
|
||||
for _ in 0..<widths[i]+2 {
|
||||
strings.write_string(b, "\u2500")
|
||||
}
|
||||
if i < len(widths)-1 {
|
||||
strings.write_string(b, mid)
|
||||
} else {
|
||||
strings.write_string(b, right)
|
||||
}
|
||||
}
|
||||
fmt.println(strings.to_string(b^))
|
||||
strings.builder_reset(b)
|
||||
}
|
||||
|
||||
hline(&b, "\u250c", "\u252c", "\u2510", col_widths)
|
||||
|
||||
strings.write_string(&b, "\u2502")
|
||||
for i in 0..<len(headers) {
|
||||
fmt.sbprintf(&b, " %-*s \u2502", col_widths[i], headers[i])
|
||||
}
|
||||
fmt.println(strings.to_string(b))
|
||||
strings.builder_reset(&b)
|
||||
|
||||
hline(&b, "\u251c", "\u253c", "\u2524", col_widths)
|
||||
|
||||
for r in rows {
|
||||
strings.write_string(&b, "\u2502")
|
||||
for i in 0..<len(r) {
|
||||
fmt.sbprintf(&b, " %-*s \u2502", col_widths[i], r[i])
|
||||
}
|
||||
fmt.println(strings.to_string(b))
|
||||
strings.builder_reset(&b)
|
||||
}
|
||||
|
||||
hline(&b, "\u2514", "\u2534", "\u2518", col_widths)
|
||||
}
|
||||
|
||||
render_json_rows :: proc(headers: []string, rows: [][]string) {
|
||||
fmt.print("[")
|
||||
for i in 0..<len(rows) {
|
||||
if i > 0 {
|
||||
fmt.print(",")
|
||||
}
|
||||
fmt.print("{")
|
||||
for j in 0..<len(headers) {
|
||||
if j > 0 {
|
||||
fmt.print(",")
|
||||
}
|
||||
fmt.printf("\"%s\":\"%s\"", headers[j], rows[i][j])
|
||||
}
|
||||
fmt.print("}")
|
||||
}
|
||||
fmt.println("]")
|
||||
}
|
||||
7
tty.odin
Normal file
7
tty.odin
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "core:sys/posix"
|
||||
|
||||
is_tty :: proc() -> bool {
|
||||
return bool(posix.isatty(1))
|
||||
}
|
||||
13
version.odin
Normal file
13
version.odin
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user