Better error handling for list dir and cli

This commit is contained in:
Riyyi
2026-08-09 17:31:53 +02:00
parent 5fcdba2c22
commit af5db96962
4 changed files with 89 additions and 30 deletions
+23 -10
View File
@@ -5,6 +5,8 @@ import "core:fmt"
import "core:os" import "core:os"
import "core:strings" import "core:strings"
// -----------------------------------------
Options :: struct { Options :: struct {
verbose: bool `args:"name=verbose" usage:"Enable verbose output"`, verbose: bool `args:"name=verbose" usage:"Enable verbose output"`,
compression: u16 `args:"name=compression" usage:"Compression level, 0-12"`, compression: u16 `args:"name=compression" usage:"Compression level, 0-12"`,
@@ -14,25 +16,31 @@ Options :: struct {
// overflow: [dynamic]string, // overflow: [dynamic]string,
} }
parse :: proc(working_dir: string) -> Options { Error :: union #shared_nil {
flags.Error,
}
// -----------------------------------------
parse :: proc(working_dir: string) -> (Options, Error) {
opts: Options opts: Options
err := flags.parse(&opts, os.args[1:], .Unix) err := flags.parse(&opts, os.args[1:], .Unix)
if err != nil { if err != nil {
flags.print_errors(typeid_of(Options), err, os.args[0], .Unix) flags.print_errors(typeid_of(Options), err, os.args[0], .Unix)
os.exit(1) return {}, err
} }
verify(opts, working_dir) verify(opts, working_dir)
return opts return opts, nil
} }
@(private) @(private)
verify :: proc(opts: Options, working_dir: string) { verify :: proc(opts: Options, working_dir: string) -> Error {
if opts.compression > 12 { if opts.compression > 12 {
throw_error( return throw_error(
`Invalid compression "%d". Compression higher than allowed maximum.`, `Invalid compression "%d". Compression higher than allowed maximum.`,
opts.compression, opts.compression,
) )
@@ -40,13 +48,16 @@ verify :: proc(opts: Options, working_dir: string) {
input_path := os.name(opts.input) input_path := os.name(opts.input)
if !os.is_dir(input_path) { if !os.is_dir(input_path) {
throw_error(`Invalid input "%v". Should be a directory`, input_path) return throw_error(
`Invalid input "%v". Should be a directory`,
input_path,
)
} }
if opts.output != nil { if opts.output != nil {
output_path := os.name(opts.output) output_path := os.name(opts.output)
if !os.is_dir(output_path) { if !os.is_dir(output_path) {
throw_error( return throw_error(
`Invalid output "%v". Should be a directory`, `Invalid output "%v". Should be a directory`,
output_path, output_path,
) )
@@ -54,18 +65,20 @@ verify :: proc(opts: Options, working_dir: string) {
} }
if !strings.has_prefix(input_path, working_dir) { if !strings.has_prefix(input_path, working_dir) {
throw_error( return throw_error(
`Invalid input "%v". Should be a subdirectory of the working directory.`, `Invalid input "%v". Should be a subdirectory of the working directory.`,
input_path, input_path,
) )
} }
return nil
} }
@(private) @(private)
throw_error :: proc(fmt_str: string, args: ..any) { throw_error :: proc(fmt_str: string, args: ..any) -> Error {
err := flags.Validation_Error { err := flags.Validation_Error {
message = fmt.tprintf(fmt_str, ..args), message = fmt.tprintf(fmt_str, ..args),
} }
flags.print_errors(typeid_of(Options), err, os.args[0], .Unix) flags.print_errors(typeid_of(Options), err, os.args[0], .Unix)
os.exit(1) return err
} }
+17 -3
View File
@@ -5,12 +5,26 @@ import "core:os"
// ----------------------------------------- // -----------------------------------------
get_working_dir :: proc(allocator := context.allocator) -> string { Error :: union #shared_nil {
os.Error,
File_Error,
}
File_Error :: enum u8 {
None = 0,
Not_A_Directory = 1,
Unimplemented = 127,
Okay = None,
}
// -----------------------------------------
get_working_dir :: proc(allocator := context.allocator) -> (string, Error) {
wd, err := os.get_working_directory(allocator) wd, err := os.get_working_directory(allocator)
if err != nil { if err != nil {
fmt.eprintln("error: get working directory failed:", err) fmt.eprintln("error: get working directory failed:", err)
os.exit(1) return {}, err
} }
return wd return wd, nil
} }
+31 -10
View File
@@ -19,9 +19,17 @@ list_dir_recursive :: proc(
f: ^os.File, f: ^os.File,
working_dir: string, working_dir: string,
allocator := context.allocator, allocator := context.allocator,
) -> [dynamic]File_Entry { ) -> (
entries: [dynamic]File_Entry,
err: Error,
) {
path := os.name(f) path := os.name(f)
return list_dir_recursive_by_path_impl(path, working_dir, allocator) result := list_dir_recursive_by_path_impl(
path,
working_dir,
allocator,
) or_return
return result, nil
} }
// Returning `[dynamic]File_Entry` and each relpath `string` inside it are owned by the caller. // Returning `[dynamic]File_Entry` and each relpath `string` inside it are owned by the caller.
@@ -29,8 +37,16 @@ list_dir_recursive_by_path :: proc(
path: string, path: string,
working_dir: string, working_dir: string,
allocator := context.allocator, allocator := context.allocator,
) -> [dynamic]File_Entry { ) -> (
return list_dir_recursive_by_path_impl(path, working_dir, allocator) entries: [dynamic]File_Entry,
err: Error,
) {
result := list_dir_recursive_by_path_impl(
path,
working_dir,
allocator,
) or_return
return result, nil
} }
// Frees every owning relpath string then the dynamic array itself. // Frees every owning relpath string then the dynamic array itself.
@@ -46,10 +62,13 @@ list_dir_recursive_by_path_impl :: proc(
path: string, path: string,
working_dir: string, working_dir: string,
allocator: runtime.Allocator, allocator: runtime.Allocator,
) -> [dynamic]File_Entry { ) -> (
[dynamic]File_Entry,
Error,
) {
if !os.is_dir(path) { if !os.is_dir(path) {
fmt.eprintln("error: path is not a directory:", path) fmt.eprintln("error: path is not a directory:", path)
os.exit(1) return {}, .Not_A_Directory
} }
result := make([dynamic]File_Entry, allocator) result := make([dynamic]File_Entry, allocator)
@@ -62,7 +81,7 @@ list_dir_recursive_by_path_impl :: proc(
list_dir_queue(&queue, &result, working_dir, allocator) list_dir_queue(&queue, &result, working_dir, allocator)
} }
return result // owning [dynamic] return result, nil // owning [dynamic]
} }
@(private) @(private)
@@ -71,7 +90,7 @@ list_dir_queue :: proc(
result: ^[dynamic]File_Entry, result: ^[dynamic]File_Entry,
working_dir: string, working_dir: string,
allocator: runtime.Allocator, allocator: runtime.Allocator,
) { ) -> Error {
assert(len(queue) > 0, "queue must not be empty") assert(len(queue) > 0, "queue must not be empty")
path := pop(queue) path := pop(queue)
@@ -80,14 +99,14 @@ list_dir_queue :: proc(
f, open_err := os.open(path, {.Read}) f, open_err := os.open(path, {.Read})
if open_err != nil { if open_err != nil {
fmt.eprintln("error: open failed:", open_err) fmt.eprintln("error: open failed:", open_err)
os.exit(1) return open_err
} }
defer os.close(f) defer os.close(f)
entries, read_err := os.read_all_directory(f, allocator) entries, read_err := os.read_all_directory(f, allocator)
if read_err != nil { if read_err != nil {
fmt.eprintln("error: read directory failed:", read_err) fmt.eprintln("error: read directory failed:", read_err)
os.exit(1) return read_err
} }
defer os.file_info_slice_delete(entries, allocator) defer os.file_info_slice_delete(entries, allocator)
@@ -118,4 +137,6 @@ list_dir_queue :: proc(
case: // skip other types case: // skip other types
} }
} }
return nil
} }
+18 -7
View File
@@ -8,39 +8,50 @@ import "src:file"
VERSION :: #config(VERSION, "dev") VERSION :: #config(VERSION, "dev")
CLI_ERR :: 1
WORKING_DIR_ERR :: 2
LIST_DIR_ERR :: 3
WRITE_ASSETS_ERR :: 4
WRITE_METADATA_ERR :: 5
// -----------------------------------------
main :: proc() { main :: proc() {
working_dir := file.get_working_dir() working_dir, wd_err := file.get_working_dir()
if wd_err != nil do os.exit(WORKING_DIR_ERR)
defer delete(working_dir) defer delete(working_dir)
opts := cli.parse(working_dir) opts, c_err := cli.parse(working_dir)
if c_err != nil do os.exit(CLI_ERR)
// fmt.println("verbose:", opts.verbose) // fmt.println("verbose:", opts.verbose)
// fmt.println("compression:", opts.compression) // fmt.println("compression:", opts.compression)
// fmt.println("size:", opts.size) // fmt.println("size:", opts.size)
entries := file.list_dir_recursive(opts.input, working_dir) entries, e_err := file.list_dir_recursive(opts.input, working_dir)
if e_err != nil do os.exit(LIST_DIR_ERR)
defer file.delete_entries(&entries) defer file.delete_entries(&entries)
w := chunks.writer_init(cast(u64)len(entries)) w := chunks.writer_init(cast(u64)len(entries))
defer chunks.writer_destroy(&w) defer chunks.writer_destroy(&w)
err := chunks.write_assets( wa_err := chunks.write_assets(
&w, &w,
opts.output, opts.output,
entries[:], entries[:],
opts.size, opts.size,
opts.compression, opts.compression,
) )
if err != nil do os.exit(1) if wa_err != nil do os.exit(WRITE_ASSETS_ERR)
chunks.write_metadata( wm_err := chunks.write_metadata(
&w, &w,
opts.output, opts.output,
entries[:], entries[:],
opts.size, opts.size,
opts.compression, opts.compression,
) )
if err != nil do os.exit(2) if wm_err != nil do os.exit(WRITE_METADATA_ERR)
} }
// TODO: .chunkignore // TODO: .chunkignore