mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Merge pull request #2458 from lightpanda-io/nikneym/cli-help-rework
`help`: rework `help` command
This commit is contained in:
324
src/Config.zig
324
src/Config.zig
@@ -203,7 +203,6 @@ const Commands = cli.Builder(.{
|
||||
.shared_options = CommonOptions,
|
||||
},
|
||||
.{ .name = "version", .options = .{} },
|
||||
.{ .name = "help", .positional = .{ .name = "subcommand", .type = ?[]const u8 }, .options = .{} },
|
||||
});
|
||||
|
||||
pub const RunMode = Commands.Enum;
|
||||
@@ -531,292 +530,51 @@ pub const HttpHeaders = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn printUsageAndExit(self: *const Config, success: bool) void {
|
||||
// MAX_HELP_LEN|
|
||||
const common_options =
|
||||
\\
|
||||
\\--insecure-disable-tls-host-verification
|
||||
\\ Disables host verification on all HTTP requests. This is an
|
||||
\\ advanced option which should only be set if you understand
|
||||
\\ and accept the risk of disabling host verification.
|
||||
\\
|
||||
\\--obey-robots
|
||||
\\ Fetches and obeys the robots.txt (if available) of the web pages
|
||||
\\ we make requests towards.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--disable-subframes
|
||||
\\ Skip loading <iframe> elements. The HTML parser registers them
|
||||
\\ in the DOM but no child frame, document fetch, or
|
||||
\\ Page.frameAttached / Runtime.executionContextCreated events are
|
||||
\\ produced. Useful for pages that load many analytics / pixel
|
||||
\\ iframes where each subframe navigation invalidates driver-side
|
||||
\\ executionContextIds (lightpanda-io/browser#2400). On the CDP
|
||||
\\ serve path, drivers can also toggle this per-session via the
|
||||
\\ LP.configureLoading method.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--disable-workers
|
||||
\\ Skip loading dedicated Web Workers. The Worker constructor
|
||||
\\ still returns a Worker object so calling pages do not throw,
|
||||
\\ but no script fetch is initiated and the worker scope's
|
||||
\\ eval never runs (postMessage from the page to the worker is
|
||||
\\ queued indefinitely). Sidesteps a v8 entered-context
|
||||
\\ corruption that crashes the process when an in-page Worker
|
||||
\\ completes its script fetch under specific HTTP-proxy timing
|
||||
\\ conditions on Shopify storefront pages. Drivers can also
|
||||
\\ toggle this per-session via the LP.configureLoading method.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--block-private-networks
|
||||
\\ Blocks HTTP requests to private/internal IP addresses
|
||||
\\ after DNS resolution. Useful for sandboxing, multi-tenant
|
||||
\\ deployments, and preventing access to internal infrastructure
|
||||
\\ regardless of what triggers the request (JavaScript, HTML
|
||||
\\ resources, redirects, etc.).
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--block-cidrs
|
||||
\\ Additional CIDR ranges to block, comma-separated.
|
||||
\\ Prefix with '-' to allow (exempt from blocking).
|
||||
\\ e.g. --block-cidrs 169.254.169.254/32,fd00:ec2::254/128
|
||||
\\ e.g. --block-cidrs 10.0.0.0/8,-10.0.0.42/32
|
||||
\\ Can be used standalone or combined with --block-private-networks.
|
||||
\\
|
||||
\\--http-proxy The HTTP proxy to use for all HTTP requests.
|
||||
\\ A username:password can be included for basic authentication.
|
||||
\\ Defaults to none.
|
||||
\\
|
||||
\\--proxy-bearer-token
|
||||
\\ The <token> to send for bearer authentication with the proxy
|
||||
\\ Proxy-Authorization: Bearer <token>
|
||||
\\
|
||||
\\--http-max-concurrent
|
||||
\\ The maximum number of concurrent HTTP requests.
|
||||
\\ Defaults to 10.
|
||||
\\
|
||||
\\--http-max-host-open
|
||||
\\ The maximum number of open connection to a given host:port.
|
||||
\\ Defaults to 4.
|
||||
\\
|
||||
\\--http-connect-timeout
|
||||
\\ The time, in milliseconds, for establishing an HTTP connection
|
||||
\\ before timing out. 0 means it never times out.
|
||||
\\ Defaults to 0.
|
||||
\\
|
||||
\\--http-timeout
|
||||
\\ The maximum time, in milliseconds, the transfer is allowed
|
||||
\\ to complete. 0 means it never times out.
|
||||
\\ Defaults to 10000.
|
||||
\\
|
||||
\\--http-max-response-size
|
||||
\\ Limits the acceptable response size for any request
|
||||
\\ (e.g. XHR, fetch, script loading, ...).
|
||||
\\ Defaults to no limit.
|
||||
\\
|
||||
\\--ws-max-concurrent
|
||||
\\ The maximum number of concurrent WebSocket connections.
|
||||
\\ Defaults to 8.
|
||||
\\
|
||||
\\--log-level The log level: debug, info, warn, error or fatal.
|
||||
\\ Defaults to
|
||||
++ (if (builtin.mode == .Debug) " info." else "warn.") ++
|
||||
\\
|
||||
\\
|
||||
\\--log-format The log format: pretty or logfmt.
|
||||
\\ Defaults to
|
||||
++ (if (builtin.mode == .Debug) " pretty." else " logfmt.") ++
|
||||
\\
|
||||
\\
|
||||
\\--log-filter-scopes
|
||||
\\ Filter out too verbose logs per scope:
|
||||
\\ http, unknown_prop, event, ...
|
||||
\\
|
||||
\\--user-agent Override the User-Agent header entirely
|
||||
\\ User-Agent mustn't impersonate other browser.
|
||||
\\ Any value containing "Mozilla" is forbidden.
|
||||
\\ The browser will continue to send Sec-Ch-Ua header.
|
||||
\\ Incompatible with --user-agent-suffix
|
||||
\\
|
||||
\\--user-agent-suffix
|
||||
\\ Suffix to append to the Lightpanda/X.Y User-Agent
|
||||
\\
|
||||
\\--web-bot-auth-key-file
|
||||
\\ Path to the Ed25519 private key PEM file.
|
||||
\\
|
||||
\\--web-bot-auth-keyid
|
||||
\\ The JWK thumbprint of your public key.
|
||||
\\
|
||||
\\--web-bot-auth-domain
|
||||
\\ Your domain e.g. yourdomain.com
|
||||
\\
|
||||
\\--http-cache-dir
|
||||
\\ Path to a directory to use as a Filesystem Cache for network resources.
|
||||
\\ Omitting this will result is no caching.
|
||||
\\ Defaults to no caching.
|
||||
\\
|
||||
\\--storage-engine
|
||||
\\ The storage engine to use. Choices are: none, sqlite.
|
||||
\\ Default to none.
|
||||
\\
|
||||
\\--storage-sqlite-path
|
||||
\\ Path to SQLite database file for persistent storage.
|
||||
\\ Use ":memory:" for in-memory storage.
|
||||
;
|
||||
pub fn printUsageAndExit(self: *const Config, help_for: RunMode, success: bool) void {
|
||||
const exec_name = self.exec_name;
|
||||
const Help = @import("help.zon");
|
||||
const is_debug = builtin.mode == .Debug;
|
||||
const info_or_warn = if (comptime is_debug) "info" else "warn";
|
||||
const pretty_or_logfmt = if (comptime is_debug) "pretty" else "logfmt";
|
||||
const comptimePrint = std.fmt.comptimePrint;
|
||||
|
||||
// MAX_HELP_LEN|
|
||||
const fetch_options =
|
||||
\\fetch command
|
||||
\\Fetches the specified URL
|
||||
\\Example: {0s} fetch --dump html https://lightpanda.io/
|
||||
\\
|
||||
\\Options:
|
||||
\\--dump Dumps document to stdout.
|
||||
\\ Argument must be 'html', 'markdown', 'semantic_tree', or 'semantic_tree_text'.
|
||||
\\ Defaults to no dump.
|
||||
\\
|
||||
\\--strip-mode Comma separated list of tag groups to remove from dump
|
||||
\\ the dump. e.g. --strip-mode js,css
|
||||
\\ - "js" script and link[as=script, rel=preload]
|
||||
\\ - "ui" includes img, picture, video, css and svg
|
||||
\\ - "css" includes style and link[rel=stylesheet]
|
||||
\\ - "full" includes js, ui and css
|
||||
\\
|
||||
\\--with-base Add a <base> tag in dump. Defaults to false.
|
||||
\\
|
||||
\\--with-frames Includes the contents of iframes. Defaults to false.
|
||||
\\
|
||||
\\--wait-ms Wait time in milliseconds. Supersedes all other --wait
|
||||
\\ parameters.
|
||||
\\ Defaults to 5000.
|
||||
\\
|
||||
\\--wait-until Wait until the specified event. Checked before the other
|
||||
\\ --wait- options. Supported events: load, domcontentloaded,
|
||||
\\ networkidle, done.
|
||||
\\ Defaults to 'done'. If --wait-selector, --wait-script or
|
||||
\\ --wait-script-file are specified, defaults to none.
|
||||
\\
|
||||
\\--wait-selector Wait for an element matching the CSS selector to appear.
|
||||
\\ Checked after --wait-until condition is met.
|
||||
\\
|
||||
\\--wait-script Wait for a JavaScript expression to return truthy.
|
||||
\\ Checked after --wait-until condition is met.
|
||||
\\
|
||||
\\--wait-script-file
|
||||
\\ Like --wait-script, but reads the script from a file.
|
||||
\\
|
||||
\\--inject-script JavaScript to execute as the document's <head> is
|
||||
\\ parsed, before any other scripts in the page run.
|
||||
\\ Can be passed multiple times; scripts run in order.
|
||||
\\
|
||||
\\--inject-script-file
|
||||
\\ Like --inject-script, but reads the script from a file.
|
||||
\\ Can be passed multiple times; can be mixed with
|
||||
\\ --inject-script and runs in CLI order.
|
||||
\\
|
||||
\\--terminate-ms Hard deadline in milliseconds. After this time elapses,
|
||||
\\ JavaScript execution is forcibly terminated (e.g. for
|
||||
\\ pages with endless scripts). Unlike --wait-ms, which
|
||||
\\ only stops waiting, --terminate-ms aborts the page.
|
||||
\\ Defaults to no terminate.
|
||||
\\
|
||||
\\--cookie Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
\\
|
||||
\\--cookie-jar Path to a JSON file to save cookies to on exit (write-only).
|
||||
\\ Defaults to no cookie saving.
|
||||
\\
|
||||
++ common_options;
|
||||
|
||||
// MAX_HELP_LEN|
|
||||
const serve_options =
|
||||
\\serve command
|
||||
\\Starts a websocket CDP server
|
||||
\\Example: {0s} serve --host 127.0.0.1 --port 9222
|
||||
\\
|
||||
\\Options:
|
||||
\\--host Host of the CDP server
|
||||
\\ Defaults to "127.0.0.1"
|
||||
\\
|
||||
\\--port Port of the CDP server
|
||||
\\ Defaults to 9222
|
||||
\\
|
||||
\\--advertise-host
|
||||
\\ The host to advertise, e.g. in the /json/version response.
|
||||
\\ Useful, for example, when --host is 0.0.0.0.
|
||||
\\ Defaults to --host value
|
||||
\\
|
||||
\\--cdp-max-connections
|
||||
\\ Maximum number of simultaneous CDP connections.
|
||||
\\ Defaults to 16.
|
||||
\\
|
||||
\\--cdp-max-pending-connections
|
||||
\\ Maximum pending connections in the accept queue.
|
||||
\\ Defaults to 128.
|
||||
\\
|
||||
\\--cookie Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
\\
|
||||
++ common_options;
|
||||
|
||||
// MAX_HELP_LEN|
|
||||
const mcp_options =
|
||||
\\mcp command
|
||||
\\Starts an MCP (Model Context Protocol) server over stdio
|
||||
\\Example: {0s} mcp
|
||||
\\
|
||||
\\Options:
|
||||
\\--cookie Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
\\
|
||||
\\--cookie-jar Path to a JSON file to save cookies to on exit (write-only).
|
||||
\\ Defaults to no cookie saving.
|
||||
\\
|
||||
++ common_options;
|
||||
|
||||
// MAX_HELP_LEN|
|
||||
const usage =
|
||||
\\usage: {0s} command [options] [URL]
|
||||
\\
|
||||
\\Command can be either 'fetch', 'serve', 'mcp' or 'help'
|
||||
\\
|
||||
++ fetch_options ++
|
||||
\\
|
||||
\\
|
||||
++ serve_options ++
|
||||
\\
|
||||
\\
|
||||
++ mcp_options ++
|
||||
\\
|
||||
\\
|
||||
\\version command
|
||||
\\Displays the version of {0s}
|
||||
\\
|
||||
\\help command
|
||||
\\Displays this message
|
||||
\\
|
||||
;
|
||||
|
||||
// When called with a subcommand argument,
|
||||
// print only the relevant subcommand section instead of the full help.
|
||||
switch (self.mode) {
|
||||
.help => |h| if (h.subcommand) |sub| {
|
||||
if (std.mem.eql(u8, sub, "fetch")) {
|
||||
std.debug.print(fetch_options ++ "\n", .{self.exec_name});
|
||||
} else if (std.mem.eql(u8, sub, "serve")) {
|
||||
std.debug.print(serve_options ++ "\n", .{self.exec_name});
|
||||
} else if (std.mem.eql(u8, sub, "mcp")) {
|
||||
std.debug.print(mcp_options ++ "\n", .{self.exec_name});
|
||||
} else {
|
||||
std.debug.print(usage, .{self.exec_name});
|
||||
}
|
||||
if (success) return std.process.cleanExit();
|
||||
std.process.exit(1);
|
||||
switch (help_for) {
|
||||
// Requested help for everything.
|
||||
.help => {
|
||||
const template = comptimePrint(
|
||||
\\
|
||||
\\Command can be either "fetch", "serve", "mcp" or "help".
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
, .{ Help.fetch, Help.serve, Help.mcp, Help.common, Help.version, Help.help });
|
||||
std.debug.print(template, .{ exec_name, info_or_warn, pretty_or_logfmt });
|
||||
},
|
||||
inline .fetch, .serve, .mcp => |tag| {
|
||||
const template = comptimePrint(
|
||||
\\{s}
|
||||
\\
|
||||
\\{s}
|
||||
\\
|
||||
, .{ @field(Help, @tagName(tag)), Help.common });
|
||||
std.debug.print(template, .{ exec_name, info_or_warn, pretty_or_logfmt });
|
||||
},
|
||||
.version => {
|
||||
const template = Help.version ++ "\n";
|
||||
std.debug.print(template, .{exec_name});
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
std.debug.print(usage, .{self.exec_name});
|
||||
if (success) {
|
||||
return std.process.cleanExit();
|
||||
}
|
||||
|
||||
133
src/cli.zig
133
src/cli.zig
@@ -23,7 +23,24 @@ const log = lp.log;
|
||||
|
||||
/// Comptime CLI builder that generates a tagged union parser from a
|
||||
/// declarative command recipe. Each command becomes a union variant whose
|
||||
/// payload is a struct with one field per option.
|
||||
/// payload is a struct with one field per option. A `help` variant is added
|
||||
/// automatically; do not include it in the recipe.
|
||||
///
|
||||
/// ## Parsing behavior
|
||||
///
|
||||
/// `parse` reads `std.process.args`, picks a command by the first non-exec
|
||||
/// argument, then walks the rest as `--flag value` pairs. Quirks:
|
||||
///
|
||||
/// - When no command is given, the parser defaults to `serve`.
|
||||
/// - `help`, `help <command>`, `<command> help`, and `<command> --help` all
|
||||
/// yield the `help` union variant. When a command is named (in either
|
||||
/// position), the variant carries that command's enum tag so callers can
|
||||
/// print command-specific help; bare `help` and `help help` carry the
|
||||
/// `.help` tag. An unknown name after `help` returns
|
||||
/// `error.UnknownCommand`.
|
||||
/// - Legacy fallback: if the first argument starts with `--` and matches a
|
||||
/// known fetch/serve flag, the parser sniffs the command from it and
|
||||
/// re-parses argv. Only exists for backwards compatibility.
|
||||
///
|
||||
/// ## Command descriptor fields
|
||||
///
|
||||
@@ -34,8 +51,9 @@ const log = lp.log;
|
||||
/// command. Useful for common flags shared across commands.
|
||||
/// - `positional: struct` (optional) — a single positional argument with
|
||||
/// `.name` and `.type`. Type must be an optional pointer-to-u8 slice
|
||||
/// (e.g. `?[:0]const u8`). Positionals can appear anywhere in argv and
|
||||
/// must be provided; a missing positional returns `error.MissingArgument`.
|
||||
/// (e.g. `?[:0]const u8`); it defaults to `null` and may appear anywhere
|
||||
/// in argv. Passing it more than once returns
|
||||
/// `error.TooManyPositionalArguments`.
|
||||
///
|
||||
/// ## Option descriptor fields
|
||||
///
|
||||
@@ -49,6 +67,8 @@ const log = lp.log;
|
||||
/// `bool` or packed-struct options.
|
||||
/// - `validator: fn` (optional) — custom parse function that replaces the
|
||||
/// built-in type switch. See the validator section below.
|
||||
/// - `variants: tuple` (optional) — alternate flag names that write into
|
||||
/// the same field. See the variants section below.
|
||||
///
|
||||
/// ## Supported types and their defaults
|
||||
///
|
||||
@@ -60,10 +80,10 @@ const log = lp.log;
|
||||
/// - `[]const u8`, `[:0]const u8` (and mutable variants) — string slices
|
||||
/// duped from argv. Sentinel is preserved. Requires `default` unless `?`.
|
||||
/// - Enums — parsed via `std.meta.stringToEnum`. Returns
|
||||
/// `error.UnknownArgument` on a bad value. Requires `default` unless `?`.
|
||||
/// `error.InvalidArgument` on a bad value. Requires `default` unless `?`.
|
||||
/// - Packed structs of `bool` fields — parsed from a comma-separated list
|
||||
/// (e.g. `--strip-mode js,css`). The literal `"full"` sets every field.
|
||||
/// Unknown names return `error.UnknownArgument`. Requires `default`.
|
||||
/// Unknown names return `error.InvalidArgument`. Requires `default`.
|
||||
/// `multiple` is not supported.
|
||||
/// - Optional types default to `null` when `default` is omitted.
|
||||
///
|
||||
@@ -80,6 +100,15 @@ const log = lp.log;
|
||||
/// When a validator is present, the built-in type switch is skipped entirely.
|
||||
/// The validator owns advancing the iterator and is free to peek ahead.
|
||||
///
|
||||
/// ## Variants
|
||||
///
|
||||
/// A `variants` tuple lets multiple flag names write into the same field
|
||||
/// using different parse logic. Each variant has its own `.name` and an
|
||||
/// optional `.validator` (with the same signatures as above); the option's
|
||||
/// `type` and `multiple` are inherited. Useful for "value or file" pairs:
|
||||
/// e.g. `--wait-script "code"` vs `--wait-script-file path/to/script.js`,
|
||||
/// both populating the same `wait_script` field.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```zig
|
||||
@@ -113,19 +142,25 @@ const log = lp.log;
|
||||
/// .{ .name = "strip_mode", .type = StripMode, .default = .{} },
|
||||
/// .{ .name = "wait_until", .type = ?WaitUntil },
|
||||
/// .{ .name = "extra_header", .type = []const u8, .multiple = true },
|
||||
/// .{
|
||||
/// .name = "wait_script",
|
||||
/// .type = ?[:0]const u8,
|
||||
/// .variants = .{
|
||||
/// .{ .name = "wait_script_file", .validator = readScriptFile },
|
||||
/// },
|
||||
/// },
|
||||
/// },
|
||||
/// .shared_options = CommonOptions,
|
||||
/// },
|
||||
/// .{ .name = "version", .options = .{} },
|
||||
/// .{ .name = "help", .options = .{} },
|
||||
/// });
|
||||
///
|
||||
/// const _, const cmd = try Cli.parse(arena);
|
||||
/// switch (cmd) {
|
||||
/// .serve => |opts| listen(opts.host, opts.port),
|
||||
/// .fetch => |opts| fetch(opts.url.?, opts.dump),
|
||||
/// .fetch => |opts| fetch(opts.url orelse return error.UrlRequired, opts.dump),
|
||||
/// .version => printVersion(),
|
||||
/// .help => printHelp(),
|
||||
/// .help => |tag| printHelp(tag),
|
||||
/// }
|
||||
/// ```
|
||||
pub fn Builder(comptime commands: anytype) type {
|
||||
@@ -134,17 +169,24 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
|
||||
/// Enum type for provided commands.
|
||||
pub const Enum = blk: {
|
||||
var enum_fields: [commands.len]std.builtin.Type.EnumField = undefined;
|
||||
for (commands, 0..) |command, i| {
|
||||
const len = commands.len + 1;
|
||||
var enum_fields: [len]std.builtin.Type.EnumField = undefined;
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < commands.len) : (i += 1) {
|
||||
const command = commands[i];
|
||||
enum_fields[i] = .{ .name = command.name, .value = i };
|
||||
}
|
||||
|
||||
// Entry for help.
|
||||
enum_fields[i] = .{ .name = "help", .value = i };
|
||||
|
||||
break :blk @Type(.{
|
||||
.@"enum" = .{
|
||||
.decls = &.{},
|
||||
.fields = &enum_fields,
|
||||
.is_exhaustive = true,
|
||||
.tag_type = std.math.IntFittingRange(0, commands.len),
|
||||
.tag_type = std.math.IntFittingRange(0, len),
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -212,8 +254,12 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
|
||||
/// Union type for provided commands.
|
||||
pub const Union = blk: {
|
||||
var union_fields: [commands.len]std.builtin.Type.UnionField = undefined;
|
||||
for (commands, 0..) |command, i| {
|
||||
const len = commands.len + 1;
|
||||
var union_fields: [len]std.builtin.Type.UnionField = undefined;
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < commands.len) : (i += 1) {
|
||||
const command = commands[i];
|
||||
const Command = @TypeOf(command);
|
||||
const options = command.options;
|
||||
|
||||
@@ -247,6 +293,10 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
union_fields[i] = .{ .name = command.name, .type = T, .alignment = @alignOf(T) };
|
||||
}
|
||||
|
||||
// Entry for help; just takes `Enum` itself.
|
||||
const Help = Enum;
|
||||
union_fields[i] = .{ .name = "help", .type = Help, .alignment = @alignOf(Help) };
|
||||
|
||||
break :blk @Type(.{
|
||||
.@"union" = .{
|
||||
.decls = &.{},
|
||||
@@ -268,27 +318,43 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
inline for (commands) |command| {
|
||||
// Match a command.
|
||||
if (std.mem.eql(u8, cmd_str, command.name)) {
|
||||
const cmd_parsed = parseCommand(allocator, command, &args) catch |err| {
|
||||
if (err == error.HelpRequested) {
|
||||
// <subcommand> help requested, return help <subcommand>
|
||||
var h = @FieldType(Union, "help"){};
|
||||
if (@hasField(@FieldType(Union, "help"), "subcommand")) {
|
||||
h.subcommand = command.name;
|
||||
}
|
||||
return .{ exec_name, @unionInit(Union, "help", h) };
|
||||
} else return err;
|
||||
};
|
||||
const cmd_parsed = try parseCommand(allocator, command, &args);
|
||||
return .{ exec_name, cmd_parsed };
|
||||
}
|
||||
}
|
||||
|
||||
// Help is not in `commands`; so, we have to special case it.
|
||||
if (std.mem.eql(u8, cmd_str, "help")) {
|
||||
// Check if we're followed by a command name.
|
||||
const command_name: []const u8 = args.next() orelse {
|
||||
// "lightpanda help"; short-circuit.
|
||||
return .{ exec_name, @unionInit(Union, "help", .help) };
|
||||
};
|
||||
|
||||
inline for (commands) |command| {
|
||||
if (std.mem.eql(u8, command_name, command.name)) {
|
||||
return .{
|
||||
exec_name,
|
||||
@unionInit(Union, "help", std.meta.stringToEnum(Enum, command.name).?),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Treat `help help` as the full help.
|
||||
if (std.mem.eql(u8, command_name, "help")) {
|
||||
return .{ exec_name, @unionInit(Union, "help", .help) };
|
||||
}
|
||||
|
||||
log.fatal(.app, "unknown command", .{ .arg = command_name });
|
||||
return error.UnknownCommand;
|
||||
}
|
||||
|
||||
// Last resort, try sniffing.
|
||||
const command_enum = try sniffCommand(cmd_str);
|
||||
|
||||
// `help` takes no arguments; short-circuit so the sniffed flag
|
||||
// isn't re-parsed as an unknown option.
|
||||
// Legacy `--help` situation.
|
||||
if (command_enum == .help) {
|
||||
return .{ exec_name, .{ .help = .{} } };
|
||||
return .{ exec_name, @unionInit(Union, "help", .help) };
|
||||
}
|
||||
|
||||
// "cmd_str" wasn't a command but an option. We can't reset args, but
|
||||
@@ -301,16 +367,7 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
|
||||
inline for (commands) |command| {
|
||||
if (std.mem.eql(u8, @tagName(command_enum), command.name)) {
|
||||
const cmd_parsed = parseCommand(allocator, command, &args) catch |err| {
|
||||
if (err == error.HelpRequested) {
|
||||
// <subcommand> help requested, return help <subcommand>
|
||||
var h = @FieldType(Union, "help"){};
|
||||
if (@hasField(@FieldType(Union, "help"), "subcommand")) {
|
||||
h.subcommand = command.name;
|
||||
}
|
||||
return .{ exec_name, @unionInit(Union, "help", h) };
|
||||
} else return err;
|
||||
};
|
||||
const cmd_parsed = try parseCommand(allocator, command, &args);
|
||||
return .{ exec_name, cmd_parsed };
|
||||
}
|
||||
}
|
||||
@@ -607,9 +664,9 @@ pub fn Builder(comptime commands: anytype) type {
|
||||
}
|
||||
}
|
||||
|
||||
// Subcommand help: `lightpanda fetch help` or `lightpanda fetch --help`
|
||||
// Subcommand help: `lightpanda fetch help` or `lightpanda fetch --help`.
|
||||
if (std.mem.eql(u8, option_name, "help") or std.mem.eql(u8, option_name, "--help")) {
|
||||
return error.HelpRequested;
|
||||
return @unionInit(Union, "help", std.meta.stringToEnum(Enum, command.name).?);
|
||||
}
|
||||
|
||||
// Encountered an option we don't know of.
|
||||
|
||||
249
src/help.zon
Normal file
249
src/help.zon
Normal file
@@ -0,0 +1,249 @@
|
||||
.{
|
||||
// MAX_HELP_LEN|
|
||||
.serve =
|
||||
\\serve command
|
||||
\\Starts a WebSocket CDP server.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ {0s} serve [OPTIONS] [COMMON_OPTIONS]
|
||||
\\
|
||||
\\Options:
|
||||
\\--host <HOST> Host of the CDP server.
|
||||
\\ Defaults to "127.0.0.1".
|
||||
\\
|
||||
\\--port <INT> Port of the CDP server.
|
||||
\\ Defaults to 9222.
|
||||
\\
|
||||
\\--advertise-host <HOST>
|
||||
\\ The host to advertise, e.g. in the /json/version
|
||||
\\ response. Useful, for example, when --host is 0.0.0.0.
|
||||
\\ Defaults to --host value.
|
||||
\\
|
||||
\\--cdp-max-connections <INT>
|
||||
\\ Maximum number of simultaneous CDP connections.
|
||||
\\ Defaults to 16.
|
||||
\\
|
||||
\\--cdp-max-pending-connections <INT>
|
||||
\\ Maximum pending connections in the accept queue.
|
||||
\\ Defaults to 128.
|
||||
\\
|
||||
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
,
|
||||
.fetch =
|
||||
\\fetch command
|
||||
\\Fetches the specified URL.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ {0s} fetch <url> [OPTIONS] [COMMON_OPTIONS]
|
||||
\\
|
||||
\\Options:
|
||||
\\--dump <DUMP> Dumps the document to stdout.
|
||||
\\ Defaults to no dump.
|
||||
\\
|
||||
\\ Allowed values:
|
||||
\\ html Serialized HTML of the DOM.
|
||||
\\ markdown Converts content to Markdown.
|
||||
\\ semantic_tree JSON-serialized semantic tree.
|
||||
\\ semantic_tree_text Pruned plain-text semantic tree.
|
||||
\\
|
||||
\\--strip-mode <STRIP> Comma-separated list of tag groups to remove from dump.
|
||||
\\ Defaults to no-strip.
|
||||
\\
|
||||
\\ Allowed values:
|
||||
\\ js script and link[as=script, rel=preload].
|
||||
\\ ui Includes img, picture, video, CSS and SVG.
|
||||
\\ css Includes style and link[rel=stylesheet].
|
||||
\\ full Strip everything.
|
||||
\\
|
||||
\\--with-base Add a <base> tag in dump.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--with-frames Includes the contents of iframes.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--wait-ms <INT> Wait time in milliseconds. Supersedes all other --wait
|
||||
\\ parameters.
|
||||
\\ Defaults to 5000.
|
||||
\\
|
||||
\\--wait-until <UNTIL> Wait until the specified event. Checked before other
|
||||
\\ --wait-* options.
|
||||
\\ Defaults to 'done'. If --wait-selector, --wait-script
|
||||
\\ or --wait-script-file specified, defaults to none.
|
||||
\\
|
||||
\\ Allowed values:
|
||||
\\ "load", "domcontentloaded", "networkidle", "done".
|
||||
\\
|
||||
\\--wait-selector <QUERY> Wait for an element matching the CSS selector to
|
||||
\\ appear. Checked after --wait-until condition is met.
|
||||
\\
|
||||
\\--wait-script <EXPR> Wait for a JavaScript expression to return truthy.
|
||||
\\ Checked after --wait-until condition is met.
|
||||
\\
|
||||
\\--wait-script-file <PATH> Like --wait-script, but reads the script from a file.
|
||||
\\
|
||||
\\--inject-script <EXPR> JavaScript to execute as the document's <head> is
|
||||
\\ parsed, before any other scripts in the page run.
|
||||
\\ Can be passed multiple times; scripts run in order.
|
||||
\\
|
||||
\\--inject-script-file <PATH>
|
||||
\\ Like --inject-script, but reads the script from a file.
|
||||
\\ Can be passed multiple times; can be mixed with
|
||||
\\ --inject-script and runs in CLI order.
|
||||
\\
|
||||
\\--terminate-ms <INT> Hard deadline in milliseconds. After this time elapses,
|
||||
\\ JavaScript execution is forcibly terminated (e.g. for
|
||||
\\ pages with endless scripts). Unlike --wait-ms, which
|
||||
\\ only stops waiting, --terminate-ms aborts the page.
|
||||
\\ Defaults to no terminate.
|
||||
\\
|
||||
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
\\
|
||||
\\--cookie-jar <PATH> Path to a JSON file to save cookies to on exit.
|
||||
\\ (write-only).
|
||||
\\ Defaults to no cookie saving.
|
||||
,
|
||||
.mcp =
|
||||
\\mcp command
|
||||
\\Starts an MCP (Model Context Protocol) server over stdio.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ {0s} mcp [OPTIONS] [COMMON_OPTIONS]
|
||||
\\
|
||||
\\Options:
|
||||
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
\\
|
||||
\\--cookie-jar <PATH> Path to a JSON file to save cookies to on exit.
|
||||
\\ (write-only).
|
||||
\\ Defaults to no cookie saving.
|
||||
,
|
||||
.version =
|
||||
\\version command
|
||||
\\Displays the version of {0s}.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ {0s} version
|
||||
,
|
||||
.help =
|
||||
\\help command
|
||||
\\Displays this message.
|
||||
\\
|
||||
\\Usage:
|
||||
\\ {0s} help
|
||||
,
|
||||
.common =
|
||||
\\Common Options:
|
||||
\\--insecure-disable-tls-host-verification
|
||||
\\ Disables host verification on all HTTP requests.
|
||||
\\ Only set this if you understand and accept the risk.
|
||||
\\
|
||||
\\--obey-robots Fetches and obeys robots.txt of the target page.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--disable-subframes Skip loading <iframe> elements. The parser still
|
||||
\\ registers them in the DOM, but no child frame or
|
||||
\\ Page.frameAttached events are produced.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--disable-workers Skip loading dedicated Web Workers. The Worker
|
||||
\\ constructor still returns a Worker object, but no
|
||||
\\ script fetch is initiated and its scope never runs.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--block-private-networks Block HTTP requests to private/internal IP
|
||||
\\ addresses after DNS resolution.
|
||||
\\ Defaults to false.
|
||||
\\
|
||||
\\--block-cidrs <LIST> Additional CIDR ranges to block, comma-separated.
|
||||
\\ Prefix with '-' to allow (exempt from blocking).
|
||||
\\ e.g. --block-cidrs 10.0.0.0/8,-10.0.0.42/32
|
||||
\\ Can be combined with --block-private-networks.
|
||||
\\
|
||||
\\--http-proxy <URL> HTTP proxy for all HTTP requests.
|
||||
\\ username:password may be included for basic auth.
|
||||
\\ Defaults to none.
|
||||
\\
|
||||
\\--proxy-bearer-token <TOKEN>
|
||||
\\ Token sent for bearer authentication with the
|
||||
\\ proxy: Proxy-Authorization: Bearer <token>.
|
||||
\\
|
||||
\\--http-max-concurrent <INT>
|
||||
\\ Maximum number of concurrent HTTP requests.
|
||||
\\ Defaults to 10.
|
||||
\\
|
||||
\\--http-max-host-open <INT> Maximum open connections to a given host:port.
|
||||
\\ Defaults to 4.
|
||||
\\
|
||||
\\--http-connect-timeout <INT>
|
||||
\\ Time in ms to establish an HTTP connection before
|
||||
\\ timing out. 0 means never.
|
||||
\\ Defaults to 0.
|
||||
\\
|
||||
\\--http-timeout <INT> Maximum time in ms the transfer is allowed to
|
||||
\\ complete. 0 means never.
|
||||
\\ Defaults to 10000.
|
||||
\\
|
||||
\\--http-max-response-size <INT>
|
||||
\\ Limits the acceptable response size for any
|
||||
\\ request (e.g. XHR, fetch, script loading).
|
||||
\\ Defaults to no limit.
|
||||
\\
|
||||
\\--ws-max-concurrent <INT> Maximum number of concurrent WebSocket connections.
|
||||
\\ Defaults to 8.
|
||||
\\
|
||||
\\--log-level <LEVEL> The log level.
|
||||
\\ Defaults to {1s}.
|
||||
\\
|
||||
\\ Allowed values:
|
||||
\\ "debug", "info", "warn", "error", "fatal".
|
||||
\\
|
||||
\\--log-format <FORMAT> The log format.
|
||||
\\ Defaults to {2s}.
|
||||
\\
|
||||
\\ Allowed values: "pretty", "logfmt".
|
||||
\\
|
||||
\\--log-filter-scopes <SCOPES>
|
||||
\\ Filter out too-verbose logs per scope,
|
||||
\\ comma-separated. e.g. http, unknown_prop, event.
|
||||
\\
|
||||
\\--user-agent <STRING> Override the User-Agent header entirely.
|
||||
\\ Must not impersonate other browsers; any value
|
||||
\\ containing "Mozilla" is forbidden. The browser
|
||||
\\ still sends Sec-Ch-Ua. Incompatible with
|
||||
\\ --user-agent-suffix.
|
||||
\\
|
||||
\\--user-agent-suffix <STRING>
|
||||
\\ Suffix appended to the Lightpanda/X.Y User-Agent.
|
||||
\\
|
||||
\\--web-bot-auth-key-file <PATH>
|
||||
\\ Path to the Ed25519 private key PEM file.
|
||||
\\
|
||||
\\--web-bot-auth-keyid <STRING>
|
||||
\\ The JWK thumbprint of your public key.
|
||||
\\
|
||||
\\--web-bot-auth-domain <DOMAIN>
|
||||
\\ Your domain, e.g. yourdomain.com.
|
||||
\\
|
||||
\\--http-cache-dir <PATH> Directory used as a filesystem cache for network
|
||||
\\ resources. Omitting this disables caching.
|
||||
\\ Defaults to no caching.
|
||||
\\
|
||||
\\--cookie <PATH> Path to a JSON file to load cookies from (read-only).
|
||||
\\ Defaults to no cookie loading.
|
||||
\\
|
||||
\\--cookie-jar <PATH> Path to a JSON file to save cookies to on exit
|
||||
\\ (write-only).
|
||||
\\ Defaults to no cookie saving.
|
||||
\\
|
||||
\\--storage-engine <ENGINE> The storage engine to use.
|
||||
\\ Defaults to none.
|
||||
\\
|
||||
\\ Allowed values: "none", "sqlite".
|
||||
\\
|
||||
\\--storage-sqlite-path <PATH>
|
||||
\\ Path to the SQLite database file for persistent
|
||||
\\ storage. Use ":memory:" for in-memory storage.
|
||||
,
|
||||
}
|
||||
@@ -54,10 +54,7 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
|
||||
defer args.deinit(main_arena);
|
||||
|
||||
switch (args.mode) {
|
||||
.help => {
|
||||
args.printUsageAndExit(true);
|
||||
return std.process.cleanExit();
|
||||
},
|
||||
.help => |tag| return args.printUsageAndExit(tag, true),
|
||||
.version => {
|
||||
var stdout = std.fs.File.stdout().writer(&.{});
|
||||
try stdout.interface.print("{s}\n", .{lp.build_config.version});
|
||||
@@ -94,7 +91,7 @@ fn run(allocator: Allocator, main_arena: Allocator) !void {
|
||||
log.debug(.app, "startup", .{ .mode = "serve", .snapshot = app.snapshot.fromEmbedded() });
|
||||
const address = std.net.Address.parseIp(opts.host, opts.port) catch |err| {
|
||||
log.fatal(.app, "invalid server address", .{ .err = err, .host = opts.host, .port = opts.port });
|
||||
return args.printUsageAndExit(false);
|
||||
return args.printUsageAndExit(.serve, false);
|
||||
};
|
||||
|
||||
var server = lp.Server.init(app, address) catch |err| {
|
||||
|
||||
Reference in New Issue
Block a user