Skip to documentation
SLOP

tiny.pluck.cli.app

Reference tiny.pluck cli app

Defined in cli.

API (17)

Actions

Public operations.

Types and contracts

Public types and contracts.

Namespaces

Public namespaces.

Values and defaults

Public values and defaults.

No direct callersNo direct callscliapp
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callsprivate sourcelib.pluck.src.cli.appexpectParseErrorMessagetest sourcelib.pluck.src.cli.apptest: CLI defaults to the agent BDD q...test sourcelib.pluck.src.cli.apptest: parseArgsFromSlice captures hel...test sourcelib.pluck.src.cli.apptest: parseArgsFromSlice captures hel...test sourcelib.pluck.src.cli.apptest: parseArgsFromSlice captures hel...+4 morecli.appfreeOptions
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callerscli.appparseArgsFromSlicecli.appparseArgs
Static calls · unresolved targets: 1 · external targets: 6.
Called byCallsprivate sourcelib.pluck.src.cli.appexpectParseErrorMessagecli.appparseArgstest sourcelib.pluck.src.cli.apptest: CLI defaults to the agent BDD q...test sourcelib.pluck.src.cli.apptest: parseArgsFromSlice captures hel...test sourcelib.pluck.src.cli.apptest: parseArgsFromSlice captures hel...+5 moreprivate sourcelib.pluck.src.cli.appapplyLongWithValueprivate sourcelib.pluck.src.cli.applongNeedsValueprivate sourcelib.pluck.src.cli.appmaybeConsumeHelpTopicprivate sourcelib.pluck.src.cli.appsetInvalidLongValueErrorprivate sourcelib.pluck.src.cli.appsetMissingLongValueError+3 morecli.appparseArgsFromSlice
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallsprivate sourcelib.pluck.src.cli.apprenderHelpprivate sourcelib.pluck.src.cli.appwritePrettyFmtcli.appprintHelp
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.pluck.src.cli.apptest: printHelpTopic supports topics ...cli.appprintHelpTopicsOverviewcli.appprintHelpTopic
Static calls · unresolved targets: 1 · external targets: 4.
Called byCallscli.appprintHelpTopiccli.appprintUnknownHelpTopicprivate sourcelib.pluck.src.cli.appwriteTopicListcli.appprintHelpTopicsOverview
Static calls · unresolved targets: 0 · external targets: 4.
Called byCallsprivate sourcelib.pluck.src.cli.apprenderParseErrorprivate sourcelib.pluck.src.cli.appwritePrettyFmtprivate sourcelib.pluck.src.cli.appwritePrettyTextcli.appprintParseError
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.pluck.src.cli.apptest: printUnknownHelpTopic includes ...cli.appprintHelpTopicsOverviewprivate sourcelib.pluck.src.cli.appwritePrettyFmtcli.appprintUnknownHelpTopic
Static calls · unresolved targets: 0 · external targets: 1.

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.

Complete caller list for cli.app.parseArgsFromSlice

10 direct callers.

Complete call list for cli.app.parseArgsFromSlice

8 direct calls.

Audit

Definitions16
Public names31
Members27
Version26.7.0
Revisiondaab053ee433