aboutsummaryrefslogtreecommitdiff
path: root/core/flags/util.odin
blob: 0d18fa19627e0e15a30696cfb5415eb8fd158d2c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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="favor_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, true, true, allocator, loc)
	if error != nil {
		stderr := os.to_stream(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="favor_size")
print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) {
	stderr := os.to_stream(os.stderr)
	stdout := os.to_stream(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 and flags %v: %s",
			specific_error,
			specific_error.errno,
			specific_error.perms,
			specific_error.flags,
			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"

	get_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
}