1 Commits

Author SHA1 Message Date
Spencer Brower cf62869f3b chore(prerelease): release 1.0.0 2026-07-27 13:15:35 -04:00
7 changed files with 138 additions and 167 deletions
-6
View File
@@ -12,12 +12,6 @@
7. Add purge command? 7. Add purge command?
8. Add docs for "building from source"
9. Add AI Usage Disclaimer to README.md
10. Check up on GitHub "SEO". i.e. Description, tags, etc.
## Double-check AI output ## Double-check AI output
+63 -82
View File
@@ -97,18 +97,14 @@ key somewhere, otherwise your data could be lost forever.`,
short = "Import a .env file into envr", short = "Import a .env file into envr",
aliases = {"add"}, aliases = {"add"},
flags = GLOBAL_FLAGS, flags = GLOBAL_FLAGS,
args = { args = {{name = "path", completion = "untracked-paths", desc = "Path to .env file to backup"}},
{name = "path", completion = "untracked-paths", desc = "Path to .env file to backup"},
},
}, },
{ {
name = "restore", name = "restore",
usage = "envr restore <path>", usage = "envr restore <path>",
short = "Restore a .env file from the database", short = "Restore a .env file from the database",
flags = GLOBAL_FLAGS, flags = GLOBAL_FLAGS,
args = { args = {{name = "path", completion = "tracked-paths", desc = "Path to .env file to restore"}},
{name = "path", completion = "tracked-paths", desc = "Path to .env file to restore"},
},
}, },
{ {
name = "list", name = "list",
@@ -121,22 +117,14 @@ key somewhere, otherwise your data could be lost forever.`,
usage = "envr remove <path>", usage = "envr remove <path>",
short = "Remove a .env file from your database", short = "Remove a .env file from your database",
flags = GLOBAL_FLAGS, flags = GLOBAL_FLAGS,
args = { args = {{name = "path", completion = "tracked-paths", desc = "Path to .env file to remove"}},
{name = "path", completion = "tracked-paths", desc = "Path to .env file to remove"},
},
}, },
{ {
name = "check", name = "check",
usage = "envr check [path]", usage = "envr check [path]",
short = "Check if files are backed up", short = "Check if files are backed up",
flags = GLOBAL_FLAGS, flags = GLOBAL_FLAGS,
args = { args = {{name = "path", optional = true, desc = "Path to check (defaults to current directory)"}},
{
name = "path",
optional = true,
desc = "Path to check (defaults to current directory)",
},
},
}, },
{name = "version", usage = "envr version", short = "Show envr's version", flags = {.Help}}, {name = "version", usage = "envr version", short = "Show envr's version", flags = {.Help}},
{ {
@@ -154,14 +142,7 @@ key somewhere, otherwise your data could be lost forever.`,
nushell nushell
bash`, bash`,
flags = {.Help}, flags = {.Help},
args = { args = {{name = "shell", ntype = "string", completion = "shells", desc = "Shell to generate completions for"}},
{
name = "shell",
ntype = "string",
completion = "shells",
desc = "Shell to generate completions for",
},
},
}, },
{ {
name = "uninstall", name = "uninstall",
@@ -290,64 +271,6 @@ Flag_Field :: struct {
completion: string, completion: string,
} }
write_flags_table :: proc(tbl: ^table.Table, flags: bit_set[Flag_Type]) {
table.caption(tbl, "Flags:")
for ft in Flag_Type {
if ft not_in flags do continue
names, hint, desc := flag_field_info(ft)
if len(hint) > 0 {
display := table.format(
tbl,
"%s%s",
colorize(.Flag, names, tbl.format_allocator),
hint,
)
table.row(tbl, display, desc)
} else {
table.row(tbl, colorize(.Flag, names, tbl.format_allocator), desc)
}
}
}
flag_field_info :: proc(
ft: Flag_Type,
) -> (
names: string,
value_hint: string,
description: string,
) {
f := flag_field(ft)
if len(f.short_name) > 0 {
names = fmt.tprintf("-%s, --%s", f.short_name, f.long_name)
} else {
names = fmt.tprintf("--%s", f.long_name)
}
switch f.kind {
case .Bool:
value_hint = ""
case .String:
value_hint = " <value>"
case .Enum:
value_hint = fmt.tprintf(" %s", f.enum_values)
}
description = f.usage
if len(f.default_val) > 0 {
switch f.kind {
case .Bool:
// do nothing
case .String:
description = fmt.tprintf(`%s (default "%s")`, f.usage, f.default_val)
case .Enum:
description = fmt.tprintf("%s (default '%s')", f.usage, f.default_val)
}
}
return
}
flag_field :: proc(ft: Flag_Type) -> Flag_Field { flag_field :: proc(ft: Flag_Type) -> Flag_Field {
field := reflect.struct_field_at(Flags, int(ft)) field := reflect.struct_field_at(Flags, int(ft))
@@ -393,6 +316,64 @@ flag_field :: proc(ft: Flag_Type) -> Flag_Field {
} }
} }
flag_field_info :: proc(
ft: Flag_Type,
) -> (
names: string,
value_hint: string,
description: string,
) {
f := flag_field(ft)
if len(f.short_name) > 0 {
names = fmt.tprintf("-%s, --%s", f.short_name, f.long_name)
} else {
names = fmt.tprintf("--%s", f.long_name)
}
switch f.kind {
case .Bool:
value_hint = ""
case .String:
value_hint = " <value>"
case .Enum:
value_hint = fmt.tprintf(" %s", f.enum_values)
}
description = f.usage
if len(f.default_val) > 0 {
switch f.kind {
case .Bool:
// do nothing
case .String:
description = fmt.tprintf(`%s (default "%s")`, f.usage, f.default_val)
case .Enum:
description = fmt.tprintf("%s (default '%s')", f.usage, f.default_val)
}
}
return
}
write_flags_table :: proc(tbl: ^table.Table, flags: bit_set[Flag_Type]) {
table.caption(tbl, "Flags:")
for ft in Flag_Type {
if ft not_in flags do continue
names, hint, desc := flag_field_info(ft)
if len(hint) > 0 {
display := table.format(
tbl,
"%s%s",
colorize(.Flag, names, tbl.format_allocator),
hint,
)
table.row(tbl, display, desc)
} else {
table.row(tbl, colorize(.Flag, names, tbl.format_allocator), desc)
}
}
}
find_command :: proc(name: string) -> (CommandInfo, bool) { find_command :: proc(name: string) -> (CommandInfo, bool) {
for c in COMMANDS { for c in COMMANDS {
if c.name == name { if c.name == name {
+1
View File
@@ -1,6 +1,7 @@
package main package main
import "core:fmt" import "core:fmt"
import "core:terminal/ansi"
cmd_init :: proc(cmd: ^Command) { cmd_init :: proc(cmd: ^Command) {
force := cmd.flags.force force := cmd.flags.force
+5 -6
View File
@@ -221,8 +221,7 @@ search_paths :: proc(cfg: Config, allocator := context.allocator) -> [dynamic]st
include = expanded include = expanded
} else { } else {
// TODO: show errors? // TODO: show errors?
resolved: string resolved, err := filepath.abs(expanded, allocator)
resolved, err = filepath.abs(expanded, allocator)
if err == nil { if err == nil {
include = resolved include = resolved
} }
@@ -232,6 +231,10 @@ search_paths :: proc(cfg: Config, allocator := context.allocator) -> [dynamic]st
return result return result
} }
envr_dir :: proc(config_path: string) -> string {
return filepath.dir(config_path)
}
// User is responsible for freeing the path // User is responsible for freeing the path
data_path :: proc( data_path :: proc(
config_path: string, config_path: string,
@@ -243,7 +246,3 @@ data_path :: proc(
return filepath.join([]string{envr_dir(config_path), "data.envr"}, allocator) return filepath.join([]string{envr_dir(config_path), "data.envr"}, allocator)
} }
envr_dir :: proc(config_path: string) -> string {
return filepath.dir(config_path)
}
+1
View File
@@ -1,5 +1,6 @@
package main package main
import "base:runtime"
import "core:crypto/hash" import "core:crypto/hash"
import "core:encoding/hex" import "core:encoding/hex"
import "core:encoding/ini" import "core:encoding/ini"
+53 -58
View File
@@ -4,12 +4,64 @@ import "base:runtime"
import "core:reflect" import "core:reflect"
import "core:strings" import "core:strings"
get_subtag :: proc(tag: string, id: string) -> (value: string, ok: bool) {
parts := strings.split(tag, ",", context.temp_allocator)
for part in parts {
trimmed := strings.trim_space(part)
if strings.has_prefix(trimmed, id) && len(trimmed) > len(id) && trimmed[len(id)] == '=' {
return trimmed[len(id) + 1:], true
}
if trimmed == id {
return "", true
}
}
return "", false
}
is_bool_type :: proc(field: reflect.Struct_Field) -> bool {
base_ti := runtime.type_info_base(field.type)
_, is_bool := base_ti.variant.(runtime.Type_Info_Boolean)
return is_bool
}
set_field :: proc(model: rawptr, field: reflect.Struct_Field, value: string) -> bool {
ptr := rawptr(uintptr(model) + field.offset)
base_ti := runtime.type_info_base(field.type)
if _, is_bool := base_ti.variant.(runtime.Type_Info_Boolean); is_bool {
(cast(^bool)ptr)^ = true
return true
}
if _, is_string := base_ti.variant.(runtime.Type_Info_String); is_string {
(cast(^string)ptr)^ = value
return true
}
if enum_ti, is_enum := base_ti.variant.(runtime.Type_Info_Enum); is_enum {
for name, i in enum_ti.names {
if strings.equal_fold(value, name) {
v := enum_ti.values[i]
switch base_ti.size {
case 1: (cast(^u8)ptr)^ = cast(u8)v
case 2: (cast(^u16)ptr)^ = cast(u16)v
case 4: (cast(^u32)ptr)^ = cast(u32)v
case 8: (cast(^u64)ptr)^ = cast(u64)v
}
return true
}
}
}
return false
}
parse_flags :: proc(model: ^$T, args: []string) -> (overflow: []string) { parse_flags :: proc(model: ^$T, args: []string) -> (overflow: []string) {
field_count := reflect.struct_field_count(T) field_count := reflect.struct_field_count(T)
long_map := make(map[string]reflect.Struct_Field, field_count, context.temp_allocator) long_map := make(map[string]reflect.Struct_Field, field_count, context.temp_allocator)
short_map := make(map[string]reflect.Struct_Field, field_count, context.temp_allocator) short_map := make(map[string]reflect.Struct_Field, field_count, context.temp_allocator)
for i in 0 ..< field_count { for i in 0..<field_count {
field := reflect.struct_field_at(T, i) field := reflect.struct_field_at(T, i)
name, _ := strings.replace(field.name, "_", "-", -1, context.temp_allocator) name, _ := strings.replace(field.name, "_", "-", -1, context.temp_allocator)
@@ -80,60 +132,3 @@ parse_flags :: proc(model: ^$T, args: []string) -> (overflow: []string) {
return overflow_dyn[:] return overflow_dyn[:]
} }
get_subtag :: proc(tag: string, id: string) -> (value: string, ok: bool) {
parts := strings.split(tag, ",", context.temp_allocator)
for part in parts {
trimmed := strings.trim_space(part)
if strings.has_prefix(trimmed, id) && len(trimmed) > len(id) && trimmed[len(id)] == '=' {
return trimmed[len(id) + 1:], true
}
if trimmed == id {
return "", true
}
}
return "", false
}
is_bool_type :: proc(field: reflect.Struct_Field) -> bool {
base_ti := runtime.type_info_base(field.type)
_, is_bool := base_ti.variant.(runtime.Type_Info_Boolean)
return is_bool
}
set_field :: proc(model: rawptr, field: reflect.Struct_Field, value: string) -> bool {
ptr := rawptr(uintptr(model) + field.offset)
base_ti := runtime.type_info_base(field.type)
if _, is_bool := base_ti.variant.(runtime.Type_Info_Boolean); is_bool {
(cast(^bool)ptr)^ = true
return true
}
if _, is_string := base_ti.variant.(runtime.Type_Info_String); is_string {
(cast(^string)ptr)^ = value
return true
}
if enum_ti, is_enum := base_ti.variant.(runtime.Type_Info_Enum); is_enum {
for name, i in enum_ti.names {
if strings.equal_fold(value, name) {
v := enum_ti.values[i]
switch base_ti.size {
case 1:
(cast(^u8)ptr)^ = cast(u8)v
case 2:
(cast(^u16)ptr)^ = cast(u16)v
case 4:
(cast(^u32)ptr)^ = cast(u32)v
case 8:
(cast(^u64)ptr)^ = cast(u64)v
}
return true
}
}
}
return false
}
+15 -15
View File
@@ -1,5 +1,6 @@
package main package main
import "core:fmt"
import "core:io" import "core:io"
import "core:text/table" import "core:text/table"
@@ -17,6 +18,20 @@ decorations := table.Decorations {
"─", "─",
} }
ansi_aware_width :: proc(str: string) -> int #no_bounds_check {
width := 0
for i := 0; i < len(str); {
if i + 1 < len(str) && str[i] == 0x1b && str[i + 1] == '[' {
i += 2
for i < len(str) {c := str[i]; i += 1; if c >= 0x40 && c <= 0x7E {break}}
} else {
width += 1
i += 1
}
}
return width
}
write_borderless_table :: proc(w: io.Writer, t: ^table.Table) { write_borderless_table :: proc(w: io.Writer, t: ^table.Table) {
table.build(t, ansi_aware_width) table.build(t, ansi_aware_width)
@@ -49,21 +64,6 @@ write_borderless_table :: proc(w: io.Writer, t: ^table.Table) {
write_table_separator(w, t) write_table_separator(w, t)
} }
ansi_aware_width :: proc(str: string) -> int #no_bounds_check {
width := 0
for i := 0; i < len(str); {
if i + 1 < len(str) && str[i] == 0x1b && str[i + 1] == '[' {
i += 2
for i < len(str) {c := str[i]; i += 1; if c >= 0x40 && c <= 0x7E {break}}
} else {
width += 1
i += 1
}
}
return width
}
table_reset :: proc(t: ^table.Table) { table_reset :: proc(t: ^table.Table) {
clear(&t.cells) clear(&t.cells)
clear(&t.colw) clear(&t.colw)