aboutsummaryrefslogtreecommitdiff
path: root/core/flags/util.odin
diff options
context:
space:
mode:
authorFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-06 18:35:43 -0400
committerFeoramund <161657516+Feoramund@users.noreply.github.com>2024-06-07 13:16:13 -0400
commitedb685f04be9aef407dc8abac7bb76fb51184f9f (patch)
tree63f48c951de7a9d751895a3cd72194e241bd38c0 /core/flags/util.odin
parent08612423b9afffb7499d8db2c69715df1627bd50 (diff)
Add package `core:flags`
Diffstat (limited to 'core/flags/util.odin')
-rw-r--r--core/flags/util.odin130
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
+}