mirror of
https://github.com/sbrow/envr.git
synced 2026-08-26 18:03:32 -04:00
Compare commits
1 Commits
v0.4.0
..
e74fc4f35a
| Author | SHA1 | Date | |
|---|---|---|---|
| e74fc4f35a |
@@ -2,7 +2,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- prerelease
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -1,36 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.4.0](https://github.com/sbrow/envr/compare/v0.3.0...v0.4.0) (2026-06-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added `--color` flag. ([c3e667e](https://github.com/sbrow/envr/commit/c3e667e7bca660b21b5851460691ec8bd5026f80))
|
|
||||||
* Added `--format`, `-f` flag. ([bb84c56](https://github.com/sbrow/envr/commit/bb84c56c98bead9ce9bad3f89fb3b61b53d05a10))
|
|
||||||
* Colorized console output. ([33cd7c4](https://github.com/sbrow/envr/commit/33cd7c4eda43287fe3dc2d2289a4e1531b524ef6))
|
|
||||||
* Removed runtime git dependency. ([12574e1](https://github.com/sbrow/envr/commit/12574e123bdedba3aca813143e906ec5e0b95719))
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* -h short flag now works on subcommands. ([2b68617](https://github.com/sbrow/envr/commit/2b68617b5d6c17444b04307d7b4b7f4a3aefb978))
|
|
||||||
* Databases errors are less likely to go unnoticed. ([f825bc2](https://github.com/sbrow/envr/commit/f825bc2b096632bb258d5681b1072941e9d4233d))
|
|
||||||
* Fixed leaks. ([c7c254f](https://github.com/sbrow/envr/commit/c7c254f6f2ac871182f3fcc233766884753e2049))
|
|
||||||
* Fixed memory leaks in the db. ([5059572](https://github.com/sbrow/envr/commit/5059572951b3ec20b3d2027032a9c3be5cb14dba))
|
|
||||||
* Fixed some leaks in `backup` and `scan`. ([dc72ff5](https://github.com/sbrow/envr/commit/dc72ff56fd0e165930771682b2fd266eef3e7e16))
|
|
||||||
* Fixed vet errors. ([1562fb3](https://github.com/sbrow/envr/commit/1562fb3665b8704dcffd4944047f7984b308e52e))
|
|
||||||
* Flags in help text are now customized per command. ([fb90342](https://github.com/sbrow/envr/commit/fb903421265baa65adbbe092f6bf797639f1fb1f))
|
|
||||||
* Handled mk_dir error. ([de1594d](https://github.com/sbrow/envr/commit/de1594d9d1fe46dd9ebc3be01fc5a5ebaf4064e5))
|
|
||||||
* **scan:** Fixed a bug preventing TUI from working. ([0083e4e](https://github.com/sbrow/envr/commit/0083e4e0dbc5b949a3cf4cd8b64af4f698ed9d33))
|
|
||||||
* Used os path separator rather than '/' where appropriate. ([5cc7973](https://github.com/sbrow/envr/commit/5cc79737753f0f19db2bc55304ea5f70237cbffd))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* Improved the performance of table rendering. ([0b5bf4d](https://github.com/sbrow/envr/commit/0b5bf4db73113bdd43d8a225ec0ae53fedd25918))
|
|
||||||
* remotes are now stored as a newline delimited list. ([96b3d63](https://github.com/sbrow/envr/commit/96b3d6340a95b67c884ba940b46461e51989fcbb))
|
|
||||||
* Replaced `fd` with custom internals. ([2ef733f](https://github.com/sbrow/envr/commit/2ef733fe58594b0a0b6e3ef85142b74af445ccb8))
|
|
||||||
|
|
||||||
## [0.3.0](https://github.com/sbrow/envr/compare/v0.2.1...v0.3.0) (2026-06-16)
|
## [0.3.0](https://github.com/sbrow/envr/compare/v0.2.1...v0.3.0) (2026-06-16)
|
||||||
|
|
||||||
Version 0.3.0 represents a significant departure (and improvement) for envr.
|
Version 0.3.0 represents a significant departure (and improvement) for envr.
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
# TODOs
|
# TODOs
|
||||||
|
|
||||||
1. Bring back windows support / cross-compilation.
|
1. Commands are still leaking. (Write tests for everything first)
|
||||||
|
|
||||||
2. Commands are still leaking. (Write tests for everything first)
|
2. Add color flag and support non colored output.
|
||||||
|
|
||||||
3. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
3. Rewrite `write_command_help` to use text/tables
|
||||||
|
|
||||||
4. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
4. Generate md and man pages again.
|
||||||
|
|
||||||
5. Test all cmds / terminal branches.
|
5. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
||||||
|
|
||||||
6. Generate md and man pages again.
|
6. Add a text filter to the multi_select.
|
||||||
|
|
||||||
7. Shell completion
|
7. Add tests for untested commands.
|
||||||
|
|
||||||
8. Add tests for untested commands.
|
8. add --format -f flag to commands that draw tables.
|
||||||
|
|
||||||
9. Update `read_wire_string` to use a slice.
|
9. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
||||||
|
|
||||||
10. Pass allocator to findr?
|
10. Shell completion
|
||||||
|
|
||||||
11. Consider getting rid of color global.
|
11. Bring back windows support / cross-compilation.
|
||||||
|
|
||||||
12. `write_flags_table` should never return false.
|
12. Test all cmds / terminal branches.
|
||||||
|
|
||||||
13. Add a text filter to the multi_select.
|
13. Pass allocator to findr?
|
||||||
|
|
||||||
|
14. Update `read_wire_string` to use a slice.
|
||||||
|
|
||||||
|
15. `-h` short flag seems to fail, at least with `envr list`
|
||||||
|
|
||||||
## Double-check AI output
|
## Double-check AI output
|
||||||
|
|
||||||
@@ -52,7 +56,6 @@
|
|||||||
- [ ] db.odin
|
- [ ] db.odin
|
||||||
- [ ] db_integration_test.odin
|
- [ ] db_integration_test.odin
|
||||||
- [ ] db_test.odin
|
- [ ] db_test.odin
|
||||||
- [ ] flags.odin
|
|
||||||
- [x] main.odin
|
- [x] main.odin
|
||||||
- [x] prompt.odin
|
- [x] prompt.odin
|
||||||
- [x] scan.odin
|
- [x] scan.odin
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
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"
|
||||||
@@ -13,52 +11,27 @@ import "core:text/table"
|
|||||||
Command :: struct {
|
Command :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
args: [dynamic]string,
|
args: [dynamic]string,
|
||||||
flags: Flags,
|
flags: map[string]string,
|
||||||
|
bool_set: map[string]bool,
|
||||||
|
config_path: string,
|
||||||
out_buf: ^bufio.Writer,
|
out_buf: ^bufio.Writer,
|
||||||
out: io.Writer,
|
out: io.Writer,
|
||||||
err: io.Writer,
|
err: io.Writer,
|
||||||
}
|
}
|
||||||
|
|
||||||
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"`,
|
|
||||||
force: bool `args:"short=f" usage:"Overwrite existing config"`,
|
|
||||||
}
|
|
||||||
|
|
||||||
Flag_Type :: enum {
|
|
||||||
Help,
|
|
||||||
Config_File,
|
|
||||||
Output,
|
|
||||||
Color,
|
|
||||||
Force,
|
|
||||||
}
|
|
||||||
|
|
||||||
Output_Format :: enum {
|
Output_Format :: enum {
|
||||||
Auto,
|
|
||||||
Table,
|
Table,
|
||||||
JSON,
|
JSON,
|
||||||
}
|
}
|
||||||
|
|
||||||
Color_Mode :: enum {
|
|
||||||
Auto,
|
|
||||||
Always,
|
|
||||||
Never,
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandInfo :: struct {
|
CommandInfo :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
usage: string,
|
usage: string,
|
||||||
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",
|
||||||
@@ -69,45 +42,22 @@ 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},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,33 +77,53 @@ parse_args :: proc(args: []string, out: io.Stream, err: io.Stream) -> (cmd: Comm
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd.name = args[1]
|
cmd.name = args[1]
|
||||||
|
|
||||||
cmd.args = make([dynamic]string)
|
cmd.args = make([dynamic]string)
|
||||||
|
cmd.flags = make(map[string]string)
|
||||||
|
cmd.bool_set = make(map[string]bool)
|
||||||
|
|
||||||
overflow := parse_flags(&cmd.flags, args[2:])
|
// TODO: Optimize loop?
|
||||||
for arg in overflow {
|
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)
|
append(&cmd.args, arg)
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.flags.output == .Auto {
|
val: string = ---
|
||||||
cmd.flags.output = terminal.is_terminal(os.stdout) ? .Table : .JSON
|
if val, ok = cmd.flags["config-file"]; ok {
|
||||||
}
|
cmd.config_path = val
|
||||||
|
} else if val, ok = cmd.flags["c"]; ok {
|
||||||
if cmd.flags.color == .Auto {
|
cmd.config_path = val
|
||||||
cmd.flags.color = terminal.is_terminal(os.stdout) ? .Always : .Never
|
} else {
|
||||||
}
|
|
||||||
if cmd.flags.color == .Never {
|
|
||||||
disable_color = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.flags.config_file == "" {
|
|
||||||
// FIXME: Handle err
|
// FIXME: Handle err
|
||||||
// TODO: Is this right?
|
// TODO: Is this right?
|
||||||
home, _ := os.user_home_dir(context.temp_allocator)
|
home, _ := os.user_home_dir(context.temp_allocator)
|
||||||
// TODO: should we copy out of the temp_allocator?
|
// TODO: should we copy out of the temp_allocator?
|
||||||
cmd.flags.config_file = default_config_path(home, context.temp_allocator)
|
cmd.config_path = default_config_path(home, context.temp_allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.flags.help {
|
if has_flag(&cmd, "help") {
|
||||||
print_command_help(&cmd)
|
print_command_help(&cmd)
|
||||||
return cmd, false
|
return cmd, false
|
||||||
}
|
}
|
||||||
@@ -170,7 +140,6 @@ 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
|
||||||
@@ -178,23 +147,36 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool {
|
|||||||
|
|
||||||
fmt.wprintf(
|
fmt.wprintf(
|
||||||
w,
|
w,
|
||||||
"%s\n\n\n%s\n\n %s [flags]\n\n",
|
"%s\n\n\n" +
|
||||||
|
COLOR_HEADINGS +
|
||||||
|
"Usage:" +
|
||||||
|
ANSI_RESET +
|
||||||
|
"\n\n " +
|
||||||
|
COLOR_FLAGS +
|
||||||
|
"%s" +
|
||||||
|
ANSI_RESET +
|
||||||
|
" [flags]\n\n",
|
||||||
info.short,
|
info.short,
|
||||||
colorize(.Heading, "Usage:"),
|
info.usage,
|
||||||
colorize(.Flag, info.usage),
|
|
||||||
flush = false,
|
flush = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(info.aliases) > 0 {
|
if len(info.aliases) > 0 {
|
||||||
fmt.wprintf(
|
fmt.wprintf(
|
||||||
w,
|
w,
|
||||||
"\n%s\n\n %s",
|
"\n" +
|
||||||
colorize(.Heading, "Aliases:"),
|
COLOR_HEADINGS +
|
||||||
colorize(.Command, info.name),
|
"Aliases:" +
|
||||||
|
ANSI_RESET +
|
||||||
|
"\n\n " +
|
||||||
|
COLOR_COMMANDS +
|
||||||
|
"%s" +
|
||||||
|
ANSI_RESET,
|
||||||
|
info.name,
|
||||||
flush = false,
|
flush = false,
|
||||||
)
|
)
|
||||||
for a in info.aliases {
|
for a in info.aliases {
|
||||||
fmt.wprintf(w, ", %s", colorize(.Command, a), flush = false)
|
fmt.wprintf(w, ", " + COLOR_COMMANDS + "%s" + ANSI_RESET, a, flush = false)
|
||||||
}
|
}
|
||||||
fmt.wprintf(w, "\n", flush = false)
|
fmt.wprintf(w, "\n", flush = false)
|
||||||
}
|
}
|
||||||
@@ -203,88 +185,25 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
tbl: table.Table
|
fmt.wprintf(
|
||||||
table.init(&tbl, context.temp_allocator, context.temp_allocator)
|
w,
|
||||||
table.padding(&tbl, 2, 0)
|
"\n" +
|
||||||
if write_flags_table(&tbl, info.flags) {
|
COLOR_HEADINGS +
|
||||||
fmt.wprintf(w, "\n", flush = false)
|
"Flags:" +
|
||||||
write_borderless_table(w, &tbl)
|
ANSI_RESET +
|
||||||
}
|
"\n\n " +
|
||||||
table_reset(&tbl)
|
COLOR_FLAGS +
|
||||||
return true
|
"-h, --help" +
|
||||||
}
|
ANSI_RESET +
|
||||||
|
" help for %s\n " +
|
||||||
flag_field_info :: proc(
|
COLOR_FLAGS +
|
||||||
ft: Flag_Type,
|
"-c, --config-file" +
|
||||||
) -> (
|
ANSI_RESET +
|
||||||
names: string,
|
` <path> config file (default "~/.envr/config.json")
|
||||||
value_hint: string,
|
`,
|
||||||
description: string,
|
info.name,
|
||||||
) {
|
flush = false,
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,13 +256,15 @@ at before, restore your backup with:
|
|||||||
|
|
||||||
> envr restore ~/<path to repository>/.env
|
> envr restore ~/<path to repository>/.env
|
||||||
|
|
||||||
%s
|
%sUsage:%s
|
||||||
|
|
||||||
%s [command]
|
%senvr%s [command]
|
||||||
|
|
||||||
`,
|
`,
|
||||||
colorize(.Heading, "Usage:"),
|
COLOR_HEADINGS,
|
||||||
colorize(.Flag, "envr"),
|
ANSI_RESET,
|
||||||
|
COLOR_FLAGS,
|
||||||
|
ANSI_RESET,
|
||||||
flush = false,
|
flush = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -359,28 +280,61 @@ at before, restore your backup with:
|
|||||||
for a in c.aliases {
|
for a in c.aliases {
|
||||||
name = strings.join([]string{name, a}, ", ", tbl.format_allocator)
|
name = strings.join([]string{name, a}, ", ", tbl.format_allocator)
|
||||||
}
|
}
|
||||||
table.row(&tbl, colorize(.Command, name, tbl.format_allocator), c.short)
|
table.row(&tbl, table.format(&tbl, "%s%s%s", COLOR_COMMANDS, name, ANSI_RESET), c.short)
|
||||||
}
|
}
|
||||||
|
|
||||||
write_borderless_table(w, &tbl)
|
write_borderless_table(w, &tbl)
|
||||||
table_reset(&tbl)
|
table_reset(&tbl)
|
||||||
|
|
||||||
if write_flags_table(&tbl, GLOBAL_FLAGS) {
|
table.caption(&tbl, "Flags:")
|
||||||
|
|
||||||
|
table.row(&tbl, COLOR_FLAGS + "-h, --help" + ANSI_RESET, `show this documentation`)
|
||||||
|
table.row(
|
||||||
|
&tbl,
|
||||||
|
COLOR_FLAGS + "-c, --config-file" + ANSI_RESET + " <path>",
|
||||||
|
`config file (default "~/.envr/config.json")`,
|
||||||
|
)
|
||||||
|
table.row(
|
||||||
|
&tbl,
|
||||||
|
COLOR_FLAGS + "-f, --format" + ANSI_RESET + " 'json'|'table'",
|
||||||
|
`the format of output data. (default 'table', unless piping)`,
|
||||||
|
)
|
||||||
write_borderless_table(w, &tbl)
|
write_borderless_table(w, &tbl)
|
||||||
}
|
|
||||||
table_reset(&tbl)
|
|
||||||
|
|
||||||
fmt.wprintf(
|
fmt.wprintf(
|
||||||
w,
|
w,
|
||||||
`Use "%s [command] --help" for more information about a command.`,
|
`Use "%senvr%s [command] --help" for more information about a command.`,
|
||||||
colorize(.Flag, "envr", tbl.format_allocator),
|
COLOR_FLAGS,
|
||||||
|
ANSI_RESET,
|
||||||
flush = false,
|
flush = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
has_flag :: proc(cmd: ^Command, name: string) -> bool {
|
||||||
|
return name in cmd.flags || name in cmd.bool_set
|
||||||
|
}
|
||||||
|
|
||||||
|
get_format :: proc(cmd: ^Command) -> Output_Format {
|
||||||
|
flags :: []string{"format", "f"}
|
||||||
|
for name in flags {
|
||||||
|
if val, ok := cmd.flags[name]; ok {
|
||||||
|
switch val {
|
||||||
|
case "json":
|
||||||
|
return .JSON
|
||||||
|
case "table":
|
||||||
|
return .Table
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return terminal.is_terminal(os.stdout) ? .Table : .JSON
|
||||||
|
}
|
||||||
|
|
||||||
delete_command :: proc(cmd: ^Command) {
|
delete_command :: proc(cmd: ^Command) {
|
||||||
bufio.writer_flush(cmd.out_buf)
|
bufio.writer_flush(cmd.out_buf)
|
||||||
delete(cmd.args)
|
delete(cmd.args)
|
||||||
|
delete(cmd.flags)
|
||||||
|
delete(cmd.bool_set)
|
||||||
bufio.writer_destroy(cmd.out_buf)
|
bufio.writer_destroy(cmd.out_buf)
|
||||||
free(cmd.out_buf)
|
free(cmd.out_buf)
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-43
@@ -110,8 +110,7 @@ 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, "help for init"), "missing 'help for init'")
|
testing.expect(t, strings.contains(text, "help for init"), "missing 'help for init'")
|
||||||
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) {
|
||||||
@@ -145,6 +144,53 @@ test_command_help_version :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
test_has_flag_bool_set :: proc(t: ^testing.T) {
|
||||||
|
cmd := Command {
|
||||||
|
name = "test",
|
||||||
|
bool_set = map[string]bool{"force" = true},
|
||||||
|
}
|
||||||
|
defer delete(cmd.bool_set)
|
||||||
|
|
||||||
|
testing.expect(t, has_flag(&cmd, "force"), "should find flag in bool_set")
|
||||||
|
testing.expect(t, !has_flag(&cmd, "verbose"), "should not find missing flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_has_flag_value_map :: proc(t: ^testing.T) {
|
||||||
|
cmd := Command {
|
||||||
|
name = "test",
|
||||||
|
flags = map[string]string{"output" = "/tmp/out"},
|
||||||
|
}
|
||||||
|
defer delete(cmd.flags)
|
||||||
|
|
||||||
|
testing.expect(t, has_flag(&cmd, "output"), "should find flag in flags map")
|
||||||
|
testing.expect(t, !has_flag(&cmd, "force"), "should not find missing flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_has_flag_both_maps :: proc(t: ^testing.T) {
|
||||||
|
cmd := Command {
|
||||||
|
name = "test",
|
||||||
|
flags = map[string]string{"output" = "/tmp/out"},
|
||||||
|
bool_set = map[string]bool{"force" = true},
|
||||||
|
}
|
||||||
|
defer delete(cmd.flags)
|
||||||
|
defer delete(cmd.bool_set)
|
||||||
|
|
||||||
|
testing.expect(t, has_flag(&cmd, "output"), "should find in flags")
|
||||||
|
testing.expect(t, has_flag(&cmd, "force"), "should find in bool_set")
|
||||||
|
testing.expect(t, !has_flag(&cmd, "verbose"), "should not find missing flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_has_flag_empty_command :: proc(t: ^testing.T) {
|
||||||
|
cmd := Command {
|
||||||
|
name = "test",
|
||||||
|
}
|
||||||
|
testing.expect(t, !has_flag(&cmd, "anything"), "empty command should have no flags")
|
||||||
|
}
|
||||||
|
|
||||||
|
test_parse_args :: proc(
|
||||||
args: []string,
|
args: []string,
|
||||||
) -> (
|
) -> (
|
||||||
cmd: Command,
|
cmd: Command,
|
||||||
@@ -180,6 +226,8 @@ test_parse_args_bare_command :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, cmd.name, "list")
|
testing.expect_value(t, cmd.name, "list")
|
||||||
testing.expect_value(t, len(cmd.args), 0)
|
testing.expect_value(t, len(cmd.args), 0)
|
||||||
testing.expect_value(t, len(cmd.flags), 0)
|
testing.expect_value(t, len(cmd.flags), 0)
|
||||||
|
testing.expect_value(t, len(cmd.bool_set), 0)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_positional :: proc(t: ^testing.T) {
|
test_parse_args_positional :: proc(t: ^testing.T) {
|
||||||
@@ -194,45 +242,43 @@ test_parse_args_positional :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_long_flag_with_value :: proc(t: ^testing.T) {
|
test_parse_args_long_flag_with_value :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args(
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "sync", "--config", "x.json"})
|
||||||
[]string{"envr", "sync", "--config-file", "x.json"},
|
testing.expect(t, ok, "should succeed")
|
||||||
)
|
|
||||||
testing.expect(t, ok, "should succeed")
|
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, cmd.flags["config"], "x.json")
|
testing.expect_value(t, cmd.flags["config"], "x.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_short_flag_with_value :: proc(t: ^testing.T) {
|
test_parse_args_short_flag_with_value :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "sync", "-c", "x.json"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "sync", "-c", "x.json"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, cmd.flags["c"], "x.json")
|
testing.expect_value(t, cmd.flags["c"], "x.json")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_long_bool_flag :: proc(t: ^testing.T) {
|
test_parse_args_long_bool_flag :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "init", "--force"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "init", "--force"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, cmd.bool_set["force"], true)
|
testing.expect_value(t, cmd.bool_set["force"], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_short_bool_flag :: proc(t: ^testing.T) {
|
test_parse_args_short_bool_flag :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "init", "-f"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "version", "-l"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, cmd.bool_set["l"], true)
|
testing.expect_value(t, cmd.bool_set["l"], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_multiple_positionals :: proc(t: ^testing.T) {
|
test_parse_args_multiple_positionals :: proc(t: ^testing.T) {
|
||||||
@@ -254,7 +300,7 @@ test_parse_args_mixed_flags_and_positionals :: proc(t: ^testing.T) {
|
|||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, cmd.bool_set["force"], true)
|
testing.expect_value(t, cmd.bool_set["force"], true)
|
||||||
testing.expect_value(t, len(cmd.args), 1)
|
testing.expect_value(t, len(cmd.args), 1)
|
||||||
testing.expect_value(t, cmd.args[0], "/project/.env")
|
testing.expect_value(t, cmd.args[0], "/project/.env")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,77 +314,90 @@ test_parse_args_no_args :: proc(t: ^testing.T) {
|
|||||||
@(test)
|
@(test)
|
||||||
test_parse_args_flag_then_positional_then_flag :: proc(t: ^testing.T) {
|
test_parse_args_flag_then_positional_then_flag :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "backup", "a.env", "--force", "--verbose"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "backup", "a.env", "--force", "--verbose"})
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
|
|
||||||
testing.expect_value(t, cmd.bool_set["force"], true)
|
testing.expect_value(t, cmd.bool_set["force"], true)
|
||||||
testing.expect_value(t, cmd.flags.output, Output_Format.JSON)
|
testing.expect_value(t, cmd.bool_set["verbose"], true)
|
||||||
testing.expect_value(t, len(cmd.args), 1)
|
testing.expect_value(t, len(cmd.args), 1)
|
||||||
testing.expect_value(t, cmd.args[0], "a.env")
|
testing.expect_value(t, cmd.args[0], "a.env")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_parse_args_config_file_long_flag :: proc(t: ^testing.T) {
|
test_parse_args_config_file_long_flag :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "list"})
|
cmd, ok, _, _ := test_parse_args(
|
||||||
|
[]string{"envr", "list", "--config-file", "/custom/config.json"},
|
||||||
|
)
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
testing.expect_value(t, cmd.config_path, "/custom/config.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_parse_args_config_file_short_flag :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-c", "/custom/config.json"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
testing.expect_value(t, cmd.config_path, "/custom/config.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_parse_args_config_file_defaults :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect(t, len(cmd.config_path) > 0, "config_path should default to non-empty path")
|
testing.expect(t, len(cmd.config_path) > 0, "config_path should default to non-empty path")
|
||||||
testing.expect(
|
testing.expect(
|
||||||
t,
|
t,
|
||||||
strings.contains(cmd.config_path, ".envr"),
|
strings.contains(cmd.config_path, ".envr"),
|
||||||
"default config_file should contain .envr dir, got %s",
|
"default config_path should contain .envr dir, got %s",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_get_format_long_json :: proc(t: ^testing.T) {
|
test_get_format_long_json :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--output", "json"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--format", "json"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, get_format(&cmd), Output_Format.JSON)
|
testing.expect_value(t, get_format(&cmd), Output_Format.JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_get_format_short_json :: proc(t: ^testing.T) {
|
test_get_format_short_json :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-o", "json"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-f", "json"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, get_format(&cmd), Output_Format.JSON)
|
testing.expect_value(t, get_format(&cmd), Output_Format.JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_get_format_long_table :: proc(t: ^testing.T) {
|
test_get_format_long_table :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--output", "table"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--format", "table"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, get_format(&cmd), Output_Format.Table)
|
testing.expect_value(t, get_format(&cmd), Output_Format.Table)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_get_format_short_table :: proc(t: ^testing.T) {
|
test_get_format_short_table :: proc(t: ^testing.T) {
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-o", "table"})
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-f", "table"})
|
||||||
testing.expect(t, ok, "should succeed")
|
testing.expect(t, ok, "should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_command(&cmd)
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
testing.expect_value(t, get_format(&cmd), Output_Format.Table)
|
testing.expect_value(t, get_format(&cmd), Output_Format.Table)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
test_parse_args_output_equals_syntax :: proc(t: ^testing.T) {
|
|
||||||
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--output=json"})
|
|
||||||
testing.expect(t, ok, "should succeed")
|
|
||||||
if !ok do return
|
|
||||||
defer delete_command(&cmd)
|
|
||||||
|
|
||||||
testing.expect_value(t, cmd.flags.output, Output_Format.JSON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ cmd_backup :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ cmd_check :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ cmd_edit_config :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config_path := cmd.flags.config_file
|
config_path := cmd.config_path
|
||||||
|
|
||||||
if !os.exists(config_path) {
|
if !os.exists(config_path) {
|
||||||
fmt.wprintf(
|
fmt.wprintf(
|
||||||
|
|||||||
+7
-13
@@ -4,12 +4,11 @@ import "core:fmt"
|
|||||||
import "core:terminal/ansi"
|
import "core:terminal/ansi"
|
||||||
|
|
||||||
cmd_init :: proc(cmd: ^Command) {
|
cmd_init :: proc(cmd: ^Command) {
|
||||||
force := cmd.flags.force
|
force := has_flag(cmd, "force") || has_flag(cmd, "f")
|
||||||
config_file := cmd.flags.config_file
|
|
||||||
|
|
||||||
fmt.wprintln(cmd.out, cmd.flags.config_file, flush = false)
|
fmt.wprintln(cmd.out, cmd.config_path, flush = false)
|
||||||
|
|
||||||
_, cfg_exists := load_config(config_file)
|
_, cfg_exists := load_config(cmd.config_path)
|
||||||
if cfg_exists && !force {
|
if cfg_exists && !force {
|
||||||
fmt.wprintln(
|
fmt.wprintln(
|
||||||
cmd.out,
|
cmd.out,
|
||||||
@@ -26,19 +25,15 @@ Run again with the --force flag if you want to reinitialize.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(keys) == 0 {
|
if len(keys) == 0 {
|
||||||
fmt.wprintln(
|
fmt.wprintln(cmd.err, `No ssh-ed25519 keys found in ~/.ssh
|
||||||
cmd.err,
|
Generate one with: ssh-keygen -t ed25519`, flush = false)
|
||||||
`No ssh-ed25519 keys found in ~/.ssh
|
|
||||||
Generate one with: ssh-keygen -t ed25519`,
|
|
||||||
flush = false,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selected, result := multi_select("Select SSH private keys:", keys[:])
|
selected, result := multi_select("Select SSH private keys:", keys[:])
|
||||||
defer delete(selected)
|
defer delete(selected)
|
||||||
if result == .Cancel {
|
if result == .Cancel {
|
||||||
fmt.wprintln(cmd.out, colorize(.Message, "Cancelled."), flush = false)
|
fmt.wprintln(cmd.out, ansi.CSI + ansi.FAINT + ansi.SGR + "Cancelled." + ANSI_RESET, flush = false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +49,7 @@ Generate one with: ssh-keygen -t ed25519`,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := new_config(selected_paths[:], config_file)
|
cfg := new_config(selected_paths[:], cmd.config_path)
|
||||||
if !save_config(cfg, force = force) {
|
if !save_config(cfg, force = force) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -66,4 +61,3 @@ Generate one with: ssh-keygen -t ed25519`,
|
|||||||
flush = false,
|
flush = false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -14,7 +14,7 @@ ListEntry :: struct {
|
|||||||
|
|
||||||
// TODO: Improve table rendering
|
// TODO: Improve table rendering
|
||||||
cmd_list :: proc(cmd: ^Command) {
|
cmd_list :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -25,15 +25,15 @@ cmd_list :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.flags.output == .Table {
|
if get_format(cmd) == .Table {
|
||||||
t: table.Table
|
t: table.Table
|
||||||
table.init(&t, context.temp_allocator, context.temp_allocator)
|
table.init(&t, context.temp_allocator, context.temp_allocator)
|
||||||
table.padding(&t, 1, 1)
|
table.padding(&t, 1, 1)
|
||||||
table.aligned_header_of_values(
|
table.aligned_header_of_values(
|
||||||
&t,
|
&t,
|
||||||
.Center,
|
.Center,
|
||||||
colorize(.Table_Heading, "Directory"),
|
COLOR_TABLE_HEADING + "Directory" + ANSI_RESET,
|
||||||
colorize(.Table_Heading, "Path"),
|
COLOR_TABLE_HEADING + "Path" + ANSI_RESET,
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in rows {
|
for row in rows {
|
||||||
|
|||||||
+4
-4
@@ -22,7 +22,7 @@ test_filepath_base_equals_rel :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_cmd_list_output_json :: proc(t: ^testing.T) {
|
test_cmd_list_format_json :: proc(t: ^testing.T) {
|
||||||
base := test_temp_dir(t, "envr-test-list-json-*")
|
base := test_temp_dir(t, "envr-test-list-json-*")
|
||||||
defer os.remove_all(base)
|
defer os.remove_all(base)
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ test_cmd_list_output_json :: proc(t: ^testing.T) {
|
|||||||
defer strings.builder_destroy(&err_b)
|
defer strings.builder_destroy(&err_b)
|
||||||
|
|
||||||
cmd, ok := parse_args(
|
cmd, ok := parse_args(
|
||||||
[]string{"envr", "list", "--output", "json", "--config-file", cfg_path},
|
[]string{"envr", "list", "--format", "json", "--config-file", cfg_path},
|
||||||
strings.to_stream(&out_b),
|
strings.to_stream(&out_b),
|
||||||
strings.to_stream(&err_b),
|
strings.to_stream(&err_b),
|
||||||
)
|
)
|
||||||
@@ -68,7 +68,7 @@ test_cmd_list_output_json :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_cmd_list_output_table :: proc(t: ^testing.T) {
|
test_cmd_list_format_table :: proc(t: ^testing.T) {
|
||||||
base := test_temp_dir(t, "envr-test-list-table-*")
|
base := test_temp_dir(t, "envr-test-list-table-*")
|
||||||
defer os.remove_all(base)
|
defer os.remove_all(base)
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ test_cmd_list_output_table :: proc(t: ^testing.T) {
|
|||||||
defer strings.builder_destroy(&err_b)
|
defer strings.builder_destroy(&err_b)
|
||||||
|
|
||||||
cmd, ok := parse_args(
|
cmd, ok := parse_args(
|
||||||
[]string{"envr", "list", "--output", "table", "--config-file", cfg_path},
|
[]string{"envr", "list", "--format", "table", "--config-file", cfg_path},
|
||||||
strings.to_stream(&out_b),
|
strings.to_stream(&out_b),
|
||||||
strings.to_stream(&err_b),
|
strings.to_stream(&err_b),
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ cmd_remove :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ cmd_restore :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-8
@@ -4,9 +4,10 @@ import "core:encoding/json"
|
|||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:terminal"
|
import "core:terminal"
|
||||||
|
import "core:terminal/ansi"
|
||||||
|
|
||||||
cmd_scan :: proc(cmd: ^Command) {
|
cmd_scan :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -71,7 +72,11 @@ cmd_scan :: proc(cmd: ^Command) {
|
|||||||
selected, result := multi_select("Select .env files to backup:", files[:])
|
selected, result := multi_select("Select .env files to backup:", files[:])
|
||||||
defer delete(selected)
|
defer delete(selected)
|
||||||
if result == .Cancel {
|
if result == .Cancel {
|
||||||
fmt.wprintln(cmd.out, colorize(.Message, "Cancelled."), flush = false)
|
fmt.wprintln(
|
||||||
|
cmd.out,
|
||||||
|
ansi.CSI + ansi.FAINT + ansi.SGR + "Cancelled." + ANSI_RESET,
|
||||||
|
flush = false,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,16 +100,25 @@ cmd_scan :: proc(cmd: ^Command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if added_count > 0 {
|
if added_count > 0 {
|
||||||
fmt.wprintln(
|
fmt.wprintf(
|
||||||
cmd.out,
|
cmd.out,
|
||||||
colorize(
|
ansi.CSI +
|
||||||
.Sucess,
|
ansi.BOLD +
|
||||||
fmt.tprintf("Successfully added %d file(s) to backup.", added_count),
|
";" +
|
||||||
),
|
ansi.FG_GREEN +
|
||||||
|
ansi.SGR +
|
||||||
|
"Successfully added %d file(s) to backup." +
|
||||||
|
ANSI_RESET +
|
||||||
|
"\n",
|
||||||
|
added_count,
|
||||||
flush = false,
|
flush = false,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
fmt.wprintln(cmd.out, colorize(.Message, "No files were added."), flush = false)
|
fmt.wprintln(
|
||||||
|
cmd.out,
|
||||||
|
ansi.CSI + ansi.FAINT + ansi.SGR + "No files were added." + ANSI_RESET,
|
||||||
|
flush = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -11,7 +11,7 @@ SyncEntry :: struct {
|
|||||||
|
|
||||||
// TODO: Check for quiet failures.
|
// TODO: Check for quiet failures.
|
||||||
cmd_sync :: proc(cmd: ^Command) {
|
cmd_sync :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open(cmd.flags.config_file)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ cmd_sync :: proc(cmd: ^Command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.flags.output == .Table {
|
if get_format(cmd) == .Table {
|
||||||
t: table.Table
|
t: table.Table
|
||||||
table.init(&t, context.temp_allocator, context.temp_allocator)
|
table.init(&t, context.temp_allocator, context.temp_allocator)
|
||||||
table.padding(&t, 1, 1)
|
table.padding(&t, 1, 1)
|
||||||
@@ -54,8 +54,8 @@ cmd_sync :: proc(cmd: ^Command) {
|
|||||||
table.aligned_header_of_values(
|
table.aligned_header_of_values(
|
||||||
&t,
|
&t,
|
||||||
.Center,
|
.Center,
|
||||||
colorize(.Table_Heading, "File"),
|
COLOR_TABLE_HEADING + "File" + ANSI_RESET,
|
||||||
colorize(.Table_Heading, "Status"),
|
COLOR_TABLE_HEADING + "Status" + ANSI_RESET,
|
||||||
)
|
)
|
||||||
|
|
||||||
for res in results {
|
for res in results {
|
||||||
|
|||||||
+9
-58
@@ -1,66 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
import "core:strings"
|
|
||||||
import "core:terminal/ansi"
|
import "core:terminal/ansi"
|
||||||
|
|
||||||
Color_Code :: enum {
|
COLOR_HEADINGS ::
|
||||||
Heading,
|
ansi.CSI + ansi.FG_BRIGHT_GREEN + ";" + ansi.BOLD + ";" + ansi.UNDERLINE + ansi.SGR
|
||||||
Command,
|
|
||||||
// Example
|
|
||||||
Flag,
|
|
||||||
Table_Heading,
|
|
||||||
Message,
|
|
||||||
Sucess,
|
|
||||||
Caret,
|
|
||||||
Option_Label,
|
|
||||||
}
|
|
||||||
|
|
||||||
// COLOR_EXAMPLE :: ansi.CSI + ansi.ITALIC + ansi.SGR
|
COLOR_COMMANDS :: ansi.CSI + ansi.FG_BRIGHT_CYAN + ";" + ansi.BOLD + ansi.SGR
|
||||||
|
|
||||||
|
COLOR_EXAMPLE :: ansi.CSI + ansi.ITALIC + ansi.SGR
|
||||||
|
|
||||||
|
COLOR_FLAGS :: ansi.CSI + ansi.BOLD + ";" + ansi.FG_BRIGHT_WHITE + ansi.SGR
|
||||||
|
|
||||||
|
COLOR_TABLE_HEADING :: ansi.CSI + ansi.FG_BRIGHT_GREEN + ansi.SGR
|
||||||
|
|
||||||
@(private = "file")
|
|
||||||
ANSI_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
ANSI_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
|
||||||
|
|
||||||
disable_color := false
|
|
||||||
|
|
||||||
colorize :: proc(
|
|
||||||
color: Color_Code,
|
|
||||||
text: string,
|
|
||||||
allocator := context.temp_allocator,
|
|
||||||
disable := disable_color,
|
|
||||||
) -> (
|
|
||||||
string,
|
|
||||||
runtime.Allocator_Error,
|
|
||||||
) #optional_allocator_error {
|
|
||||||
if disable {
|
|
||||||
return text, nil
|
|
||||||
} else {
|
|
||||||
return strings.concatenate(
|
|
||||||
{ansi.CSI, color_code(color), ansi.SGR, text, ANSI_RESET},
|
|
||||||
allocator,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@(private = "file")
|
|
||||||
color_code :: proc(code: Color_Code) -> string {
|
|
||||||
switch code {
|
|
||||||
case .Heading:
|
|
||||||
return ansi.BOLD + ";" + ansi.UNDERLINE + ";" + ansi.FG_BRIGHT_GREEN
|
|
||||||
case .Command:
|
|
||||||
return ansi.BOLD + ";" + ansi.FG_BRIGHT_CYAN
|
|
||||||
case .Flag:
|
|
||||||
return ansi.BOLD + ";" + ansi.FG_BRIGHT_WHITE
|
|
||||||
case .Table_Heading:
|
|
||||||
return ansi.FG_BRIGHT_GREEN
|
|
||||||
case .Message:
|
|
||||||
return ansi.FAINT
|
|
||||||
case .Sucess, .Caret:
|
|
||||||
return ansi.BOLD + ";" + ansi.FG_GREEN
|
|
||||||
case .Option_Label:
|
|
||||||
return ansi.BOLD + ";" + ansi.FG_CYAN
|
|
||||||
case:
|
|
||||||
panic("Unknown case")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ db_close :: proc(db: ^Db) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sz: i64
|
sz: i64
|
||||||
data := sqlite.serialize(db.conn, "main", &sz, {})
|
data := sqlite.serialize(db.conn, "main", &sz, 0)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
fmt.eprintln("Error: failed to serialize database")
|
fmt.eprintln("Error: failed to serialize database")
|
||||||
return
|
return
|
||||||
|
|||||||
+1
-1
@@ -196,7 +196,7 @@ test_db_serialize :: proc(t: ^testing.T) {
|
|||||||
db_insert(&db, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
sz: i64
|
sz: i64
|
||||||
data := sqlite.serialize(db.conn, "main", &sz, {})
|
data := sqlite.serialize(db.conn, "main", &sz, 0)
|
||||||
testing.expect(t, data != nil, "serialize should return non-nil")
|
testing.expect(t, data != nil, "serialize should return non-nil")
|
||||||
if data == nil do return
|
if data == nil do return
|
||||||
defer sqlite.free(data)
|
defer sqlite.free(data)
|
||||||
|
|||||||
-134
@@ -1,134 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "base:runtime"
|
|
||||||
import "core:reflect"
|
|
||||||
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) {
|
|
||||||
field_count := reflect.struct_field_count(T)
|
|
||||||
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)
|
|
||||||
|
|
||||||
for i in 0..<field_count {
|
|
||||||
field := reflect.struct_field_at(T, i)
|
|
||||||
|
|
||||||
name, _ := strings.replace(field.name, "_", "-", -1, context.temp_allocator)
|
|
||||||
args_tag := reflect.struct_tag_get(field.tag, "args")
|
|
||||||
if n, ok := get_subtag(args_tag, "name"); ok {
|
|
||||||
name = n
|
|
||||||
}
|
|
||||||
long_map[name] = field
|
|
||||||
|
|
||||||
if s, ok := get_subtag(args_tag, "short"); ok {
|
|
||||||
short_map[s] = field
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
overflow_dyn := make([dynamic]string, 0, len(args), context.temp_allocator)
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for i < len(args) {
|
|
||||||
arg := args[i]
|
|
||||||
|
|
||||||
if strings.starts_with(arg, "--") {
|
|
||||||
key := arg[2:]
|
|
||||||
value := ""
|
|
||||||
has_value := false
|
|
||||||
|
|
||||||
if eq_idx := strings.index(key, "="); eq_idx >= 0 {
|
|
||||||
value = key[eq_idx + 1:]
|
|
||||||
key = key[:eq_idx]
|
|
||||||
has_value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if field, ok := long_map[key]; ok {
|
|
||||||
if is_bool_type(field) {
|
|
||||||
set_field(model, field, "")
|
|
||||||
i += 1
|
|
||||||
} else if has_value {
|
|
||||||
set_field(model, field, value)
|
|
||||||
i += 1
|
|
||||||
} else if i + 1 < len(args) && !strings.starts_with(args[i + 1], "-") {
|
|
||||||
set_field(model, field, args[i + 1])
|
|
||||||
i += 2
|
|
||||||
} else {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
} else if strings.starts_with(arg, "-") && len(arg) == 2 {
|
|
||||||
short := arg[1:2]
|
|
||||||
if field, ok := short_map[short]; ok {
|
|
||||||
if is_bool_type(field) {
|
|
||||||
set_field(model, field, "")
|
|
||||||
i += 1
|
|
||||||
} else if i + 1 < len(args) && !strings.starts_with(args[i + 1], "-") {
|
|
||||||
set_field(model, field, args[i + 1])
|
|
||||||
i += 2
|
|
||||||
} else {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
append(&overflow_dyn, arg)
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return overflow_dyn[:]
|
|
||||||
}
|
|
||||||
@@ -75,8 +75,6 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
pkgs.git
|
|
||||||
|
|
||||||
pkgs.libsodium
|
pkgs.libsodium
|
||||||
mysqlite
|
mysqlite
|
||||||
];
|
];
|
||||||
|
|||||||
+5
-19
@@ -66,17 +66,11 @@ multi_select :: proc(
|
|||||||
case .Space:
|
case .Space:
|
||||||
selected[cursor] = !selected[cursor]
|
selected[cursor] = !selected[cursor]
|
||||||
case .Enter:
|
case .Enter:
|
||||||
fmt.printf(
|
fmt.printf(ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.ED + ansi.CSI + ansi.DECTCEM_SHOW, visible + 1)
|
||||||
ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.ED + ansi.CSI + ansi.DECTCEM_SHOW,
|
|
||||||
visible + 1,
|
|
||||||
)
|
|
||||||
result = .Confirm
|
result = .Confirm
|
||||||
return
|
return
|
||||||
case .Escape:
|
case .Escape:
|
||||||
fmt.printf(
|
fmt.printf(ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.ED + ansi.CSI + ansi.DECTCEM_SHOW, visible + 1)
|
||||||
ansi.CSI + "%d" + ansi.CUU + ansi.CSI + ansi.ED + ansi.CSI + ansi.DECTCEM_SHOW,
|
|
||||||
visible + 1,
|
|
||||||
)
|
|
||||||
result = .Cancel
|
result = .Cancel
|
||||||
return
|
return
|
||||||
case .Unknown:
|
case .Unknown:
|
||||||
@@ -95,10 +89,7 @@ render_options :: proc(
|
|||||||
cursor: int,
|
cursor: int,
|
||||||
scroll_offset: int,
|
scroll_offset: int,
|
||||||
) -> int {
|
) -> int {
|
||||||
fmt.printf(
|
fmt.printf(ansi.CSI + ansi.BOLD + ";" + ansi.FG_CYAN + ansi.SGR + "%s" + ANSI_RESET + " (↑/↓ move, space select, enter confirm)\r\n", prompt)
|
||||||
"%s (↑/↓ move, space select, enter confirm)\r\n",
|
|
||||||
colorize(.Option_Label, prompt),
|
|
||||||
)
|
|
||||||
|
|
||||||
end := scroll_offset + MAX_VISIBLE
|
end := scroll_offset + MAX_VISIBLE
|
||||||
if end > len(options) {
|
if end > len(options) {
|
||||||
@@ -111,14 +102,9 @@ render_options :: proc(
|
|||||||
checkbox = "x"
|
checkbox = "x"
|
||||||
}
|
}
|
||||||
if i == cursor {
|
if i == cursor {
|
||||||
fmt.printf(
|
fmt.printf(ansi.CSI + ansi.BOLD + ";" + ansi.FG_GREEN + ansi.SGR + "> " + ANSI_RESET + "[" + ansi.CSI + ansi.FG_GREEN + ansi.SGR + "%s" + ANSI_RESET + "] %s\r\n", checkbox, options[i])
|
||||||
"%s [%s] %s\r\n",
|
|
||||||
colorize(.Caret, ">"),
|
|
||||||
colorize(.Sucess, checkbox),
|
|
||||||
options[i],
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
fmt.printf(" [%s] %s\r\n", colorize(.Sucess, checkbox), options[i])
|
fmt.printf(" [" + ansi.CSI + ansi.FAINT + ansi.SGR + "%s" + ANSI_RESET + "] %s\r\n", checkbox, options[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ write_borderless_table :: proc(w: io.Writer, t: ^table.Table) {
|
|||||||
if t.caption != "" {
|
if t.caption != "" {
|
||||||
table.write_text_align(
|
table.write_text_align(
|
||||||
w,
|
w,
|
||||||
colorize(.Heading, t.caption),
|
fmt.tprintf("%s%s%s", COLOR_HEADINGS, t.caption, ANSI_RESET),
|
||||||
.Left,
|
.Left,
|
||||||
0, //t.lpad,
|
0, //t.lpad,
|
||||||
0, //t.rpad,
|
0, //t.rpad,
|
||||||
|
|||||||
+2
-10
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "core:strings"
|
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -17,7 +16,7 @@ test_ansi_aware_width_empty :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_ansi_aware_width_with_color_codes :: proc(t: ^testing.T) {
|
test_ansi_aware_width_with_color_codes :: proc(t: ^testing.T) {
|
||||||
colored := colorize(.Heading, "Directory", disable = false)
|
colored := COLOR_TABLE_HEADING + "Directory" + ANSI_RESET
|
||||||
testing.expect_value(t, ansi_aware_width(colored), 9)
|
testing.expect_value(t, ansi_aware_width(colored), 9)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,13 +28,6 @@ test_ansi_aware_width_multibyte :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_ansi_aware_width_multiple_escape_sequences :: proc(t: ^testing.T) {
|
test_ansi_aware_width_multiple_escape_sequences :: proc(t: ^testing.T) {
|
||||||
colored := strings.concatenate(
|
colored := COLOR_TABLE_HEADING + "a" + ANSI_RESET + "b" + COLOR_TABLE_HEADING + "c" + ANSI_RESET
|
||||||
{
|
|
||||||
colorize(.Heading, "a", disable = false),
|
|
||||||
colorize(.Heading, "b", disable = false),
|
|
||||||
colorize(.Heading, "c", disable = false),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
testing.expect_value(t, ansi_aware_width(colored), 3)
|
testing.expect_value(t, ansi_aware_width(colored), 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
0.4.0
|
0.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user