diff options
| author | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2024-06-06 18:35:43 -0400 |
|---|---|---|
| committer | Feoramund <161657516+Feoramund@users.noreply.github.com> | 2024-06-07 13:16:13 -0400 |
| commit | edb685f04be9aef407dc8abac7bb76fb51184f9f (patch) | |
| tree | 63f48c951de7a9d751895a3cd72194e241bd38c0 /core/flags/util.odin | |
| parent | 08612423b9afffb7499d8db2c69715df1627bd50 (diff) | |
Add package `core:flags`
Diffstat (limited to 'core/flags/util.odin')
| -rw-r--r-- | core/flags/util.odin | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/core/flags/util.odin b/core/flags/util.odin new file mode 100644 index 000000000..e4f32eea1 --- /dev/null +++ b/core/flags/util.odin @@ -0,0 +1,130 @@ +package flags + +import "core:fmt" +@require import "core:os" +@require import "core:path/filepath" +import "core:strings" + +/* +Parse any arguments into an annotated struct or exit if there was an error. + +*Allocates Using Provided Allocator* + +This is a convenience wrapper over `parse` and `print_errors`. + +Inputs: +- model: A pointer to an annotated struct. +- program_args: A slice of strings, usually `os.args`. +- style: The argument parsing style. +- allocator: (default: context.allocator) +- loc: The caller location for debugging purposes (default: #caller_location) +*/ +@(optimization_mode="size") +parse_or_exit :: proc( + model: ^$T, + program_args: []string, + style: Parsing_Style = .Odin, + allocator := context.allocator, + loc := #caller_location, +) { + assert(len(program_args) > 0, "Program arguments slice is empty.", loc) + + program := filepath.base(program_args[0]) + args: []string + + if len(program_args) > 1 { + args = program_args[1:] + } + + error := parse(model, args, style) + if error != nil { + stderr := os.stream_from_handle(os.stderr) + + if len(args) == 0 { + // No arguments entered, and there was an error; show the usage, + // specifically on STDERR. + write_usage(stderr, T, program, style) + fmt.wprintln(stderr) + } + + print_errors(T, error, program, style) + + _, was_help_request := error.(Help_Request) + os.exit(0 if was_help_request else 1) + } +} +/* +Print out any errors that may have resulted from parsing. + +All error messages print to STDERR, while usage goes to STDOUT, if requested. + +Inputs: +- data_type: The typeid of the data structure to describe, if usage is requested. +- error: The error returned from `parse`. +- style: The argument parsing style, required to show flags in the proper style, when usage is shown. +*/ +@(optimization_mode="size") +print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) { + stderr := os.stream_from_handle(os.stderr) + stdout := os.stream_from_handle(os.stdout) + + switch specific_error in error { + case Parse_Error: + fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message) + case Open_File_Error: + fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s", + specific_error, + specific_error.errno, + specific_error.perms, + specific_error.mode, + specific_error.filename) + case Validation_Error: + fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message) + case Help_Request: + write_usage(stdout, data_type, program, style) + } +} +/* +Get the value for a subtag. + +This is useful if you need to parse through the `args` tag for a struct field +on a custom type setter or custom flag checker. + +Example: + + import "core:flags" + import "core:fmt" + + subtag_example :: proc() { + args_tag := "precision=3,signed" + + precision, has_precision := flags.get_subtag(args_tag, "precision") + signed, is_signed := flags.get_subtag(args_tag, "signed") + + fmt.printfln("precision = %q, %t", precision, has_precision) + fmt.printfln("signed = %q, %t", signed, is_signed) + } + +Output: + + precision = "3", true + signed = "", true + +*/ +get_subtag :: proc(tag, id: string) -> (value: string, ok: bool) { + // This proc was initially private in `internal_rtti.odin`, but given how + // useful it would be to custom type setters and flag checkers, it lives + // here now. + + tag := tag + + for subtag in strings.split_iterator(&tag, ",") { + if equals := strings.index_byte(subtag, '='); equals != -1 && id == subtag[:equals] { + return subtag[1 + equals:], true + } else if id == subtag { + return "", true + } + } + + return +} |