mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
Compare commits
4 Commits
e74fc4f35a
...
581967a58d
| Author | SHA1 | Date | |
|---|---|---|---|
| 581967a58d | |||
| 6ec09309dd | |||
| c5020bd6a6 | |||
| d5981d7b88 |
28
TODOS.md
28
TODOS.md
@@ -1,32 +1,32 @@
|
|||||||
# TODOs
|
# TODOs
|
||||||
|
|
||||||
1. Commands are still leaking. (Write tests for everything first)
|
1. Bring back windows support / cross-compilation.
|
||||||
|
|
||||||
2. Add color flag and support non colored output.
|
2. Commands are still leaking. (Write tests for everything first)
|
||||||
|
|
||||||
3. Rewrite `write_command_help` to use text/tables
|
3. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
||||||
|
|
||||||
4. Generate md and man pages again.
|
4. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
||||||
|
|
||||||
5. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
5. Test all cmds / terminal branches.
|
||||||
|
|
||||||
6. Add a text filter to the multi_select.
|
6. Generate md and man pages again.
|
||||||
|
|
||||||
7. Add tests for untested commands.
|
7. Shell completion
|
||||||
|
|
||||||
8. add --format -f flag to commands that draw tables.
|
8. Add tests for untested commands.
|
||||||
|
|
||||||
9. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
9. Update `read_wire_string` to use a slice.
|
||||||
|
|
||||||
10. Shell completion
|
10. Pass allocator to findr?
|
||||||
|
|
||||||
11. Bring back windows support / cross-compilation.
|
11. Smarter flag parsing?
|
||||||
|
|
||||||
12. Test all cmds / terminal branches.
|
12. Rewrite `write_command_help` to use text/tables
|
||||||
|
|
||||||
13. Pass allocator to findr?
|
13. Add color flag and support non colored output.
|
||||||
|
|
||||||
14. Update `read_wire_string` to use a slice.
|
14. Add a text filter to the multi_select.
|
||||||
|
|
||||||
## Double-check AI output
|
## Double-check AI output
|
||||||
|
|
||||||
|
|||||||
29
cli.odin
29
cli.odin
@@ -5,6 +5,7 @@ import "core:fmt"
|
|||||||
import "core:io"
|
import "core:io"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
import "core:terminal"
|
||||||
import "core:text/table"
|
import "core:text/table"
|
||||||
|
|
||||||
Command :: struct {
|
Command :: struct {
|
||||||
@@ -18,6 +19,11 @@ Command :: struct {
|
|||||||
err: io.Writer,
|
err: io.Writer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Output_Format :: enum {
|
||||||
|
Table,
|
||||||
|
JSON,
|
||||||
|
}
|
||||||
|
|
||||||
CommandInfo :: struct {
|
CommandInfo :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
usage: string,
|
usage: string,
|
||||||
@@ -117,7 +123,7 @@ parse_args :: proc(args: []string, out: io.Stream, err: io.Stream) -> (cmd: Comm
|
|||||||
cmd.config_path = default_config_path(home, context.temp_allocator)
|
cmd.config_path = default_config_path(home, context.temp_allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_flag(&cmd, "help") {
|
if has_flag(&cmd, "help") || has_flag(&cmd, "h") {
|
||||||
print_command_help(&cmd)
|
print_command_help(&cmd)
|
||||||
return cmd, false
|
return cmd, false
|
||||||
}
|
}
|
||||||
@@ -288,6 +294,11 @@ at before, restore your backup with:
|
|||||||
COLOR_FLAGS + "-c, --config-file" + ANSI_RESET + " <path>",
|
COLOR_FLAGS + "-c, --config-file" + ANSI_RESET + " <path>",
|
||||||
`config file (default "~/.envr/config.json")`,
|
`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)
|
||||||
|
|
||||||
fmt.wprintf(
|
fmt.wprintf(
|
||||||
@@ -303,6 +314,22 @@ has_flag :: proc(cmd: ^Command, name: string) -> bool {
|
|||||||
return name in cmd.flags || name in cmd.bool_set
|
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)
|
||||||
|
|||||||
@@ -361,3 +361,43 @@ test_parse_args_config_file_defaults :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
test_get_format_long_json :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--format", "json"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
testing.expect_value(t, get_format(&cmd), Output_Format.JSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_get_format_short_json :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-f", "json"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
testing.expect_value(t, get_format(&cmd), Output_Format.JSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_get_format_long_table :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "--format", "table"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
testing.expect_value(t, get_format(&cmd), Output_Format.Table)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_get_format_short_table :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok, _, _ := test_parse_args([]string{"envr", "list", "-f", "table"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
testing.expect_value(t, get_format(&cmd), Output_Format.Table)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import "core:fmt"
|
|||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:path/filepath"
|
import "core:path/filepath"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:terminal"
|
|
||||||
import "core:text/table"
|
import "core:text/table"
|
||||||
|
|
||||||
ListEntry :: struct {
|
ListEntry :: struct {
|
||||||
@@ -13,7 +12,6 @@ ListEntry :: struct {
|
|||||||
path: string `json:"path"`,
|
path: string `json:"path"`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support --format flag
|
|
||||||
// TODO: Improve table rendering
|
// TODO: Improve table rendering
|
||||||
cmd_list :: proc(cmd: ^Command) {
|
cmd_list :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open(cmd.config_path)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
@@ -27,7 +25,7 @@ cmd_list :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if terminal.is_terminal(os.stdout) {
|
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)
|
||||||
@@ -51,7 +49,7 @@ cmd_list :: proc(cmd: ^Command) {
|
|||||||
table.write_decorated_table(cmd.out, &t, decorations, ansi_aware_width)
|
table.write_decorated_table(cmd.out, &t, decorations, ansi_aware_width)
|
||||||
} else {
|
} else {
|
||||||
// TODO: Should we instead print full entries here?
|
// TODO: Should we instead print full entries here?
|
||||||
entries: [dynamic]ListEntry
|
entries := make([dynamic]ListEntry, 0, len(rows), context.temp_allocator)
|
||||||
for row in rows {
|
for row in rows {
|
||||||
filename := filepath.base(row.path)
|
filename := filepath.base(row.path)
|
||||||
append(
|
append(
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
#+feature dynamic-literals
|
||||||
#+test
|
#+test
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "core:bufio"
|
||||||
|
import "core:os"
|
||||||
import "core:path/filepath"
|
import "core:path/filepath"
|
||||||
|
import "core:strings"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
@@ -17,3 +21,95 @@ test_filepath_base_equals_rel :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_cmd_list_format_json :: proc(t: ^testing.T) {
|
||||||
|
base := test_temp_dir(t, "envr-test-list-json-*")
|
||||||
|
defer os.remove_all(base)
|
||||||
|
|
||||||
|
cfg_path, _ := filepath.join([]string{base, "config.json"}, context.temp_allocator)
|
||||||
|
cfg := new_config([]string{"fixtures/keys/insecure-test-key"}, cfg_path)
|
||||||
|
testing.expect(t, save_config(cfg, force = true), "save should succeed")
|
||||||
|
delete_config(&cfg)
|
||||||
|
|
||||||
|
db, db_ok := db_open(cfg_path)
|
||||||
|
testing.expect(t, db_ok, "db should open")
|
||||||
|
if !db_ok do return
|
||||||
|
f := make_test_env_file("/project/.env", "abc123", "SECRET=value")
|
||||||
|
defer delete(f.remotes)
|
||||||
|
testing.expect(t, db_insert(&db, f), "insert should succeed")
|
||||||
|
db_close(&db)
|
||||||
|
|
||||||
|
out_b: strings.Builder
|
||||||
|
strings.builder_init(&out_b)
|
||||||
|
defer strings.builder_destroy(&out_b)
|
||||||
|
err_b: strings.Builder
|
||||||
|
strings.builder_init(&err_b)
|
||||||
|
defer strings.builder_destroy(&err_b)
|
||||||
|
|
||||||
|
cmd, ok := parse_args(
|
||||||
|
[]string{"envr", "list", "--format", "json", "--config-file", cfg_path},
|
||||||
|
strings.to_stream(&out_b),
|
||||||
|
strings.to_stream(&err_b),
|
||||||
|
)
|
||||||
|
testing.expect(t, ok, "parse_args should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
cmd_list(&cmd)
|
||||||
|
bufio.writer_flush(cmd.out_buf)
|
||||||
|
output := strings.to_string(out_b)
|
||||||
|
|
||||||
|
testing.expect(t, strings.contains(output, "["), "json output should contain '['")
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(output, "\"directory\""),
|
||||||
|
"json output should contain directory key",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_cmd_list_format_table :: proc(t: ^testing.T) {
|
||||||
|
base := test_temp_dir(t, "envr-test-list-table-*")
|
||||||
|
defer os.remove_all(base)
|
||||||
|
|
||||||
|
cfg_path, _ := filepath.join([]string{base, "config.json"}, context.temp_allocator)
|
||||||
|
cfg := new_config([]string{"fixtures/keys/insecure-test-key"}, cfg_path)
|
||||||
|
testing.expect(t, save_config(cfg, force = true), "save should succeed")
|
||||||
|
delete_config(&cfg)
|
||||||
|
|
||||||
|
db, db_ok := db_open(cfg_path)
|
||||||
|
testing.expect(t, db_ok, "db should open")
|
||||||
|
if !db_ok do return
|
||||||
|
f := make_test_env_file("/project/.env", "abc123", "SECRET=value")
|
||||||
|
defer delete(f.remotes)
|
||||||
|
testing.expect(t, db_insert(&db, f), "insert should succeed")
|
||||||
|
db_close(&db)
|
||||||
|
|
||||||
|
out_b: strings.Builder
|
||||||
|
strings.builder_init(&out_b)
|
||||||
|
defer strings.builder_destroy(&out_b)
|
||||||
|
err_b: strings.Builder
|
||||||
|
strings.builder_init(&err_b)
|
||||||
|
defer strings.builder_destroy(&err_b)
|
||||||
|
|
||||||
|
cmd, ok := parse_args(
|
||||||
|
[]string{"envr", "list", "--format", "table", "--config-file", cfg_path},
|
||||||
|
strings.to_stream(&out_b),
|
||||||
|
strings.to_stream(&err_b),
|
||||||
|
)
|
||||||
|
testing.expect(t, ok, "parse_args should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete_command(&cmd)
|
||||||
|
|
||||||
|
cmd_list(&cmd)
|
||||||
|
bufio.writer_flush(cmd.out_buf)
|
||||||
|
output := strings.to_string(out_b)
|
||||||
|
|
||||||
|
testing.expect(t, strings.contains(output, "│"), "table output should contain border chars")
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(output, "Directory"),
|
||||||
|
"table output should contain Directory header",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package main
|
|||||||
|
|
||||||
import "core:encoding/json"
|
import "core:encoding/json"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:os"
|
|
||||||
import "core:terminal"
|
|
||||||
import "core:text/table"
|
import "core:text/table"
|
||||||
|
|
||||||
SyncEntry :: struct {
|
SyncEntry :: struct {
|
||||||
@@ -12,7 +10,6 @@ SyncEntry :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check for quiet failures.
|
// TODO: Check for quiet failures.
|
||||||
// TODO: Support --format -f flags
|
|
||||||
cmd_sync :: proc(cmd: ^Command) {
|
cmd_sync :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open(cmd.config_path)
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
@@ -49,7 +46,7 @@ cmd_sync :: proc(cmd: ^Command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if terminal.is_terminal(os.stdout) {
|
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)
|
||||||
|
|||||||
2
db.odin
2
db.odin
@@ -172,7 +172,7 @@ db_close :: proc(db: ^Db) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sz: i64
|
sz: i64
|
||||||
data := sqlite.serialize(db.conn, "main", &sz, 0)
|
data := sqlite.serialize(db.conn, "main", &sz, {})
|
||||||
if data == nil {
|
if data == nil {
|
||||||
fmt.eprintln("Error: failed to serialize database")
|
fmt.eprintln("Error: failed to serialize database")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -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, 0)
|
data := sqlite.serialize(db.conn, "main", &sz, {})
|
||||||
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)
|
||||||
|
|||||||
@@ -75,6 +75,8 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
|
pkgs.git
|
||||||
|
|
||||||
pkgs.libsodium
|
pkgs.libsodium
|
||||||
mysqlite
|
mysqlite
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ DESERIALIZE_FLAGS :: bit_set[DESERIALIZE_FLAG]
|
|||||||
DESERIALIZE_FLAG :: enum u32 {
|
DESERIALIZE_FLAG :: enum u32 {
|
||||||
FREEONCLOSE = 1,
|
FREEONCLOSE = 1,
|
||||||
RESIZEABLE = 2,
|
RESIZEABLE = 2,
|
||||||
|
READONLY = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
SERIALIZE_FLAGS :: bit_set[SERIALIZE_FLAG]
|
||||||
|
SERIALIZE_FLAG :: enum u32 {
|
||||||
|
NOCOPY = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
foreign lib {
|
foreign lib {
|
||||||
@@ -43,7 +49,7 @@ foreign lib {
|
|||||||
@(link_name = "sqlite3_changes")
|
@(link_name = "sqlite3_changes")
|
||||||
changes :: proc(db: Db) -> c.int ---
|
changes :: proc(db: Db) -> c.int ---
|
||||||
@(link_name = "sqlite3_serialize")
|
@(link_name = "sqlite3_serialize")
|
||||||
serialize :: proc(db: Db, zSchema: cstring, piSize: ^i64, mFlags: u32) -> [^]u8 ---
|
serialize :: proc(db: Db, zSchema: cstring, piSize: ^i64, mFlags: SERIALIZE_FLAGS) -> [^]u8 ---
|
||||||
@(link_name = "sqlite3_deserialize")
|
@(link_name = "sqlite3_deserialize")
|
||||||
deserialize :: proc(db: Db, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: DESERIALIZE_FLAGS) -> c.int ---
|
deserialize :: proc(db: Db, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: DESERIALIZE_FLAGS) -> c.int ---
|
||||||
@(link_name = "sqlite3_malloc64")
|
@(link_name = "sqlite3_malloc64")
|
||||||
|
|||||||
Reference in New Issue
Block a user