| 1 | import flag |
| 2 | |
| 3 | const all_style_enums = [flag.Style.short, .short_long, .long, .v, .v_flag_parser] |
| 4 | const posix_gnu_style_enums = [flag.Style.short, .short_long, .long] |
| 5 | const mixed_args = ['/path/to/exe', '-vv', 'vvv', '-version', '--mix', '--mix-all=all', '-ldflags', |
| 6 | '-m', '2', '-fgh', '["test", "test"]', '-m', '{map: 2, ml-q:"hello"}'] |
| 7 | |
| 8 | const posix_and_gnu_args = ['-vv', 'vvv', '-mwindows', '-d', 'one', '--device=two', '--amount=8', |
| 9 | '-d', 'three'] |
| 10 | |
| 11 | const posix_and_gnu_args_with_subcmd = ['/path/to/exe', 'subcmd', '-vv', 'vvv', '-mwindows', '-d', |
| 12 | 'one', '--device=two', '--amount=8', '-d', 'three'] |
| 13 | |
| 14 | const posix_and_gnu_args_with_paths = ['/path/to/exe', '-vv', 'vvv', '-mwindows', '-d', 'one', |
| 15 | '--device=two', '--amount=8', '-d', 'three', '/path/to/a', '/path/to/b'] |
| 16 | |
| 17 | const posix_args_error = ['/path/to/exe', '-vv', 'vvv', '-mwindows', '-m', 'gnu'] |
| 18 | const gnu_args = ['--f=10.2', '--mix', '--test=test', '--amount=5', '--version', 'other'] |
| 19 | const gnu_args_error = ['--f=10.2', '--mix', '--test=test', '--amount=5', '--version', 'other', |
| 20 | 'oo'] |
| 21 | const ignore_args_error = ['--show-version', '--some-test=ouch', '--amount=5', 'end'] |
| 22 | |
| 23 | struct Config { |
| 24 | mix bool |
| 25 | linker_option string @[only: m] |
| 26 | mix_hard bool @[json: muh] // Test that no other attributes get picked up |
| 27 | def_test string = 'def' @[long: test; short: t] |
| 28 | device []string @[short: d] |
| 29 | paths []string @[tail] |
| 30 | amount int = 1 |
| 31 | verbosity int @[repeats; short: v] |
| 32 | show_version bool @[long: version] |
| 33 | no_long_beef bool @[only: n] |
| 34 | } |
| 35 | |
| 36 | struct LongConfig { |
| 37 | f f32 |
| 38 | mix bool |
| 39 | some_test string = 'abc' @[long: test] |
| 40 | path string @[tail] |
| 41 | amount int = 1 |
| 42 | show_version bool @[long: version] |
| 43 | } |
| 44 | |
| 45 | struct IgnoreConfig { |
| 46 | some_test string = 'abc' @[ignore] |
| 47 | path string @[tail] |
| 48 | amount int = 1 |
| 49 | show_version bool |
| 50 | } |
| 51 | |
| 52 | fn test_flags() { |
| 53 | // Test .short_long parse style |
| 54 | config1, _ := flag.to_struct[Config](posix_and_gnu_args_with_subcmd, skip: 1)! |
| 55 | assert config1.mix == false |
| 56 | assert config1.verbosity == 5 |
| 57 | assert config1.amount == 8 |
| 58 | assert config1.def_test == 'def' |
| 59 | assert 'one' in config1.device |
| 60 | assert 'two' in config1.device |
| 61 | assert 'three' in config1.device |
| 62 | assert config1.linker_option == 'windows' |
| 63 | |
| 64 | config2, _ := flag.to_struct[Config](posix_and_gnu_args)! |
| 65 | assert config2.mix == false |
| 66 | assert config2.verbosity == 5 |
| 67 | assert config2.amount == 8 |
| 68 | assert config2.def_test == 'def' |
| 69 | assert 'one' in config2.device |
| 70 | assert 'two' in config2.device |
| 71 | assert 'three' in config2.device |
| 72 | assert config2.device.len == 3 |
| 73 | assert config2.linker_option == 'windows' |
| 74 | |
| 75 | mut posix_and_gnu_args_plus_test := posix_and_gnu_args.clone() |
| 76 | posix_and_gnu_args_plus_test << ['--test=ok', '-d', 'four'] |
| 77 | config3, _ := flag.to_struct[Config](posix_and_gnu_args_plus_test)! |
| 78 | assert config3.mix == false |
| 79 | assert config3.verbosity == 5 |
| 80 | assert config3.amount == 8 |
| 81 | assert config3.def_test == 'ok' |
| 82 | assert config3.device.len == 4 |
| 83 | assert 'one' == config3.device[0] |
| 84 | assert 'two' == config3.device[1] |
| 85 | assert 'three' == config3.device[2] |
| 86 | assert 'four' == config3.device[3] |
| 87 | assert config3.linker_option == 'windows' |
| 88 | |
| 89 | config4, _ := flag.to_struct[Config](posix_and_gnu_args_with_paths, skip: 1)! |
| 90 | assert config4.verbosity == 5 |
| 91 | assert config4.amount == 8 |
| 92 | assert config4.def_test == 'def' |
| 93 | assert config4.device.len == 3 |
| 94 | assert 'one' == config4.device[0] |
| 95 | assert 'two' == config4.device[1] |
| 96 | assert 'three' == config4.device[2] |
| 97 | assert config4.linker_option == 'windows' |
| 98 | assert config4.paths.len == 2 |
| 99 | assert config4.paths[0] == '/path/to/a' |
| 100 | assert config4.paths[1] == '/path/to/b' |
| 101 | } |
| 102 | |
| 103 | fn test_long_flags() { |
| 104 | // Test .long parse style |
| 105 | lc1, _ := flag.to_struct[LongConfig](gnu_args, style: .long)! |
| 106 | assert lc1.f == 10.2 |
| 107 | assert lc1.mix == true |
| 108 | assert lc1.some_test == 'test' |
| 109 | assert lc1.path == 'other' |
| 110 | assert lc1.amount == 5 |
| 111 | assert lc1.show_version == true |
| 112 | } |
| 113 | |
| 114 | fn test_flag_error_messages() { |
| 115 | // Test error for GNU long flag in .short (Posix) mode |
| 116 | if _, _ := flag.to_struct[Config](posix_and_gnu_args_with_subcmd, |
| 117 | skip: 1 |
| 118 | style: .short |
| 119 | ) |
| 120 | { |
| 121 | assert false, 'flags should not have reached this assert' |
| 122 | } else { |
| 123 | assert err.msg() == 'long delimiter `--` encountered in flag `--device=two` in short (POSIX) style parsing mode' |
| 124 | } |
| 125 | |
| 126 | // Test double mapping of flags |
| 127 | if _, _ := flag.to_struct[Config](posix_args_error, skip: 1) { |
| 128 | assert false, 'flags should not have reached this assert' |
| 129 | } else { |
| 130 | assert err.msg() == 'flag `-m gnu` is already mapped to field `linker_option` via `-m windows`' |
| 131 | } |
| 132 | |
| 133 | for e_num in all_style_enums { |
| 134 | // Test no match for non-flag as first arg (usually the `/path/to/executable`) - which must be skipped with `.skip` |
| 135 | if _, no_matches := flag.to_struct[Config](posix_and_gnu_args_with_subcmd, |
| 136 | style: e_num |
| 137 | ) |
| 138 | { |
| 139 | assert no_matches == ['/path/to/exe', 'subcmd'] // index 0 = executable, index 1 = subcmd |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | for e_num in posix_gnu_style_enums { |
| 144 | if _, _ := flag.to_struct[Config](mixed_args, skip: 1, style: e_num) { |
| 145 | assert false, 'flags should not have reached this assert' |
| 146 | } else { |
| 147 | if e_num == .short { |
| 148 | assert err.msg() == 'long delimiter `--` encountered in flag `--mix` in short (POSIX) style parsing mode' |
| 149 | } else if e_num == .long { |
| 150 | assert err.msg() == 'short delimiter `-` encountered in flag `-vv` in long (GNU) style parsing mode' |
| 151 | } else { |
| 152 | assert err.msg() == 'flag `-m {map: 2, ml-q:"hello"}` is already mapped to field `linker_option` via `-m 2`' |
| 153 | } |
| 154 | assert true |
| 155 | } |
| 156 | } |
| 157 | if _, no_matches := flag.to_struct[LongConfig](gnu_args_error, style: .long) { |
| 158 | assert no_matches == ['oo'] |
| 159 | } else { |
| 160 | assert false, 'flags should not have reached this assert' |
| 161 | } |
| 162 | |
| 163 | if _, _ := flag.to_struct[LongConfig](['--version=1.2.3'], style: .long) { |
| 164 | assert false, 'flags should not have reached this assert' |
| 165 | } else { |
| 166 | assert err.msg() == 'flag `--version=1.2.3` can not be assigned to bool field "show_version"' |
| 167 | } |
| 168 | if _, no_matches := flag.to_struct[IgnoreConfig](ignore_args_error, style: .long) { |
| 169 | assert no_matches == ['--some-test=ouch'] |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | fn test_flag_no_error_messages_when_relaxed() { |
| 174 | // Test that there's no errors in `mode: .relaxed` |
| 175 | _, no_matches1 := flag.to_struct[Config](posix_and_gnu_args_with_subcmd, |
| 176 | skip: 1 |
| 177 | style: .short |
| 178 | mode: .relaxed |
| 179 | )! |
| 180 | assert no_matches1 == ['subcmd', '--device=two', '--amount=8'] |
| 181 | |
| 182 | _, no_matches2 := flag.to_struct[LongConfig](gnu_args_error, |
| 183 | style: .long |
| 184 | mode: .relaxed |
| 185 | )! |
| 186 | assert no_matches2 == ['oo'] |
| 187 | |
| 188 | if _, _ := flag.to_struct[LongConfig](['--version=1.2.3'], |
| 189 | style: .long |
| 190 | mode: .relaxed |
| 191 | ) |
| 192 | { |
| 193 | assert false, 'flags should not have reached this assert' |
| 194 | } else { |
| 195 | // Type errors should not be ignored, only non-matching flags/args |
| 196 | assert err.msg() == 'flag `--version=1.2.3` can not be assigned to bool field "show_version"' |
| 197 | } |
| 198 | |
| 199 | _, no_matches3 := flag.to_struct[IgnoreConfig](ignore_args_error, |
| 200 | style: .long |
| 201 | mode: .relaxed |
| 202 | )! |
| 203 | assert no_matches3 == ['--some-test=ouch'] |
| 204 | } |
| 205 | |