mirror of
https://github.com/sbrow/envr.git
synced 2026-08-26 09:53:32 -04:00
feat: Improved nushell wrapper output.
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
|
||||
6. Generate md and man pages again.
|
||||
|
||||
7. Shell completion
|
||||
7. Bash Shell completion
|
||||
|
||||
8. Add tests for untested commands.
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ Command :: struct {
|
||||
Flags :: struct {
|
||||
help: bool `args:"short=h" usage:"show this documentation"`,
|
||||
config_file: string `args:"name=config-file,short=c" usage:"config file" default:"~/.envr/config.json"`,
|
||||
output: Output_Format `args:"short=o" usage:"the format of output data" default:"table"`,
|
||||
color: Color_Mode `usage:"Whether or not to colorize output" default:"auto"`,
|
||||
output: Output_Format `args:"short=o" usage:"the format of output data" default:"table" completion:"output"`,
|
||||
color: Color_Mode `usage:"Whether or not to colorize output" default:"auto" completion:"color"`,
|
||||
force: bool `args:"short=f" usage:"Overwrite existing config"`,
|
||||
}
|
||||
|
||||
@@ -47,6 +47,12 @@ Color_Mode :: enum {
|
||||
Never,
|
||||
}
|
||||
|
||||
Positional_Arg :: struct {
|
||||
name: string,
|
||||
completion: string,
|
||||
optional: bool,
|
||||
}
|
||||
|
||||
CommandInfo :: struct {
|
||||
name: string,
|
||||
usage: string,
|
||||
@@ -55,6 +61,7 @@ CommandInfo :: struct {
|
||||
aliases: []string,
|
||||
// Flags supported by the command
|
||||
flags: bit_set[Flag_Type],
|
||||
args: []Positional_Arg,
|
||||
}
|
||||
|
||||
GLOBAL_FLAGS: bit_set[Flag_Type] = {.Help, .Config_File, .Color}
|
||||
@@ -70,10 +77,27 @@ encrypt your databse. **Make 100% sure** that you have **a remote copy** of this
|
||||
key somewhere, otherwise your data could be lost forever.`,
|
||||
{},
|
||||
GLOBAL_FLAGS + {.Force},
|
||||
{},
|
||||
},
|
||||
{"scan", "envr scan", "Find and select .env files for backup", "", {}, GLOBAL_FLAGS, {}},
|
||||
{
|
||||
"sync",
|
||||
"envr sync",
|
||||
"Update or restore your env backups",
|
||||
"",
|
||||
{},
|
||||
GLOBAL_FLAGS + {.Output},
|
||||
{},
|
||||
},
|
||||
{
|
||||
"backup",
|
||||
"envr backup <path>",
|
||||
"Import a .env file into envr",
|
||||
"",
|
||||
{"add"},
|
||||
GLOBAL_FLAGS,
|
||||
{{name = "path", completion = "untracked-paths"}},
|
||||
},
|
||||
{"scan", "envr scan", "Find and select .env files for backup", "", {}, GLOBAL_FLAGS},
|
||||
{"sync", "envr sync", "Update or restore your env backups", "", {}, GLOBAL_FLAGS + {.Output}},
|
||||
{"backup", "envr backup <path>", "Import a .env file into envr", "", {"add"}, GLOBAL_FLAGS},
|
||||
{
|
||||
"restore",
|
||||
"envr restore <path>",
|
||||
@@ -81,8 +105,9 @@ key somewhere, otherwise your data could be lost forever.`,
|
||||
"",
|
||||
{},
|
||||
GLOBAL_FLAGS,
|
||||
{{name = "path", completion = "tracked-paths"}},
|
||||
},
|
||||
{"list", "envr list", "View your tracked files", "", {}, GLOBAL_FLAGS + {.Output}},
|
||||
{"list", "envr list", "View your tracked files", "", {}, GLOBAL_FLAGS + {.Output}, {}},
|
||||
{
|
||||
"remove",
|
||||
"envr remove <path>",
|
||||
@@ -90,9 +115,18 @@ key somewhere, otherwise your data could be lost forever.`,
|
||||
"",
|
||||
{},
|
||||
GLOBAL_FLAGS,
|
||||
{{name = "path", completion = "tracked-paths"}},
|
||||
},
|
||||
{"check", "envr check [path]", "Check if files are backed up", "", {}, GLOBAL_FLAGS},
|
||||
{"version", "envr version", "Show envr's version", "", {}, {.Help}},
|
||||
{
|
||||
"check",
|
||||
"envr check [path]",
|
||||
"Check if files are backed up",
|
||||
"",
|
||||
{},
|
||||
GLOBAL_FLAGS,
|
||||
{{name = "path", optional = true}},
|
||||
},
|
||||
{"version", "envr version", "Show envr's version", "", {}, {.Help}, {}},
|
||||
{
|
||||
"edit-config",
|
||||
"envr edit-config",
|
||||
@@ -100,6 +134,7 @@ key somewhere, otherwise your data could be lost forever.`,
|
||||
"",
|
||||
{},
|
||||
GLOBAL_FLAGS,
|
||||
{},
|
||||
},
|
||||
{
|
||||
"nushell-completion",
|
||||
@@ -108,6 +143,7 @@ key somewhere, otherwise your data could be lost forever.`,
|
||||
"",
|
||||
{},
|
||||
{.Help},
|
||||
{},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -213,13 +249,23 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool {
|
||||
return true
|
||||
}
|
||||
|
||||
flag_field_info :: proc(
|
||||
ft: Flag_Type,
|
||||
) -> (
|
||||
names: string,
|
||||
value_hint: string,
|
||||
description: string,
|
||||
) {
|
||||
Flag_Kind :: enum {
|
||||
Bool,
|
||||
String,
|
||||
Enum,
|
||||
}
|
||||
|
||||
Flag_Field :: struct {
|
||||
long_name: string,
|
||||
short_name: string,
|
||||
kind: Flag_Kind,
|
||||
usage: string,
|
||||
default_val: string,
|
||||
enum_values: string,
|
||||
completion: string,
|
||||
}
|
||||
|
||||
flag_field :: proc(ft: Flag_Type) -> Flag_Field {
|
||||
field := reflect.struct_field_at(Flags, int(ft))
|
||||
|
||||
args_tag := reflect.struct_tag_get(field.tag, "args")
|
||||
@@ -229,37 +275,74 @@ flag_field_info :: proc(
|
||||
}
|
||||
|
||||
short, has_short := get_subtag(args_tag, "short")
|
||||
if has_short {
|
||||
names = fmt.tprintf("-%s, --%s", short, long_name)
|
||||
} else {
|
||||
names = fmt.tprintf("--%s", long_name)
|
||||
}
|
||||
|
||||
base_ti := runtime.type_info_base(field.type)
|
||||
kind: Flag_Kind
|
||||
enum_values: string
|
||||
|
||||
if _, is_bool := base_ti.variant.(runtime.Type_Info_Boolean); is_bool {
|
||||
value_hint = ""
|
||||
kind = .Bool
|
||||
} else if _, is_string := base_ti.variant.(runtime.Type_Info_String); is_string {
|
||||
value_hint = " <value>"
|
||||
kind = .String
|
||||
} else if enum_ti, is_enum := base_ti.variant.(runtime.Type_Info_Enum); is_enum {
|
||||
kind = .Enum
|
||||
parts := make([dynamic]string, 0, len(enum_ti.names), context.temp_allocator)
|
||||
for name in enum_ti.names {
|
||||
lower := strings.to_lower(name, context.temp_allocator)
|
||||
append(&parts, fmt.tprintf("'%s'", lower))
|
||||
}
|
||||
value_hint = fmt.tprintf(" %s", strings.join(parts[:], "|", context.temp_allocator))
|
||||
enum_values = strings.join(parts[:], "|", context.temp_allocator)
|
||||
delete(parts)
|
||||
}
|
||||
|
||||
usage := reflect.struct_tag_get(field.tag, "usage")
|
||||
default_val := reflect.struct_tag_get(field.tag, "default")
|
||||
completion := reflect.struct_tag_get(field.tag, "completion")
|
||||
|
||||
description = usage
|
||||
if len(default_val) > 0 {
|
||||
if _, is_string := base_ti.variant.(runtime.Type_Info_String); is_string {
|
||||
description = fmt.tprintf(`%s (default "%s")`, usage, default_val)
|
||||
} else if _, is_enum := base_ti.variant.(runtime.Type_Info_Enum); is_enum {
|
||||
description = fmt.tprintf("%s (default '%s')", usage, default_val)
|
||||
return {
|
||||
long_name = long_name,
|
||||
short_name = has_short ? short : "",
|
||||
kind = kind,
|
||||
usage = usage,
|
||||
default_val = default_val,
|
||||
enum_values = enum_values,
|
||||
completion = completion,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,99 @@
|
||||
package main
|
||||
|
||||
import "core:fmt"
|
||||
import "core:strings"
|
||||
|
||||
COMPLETION_SCRIPT: string : string(#load("mod.nu"))
|
||||
nushell_header :: `def tracked-paths [] {
|
||||
(
|
||||
^envr list
|
||||
| from json
|
||||
| each {
|
||||
[$in.directory $in.path] | path join
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
def untracked-paths [] {
|
||||
(
|
||||
^envr scan
|
||||
| from json
|
||||
)
|
||||
}
|
||||
|
||||
def color [] {
|
||||
['auto' 'always' 'never']
|
||||
}
|
||||
|
||||
def output [] {
|
||||
['auto' 'table' 'json']
|
||||
}
|
||||
|
||||
`
|
||||
|
||||
cmd_nushell_completion :: proc(cmd: ^Command) {
|
||||
fmt.wprint(cmd.out, COMPLETION_SCRIPT, flush = false)
|
||||
fmt.wprint(cmd.out, generate_nushell_completion(), flush = true)
|
||||
}
|
||||
|
||||
generate_nushell_completion :: proc() -> string {
|
||||
sb: strings.Builder
|
||||
strings.builder_init(&sb, context.temp_allocator)
|
||||
defer strings.builder_destroy(&sb)
|
||||
|
||||
fmt.sbprint(&sb, nushell_header)
|
||||
|
||||
for c in COMMANDS {
|
||||
fmt.sbprintf(&sb, "# %s\n", c.short)
|
||||
fmt.sbprintf(&sb, "export extern \"envr %s\" [\n", c.name)
|
||||
for ft in Flag_Type {
|
||||
if ft not_in c.flags do continue
|
||||
f := flag_field(ft)
|
||||
fmt.sbprintf(&sb, " %s\n", nushell_flag_line(f))
|
||||
}
|
||||
for arg in c.args {
|
||||
fmt.sbprintf(&sb, " %s\n", nushell_positional_line(arg))
|
||||
}
|
||||
fmt.sbprintf(&sb, "]\n")
|
||||
|
||||
for a in c.aliases {
|
||||
fmt.sbprintf(&sb, "\nexport alias \"envr %s\" = envr %s\n", a, c.name)
|
||||
}
|
||||
fmt.sbprintf(&sb, "\n")
|
||||
}
|
||||
|
||||
return strings.to_string(sb)
|
||||
}
|
||||
|
||||
nushell_flag_line :: proc(f: Flag_Field) -> string {
|
||||
line: string
|
||||
if len(f.short_name) > 0 {
|
||||
line = fmt.tprintf("--%s(-%s)", f.long_name, f.short_name)
|
||||
} else {
|
||||
line = fmt.tprintf("--%s", f.long_name)
|
||||
}
|
||||
|
||||
switch f.kind {
|
||||
case .Bool:
|
||||
case .String:
|
||||
line = fmt.tprintf("%s: path", line)
|
||||
case .Enum:
|
||||
if len(f.completion) > 0 {
|
||||
line = fmt.tprintf("%s: string@%s", line, f.completion)
|
||||
} else {
|
||||
line = fmt.tprintf("%s: string", line)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.tprintf("%s # %s", line, f.usage)
|
||||
}
|
||||
|
||||
nushell_positional_line :: proc(arg: Positional_Arg) -> string {
|
||||
name := arg.name
|
||||
if arg.optional {
|
||||
name = fmt.tprintf("%s?", name)
|
||||
}
|
||||
if len(arg.completion) > 0 {
|
||||
return fmt.tprintf("%s: path@%s", name, arg.completion)
|
||||
}
|
||||
return fmt.tprintf("%s: path", name)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,31 +7,72 @@ import "core:testing"
|
||||
|
||||
@(test)
|
||||
test_nushell_completion_nonempty :: proc(t: ^testing.T) {
|
||||
testing.expect(t, len(COMPLETION_SCRIPT) > 0, "completion script should not be empty")
|
||||
script := generate_nushell_completion()
|
||||
testing.expect(t, len(script) > 0, "completion script should not be empty")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_nushell_completion_contains_externs :: proc(t: ^testing.T) {
|
||||
test_nushell_completion_contains_commands :: proc(t: ^testing.T) {
|
||||
script := generate_nushell_completion()
|
||||
expected := []string{
|
||||
"tracked-paths",
|
||||
"untracked-paths",
|
||||
"envr backup",
|
||||
"envr check",
|
||||
"envr edit-config",
|
||||
"envr help",
|
||||
"envr init",
|
||||
"envr list",
|
||||
"envr remove",
|
||||
"envr restore",
|
||||
"envr scan",
|
||||
"envr sync",
|
||||
"envr version",
|
||||
"envr nushell-completion",
|
||||
}
|
||||
for ext in expected {
|
||||
testing.expect(
|
||||
t,
|
||||
strings.contains(COMPLETION_SCRIPT, ext),
|
||||
strings.contains(script, ext),
|
||||
fmt.tprintf("expected script to contain %q", ext),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_nushell_completion_contains_flags :: proc(t: ^testing.T) {
|
||||
script := generate_nushell_completion()
|
||||
expected_flags := []string{
|
||||
"--help(-h)",
|
||||
"--config-file(-c)",
|
||||
"--color",
|
||||
"--force(-f)",
|
||||
"--output(-o)",
|
||||
}
|
||||
for flag in expected_flags {
|
||||
testing.expect(
|
||||
t,
|
||||
strings.contains(script, flag),
|
||||
fmt.tprintf("expected script to contain %q", flag),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_nushell_completion_contains_aliases :: proc(t: ^testing.T) {
|
||||
script := generate_nushell_completion()
|
||||
testing.expect(
|
||||
t,
|
||||
strings.contains(script, "envr add"),
|
||||
"expected script to contain 'envr add' alias",
|
||||
)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_nushell_completion_no_help_command :: proc(t: ^testing.T) {
|
||||
script := generate_nushell_completion()
|
||||
testing.expect(
|
||||
t,
|
||||
!strings.contains(script, "envr help"),
|
||||
"script should not contain 'envr help' (not a real command)",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# envr command extern definitions for Nushell
|
||||
# A tool for managing environment files and backups
|
||||
|
||||
export def tracked-paths [] {
|
||||
(
|
||||
^envr list
|
||||
| from json
|
||||
| each {
|
||||
[$in.directory $in.path] | path join
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export def untracked-paths [] {
|
||||
(
|
||||
^envr scan
|
||||
| from json
|
||||
)
|
||||
}
|
||||
|
||||
export extern envr [
|
||||
...args: any
|
||||
--help(-h) # Show help information
|
||||
]
|
||||
|
||||
export extern "envr backup" [
|
||||
--help(-h) # Show help for backup command
|
||||
path: path@untracked-paths # Path to .env file to backup
|
||||
]
|
||||
|
||||
export extern "envr check" [
|
||||
--help(-h) # Show help for check command
|
||||
]
|
||||
|
||||
export extern "envr edit-config" [
|
||||
--help(-h) # Show help for edit-config command
|
||||
]
|
||||
|
||||
export extern "envr help" [
|
||||
command?: string # Show help for specific command
|
||||
]
|
||||
|
||||
export extern "envr init" [
|
||||
--help(-h) # Show help for init command
|
||||
]
|
||||
|
||||
export extern "envr list" [
|
||||
--help(-h) # Show help for list command
|
||||
]
|
||||
|
||||
export extern "envr remove" [
|
||||
--help(-h) # Show help for remove command
|
||||
path: path@tracked-paths
|
||||
]
|
||||
|
||||
export extern "envr restore" [
|
||||
--help(-h) # Show help for restore command
|
||||
path: path@tracked-paths
|
||||
]
|
||||
|
||||
export extern "envr scan" [
|
||||
--help(-h) # Show help for scan command
|
||||
]
|
||||
|
||||
export extern "envr sync" [
|
||||
--help(-h) # Show help for sync command
|
||||
]
|
||||
|
||||
export extern "envr nushell-completion" [
|
||||
--help(-h) # Show help for nushell-completion command
|
||||
]
|
||||
Reference in New Issue
Block a user