Merge pull request #5304 from Feoramund/clarify-flags-variadic

Clarify `core:flags` variadic behaviors
This commit is contained in:
Jeroen van Rijn
2025-06-09 19:08:53 +02:00
committed by GitHub
10 changed files with 215 additions and 94 deletions
+2 -2
View File
@@ -19,7 +19,7 @@ SUBTAG_NAME :: "name"
SUBTAG_POS :: "pos" SUBTAG_POS :: "pos"
SUBTAG_REQUIRED :: "required" SUBTAG_REQUIRED :: "required"
SUBTAG_HIDDEN :: "hidden" SUBTAG_HIDDEN :: "hidden"
SUBTAG_VARIADIC :: "variadic" SUBTAG_MANIFOLD :: "manifold"
SUBTAG_FILE :: "file" SUBTAG_FILE :: "file"
SUBTAG_PERMS :: "perms" SUBTAG_PERMS :: "perms"
SUBTAG_INDISTINCT :: "indistinct" SUBTAG_INDISTINCT :: "indistinct"
@@ -28,7 +28,7 @@ TAG_USAGE :: "usage"
UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>" UNDOCUMENTED_FLAG :: "<This flag has not been documented yet.>"
INTERNAL_VARIADIC_FLAG :: "varg" INTERNAL_OVERFLOW_FLAG :: #config(ODIN_CORE_FLAGS_OVERFLOW_FLAG, "overflow")
RESERVED_HELP_FLAG :: "help" RESERVED_HELP_FLAG :: "help"
RESERVED_HELP_FLAG_SHORT :: "h" RESERVED_HELP_FLAG_SHORT :: "h"
+16 -4
View File
@@ -20,6 +20,17 @@ The format is similar to the Odin binary's way of handling compiler flags.
-<map>:<key>=<value> set map[key] to value -<map>:<key>=<value> set map[key] to value
Unhandled Arguments:
All unhandled positional arguments are placed into the `overflow` field on a
struct, if it exists. In UNIX-style parsing, the existence of a `--` on the
command line will also pass all arguments afterwards into this field.
If desired, the name of the field may be changed from `overflow` to any string
by setting the `ODIN_CORE_FLAGS_OVERFLOW_FLAG` compile-time config option with
`-define:ODIN_CORE_FLAGS_OVERFLOW_FLAG=<name>`.
Struct Tags: Struct Tags:
Users of the `core:encoding/json` package may be familiar with using tags to Users of the `core:encoding/json` package may be familiar with using tags to
@@ -32,7 +43,7 @@ Under the `args` tag, there are the following subtags:
- `pos=N`: place positional argument `N` into this flag. - `pos=N`: place positional argument `N` into this flag.
- `hidden`: hide this flag from the usage documentation. - `hidden`: hide this flag from the usage documentation.
- `required`: cause verification to fail if this argument is not set. - `required`: cause verification to fail if this argument is not set.
- `variadic`: take all remaining arguments when set, UNIX-style only. - `manifold=N`: take several arguments at once, UNIX-style only.
- `file`: for `os.Handle` types, file open mode. - `file`: for `os.Handle` types, file open mode.
- `perms`: for `os.Handle` types, file open permissions. - `perms`: for `os.Handle` types, file open permissions.
- `indistinct`: allow the setting of distinct types by their base type. - `indistinct`: allow the setting of distinct types by their base type.
@@ -47,8 +58,9 @@ you want to require 3 and only 3 arguments in a dynamic array, you would
specify `required=3<4`. specify `required=3<4`.
`variadic` may be given a number (`variadic=N`) above 1 to limit how many extra `manifold` may be given a number (`manifold=N`) above 1 to limit how many extra
arguments it consumes. arguments it consumes at once. If this number is not specified, it will take as
many arguments as can be converted to the underlying element type.
`file` determines the file open mode for an `os.Handle`. `file` determines the file open mode for an `os.Handle`.
@@ -160,7 +172,7 @@ at parse time.
--flag --flag
--flag=argument --flag=argument
--flag argument --flag argument
--flag argument repeating-argument --flag argument (manifold-argument)
`-flag` may also be substituted for `--flag`. `-flag` may also be substituted for `--flag`.
+1 -1
View File
@@ -4,7 +4,7 @@ import "core:os"
Parse_Error_Reason :: enum { Parse_Error_Reason :: enum {
None, None,
// An extra positional argument was given, and there is no `varg` field. // An extra positional argument was given, and there is no `overflow` field.
Extra_Positional, Extra_Positional,
// The underlying type does not support the string value it is being set to. // The underlying type does not support the string value it is being set to.
Bad_Value, Bad_Value,
+3 -3
View File
@@ -107,14 +107,14 @@ main :: proc() {
// assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`, // assignments: map[string]u8 `args:"name=assign" usage:"Number of jobs per worker."`,
// (Variadic) Only available in UNIX style: // (Manifold) Only available in UNIX style:
// bots: [dynamic]string `args:"variadic=2,required"`, // bots: [dynamic]string `args:"manifold=2,required"`,
verbose: bool `usage:"Show verbose output."`, verbose: bool `usage:"Show verbose output."`,
debug: bool `args:"hidden" usage:"print debug info"`, debug: bool `args:"hidden" usage:"print debug info"`,
varg: [dynamic]string `usage:"Any extra arguments go here."`, overflow: [dynamic]string `usage:"Any extra arguments go here."`,
} }
opt: Options opt: Options
+4 -4
View File
@@ -33,9 +33,9 @@ push_positional :: #force_no_inline proc (model: ^$T, parser: ^Parser, arg: stri
field, index, has_pos_assigned := get_field_by_pos(model, pos) field, index, has_pos_assigned := get_field_by_pos(model, pos)
if !has_pos_assigned { if !has_pos_assigned {
when intrinsics.type_has_field(T, INTERNAL_VARIADIC_FLAG) { when intrinsics.type_has_field(T, INTERNAL_OVERFLOW_FLAG) {
// Add it to the fallback array. // Add it to the fallback array.
field = reflect.struct_field_by_name(T, INTERNAL_VARIADIC_FLAG) field = reflect.struct_field_by_name(T, INTERNAL_OVERFLOW_FLAG)
} else { } else {
return Parse_Error { return Parse_Error {
.Extra_Positional, .Extra_Positional,
@@ -117,8 +117,8 @@ set_unix_flag :: proc(model: ^$T, parser: ^Parser, name: string) -> (future_args
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
future_args = 1 future_args = 1
if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok { if tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
if length, is_variadic := get_struct_subtag(tag, SUBTAG_VARIADIC); is_variadic { if length, is_manifold := get_struct_subtag(tag, SUBTAG_MANIFOLD); is_manifold {
// Variadic arrays may specify how many arguments they consume at once. // Manifold arrays may specify how many arguments they consume at once.
// Otherwise, they take everything that's left. // Otherwise, they take everything that's left.
if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok { if value, value_ok := strconv.parse_u64_of_base(length, 10); value_ok {
future_args = cast(int)value future_args = cast(int)value
+1 -1
View File
@@ -95,7 +95,7 @@ parse_one_unix_arg :: proc(model: ^$T, parser: ^Parser, arg: string) -> (
// `--`, and only `--`. // `--`, and only `--`.
// Everything from now on will be treated as an argument. // Everything from now on will be treated as an argument.
future_args = max(int) future_args = max(int)
current_flag = INTERNAL_VARIADIC_FLAG current_flag = INTERNAL_OVERFLOW_FLAG
return return
} }
} }
+13 -8
View File
@@ -59,7 +59,8 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
} }
} }
if pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS); has_pos { pos_str, has_pos := get_struct_subtag(args_tag, SUBTAG_POS)
if has_pos {
#partial switch specific_type_info in field.type.variant { #partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Map: case runtime.Type_Info_Map:
fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.", fmt.panicf("%T.%s has `%s` defined, and this does not make sense on a map type.",
@@ -79,7 +80,7 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.", fmt.assertf(!reflect.is_boolean(field.type), "%T.%s is a required boolean. This is disallowed.",
model_type, field.name, loc = loc) model_type, field.name, loc = loc)
fmt.assertf(field.name != INTERNAL_VARIADIC_FLAG, "%T.%s is defined as required. This is disallowed.", fmt.assertf(field.name != INTERNAL_OVERFLOW_FLAG, "%T.%s is defined as required. This is disallowed.",
model_type, field.name, loc = loc) model_type, field.name, loc = loc)
if len(requirement) > 0 { if len(requirement) > 0 {
@@ -109,24 +110,28 @@ validate_structure :: proc(model_type: $T, style: Parsing_Style, loc := #caller_
} }
} }
if length, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { if length, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
fmt.assertf(!has_pos,
"%T.%s has both `%s` and `%s` defined. This is disallowed.\n\tSuggestion: Use a dynamic array field named `%s` to accept unspecified positional arguments.",
model_type, field.name, SUBTAG_POS, SUBTAG_MANIFOLD, INTERNAL_OVERFLOW_FLAG, loc = loc)
if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok { if value, parse_ok := strconv.parse_u64_of_base(length, 10); parse_ok {
fmt.assertf(value > 0, fmt.assertf(value > 0,
"%T.%s has `%s` set to %i. It must be greater than zero.", "%T.%s has `%s` set to %i. It must be greater than zero.",
model_type, field.name, value, SUBTAG_VARIADIC, loc = loc) model_type, field.name, value, SUBTAG_MANIFOLD, loc = loc)
fmt.assertf(value != 1, fmt.assertf(value != 1,
"%T.%s has `%s` set to 1. This has no effect.", "%T.%s has `%s` set to 1. This is equivalent to not defining `%s`.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, SUBTAG_MANIFOLD, loc = loc)
} }
#partial switch specific_type_info in field.type.variant { #partial switch specific_type_info in field.type.variant {
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
fmt.assertf(style != .Odin, fmt.assertf(style != .Odin,
"%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.", "%T.%s has `%s` defined, but this only makes sense in UNIX-style parsing mode.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
case: case:
fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.", fmt.panicf("%T.%s has `%s` defined, but this only makes sense on dynamic arrays.",
model_type, field.name, SUBTAG_VARIADIC, loc = loc) model_type, field.name, SUBTAG_MANIFOLD, loc = loc)
} }
} }
+2 -2
View File
@@ -6,7 +6,7 @@ package flags
Parsing_Style :: enum { Parsing_Style :: enum {
// Odin-style: `-flag`, `-flag:option`, `-map:key=value` // Odin-style: `-flag`, `-flag:option`, `-map:key=value`
Odin, Odin,
// UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument repeating-argument` // UNIX-style: `-flag` or `--flag`, `--flag=argument`, `--flag argument (manifold-argument)`
Unix, Unix,
} }
@@ -61,7 +61,7 @@ parse :: proc(
} }
case .Unix: case .Unix:
// Support for `-flag argument (repeating-argument ...)` // Support for `-flag argument (manifold-argument ...)`
future_args: int future_args: int
current_flag: string current_flag: string
+15 -15
View File
@@ -30,18 +30,18 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
is_positional: bool, is_positional: bool,
is_required: bool, is_required: bool,
is_boolean: bool, is_boolean: bool,
is_variadic: bool, is_manifold: bool,
variadic_length: int, manifold_length: int,
} }
// //
// POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ... // POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
// //
sort_flags :: proc(i, j: Flag) -> slice.Ordering { sort_flags :: proc(i, j: Flag) -> slice.Ordering {
// `varg` goes to the end. // `overflow` goes to the end.
if i.name == INTERNAL_VARIADIC_FLAG { if i.name == INTERNAL_OVERFLOW_FLAG {
return .Greater return .Greater
} else if j.name == INTERNAL_VARIADIC_FLAG { } else if j.name == INTERNAL_OVERFLOW_FLAG {
return .Less return .Less
} }
@@ -120,10 +120,10 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
flag.is_required = true flag.is_required = true
flag.required_min, flag.required_max, _ = parse_requirements(requirement) flag.required_min, flag.required_max, _ = parse_requirements(requirement)
} }
if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic { if length_str, is_manifold := get_struct_subtag(args_tag, SUBTAG_MANIFOLD); is_manifold {
flag.is_variadic = true flag.is_manifold = true
if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok { if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
flag.variadic_length = cast(int)length flag.manifold_length = cast(int)length
} }
} }
} }
@@ -147,15 +147,15 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
case runtime.Type_Info_Dynamic_Array: case runtime.Type_Info_Dynamic_Array:
requirement_spec := describe_array_requirements(flag) requirement_spec := describe_array_requirements(flag)
if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG { if flag.is_manifold || flag.name == INTERNAL_OVERFLOW_FLAG {
if flag.variadic_length == 0 { if flag.manifold_length == 0 {
flag.type_description = fmt.tprintf("<%v, ...>%s", flag.type_description = fmt.tprintf("<%v, ...>%s",
specific_type_info.elem.id, specific_type_info.elem.id,
requirement_spec) requirement_spec)
} else { } else {
flag.type_description = fmt.tprintf("<%v, %i at once>%s", flag.type_description = fmt.tprintf("<%v, %i at once>%s",
specific_type_info.elem.id, specific_type_info.elem.id,
flag.variadic_length, flag.manifold_length,
requirement_spec) requirement_spec)
} }
} else { } else {
@@ -177,7 +177,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
} }
} }
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
flag.full_length = len(flag.type_description) flag.full_length = len(flag.type_description)
} else if flag.is_boolean { } else if flag.is_boolean {
flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description) flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
@@ -201,13 +201,13 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_string(&builder, program) strings.write_string(&builder, program)
for flag in visible_flags { for flag in visible_flags {
if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) { if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_OVERFLOW_FLAG) {
continue continue
} }
strings.write_byte(&builder, ' ') strings.write_byte(&builder, ' ')
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, "...") strings.write_string(&builder, "...")
continue continue
} }
@@ -252,7 +252,7 @@ write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", sty
strings.write_byte(&builder, '\t') strings.write_byte(&builder, '\t')
if flag.name == INTERNAL_VARIADIC_FLAG { if flag.name == INTERNAL_OVERFLOW_FLAG {
strings.write_string(&builder, flag.type_description) strings.write_string(&builder, flag.type_description)
} else { } else {
strings.write_string(&builder, flag_prefix) strings.write_string(&builder, flag_prefix)
+158 -54
View File
@@ -454,44 +454,44 @@ test_arrays :: proc(t: ^testing.T) {
@(test) @(test)
test_varargs :: proc(t: ^testing.T) { test_varargs :: proc(t: ^testing.T) {
S :: struct { S :: struct {
varg: [dynamic]string, overflow: [dynamic]string,
} }
s: S s: S
args := [?]string { "abc", "foo", "bar" } args := [?]string { "abc", "foo", "bar" }
result := flags.parse(&s, args[:]) result := flags.parse(&s, args[:])
defer delete(s.varg) defer delete(s.overflow)
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.varg), 3) testing.expect_value(t, len(s.overflow), 3)
if len(s.varg) < 3 { if len(s.overflow) < 3 {
return return
} }
testing.expect_value(t, s.varg[0], "abc") testing.expect_value(t, s.overflow[0], "abc")
testing.expect_value(t, s.varg[1], "foo") testing.expect_value(t, s.overflow[1], "foo")
testing.expect_value(t, s.varg[2], "bar") testing.expect_value(t, s.overflow[2], "bar")
} }
@(test) @(test)
test_mixed_varargs :: proc(t: ^testing.T) { test_mixed_varargs :: proc(t: ^testing.T) {
S :: struct { S :: struct {
input: string `args:"pos=0"`, input: string `args:"pos=0"`,
varg: [dynamic]string, overflow: [dynamic]string,
} }
s: S s: S
args := [?]string { "abc", "foo", "bar" } args := [?]string { "abc", "foo", "bar" }
result := flags.parse(&s, args[:]) result := flags.parse(&s, args[:])
defer delete(s.varg) defer delete(s.overflow)
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.varg), 2) testing.expect_value(t, len(s.overflow), 2)
if len(s.varg) < 2 { if len(s.overflow) < 2 {
return return
} }
testing.expect_value(t, s.input, "abc") testing.expect_value(t, s.input, "abc")
testing.expect_value(t, s.varg[0], "foo") testing.expect_value(t, s.overflow[0], "foo")
testing.expect_value(t, s.varg[1], "bar") testing.expect_value(t, s.overflow[1], "bar")
} }
@(test) @(test)
@@ -718,23 +718,23 @@ test_tags_required_limit_max :: proc(t: ^testing.T) {
test_tags_pos_out_of_order :: proc(t: ^testing.T) { test_tags_pos_out_of_order :: proc(t: ^testing.T) {
S :: struct { S :: struct {
a: int `args:"pos=2"`, a: int `args:"pos=2"`,
varg: [dynamic]int, overflow: [dynamic]int,
} }
s: S s: S
args := [?]string { "1", "2", "3", "4" } args := [?]string { "1", "2", "3", "4" }
result := flags.parse(&s, args[:]) result := flags.parse(&s, args[:])
defer delete(s.varg) defer delete(s.overflow)
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.varg), 3) testing.expect_value(t, len(s.overflow), 3)
if len(s.varg) < 3 { if len(s.overflow) < 3 {
return return
} }
testing.expect_value(t, s.a, 3) testing.expect_value(t, s.a, 3)
testing.expect_value(t, s.varg[0], 1) testing.expect_value(t, s.overflow[0], 1)
testing.expect_value(t, s.varg[1], 2) testing.expect_value(t, s.overflow[1], 2)
testing.expect_value(t, s.varg[2], 4) testing.expect_value(t, s.overflow[2], 4)
} }
@(test) @(test)
@@ -899,7 +899,7 @@ test_pos_nonoverlap :: proc(t: ^testing.T) {
@(test) @(test)
test_pos_many_args :: proc(t: ^testing.T) { test_pos_many_args :: proc(t: ^testing.T) {
S :: struct { S :: struct {
varg: [dynamic]int, overflow: [dynamic]int,
a: int `args:"pos=0,required"`, a: int `args:"pos=0,required"`,
b: int `args:"pos=64,required"`, b: int `args:"pos=64,required"`,
c: int `args:"pos=66,required"`, c: int `args:"pos=66,required"`,
@@ -908,7 +908,7 @@ test_pos_many_args :: proc(t: ^testing.T) {
s: S s: S
args: [dynamic]string args: [dynamic]string
defer delete(s.varg) defer delete(s.overflow)
for i in 0 ..< 130 { append(&args, fmt.aprintf("%i", 1 + i)) } for i in 0 ..< 130 { append(&args, fmt.aprintf("%i", 1 + i)) }
defer { defer {
@@ -922,14 +922,14 @@ test_pos_many_args :: proc(t: ^testing.T) {
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
testing.expect_value(t, s.a, 1) testing.expect_value(t, s.a, 1)
for i in 1 ..< 63 { testing.expect_value(t, s.varg[i], 2 + i) } for i in 1 ..< 63 { testing.expect_value(t, s.overflow[i], 2 + i) }
testing.expect_value(t, s.b, 65) testing.expect_value(t, s.b, 65)
testing.expect_value(t, s.varg[63], 66) testing.expect_value(t, s.overflow[63], 66)
testing.expect_value(t, s.c, 67) testing.expect_value(t, s.c, 67)
testing.expect_value(t, s.varg[64], 68) testing.expect_value(t, s.overflow[64], 68)
testing.expect_value(t, s.varg[65], 69) testing.expect_value(t, s.overflow[65], 69)
testing.expect_value(t, s.varg[66], 70) testing.expect_value(t, s.overflow[66], 70)
for i in 67 ..< 126 { testing.expect_value(t, s.varg[i], 4 + i) } for i in 67 ..< 126 { testing.expect_value(t, s.overflow[i], 4 + i) }
testing.expect_value(t, s.d, 130) testing.expect_value(t, s.d, 130)
} }
@@ -966,9 +966,9 @@ test_unix :: proc(t: ^testing.T) {
} }
@(test) @(test)
test_unix_variadic :: proc(t: ^testing.T) { test_unix_manifold :: proc(t: ^testing.T) {
S :: struct { S :: struct {
a: [dynamic]int `args:"variadic"`, a: [dynamic]int `args:"manifold"`,
} }
s: S s: S
@@ -989,9 +989,9 @@ test_unix_variadic :: proc(t: ^testing.T) {
} }
@(test) @(test)
test_unix_variadic_limited :: proc(t: ^testing.T) { test_unix_manifold_limited :: proc(t: ^testing.T) {
S :: struct { S :: struct {
a: [dynamic]int `args:"variadic=2"`, a: [dynamic]int `args:"manifold=2"`,
b: int, b: int,
} }
s: S s: S
@@ -1012,6 +1012,110 @@ test_unix_variadic_limited :: proc(t: ^testing.T) {
testing.expect_value(t, s.b, 3) testing.expect_value(t, s.b, 3)
} }
@(test)
test_unix_two_manifold_limited :: proc(t: ^testing.T) {
S :: struct {
a: [dynamic]int `args:"manifold=2"`,
b: [dynamic]int `args:"manifold=2"`,
c: int,
}
s: S
args := [?]string { "-a", "11", "101", "-b", "3", "7", "-c", "9" }
result := flags.parse(&s, args[:], .Unix)
defer {
delete(s.a)
delete(s.b)
}
testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.a), 2)
testing.expect_value(t, len(s.b), 2)
if len(s.a) < 2 || len(s.b) < 2 {
return
}
testing.expect_value(t, s.a[0], 11)
testing.expect_value(t, s.a[1], 101)
testing.expect_value(t, s.b[0], 3)
testing.expect_value(t, s.b[1], 7)
testing.expect_value(t, s.c, 9)
}
@(test)
test_unix_two_manifold_string :: proc(t: ^testing.T) {
// The expected behavior of a manifold flag is to consume all arguments as
// fitting for the element type.
S :: struct {
a: [dynamic]string `args:"manifold"`,
b: [dynamic]string `args:"manifold"`,
c: int,
}
s: S
args := [?]string { "-a", "11", "101", "-b", "3", "7", "-c", "9" }
result := flags.parse(&s, args[:], .Unix)
defer {
delete(s.a)
delete(s.b)
}
testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.a), 7)
testing.expect_value(t, len(s.b), 0)
if len(s.a) != 7 {
return
}
testing.expect_value(t, s.a[0], "11")
testing.expect_value(t, s.a[1], "101")
testing.expect_value(t, s.a[2], "-b")
testing.expect_value(t, s.a[3], "3")
testing.expect_value(t, s.a[4], "7")
testing.expect_value(t, s.a[5], "-c")
testing.expect_value(t, s.a[6], "9")
}
@(test)
test_unix_two_manifold_int :: proc(t: ^testing.T) {
// If a manifold flag encounters an argument that it cannot convert to the
// element type, then this is an error.
S :: struct {
a: [dynamic]int `args:"manifold"`,
b: [dynamic]int `args:"manifold"`,
c: int,
}
s: S
args := [?]string { "-a", "11", "101", "-b", "3", "7", "-c", "9" }
result := flags.parse(&s, args[:], .Unix)
defer {
delete(s.a)
delete(s.b)
}
err, ok := result.(flags.Parse_Error)
testing.expectf(t, ok, "unexpected result: %v", result)
if ok {
testing.expect_value(t, err.reason, flags.Parse_Error_Reason.Bad_Value)
}
// It is expected that arguments which pass will still be available.
testing.expect_value(t, len(s.a), 2)
testing.expect_value(t, len(s.b), 0)
if len(s.a) != 2 {
return
}
testing.expect_value(t, s.a[0], 11)
testing.expect_value(t, s.a[1], 101)
}
@(test) @(test)
test_unix_positional :: proc(t: ^testing.T) { test_unix_positional :: proc(t: ^testing.T) {
S :: struct { S :: struct {
@@ -1029,10 +1133,10 @@ test_unix_positional :: proc(t: ^testing.T) {
} }
@(test) @(test)
test_unix_positional_with_variadic :: proc(t: ^testing.T) { test_unix_positional_with_manifold :: proc(t: ^testing.T) {
S :: struct { S :: struct {
varg: [dynamic]int, overflow: [dynamic]int,
v: [dynamic]int `args:"variadic"`, v: [dynamic]int `args:"manifold"`,
} }
s: S s: S
@@ -1040,18 +1144,18 @@ test_unix_positional_with_variadic :: proc(t: ^testing.T) {
result := flags.parse(&s, args[:], .Unix) result := flags.parse(&s, args[:], .Unix)
defer { defer {
delete(s.varg) delete(s.overflow)
delete(s.v) delete(s.v)
} }
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.varg), 1) testing.expect_value(t, len(s.overflow), 1)
testing.expect_value(t, len(s.v), 2) testing.expect_value(t, len(s.v), 2)
} }
@(test) @(test)
test_unix_double_dash_variadic :: proc(t: ^testing.T) { test_unix_double_dash_varargs :: proc(t: ^testing.T) {
S :: struct { S :: struct {
varg: [dynamic]string, overflow: [dynamic]string,
i: int, i: int,
} }
s: S s: S
@@ -1060,19 +1164,19 @@ test_unix_double_dash_variadic :: proc(t: ^testing.T) {
result := flags.parse(&s, args[:], .Unix) result := flags.parse(&s, args[:], .Unix)
defer { defer {
delete(s.varg) delete(s.overflow)
} }
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
testing.expect_value(t, len(s.varg), 3) testing.expect_value(t, len(s.overflow), 3)
testing.expect_value(t, s.i, 3) testing.expect_value(t, s.i, 3)
if len(s.varg) != 3 { if len(s.overflow) != 3 {
return return
} }
testing.expect_value(t, s.varg[0], "hellope") testing.expect_value(t, s.overflow[0], "hellope")
testing.expect_value(t, s.varg[1], "-i") testing.expect_value(t, s.overflow[1], "-i")
testing.expect_value(t, s.varg[2], "5") testing.expect_value(t, s.overflow[2], "5")
} }
@(test) @(test)
@@ -1096,17 +1200,17 @@ test_unix_no_value :: proc(t: ^testing.T) {
@(test) @(test)
test_if_dynamic_cstrings_get_freed :: proc(t: ^testing.T) { test_if_dynamic_cstrings_get_freed :: proc(t: ^testing.T) {
S :: struct { S :: struct {
varg: [dynamic]cstring, overflow: [dynamic]cstring,
} }
s: S s: S
args := [?]string { "Hellope", "world!" } args := [?]string { "Hellope", "world!" }
result := flags.parse(&s, args[:]) result := flags.parse(&s, args[:])
defer { defer {
for v in s.varg { for v in s.overflow {
delete(v) delete(v)
} }
delete(s.varg) delete(s.overflow)
} }
testing.expect_value(t, result, nil) testing.expect_value(t, result, nil)
} }
@@ -1324,7 +1428,7 @@ very nicely.
debug: bool `args:"hidden" usage:"print debug info"`, debug: bool `args:"hidden" usage:"print debug info"`,
verbose: bool, verbose: bool,
varg: [dynamic]string, overflow: [dynamic]string,
} }
builder := strings.builder_make() builder := strings.builder_make()
@@ -1337,7 +1441,7 @@ very nicely.
@(test) @(test)
test_usage_write_unix :: proc(t: ^testing.T) { test_usage_write_unix :: proc(t: ^testing.T) {
Expected_Output :: `Usage: Expected_Output :: `Usage:
varg required-number [number] [name] --bars --bots --foos --gadgets --variadic-flag --widgets [--array] [--count] [--greek] [--verbose] ... overflow required-number [number] [name] --bars --bots --foos --gadgets --manifold-flag --widgets [--array] [--count] [--greek] [--verbose] ...
Flags: Flags:
--required-number <int>, required | some number --required-number <int>, required | some number
--number <int> | some other number --number <int> | some other number
@@ -1349,7 +1453,7 @@ Flags:
--bots <string>, at least 1 | <This flag has not been documented yet.> --bots <string>, at least 1 | <This flag has not been documented yet.>
--foos <string>, between 2 and 3 | <This flag has not been documented yet.> --foos <string>, between 2 and 3 | <This flag has not been documented yet.>
--gadgets <string>, at least 1 | <This flag has not been documented yet.> --gadgets <string>, at least 1 | <This flag has not been documented yet.>
--variadic-flag <int, ...>, at least 2 | <This flag has not been documented yet.> --manifold-flag <int, ...>, at least 2 | <This flag has not been documented yet.>
--widgets <string>, at most 2 | <This flag has not been documented yet.> --widgets <string>, at most 2 | <This flag has not been documented yet.>
| |
--array <rune>, multiple | <This flag has not been documented yet.> --array <rune>, multiple | <This flag has not been documented yet.>
@@ -1378,7 +1482,7 @@ very nicely.
greek: Custom_Enum, greek: Custom_Enum,
array: [dynamic]rune, array: [dynamic]rune,
variadic_flag: [dynamic]int `args:"variadic,required=2"`, manifold_flag: [dynamic]int `args:"manifold,required=2"`,
gadgets: [dynamic]string `args:"required=1"`, gadgets: [dynamic]string `args:"required=1"`,
widgets: [dynamic]string `args:"required=<3"`, widgets: [dynamic]string `args:"required=<3"`,
@@ -1389,12 +1493,12 @@ very nicely.
debug: bool `args:"hidden" usage:"print debug info"`, debug: bool `args:"hidden" usage:"print debug info"`,
verbose: bool, verbose: bool,
varg: [dynamic]string, overflow: [dynamic]string,
} }
builder := strings.builder_make() builder := strings.builder_make()
defer strings.builder_destroy(&builder) defer strings.builder_destroy(&builder)
writer := strings.to_stream(&builder) writer := strings.to_stream(&builder)
flags.write_usage(writer, S, "varg", .Unix) flags.write_usage(writer, S, "overflow", .Unix)
testing.expect_value(t, strings.to_string(builder), Expected_Output) testing.expect_value(t, strings.to_string(builder), Expected_Output)
} }