mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
feat: Added --format, -f flag.
Allows printing data in tabular or json format.
This commit is contained in:
14
TODOS.md
14
TODOS.md
@@ -14,19 +14,19 @@
|
||||
|
||||
7. Add tests for untested commands.
|
||||
|
||||
8. add --format -f flag to commands that draw tables.
|
||||
8. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
||||
|
||||
9. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
||||
9. Shell completion
|
||||
|
||||
10. Shell completion
|
||||
10. Bring back windows support / cross-compilation.
|
||||
|
||||
11. Bring back windows support / cross-compilation.
|
||||
11. Test all cmds / terminal branches.
|
||||
|
||||
12. Test all cmds / terminal branches.
|
||||
12. Pass allocator to findr?
|
||||
|
||||
13. Pass allocator to findr?
|
||||
13. Update `read_wire_string` to use a slice.
|
||||
|
||||
14. Update `read_wire_string` to use a slice.
|
||||
14. `-h` short flag seems to fail, at least with `envr list`
|
||||
|
||||
## Double-check AI output
|
||||
|
||||
|
||||
27
cli.odin
27
cli.odin
@@ -5,6 +5,7 @@ import "core:fmt"
|
||||
import "core:io"
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "core:terminal"
|
||||
import "core:text/table"
|
||||
|
||||
Command :: struct {
|
||||
@@ -18,6 +19,11 @@ Command :: struct {
|
||||
err: io.Writer,
|
||||
}
|
||||
|
||||
Output_Format :: enum {
|
||||
Table,
|
||||
JSON,
|
||||
}
|
||||
|
||||
CommandInfo :: struct {
|
||||
name: string,
|
||||
usage: string,
|
||||
@@ -288,6 +294,11 @@ at before, restore your backup with:
|
||||
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)
|
||||
|
||||
fmt.wprintf(
|
||||
@@ -303,6 +314,22 @@ 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) {
|
||||
bufio.writer_flush(cmd.out_buf)
|
||||
delete(cmd.args)
|
||||
|
||||
@@ -361,3 +361,43 @@ test_parse_args_config_file_defaults :: proc(t: ^testing.T) {
|
||||
}
|
||||
|
||||
@(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:path/filepath"
|
||||
import "core:strings"
|
||||
import "core:terminal"
|
||||
import "core:text/table"
|
||||
|
||||
ListEntry :: struct {
|
||||
@@ -13,7 +12,6 @@ ListEntry :: struct {
|
||||
path: string `json:"path"`,
|
||||
}
|
||||
|
||||
// TODO: Support --format flag
|
||||
// TODO: Improve table rendering
|
||||
cmd_list :: proc(cmd: ^Command) {
|
||||
db, db_ok := db_open(cmd.config_path)
|
||||
@@ -27,7 +25,7 @@ cmd_list :: proc(cmd: ^Command) {
|
||||
return
|
||||
}
|
||||
|
||||
if terminal.is_terminal(os.stdout) {
|
||||
if get_format(cmd) == .Table {
|
||||
t: table.Table
|
||||
table.init(&t, context.temp_allocator, context.temp_allocator)
|
||||
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)
|
||||
} else {
|
||||
// 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 {
|
||||
filename := filepath.base(row.path)
|
||||
append(
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
#+feature dynamic-literals
|
||||
#+test
|
||||
package main
|
||||
|
||||
import "core:bufio"
|
||||
import "core:os"
|
||||
import "core:path/filepath"
|
||||
import "core:strings"
|
||||
import "core:testing"
|
||||
|
||||
@(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:fmt"
|
||||
import "core:os"
|
||||
import "core:terminal"
|
||||
import "core:text/table"
|
||||
|
||||
SyncEntry :: struct {
|
||||
@@ -12,7 +10,6 @@ SyncEntry :: struct {
|
||||
}
|
||||
|
||||
// TODO: Check for quiet failures.
|
||||
// TODO: Support --format -f flags
|
||||
cmd_sync :: proc(cmd: ^Command) {
|
||||
db, db_ok := db_open(cmd.config_path)
|
||||
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
|
||||
table.init(&t, context.temp_allocator, context.temp_allocator)
|
||||
table.padding(&t, 1, 1)
|
||||
|
||||
2
db.odin
2
db.odin
@@ -172,7 +172,7 @@ db_close :: proc(db: ^Db) {
|
||||
}
|
||||
|
||||
sz: i64
|
||||
data := sqlite.serialize(db.conn, "main", &sz, 0)
|
||||
data := sqlite.serialize(db.conn, "main", &sz, {})
|
||||
if data == nil {
|
||||
fmt.eprintln("Error: failed to serialize database")
|
||||
return
|
||||
|
||||
@@ -17,6 +17,12 @@ DESERIALIZE_FLAGS :: bit_set[DESERIALIZE_FLAG]
|
||||
DESERIALIZE_FLAG :: enum u32 {
|
||||
FREEONCLOSE = 1,
|
||||
RESIZEABLE = 2,
|
||||
READONLY = 4,
|
||||
}
|
||||
|
||||
SERIALIZE_FLAGS :: bit_set[SERIALIZE_FLAG]
|
||||
SERIALIZE_FLAG :: enum u32 {
|
||||
NOCOPY = 1,
|
||||
}
|
||||
|
||||
foreign lib {
|
||||
@@ -43,7 +49,7 @@ foreign lib {
|
||||
@(link_name = "sqlite3_changes")
|
||||
changes :: proc(db: Db) -> c.int ---
|
||||
@(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")
|
||||
deserialize :: proc(db: Db, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: DESERIALIZE_FLAGS) -> c.int ---
|
||||
@(link_name = "sqlite3_malloc64")
|
||||
|
||||
Reference in New Issue
Block a user