tiny.pluck.cli.app
Defined in cli.
API (17)
Actions
Public operations.
freeOptionsisStdinTtyparseArgsparseArgsFromSliceprettyOptionsprintHelpprintHelpTopicprintHelpTopicsOverviewprintParseErrorprintUnknownHelpTopicprintUsage
Types and contracts
Public types and contracts.
Namespaces
Public namespaces.
Values and defaults
Public values and defaults.
Source
Source: lib/pluck/src/cli/app.zig
zig
pub const exit_codes = @import("codes.zig");pub const ExitCode = exit_codes.ExitCode;const std = @import("std");const sys = @import("sys");const build_options = @import("build_options");const pluck = @import("../root.zig");const pretty = @import("pretty");const pretty_usage = @import("pretty_usage");const evaluator = pluck.evaluator;const help = pluck.help_topics;const def_order = pluck.definition_order;const Allocator = std.mem.Allocator;pub const DEFAULT_MAX_DEPTH: ?u32 = null;pub const DEFAULT_ITE_LIMIT: ?u64 = pluck.limits.default_agent_ite_limit;pub const version = build_options.version;const ParseValueResult = union(enum) { ok, unknown_option, invalid_value: []const u8,};const expected_time_limit = "a finite non-negative number of seconds (for example: 0.25 or 10)";const expected_non_negative_integer = "a non-negative integer";const expected_limit_integer = "a non-negative integer (0 means unlimited)";const expected_positive_integer = "an integer greater than 0";const expected_fallback_mode = "one of: error, lpsmc";const expected_var_order = "one of: strict, reverse, creation, topo, min-fill, auto";const usage_text = \\usage: pluck [OPTIONS] [FILES...] \\ pluck -e EXPR [OPTIONS] \\ pluck --help [topic];const help_text = \\Run probabilistic programs and evaluate one-off queries. \\ \\usage: pluck [OPTIONS] [FILES...] \\ pluck -e EXPR [OPTIONS] \\ pluck --help [topic] \\ \\Common workflows: \\ pluck model.pluck \\ pluck -e "(Marginal (flip 0.5))" \\ echo "(Marginal (flip 0.3))" | pluck - \\ pluck --json model.pluck \\ pluck --help topics \\ pluck --help syntax-overview \\ pluck --help Marginal \\ \\Options: \\ Help and metadata: \\ -h, --help [topic] Show general help or a specific topic \\ --version Show CLI version \\ \\ Input and output: \\ -e, --eval EXPR Evaluate one expression and exit \\ -j, --json Emit JSON Lines output for scripts/agents \\ -q, --silent Suppress non-essential output \\ -v, --verbose Print file loading and execution progress \\ \\ Execution controls: \\ --time-limit=SECS Inference time limit in seconds (default: unlimited) \\ --max-depth=N Recursion depth limit (default: unlimited, 0=unlimited) \\ --ite-limit=N BDD work quota (default: 10000000, 0=unlimited) \\ --sample-after-max-depth Sample flips beyond max-depth instead of erroring \\ --seed=N RNG seed for sampling queries (default: random) \\ -p, --parallel Enable parallel weighted model counting \\ \\ Inference fallback and ordering: \\ --fallback-mode=MODE Unsupported exact inference: error|lpsmc (default: error) \\ --fallback-k=N Top-k paths for lpsmc fallback (default: 10, >0) \\ --lpsmc-adaptive-k Enable adaptive per-subproblem k \\ --lpsmc-adaptive-k-max=N Adaptive-k upper bound (default: 64, >0) \\ --lpsmc-workers=N Parallel workers for lpsmc (default: 1, >0) \\ --var-order=MODE strict|reverse|creation|topo|min-fill|auto (default: strict) \\ --factor-max-branches=N Branch cap before WeightDD fallback (default: 64) \\ --weight-dd-max-nodes=N WeightDD node cap (default: 0, 0=unlimited) \\ \\Exit Codes: \\ 0 Success \\ 1 Parse/runtime/IO/help error \\ 2 Partial results due to resource limits \\ \\Need a topic index? \\ pluck --help topics;const parse_error_tail_text = \\usage: pluck [OPTIONS] [FILES...] \\ pluck -e EXPR [OPTIONS] \\ pluck --help [topic] \\ \\Run `pluck --help` for the full option reference. \\Run `pluck --help topics` to browse language/query help topics. \\ \\Common examples: \\ pluck model.pluck \\ pluck -e "(Marginal (flip 0.5))" \\ pluck --json model.pluck;const unknown_help_tail_text = \\Try one of these first: \\ pluck --help topics \\ pluck --help syntax-overview \\ pluck --help Marginal \\ pluck --help flip \\;pub const CLIOptions = struct { files: std.ArrayList([]const u8), time_limit: ?f64 = null, max_depth: ?u32 = DEFAULT_MAX_DEPTH, ite_limit: ?u64 = DEFAULT_ITE_LIMIT, sample_after_max_depth: bool = false, verbose: bool = false, help: bool = false, help_topic: ?[]const u8 = null, version: bool = false, parse_error: bool = false, parse_error_message: ?[]const u8 = null, parallel: bool = false, json: bool = false, silent: bool = false, eval_expr: ?[]const u8 = null, rng_seed: ?u64 = null, fallback_mode: evaluator.FallbackMode = .@"error", fallback_lpsmc_k: usize = 10, lpsmc_adaptive_k: bool = false, lpsmc_adaptive_k_max: usize = 64, lpsmc_workers: usize = 1, factor_max_branches: usize = 64, weight_dd_max_nodes: usize = 0, use_strict_order: bool = true, use_reverse_order: bool = false, var_order_fallback: bool = false, definition_order_mode: def_order.DefinitionOrderMode = .none,};pub fn parseArgs(allocator: Allocator, process_args: sys.process.Args) !CLIOptions { var args_arena = std.heap.ArenaAllocator.init(allocator); defer args_arena.deinit(); const args_z = try process_args.toSlice(args_arena.allocator()); var args_list: std.ArrayList([]const u8) = .empty; defer args_list.deinit(allocator); for (args_z) |arg_z| { try args_list.append(allocator, arg_z[0..arg_z.len]); } const args = try args_list.toOwnedSlice(allocator); defer allocator.free(args); const all_args = if (args.len > 0) args[1..] else args; return parseArgsFromSlice(allocator, all_args);}pub fn parseArgsFromSlice(allocator: Allocator, all_args: []const []const u8) !CLIOptions { var opts = CLIOptions{ .files = .empty, }; errdefer { for (opts.files.items) |f| { allocator.free(f); } opts.files.deinit(allocator); if (opts.eval_expr) |e| allocator.free(e); if (opts.help_topic) |topic| allocator.free(topic); if (opts.parse_error_message) |message| allocator.free(message); } var i: usize = 0; var seen_separator = false; while (i < all_args.len) : (i += 1) { const arg = all_args[i]; if (std.mem.eql(u8, arg, "--")) { seen_separator = true; continue; } if (seen_separator) { const duped = try allocator.dupe(u8, arg); try opts.files.append(allocator, duped); continue; } if (arg.len > 2 and arg[0] == '-' and arg[1] == '-') { const rest = arg[2..]; if (std.mem.indexOf(u8, rest, "=")) |eq_pos| { const key = rest[0..eq_pos]; const val = rest[eq_pos + 1 ..]; const result = try applyLongWithValue(&opts, allocator, key, val); switch (result) { .ok => {}, .unknown_option => { setUnknownLongOptionError(&opts, allocator, key); return opts; }, .invalid_value => |expected| { setInvalidLongValueError(&opts, allocator, key, val, expected); return opts; }, } } else { if (std.mem.eql(u8, rest, "help")) { opts.help = true; try maybeConsumeHelpTopic(&opts, allocator, all_args, &i); } else if (std.mem.eql(u8, rest, "version")) { opts.version = true; } else if (std.mem.eql(u8, rest, "verbose")) { opts.verbose = true; } else if (std.mem.eql(u8, rest, "json")) { opts.json = true; } else if (std.mem.eql(u8, rest, "silent")) { opts.silent = true; } else if (std.mem.eql(u8, rest, "parallel")) { opts.parallel = true; } else if (std.mem.eql(u8, rest, "sample-after-max-depth")) { opts.sample_after_max_depth = true; } else if (std.mem.eql(u8, rest, "lpsmc-adaptive-k")) { opts.lpsmc_adaptive_k = true; } else if (longNeedsValue(rest)) { if (i + 1 < all_args.len) { i += 1; const val = all_args[i]; const result = try applyLongWithValue(&opts, allocator, rest, val); switch (result) { .ok => {}, .unknown_option => { setUnknownLongOptionError(&opts, allocator, rest); return opts; }, .invalid_value => |expected| { setInvalidLongValueError(&opts, allocator, rest, val, expected); return opts; }, } } else { setMissingLongValueError(&opts, allocator, rest); return opts; } } else { setUnknownLongOptionError(&opts, allocator, rest); return opts; } } continue; } if (arg.len > 1 and arg[0] == '-') { if (arg.len == 1) { const duped = try allocator.dupe(u8, arg); try opts.files.append(allocator, duped); continue; } const ch = arg[1]; switch (ch) { 'h' => { opts.help = true; try maybeConsumeHelpTopic(&opts, allocator, all_args, &i); }, 'v' => opts.verbose = true, 'j' => opts.json = true, 'q' => opts.silent = true, 'p' => opts.parallel = true, 'e' => { const val = if (arg.len > 2) arg[2..] else blk: { if (i + 1 < all_args.len) { i += 1; break :blk all_args[i]; } else { setMissingShortValueError(&opts, allocator, 'e', "pluck -e \"(Marginal (flip 0.5))\""); return opts; } }; opts.eval_expr = try allocator.dupe(u8, val); }, else => { setUnknownShortOptionError(&opts, allocator, ch); return opts; }, } continue; } const duped = try allocator.dupe(u8, arg); try opts.files.append(allocator, duped); } return opts;}fn setParseError(opts: *CLIOptions, allocator: Allocator, comptime fmt: []const u8, args: anytype) void { opts.parse_error = true; opts.help = true; if (opts.parse_error_message != null) return; opts.parse_error_message = std.fmt.allocPrint(allocator, fmt, args) catch null;}fn setUnknownLongOptionError(opts: *CLIOptions, allocator: Allocator, name: []const u8) void { setParseError( opts, allocator, "error: unknown option '--{s}'\nHint: run `pluck --help` to list supported options.", .{name}, );}fn setUnknownShortOptionError(opts: *CLIOptions, allocator: Allocator, short_opt: u8) void { setParseError( opts, allocator, "error: unknown option '-{c}'\nHint: run `pluck --help` to list supported options.", .{short_opt}, );}fn setMissingLongValueError(opts: *CLIOptions, allocator: Allocator, name: []const u8) void { setParseError( opts, allocator, "error: option '--{s}' requires a value\nExample: {s}", .{ name, exampleForLongOption(name) }, );}fn setMissingShortValueError(opts: *CLIOptions, allocator: Allocator, short_opt: u8, example: []const u8) void { setParseError( opts, allocator, "error: option '-{c}' requires a value\nExample: {s}", .{ short_opt, example }, );}fn setInvalidLongValueError(opts: *CLIOptions, allocator: Allocator, name: []const u8, value: []const u8, expected: []const u8) void { setParseError( opts, allocator, "error: invalid value '{s}' for '--{s}'\nExpected: {s}", .{ value, name, expected }, );}fn exampleForLongOption(name: []const u8) []const u8 { if (std.mem.eql(u8, name, "eval")) return "pluck --eval \"(Marginal (flip 0.5))\""; if (std.mem.eql(u8, name, "time-limit")) return "pluck --time-limit=1.5 model.pluck"; if (std.mem.eql(u8, name, "max-depth")) return "pluck --max-depth=500 model.pluck"; if (std.mem.eql(u8, name, "ite-limit")) return "pluck --ite-limit=500000 model.pluck"; if (std.mem.eql(u8, name, "seed")) return "pluck --seed=42 model.pluck"; if (std.mem.eql(u8, name, "fallback-mode")) return "pluck --fallback-mode=lpsmc model.pluck"; if (std.mem.eql(u8, name, "fallback-k")) return "pluck --fallback-k=20 model.pluck"; if (std.mem.eql(u8, name, "lpsmc-adaptive-k-max")) return "pluck --lpsmc-adaptive-k --lpsmc-adaptive-k-max=128 model.pluck"; if (std.mem.eql(u8, name, "lpsmc-workers")) return "pluck --lpsmc-workers=4 model.pluck"; if (std.mem.eql(u8, name, "factor-max-branches")) return "pluck --factor-max-branches=128 model.pluck"; if (std.mem.eql(u8, name, "weight-dd-max-nodes")) return "pluck --weight-dd-max-nodes=200000 model.pluck"; if (std.mem.eql(u8, name, "var-order")) return "pluck --var-order=min-fill model.pluck"; return "pluck --help";}fn maybeConsumeHelpTopic(opts: *CLIOptions, allocator: Allocator, all_args: []const []const u8, index: *usize) !void { if (opts.help_topic != null) return; if (index.* + 1 >= all_args.len) return; const next = all_args[index.* + 1]; if (!isHelpTopicCandidate(next)) return; index.* += 1; opts.help_topic = try allocator.dupe(u8, next);}fn isHelpTopicCandidate(arg: []const u8) bool { if (arg.len == 0) return false; return arg[0] != '-';}fn longNeedsValue(name: []const u8) bool { const value_opts = [_][]const u8{ "eval", "time-limit", "max-depth", "ite-limit", "seed", "fallback-mode", "fallback-k", "lpsmc-adaptive-k-max", "lpsmc-workers", "factor-max-branches", "weight-dd-max-nodes", "var-order", }; for (&value_opts) |opt| { if (std.mem.eql(u8, name, opt)) return true; } return false;}fn applyLongWithValue(opts: *CLIOptions, allocator: Allocator, key: []const u8, val: []const u8) !ParseValueResult { if (std.mem.eql(u8, key, "help")) { opts.help = true; opts.help_topic = try allocator.dupe(u8, val); } else if (std.mem.eql(u8, key, "eval")) { opts.eval_expr = try allocator.dupe(u8, val); } else if (std.mem.eql(u8, key, "time-limit")) { const secs = std.fmt.parseFloat(f64, val) catch return .{ .invalid_value = expected_time_limit }; if (!std.math.isFinite(secs) or secs < 0) return .{ .invalid_value = expected_time_limit }; opts.time_limit = secs; } else if (std.mem.eql(u8, key, "max-depth")) { const md = std.fmt.parseInt(u32, val, 10) catch return .{ .invalid_value = expected_limit_integer }; opts.max_depth = if (md == 0) null else md; } else if (std.mem.eql(u8, key, "ite-limit")) { const il = std.fmt.parseInt(u64, val, 10) catch return .{ .invalid_value = expected_limit_integer }; opts.ite_limit = if (il == 0) null else il; } else if (std.mem.eql(u8, key, "seed")) { opts.rng_seed = std.fmt.parseInt(u64, val, 10) catch return .{ .invalid_value = expected_non_negative_integer }; } else if (std.mem.eql(u8, key, "fallback-mode")) { if (std.mem.eql(u8, val, "error")) { opts.fallback_mode = .@"error"; } else if (std.mem.eql(u8, val, "lpsmc")) { opts.fallback_mode = .lpsmc; } else { return .{ .invalid_value = expected_fallback_mode }; } } else if (std.mem.eql(u8, key, "fallback-k")) { const k = std.fmt.parseInt(usize, val, 10) catch return .{ .invalid_value = expected_positive_integer }; if (k == 0) return .{ .invalid_value = expected_positive_integer }; opts.fallback_lpsmc_k = k; } else if (std.mem.eql(u8, key, "lpsmc-adaptive-k-max")) { const k = std.fmt.parseInt(usize, val, 10) catch return .{ .invalid_value = expected_positive_integer }; if (k == 0) return .{ .invalid_value = expected_positive_integer }; opts.lpsmc_adaptive_k_max = k; } else if (std.mem.eql(u8, key, "lpsmc-workers")) { const workers = std.fmt.parseInt(usize, val, 10) catch return .{ .invalid_value = expected_positive_integer }; if (workers == 0) return .{ .invalid_value = expected_positive_integer }; opts.lpsmc_workers = workers; } else if (std.mem.eql(u8, key, "factor-max-branches")) { opts.factor_max_branches = std.fmt.parseInt(usize, val, 10) catch return .{ .invalid_value = expected_non_negative_integer }; } else if (std.mem.eql(u8, key, "weight-dd-max-nodes")) { opts.weight_dd_max_nodes = std.fmt.parseInt(usize, val, 10) catch return .{ .invalid_value = expected_non_negative_integer }; } else if (std.mem.eql(u8, key, "var-order")) { if (std.mem.eql(u8, val, "strict") or std.mem.eql(u8, val, "callstack")) { opts.use_strict_order = true; opts.use_reverse_order = false; opts.var_order_fallback = false; opts.definition_order_mode = .none; } else if (std.mem.eql(u8, val, "reverse") or std.mem.eql(u8, val, "callstack-reverse")) { opts.use_strict_order = true; opts.use_reverse_order = true; opts.var_order_fallback = false; opts.definition_order_mode = .none; } else if (std.mem.eql(u8, val, "creation")) { opts.use_strict_order = false; opts.use_reverse_order = false; opts.var_order_fallback = false; opts.definition_order_mode = .none; } else if (std.mem.eql(u8, val, "topo") or std.mem.eql(u8, val, "topological")) { opts.use_strict_order = true; opts.use_reverse_order = false; opts.var_order_fallback = false; opts.definition_order_mode = .topological; } else if (std.mem.eql(u8, val, "min-fill") or std.mem.eql(u8, val, "minfill") or std.mem.eql(u8, val, "min-degree")) { opts.use_strict_order = true; opts.use_reverse_order = false; opts.var_order_fallback = false; opts.definition_order_mode = .min_fill; } else if (std.mem.eql(u8, val, "auto")) { opts.use_strict_order = true; opts.use_reverse_order = false; opts.var_order_fallback = true; opts.definition_order_mode = .none; } else { return .{ .invalid_value = expected_var_order }; } } else { return .unknown_option; } return .ok;}pub fn freeOptions(allocator: Allocator, opts: *CLIOptions) void { for (opts.files.items) |f| { allocator.free(f); } opts.files.deinit(allocator); if (opts.eval_expr) |e| allocator.free(e); if (opts.help_topic) |topic| allocator.free(topic); if (opts.parse_error_message) |message| allocator.free(message);}pub fn printUsage(allocator: Allocator, writer: *std.Io.Writer, options: pretty.LayoutOptions) !void { try pretty_usage.writeUsageText(allocator, writer, usage_text, .{ .layout = options });}pub fn printHelp(allocator: Allocator, writer: *std.Io.Writer, options: pretty.LayoutOptions) !void { try writePrettyFmt(allocator, writer, options, "pluck {s} - Probabilistic Programming in Zig\n\n", .{version}); try pretty_usage.writeUsageText(allocator, writer, help_text, .{ .layout = options });}pub fn printHelpTopic(allocator: Allocator, writer: *std.Io.Writer, topic: []const u8, options: pretty.LayoutOptions) !bool { if (std.mem.eql(u8, topic, "topics")) { try printHelpTopicsOverview(allocator, writer, options); return true; } if (help.getHelpTopic(topic)) |help_topic| { var text = std.Io.Writer.Allocating.init(allocator); defer text.deinit(); try text.writer.print("{s}\n", .{help_topic.title}); for (help_topic.lines) |line| { try text.writer.print("{s}\n", .{line}); } try pretty_usage.writeUsageText(allocator, writer, text.writer.buffered(), .{ .layout = options }); return true; } return false;}pub fn printHelpTopicsOverview(allocator: Allocator, writer: *std.Io.Writer, options: pretty.LayoutOptions) !void { var text = std.Io.Writer.Allocating.init(allocator); defer text.deinit(); try text.writer.writeAll("Available help topics by category:\n"); try text.writer.writeAll(" primitives: "); try writeTopicList(&text.writer, &help.help_topics.primitives); try text.writer.writeAll(" queries: "); try writeTopicList(&text.writer, &help.help_topics.queries); try text.writer.writeAll(" syntax: "); try writeTopicList(&text.writer, &help.help_topics.syntax); try text.writer.writeAll(" types: "); try writeTopicList(&text.writer, &help.help_topics.types); try text.writer.writeAll(" stdlib: "); try writeTopicList(&text.writer, &help.help_topics.stdlib); try text.writer.writeAll(" commands: "); try writeTopicList(&text.writer, &help.help_topics.commands); try text.writer.writeAll("Use `pluck --help <topic>` to view details.\n"); try pretty_usage.writeUsageText(allocator, writer, text.writer.buffered(), .{ .layout = options });}pub fn printParseError(allocator: Allocator, writer: *std.Io.Writer, opts: *const CLIOptions, options: pretty.LayoutOptions) !void { if (opts.parse_error_message) |message| { try writePrettyFmt(allocator, writer, options, "{s}\n\n", .{message}); } else { try writePrettyText(writer, options, "error: invalid command-line arguments\n\n"); } try pretty_usage.writeUsageText(allocator, writer, parse_error_tail_text, .{ .layout = options });}pub fn printUnknownHelpTopic(allocator: Allocator, writer: *std.Io.Writer, topic: []const u8, options: pretty.LayoutOptions) !void { try writePrettyFmt(allocator, writer, options, "error: unknown help topic '{s}'\n\n", .{topic}); try pretty_usage.writeUsageText(allocator, writer, unknown_help_tail_text, .{ .layout = options }); try printHelpTopicsOverview(allocator, writer, options);}fn writeTopicList(writer: *std.Io.Writer, topics: []const []const u8) !void { for (topics, 0..) |topic, i| { if (i > 0) try writer.writeAll(", "); try writer.writeAll(topic); } try writer.writeAll("\n");}pub fn prettyOptions(file: std.Io.File) pretty.LayoutOptions { return pretty_usage.layoutOptions(file, .{});}fn writePrettyFmt(allocator: Allocator, writer: *std.Io.Writer, options: pretty.LayoutOptions, comptime fmt: []const u8, args: anytype) !void { const text = try std.fmt.allocPrint(allocator, fmt, args); defer allocator.free(text); try writePrettyText(writer, options, text);}fn writePrettyText(writer: *std.Io.Writer, options: pretty.LayoutOptions, text: []const u8) !void { try pretty.write(writer, .{ .text = text }, options);}pub fn isStdinTty() bool { const stdin = sys.stdio.stdin(); return stdin.isTty(sys.stdio.debugIo()) catch false;}fn expectParseErrorMessage(args: []const []const u8, expected: []const u8) !void { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, args); defer freeOptions(allocator, &opts); try std.testing.expect(opts.parse_error); try std.testing.expect(opts.help); try std.testing.expect(opts.parse_error_message != null); try std.testing.expectEqualStrings(expected, opts.parse_error_message.?);}fn renderHelp(allocator: Allocator, options: pretty.LayoutOptions) ![]u8 { var out = std.Io.Writer.Allocating.init(allocator); errdefer out.deinit(); try printHelp(allocator, &out.writer, options); return try out.toOwnedSlice();}fn renderParseError(allocator: Allocator, opts: *const CLIOptions, options: pretty.LayoutOptions) ![]u8 { var out = std.Io.Writer.Allocating.init(allocator); errdefer out.deinit(); try printParseError(allocator, &out.writer, opts, options); return try out.toOwnedSlice();}test "CLI defaults to the agent BDD quota and zero clears it" { const allocator = std.testing.allocator; var defaults = try parseArgsFromSlice(allocator, &.{}); defer freeOptions(allocator, &defaults); try std.testing.expectEqual(@as(?u64, pluck.limits.default_agent_ite_limit), defaults.ite_limit); var unlimited = try parseArgsFromSlice(allocator, &.{"--ite-limit=0"}); defer freeOptions(allocator, &unlimited); try std.testing.expect(unlimited.ite_limit == null);}test "parseArgsFromSlice captures help topic from --help value" { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, &[_][]const u8{ "--help", "discrete" }); defer freeOptions(allocator, &opts); try std.testing.expect(opts.help); try std.testing.expect(opts.help_topic != null); try std.testing.expectEqualStrings("discrete", opts.help_topic.?); try std.testing.expectEqual(@as(usize, 0), opts.files.items.len);}test "parseArgsFromSlice captures help topic from --help=topic" { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, &[_][]const u8{"--help=flip"}); defer freeOptions(allocator, &opts); try std.testing.expect(opts.help); try std.testing.expect(opts.help_topic != null); try std.testing.expectEqualStrings("flip", opts.help_topic.?);}test "parseArgsFromSlice captures help topic from -h value" { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, &[_][]const u8{ "-h", "Posterior" }); defer freeOptions(allocator, &opts); try std.testing.expect(opts.help); try std.testing.expect(opts.help_topic != null); try std.testing.expectEqualStrings("Posterior", opts.help_topic.?); try std.testing.expectEqual(@as(usize, 0), opts.files.items.len);}test "parseArgsFromSlice leaves help topic null for bare --help" { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, &[_][]const u8{"--help"}); defer freeOptions(allocator, &opts); try std.testing.expect(opts.help); try std.testing.expect(opts.help_topic == null);}test "parseArgsFromSlice parses --var-order modes" { const allocator = std.testing.allocator; var opts_reverse = try parseArgsFromSlice(allocator, &[_][]const u8{"--var-order=reverse"}); defer freeOptions(allocator, &opts_reverse); try std.testing.expect(opts_reverse.use_strict_order); try std.testing.expect(opts_reverse.use_reverse_order); try std.testing.expectEqual(def_order.DefinitionOrderMode.none, opts_reverse.definition_order_mode); var opts_creation = try parseArgsFromSlice(allocator, &[_][]const u8{ "--var-order", "creation" }); defer freeOptions(allocator, &opts_creation); try std.testing.expect(!opts_creation.use_strict_order); try std.testing.expect(!opts_creation.use_reverse_order); try std.testing.expectEqual(def_order.DefinitionOrderMode.none, opts_creation.definition_order_mode); var opts_topo = try parseArgsFromSlice(allocator, &[_][]const u8{"--var-order=topo"}); defer freeOptions(allocator, &opts_topo); try std.testing.expect(opts_topo.use_strict_order); try std.testing.expect(!opts_topo.use_reverse_order); try std.testing.expectEqual(def_order.DefinitionOrderMode.topological, opts_topo.definition_order_mode); var opts_min_fill = try parseArgsFromSlice(allocator, &[_][]const u8{"--var-order=min-fill"}); defer freeOptions(allocator, &opts_min_fill); try std.testing.expect(opts_min_fill.use_strict_order); try std.testing.expect(!opts_min_fill.use_reverse_order); try std.testing.expectEqual(def_order.DefinitionOrderMode.min_fill, opts_min_fill.definition_order_mode); var opts_auto = try parseArgsFromSlice(allocator, &[_][]const u8{"--var-order=auto"}); defer freeOptions(allocator, &opts_auto); try std.testing.expect(opts_auto.use_strict_order); try std.testing.expect(!opts_auto.use_reverse_order); try std.testing.expect(opts_auto.var_order_fallback); try std.testing.expectEqual(def_order.DefinitionOrderMode.none, opts_auto.definition_order_mode);}test "parseArgsFromSlice treats arguments after -- as files" { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, &[_][]const u8{ "--json", "--", "--not-an-option", "-e", "file.pluck" }); defer freeOptions(allocator, &opts); try std.testing.expect(!opts.parse_error); try std.testing.expect(opts.json); try std.testing.expectEqual(@as(usize, 3), opts.files.items.len); try std.testing.expectEqualStrings("--not-an-option", opts.files.items[0]); try std.testing.expectEqualStrings("-e", opts.files.items[1]); try std.testing.expectEqualStrings("file.pluck", opts.files.items[2]);}test "parseArgsFromSlice reports unknown long option with hint" { try expectParseErrorMessage( &[_][]const u8{"--bogus"}, "error: unknown option '--bogus'\nHint: run `pluck --help` to list supported options.", );}test "parseArgsFromSlice reports unknown short option with hint" { try expectParseErrorMessage( &[_][]const u8{"-z"}, "error: unknown option '-z'\nHint: run `pluck --help` to list supported options.", );}test "parseArgsFromSlice reports missing long option value with example" { try expectParseErrorMessage( &[_][]const u8{"--eval"}, "error: option '--eval' requires a value\nExample: pluck --eval \"(Marginal (flip 0.5))\"", );}test "parseArgsFromSlice reports missing short option value with example" { try expectParseErrorMessage( &[_][]const u8{"-e"}, "error: option '-e' requires a value\nExample: pluck -e \"(Marginal (flip 0.5))\"", );}test "parseArgsFromSlice reports invalid numeric value" { try expectParseErrorMessage( &[_][]const u8{"--max-depth=oops"}, "error: invalid value 'oops' for '--max-depth'\nExpected: a non-negative integer (0 means unlimited)", );}test "parseArgsFromSlice reports invalid enum value" { try expectParseErrorMessage( &[_][]const u8{"--fallback-mode=approx"}, "error: invalid value 'approx' for '--fallback-mode'\nExpected: one of: error, lpsmc", );}test "printHelp renders through pretty usage layout" { const allocator = std.testing.allocator; const output = try renderHelp(allocator, .{ .width = 48 }); defer allocator.free(output); try std.testing.expect(std.mem.indexOf(u8, output, "pluck " ++ version ++ " - Probabilistic Programming in Zig") != null); try std.testing.expect(std.mem.indexOf(u8, output, "usage: pluck [OPTIONS] [FILES...]") != null); try std.testing.expect(std.mem.indexOf(u8, output, "Options:\n") != null); try std.testing.expect(std.mem.indexOf(u8, output, "Show general\n help or a\n specific topic") != null); try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[") == null);}test "printHelp supports pretty color roles" { const allocator = std.testing.allocator; const output = try renderHelp(allocator, .{ .width = 88, .color = .ansi }); defer allocator.free(output); try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[") != null); try std.testing.expect(std.mem.indexOf(u8, output, "pluck [OPTIONS]") != null);}test "printParseError snapshot for invalid args" { const allocator = std.testing.allocator; var opts = try parseArgsFromSlice(allocator, &[_][]const u8{"--unknown-option"}); defer freeOptions(allocator, &opts); const output = try renderParseError(allocator, &opts, .{ .width = 88 }); defer allocator.free(output); try std.testing.expectEqualStrings( \\error: unknown option '--unknown-option' \\Hint: run `pluck --help` to list supported options. \\ \\usage: pluck [OPTIONS] [FILES...] \\ pluck -e EXPR [OPTIONS] \\ pluck --help [topic] \\ \\Run `pluck --help` for the full option reference. Run `pluck --help topics` to browse \\language/query help topics. \\ \\Common examples: \\ pluck model.pluck \\ pluck -e "(Marginal (flip 0.5))" \\ pluck --json model.pluck \\ , output, );}test "printHelpTopic supports topics overview alias" { const allocator = std.testing.allocator; var out_buffer = std.Io.Writer.Allocating.init(allocator); defer out_buffer.deinit(); try std.testing.expect(try printHelpTopic(allocator, &out_buffer.writer, "topics", .{ .width = 88 })); const out = out_buffer.writer.buffered(); try std.testing.expect(std.mem.indexOf(u8, out, "Available help topics by category:") != null); try std.testing.expect(std.mem.indexOf(u8, out, "primitives:") != null); try std.testing.expect(std.mem.indexOf(u8, out, "queries:") != null); try std.testing.expect(std.mem.indexOf(u8, out, "commands:") != null);}test "printUnknownHelpTopic includes guidance and topic list" { const allocator = std.testing.allocator; var out_buffer = std.Io.Writer.Allocating.init(allocator); defer out_buffer.deinit(); try printUnknownHelpTopic(allocator, &out_buffer.writer, "does-not-exist", .{ .width = 88 }); const out = out_buffer.writer.buffered(); try std.testing.expect(std.mem.indexOf(u8, out, "error: unknown help topic 'does-not-exist'") != null); try std.testing.expect(std.mem.indexOf(u8, out, "pluck --help topics") != null); try std.testing.expect(std.mem.indexOf(u8, out, "Available help topics by category:") != null);}Source: lib/pluck/src/cli/root.zig:2
zig
pub const app = @import("app.zig");Complete caller list for cli.app.freeOptions
9 direct callers.
lib.pluck.src.cli.app.expectParseErrorMessage[function] — private source atlib/pluck/src/cli/app.zig:592in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_CLI_defaults_to_the_agent_BDD_quota_and_zero_clears_it[function] — test source atlib/pluck/src/cli/app.zig:617in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_captures_help_topic_from_--help=topic[function] — test source atlib/pluck/src/cli/app.zig:639in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_captures_help_topic_from_--help_value[function] — test source atlib/pluck/src/cli/app.zig:628in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_captures_help_topic_from_-h_value[function] — test source atlib/pluck/src/cli/app.zig:649in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_leaves_help_topic_null_for_bare_--help[function] — test source atlib/pluck/src/cli/app.zig:660in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_parses_--var-order_modes[function] — test source atlib/pluck/src/cli/app.zig:669in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_treats_arguments_after_--_as_files[function] — test source atlib/pluck/src/cli/app.zig:704in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_printParseError_snapshot_for_invalid_args[function] — test source atlib/pluck/src/cli/app.zig:780in nearest public ownertiny.pluck.cli.app
Complete caller list for cli.app.parseArgsFromSlice
10 direct callers.
lib.pluck.src.cli.app.expectParseErrorMessage[function] — private source atlib/pluck/src/cli/app.zig:592in nearest public ownertiny.pluck.cli.apptiny.pluck.cli.app.parseArgs[function] atlib/pluck/src/cli/app.zig:149lib.pluck.src.cli.app.test_CLI_defaults_to_the_agent_BDD_quota_and_zero_clears_it[function] — test source atlib/pluck/src/cli/app.zig:617in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_captures_help_topic_from_--help=topic[function] — test source atlib/pluck/src/cli/app.zig:639in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_captures_help_topic_from_--help_value[function] — test source atlib/pluck/src/cli/app.zig:628in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_captures_help_topic_from_-h_value[function] — test source atlib/pluck/src/cli/app.zig:649in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_leaves_help_topic_null_for_bare_--help[function] — test source atlib/pluck/src/cli/app.zig:660in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_parses_--var-order_modes[function] — test source atlib/pluck/src/cli/app.zig:669in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_parseArgsFromSlice_treats_arguments_after_--_as_files[function] — test source atlib/pluck/src/cli/app.zig:704in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.test_printParseError_snapshot_for_invalid_args[function] — test source atlib/pluck/src/cli/app.zig:780in nearest public ownertiny.pluck.cli.app
Complete call list for cli.app.parseArgsFromSlice
8 direct calls.
lib.pluck.src.cli.app.applyLongWithValue[function] — private source atlib/pluck/src/cli/app.zig:409in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.longNeedsValue[function] — private source atlib/pluck/src/cli/app.zig:388in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.maybeConsumeHelpTopic[function] — private source atlib/pluck/src/cli/app.zig:374in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.setInvalidLongValueError[function] — private source atlib/pluck/src/cli/app.zig:349in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.setMissingLongValueError[function] — private source atlib/pluck/src/cli/app.zig:331in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.setMissingShortValueError[function] — private source atlib/pluck/src/cli/app.zig:340in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.setUnknownLongOptionError[function] — private source atlib/pluck/src/cli/app.zig:313in nearest public ownertiny.pluck.cli.applib.pluck.src.cli.app.setUnknownShortOptionError[function] — private source atlib/pluck/src/cli/app.zig:322in nearest public ownertiny.pluck.cli.app
Audit
| Definitions | 16 |
|---|---|
| Public names | 31 |
| Members | 27 |
| Version | 26.7.0 |
| Revision | daab053ee433 |