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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
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:
if os.exists(specific_error.filename) {
flags: string
if specific_error.flags == {.Read} {
flags = "read-only"
} else if specific_error.flags == {.Write} {
flags = "write-only"
} else if specific_error.flags == {.Read, .Write} {
flags = "read/write"
}
if flags != "" {
fmt.wprintfln(stderr, "[%T#%i] Unable to open %q with perms 0o%o as %s",
specific_error,
specific_error.errno,
specific_error.filename,
u16(transmute(u32)specific_error.perms),
flags)
} else {
fmt.wprintfln(stderr, "[%T#%i] Unable to open %q with perms 0o%o and flags %v",
specific_error,
specific_error.errno,
specific_error.filename,
u16(transmute(u32)specific_error.perms),
specific_error.flags)
}
} else {
fmt.wprintfln(stderr, "[%T#%i] Unable to open %q. File not found",
specific_error,
specific_error.errno,
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
}
|