mirror of
https://github.com/sbrow/envr.git
synced 2026-08-26 09:53:32 -04:00
fix: Flags in help text are now customized per command.
This commit is contained in:
@@ -20,15 +20,11 @@
|
|||||||
|
|
||||||
10. Pass allocator to findr?
|
10. Pass allocator to findr?
|
||||||
|
|
||||||
11. Smarter flag parsing?
|
11. Consider getting rid of color global.
|
||||||
|
|
||||||
12. Rewrite `write_command_help` to use text/tables
|
12. `write_flags_table` should never return false.
|
||||||
|
|
||||||
13. Consider getting rid of color global.
|
13. Add a text filter to the multi_select.
|
||||||
|
|
||||||
14. Add a text filter to the multi_select.
|
|
||||||
|
|
||||||
15. init -h doesn't show --force flag. Separate into multiple structs: Global_FLags, and Init_Flags?
|
|
||||||
|
|
||||||
## Double-check AI output
|
## Double-check AI output
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "base:runtime"
|
||||||
import "core:bufio"
|
import "core:bufio"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:io"
|
import "core:io"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
|
import "core:reflect"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:terminal"
|
import "core:terminal"
|
||||||
import "core:text/table"
|
import "core:text/table"
|
||||||
@@ -17,13 +19,20 @@ Command :: struct {
|
|||||||
err: io.Writer,
|
err: io.Writer,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Put help test in usage:"whatever" tag.
|
|
||||||
Flags :: struct {
|
Flags :: struct {
|
||||||
help: bool `args:"short=h"`,
|
help: bool `args:"short=h" usage:"show this documentation"`,
|
||||||
config_file: string `args:"name=config-file,short=c"`,
|
config_file: string `args:"name=config-file,short=c" usage:"config file" default:"~/.envr/config.json"`,
|
||||||
output: Output_Format `args:"short=o"`,
|
output: Output_Format `args:"short=o" usage:"the format of output data" default:"table"`,
|
||||||
color: Color_Mode,
|
color: Color_Mode `usage:"Whether or not to colorize output" default:"auto"`,
|
||||||
force: bool `args:"short=f"`,
|
force: bool `args:"short=f" usage:"Overwrite existing config"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
Flag_Type :: enum {
|
||||||
|
Help,
|
||||||
|
Config_File,
|
||||||
|
Output,
|
||||||
|
Color,
|
||||||
|
Force,
|
||||||
}
|
}
|
||||||
|
|
||||||
Output_Format :: enum {
|
Output_Format :: enum {
|
||||||
@@ -44,8 +53,12 @@ CommandInfo :: struct {
|
|||||||
short: string,
|
short: string,
|
||||||
long: string,
|
long: string,
|
||||||
aliases: []string,
|
aliases: []string,
|
||||||
|
// Flags supported by the command
|
||||||
|
flags: bit_set[Flag_Type],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLOBAL_FLAGS: bit_set[Flag_Type] = {.Help, .Config_File, .Color}
|
||||||
|
|
||||||
COMMANDS := []CommandInfo {
|
COMMANDS := []CommandInfo {
|
||||||
{
|
{
|
||||||
"init",
|
"init",
|
||||||
@@ -56,22 +69,45 @@ COMMANDS := []CommandInfo {
|
|||||||
encrypt your databse. **Make 100% sure** that you have **a remote copy** of this
|
encrypt your databse. **Make 100% sure** that you have **a remote copy** of this
|
||||||
key somewhere, otherwise your data could be lost forever.`,
|
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},
|
||||||
|
{
|
||||||
|
"restore",
|
||||||
|
"envr restore <path>",
|
||||||
|
"Restore a .env file from the database",
|
||||||
|
"",
|
||||||
|
{},
|
||||||
|
GLOBAL_FLAGS,
|
||||||
|
},
|
||||||
|
{"list", "envr list", "View your tracked files", "", {}, GLOBAL_FLAGS + {.Output}},
|
||||||
|
{
|
||||||
|
"remove",
|
||||||
|
"envr remove <path>",
|
||||||
|
"Remove a .env file from your database",
|
||||||
|
"",
|
||||||
|
{},
|
||||||
|
GLOBAL_FLAGS,
|
||||||
|
},
|
||||||
|
{"check", "envr check [path]", "Check if files are backed up", "", {}, GLOBAL_FLAGS},
|
||||||
|
{"version", "envr version", "Show envr's version", "", {}, {.Help}},
|
||||||
|
{
|
||||||
|
"edit-config",
|
||||||
|
"envr edit-config",
|
||||||
|
"Edit your config with your default editor",
|
||||||
|
"",
|
||||||
|
{},
|
||||||
|
GLOBAL_FLAGS,
|
||||||
},
|
},
|
||||||
{"scan", "envr scan", "Find and select .env files for backup", "", {}},
|
|
||||||
{"sync", "envr sync", "Update or restore your env backups", "", {}},
|
|
||||||
{"backup", "envr backup <path>", "Import a .env file into envr", "", {"add"}},
|
|
||||||
{"restore", "envr restore <path>", "Restore a .env file from the database", "", {}},
|
|
||||||
{"list", "envr list", "View your tracked files", "", {}},
|
|
||||||
{"remove", "envr remove <path>", "Remove a .env file from your database", "", {}},
|
|
||||||
{"check", "envr check [path]", "Check if files are backed up", "", {}},
|
|
||||||
{"version", "envr version", "Show envr's version", "", {}},
|
|
||||||
{"edit-config", "envr edit-config", "Edit your config with your default editor", "", {}},
|
|
||||||
{
|
{
|
||||||
"nushell-completion",
|
"nushell-completion",
|
||||||
"envr nushell-completion",
|
"envr nushell-completion",
|
||||||
"Generate custom completions for nushell",
|
"Generate custom completions for nushell",
|
||||||
"",
|
"",
|
||||||
{},
|
{},
|
||||||
|
GLOBAL_FLAGS - {.Color},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +170,7 @@ print_command_help :: proc(cmd: ^Command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
write_command_help :: proc(name: string, w: io.Writer) -> bool {
|
write_command_help :: proc(name: string, w: io.Writer) -> bool {
|
||||||
|
// TODO: rename info to cmd?
|
||||||
info, found := find_command(name)
|
info, found := find_command(name)
|
||||||
if !found {
|
if !found {
|
||||||
return false
|
return false
|
||||||
@@ -166,18 +203,88 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool {
|
|||||||
fmt.wprintf(w, "\n%s\n", info.long, flush = false)
|
fmt.wprintf(w, "\n%s\n", info.long, flush = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.wprintf(
|
tbl: table.Table
|
||||||
w,
|
table.init(&tbl, context.temp_allocator, context.temp_allocator)
|
||||||
"\n%s\n\n %s" +
|
table.padding(&tbl, 2, 0)
|
||||||
` help for %s
|
if write_flags_table(&tbl, info.flags) {
|
||||||
%s <path> config file (default "~/.envr/config.json")
|
fmt.wprintf(w, "\n", flush = false)
|
||||||
`,
|
write_borderless_table(w, &tbl)
|
||||||
colorize(.Heading, "Flags:"),
|
}
|
||||||
colorize(.Flag, "-h, --help"),
|
table_reset(&tbl)
|
||||||
info.name,
|
return true
|
||||||
colorize(.Flag, "-c, --config-file"),
|
}
|
||||||
flush = false,
|
|
||||||
|
flag_field_info :: proc(
|
||||||
|
ft: Flag_Type,
|
||||||
|
) -> (
|
||||||
|
names: string,
|
||||||
|
value_hint: string,
|
||||||
|
description: string,
|
||||||
|
) {
|
||||||
|
field := reflect.struct_field_at(Flags, int(ft))
|
||||||
|
|
||||||
|
args_tag := reflect.struct_tag_get(field.tag, "args")
|
||||||
|
long_name, _ := strings.replace(field.name, "_", "-", -1, context.temp_allocator)
|
||||||
|
if n, ok := get_subtag(args_tag, "name"); ok {
|
||||||
|
long_name = n
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if _, is_bool := base_ti.variant.(runtime.Type_Info_Boolean); is_bool {
|
||||||
|
value_hint = ""
|
||||||
|
} else if _, is_string := base_ti.variant.(runtime.Type_Info_String); is_string {
|
||||||
|
value_hint = " <value>"
|
||||||
|
} else if enum_ti, is_enum := base_ti.variant.(runtime.Type_Info_Enum); is_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))
|
||||||
|
delete(parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
usage := reflect.struct_tag_get(field.tag, "usage")
|
||||||
|
default_val := reflect.struct_tag_get(field.tag, "default")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
write_flags_table :: proc(tbl: ^table.Table, flags: bit_set[Flag_Type]) -> (has_rows: bool) {
|
||||||
|
if flags == {} do return false
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,37 +365,10 @@ at before, restore your backup with:
|
|||||||
write_borderless_table(w, &tbl)
|
write_borderless_table(w, &tbl)
|
||||||
table_reset(&tbl)
|
table_reset(&tbl)
|
||||||
|
|
||||||
table.caption(&tbl, "Flags:")
|
if write_flags_table(&tbl, GLOBAL_FLAGS) {
|
||||||
|
|
||||||
table.row(&tbl, colorize(.Flag, "-h, --help", tbl.format_allocator), `show this documentation`)
|
|
||||||
table.row(
|
|
||||||
&tbl,
|
|
||||||
table.format(
|
|
||||||
&tbl,
|
|
||||||
"%s <path>",
|
|
||||||
colorize(.Flag, "-c, --config-file", tbl.format_allocator),
|
|
||||||
),
|
|
||||||
`config file (default "~/.envr/config.json")`,
|
|
||||||
)
|
|
||||||
table.row(
|
|
||||||
&tbl,
|
|
||||||
table.format(
|
|
||||||
&tbl,
|
|
||||||
"%s 'table'|'json'",
|
|
||||||
colorize(.Flag, "-o, --output", tbl.format_allocator),
|
|
||||||
),
|
|
||||||
`The format of output data. (default 'table')`,
|
|
||||||
)
|
|
||||||
table.row(
|
|
||||||
&tbl,
|
|
||||||
table.format(
|
|
||||||
&tbl,
|
|
||||||
"%s 'auto'|'always'|'never'",
|
|
||||||
colorize(.Flag, "--color", tbl.format_allocator),
|
|
||||||
),
|
|
||||||
`Whether or not to colorize output. (default 'auto')`,
|
|
||||||
)
|
|
||||||
write_borderless_table(w, &tbl)
|
write_borderless_table(w, &tbl)
|
||||||
|
}
|
||||||
|
table_reset(&tbl)
|
||||||
|
|
||||||
fmt.wprintf(
|
fmt.wprintf(
|
||||||
w,
|
w,
|
||||||
|
|||||||
+2
-1
@@ -110,7 +110,8 @@ test_command_help_init_no_aliases :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, !strings.contains(text, "Aliases:"), "init should not have Aliases section")
|
testing.expect(t, !strings.contains(text, "Aliases:"), "init should not have Aliases section")
|
||||||
testing.expect(t, strings.contains(text, "Flags:"), "missing Flags section")
|
testing.expect(t, strings.contains(text, "Flags:"), "missing Flags section")
|
||||||
testing.expect(t, strings.contains(text, "show this documentation"), "missing help flag description")
|
testing.expect(t, strings.contains(text, "show this documentation"), "missing help flag description")
|
||||||
}
|
testing.expect(t, strings.contains(text, "--force"), "missing --force flag")
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_command_help_unknown :: proc(t: ^testing.T) {
|
test_command_help_unknown :: proc(t: ^testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user