# Builder Pattern for Argument Parsing ## Summary The builder pattern uses method chaining to programmatically construct the argument parser configuration. Instead of declaring everything in a static schema or struct, you call a series of methods that each add one piece of configuration, returning the builder object so you can chain the next call. Think of it like building with LEGO blocks - you start with a base and keep adding pieces one at a time. ## Core Concept ``` parser = new Parser() .addArg(...) .addArg(...) .addArg(...) .parse() ``` Each `.addArg()` returns the parser object, so you can keep chaining. ## Concrete Examples ### Example 1: Simple CLI Tool (Rust-style with clap) ```rust use clap::{App, Arg}; fn main() { let matches = App::new("MyApp") .version("1.0") .author("John Doe") .about("Does awesome things") .arg(Arg::new("verbose") .short('v') .long("verbose") .help("Enable verbose output")) .arg(Arg::new("output") .short('o') .long("output") .value_name("FILE") .help("Output file path") .takes_value(true) .required(false)) .arg(Arg::new("count") .short('n') .long("count") .value_name("NUM") .help("Number of iterations") .takes_value(true) .default_value("1") .validator(|s| s.parse::().map(|_| ()).map_err(|_| "Must be a number"))) .arg(Arg::new("config") .short('c') .long("config") .value_name("PATH") .help("Config file path") .takes_value(true) .conflicts_with("output")) .get_matches(); // Use the parsed arguments let verbose = matches.is_present("verbose"); let output = matches.value_of("output"); let count: u32 = matches.value_of_t("count").unwrap(); } ``` ### Example 2: Hypothetical Zig Builder Style ```zig const std = @import("std"); const ArgParser = @import("zargs").ArgParser; pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); // Build the parser with chained calls var parser = ArgParser.init(allocator) .name("mytool") .version("1.0.0") .description("Does awesome things") .flag("verbose") .short('v') .long("verbose") .help("Enable verbose output") .done() .option("output") .short('o') .long("output") .help("Output file path") .value_name("FILE") .required(false) .done() .option("count") .short('n') .long("count") .help("Number of iterations") .value_name("NUM") .default_value("1") .value_parser(parseU32) .done() .option("config") .short('c') .long("config") .help("Config file path") .value_name("PATH") .conflicts_with(&.{"output"}) .done(); // Parse the arguments const args = try parser.parse(); // Access the results const verbose = args.getFlag("verbose"); const output = args.getString("output"); const count = args.getInt("count") orelse 1; } fn parseU32(s: []const u8) !u32 { return std.fmt.parseInt(u32, s, 10); } ``` ### Example 3: Java-style with JCommander ```java import com.beust.jcommander.JCommander; import com.beust.jcommander.Parameter; public class MyApp { @Parameter(names = {"-v", "--verbose"}, description = "Enable verbose output") private boolean verbose = false; @Parameter(names = {"-o", "--output"}, description = "Output file path") private String output; @Parameter(names = {"-n", "--count"}, description = "Number of iterations") private int count = 1; public static void main(String[] args) { MyApp app = new MyApp(); // Builder pattern for the parser itself JCommander commander = JCommander.newBuilder() .addObject(app) .programName("myapp") .build(); commander.parse(args); // Use the parsed values System.out.println("Verbose: " + app.verbose); System.out.println("Output: " + app.output); System.out.println("Count: " + app.count); } } ``` ### Example 4: C++ with cxxopts ```cpp #include #include int main(int argc, char* argv[]) { cxxopts::Options options("MyApp", "Does awesome things"); // Builder pattern for adding options options .add_options() ("v,verbose", "Enable verbose output") ("o,output", "Output file path", cxxopts::value()) ("n,count", "Number of iterations", cxxopts::value()->default_value("1")) ("c,config", "Config file path", cxxopts::value()) ("h,help", "Print help"); auto result = options.parse(argc, argv); if (result.count("help")) { std::cout << options.help() << std::endl; return 0; } bool verbose = result["verbose"].as(); std::string output = result["output"].as(); int count = result["count"].as(); } ``` ## Key Characteristics ### Fluent Interface Each method returns `self` (or the builder) so you can chain: ``` builder.method1().method2().method3() ``` ### Incremental Construction Build up the configuration step by step: ```zig var parser = ArgParser.init(allocator); parser = parser.name("mytool"); parser = parser.version("1.0"); // ... etc ``` ### Nested Builders Often there's a hierarchy: ```zig parser .option("output") // Start building an option .short('o') // Configure the option .long("output") // More config .help("...") // More config .done() // Return to parent parser .option("count") // Start next option .short('n') .done() ``` ## Advantages for Zig 1. **No macros needed** - Pure runtime construction 2. **Conditional arguments** - Easy to add args based on runtime conditions: ```zig var parser = ArgParser.init(allocator); if (enable_debug_features) { parser = parser.flag("trace").help("Enable tracing").done(); } ``` 3. **Type-safe** - Compiler checks method calls 4. **Readable** - Sequential, easy to follow 5. **Still generates help** - All metadata collected during building ## Disadvantages 1. **Verbose** - More code than declarative style 2. **Boilerplate** - Lots of repeated method calls 3. **No compile-time validation** - Errors happen at runtime 4. **Memory overhead** - Must allocate storage for builder state ## When to Use - When you need runtime flexibility in argument definition - When you want good help generation but can't use macros/comptime - When arguments depend on configuration or conditional compilation - When you prefer explicit, procedural code over declarative schemas ## Comparison to Other Styles | Feature | Builder | Declarative | Ad-hoc | |---------|---------|-------------|---------| | Help generation | ✅ Good | ✅ Excellent | ❌ Poor | | Flexibility | ✅ Good | ❌ Poor | ✅ Excellent | | Verbosity | ⚠️ Moderate | ✅ Low | ✅ Very Low | | Runtime overhead | ⚠️ Moderate | ⚠️ Moderate | ✅ Minimal | | Type safety | ✅ Good | ✅ Excellent | ❌ Poor | ## Builder Pattern in Zig Context Zig could make this pattern very clean with: - Method chaining (returning `*Self`) - Comptime validation of method call sequences - Tagged unions for storing different arg types - Allocator control for builder state The sweet spot might be a builder pattern that's mostly runtime but validates at comptime when possible.