tigerbeetle 0.0.34 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/client.rb +1 -1
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +2 -2
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,1414 @@
|
|
|
1
|
+
//! The purpose of `flags` is to define standard behavior for parsing CLI arguments and provide
|
|
2
|
+
//! a specific parsing library, implementing this behavior.
|
|
3
|
+
//!
|
|
4
|
+
//! These are TigerBeetle CLI guidelines:
|
|
5
|
+
//!
|
|
6
|
+
//! - The main principle is robustness --- make operator errors harder to make.
|
|
7
|
+
//! - For production usage, avoid defaults.
|
|
8
|
+
//! - Thoroughly validate options.
|
|
9
|
+
//! - In particular, check that no options are repeated.
|
|
10
|
+
//! - Use only long options (`--addresses`).
|
|
11
|
+
//! - Exception: `-h/--help` is allowed.
|
|
12
|
+
//! - Use `--key=value` syntax for an option with an argument.
|
|
13
|
+
//! Don't use `--key value`, as that can be ambiguous (e.g., `--key --verbose`).
|
|
14
|
+
//! - Use subcommand syntax when appropriate.
|
|
15
|
+
//! - Use positional arguments when appropriate.
|
|
16
|
+
//!
|
|
17
|
+
//! Design choices for this particular `flags` library:
|
|
18
|
+
//!
|
|
19
|
+
//! - Be a 80% solution. Parsing arguments is a surprisingly vast topic: auto-generated help,
|
|
20
|
+
//! bash completions, typo correction. Rather than providing a definitive solution, `flags`
|
|
21
|
+
//! is just one possible option. It is ok to re-implement arg parsing in a different way, as long
|
|
22
|
+
//! as the CLI guidelines are observed.
|
|
23
|
+
//!
|
|
24
|
+
//! - No auto-generated help. Zig doesn't expose doc comments through `@typeInfo`, so its hard to
|
|
25
|
+
//! implement auto-help nicely. Additionally, fully hand-crafted `--help` message can be of
|
|
26
|
+
//! higher quality.
|
|
27
|
+
//!
|
|
28
|
+
//! - Fatal errors. It might be "cleaner" to use `try` to propagate the error to the caller, but
|
|
29
|
+
//! during early CLI parsing, it is much simpler to terminate the process directly and save the
|
|
30
|
+
//! caller the hassle of propagating errors. The `fatal` function is public, to allow the caller
|
|
31
|
+
//! to run additional validation or parsing using the same error reporting mechanism.
|
|
32
|
+
//!
|
|
33
|
+
//! - Concise DSL. Most cli parsing is done for ad-hoc tools like benchmarking, where the ability to
|
|
34
|
+
//! quickly add a new argument is valuable. As this is a 80% solution, production code may use
|
|
35
|
+
//! more verbose approach if it gives better UX.
|
|
36
|
+
//!
|
|
37
|
+
//! - Caller manages ArgsIterator. ArgsIterator owns the backing memory of the args, so we let the
|
|
38
|
+
//! caller to manage the lifetime. The caller should be skipping program name.
|
|
39
|
+
|
|
40
|
+
const std = @import("std");
|
|
41
|
+
const stdx = @import("stdx.zig");
|
|
42
|
+
const builtin = @import("builtin");
|
|
43
|
+
const assert = std.debug.assert;
|
|
44
|
+
|
|
45
|
+
/// Format and print an error message to stderr, then exit with an exit code of 1.
|
|
46
|
+
fn fatal(comptime fmt_string: []const u8, args: anytype) noreturn {
|
|
47
|
+
const stderr = std.io.getStdErr().writer();
|
|
48
|
+
stderr.print("error: " ++ fmt_string ++ "\n", args) catch {};
|
|
49
|
+
// NB: this status must match vsr.FatalReason.cli, but it would be wrong for flags to depend on
|
|
50
|
+
// vsr. The right way would be to parametrize flags by this behavior, and let the caller inject
|
|
51
|
+
// the implementation of fatal function, but let's be pragmatic here and just match the behavior
|
|
52
|
+
// manually.
|
|
53
|
+
std.process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Parse CLI arguments for subcommands specified as Zig `struct` or `union(enum)`:
|
|
57
|
+
///
|
|
58
|
+
/// ```
|
|
59
|
+
/// const CLIArgs = union(enum) {
|
|
60
|
+
/// start: struct { addresses: []const u8, replica: u32 },
|
|
61
|
+
/// format: struct {
|
|
62
|
+
/// verbose: bool = false,
|
|
63
|
+
/// @"--": void,
|
|
64
|
+
/// path: []const u8,
|
|
65
|
+
/// },
|
|
66
|
+
///
|
|
67
|
+
/// pub const help =
|
|
68
|
+
/// \\ tigerbeetle start --addresses=<addresses> --replica=<replica>
|
|
69
|
+
/// \\ tigerbeetle format [--verbose] <path>
|
|
70
|
+
/// }
|
|
71
|
+
///
|
|
72
|
+
/// const cli_args = parse_commands(&args, CLIArgs);
|
|
73
|
+
/// ```
|
|
74
|
+
///
|
|
75
|
+
/// `@"--"` field is treated specially, it delineates positional arguments.
|
|
76
|
+
///
|
|
77
|
+
/// If `pub const help` declaration is present, it is used to implement `-h/--help` argument.
|
|
78
|
+
///
|
|
79
|
+
/// Value parsing can be customized on per-type basis via `parse_flag_value` customization point.
|
|
80
|
+
pub fn parse(args: *std.process.ArgIterator, comptime CLIArgs: type) CLIArgs {
|
|
81
|
+
comptime assert(CLIArgs != void);
|
|
82
|
+
assert(args.skip()); // Discard executable name.
|
|
83
|
+
return parse_flags(args, CLIArgs);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn parse_commands(args: *std.process.ArgIterator, comptime Commands: type) Commands {
|
|
87
|
+
comptime assert(@typeInfo(Commands) == .@"union");
|
|
88
|
+
comptime assert(std.meta.fields(Commands).len >= 2);
|
|
89
|
+
|
|
90
|
+
const first_arg = args.next() orelse fatal(
|
|
91
|
+
"subcommand required, expected {s}",
|
|
92
|
+
.{comptime fields_to_comma_list(Commands)},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// NB: help must be declared as *pub* const to be visible here.
|
|
96
|
+
if (@hasDecl(Commands, "help")) {
|
|
97
|
+
if (std.mem.eql(u8, first_arg, "-h") or std.mem.eql(u8, first_arg, "--help")) {
|
|
98
|
+
std.io.getStdOut().writeAll(Commands.help) catch std.process.exit(1);
|
|
99
|
+
std.process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
inline for (comptime std.meta.fields(Commands)) |field| {
|
|
104
|
+
comptime assert(std.mem.indexOfScalar(u8, field.name, '_') == null);
|
|
105
|
+
if (std.mem.eql(u8, first_arg, field.name)) {
|
|
106
|
+
return @unionInit(Commands, field.name, parse_flags(args, field.type));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
fatal("unknown subcommand: '{s}'", .{first_arg});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn parse_flags(args: *std.process.ArgIterator, comptime Flags: type) Flags {
|
|
113
|
+
@setEvalBranchQuota(5_000);
|
|
114
|
+
|
|
115
|
+
if (Flags == void) {
|
|
116
|
+
if (args.next()) |arg| {
|
|
117
|
+
fatal("unexpected argument: '{s}'", .{arg});
|
|
118
|
+
}
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (@typeInfo(Flags) == .@"union") {
|
|
123
|
+
return parse_commands(args, Flags);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
assert(@typeInfo(Flags) == .@"struct");
|
|
127
|
+
|
|
128
|
+
const fields = std.meta.fields(Flags);
|
|
129
|
+
comptime var fields_named, const fields_positional, const fields_extended =
|
|
130
|
+
for (fields, 0..) |field, index| {
|
|
131
|
+
if (std.mem.eql(u8, field.name, "--")) {
|
|
132
|
+
assert(field.type == void);
|
|
133
|
+
break .{
|
|
134
|
+
fields[0..index].*,
|
|
135
|
+
fields[index + 1 ..].*,
|
|
136
|
+
index == fields.len - 1,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
} else .{
|
|
140
|
+
fields[0..fields.len].*,
|
|
141
|
+
[_]std.builtin.Type.StructField{},
|
|
142
|
+
false,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
comptime {
|
|
146
|
+
if (fields_positional.len == 0) {
|
|
147
|
+
assert(fields.len == fields_named.len + @intFromBool(fields_extended));
|
|
148
|
+
} else {
|
|
149
|
+
assert(fields.len == fields_named.len + 1 + fields_positional.len);
|
|
150
|
+
assert(!fields_extended);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// When parsing named arguments, we must consider longer arguments first, such that
|
|
154
|
+
// `--foo-bar=92` is not confused for a misspelled `--foo=92`. Using `std.sort` for
|
|
155
|
+
// comptime-only values does not work, so open-code insertion sort, and comptime assert
|
|
156
|
+
// order during the actual parsing.
|
|
157
|
+
for (fields_named[0..], 0..) |*field_right, i| {
|
|
158
|
+
for (fields_named[0..i]) |*field_left| {
|
|
159
|
+
if (field_left.name.len < field_right.name.len) {
|
|
160
|
+
std.mem.swap(std.builtin.Type.StructField, field_left, field_right);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (fields_named) |field| {
|
|
166
|
+
switch (@typeInfo(field.type)) {
|
|
167
|
+
.bool => {
|
|
168
|
+
// Boolean flags must have a default.
|
|
169
|
+
assert(field.defaultValue() != null);
|
|
170
|
+
assert(field.defaultValue().? == false);
|
|
171
|
+
},
|
|
172
|
+
.optional => |optional| {
|
|
173
|
+
// Optional flags must have a default.
|
|
174
|
+
assert(field.defaultValue() != null);
|
|
175
|
+
assert(field.defaultValue().? == null);
|
|
176
|
+
|
|
177
|
+
assert_valid_value_type(optional.child);
|
|
178
|
+
},
|
|
179
|
+
else => {
|
|
180
|
+
assert_valid_value_type(field.type);
|
|
181
|
+
},
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
var optional_tail: bool = false;
|
|
186
|
+
for (fields_positional) |field| {
|
|
187
|
+
if (field.defaultValue() == null) {
|
|
188
|
+
if (optional_tail) @panic("optional positional arguments must be trailing");
|
|
189
|
+
} else {
|
|
190
|
+
optional_tail = true;
|
|
191
|
+
}
|
|
192
|
+
switch (@typeInfo(field.type)) {
|
|
193
|
+
.optional => |optional| {
|
|
194
|
+
// optional flags should have a default
|
|
195
|
+
assert(field.defaultValue() != null);
|
|
196
|
+
assert(field.defaultValue().? == null);
|
|
197
|
+
assert_valid_value_type(optional.child);
|
|
198
|
+
},
|
|
199
|
+
else => {
|
|
200
|
+
assert_valid_value_type(field.type);
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
var counts: std.enums.EnumFieldStruct(std.meta.FieldEnum(Flags), u32, 0) = .{};
|
|
207
|
+
var result: Flags = undefined;
|
|
208
|
+
var parsed_positional = false;
|
|
209
|
+
next_arg: while (args.next()) |arg| {
|
|
210
|
+
comptime var field_len_prev = std.math.maxInt(usize);
|
|
211
|
+
inline for (fields_named) |field| {
|
|
212
|
+
const flag = comptime flag_name(field);
|
|
213
|
+
|
|
214
|
+
comptime assert(field_len_prev >= field.name.len);
|
|
215
|
+
field_len_prev = field.name.len;
|
|
216
|
+
if (std.mem.startsWith(u8, arg, flag)) {
|
|
217
|
+
if (parsed_positional) {
|
|
218
|
+
fatal("unexpected trailing option: '{s}'", .{arg});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@field(counts, field.name) += 1;
|
|
222
|
+
const flag_value = parse_flag(field.type, flag, arg);
|
|
223
|
+
@field(result, field.name) = flag_value;
|
|
224
|
+
continue :next_arg;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (fields_positional.len > 0) {
|
|
229
|
+
assert(!fields_extended);
|
|
230
|
+
counts.@"--" += 1;
|
|
231
|
+
switch (counts.@"--" - 1) {
|
|
232
|
+
inline 0...fields_positional.len - 1 => |field_index| {
|
|
233
|
+
const field = fields_positional[field_index];
|
|
234
|
+
const flag = comptime flag_name_positional(field);
|
|
235
|
+
|
|
236
|
+
if (arg.len == 0) fatal("{s}: empty argument", .{flag});
|
|
237
|
+
// Prevent ambiguity between a flag and positional argument value. We could add
|
|
238
|
+
// support for bare ` -- ` as a disambiguation mechanism once we have a real
|
|
239
|
+
// use-case.
|
|
240
|
+
if (arg[0] == '-') fatal("unexpected argument: '{s}'", .{arg});
|
|
241
|
+
parsed_positional = true;
|
|
242
|
+
|
|
243
|
+
@field(result, field.name) =
|
|
244
|
+
parse_value(field.type, flag, arg);
|
|
245
|
+
continue :next_arg;
|
|
246
|
+
},
|
|
247
|
+
else => {}, // Fall-through to the unexpected argument error.
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
if (fields_extended) {
|
|
251
|
+
if (std.mem.eql(u8, arg, "--")) {
|
|
252
|
+
break;
|
|
253
|
+
} else {
|
|
254
|
+
fatal("unexpected argument: '{s}'; expected '-- ...'", .{arg});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fatal("unexpected argument: '{s}'", .{arg});
|
|
260
|
+
}
|
|
261
|
+
if (!fields_extended) assert(args.next() == null);
|
|
262
|
+
|
|
263
|
+
inline for (fields_named) |field| {
|
|
264
|
+
const flag = flag_name(field);
|
|
265
|
+
switch (@field(counts, field.name)) {
|
|
266
|
+
0 => if (field.defaultValue()) |default| {
|
|
267
|
+
@field(result, field.name) = default;
|
|
268
|
+
} else {
|
|
269
|
+
fatal("{s}: argument is required", .{flag});
|
|
270
|
+
},
|
|
271
|
+
1 => {},
|
|
272
|
+
else => fatal("{s}: duplicate argument", .{flag}),
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (fields_positional.len > 0) {
|
|
277
|
+
assert(counts.@"--" <= fields_positional.len);
|
|
278
|
+
inline for (fields_positional, 0..) |field, field_index| {
|
|
279
|
+
if (field_index >= counts.@"--") {
|
|
280
|
+
const flag = comptime flag_name_positional(field);
|
|
281
|
+
if (field.defaultValue()) |default| {
|
|
282
|
+
@field(result, field.name) = default;
|
|
283
|
+
} else {
|
|
284
|
+
fatal("{s}: argument is required", .{flag});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
fn assert_valid_value_type(comptime T: type) void {
|
|
294
|
+
comptime {
|
|
295
|
+
if (T == []const u8 or T == [:0]const u8 or @typeInfo(T) == .int) return;
|
|
296
|
+
if (@hasDecl(T, "parse_flag_value")) return;
|
|
297
|
+
|
|
298
|
+
if (@typeInfo(T) == .@"enum") {
|
|
299
|
+
const info = @typeInfo(T).@"enum";
|
|
300
|
+
assert(info.is_exhaustive);
|
|
301
|
+
assert(info.fields.len >= 2);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@compileError("flags: unsupported type: " ++ @typeName(T));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// Parse, e.g., `--cluster=123` into `123` integer
|
|
310
|
+
fn parse_flag(comptime T: type, flag: []const u8, arg: [:0]const u8) T {
|
|
311
|
+
assert(flag[0] == '-' and flag[1] == '-');
|
|
312
|
+
|
|
313
|
+
if (T == bool) {
|
|
314
|
+
if (std.mem.eql(u8, arg, flag)) {
|
|
315
|
+
// Bool argument may not have a value.
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const value = parse_flag_split_value(flag, arg);
|
|
321
|
+
assert(value.len > 0);
|
|
322
|
+
return parse_value(T, flag, value);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// Splits the value part from a `--arg=value` syntax.
|
|
326
|
+
fn parse_flag_split_value(flag: []const u8, arg: [:0]const u8) [:0]const u8 {
|
|
327
|
+
assert(flag[0] == '-' and flag[1] == '-');
|
|
328
|
+
assert(std.mem.startsWith(u8, arg, flag));
|
|
329
|
+
|
|
330
|
+
const value = arg[flag.len..];
|
|
331
|
+
if (value.len == 0) {
|
|
332
|
+
fatal("{s}: expected value separator '='", .{flag});
|
|
333
|
+
}
|
|
334
|
+
if (value[0] != '=') {
|
|
335
|
+
fatal(
|
|
336
|
+
"{s}: expected value separator '=', but found '{c}' in '{s}'",
|
|
337
|
+
.{ flag, value[0], arg },
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
if (value.len == 1) fatal("{s}: argument requires a value", .{flag});
|
|
341
|
+
return value[1..];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
fn parse_value(comptime T: type, flag: []const u8, value: [:0]const u8) T {
|
|
345
|
+
assert((flag[0] == '-' and flag[1] == '-') or flag[0] == '<');
|
|
346
|
+
assert(value.len > 0);
|
|
347
|
+
|
|
348
|
+
const V = switch (@typeInfo(T)) {
|
|
349
|
+
.optional => |optional| optional.child,
|
|
350
|
+
else => T,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
if (V == []const u8 or V == [:0]const u8) return value;
|
|
354
|
+
if (V == bool) return parse_value_bool(flag, value);
|
|
355
|
+
if (@typeInfo(V) == .int) return parse_value_int(V, flag, value);
|
|
356
|
+
if (@typeInfo(V) == .@"enum") return parse_value_enum(V, flag, value);
|
|
357
|
+
if (@hasDecl(V, "parse_flag_value")) {
|
|
358
|
+
|
|
359
|
+
// Contracts:
|
|
360
|
+
// - Input string is guaranteed to be not empty.
|
|
361
|
+
// - Output diagnostic must point to statically-allocated data.
|
|
362
|
+
// - Diagnostic must start with a lower case letter.
|
|
363
|
+
// - Diagnostic must end with a ':' (it will be concatenated with original input).
|
|
364
|
+
// - (static_diagnostic != null) iff error.InvalidFlagValue is returned.
|
|
365
|
+
const parse_flag_value: fn (
|
|
366
|
+
string: []const u8,
|
|
367
|
+
static_diagnostic: *?[]const u8,
|
|
368
|
+
) error{InvalidFlagValue}!V = V.parse_flag_value;
|
|
369
|
+
|
|
370
|
+
var diagnostic: ?[]const u8 = null;
|
|
371
|
+
if (parse_flag_value(value, &diagnostic)) |result| {
|
|
372
|
+
assert(diagnostic == null);
|
|
373
|
+
return result;
|
|
374
|
+
} else |err| switch (err) {
|
|
375
|
+
error.InvalidFlagValue => {
|
|
376
|
+
const message = diagnostic.?;
|
|
377
|
+
assert(std.ascii.isLower(message[0]));
|
|
378
|
+
assert(message[message.len - 1] == ':');
|
|
379
|
+
fatal("{s}: {s} '{s}'", .{ flag, message, value });
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
comptime unreachable;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/// Parse string value into an integer, providing a nice error message for the user.
|
|
387
|
+
fn parse_value_int(comptime T: type, flag: []const u8, value: [:0]const u8) T {
|
|
388
|
+
assert((flag[0] == '-' and flag[1] == '-') or flag[0] == '<');
|
|
389
|
+
|
|
390
|
+
// Support only unsigned integers, as a conservative choice.
|
|
391
|
+
comptime assert(@typeInfo(T).int.signedness == .unsigned);
|
|
392
|
+
return std.fmt.parseUnsigned(T, value, 10) catch |err| {
|
|
393
|
+
switch (err) {
|
|
394
|
+
error.Overflow => fatal(
|
|
395
|
+
"{s}: value exceeds {d}-bit {s} integer: '{s}'",
|
|
396
|
+
.{ flag, @typeInfo(T).int.bits, @tagName(@typeInfo(T).int.signedness), value },
|
|
397
|
+
),
|
|
398
|
+
error.InvalidCharacter => fatal(
|
|
399
|
+
"{s}: expected an integer value, but found '{s}' (invalid digit)",
|
|
400
|
+
.{ flag, value },
|
|
401
|
+
),
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fn parse_value_bool(flag: []const u8, value: [:0]const u8) bool {
|
|
407
|
+
return switch (parse_value_enum(
|
|
408
|
+
enum {
|
|
409
|
+
true,
|
|
410
|
+
false,
|
|
411
|
+
},
|
|
412
|
+
flag,
|
|
413
|
+
value,
|
|
414
|
+
)) {
|
|
415
|
+
.true => true,
|
|
416
|
+
.false => false,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
fn parse_value_enum(comptime E: type, flag: []const u8, value: [:0]const u8) E {
|
|
421
|
+
assert((flag[0] == '-' and flag[1] == '-') or flag[0] == '<');
|
|
422
|
+
comptime assert(@typeInfo(E).@"enum".is_exhaustive);
|
|
423
|
+
|
|
424
|
+
return std.meta.stringToEnum(E, value) orelse fatal(
|
|
425
|
+
"{s}: expected one of {s}, but found '{s}'",
|
|
426
|
+
.{ flag, comptime fields_to_comma_list(E), value },
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
fn fields_to_comma_list(comptime E: type) []const u8 {
|
|
431
|
+
comptime {
|
|
432
|
+
const field_count = std.meta.fields(E).len;
|
|
433
|
+
assert(field_count >= 2);
|
|
434
|
+
|
|
435
|
+
var result: []const u8 = "";
|
|
436
|
+
for (std.meta.fields(E), 0..) |field, field_index| {
|
|
437
|
+
const separator = switch (field_index) {
|
|
438
|
+
0 => "",
|
|
439
|
+
else => ", ",
|
|
440
|
+
field_count - 1 => if (field_count == 2) " or " else ", or ",
|
|
441
|
+
};
|
|
442
|
+
result = result ++ separator ++ "'" ++ field.name ++ "'";
|
|
443
|
+
}
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
fn flag_name(comptime field: std.builtin.Type.StructField) []const u8 {
|
|
449
|
+
return comptime blk: {
|
|
450
|
+
assert(!std.mem.eql(u8, field.name, "-"));
|
|
451
|
+
assert(!std.mem.eql(u8, field.name, "--"));
|
|
452
|
+
|
|
453
|
+
var result: []const u8 = "--";
|
|
454
|
+
var index = 0;
|
|
455
|
+
while (std.mem.indexOfScalar(u8, field.name[index..], '_')) |i| {
|
|
456
|
+
result = result ++ field.name[index..][0..i] ++ "-";
|
|
457
|
+
index = index + i + 1;
|
|
458
|
+
}
|
|
459
|
+
result = result ++ field.name[index..];
|
|
460
|
+
break :blk result;
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
test flag_name {
|
|
465
|
+
const field = @typeInfo(struct { statsd: bool }).@"struct".fields[0];
|
|
466
|
+
try std.testing.expectEqualStrings(flag_name(field), "--statsd");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
fn flag_name_positional(comptime field: std.builtin.Type.StructField) []const u8 {
|
|
470
|
+
comptime assert(std.mem.indexOfScalar(u8, field.name, '_') == null);
|
|
471
|
+
return "<" ++ field.name ++ ">";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// Fuzz parse_flag_value function:
|
|
475
|
+
///
|
|
476
|
+
/// - Check that ok cases return a value.
|
|
477
|
+
/// - Check that err cases return an error with a properly formatted diagnostics.
|
|
478
|
+
/// - Check that the diagnostic contains specified substring
|
|
479
|
+
/// - Random tests with the input alphabet seeded from explicit cases.
|
|
480
|
+
/// - Random tests with uniform input.
|
|
481
|
+
pub fn parse_flag_value_fuzz(
|
|
482
|
+
comptime T: type,
|
|
483
|
+
parse_flag_value: fn ([]const u8, *?[]const u8) error{InvalidFlagValue}!T,
|
|
484
|
+
cases: struct {
|
|
485
|
+
ok: []const struct { []const u8, T },
|
|
486
|
+
err: []const struct { []const u8, []const u8 },
|
|
487
|
+
},
|
|
488
|
+
) !void {
|
|
489
|
+
comptime assert(T.parse_flag_value == parse_flag_value);
|
|
490
|
+
|
|
491
|
+
const test_count = 50_000;
|
|
492
|
+
const string_size_max = 32;
|
|
493
|
+
|
|
494
|
+
const gpa = std.testing.allocator;
|
|
495
|
+
var prng = stdx.PRNG.from_seed_testing();
|
|
496
|
+
|
|
497
|
+
for (cases.ok) |case| {
|
|
498
|
+
const string, const want = case;
|
|
499
|
+
assert(string.len > 0);
|
|
500
|
+
|
|
501
|
+
var diagnostic: ?[]const u8 = null;
|
|
502
|
+
const got = try parse_flag_value(string, &diagnostic);
|
|
503
|
+
assert(diagnostic == null);
|
|
504
|
+
try std.testing.expectEqual(want, got);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
for (cases.err) |case| {
|
|
508
|
+
const string, const want_message = case;
|
|
509
|
+
assert(string.len > 0); // Empty value are rejected early.
|
|
510
|
+
|
|
511
|
+
var diagnostic: ?[]const u8 = null;
|
|
512
|
+
if (parse_flag_value(string, &diagnostic)) |value| {
|
|
513
|
+
std.debug.print("expected an error, got value: input='{s}', value={}", .{
|
|
514
|
+
string,
|
|
515
|
+
value,
|
|
516
|
+
});
|
|
517
|
+
return error.TestUnexpectedResult;
|
|
518
|
+
} else |err| switch (err) {
|
|
519
|
+
error.InvalidFlagValue => {
|
|
520
|
+
try parse_flag_value_check_diagnostic(string, diagnostic);
|
|
521
|
+
if (stdx.cut(diagnostic.?, want_message) == null) {
|
|
522
|
+
std.debug.print(
|
|
523
|
+
"expected diagnostic to contain substring='{s}' diagnostic='{s}'",
|
|
524
|
+
.{ want_message, diagnostic.? },
|
|
525
|
+
);
|
|
526
|
+
return error.TestUnexpectedResult;
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
var corpus: std.ArrayListUnmanaged(u8) = .empty;
|
|
533
|
+
defer corpus.deinit(gpa);
|
|
534
|
+
|
|
535
|
+
for (cases.ok) |case| try corpus.appendSlice(gpa, case[0]);
|
|
536
|
+
for (cases.err) |case| try corpus.appendSlice(gpa, case[0]);
|
|
537
|
+
for (0..5) |_| try corpus.append(gpa, prng.int(u8));
|
|
538
|
+
|
|
539
|
+
std.mem.sort(u8, corpus.items, {}, std.sort.asc(u8));
|
|
540
|
+
|
|
541
|
+
const alphabet = unique(corpus.items);
|
|
542
|
+
|
|
543
|
+
var string_buffer: [string_size_max]u8 = @splat(0);
|
|
544
|
+
for (0..test_count) |_| {
|
|
545
|
+
const string_size = prng.range_inclusive(usize, 1, string_size_max);
|
|
546
|
+
const string = string_buffer[0..string_size];
|
|
547
|
+
assert(string.len > 0);
|
|
548
|
+
if (prng.boolean()) {
|
|
549
|
+
for (string) |*c| c.* = alphabet[prng.index(alphabet)];
|
|
550
|
+
} else {
|
|
551
|
+
for (string) |*c| c.* = prng.int(u8);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
var diagnostic: ?[]const u8 = null;
|
|
555
|
+
if (parse_flag_value(string, &diagnostic)) |_| {
|
|
556
|
+
assert(diagnostic == null);
|
|
557
|
+
} else |err| switch (err) {
|
|
558
|
+
error.InvalidFlagValue => try parse_flag_value_check_diagnostic(string, diagnostic),
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
fn parse_flag_value_check_diagnostic(string: []const u8, diagnostic: ?[]const u8) !void {
|
|
564
|
+
const message = diagnostic orelse {
|
|
565
|
+
std.debug.print("expected a diagnostic: string='{s}'", .{string});
|
|
566
|
+
return error.TestUnexpectedResult;
|
|
567
|
+
};
|
|
568
|
+
if (!(message.len > 0 and
|
|
569
|
+
std.ascii.isLower(message[0]) and
|
|
570
|
+
message[message.len - 1] == ':'))
|
|
571
|
+
{
|
|
572
|
+
std.debug.print("wrong diagnostic format: string='{s}' diagnostic='{s}'", .{
|
|
573
|
+
string,
|
|
574
|
+
message,
|
|
575
|
+
});
|
|
576
|
+
return error.TestUnexpectedResult;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
fn unique(sorted: []u8) []u8 {
|
|
581
|
+
assert(sorted.len > 0);
|
|
582
|
+
|
|
583
|
+
var count: usize = 1;
|
|
584
|
+
for (1..sorted.len) |index| {
|
|
585
|
+
assert(sorted[count - 1] <= sorted[index]);
|
|
586
|
+
if (sorted[count - 1] == sorted[index]) {
|
|
587
|
+
// Duplicate! Skip to the next index.
|
|
588
|
+
} else {
|
|
589
|
+
sorted[count] = sorted[index];
|
|
590
|
+
count += 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
return sorted[0..count];
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// CLI parsing makes a liberal use of `fatal`, so testing it within the process is impossible. We
|
|
598
|
+
// test it out of process by:
|
|
599
|
+
// - using Zig compiler to build this very file as an executable in a temporary directory,
|
|
600
|
+
// - running the following main with various args and capturing stdout, stderr, and the exit code.
|
|
601
|
+
// - asserting that the captured values are correct.
|
|
602
|
+
// For production builds, don't include the main function.
|
|
603
|
+
// This is `if __name__ == "__main__":` at comptime!
|
|
604
|
+
pub const main =
|
|
605
|
+
if (@import("root") != @This()) {} else struct {
|
|
606
|
+
const CLIArgs = union(enum) {
|
|
607
|
+
empty,
|
|
608
|
+
prefix: struct {
|
|
609
|
+
foo: u8 = 0,
|
|
610
|
+
foo_bar: u8 = 0,
|
|
611
|
+
opt: bool = false,
|
|
612
|
+
option: bool = false,
|
|
613
|
+
},
|
|
614
|
+
positional: struct {
|
|
615
|
+
flag: bool = false,
|
|
616
|
+
|
|
617
|
+
@"--": void,
|
|
618
|
+
p1: []const u8,
|
|
619
|
+
p2: []const u8,
|
|
620
|
+
p3: ?u32 = null,
|
|
621
|
+
p4: ?u32 = null,
|
|
622
|
+
},
|
|
623
|
+
extended: struct {
|
|
624
|
+
flag: bool = false,
|
|
625
|
+
@"--": void,
|
|
626
|
+
},
|
|
627
|
+
required: struct {
|
|
628
|
+
foo: u8,
|
|
629
|
+
bar: u8,
|
|
630
|
+
},
|
|
631
|
+
values: struct {
|
|
632
|
+
int: u32 = 0,
|
|
633
|
+
size: stdx.ByteSize = .{ .value = 0 },
|
|
634
|
+
boolean: bool = false,
|
|
635
|
+
path: []const u8 = "not-set",
|
|
636
|
+
optional: ?[]const u8 = null,
|
|
637
|
+
choice: enum { marlowe, shakespeare } = .marlowe,
|
|
638
|
+
},
|
|
639
|
+
subcommand: union(enum) {
|
|
640
|
+
pub const help =
|
|
641
|
+
\\subcommand help
|
|
642
|
+
\\
|
|
643
|
+
;
|
|
644
|
+
|
|
645
|
+
c1: struct { a: bool = false },
|
|
646
|
+
c2: struct { b: bool = false },
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
pub const help =
|
|
650
|
+
\\ flags-test-program [flags]
|
|
651
|
+
\\
|
|
652
|
+
;
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
fn main() !void {
|
|
656
|
+
var gpa_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
|
657
|
+
const gpa = gpa_allocator.allocator();
|
|
658
|
+
|
|
659
|
+
var args = try std.process.argsWithAllocator(gpa);
|
|
660
|
+
defer args.deinit();
|
|
661
|
+
|
|
662
|
+
const cli_args = parse(&args, CLIArgs);
|
|
663
|
+
|
|
664
|
+
const stdout = std.io.getStdOut();
|
|
665
|
+
const out_stream = stdout.writer();
|
|
666
|
+
switch (cli_args) {
|
|
667
|
+
.empty => try out_stream.print("empty\n", .{}),
|
|
668
|
+
.prefix => |values| {
|
|
669
|
+
try out_stream.print("foo: {}\n", .{values.foo});
|
|
670
|
+
try out_stream.print("foo-bar: {}\n", .{values.foo_bar});
|
|
671
|
+
try out_stream.print("opt: {}\n", .{values.opt});
|
|
672
|
+
try out_stream.print("option: {}\n", .{values.option});
|
|
673
|
+
},
|
|
674
|
+
.positional => |values| {
|
|
675
|
+
try out_stream.print("p1: {s}\n", .{values.p1});
|
|
676
|
+
try out_stream.print("p2: {s}\n", .{values.p2});
|
|
677
|
+
try out_stream.print("p3: {?}\n", .{values.p3});
|
|
678
|
+
try out_stream.print("p4: {?}\n", .{values.p4});
|
|
679
|
+
try out_stream.print("flag: {}\n", .{values.flag});
|
|
680
|
+
},
|
|
681
|
+
.extended => |values| {
|
|
682
|
+
try out_stream.print("flag: {}\n", .{values.flag});
|
|
683
|
+
while (args.next()) |arg| try out_stream.print("arg: {s}\n", .{arg});
|
|
684
|
+
},
|
|
685
|
+
.required => |required| {
|
|
686
|
+
try out_stream.print("foo: {}\n", .{required.foo});
|
|
687
|
+
try out_stream.print("bar: {}\n", .{required.bar});
|
|
688
|
+
},
|
|
689
|
+
.values => |values| {
|
|
690
|
+
try out_stream.print("int: {}\n", .{values.int});
|
|
691
|
+
try out_stream.print("size: {}\n", .{values.size.bytes()});
|
|
692
|
+
try out_stream.print("boolean: {}\n", .{values.boolean});
|
|
693
|
+
try out_stream.print("path: {s}\n", .{values.path});
|
|
694
|
+
try out_stream.print("optional: {?s}\n", .{values.optional});
|
|
695
|
+
try out_stream.print("choice: {?s}\n", .{@tagName(values.choice)});
|
|
696
|
+
},
|
|
697
|
+
.subcommand => |values| {
|
|
698
|
+
switch (values) {
|
|
699
|
+
.c1 => |c1| try out_stream.print("c1.a: {}\n", .{c1.a}),
|
|
700
|
+
.c2 => |c2| try out_stream.print("c2.b: {}\n", .{c2.b}),
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}.main;
|
|
706
|
+
|
|
707
|
+
test "flags" {
|
|
708
|
+
const Snap = stdx.Snap;
|
|
709
|
+
const module_path = "src/stdx";
|
|
710
|
+
const snap = Snap.snap_fn(module_path);
|
|
711
|
+
|
|
712
|
+
const T = struct {
|
|
713
|
+
const T = @This();
|
|
714
|
+
|
|
715
|
+
gpa: std.mem.Allocator,
|
|
716
|
+
tmp_dir: std.testing.TmpDir,
|
|
717
|
+
output_buf: std.ArrayList(u8),
|
|
718
|
+
flags_exe_buf: *[std.fs.max_path_bytes]u8,
|
|
719
|
+
flags_exe: []const u8,
|
|
720
|
+
|
|
721
|
+
fn init(gpa: std.mem.Allocator) !T {
|
|
722
|
+
// TODO: Avoid std.posix.getenv() as it currently causes a linker error on windows.
|
|
723
|
+
// See: https://github.com/ziglang/zig/issues/8456
|
|
724
|
+
const zig_exe = try std.process.getEnvVarOwned(gpa, "ZIG_EXE"); // Set by build.zig
|
|
725
|
+
defer gpa.free(zig_exe);
|
|
726
|
+
|
|
727
|
+
var tmp_dir = std.testing.tmpDir(.{});
|
|
728
|
+
errdefer tmp_dir.cleanup();
|
|
729
|
+
|
|
730
|
+
const tmp_dir_path = try std.fs.path.join(gpa, &.{
|
|
731
|
+
".zig-cache",
|
|
732
|
+
"tmp",
|
|
733
|
+
&tmp_dir.sub_path,
|
|
734
|
+
});
|
|
735
|
+
defer gpa.free(tmp_dir_path);
|
|
736
|
+
|
|
737
|
+
const output_buf = std.ArrayList(u8).init(gpa);
|
|
738
|
+
errdefer output_buf.deinit();
|
|
739
|
+
|
|
740
|
+
const flags_exe_buf = try gpa.create([std.fs.max_path_bytes]u8);
|
|
741
|
+
errdefer gpa.destroy(flags_exe_buf);
|
|
742
|
+
|
|
743
|
+
{ // Compile this file as an executable!
|
|
744
|
+
const path_relative = try std.fs.path.join(gpa, &.{
|
|
745
|
+
module_path,
|
|
746
|
+
@src().file,
|
|
747
|
+
});
|
|
748
|
+
defer gpa.free(path_relative);
|
|
749
|
+
|
|
750
|
+
const this_file = try std.fs.cwd().realpath(
|
|
751
|
+
path_relative,
|
|
752
|
+
flags_exe_buf,
|
|
753
|
+
);
|
|
754
|
+
const argv = [_][]const u8{ zig_exe, "build-exe", this_file };
|
|
755
|
+
const exec_result = try std.process.Child.run(.{
|
|
756
|
+
.allocator = gpa,
|
|
757
|
+
.argv = &argv,
|
|
758
|
+
.cwd = tmp_dir_path,
|
|
759
|
+
});
|
|
760
|
+
defer gpa.free(exec_result.stdout);
|
|
761
|
+
defer gpa.free(exec_result.stderr);
|
|
762
|
+
|
|
763
|
+
if (exec_result.term.Exited != 0) {
|
|
764
|
+
std.debug.print("{s}{s}", .{ exec_result.stdout, exec_result.stderr });
|
|
765
|
+
return error.FailedToCompile;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const flags_exe = try tmp_dir.dir.realpath(
|
|
770
|
+
"flags" ++ comptime builtin.target.exeFileExt(),
|
|
771
|
+
flags_exe_buf,
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
const sanity_check = try std.fs.openFileAbsolute(flags_exe, .{});
|
|
775
|
+
sanity_check.close();
|
|
776
|
+
|
|
777
|
+
return .{
|
|
778
|
+
.gpa = gpa,
|
|
779
|
+
.tmp_dir = tmp_dir,
|
|
780
|
+
.output_buf = output_buf,
|
|
781
|
+
.flags_exe_buf = flags_exe_buf,
|
|
782
|
+
.flags_exe = flags_exe,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
fn deinit(t: *T) void {
|
|
787
|
+
t.gpa.destroy(t.flags_exe_buf);
|
|
788
|
+
t.output_buf.deinit();
|
|
789
|
+
t.tmp_dir.cleanup();
|
|
790
|
+
t.* = undefined;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
fn check(t: *T, cli: []const []const u8, want: Snap) !void {
|
|
794
|
+
const argv = try t.gpa.alloc([]const u8, cli.len + 1);
|
|
795
|
+
defer t.gpa.free(argv);
|
|
796
|
+
|
|
797
|
+
argv[0] = t.flags_exe;
|
|
798
|
+
for (argv[1..], 0..) |*arg, i| {
|
|
799
|
+
arg.* = cli[i];
|
|
800
|
+
}
|
|
801
|
+
if (cli.len > 0) {
|
|
802
|
+
assert(argv[argv.len - 1].ptr == cli[cli.len - 1].ptr);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const exec_result = try std.process.Child.run(.{
|
|
806
|
+
.allocator = t.gpa,
|
|
807
|
+
.argv = argv,
|
|
808
|
+
});
|
|
809
|
+
defer t.gpa.free(exec_result.stdout);
|
|
810
|
+
defer t.gpa.free(exec_result.stderr);
|
|
811
|
+
|
|
812
|
+
t.output_buf.clearRetainingCapacity();
|
|
813
|
+
|
|
814
|
+
if (exec_result.term.Exited != 0) {
|
|
815
|
+
try t.output_buf.writer().print("status: {}\n", .{exec_result.term.Exited});
|
|
816
|
+
}
|
|
817
|
+
if (exec_result.stdout.len > 0) {
|
|
818
|
+
try t.output_buf.writer().print("stdout:\n{s}", .{exec_result.stdout});
|
|
819
|
+
}
|
|
820
|
+
if (exec_result.stderr.len > 0) {
|
|
821
|
+
try t.output_buf.writer().print("stderr:\n{s}", .{exec_result.stderr});
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
try want.diff(t.output_buf.items);
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
|
|
828
|
+
var t = try T.init(std.testing.allocator);
|
|
829
|
+
defer t.deinit();
|
|
830
|
+
|
|
831
|
+
// Test-cases are roughly in the source order of the corresponding features.
|
|
832
|
+
|
|
833
|
+
try t.check(&.{"empty"}, snap(@src(),
|
|
834
|
+
\\stdout:
|
|
835
|
+
\\empty
|
|
836
|
+
\\
|
|
837
|
+
));
|
|
838
|
+
|
|
839
|
+
try t.check(&.{}, snap(@src(),
|
|
840
|
+
\\status: 1
|
|
841
|
+
\\stderr:
|
|
842
|
+
\\error: subcommand required, expected 'empty', 'prefix', 'positional', 'extended', 'required', 'values', or 'subcommand'
|
|
843
|
+
\\
|
|
844
|
+
));
|
|
845
|
+
|
|
846
|
+
try t.check(&.{"-h"}, snap(@src(),
|
|
847
|
+
\\stdout:
|
|
848
|
+
\\ flags-test-program [flags]
|
|
849
|
+
\\
|
|
850
|
+
));
|
|
851
|
+
|
|
852
|
+
try t.check(&.{"--help"}, snap(@src(),
|
|
853
|
+
\\stdout:
|
|
854
|
+
\\ flags-test-program [flags]
|
|
855
|
+
\\
|
|
856
|
+
));
|
|
857
|
+
|
|
858
|
+
try t.check(&.{""}, snap(@src(),
|
|
859
|
+
\\status: 1
|
|
860
|
+
\\stderr:
|
|
861
|
+
\\error: unknown subcommand: ''
|
|
862
|
+
\\
|
|
863
|
+
));
|
|
864
|
+
|
|
865
|
+
try t.check(&.{"bogus"}, snap(@src(),
|
|
866
|
+
\\status: 1
|
|
867
|
+
\\stderr:
|
|
868
|
+
\\error: unknown subcommand: 'bogus'
|
|
869
|
+
\\
|
|
870
|
+
));
|
|
871
|
+
|
|
872
|
+
try t.check(&.{"--int=92"}, snap(@src(),
|
|
873
|
+
\\status: 1
|
|
874
|
+
\\stderr:
|
|
875
|
+
\\error: unknown subcommand: '--int=92'
|
|
876
|
+
\\
|
|
877
|
+
));
|
|
878
|
+
|
|
879
|
+
try t.check(&.{ "empty", "--help" }, snap(@src(),
|
|
880
|
+
\\status: 1
|
|
881
|
+
\\stderr:
|
|
882
|
+
\\error: unexpected argument: '--help'
|
|
883
|
+
\\
|
|
884
|
+
));
|
|
885
|
+
|
|
886
|
+
try t.check(&.{ "prefix", "--foo=92" }, snap(@src(),
|
|
887
|
+
\\stdout:
|
|
888
|
+
\\foo: 92
|
|
889
|
+
\\foo-bar: 0
|
|
890
|
+
\\opt: false
|
|
891
|
+
\\option: false
|
|
892
|
+
\\
|
|
893
|
+
));
|
|
894
|
+
|
|
895
|
+
try t.check(&.{ "prefix", "--foo-bar=92" }, snap(@src(),
|
|
896
|
+
\\stdout:
|
|
897
|
+
\\foo: 0
|
|
898
|
+
\\foo-bar: 92
|
|
899
|
+
\\opt: false
|
|
900
|
+
\\option: false
|
|
901
|
+
\\
|
|
902
|
+
));
|
|
903
|
+
|
|
904
|
+
try t.check(&.{ "prefix", "--foo-baz=92" }, snap(@src(),
|
|
905
|
+
\\status: 1
|
|
906
|
+
\\stderr:
|
|
907
|
+
\\error: --foo: expected value separator '=', but found '-' in '--foo-baz=92'
|
|
908
|
+
\\
|
|
909
|
+
));
|
|
910
|
+
|
|
911
|
+
try t.check(&.{ "prefix", "--opt" }, snap(@src(),
|
|
912
|
+
\\stdout:
|
|
913
|
+
\\foo: 0
|
|
914
|
+
\\foo-bar: 0
|
|
915
|
+
\\opt: true
|
|
916
|
+
\\option: false
|
|
917
|
+
\\
|
|
918
|
+
));
|
|
919
|
+
|
|
920
|
+
try t.check(&.{ "prefix", "--option" }, snap(@src(),
|
|
921
|
+
\\stdout:
|
|
922
|
+
\\foo: 0
|
|
923
|
+
\\foo-bar: 0
|
|
924
|
+
\\opt: false
|
|
925
|
+
\\option: true
|
|
926
|
+
\\
|
|
927
|
+
));
|
|
928
|
+
|
|
929
|
+
try t.check(&.{ "prefix", "--optx" }, snap(@src(),
|
|
930
|
+
\\status: 1
|
|
931
|
+
\\stderr:
|
|
932
|
+
\\error: --opt: expected value separator '=', but found 'x' in '--optx'
|
|
933
|
+
\\
|
|
934
|
+
));
|
|
935
|
+
|
|
936
|
+
try t.check(&.{ "positional", "x", "y" }, snap(@src(),
|
|
937
|
+
\\stdout:
|
|
938
|
+
\\p1: x
|
|
939
|
+
\\p2: y
|
|
940
|
+
\\p3: null
|
|
941
|
+
\\p4: null
|
|
942
|
+
\\flag: false
|
|
943
|
+
\\
|
|
944
|
+
));
|
|
945
|
+
|
|
946
|
+
try t.check(&.{ "positional", "x", "y", "1" }, snap(@src(),
|
|
947
|
+
\\stdout:
|
|
948
|
+
\\p1: x
|
|
949
|
+
\\p2: y
|
|
950
|
+
\\p3: 1
|
|
951
|
+
\\p4: null
|
|
952
|
+
\\flag: false
|
|
953
|
+
\\
|
|
954
|
+
));
|
|
955
|
+
|
|
956
|
+
try t.check(&.{ "positional", "x", "y", "1", "2" }, snap(@src(),
|
|
957
|
+
\\stdout:
|
|
958
|
+
\\p1: x
|
|
959
|
+
\\p2: y
|
|
960
|
+
\\p3: 1
|
|
961
|
+
\\p4: 2
|
|
962
|
+
\\flag: false
|
|
963
|
+
\\
|
|
964
|
+
));
|
|
965
|
+
|
|
966
|
+
try t.check(&.{"positional"}, snap(@src(),
|
|
967
|
+
\\status: 1
|
|
968
|
+
\\stderr:
|
|
969
|
+
\\error: <p1>: argument is required
|
|
970
|
+
\\
|
|
971
|
+
));
|
|
972
|
+
|
|
973
|
+
try t.check(&.{ "positional", "x" }, snap(@src(),
|
|
974
|
+
\\status: 1
|
|
975
|
+
\\stderr:
|
|
976
|
+
\\error: <p2>: argument is required
|
|
977
|
+
\\
|
|
978
|
+
));
|
|
979
|
+
|
|
980
|
+
try t.check(&.{ "positional", "x", "y", "z" }, snap(@src(),
|
|
981
|
+
\\status: 1
|
|
982
|
+
\\stderr:
|
|
983
|
+
\\error: <p3>: expected an integer value, but found 'z' (invalid digit)
|
|
984
|
+
\\
|
|
985
|
+
));
|
|
986
|
+
|
|
987
|
+
try t.check(&.{ "positional", "x", "y", "1", "2", "3" }, snap(@src(),
|
|
988
|
+
\\status: 1
|
|
989
|
+
\\stderr:
|
|
990
|
+
\\error: unexpected argument: '3'
|
|
991
|
+
\\
|
|
992
|
+
));
|
|
993
|
+
|
|
994
|
+
try t.check(&.{ "positional", "" }, snap(@src(),
|
|
995
|
+
\\status: 1
|
|
996
|
+
\\stderr:
|
|
997
|
+
\\error: <p1>: empty argument
|
|
998
|
+
\\
|
|
999
|
+
));
|
|
1000
|
+
|
|
1001
|
+
try t.check(&.{ "positional", "x", "--flag" }, snap(@src(),
|
|
1002
|
+
\\status: 1
|
|
1003
|
+
\\stderr:
|
|
1004
|
+
\\error: unexpected trailing option: '--flag'
|
|
1005
|
+
\\
|
|
1006
|
+
));
|
|
1007
|
+
|
|
1008
|
+
try t.check(&.{ "positional", "x", "--flag", "y" }, snap(@src(),
|
|
1009
|
+
\\status: 1
|
|
1010
|
+
\\stderr:
|
|
1011
|
+
\\error: unexpected trailing option: '--flag'
|
|
1012
|
+
\\
|
|
1013
|
+
));
|
|
1014
|
+
|
|
1015
|
+
try t.check(&.{ "positional", "--flag", "x", "y" }, snap(@src(),
|
|
1016
|
+
\\stdout:
|
|
1017
|
+
\\p1: x
|
|
1018
|
+
\\p2: y
|
|
1019
|
+
\\p3: null
|
|
1020
|
+
\\p4: null
|
|
1021
|
+
\\flag: true
|
|
1022
|
+
\\
|
|
1023
|
+
));
|
|
1024
|
+
|
|
1025
|
+
try t.check(&.{ "positional", "--", "x", "y" }, snap(@src(),
|
|
1026
|
+
\\status: 1
|
|
1027
|
+
\\stderr:
|
|
1028
|
+
\\error: unexpected argument: '--'
|
|
1029
|
+
\\
|
|
1030
|
+
));
|
|
1031
|
+
|
|
1032
|
+
try t.check(&.{ "positional", "--flak", "x", "y" }, snap(@src(),
|
|
1033
|
+
\\status: 1
|
|
1034
|
+
\\stderr:
|
|
1035
|
+
\\error: unexpected argument: '--flak'
|
|
1036
|
+
\\
|
|
1037
|
+
));
|
|
1038
|
+
|
|
1039
|
+
try t.check(&.{ "required", "--foo=1", "--bar=2" }, snap(@src(),
|
|
1040
|
+
\\stdout:
|
|
1041
|
+
\\foo: 1
|
|
1042
|
+
\\bar: 2
|
|
1043
|
+
\\
|
|
1044
|
+
));
|
|
1045
|
+
|
|
1046
|
+
try t.check(&.{ "required", "--surprise" }, snap(@src(),
|
|
1047
|
+
\\status: 1
|
|
1048
|
+
\\stderr:
|
|
1049
|
+
\\error: unexpected argument: '--surprise'
|
|
1050
|
+
\\
|
|
1051
|
+
));
|
|
1052
|
+
|
|
1053
|
+
try t.check(&.{ "required", "--foo=1" }, snap(@src(),
|
|
1054
|
+
\\status: 1
|
|
1055
|
+
\\stderr:
|
|
1056
|
+
\\error: --bar: argument is required
|
|
1057
|
+
\\
|
|
1058
|
+
));
|
|
1059
|
+
|
|
1060
|
+
try t.check(&.{ "required", "--foo=1", "--bar=2", "--foo=3" }, snap(@src(),
|
|
1061
|
+
\\status: 1
|
|
1062
|
+
\\stderr:
|
|
1063
|
+
\\error: --foo: duplicate argument
|
|
1064
|
+
\\
|
|
1065
|
+
));
|
|
1066
|
+
|
|
1067
|
+
try t.check(&.{
|
|
1068
|
+
"values",
|
|
1069
|
+
"--int=92",
|
|
1070
|
+
"--size=1GiB",
|
|
1071
|
+
"--boolean",
|
|
1072
|
+
"--path=/home",
|
|
1073
|
+
"--optional=some",
|
|
1074
|
+
"--choice=shakespeare",
|
|
1075
|
+
}, snap(@src(),
|
|
1076
|
+
\\stdout:
|
|
1077
|
+
\\int: 92
|
|
1078
|
+
\\size: 1073741824
|
|
1079
|
+
\\boolean: true
|
|
1080
|
+
\\path: /home
|
|
1081
|
+
\\optional: some
|
|
1082
|
+
\\choice: shakespeare
|
|
1083
|
+
\\
|
|
1084
|
+
));
|
|
1085
|
+
|
|
1086
|
+
try t.check(&.{"values"}, snap(@src(),
|
|
1087
|
+
\\stdout:
|
|
1088
|
+
\\int: 0
|
|
1089
|
+
\\size: 0
|
|
1090
|
+
\\boolean: false
|
|
1091
|
+
\\path: not-set
|
|
1092
|
+
\\optional: null
|
|
1093
|
+
\\choice: marlowe
|
|
1094
|
+
\\
|
|
1095
|
+
));
|
|
1096
|
+
|
|
1097
|
+
try t.check(&.{ "values", "--boolean=true" }, snap(@src(),
|
|
1098
|
+
\\stdout:
|
|
1099
|
+
\\int: 0
|
|
1100
|
+
\\size: 0
|
|
1101
|
+
\\boolean: true
|
|
1102
|
+
\\path: not-set
|
|
1103
|
+
\\optional: null
|
|
1104
|
+
\\choice: marlowe
|
|
1105
|
+
\\
|
|
1106
|
+
));
|
|
1107
|
+
|
|
1108
|
+
try t.check(&.{ "values", "--boolean=false" }, snap(@src(),
|
|
1109
|
+
\\stdout:
|
|
1110
|
+
\\int: 0
|
|
1111
|
+
\\size: 0
|
|
1112
|
+
\\boolean: false
|
|
1113
|
+
\\path: not-set
|
|
1114
|
+
\\optional: null
|
|
1115
|
+
\\choice: marlowe
|
|
1116
|
+
\\
|
|
1117
|
+
));
|
|
1118
|
+
|
|
1119
|
+
try t.check(&.{ "values", "--boolean=foo" }, snap(@src(),
|
|
1120
|
+
\\status: 1
|
|
1121
|
+
\\stderr:
|
|
1122
|
+
\\error: --boolean: expected one of 'true' or 'false', but found 'foo'
|
|
1123
|
+
\\
|
|
1124
|
+
));
|
|
1125
|
+
|
|
1126
|
+
try t.check(&.{ "values", "--int" }, snap(@src(),
|
|
1127
|
+
\\status: 1
|
|
1128
|
+
\\stderr:
|
|
1129
|
+
\\error: --int: expected value separator '='
|
|
1130
|
+
\\
|
|
1131
|
+
));
|
|
1132
|
+
|
|
1133
|
+
try t.check(&.{ "values", "--int:" }, snap(@src(),
|
|
1134
|
+
\\status: 1
|
|
1135
|
+
\\stderr:
|
|
1136
|
+
\\error: --int: expected value separator '=', but found ':' in '--int:'
|
|
1137
|
+
\\
|
|
1138
|
+
));
|
|
1139
|
+
|
|
1140
|
+
try t.check(&.{ "values", "--int=" }, snap(@src(),
|
|
1141
|
+
\\status: 1
|
|
1142
|
+
\\stderr:
|
|
1143
|
+
\\error: --int: argument requires a value
|
|
1144
|
+
\\
|
|
1145
|
+
));
|
|
1146
|
+
|
|
1147
|
+
try t.check(&.{ "values", "--int=-92" }, snap(@src(),
|
|
1148
|
+
\\status: 1
|
|
1149
|
+
\\stderr:
|
|
1150
|
+
\\error: --int: expected an integer value, but found '-92' (invalid digit)
|
|
1151
|
+
\\
|
|
1152
|
+
));
|
|
1153
|
+
|
|
1154
|
+
try t.check(&.{ "values", "--int=_92" }, snap(@src(),
|
|
1155
|
+
\\status: 1
|
|
1156
|
+
\\stderr:
|
|
1157
|
+
\\error: --int: expected an integer value, but found '_92' (invalid digit)
|
|
1158
|
+
\\
|
|
1159
|
+
));
|
|
1160
|
+
|
|
1161
|
+
try t.check(&.{ "values", "--int=92_" }, snap(@src(),
|
|
1162
|
+
\\status: 1
|
|
1163
|
+
\\stderr:
|
|
1164
|
+
\\error: --int: expected an integer value, but found '92_' (invalid digit)
|
|
1165
|
+
\\
|
|
1166
|
+
));
|
|
1167
|
+
|
|
1168
|
+
try t.check(&.{ "values", "--int=92" }, snap(@src(),
|
|
1169
|
+
\\stdout:
|
|
1170
|
+
\\int: 92
|
|
1171
|
+
\\size: 0
|
|
1172
|
+
\\boolean: false
|
|
1173
|
+
\\path: not-set
|
|
1174
|
+
\\optional: null
|
|
1175
|
+
\\choice: marlowe
|
|
1176
|
+
\\
|
|
1177
|
+
));
|
|
1178
|
+
|
|
1179
|
+
try t.check(&.{ "values", "--int=900_200" }, snap(@src(),
|
|
1180
|
+
\\stdout:
|
|
1181
|
+
\\int: 900200
|
|
1182
|
+
\\size: 0
|
|
1183
|
+
\\boolean: false
|
|
1184
|
+
\\path: not-set
|
|
1185
|
+
\\optional: null
|
|
1186
|
+
\\choice: marlowe
|
|
1187
|
+
\\
|
|
1188
|
+
));
|
|
1189
|
+
|
|
1190
|
+
try t.check(&.{ "values", "--int=XCII" }, snap(@src(),
|
|
1191
|
+
\\status: 1
|
|
1192
|
+
\\stderr:
|
|
1193
|
+
\\error: --int: expected an integer value, but found 'XCII' (invalid digit)
|
|
1194
|
+
\\
|
|
1195
|
+
));
|
|
1196
|
+
|
|
1197
|
+
try t.check(&.{ "values", "--int=44444444444444444444" }, snap(@src(),
|
|
1198
|
+
\\status: 1
|
|
1199
|
+
\\stderr:
|
|
1200
|
+
\\error: --int: value exceeds 32-bit unsigned integer: '44444444444444444444'
|
|
1201
|
+
\\
|
|
1202
|
+
));
|
|
1203
|
+
|
|
1204
|
+
try t.check(&.{ "values", "--int=-0" }, snap(@src(),
|
|
1205
|
+
\\status: 1
|
|
1206
|
+
\\stderr:
|
|
1207
|
+
\\error: --int: expected an integer value, but found '-0' (invalid digit)
|
|
1208
|
+
\\
|
|
1209
|
+
));
|
|
1210
|
+
|
|
1211
|
+
try t.check(&.{ "values", "--int=+0" }, snap(@src(),
|
|
1212
|
+
\\status: 1
|
|
1213
|
+
\\stderr:
|
|
1214
|
+
\\error: --int: expected an integer value, but found '+0' (invalid digit)
|
|
1215
|
+
\\
|
|
1216
|
+
));
|
|
1217
|
+
|
|
1218
|
+
try t.check(&.{ "values", "--size=-0" }, snap(@src(),
|
|
1219
|
+
\\status: 1
|
|
1220
|
+
\\stderr:
|
|
1221
|
+
\\error: --size: expected a size, but found: '-0'
|
|
1222
|
+
\\
|
|
1223
|
+
));
|
|
1224
|
+
|
|
1225
|
+
try t.check(&.{ "values", "--size=+0" }, snap(@src(),
|
|
1226
|
+
\\status: 1
|
|
1227
|
+
\\stderr:
|
|
1228
|
+
\\error: --size: expected a size, but found: '+0'
|
|
1229
|
+
\\
|
|
1230
|
+
));
|
|
1231
|
+
|
|
1232
|
+
try t.check(&.{ "values", "--size=_1000KiB" }, snap(@src(),
|
|
1233
|
+
\\status: 1
|
|
1234
|
+
\\stderr:
|
|
1235
|
+
\\error: --size: expected a size, but found: '_1000KiB'
|
|
1236
|
+
\\
|
|
1237
|
+
));
|
|
1238
|
+
|
|
1239
|
+
try t.check(&.{ "values", "--size=1000_KiB" }, snap(@src(),
|
|
1240
|
+
\\status: 1
|
|
1241
|
+
\\stderr:
|
|
1242
|
+
\\error: --size: expected a size, but found: '1000_KiB'
|
|
1243
|
+
\\
|
|
1244
|
+
));
|
|
1245
|
+
|
|
1246
|
+
try t.check(&.{ "values", "--size=1_000KiB" }, snap(@src(),
|
|
1247
|
+
\\stdout:
|
|
1248
|
+
\\int: 0
|
|
1249
|
+
\\size: 1024000
|
|
1250
|
+
\\boolean: false
|
|
1251
|
+
\\path: not-set
|
|
1252
|
+
\\optional: null
|
|
1253
|
+
\\choice: marlowe
|
|
1254
|
+
\\
|
|
1255
|
+
));
|
|
1256
|
+
|
|
1257
|
+
try t.check(&.{ "values", "--size=3MiB" }, snap(@src(),
|
|
1258
|
+
\\stdout:
|
|
1259
|
+
\\int: 0
|
|
1260
|
+
\\size: 3145728
|
|
1261
|
+
\\boolean: false
|
|
1262
|
+
\\path: not-set
|
|
1263
|
+
\\optional: null
|
|
1264
|
+
\\choice: marlowe
|
|
1265
|
+
\\
|
|
1266
|
+
));
|
|
1267
|
+
|
|
1268
|
+
try t.check(&.{ "values", "--size=44444444444444444444" }, snap(@src(),
|
|
1269
|
+
\\status: 1
|
|
1270
|
+
\\stderr:
|
|
1271
|
+
\\error: --size: value exceeds 64-bit unsigned integer: '44444444444444444444'
|
|
1272
|
+
\\
|
|
1273
|
+
));
|
|
1274
|
+
|
|
1275
|
+
try t.check(&.{ "values", "--size=100000000000000000" }, snap(@src(),
|
|
1276
|
+
\\stdout:
|
|
1277
|
+
\\int: 0
|
|
1278
|
+
\\size: 100000000000000000
|
|
1279
|
+
\\boolean: false
|
|
1280
|
+
\\path: not-set
|
|
1281
|
+
\\optional: null
|
|
1282
|
+
\\choice: marlowe
|
|
1283
|
+
\\
|
|
1284
|
+
));
|
|
1285
|
+
|
|
1286
|
+
try t.check(&.{ "values", "--size=100000000000000000kib" }, snap(@src(),
|
|
1287
|
+
\\status: 1
|
|
1288
|
+
\\stderr:
|
|
1289
|
+
\\error: --size: size in bytes exceeds 64-bit unsigned integer: '100000000000000000kib'
|
|
1290
|
+
\\
|
|
1291
|
+
));
|
|
1292
|
+
|
|
1293
|
+
try t.check(&.{ "values", "--size=3bogus" }, snap(@src(),
|
|
1294
|
+
\\status: 1
|
|
1295
|
+
\\stderr:
|
|
1296
|
+
\\error: --size: invalid unit in size, needed KiB, MiB, GiB or TiB: '3bogus'
|
|
1297
|
+
\\
|
|
1298
|
+
));
|
|
1299
|
+
|
|
1300
|
+
try t.check(&.{ "values", "--size=MiB" }, snap(@src(),
|
|
1301
|
+
\\status: 1
|
|
1302
|
+
\\stderr:
|
|
1303
|
+
\\error: --size: expected a size, but found: 'MiB'
|
|
1304
|
+
\\
|
|
1305
|
+
));
|
|
1306
|
+
|
|
1307
|
+
try t.check(&.{ "values", "--path=" }, snap(@src(),
|
|
1308
|
+
\\status: 1
|
|
1309
|
+
\\stderr:
|
|
1310
|
+
\\error: --path: argument requires a value
|
|
1311
|
+
\\
|
|
1312
|
+
));
|
|
1313
|
+
|
|
1314
|
+
try t.check(&.{ "values", "--optional=" }, snap(@src(),
|
|
1315
|
+
\\status: 1
|
|
1316
|
+
\\stderr:
|
|
1317
|
+
\\error: --optional: argument requires a value
|
|
1318
|
+
\\
|
|
1319
|
+
));
|
|
1320
|
+
|
|
1321
|
+
try t.check(&.{ "values", "--choice=molière" }, snap(@src(),
|
|
1322
|
+
\\status: 1
|
|
1323
|
+
\\stderr:
|
|
1324
|
+
\\error: --choice: expected one of 'marlowe' or 'shakespeare', but found 'molière'
|
|
1325
|
+
\\
|
|
1326
|
+
));
|
|
1327
|
+
|
|
1328
|
+
try t.check(&.{"subcommand"}, snap(@src(),
|
|
1329
|
+
\\status: 1
|
|
1330
|
+
\\stderr:
|
|
1331
|
+
\\error: subcommand required, expected 'c1' or 'c2'
|
|
1332
|
+
\\
|
|
1333
|
+
));
|
|
1334
|
+
try t.check(&.{ "subcommand", "c1", "--a" }, snap(@src(),
|
|
1335
|
+
\\stdout:
|
|
1336
|
+
\\c1.a: true
|
|
1337
|
+
\\
|
|
1338
|
+
));
|
|
1339
|
+
try t.check(&.{ "subcommand", "c2", "--b" }, snap(@src(),
|
|
1340
|
+
\\stdout:
|
|
1341
|
+
\\c2.b: true
|
|
1342
|
+
\\
|
|
1343
|
+
));
|
|
1344
|
+
try t.check(&.{ "subcommand", "c1", "--b" }, snap(@src(),
|
|
1345
|
+
\\status: 1
|
|
1346
|
+
\\stderr:
|
|
1347
|
+
\\error: unexpected argument: '--b'
|
|
1348
|
+
\\
|
|
1349
|
+
));
|
|
1350
|
+
try t.check(&.{ "subcommand", "c2", "--a" }, snap(@src(),
|
|
1351
|
+
\\status: 1
|
|
1352
|
+
\\stderr:
|
|
1353
|
+
\\error: unexpected argument: '--a'
|
|
1354
|
+
\\
|
|
1355
|
+
));
|
|
1356
|
+
try t.check(&.{ "subcommand", "--help" }, snap(@src(),
|
|
1357
|
+
\\stdout:
|
|
1358
|
+
\\subcommand help
|
|
1359
|
+
\\
|
|
1360
|
+
));
|
|
1361
|
+
try t.check(&.{ "subcommand", "-h" }, snap(@src(),
|
|
1362
|
+
\\stdout:
|
|
1363
|
+
\\subcommand help
|
|
1364
|
+
\\
|
|
1365
|
+
));
|
|
1366
|
+
|
|
1367
|
+
try t.check(&.{"extended"}, snap(@src(),
|
|
1368
|
+
\\stdout:
|
|
1369
|
+
\\flag: false
|
|
1370
|
+
\\
|
|
1371
|
+
));
|
|
1372
|
+
try t.check(&.{ "extended", "--" }, snap(@src(),
|
|
1373
|
+
\\stdout:
|
|
1374
|
+
\\flag: false
|
|
1375
|
+
\\
|
|
1376
|
+
));
|
|
1377
|
+
try t.check(&.{ "extended", "--flag", "--" }, snap(@src(),
|
|
1378
|
+
\\stdout:
|
|
1379
|
+
\\flag: true
|
|
1380
|
+
\\
|
|
1381
|
+
));
|
|
1382
|
+
try t.check(&.{ "extended", "a" }, snap(@src(),
|
|
1383
|
+
\\status: 1
|
|
1384
|
+
\\stderr:
|
|
1385
|
+
\\error: unexpected argument: 'a'; expected '-- ...'
|
|
1386
|
+
\\
|
|
1387
|
+
));
|
|
1388
|
+
try t.check(&.{ "extended", "--", "a" }, snap(@src(),
|
|
1389
|
+
\\stdout:
|
|
1390
|
+
\\flag: false
|
|
1391
|
+
\\arg: a
|
|
1392
|
+
\\
|
|
1393
|
+
));
|
|
1394
|
+
try t.check(&.{ "extended", "--flag", "--", "a" }, snap(@src(),
|
|
1395
|
+
\\stdout:
|
|
1396
|
+
\\flag: true
|
|
1397
|
+
\\arg: a
|
|
1398
|
+
\\
|
|
1399
|
+
));
|
|
1400
|
+
try t.check(&.{ "extended", "--", "a", "b" }, snap(@src(),
|
|
1401
|
+
\\stdout:
|
|
1402
|
+
\\flag: false
|
|
1403
|
+
\\arg: a
|
|
1404
|
+
\\arg: b
|
|
1405
|
+
\\
|
|
1406
|
+
));
|
|
1407
|
+
try t.check(&.{ "extended", "--flag", "--", "a", "b" }, snap(@src(),
|
|
1408
|
+
\\stdout:
|
|
1409
|
+
\\flag: true
|
|
1410
|
+
\\arg: a
|
|
1411
|
+
\\arg: b
|
|
1412
|
+
\\
|
|
1413
|
+
));
|
|
1414
|
+
}
|