diff options
author | 2023-03-01 16:17:47 -0800 | |
---|---|---|
committer | 2023-03-01 16:17:47 -0800 | |
commit | 6bc075e377a1f4dcb49ea27c9224117c6cbada70 (patch) | |
tree | e29d748c346624e7e3c667d6b80c479fed051f1b | |
parent | ba0706939d1c3b26121d7e39edf0c1a7a3f3a4da (diff) | |
download | bun-6bc075e377a1f4dcb49ea27c9224117c6cbada70.tar.gz bun-6bc075e377a1f4dcb49ea27c9224117c6cbada70.tar.zst bun-6bc075e377a1f4dcb49ea27c9224117c6cbada70.zip |
Revert "Update clap (#2238)"
This reverts commit 7b9a17f9d7106ffd8e553a5192aba60d14ea5e9c.
-rw-r--r-- | .gitmodules | 3 | ||||
-rw-r--r-- | misctools/fetch.zig | 16 | ||||
-rw-r--r-- | misctools/http_bench.zig | 22 | ||||
-rw-r--r-- | src/cli.zig | 292 | ||||
-rw-r--r-- | src/cli/create_command.zig | 44 | ||||
m--------- | src/deps/zig-clap | 0 | ||||
-rw-r--r-- | src/deps/zig-clap/.gitignore | 1 | ||||
-rw-r--r-- | src/deps/zig-clap/LICENSE | 24 | ||||
-rw-r--r-- | src/deps/zig-clap/build.zig | 55 | ||||
-rw-r--r-- | src/deps/zig-clap/clap.zig | 546 | ||||
-rw-r--r-- | src/deps/zig-clap/clap/args.zig | 337 | ||||
-rw-r--r-- | src/deps/zig-clap/clap/comptime.zig | 194 | ||||
-rw-r--r-- | src/deps/zig-clap/clap/streaming.zig | 430 | ||||
-rw-r--r-- | src/deps/zig-clap/gyro.zzz | 14 | ||||
-rw-r--r-- | src/deps/zig-clap/zig.mod | 5 | ||||
-rw-r--r-- | src/install/install.zig | 147 |
16 files changed, 1840 insertions, 290 deletions
diff --git a/.gitmodules b/.gitmodules index a86b743b7..c22446cbd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -65,6 +65,3 @@ fetchRecurseSubmodules = false [submodule "src/deps/c-ares"] path = src/deps/c-ares url = https://github.com/c-ares/c-ares.git -[submodule "src/deps/zig-clap"] - path = src/deps/zig-clap - url = https://github.com/Hejsil/zig-clap.git diff --git a/misctools/fetch.zig b/misctools/fetch.zig index 85f21c3b7..cdefd29d4 100644 --- a/misctools/fetch.zig +++ b/misctools/fetch.zig @@ -78,7 +78,7 @@ pub const Arguments = struct { pub fn parse(allocator: std.mem.Allocator) !Arguments { var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ + var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag, .allocator = allocator, }) catch |err| { @@ -87,7 +87,7 @@ pub const Arguments = struct { return err; }; - var positionals = res.positionals; + var positionals = args.positionals(); var raw_args: std.ArrayListUnmanaged(string) = undefined; if (positionals.len > 0) { @@ -96,16 +96,16 @@ pub const Arguments = struct { raw_args = .{}; } - if (res.args.version) { + if (args.flag("--version")) { try Output.writer().writeAll(VERSION); Global.exit(0); } var method = Method.GET; var url: URL = .{}; - var body_string: string = res.args.body orelse ""; + var body_string: string = args.option("--body") orelse ""; - if (res.args.file) |file_path| { + if (args.option("--file")) |file_path| { if (file_path.len > 0) { var cwd = try std.process.getCwd(&cwd_buf); var parts = [_]string{file_path}; @@ -154,12 +154,12 @@ pub const Arguments = struct { return Arguments{ .url = url, .method = method, - .verbose = res.args.verbose, + .verbose = args.flag("--verbose"), .headers = .{}, .headers_buf = "", .body = body_string, - .turbo = res.args.turbo, - .quiet = res.args.quiet, + .turbo = args.flag("--turbo"), + .quiet = args.flag("--quiet"), }; } }; diff --git a/misctools/http_bench.zig b/misctools/http_bench.zig index d4d27b56e..5e12f0157 100644 --- a/misctools/http_bench.zig +++ b/misctools/http_bench.zig @@ -82,7 +82,7 @@ pub const Arguments = struct { pub fn parse(allocator: std.mem.Allocator) !Arguments { var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ + var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag, .allocator = allocator, }) catch |err| { @@ -91,7 +91,7 @@ pub const Arguments = struct { return err; }; - var positionals = res.positionals; + var positionals = args.positionals(); var raw_args: std.ArrayListUnmanaged(string) = undefined; if (positionals.len > 0) { @@ -100,16 +100,16 @@ pub const Arguments = struct { raw_args = .{}; } - if (res.args.version) { + if (args.flag("--version")) { try Output.writer().writeAll(VERSION); Global.exit(0); } var method = Method.GET; var url: URL = .{}; - var body_string: string = res.args.body orelse ""; + var body_string: string = args.option("--body") orelse ""; - if (res.args.file) |file_path| { + if (args.option("--file")) |file_path| { if (file_path.len > 0) { var cwd = try std.process.getCwd(&cwd_buf); var parts = [_]string{file_path}; @@ -158,18 +158,18 @@ pub const Arguments = struct { return Arguments{ .url = url, .method = method, - .verbose = res.args.verbose, + .verbose = args.flag("--verbose"), .headers = .{}, .headers_buf = "", .body = body_string, - // .keep_alive = !res.args.@"--no-keep-alive", - .concurrency = std.fmt.parseInt(u16, res.args.@"max-concurrency" orelse "32", 10) catch 32, - .turbo = res.args.turbo, - .timeout = std.fmt.parseInt(usize, res.args.timeout orelse "0", 10) catch |err| { + // .keep_alive = !args.flag("--no-keep-alive"), + .concurrency = std.fmt.parseInt(u16, args.option("--max-concurrency") orelse "32", 10) catch 32, + .turbo = args.flag("--turbo"), + .timeout = std.fmt.parseInt(usize, args.option("--timeout") orelse "0", 10) catch |err| { Output.prettyErrorln("<r><red>{s}<r> parsing timeout", .{@errorName(err)}); Global.exit(1); }, - .count = std.fmt.parseInt(usize, res.args.count orelse "10", 10) catch |err| { + .count = std.fmt.parseInt(usize, args.option("--count") orelse "10", 10) catch |err| { Output.prettyErrorln("<r><red>{s}<r> parsing count", .{@errorName(err)}); Global.exit(1); }, diff --git a/src/cli.zig b/src/cli.zig index e44c75d43..e05bd113d 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -160,70 +160,59 @@ pub const Arguments = struct { } } - pub const parser = .{ - .STR = clap.parsers.string, - .PATH = clap.parsers.string, - .POS = clap.parsers.string, - }; - pub const ParamType = clap.Param(clap.Help); - const public_params = clap.parseParamsComptime( - \\--use <STR> Choose a framework, e.g. "--use next". It checks first for a package named "bun-framework-packagename" and then "packagename". - \\-b, --bun Force a script or package to use Bun.js instead of Node.js (via symlinking node) - \\--bunfile <STR> Use a .bun file (default: node_modules.bun) - \\--server-bunfile <STR> Use a .server.bun file (default: node_modules.server.bun) - \\--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd. - \\-c, --config <PATH> Config file to load bun from (e.g. -c bunfig.toml) - \\--disable-react-fast-refresh Disable React Fast Refresh - \\--disable-hmr Disable Hot Module Reloading (disables fast refresh too) in bun dev - \\--extension-order <STR>... defaults to: .tsx,.ts,.jsx,.js,.json - \\--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime - \\--jsx-fragment <STR> Changes the function called when compiling JSX fragments - \\--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: "react" - \\--jsx-production Use jsx instead of jsxDEV (default) for the automatic runtime - \\--jsx-runtime <STR> "automatic" (default) or "classic" - \\-r, --preload <STR>... Import a module before other modules are loaded") catch unreachable, - \\--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent - \\--no-summary Don't print a summary (when generating .bun - \\-v, --version Print version and exit - \\--platform <STR> "bun" or "browser" or "node", used when building or bundling - \\--public-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to "<cwd>/public", to match create-react-app and Next.js - \\--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json - \\-d, --define <STR>... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:"development". Values are parsed as JSON. - \\-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react - \\-h, --help Display this help and exit. - \\-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx, ts, css - \\-u, --origin <STR> Rewrite import URLs to start with --origin. Default: "" - \\-p, --port <STR> Port to serve bun's dev server on. Default: "3000" - \\--hot Enable auto reload in bun's JavaScript runtime - \\--no-install Disable auto install in bun's JavaScript runtime - \\-i, --install-fallback Automatically install dependencies and use global cache in bun's runtime, equivalent to --install=fallback - \\--install <STR> Install dependencies automatically when no node_modules are present, default: "auto". "force" to ignore node_modules, fallback to install any missing - \\--prefer-offline Skip staleness checks for packages in bun's JavaScript runtime and resolve from disk - \\--prefer-latest Use the latest matching versions of packages in bun's JavaScript runtime, always checking npm - \\--silent Don't repeat the command for bun run - \\<POS>... - ); - - const debug_params = clap.parseParamsComptime( - \\--dump-environment-variables Dump environment variables from .env and process as JSON and quit. Useful for debugging - \\--dump-limits Dump system limits. Useful for debugging - ) - // clap can't handle parsing the '.' - ++ .{ - clap.Param(clap.Help){ - .id = .{ .val = "disable-bun.js", .desc = "Disable bun.js from loading in the dev server" }, - .names = .{ .long = "disable-bun.js" }, - } + const public_params = [_]ParamType{ + clap.parseParam("--use <STR> Choose a framework, e.g. \"--use next\". It checks first for a package named \"bun-framework-packagename\" and then \"packagename\".") catch unreachable, + clap.parseParam("-b, --bun Force a script or package to use Bun.js instead of Node.js (via symlinking node)") catch unreachable, + clap.parseParam("--bunfile <STR> Use a .bun file (default: node_modules.bun)") catch unreachable, + clap.parseParam("--server-bunfile <STR> Use a .server.bun file (default: node_modules.server.bun)") catch unreachable, + clap.parseParam("--cwd <STR> Absolute path to resolve files & entry points from. This just changes the process' cwd.") catch unreachable, + clap.parseParam("-c, --config <PATH>? Config file to load bun from (e.g. -c bunfig.toml") catch unreachable, + clap.parseParam("--disable-react-fast-refresh Disable React Fast Refresh") catch unreachable, + clap.parseParam("--disable-hmr Disable Hot Module Reloading (disables fast refresh too) in bun dev") catch unreachable, + clap.parseParam("--extension-order <STR>... defaults to: .tsx,.ts,.jsx,.js,.json ") catch unreachable, + clap.parseParam("--jsx-factory <STR> Changes the function called when compiling JSX elements using the classic JSX runtime") catch unreachable, + clap.parseParam("--jsx-fragment <STR> Changes the function called when compiling JSX fragments") catch unreachable, + clap.parseParam("--jsx-import-source <STR> Declares the module specifier to be used for importing the jsx and jsxs factory functions. Default: \"react\"") catch unreachable, + clap.parseParam("--jsx-production Use jsx instead of jsxDEV (default) for the automatic runtime") catch unreachable, + clap.parseParam("--jsx-runtime <STR> \"automatic\" (default) or \"classic\"") catch unreachable, + clap.parseParam("-r, --preload <STR>... Import a module before other modules are loaded") catch unreachable, + clap.parseParam("--main-fields <STR>... Main fields to lookup in package.json. Defaults to --platform dependent") catch unreachable, + clap.parseParam("--no-summary Don't print a summary (when generating .bun") catch unreachable, + clap.parseParam("-v, --version Print version and exit") catch unreachable, + clap.parseParam("--platform <STR> \"bun\" or \"browser\" or \"node\", used when building or bundling") catch unreachable, + // clap.parseParam("--production [not implemented] generate production code") catch unreachable, + clap.parseParam("--public-dir <STR> Top-level directory for .html files, fonts or anything external. Defaults to \"<cwd>/public\", to match create-react-app and Next.js") catch unreachable, + clap.parseParam("--tsconfig-override <STR> Load tsconfig from path instead of cwd/tsconfig.json") catch unreachable, + clap.parseParam("-d, --define <STR>... Substitute K:V while parsing, e.g. --define process.env.NODE_ENV:\"development\". Values are parsed as JSON.") catch unreachable, + clap.parseParam("-e, --external <STR>... Exclude module from transpilation (can use * wildcards). ex: -e react") catch unreachable, + clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, + clap.parseParam("-l, --loader <STR>... Parse files with .ext:loader, e.g. --loader .js:jsx. Valid loaders: jsx, js, json, tsx, ts, css") catch unreachable, + clap.parseParam("-u, --origin <STR> Rewrite import URLs to start with --origin. Default: \"\"") catch unreachable, + clap.parseParam("-p, --port <STR> Port to serve bun's dev server on. Default: \"3000\"") catch unreachable, + clap.parseParam("--hot Enable auto reload in bun's JavaScript runtime") catch unreachable, + clap.parseParam("--no-install Disable auto install in bun's JavaScript runtime") catch unreachable, + clap.parseParam("-i Automatically install dependencies and use global cache in bun's runtime, equivalent to --install=fallback") catch unreachable, + clap.parseParam("--install <STR> Install dependencies automatically when no node_modules are present, default: \"auto\". \"force\" to ignore node_modules, fallback to install any missing") catch unreachable, + clap.parseParam("--prefer-offline Skip staleness checks for packages in bun's JavaScript runtime and resolve from disk") catch unreachable, + clap.parseParam("--prefer-latest Use the latest matching versions of packages in bun's JavaScript runtime, always checking npm") catch unreachable, + clap.parseParam("--silent Don't repeat the command for bun run") catch unreachable, + clap.parseParam("<POS>... ") catch unreachable, + }; + + const debug_params = [_]ParamType{ + clap.parseParam("--dump-environment-variables Dump environment variables from .env and process as JSON and quit. Useful for debugging") catch unreachable, + clap.parseParam("--dump-limits Dump system limits. Useful for debugging") catch unreachable, + clap.parseParam("--disable-bun.js Disable bun.js from loading in the dev server") catch unreachable, }; pub const params = public_params ++ debug_params; - const build_only_params = clap.parseParamsComptime( - \\--sourcemap <STR> Build with sourcemaps - "inline", "external", or "none" - \\--outdir <STR> Default to "dist" if multiple files - ); + const build_only_params = [_]ParamType{ + clap.parseParam("--sourcemap <STR>? Build with sourcemaps - 'inline', 'external', or 'none'") catch unreachable, + clap.parseParam("--outdir <STR> Default to \"dist\" if multiple files") catch unreachable, + }; const build_params_public = public_params ++ build_only_params; pub const build_params = build_params_public ++ debug_params; @@ -333,78 +322,40 @@ pub const Arguments = struct { try loadConfigPath(allocator, auto_loaded, config_path, ctx, comptime cmd); } + pub fn loadConfigWithCmdArgs( + comptime cmd: Command.Tag, + allocator: std.mem.Allocator, + args: clap.Args(clap.Help, cmd.params()), + ctx: *Command.Context, + ) !void { + return try loadConfig(allocator, args.option("--config"), ctx, comptime cmd); + } + pub fn parse(allocator: std.mem.Allocator, ctx: *Command.Context, comptime cmd: Command.Tag) !Api.TransformOptions { var diag = clap.Diagnostic{}; + const params_to_use = comptime cmd.params(); - return parseImpl(allocator, ctx, cmd, &diag) catch |err| { + var args = clap.parse(clap.Help, params_to_use, .{ + .diagnostic = &diag, + .allocator = allocator, + .stop_after_positional_at = if (cmd == .RunCommand) 2 else if (cmd == .AutoCommand) + 1 + else + 0, + }) catch |err| { // Report useful error and exit - clap.help(Output.errorWriter(), clap.Help, cmd.params(), .{}) catch {}; + clap.help(Output.errorWriter(), params_to_use) catch {}; Output.errorWriter().writeAll("\n") catch {}; diag.report(Output.errorWriter(), err) catch {}; Global.exit(1); }; - } - // This implementation is separated into a function so that we can use try at - // various key points knowing that the public parse fn will catch and handle - // those errors for us. - fn parseImpl(allocator: std.mem.Allocator, ctx: *Command.Context, comptime cmd: Command.Tag, diag: *clap.Diagnostic) !Api.TransformOptions { - const params_to_use = comptime cmd.params(); - - // We'll need to work around clap a bit to support this pattern: - // bun run --bun-arg app.js --app-arg - // - // 1) Collect all args into a slice so that we can iterate them multiple times. - // 2) Count positionals until we hit the second one (in the example above, - // could vary based on command). - // 3) Call clap.parse with only the slice up to and including that positional. - // 4) All remaining args become passthroughs - const full_args = try std.process.argsAlloc(allocator); - //NOTE: we won't free full_args because we'll make slices available to - // the running script and they need to remain available for the entire run - const args = full_args[1..]; // skip exe path - - // 2: Find our parse stopping point - const passthrough_after_nth_positional = comptime if (cmd == .RunCommand) 2 - else if (cmd == .AutoCommand) 1 - else 0; - var it = clap.args.SliceIterator{ .args = args[0..] }; - var streaming_clap = clap.streaming.Clap(clap.Help, clap.args.SliceIterator){ - .params = params_to_use, - .iter = &it, - .diagnostic = diag, - }; - var args_to_parse: usize = 0; - if (comptime passthrough_after_nth_positional > 0) { - var num_positionals_seen: usize = 0; - while (try streaming_clap.next()) |arg| : (args_to_parse += 1) { - if (arg.param.names.longest().kind == .positional) { - num_positionals_seen += 1; - - if (num_positionals_seen == passthrough_after_nth_positional) { - args_to_parse += 1; - break; - } - } - } - } else - args_to_parse = args.len; - - // 3: parse but only up to args_to_parse - it = clap.args.SliceIterator{ .args = args[0..args_to_parse] }; - var res = try clap.parseEx(clap.Help, params_to_use, Arguments.parser, &it, .{ - .diagnostic = diag, - .allocator = allocator, - }); - //defer res.deinit(); - - - if (res.args.version) { + if (args.flag("--version")) { printVersionAndExit(); } var cwd: []u8 = undefined; - if (res.args.cwd) |cwd_| { + if (args.option("--cwd")) |cwd_| { cwd = brk: { var outbuf: [bun.MAX_PATH_BYTES]u8 = undefined; const out = std.os.realpath(cwd_, &outbuf) catch |err| { @@ -418,17 +369,15 @@ pub const Arguments = struct { } ctx.args.absolute_working_dir = cwd; - ctx.positionals = res.positionals; - // All remaining args should be passed through - ctx.passthrough = args[args_to_parse..]; + ctx.positionals = args.positionals(); if (comptime Command.Tag.loads_config.get(cmd)) { - try loadConfig(allocator, res.args.config, ctx, cmd); + try loadConfigWithCmdArgs(cmd, allocator, args, ctx); } var opts: Api.TransformOptions = ctx.args; - var defines_tuple = try DefineColonList.resolve(allocator, res.args.define); + var defines_tuple = try DefineColonList.resolve(allocator, args.options("--define")); if (defines_tuple.keys.len > 0) { opts.define = .{ @@ -437,7 +386,7 @@ pub const Arguments = struct { }; } - var loader_tuple = try LoaderColonList.resolve(allocator, res.args.loader); + var loader_tuple = try LoaderColonList.resolve(allocator, args.options("--loader")); if (loader_tuple.keys.len > 0) { opts.loaders = .{ @@ -446,39 +395,40 @@ pub const Arguments = struct { }; } - if (res.args.external.len > 0) { - var externals = try allocator.alloc([]u8, res.args.external.len); - for (res.args.external, 0..) |external, i| { + if (args.options("--external").len > 0) { + var externals = try allocator.alloc([]u8, args.options("--external").len); + for (args.options("--external"), 0..) |external, i| { externals[i] = constStrToU8(external); } opts.external = externals; } - opts.tsconfig_override = if (res.args.@"tsconfig-override") |ts| + opts.tsconfig_override = if (args.option("--tsconfig-override")) |ts| (Arguments.readFile(allocator, cwd, ts) catch |err| fileReadError(err, Output.errorStream(), ts, "tsconfig.json")) else null; - if (res.args.origin) |origin| { + if (args.option("--origin")) |origin| { opts.origin = origin; } - if (res.args.port) |port_str| { + if (args.option("--port")) |port_str| { opts.port = std.fmt.parseInt(u16, port_str, 10) catch return error.InvalidPort; } opts.serve = cmd == .DevCommand; - opts.main_fields = res.args.@"main-fields"; + opts.main_fields = args.options("--main-fields"); opts.generate_node_module_bundle = cmd == .BunCommand; // we never actually supported inject. - // opts.inject = res.args.inject; - opts.extension_order = res.args.@"extension-order"; - ctx.debug.hot_reload = res.args.hot; + // opts.inject = args.options("--inject"); + opts.extension_order = args.options("--extension-order"); + ctx.debug.hot_reload = args.flag("--hot"); + ctx.passthrough = args.remaining(); - opts.no_summary = res.args.@"no-summary"; - opts.disable_hmr = res.args.@"disable-hmr"; + opts.no_summary = args.flag("--no-summary"); + opts.disable_hmr = args.flag("--disable-hmr"); if (cmd != .DevCommand) { - const preloads = res.args.preload; + const preloads = args.options("--preload"); if (ctx.preloads.len > 0 and preloads.len > 0) { var all = std.ArrayList(string).initCapacity(ctx.allocator, ctx.preloads.len + preloads.len) catch unreachable; all.appendSliceAssumeCapacity(ctx.preloads); @@ -489,36 +439,37 @@ pub const Arguments = struct { } } - ctx.debug.silent = res.args.@"silent"; + ctx.debug.silent = args.flag("--silent"); if (opts.port != null and opts.origin == null) { opts.origin = try std.fmt.allocPrint(allocator, "http://localhost:{d}/", .{opts.port.?}); } - if (res.args.help) { + const print_help = args.flag("--help"); + if (print_help) { const params_len = if (cmd == .BuildCommand) build_params_public.len else public_params.len; - clap.help(Output.writer(), clap.Help, params_to_use[0..params_len], .{}) catch {}; + clap.help(Output.writer(), params_to_use[0..params_len]) catch {}; Output.prettyln("\n-------\n\n", .{}); Output.flush(); HelpCommand.printWithReason(.explicit); Global.exit(0); } - ctx.debug.dump_environment_variables = res.args.@"dump-environment-variables"; - ctx.debug.fallback_only = ctx.debug.fallback_only or res.args.@"disable-bun.js"; - ctx.debug.dump_limits = res.args.@"dump-limits"; + ctx.debug.dump_environment_variables = args.flag("--dump-environment-variables"); + ctx.debug.fallback_only = ctx.debug.fallback_only or args.flag("--disable-bun.js"); + ctx.debug.dump_limits = args.flag("--dump-limits"); - ctx.debug.offline_mode_setting = if (res.args.@"prefer-offline") + ctx.debug.offline_mode_setting = if (args.flag("--prefer-offline")) Bunfig.OfflineMode.offline - else if (res.args.@"prefer-latest") + else if (args.flag("--prefer-latest")) Bunfig.OfflineMode.latest else Bunfig.OfflineMode.online; - if (res.args.@"no-install") { + if (args.flag("--no-install")) { ctx.debug.global_cache = .disable; - } else if (res.args.@"install-fallback") { + } else if (args.flag("-i")) { ctx.debug.global_cache = .fallback; - } else if (res.args.install) |enum_value| { + } else if (args.option("--install")) |enum_value| { // -i=auto --install=force, --install=disable if (options.GlobalCache.Map.get(enum_value)) |result| { ctx.debug.global_cache = result; @@ -531,18 +482,18 @@ pub const Arguments = struct { } } - // var output_dir = res.args.outdir; + // var output_dir = args.option("--outdir"); var output_dir: ?string = null; const production = false; - if (comptime cmd == .BuildCommand) { - if (res.args.outdir) |outdir| { + if (cmd == .BuildCommand) { + if (args.option("--outdir")) |outdir| { if (outdir.len > 0) { output_dir = outdir; } } - if (res.args.sourcemap) |setting| { + if (args.option("--sourcemap")) |setting| { if (setting.len == 0 or strings.eqlComptime(setting, "inline")) { opts.source_map = Api.SourceMapMode.inline_into_file; } else if (strings.eqlComptime(setting, "none")) { @@ -614,24 +565,24 @@ pub const Arguments = struct { opts.entry_points = entry_points; } - var jsx_factory = res.args.@"jsx-factory"; - var jsx_fragment = res.args.@"jsx-fragment"; - var jsx_import_source = res.args.@"jsx-import-source"; - var jsx_runtime = res.args.@"jsx-runtime"; - var jsx_production = res.args.@"jsx-production"; + var jsx_factory = args.option("--jsx-factory"); + var jsx_fragment = args.option("--jsx-fragment"); + var jsx_import_source = args.option("--jsx-import-source"); + var jsx_runtime = args.option("--jsx-runtime"); + var jsx_production = args.flag("--jsx-production"); const react_fast_refresh = switch (comptime cmd) { - .BunCommand, .DevCommand => !(res.args.@"disable-react-fast-refresh" or jsx_production), + .BunCommand, .DevCommand => !(args.flag("--disable-react-fast-refresh") or jsx_production), else => true, }; if (comptime Command.Tag.cares_about_bun_file.get(cmd)) { - opts.node_modules_bundle_path = res.args.bunfile orelse opts.node_modules_bundle_path orelse brk: { + opts.node_modules_bundle_path = args.option("--bunfile") orelse opts.node_modules_bundle_path orelse brk: { const node_modules_bundle_path_absolute = resolve_path.joinAbs(cwd, .auto, "node_modules.bun"); break :brk std.fs.realpathAlloc(allocator, node_modules_bundle_path_absolute) catch null; }; - opts.node_modules_bundle_path_server = res.args.@"server-bunfile" orelse opts.node_modules_bundle_path_server orelse brk: { + opts.node_modules_bundle_path_server = args.option("--server-bunfile") orelse opts.node_modules_bundle_path_server orelse brk: { const node_modules_bundle_path_absolute = resolve_path.joinAbs(cwd, .auto, "node_modules.server.bun"); break :brk std.fs.realpathAlloc(allocator, node_modules_bundle_path_absolute) catch null; @@ -640,7 +591,7 @@ pub const Arguments = struct { switch (comptime cmd) { .AutoCommand, .DevCommand, .BuildCommand, .BunCommand => { - if (res.args.@"public-dir") |public_dir| { + if (args.option("--public-dir")) |public_dir| { if (public_dir.len > 0) { opts.router = Api.RouteConfig{ .extensions = &.{}, .dir = &.{}, .static_dir = public_dir }; } @@ -655,7 +606,7 @@ pub const Arguments = struct { switch (comptime cmd) { .BuildCommand => { - // if (res.args.resolve) |_resolve| { + // if (args.option("--resolve")) |_resolve| { // switch (ResolveMatcher.match(_resolve)) { // ResolveMatcher.case("disable") => { // opts.resolve = Api.ResolveMode.disable; @@ -683,19 +634,19 @@ pub const Arguments = struct { const PlatformMatcher = strings.ExactSizeMatcher(8); - if (res.args.platform) |_platform| { + if (args.option("--platform")) |_platform| { opts.platform = opts.platform orelse switch (PlatformMatcher.match(_platform)) { PlatformMatcher.case("browser") => Api.Platform.browser, PlatformMatcher.case("node") => Api.Platform.node, PlatformMatcher.case("macro") => if (cmd == .BuildCommand) Api.Platform.bun_macro else Api.Platform.bun, PlatformMatcher.case("bun") => Api.Platform.bun, - else => invalidPlatform(diag, _platform), + else => invalidPlatform(&diag, _platform), }; ctx.debug.run_in_bun = opts.platform.? == .bun; } - ctx.debug.run_in_bun = res.args.bun or ctx.debug.run_in_bun; + ctx.debug.run_in_bun = args.flag("--bun") or ctx.debug.run_in_bun; if (jsx_factory != null or jsx_fragment != null or @@ -727,7 +678,7 @@ pub const Arguments = struct { } } - if (res.args.use) |entry| { + if (args.option("--use")) |entry| { opts.framework = Api.FrameworkConfig{ .package = entry, .development = !production, @@ -868,17 +819,16 @@ pub const PrintBundleCommand = struct { var stdout = std.io.getStdOut(); var input = try std.fs.openFileAbsolute(try std.os.realpath(entry_point, &out_buffer), .{ .mode = .read_only }); - const params = comptime clap.parseParamsComptime( - \\--summary Peek inside the .bun" - ); + const params = comptime [_]Arguments.ParamType{ + clap.parseParam("--summary Peek inside the .bun") catch unreachable, + }; - var jsBundleArgs = clap.parse(clap.Help, ¶ms, Arguments.parser, .{ .allocator = ctx.allocator }) catch { + var jsBundleArgs = clap.parse(clap.Help, ¶ms, .{ .allocator = ctx.allocator }) catch { try NodeModuleBundle.printBundle(std.fs.File, input, @TypeOf(stdout), stdout); return; }; - defer jsBundleArgs.deinit(); - if (jsBundleArgs.args.summary) { + if (jsBundleArgs.flag("--summary")) { NodeModuleBundle.printSummaryFromDisk(std.fs.File, input, @TypeOf(stdout), stdout, ctx.allocator) catch {}; return; } diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index c21ccf21d..0c76a9c01 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -25,7 +25,6 @@ const Api = @import("../api/schema.zig").Api; const resolve_path = @import("../resolver/resolve_path.zig"); const configureTransformOptionsForBun = @import("../bun.js/config.zig").configureTransformOptionsForBun; const Command = @import("../cli.zig").Command; -const BunArguments = @import("../cli.zig").Arguments; const bundler = bun.bundler; const NodeModuleBundle = @import("../node_module_bundle.zig").NodeModuleBundle; const fs = @import("../fs.zig"); @@ -188,34 +187,35 @@ const CreateOptions = struct { verbose: bool = false, open: bool = false, - const params = clap.parseParamsComptime( - \\--help Print this menu - \\--force Overwrite existing files - \\--no-install Don't install node_modules - \\--no-git Don't create a git repository - \\--verbose Too many logs - \\--no-package-json Disable package.json transforms - \\--open On finish, start bun & open in-browser - \\<POS>... - ); + const params = [_]clap.Param(clap.Help){ + clap.parseParam("--help Print this menu") catch unreachable, + clap.parseParam("--force Overwrite existing files") catch unreachable, + clap.parseParam("--no-install Don't install node_modules") catch unreachable, + clap.parseParam("--no-git Don't create a git repository") catch unreachable, + clap.parseParam("--verbose Too many logs") catch unreachable, + clap.parseParam("--no-package-json Disable package.json transforms") catch unreachable, + clap.parseParam("--open On finish, start bun & open in-browser") catch unreachable, + clap.parseParam("<POS>... ") catch unreachable, + }; pub fn parse(ctx: Command.Context, comptime print_flags_only: bool) !CreateOptions { var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, ¶ms, BunArguments.parser, .{ .diagnostic = &diag, .allocator = ctx.allocator }) catch |err| { + + var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag, .allocator = ctx.allocator }) catch |err| { // Report useful error and exit diag.report(Output.errorWriter(), err) catch {}; return err; }; - if (res.args.help or comptime print_flags_only) { + if (args.flag("--help") or comptime print_flags_only) { if (comptime print_flags_only) { - clap.help(Output.writer(), clap.Help, params[1..], .{}) catch {}; + clap.help(Output.writer(), params[1..]) catch {}; return undefined; } Output.prettyln("<r><b>bun create<r>\n\n flags:\n", .{}); Output.flush(); - clap.help(Output.writer(), clap.Help, params[1..], .{}) catch {}; + clap.help(Output.writer(), params[1..]) catch {}; Output.pretty("\n", .{}); Output.prettyln("<r> environment variables:\n\n", .{}); Output.prettyln(" GITHUB_ACCESS_TOKEN<r> Downloading code from GitHub with a higher rate limit", .{}); @@ -226,19 +226,19 @@ const CreateOptions = struct { Global.exit(0); } - var opts = CreateOptions{ .positionals = res.positionals }; + var opts = CreateOptions{ .positionals = args.positionals() }; if (opts.positionals.len >= 1 and (strings.eqlComptime(opts.positionals[0], "c") or strings.eqlComptime(opts.positionals[0], "create"))) { opts.positionals = opts.positionals[1..]; } - opts.skip_package_json = res.args.@"no-package-json"; + opts.skip_package_json = args.flag("--no-package-json"); - opts.verbose = res.args.verbose; - opts.open = res.args.open; - opts.skip_install = res.args.@"no-install"; - opts.skip_git = res.args.@"no-git"; - opts.overwrite = res.args.force; + opts.verbose = args.flag("--verbose"); + opts.open = args.flag("--open"); + opts.skip_install = args.flag("--no-install"); + opts.skip_git = args.flag("--no-git"); + opts.overwrite = args.flag("--force"); return opts; } diff --git a/src/deps/zig-clap b/src/deps/zig-clap deleted file mode 160000 -Subproject 9c3ac846121a03934c7460cc54989059b1f66b2 diff --git a/src/deps/zig-clap/.gitignore b/src/deps/zig-clap/.gitignore new file mode 100644 index 000000000..2040c29db --- /dev/null +++ b/src/deps/zig-clap/.gitignore @@ -0,0 +1 @@ +zig-cache diff --git a/src/deps/zig-clap/LICENSE b/src/deps/zig-clap/LICENSE new file mode 100644 index 000000000..cf1ab25da --- /dev/null +++ b/src/deps/zig-clap/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org> diff --git a/src/deps/zig-clap/build.zig b/src/deps/zig-clap/build.zig new file mode 100644 index 000000000..5ab66da8a --- /dev/null +++ b/src/deps/zig-clap/build.zig @@ -0,0 +1,55 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const Builder = std.build.Builder; +const Mode = std.builtin.Mode; + +pub fn build(b: *Builder) void { + const mode = b.standardReleaseOptions(); + const target = b.standardTargetOptions(.{}); + + const test_all_step = b.step("test", "Run all tests in all modes."); + inline for ([_]Mode{ Mode.Debug, Mode.ReleaseFast, Mode.ReleaseSafe, Mode.ReleaseSmall }) |test_mode| { + const mode_str = comptime modeToString(test_mode); + + const tests = b.addTest("clap.zig"); + tests.setBuildMode(test_mode); + tests.setTarget(target); + tests.setNamePrefix(mode_str ++ " "); + + const test_step = b.step("test-" ++ mode_str, "Run all tests in " ++ mode_str ++ "."); + test_step.dependOn(&tests.step); + test_all_step.dependOn(test_step); + } + + const example_step = b.step("examples", "Build examples"); + inline for ([_][]const u8{ + "simple", + "simple-ex", + //"simple-error", + "streaming-clap", + "help", + "usage", + }) |example_name| { + const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); + example.addPackagePath("clap", "clap.zig"); + example.setBuildMode(mode); + example.setTarget(target); + example.install(); + example_step.dependOn(&example.step); + } + + const all_step = b.step("all", "Build everything and runs all tests"); + all_step.dependOn(test_all_step); + + b.default_step.dependOn(all_step); +} + +fn modeToString(mode: Mode) []const u8 { + return switch (mode) { + Mode.Debug => "debug", + Mode.ReleaseFast => "release-fast", + Mode.ReleaseSafe => "release-safe", + Mode.ReleaseSmall => "release-small", + }; +} diff --git a/src/deps/zig-clap/clap.zig b/src/deps/zig-clap/clap.zig new file mode 100644 index 000000000..a21a1cb1a --- /dev/null +++ b/src/deps/zig-clap/clap.zig @@ -0,0 +1,546 @@ +const std = @import("std"); + +const debug = std.debug; +const heap = std.heap; +const io = std.io; +const mem = std.mem; +const testing = std.testing; + +pub const args = @import("clap/args.zig"); + +test "clap" { + testing.refAllDecls(@This()); +} + +pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap; +pub const StreamingClap = @import("clap/streaming.zig").StreamingClap; + +/// The names a ::Param can have. +pub const Names = struct { + /// '-' prefix + short: ?u8 = null, + + /// '--' prefix + long: ?[]const u8 = null, +}; + +/// Whether a param takes no value (a flag), one value, or can be specified multiple times. +pub const Values = enum { + none, + one, + many, + one_optional, +}; + +/// Represents a parameter for the command line. +/// Parameters come in three kinds: +/// * Short ("-a"): Should be used for the most commonly used parameters in your program. +/// * They can take a value three different ways. +/// * "-a value" +/// * "-a=value" +/// * "-avalue" +/// * They chain if they don't take values: "-abc". +/// * The last given parameter can take a value in the same way that a single parameter can: +/// * "-abc value" +/// * "-abc=value" +/// * "-abcvalue" +/// * Long ("--long-param"): Should be used for less common parameters, or when no single character +/// can describe the paramter. +/// * They can take a value two different ways. +/// * "--long-param value" +/// * "--long-param=value" +/// * Positional: Should be used as the primary parameter of the program, like a filename or +/// an expression to parse. +/// * Positional parameters have both names.long and names.short == null. +/// * Positional parameters must take a value. +pub fn Param(comptime Id: type) type { + return struct { + id: Id = std.mem.zeroes(Id), + names: Names = std.mem.zeroes(Names), + takes_value: Values = .none, + }; +} + +/// Takes a string and parses it to a Param(Help). +/// This is the reverse of 'help' but for at single parameter only. +pub fn parseParam(line: []const u8) !Param(Help) { + @setEvalBranchQuota(999999); + + var found_comma = false; + var it = mem.tokenize(u8, line, " \t"); + var param_str = it.next() orelse return error.NoParamFound; + + const short_name = if (!mem.startsWith(u8, param_str, "--") and + mem.startsWith(u8, param_str, "-")) + blk: { + found_comma = param_str[param_str.len - 1] == ','; + if (found_comma) + param_str = param_str[0 .. param_str.len - 1]; + + if (param_str.len != 2) + return error.InvalidShortParam; + + const short_name = param_str[1]; + if (!found_comma) { + var res = parseParamRest(it.rest()); + res.names.short = short_name; + return res; + } + + param_str = it.next() orelse return error.NoParamFound; + break :blk short_name; + } else null; + + _ = if (mem.startsWith(u8, param_str, "--")) { + if (param_str[param_str.len - 1] == ',') + return error.TrailingComma; + } else if (found_comma) { + return error.TrailingComma; + } else if (short_name == null) { + return parseParamRest(mem.trimLeft(u8, line, " \t")); + } else null; + + var res = parseParamRest(it.rest()); + res.names.long = param_str[2..]; + res.names.short = short_name; + return res; +} + +fn parseParamRest(line: []const u8) Param(Help) { + if (mem.startsWith(u8, line, "<")) blk: { + const len = mem.indexOfScalar(u8, line, '>') orelse break :blk; + const takes_many = mem.startsWith(u8, line[len + 1 ..], "..."); + const takes_one_optional = mem.startsWith(u8, line[len + 1 ..], "?"); + const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many) + (@as(usize, 1) * @boolToInt(takes_one_optional)); + return .{ + .takes_value = if (takes_many) Values.many else if (takes_one_optional) Values.one_optional else Values.one, + .id = .{ + .msg = mem.trim(u8, line[help_start..], " \t"), + .value = line[1..len], + }, + }; + } + + return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } }; +} + +fn expectParam(expect: Param(Help), actual: Param(Help)) void { + testing.expectEqualStrings(expect.id.msg, actual.id.msg); + testing.expectEqualStrings(expect.id.value, actual.id.value); + testing.expectEqual(expect.names.short, actual.names.short); + testing.expectEqual(expect.takes_value, actual.takes_value); + if (expect.names.long) |long| { + testing.expectEqualStrings(long, actual.names.long.?); + } else { + testing.expectEqual(@as(?[]const u8, null), actual.names.long); + } +} + +test "parseParam" { + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .short = 's', .long = "long" }, + .takes_value = .one, + }, try parseParam("-s, --long <value> Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .short = 's', .long = "long" }, + .takes_value = .many, + }, try parseParam("-s, --long <value>... Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .long = "long" }, + .takes_value = .one, + }, try parseParam("--long <value> Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .short = 's' }, + .takes_value = .one, + }, try parseParam("-s <value> Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text" }, + .names = .{ .short = 's', .long = "long" }, + }, try parseParam("-s, --long Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text" }, + .names = .{ .short = 's' }, + }, try parseParam("-s Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text" }, + .names = .{ .long = "long" }, + }, try parseParam("--long Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "A | B" }, + .names = .{ .long = "long" }, + .takes_value = .one, + }, try parseParam("--long <A | B> Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "A" }, + .names = .{}, + .takes_value = .one, + }, try parseParam("<A> Help text")); + + expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "A" }, + .names = .{}, + .takes_value = .many, + }, try parseParam("<A>... Help text")); + + testing.expectError(error.TrailingComma, parseParam("--long, Help")); + testing.expectError(error.TrailingComma, parseParam("-s, Help")); + testing.expectError(error.InvalidShortParam, parseParam("-ss Help")); + testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help")); + testing.expectError(error.InvalidShortParam, parseParam("- Help")); +} + +/// Optional diagnostics used for reporting useful errors +pub const Diagnostic = struct { + arg: []const u8 = "", + name: Names = Names{}, + + /// Default diagnostics reporter when all you want is English with no colors. + /// Use this as a reference for implementing your own if needed. + pub fn report(diag: Diagnostic, stream: anytype, err: anyerror) !void { + const Arg = struct { + prefix: []const u8, + name: []const u8, + }; + const a = if (diag.name.short) |*c| + Arg{ .prefix = "-", .name = @as(*const [1]u8, c)[0..] } + else if (diag.name.long) |l| + Arg{ .prefix = "--", .name = l } + else + Arg{ .prefix = "", .name = diag.arg }; + + switch (err) { + error.DoesntTakeValue => try stream.print("The argument '{s}{s}' does not take a value\n", .{ a.prefix, a.name }), + error.MissingValue => try stream.print("The argument '{s}{s}' requires a value but none was supplied\n", .{ a.prefix, a.name }), + error.InvalidArgument => if (a.prefix.len > 0 and a.name.len > 0) + try stream.print("Invalid argument '{s}{s}'\n", .{ a.prefix, a.name }) + else + try stream.print("Failed to parse argument due to unexpected single dash\n", .{}), + else => try stream.print("Error while parsing arguments: {s}\n", .{@errorName(err)}), + } + } +}; + +fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) void { + var buf: [1024]u8 = undefined; + var slice_stream = io.fixedBufferStream(&buf); + diag.report(slice_stream.writer(), err) catch unreachable; + testing.expectEqualStrings(expected, slice_stream.getWritten()); +} + +pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { + return struct { + arena: std.heap.ArenaAllocator, + clap: ComptimeClap(Id, params), + exe_arg: ?[]const u8, + + pub fn deinit(a: *@This()) void { + a.arena.deinit(); + } + + pub fn flag(a: @This(), comptime name: []const u8) bool { + return a.clap.flag(name); + } + + pub fn option(a: @This(), comptime name: []const u8) ?[]const u8 { + return a.clap.option(name); + } + + pub fn options(a: @This(), comptime name: []const u8) []const []const u8 { + return a.clap.options(name); + } + + pub fn positionals(a: @This()) []const []const u8 { + return a.clap.positionals(); + } + + pub fn remaining(a: @This()) []const []const u8 { + return a.clap.remaining(); + } + + pub fn hasFlag(comptime name: []const u8) bool { + return ComptimeClap(Id, params).hasFlag(name); + } + }; +} + +/// Options that can be set to customize the behavior of parsing. +pub const ParseOptions = struct { + /// The allocator used for all memory allocations. Defaults to the `heap.page_allocator`. + /// Note: You should probably override this allocator if you are calling `parseEx`. Unlike + /// `parse`, `parseEx` does not wrap the allocator so the heap allocator can be + /// quite expensive. (TODO: Can we pick a better default? For `parse`, this allocator + /// is fine, as it wraps it in an arena) + allocator: mem.Allocator = heap.page_allocator, + diagnostic: ?*Diagnostic = null, + stop_after_positional_at: usize = 0, +}; + +/// Same as `parseEx` but uses the `args.OsIterator` by default. +pub fn parse( + comptime Id: type, + comptime params: []const Param(Id), + opt: ParseOptions, +) !Args(Id, params) { + var iter = args.OsIterator.init(opt.allocator); + var res = Args(Id, params){ + .arena = iter.arena, + .exe_arg = iter.exe_arg, + .clap = undefined, + }; + + // Let's reuse the arena from the `OSIterator` since we already have + // it. + res.clap = try parseEx(Id, params, &iter, .{ + .allocator = res.arena.allocator(), + .diagnostic = opt.diagnostic, + .stop_after_positional_at = opt.stop_after_positional_at, + }); + return res; +} + +/// Parses the command line arguments passed into the program based on an +/// array of `Param`s. +pub fn parseEx( + comptime Id: type, + comptime params: []const Param(Id), + iter: anytype, + opt: ParseOptions, +) !ComptimeClap(Id, params) { + const Clap = ComptimeClap(Id, params); + return try Clap.parse(iter, opt); +} + +/// Will print a help message in the following format: +/// -s, --long <valueText> helpText +/// -s, helpText +/// -s <valueText> helpText +/// --long helpText +/// --long <valueText> helpText +pub fn helpFull( + stream: anytype, + comptime Id: type, + params: []const Param(Id), + comptime Error: type, + context: anytype, + helpText: fn (@TypeOf(context), Param(Id)) Error![]const u8, + valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, +) !void { + const max_spacing = blk: { + var res: usize = 0; + for (params) |param| { + var cs = io.countingWriter(io.null_writer); + try printParam(cs.writer(), Id, param, Error, context, valueText); + if (res < cs.bytes_written) + res = @intCast(usize, cs.bytes_written); + } + + break :blk res; + }; + + for (params) |param| { + if (param.names.short == null and param.names.long == null) + continue; + + var cs = io.countingWriter(stream); + try stream.print("\t", .{}); + try printParam(cs.writer(), Id, param, Error, context, valueText); + try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written)); + try stream.print("\t{s}\n", .{try helpText(context, param)}); + } +} + +fn printParam( + stream: anytype, + comptime Id: type, + param: Param(Id), + comptime Error: type, + context: anytype, + valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, +) !void { + if (param.names.short) |s| { + try stream.print("-{c}", .{s}); + } else { + try stream.print(" ", .{}); + } + if (param.names.long) |l| { + if (param.names.short) |_| { + try stream.print(", ", .{}); + } else { + try stream.print(" ", .{}); + } + + try stream.print("--{s}", .{l}); + } + + switch (param.takes_value) { + .none => {}, + .one => try stream.print(" <{s}>", .{try valueText(context, param)}), + .one_optional => try stream.print(" <{s}>?", .{try valueText(context, param)}), + .many => try stream.print(" <{s}>...", .{try valueText(context, param)}), + } +} + +/// A wrapper around helpFull for simple helpText and valueText functions that +/// cant return an error or take a context. +pub fn helpEx( + stream: anytype, + comptime Id: type, + params: []const Param(Id), + helpText: *const fn (Param(Id)) []const u8, + valueText: *const fn (Param(Id)) []const u8, +) !void { + const Context = struct { + helpText: *const fn (Param(Id)) []const u8, + valueText: *const fn (Param(Id)) []const u8, + + pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 { + return c.helpText(p); + } + + pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { + return c.valueText(p); + } + }; + + return helpFull( + stream, + Id, + params, + error{}, + Context{ + .helpText = helpText, + .valueText = valueText, + }, + Context.help, + Context.value, + ); +} + +pub const Help = struct { + msg: []const u8 = "", + value: []const u8 = "", +}; + +/// A wrapper around helpEx that takes a Param(Help). +pub fn help(stream: anytype, params: []const Param(Help)) !void { + try helpEx(stream, Help, params, getHelpSimple, getValueSimple); +} + +fn getHelpSimple(param: Param(Help)) []const u8 { + return param.id.msg; +} + +fn getValueSimple(param: Param(Help)) []const u8 { + return param.id.value; +} + +/// Will print a usage message in the following format: +/// [-abc] [--longa] [-d <valueText>] [--longb <valueText>] <valueText> +/// +/// First all none value taking parameters, which have a short name are +/// printed, then non positional parameters and finally the positinal. +pub fn usageFull( + stream: anytype, + comptime Id: type, + params: []const Param(Id), + comptime Error: type, + context: anytype, + valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, +) !void { + var cos = io.countingWriter(stream); + const cs = cos.writer(); + for (params) |param| { + const name = param.names.short orelse continue; + if (param.takes_value != .none) + continue; + + if (cos.bytes_written == 0) + try stream.writeAll("[-"); + try cs.writeByte(name); + } + if (cos.bytes_written != 0) + try cs.writeByte(']'); + + var positional: ?Param(Id) = null; + for (params) |param| { + if (param.takes_value == .none and param.names.short != null) + continue; + + const prefix = if (param.names.short) |_| "-" else "--"; + + // Seems the zig compiler is being a little wierd. I doesn't allow me to write + // @as(*const [1]u8, s) VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV + const name = if (param.names.short) |*s| @ptrCast([*]const u8, s)[0..1] else param.names.long orelse { + positional = param; + continue; + }; + if (cos.bytes_written != 0) + try cs.writeByte(' '); + + try cs.print("[{s}{s}", .{ prefix, name }); + switch (param.takes_value) { + .none => {}, + .one => try cs.print(" <{s}>", .{try valueText(context, param)}), + .one_optional => try cs.print(" <{s}>?", .{try valueText(context, param)}), + .many => try cs.print(" <{s}>...", .{try valueText(context, param)}), + } + + try cs.writeByte(']'); + } + + if (positional) |p| { + if (cos.bytes_written != 0) + try cs.writeByte(' '); + try cs.print("<{s}>", .{try valueText(context, p)}); + } +} + +/// A wrapper around usageFull for a simple valueText functions that +/// cant return an error or take a context. +pub fn usageEx( + stream: anytype, + comptime Id: type, + params: []const Param(Id), + valueText: fn (Param(Id)) []const u8, +) !void { + const Context = struct { + valueText: fn (Param(Id)) []const u8, + + pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { + return c.valueText(p); + } + }; + + return usageFull( + stream, + Id, + params, + error{}, + Context{ .valueText = valueText }, + Context.value, + ); +} + +/// A wrapper around usageEx that takes a Param(Help). +pub fn usage(stream: anytype, params: []const Param(Help)) !void { + try usageEx(stream, Help, params, getValueSimple); +} + +fn testUsage(expected: []const u8, params: []const Param(Help)) !void { + var buf: [1024]u8 = undefined; + var fbs = io.fixedBufferStream(&buf); + try usage(fbs.writer(), params); + testing.expectEqualStrings(expected, fbs.getWritten()); +} diff --git a/src/deps/zig-clap/clap/args.zig b/src/deps/zig-clap/clap/args.zig new file mode 100644 index 000000000..a1fa3773a --- /dev/null +++ b/src/deps/zig-clap/clap/args.zig @@ -0,0 +1,337 @@ +const std = @import("std"); + +const builtin = @import("builtin"); +const debug = std.debug; +const heap = std.heap; +const mem = std.mem; +const process = std.process; +const testing = std.testing; + +/// An example of what methods should be implemented on an arg iterator. +pub const ExampleArgIterator = struct { + const Error = error{}; + + pub fn next(_: *ExampleArgIterator) Error!?[]const u8 { + return "2"; + } +}; + +/// An argument iterator which iterates over a slice of arguments. +/// This implementation does not allocate. +pub const SliceIterator = struct { + const Error = error{}; + + args: []const []const u8, + index: usize = 0, + + pub fn next(iter: *SliceIterator) Error!?[]const u8 { + if (iter.args.len <= iter.index) + return null; + + defer iter.index += 1; + return iter.args[iter.index]; + } +}; + +test "SliceIterator" { + const args = &[_][]const u8{ "A", "BB", "CCC" }; + var iter = SliceIterator{ .args = args }; + + for (args) |a| { + const b = try iter.next(); + debug.assert(mem.eql(u8, a, b.?)); + } +} + +/// An argument iterator which wraps the ArgIterator in ::std. +/// On windows, this iterator allocates. +pub const OsIterator = struct { + const Error = process.ArgIterator.InitError; + + arena: heap.ArenaAllocator, + args: process.ArgIterator, + + /// The executable path (this is the first argument passed to the program) + /// TODO: Is it the right choice for this to be null? Maybe `init` should + /// return an error when we have no exe. + exe_arg: ?[:0]const u8, + + pub fn init(allocator: mem.Allocator) OsIterator { + var res = OsIterator{ + .arena = heap.ArenaAllocator.init(allocator), + .args = process.args(), + .exe_arg = undefined, + }; + res.exe_arg = res.next(); + return res; + } + + pub fn deinit(iter: *OsIterator) void { + iter.arena.deinit(); + } + + pub fn next(iter: *OsIterator) ?[:0]const u8 { + return iter.args.next(); + } +}; + +/// An argument iterator that takes a string and parses it into arguments, simulating +/// how shells split arguments. +pub const ShellIterator = struct { + const Error = error{ + DanglingEscape, + QuoteNotClosed, + } || mem.Allocator.Error; + + arena: heap.ArenaAllocator, + str: []const u8, + + pub fn init(allocator: mem.Allocator, str: []const u8) ShellIterator { + return .{ + .arena = heap.ArenaAllocator.init(allocator), + .str = str, + }; + } + + pub fn deinit(iter: *ShellIterator) void { + iter.arena.deinit(); + } + + pub fn next(iter: *ShellIterator) Error!?[]const u8 { + // Whenever possible, this iterator will return slices into `str` instead of + // allocating. Sometimes this is not possible, for example, escaped characters + // have be be unescape, so we need to allocate in this case. + var list = std.ArrayList(u8).init(&iter.arena.allocator); + var start: usize = 0; + var state: enum { + skip_whitespace, + no_quote, + no_quote_escape, + single_quote, + double_quote, + double_quote_escape, + after_quote, + } = .skip_whitespace; + + for (iter.str, 0..) |c, i| { + switch (state) { + // The state that skips the initial whitespace. + .skip_whitespace => switch (c) { + ' ', '\t', '\n' => {}, + '\'' => { + start = i + 1; + state = .single_quote; + }, + '"' => { + start = i + 1; + state = .double_quote; + }, + '\\' => { + start = i + 1; + state = .no_quote_escape; + }, + else => { + start = i; + state = .no_quote; + }, + }, + + // The state that parses the none quoted part of a argument. + .no_quote => switch (c) { + // We're done parsing a none quoted argument when we hit a + // whitespace. + ' ', '\t', '\n' => { + defer iter.str = iter.str[i..]; + return iter.result(start, i, &list); + }, + + // Slicing is not possible if a quote starts while parsing none + // quoted args. + // Example: + // ab'cd' -> abcd + '\'' => { + try list.appendSlice(iter.str[start..i]); + start = i + 1; + state = .single_quote; + }, + '"' => { + try list.appendSlice(iter.str[start..i]); + start = i + 1; + state = .double_quote; + }, + + // Slicing is not possible if we need to escape a character. + // Example: + // ab\"d -> ab"d + '\\' => { + try list.appendSlice(iter.str[start..i]); + start = i + 1; + state = .no_quote_escape; + }, + else => {}, + }, + + // We're in this state after having parsed the quoted part of an + // argument. This state works mostly the same as .no_quote, but + // is aware, that the last character seen was a quote, which should + // not be part of the argument. This is why you will see `i - 1` here + // instead of just `i` when `iter.str` is sliced. + .after_quote => switch (c) { + ' ', '\t', '\n' => { + defer iter.str = iter.str[i..]; + return iter.result(start, i - 1, &list); + }, + '\'' => { + try list.appendSlice(iter.str[start .. i - 1]); + start = i + 1; + state = .single_quote; + }, + '"' => { + try list.appendSlice(iter.str[start .. i - 1]); + start = i + 1; + state = .double_quote; + }, + '\\' => { + try list.appendSlice(iter.str[start .. i - 1]); + start = i + 1; + state = .no_quote_escape; + }, + else => { + try list.appendSlice(iter.str[start .. i - 1]); + start = i; + state = .no_quote; + }, + }, + + // The states that parse the quoted part of arguments. The only differnece + // between single and double quoted arguments is that single quoted + // arguments ignore escape sequences, while double quoted arguments + // does escaping. + .single_quote => switch (c) { + '\'' => state = .after_quote, + else => {}, + }, + .double_quote => switch (c) { + '"' => state = .after_quote, + '\\' => { + try list.appendSlice(iter.str[start..i]); + start = i + 1; + state = .double_quote_escape; + }, + else => {}, + }, + + // The state we end up when after the escape character (`\`). All these + // states do is transition back into the previous state. + // TODO: Are there any escape sequences that does transform the second + // character into something else? For example, in Zig, `\n` is + // transformed into the line feed ascii character. + .no_quote_escape => switch (c) { + else => state = .no_quote, + }, + .double_quote_escape => switch (c) { + else => state = .double_quote, + }, + } + } + + defer iter.str = iter.str[iter.str.len..]; + switch (state) { + .skip_whitespace => return null, + .no_quote => return iter.result(start, iter.str.len, &list), + .after_quote => return iter.result(start, iter.str.len - 1, &list), + .no_quote_escape => return Error.DanglingEscape, + .single_quote, + .double_quote, + .double_quote_escape, + => return Error.QuoteNotClosed, + } + } + + fn result(iter: *ShellIterator, start: usize, end: usize, list: *std.ArrayList(u8)) Error!?[]const u8 { + const res = iter.str[start..end]; + + // If we already have something in `list` that means that we could not + // parse the argument without allocation. We therefor need to just append + // the rest we have to the list and return that. + if (list.items.len != 0) { + try list.appendSlice(res); + return try list.toOwnedSlice(); + } + return res; + } +}; + +fn testShellIteratorOk(str: []const u8, allocations: usize, expect: []const []const u8) void { + var allocator = testing.FailingAllocator.init(testing.allocator, allocations); + var it = ShellIterator.init(&allocator.allocator, str); + defer it.deinit(); + + for (expect) |e| { + if (it.next()) |actual| { + testing.expect(actual != null); + testing.expectEqualStrings(e, actual.?); + } else |err| testing.expectEqual(@as(anyerror![]const u8, e), err); + } + + if (it.next()) |actual| { + testing.expectEqual(@as(?[]const u8, null), actual); + testing.expectEqual(allocations, allocator.allocations); + } else |err| testing.expectEqual(@as(anyerror!void, {}), err); +} + +fn testShellIteratorErr(str: []const u8, expect: anyerror) void { + var it = ShellIterator.init(testing.allocator, str); + defer it.deinit(); + + while (it.next() catch |err| { + testing.expectError(expect, @as(anyerror!void, err)); + return; + }) |_| {} + + testing.expectError(expect, @as(anyerror!void, {})); +} + +test "ShellIterator" { + testShellIteratorOk("a", 0, &[_][]const u8{"a"}); + testShellIteratorOk("'a'", 0, &[_][]const u8{"a"}); + testShellIteratorOk("\"a\"", 0, &[_][]const u8{"a"}); + testShellIteratorOk("a b", 0, &[_][]const u8{ "a", "b" }); + testShellIteratorOk("'a' b", 0, &[_][]const u8{ "a", "b" }); + testShellIteratorOk("\"a\" b", 0, &[_][]const u8{ "a", "b" }); + testShellIteratorOk("a 'b'", 0, &[_][]const u8{ "a", "b" }); + testShellIteratorOk("a \"b\"", 0, &[_][]const u8{ "a", "b" }); + testShellIteratorOk("'a b'", 0, &[_][]const u8{"a b"}); + testShellIteratorOk("\"a b\"", 0, &[_][]const u8{"a b"}); + testShellIteratorOk("\"a\"\"b\"", 1, &[_][]const u8{"ab"}); + testShellIteratorOk("'a''b'", 1, &[_][]const u8{"ab"}); + testShellIteratorOk("'a'b", 1, &[_][]const u8{"ab"}); + testShellIteratorOk("a'b'", 1, &[_][]const u8{"ab"}); + testShellIteratorOk("a\\ b", 1, &[_][]const u8{"a b"}); + testShellIteratorOk("\"a\\ b\"", 1, &[_][]const u8{"a b"}); + testShellIteratorOk("'a\\ b'", 0, &[_][]const u8{"a\\ b"}); + testShellIteratorOk(" a b ", 0, &[_][]const u8{ "a", "b" }); + testShellIteratorOk("\\ \\ ", 0, &[_][]const u8{ " ", " " }); + + testShellIteratorOk( + \\printf 'run\nuninstall\n' + , 0, &[_][]const u8{ "printf", "run\\nuninstall\\n" }); + testShellIteratorOk( + \\setsid -f steam "steam://$action/$id" + , 0, &[_][]const u8{ "setsid", "-f", "steam", "steam://$action/$id" }); + testShellIteratorOk( + \\xargs -I% rg --no-heading --no-line-number --only-matching + \\ --case-sensitive --multiline --text --byte-offset '(?-u)%' $@ + \\ + , 0, &[_][]const u8{ + "xargs", "-I%", "rg", "--no-heading", + "--no-line-number", "--only-matching", "--case-sensitive", "--multiline", + "--text", "--byte-offset", "(?-u)%", "$@", + }); + + testShellIteratorErr("'a", error.QuoteNotClosed); + testShellIteratorErr("'a\\", error.QuoteNotClosed); + testShellIteratorErr("\"a", error.QuoteNotClosed); + testShellIteratorErr("\"a\\", error.QuoteNotClosed); + testShellIteratorErr("a\\", error.DanglingEscape); +} diff --git a/src/deps/zig-clap/clap/comptime.zig b/src/deps/zig-clap/clap/comptime.zig new file mode 100644 index 000000000..3dcd4f7d7 --- /dev/null +++ b/src/deps/zig-clap/clap/comptime.zig @@ -0,0 +1,194 @@ +const clap = @import("../clap.zig"); +const std = @import("std"); + +const debug = std.debug; +const heap = std.heap; +const mem = std.mem; +const testing = std.testing; + +/// Deprecated: Use `parseEx` instead +pub fn ComptimeClap( + comptime Id: type, + comptime params: []const clap.Param(Id), +) type { + var _flags: usize = 0; + var _single_options: usize = 0; + var _multi_options: usize = 0; + var _converted_params: []const clap.Param(usize) = &[_]clap.Param(usize){}; + for (params) |param| { + var index: usize = 0; + if (param.names.long != null or param.names.short != null) { + const ptr = switch (param.takes_value) { + .none => &_flags, + .one_optional, .one => &_single_options, + .many => &_multi_options, + }; + index = ptr.*; + ptr.* += 1; + } + + const converted = clap.Param(usize){ + .id = index, + .names = param.names, + .takes_value = param.takes_value, + }; + _converted_params = _converted_params ++ [_]clap.Param(usize){converted}; + } + const flags = _flags; + const single_options = _single_options; + const multi_options = _multi_options; + const converted_params = _converted_params; + + return struct { + single_options: [single_options]?[]const u8, + multi_options: [multi_options][]const []const u8, + flags: [flags]bool, + pos: []const []const u8, + passthrough_positionals: []const []const u8, + allocator: mem.Allocator, + + pub fn parse(iter: anytype, opt: clap.ParseOptions) !@This() { + const allocator = opt.allocator; + var multis = [_]std.ArrayList([]const u8){undefined} ** multi_options; + for (&multis) |*multi| { + multi.* = std.ArrayList([]const u8).init(allocator); + } + + var pos = std.ArrayList([]const u8).init(allocator); + var passthrough_positionals = std.ArrayList([]const u8).init(allocator); + + var res = @This(){ + .single_options = [_]?[]const u8{null} ** single_options, + .multi_options = [_][]const []const u8{undefined} ** multi_options, + .flags = [_]bool{false} ** flags, + .pos = undefined, + .allocator = allocator, + .passthrough_positionals = undefined, + }; + + var stream = clap.StreamingClap(usize, @typeInfo(@TypeOf(iter)).Pointer.child){ + .params = converted_params, + .iter = iter, + }; + + while (try stream.next()) |arg| { + const param = arg.param; + if (param.names.long == null and param.names.short == null) { + try pos.append(arg.value.?); + if (opt.stop_after_positional_at > 0 and pos.items.len >= opt.stop_after_positional_at) { + const bun = @import("bun"); + if (comptime bun.Environment.isWindows) @compileError( + "TODO: implement stop_after_positional_at on windows", + ); + + var remaining_ = std.os.argv[@min(std.os.argv.len, stream.iter.args.inner.index)..]; + const first: []const u8 = if (remaining_.len > 0) bun.span(remaining_[0]) else ""; + if (first.len > 0 and std.mem.eql(u8, first, "--")) { + remaining_ = remaining_[1..]; + } + + try passthrough_positionals.ensureTotalCapacityPrecise(remaining_.len); + for (remaining_) |arg_| { + // use bun.span due to the optimization for long strings + passthrough_positionals.appendAssumeCapacity(bun.span(arg_)); + } + break; + } + } else if (param.takes_value == .one or param.takes_value == .one_optional) { + debug.assert(res.single_options.len != 0); + if (res.single_options.len != 0) + res.single_options[param.id] = arg.value orelse ""; + } else if (param.takes_value == .many) { + debug.assert(multis.len != 0); + if (multis.len != 0) + try multis[param.id].append(arg.value.?); + } else { + debug.assert(res.flags.len != 0); + if (res.flags.len != 0) + res.flags[param.id] = true; + } + } + + for (&multis, 0..) |*multi, i| + res.multi_options[i] = try multi.toOwnedSlice(); + res.pos = try pos.toOwnedSlice(); + res.passthrough_positionals = try passthrough_positionals.toOwnedSlice(); + return res; + } + + pub fn deinit(parser: @This()) void { + for (parser.multi_options) |o| + parser.allocator.free(o); + parser.allocator.free(parser.pos); + } + + pub fn flag(parser: @This(), comptime name: []const u8) bool { + const param = comptime findParam(name); + if (param.takes_value != .none and param.takes_value != .one_optional) + @compileError(name ++ " is an option and not a flag."); + + return parser.flags[param.id]; + } + + pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 { + const param = comptime findParam(name); + if (param.takes_value == .none) + @compileError(name ++ " is a flag and not an option."); + if (param.takes_value == .many) + @compileError(name ++ " takes many options, not one."); + return parser.single_options[param.id]; + } + + pub fn options(parser: @This(), comptime name: []const u8) []const []const u8 { + const param = comptime findParam(name); + if (param.takes_value == .none) + @compileError(name ++ " is a flag and not an option."); + if (param.takes_value == .one or param.takes_value == .one_optional) + @compileError(name ++ " takes one option, not multiple."); + + return parser.multi_options[param.id]; + } + + pub fn positionals(parser: @This()) []const []const u8 { + return parser.pos; + } + + pub fn remaining(parser: @This()) []const []const u8 { + return parser.passthrough_positionals; + } + + pub fn hasFlag(comptime name: []const u8) bool { + comptime { + for (converted_params) |param| { + if (param.names.short) |s| { + if (mem.eql(u8, name, "-" ++ [_]u8{s})) + return true; + } + if (param.names.long) |l| { + if (mem.eql(u8, name, "--" ++ l)) + return true; + } + } + + return false; + } + } + + fn findParam(comptime name: []const u8) clap.Param(usize) { + comptime { + for (converted_params) |param| { + if (param.names.short) |s| { + if (mem.eql(u8, name, "-" ++ [_]u8{s})) + return param; + } + if (param.names.long) |l| { + if (mem.eql(u8, name, "--" ++ l)) + return param; + } + } + + @compileError(name ++ " is not a parameter."); + } + } + }; +} diff --git a/src/deps/zig-clap/clap/streaming.zig b/src/deps/zig-clap/clap/streaming.zig new file mode 100644 index 000000000..e3948e33b --- /dev/null +++ b/src/deps/zig-clap/clap/streaming.zig @@ -0,0 +1,430 @@ +const builtin = @import("builtin"); +const clap = @import("../clap.zig"); +const std = @import("std"); + +const args = clap.args; +const debug = std.debug; +const heap = std.heap; +const io = std.io; +const mem = std.mem; +const os = std.os; +const testing = std.testing; + +/// The result returned from StreamingClap.next +pub fn Arg(comptime Id: type) type { + return struct { + const Self = @This(); + + param: *const clap.Param(Id), + value: ?[]const u8 = null, + }; +} + +/// A command line argument parser which, given an ArgIterator, will parse arguments according +/// to the params. StreamingClap parses in an iterating manner, so you have to use a loop together with +/// StreamingClap.next to parse all the arguments of your program. +pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { + return struct { + const State = union(enum) { + normal, + chaining: Chaining, + rest_are_positional, + + const Chaining = struct { + arg: []const u8, + index: usize, + }; + }; + + params: []const clap.Param(Id), + iter: *ArgIterator, + state: State = .normal, + positional: ?*const clap.Param(Id) = null, + diagnostic: ?*clap.Diagnostic = null, + + /// Get the next Arg that matches a Param. + pub fn next(parser: *@This()) !?Arg(Id) { + switch (parser.state) { + .normal => return try parser.normal(), + .chaining => |state| return try parser.chainging(state), + .rest_are_positional => { + const param = parser.positionalParam() orelse unreachable; + const value = parser.iter.next() orelse return null; + return Arg(Id){ .param = param, .value = value }; + }, + } + } + + fn normal(parser: *@This()) !?Arg(Id) { + const ArgType = Arg(Id); + const arg_info = (try parser.parseNextArg()) orelse return null; + const arg = arg_info.arg; + + switch (arg_info.kind) { + .long => { + const eql_index = mem.indexOfScalar(u8, arg, '='); + const name = if (eql_index) |i| arg[0..i] else arg; + const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; + + for (parser.params) |*param| { + const match = param.names.long orelse continue; + + if (!mem.eql(u8, name, match)) + continue; + if (param.takes_value == .none) { + if (maybe_value != null) + return parser.err(arg, .{ .long = name }, error.DoesntTakeValue); + + return ArgType{ .param = param }; + } + + const value = blk: { + if (maybe_value) |v| + break :blk v; + + break :blk parser.iter.next() orelse brk2: { + if (param.takes_value != .one_optional) + return parser.err(arg, .{ .long = name }, error.MissingValue); + + break :brk2 ""; + }; + }; + + return ArgType{ .param = param, .value = value }; + } + + return null; + }, + .short => return try parser.chainging(.{ + .arg = arg, + .index = 0, + }), + .positional => if (parser.positionalParam()) |param| { + // If we find a positional with the value `--` then we + // interpret the rest of the arguments as positional + // arguments. + if (mem.eql(u8, arg, "--")) { + parser.state = .rest_are_positional; + const value = parser.iter.next() orelse return null; + return Arg(Id){ .param = param, .value = value }; + } + + return Arg(Id){ .param = param, .value = arg }; + } else { + return parser.err(arg, .{}, error.InvalidArgument); + }, + } + } + + fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) { + const arg = state.arg; + const index = state.index; + const next_index = index + 1; + + for (parser.params) |*param| { + const short = param.names.short orelse continue; + if (short != arg[index]) + continue; + + // Before we return, we have to set the new state of the clap + defer { + if (arg.len <= next_index or param.takes_value != .none) { + parser.state = .normal; + } else { + parser.state = .{ + .chaining = .{ + .arg = arg, + .index = next_index, + }, + }; + } + } + + const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false; + if (param.takes_value == .none or param.takes_value == .one_optional) { + if (next_is_eql and param.takes_value == .none) + return parser.err(arg, .{ .short = short }, error.DoesntTakeValue); + return Arg(Id){ .param = param }; + } + + if (arg.len <= next_index) { + const value = parser.iter.next() orelse + return parser.err(arg, .{ .short = short }, error.MissingValue); + + return Arg(Id){ .param = param, .value = value }; + } + + if (next_is_eql) + return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] }; + + return Arg(Id){ .param = param, .value = arg[next_index..] }; + } + + return parser.err(arg, .{ .short = arg[index] }, error.InvalidArgument); + } + + fn positionalParam(parser: *@This()) ?*const clap.Param(Id) { + if (parser.positional) |p| + return p; + + for (parser.params) |*param| { + if (param.names.long) |_| + continue; + if (param.names.short) |_| + continue; + + parser.positional = param; + return param; + } + + return null; + } + + const ArgInfo = struct { + arg: []const u8, + kind: enum { + long, + short, + positional, + }, + }; + + fn parseNextArg(parser: *@This()) !?ArgInfo { + const full_arg = parser.iter.next() orelse return null; + if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) + return ArgInfo{ .arg = full_arg, .kind = .positional }; + if (mem.startsWith(u8, full_arg, "--")) + return ArgInfo{ .arg = full_arg[2..], .kind = .long }; + if (mem.startsWith(u8, full_arg, "-")) + return ArgInfo{ .arg = full_arg[1..], .kind = .short }; + + return ArgInfo{ .arg = full_arg, .kind = .positional }; + } + + fn err(parser: @This(), arg: []const u8, names: clap.Names, _err: anytype) @TypeOf(_err) { + if (parser.diagnostic) |d| + d.* = .{ .arg = arg, .name = names }; + return _err; + } + }; +} + +fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void { + var iter = args.SliceIterator{ .args = args_strings }; + var c = StreamingClap(u8, args.SliceIterator){ + .params = params, + .iter = &iter, + }; + + for (results) |res| { + const arg = (c.next() catch unreachable) orelse unreachable; + testing.expectEqual(res.param, arg.param); + const expected_value = res.value orelse { + testing.expectEqual(@as(@TypeOf(arg.value), null), arg.value); + continue; + }; + const actual_value = arg.value orelse unreachable; + testing.expectEqualSlices(u8, expected_value, actual_value); + } + + if (c.next() catch unreachable) |_| + unreachable; +} + +fn testErr(params: []const clap.Param(u8), args_strings: []const []const u8, expected: []const u8) void { + var diag = clap.Diagnostic{}; + var iter = args.SliceIterator{ .args = args_strings }; + var c = StreamingClap(u8, args.SliceIterator){ + .params = params, + .iter = &iter, + .diagnostic = &diag, + }; + while (c.next() catch |err| { + var buf: [1024]u8 = undefined; + var fbs = io.fixedBufferStream(&buf); + diag.report(fbs.writer(), err) catch unreachable; + testing.expectEqualStrings(expected, fbs.getWritten()); + return; + }) |_| {} + + testing.expect(false); +} + +test "short params" { + const params = [_]clap.Param(u8){ + .{ .id = 0, .names = .{ .short = 'a' } }, + .{ .id = 1, .names = .{ .short = 'b' } }, + .{ + .id = 2, + .names = .{ .short = 'c' }, + .takes_value = .one, + }, + .{ + .id = 3, + .names = .{ .short = 'd' }, + .takes_value = .many, + }, + }; + + const a = ¶ms[0]; + const b = ¶ms[1]; + const c = ¶ms[2]; + const d = ¶ms[3]; + + testNoErr( + ¶ms, + &[_][]const u8{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", "-ac", + "0", "-ac=0", "-d=0", + }, + &[_]Arg(u8){ + .{ .param = a }, + .{ .param = b }, + .{ .param = a }, + .{ .param = b }, + .{ .param = b }, + .{ .param = a }, + .{ .param = c, .value = "0" }, + .{ .param = c, .value = "0" }, + .{ .param = a }, + .{ .param = c, .value = "0" }, + .{ .param = a }, + .{ .param = c, .value = "0" }, + .{ .param = d, .value = "0" }, + }, + ); +} + +test "long params" { + const params = [_]clap.Param(u8){ + .{ .id = 0, .names = .{ .long = "aa" } }, + .{ .id = 1, .names = .{ .long = "bb" } }, + .{ + .id = 2, + .names = .{ .long = "cc" }, + .takes_value = .one, + }, + .{ + .id = 3, + .names = .{ .long = "dd" }, + .takes_value = .many, + }, + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + const dd = ¶ms[3]; + + testNoErr( + ¶ms, + &[_][]const u8{ + "--aa", "--bb", + "--cc", "0", + "--cc=0", "--dd=0", + }, + &[_]Arg(u8){ + .{ .param = aa }, + .{ .param = bb }, + .{ .param = cc, .value = "0" }, + .{ .param = cc, .value = "0" }, + .{ .param = dd, .value = "0" }, + }, + ); +} + +test "positional params" { + const params = [_]clap.Param(u8){.{ + .id = 0, + .takes_value = .one, + }}; + + testNoErr( + ¶ms, + &[_][]const u8{ "aa", "bb" }, + &[_]Arg(u8){ + .{ .param = ¶ms[0], .value = "aa" }, + .{ .param = ¶ms[0], .value = "bb" }, + }, + ); +} + +test "all params" { + const params = [_]clap.Param(u8){ + .{ + .id = 0, + .names = .{ .short = 'a', .long = "aa" }, + }, + .{ + .id = 1, + .names = .{ .short = 'b', .long = "bb" }, + }, + .{ + .id = 2, + .names = .{ .short = 'c', .long = "cc" }, + .takes_value = .one, + }, + .{ .id = 3, .takes_value = .one }, + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + const positional = ¶ms[3]; + + testNoErr( + ¶ms, + &[_][]const u8{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", "-ac", + "0", "-ac=0", "--aa", "--bb", + "--cc", "0", "--cc=0", "something", + "-", "--", "--cc=0", "-a", + }, + &[_]Arg(u8){ + .{ .param = aa }, + .{ .param = bb }, + .{ .param = aa }, + .{ .param = bb }, + .{ .param = bb }, + .{ .param = aa }, + .{ .param = cc, .value = "0" }, + .{ .param = cc, .value = "0" }, + .{ .param = aa }, + .{ .param = cc, .value = "0" }, + .{ .param = aa }, + .{ .param = cc, .value = "0" }, + .{ .param = aa }, + .{ .param = bb }, + .{ .param = cc, .value = "0" }, + .{ .param = cc, .value = "0" }, + .{ .param = positional, .value = "something" }, + .{ .param = positional, .value = "-" }, + .{ .param = positional, .value = "--cc=0" }, + .{ .param = positional, .value = "-a" }, + }, + ); +} + +test "errors" { + const params = [_]clap.Param(u8){ + .{ + .id = 0, + .names = .{ .short = 'a', .long = "aa" }, + }, + .{ + .id = 1, + .names = .{ .short = 'c', .long = "cc" }, + .takes_value = .one, + }, + }; + testErr(¶ms, &[_][]const u8{"q"}, "Invalid argument 'q'\n"); + testErr(¶ms, &[_][]const u8{"-q"}, "Invalid argument '-q'\n"); + testErr(¶ms, &[_][]const u8{"--q"}, "Invalid argument '--q'\n"); + testErr(¶ms, &[_][]const u8{"--q=1"}, "Invalid argument '--q'\n"); + testErr(¶ms, &[_][]const u8{"-a=1"}, "The argument '-a' does not take a value\n"); + testErr(¶ms, &[_][]const u8{"--aa=1"}, "The argument '--aa' does not take a value\n"); + testErr(¶ms, &[_][]const u8{"-c"}, "The argument '-c' requires a value but none was supplied\n"); + testErr(¶ms, &[_][]const u8{"--cc"}, "The argument '--cc' requires a value but none was supplied\n"); +} diff --git a/src/deps/zig-clap/gyro.zzz b/src/deps/zig-clap/gyro.zzz new file mode 100644 index 000000000..3853db049 --- /dev/null +++ b/src/deps/zig-clap/gyro.zzz @@ -0,0 +1,14 @@ +pkgs: + clap: + version: 0.3.0 + license: Unlicense + description: Simple command line argument parsing library + source_url: "https://github.com/Hejsil/zig-clap" + root: clap.zig + files: + README.md + LICENSE + build.zig + clap/*.zig + example/*.zig + diff --git a/src/deps/zig-clap/zig.mod b/src/deps/zig-clap/zig.mod new file mode 100644 index 000000000..00c1a690d --- /dev/null +++ b/src/deps/zig-clap/zig.mod @@ -0,0 +1,5 @@ +id: aoe2l16htluewam6bfwvv0khsbbno8g8jd7suonifg74u7kd +name: clap +main: clap.zig +license: Unlicense +dependencies: diff --git a/src/install/install.zig b/src/install/install.zig index 5399a6fd3..0c280ccaa 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -5271,58 +5271,56 @@ pub const PackageManager = struct { else "Possible values: \"hardlink\" (default), \"symlink\", \"copyfile\""; - pub const install_params_ = clap.parseParamsComptime( - \\-c, --config <STR>? Load config (bunfig.toml) - \\-y, --yarn Write a yarn.lock file (yarn v1) - \\-p, --production Don't install devDependencies - \\--no-save Don't save a lockfile - \\--dry-run Don't install anything - \\--lockfile <PATH> Store & load a lockfile at a specific filepath - \\-f, --force Always request the latest versions from the registry & reinstall all dependencies - \\--cache-dir <PATH> Store & load cached data from a specific directory path - \\--no-cache Ignore manifest cache entirely - \\--silent Don't log anything - \\--verbose Excessively verbose logging - \\--no-progress Disable the progress bar - \\--no-summary Don't print a summary - \\--no-verify Skip verifying integrity of newly downloaded packages - \\--ignore-scripts Skip lifecycle scripts in the project's package.json (dependency scripts are never run) - \\-g, --global Install globally - \\--cwd <STR> Set a specific cwd - \\--backend <STR> Platform-specific optimizations for installing dependencies. - \\ - ++ platform_specific_backend_label ++ - \\ - \\--link-native-bins <STR>... Link "bin" from a matching platform-specific "optionalDependencies" instead. Default: esbuild, turbo - \\--help Print this help menu - ); - // clap.parseParam("--omit <str>... Skip installing dependencies of a certain type. \"dev\", \"optional\", or \"peer\"") catch unreachable, + pub const install_params_ = [_]ParamType{ + clap.parseParam("-c, --config <STR>? Load config (bunfig.toml)") catch unreachable, + clap.parseParam("-y, --yarn Write a yarn.lock file (yarn v1)") catch unreachable, + clap.parseParam("-p, --production Don't install devDependencies") catch unreachable, + clap.parseParam("--no-save Don't save a lockfile") catch unreachable, + clap.parseParam("--dry-run Don't install anything") catch unreachable, + clap.parseParam("--lockfile <PATH> Store & load a lockfile at a specific filepath") catch unreachable, + clap.parseParam("-f, --force Always request the latest versions from the registry & reinstall all dependencies") catch unreachable, + clap.parseParam("--cache-dir <PATH> Store & load cached data from a specific directory path") catch unreachable, + clap.parseParam("--no-cache Ignore manifest cache entirely") catch unreachable, + clap.parseParam("--silent Don't log anything") catch unreachable, + clap.parseParam("--verbose Excessively verbose logging") catch unreachable, + clap.parseParam("--no-progress Disable the progress bar") catch unreachable, + clap.parseParam("--no-summary Don't print a summary") catch unreachable, + clap.parseParam("--no-verify Skip verifying integrity of newly downloaded packages") catch unreachable, + clap.parseParam("--ignore-scripts Skip lifecycle scripts in the project's package.json (dependency scripts are never run)") catch unreachable, + clap.parseParam("-g, --global Install globally") catch unreachable, + clap.parseParam("--cwd <STR> Set a specific cwd") catch unreachable, + clap.parseParam("--backend <STR> Platform-specific optimizations for installing dependencies. " ++ platform_specific_backend_label) catch unreachable, + clap.parseParam("--link-native-bins <STR>... Link \"bin\" from a matching platform-specific \"optionalDependencies\" instead. Default: esbuild, turbo") catch unreachable, + + // clap.parseParam("--omit <STR>... Skip installing dependencies of a certain type. \"dev\", \"optional\", or \"peer\"") catch unreachable, // clap.parseParam("--no-dedupe Disable automatic downgrading of dependencies that would otherwise cause unnecessary duplicate package versions ($BUN_CONFIG_NO_DEDUPLICATE)") catch unreachable, + clap.parseParam("--help Print this help menu") catch unreachable, + }; - pub const install_params = install_params_ ++ clap.parseParamsComptime( - \\<STR> ... - ); + pub const install_params = install_params_ ++ [_]ParamType{ + clap.parseParam("<POS> ... ") catch unreachable, + }; - pub const add_params = install_params_ ++ clap.parseParamsComptime( - \\-d, --development Add dependency to "devDependencies" - \\--optional Add dependency to "optionalDependencies" - \\<STR> ... "name" or "name@version" of packages to install - ); + pub const add_params = install_params_ ++ [_]ParamType{ + clap.parseParam("-d, --development Add dependency to \"devDependencies\"") catch unreachable, + clap.parseParam("--optional Add dependency to \"optionalDependencies\"") catch unreachable, + clap.parseParam("<POS> ... \"name\" or \"name@version\" of packages to install") catch unreachable, + }; - pub const remove_params = install_params_ ++ clap.parseParamsComptime( - \\<STR> ... "name" of packages to remove from package.json - ); + pub const remove_params = install_params_ ++ [_]ParamType{ + clap.parseParam("<POS> ... \"name\" of packages to remove from package.json") catch unreachable, + }; - pub const link_params = install_params_ ++ clap.parseParamsComptime( - \\--save Save to package.json - \\<STR> ... "name" install package as a link - ); + pub const link_params = install_params_ ++ [_]ParamType{ + clap.parseParam("--save Save to package.json") catch unreachable, + clap.parseParam("<POS> ... \"name\" install package as a link") catch unreachable, + }; - pub const unlink_params = install_params_ ++ clap.parseParamsComptime( - \\--save Save to package.json - \\<STR> ... "name" uninstall package as a link - ); + pub const unlink_params = install_params_ ++ [_]ParamType{ + clap.parseParam("--save Save to package.json") catch unreachable, + clap.parseParam("<POS> ... \"name\" uninstall package as a link") catch unreachable, + }; pub const CommandLineArguments = struct { registry: string = "", @@ -5379,63 +5377,62 @@ pub const PackageManager = struct { ) !CommandLineArguments { var diag = clap.Diagnostic{}; - var res = clap.parse(clap.Help, params, BunArguments.parser, .{ + var args = clap.parse(clap.Help, params, .{ .diagnostic = &diag, .allocator = allocator, }) catch |err| { - clap.help(Output.errorWriter(), clap.Help, params, .{}) catch {}; + clap.help(Output.errorWriter(), params) catch {}; Output.errorWriter().writeAll("\n") catch {}; diag.report(Output.errorWriter(), err) catch {}; return err; }; - if (res.args.help) { + if (args.flag("--help")) { Output.prettyln("\n<b><magenta>bun<r> (package manager) flags:<r>\n\n", .{}); Output.flush(); - clap.help(Output.writer(), clap.Help, params, .{}) catch {}; + clap.help(Output.writer(), params) catch {}; Global.exit(0); } var cli = CommandLineArguments{}; - cli.yarn = res.args.yarn; - cli.production = res.args.production; - cli.no_save = res.args.@"no-save"; - cli.no_progress = res.args.@"no-progress"; - cli.dry_run = res.args.@"dry-run"; - cli.global = res.args.global; - cli.force = res.args.force; - cli.no_verify = res.args.@"no-verify"; - // cli.no_dedupe = res.args.no_dedupe; - cli.no_cache = res.args.@"no-cache"; - cli.silent = res.args.silent; - cli.verbose = res.args.verbose; - cli.ignore_scripts = res.args.@"ignore-scripts"; - cli.no_summary = res.args.@"no-summary"; - - if (comptime @hasDecl(@TypeOf(res.args), "save")) { + cli.yarn = args.flag("--yarn"); + cli.production = args.flag("--production"); + cli.no_save = args.flag("--no-save"); + cli.no_progress = args.flag("--no-progress"); + cli.dry_run = args.flag("--dry-run"); + cli.global = args.flag("--global"); + cli.force = args.flag("--force"); + cli.no_verify = args.flag("--no-verify"); + // cli.no_dedupe = args.flag("--no-dedupe"); + cli.no_cache = args.flag("--no-cache"); + cli.silent = args.flag("--silent"); + cli.verbose = args.flag("--verbose"); + cli.ignore_scripts = args.flag("--ignore-scripts"); + cli.no_summary = args.flag("--no-summary"); + if (comptime @TypeOf(args).hasFlag("--save")) { cli.no_save = true; - if (res.args.save) { + if (args.flag("--save")) { cli.no_save = false; } } - if (res.args.config) |opt| { + if (args.option("--config")) |opt| { cli.config = opt; } try BunArguments.loadConfig(allocator, cli.config, ctx, .InstallCommand); - cli.link_native_bins = res.args.@"link-native-bins"; + cli.link_native_bins = args.options("--link-native-bins"); if (comptime params.len == add_params.len) { - cli.development = res.args.development; - cli.optional = res.args.optional; + cli.development = args.flag("--development"); + cli.optional = args.flag("--optional"); } - // for (res.args.omit) |omit| { + // for (args.options("--omit")) |omit| { // if (strings.eqlComptime(omit, "dev")) { // cli.omit.dev = true; // } else if (strings.eqlComptime(omit, "optional")) { @@ -5448,11 +5445,11 @@ pub const PackageManager = struct { // } // } - if (res.args.lockfile) |lockfile| { + if (args.option("--lockfile")) |lockfile| { cli.lockfile = lockfile; } - if (res.args.cwd) |cwd_| { + if (args.option("--cwd")) |cwd_| { var buf: [bun.MAX_PATH_BYTES]u8 = undefined; var buf2: [bun.MAX_PATH_BYTES]u8 = undefined; var final_path: [:0]u8 = undefined; @@ -5471,7 +5468,7 @@ pub const PackageManager = struct { } const specified_backend: ?PackageInstall.Method = brk: { - if (res.args.backend) |backend_| { + if (args.option("--backend")) |backend_| { break :brk PackageInstall.Method.map.get(backend_); } break :brk null; @@ -5483,7 +5480,7 @@ pub const PackageManager = struct { } } - cli.positionals = res.positionals; + cli.positionals = args.positionals(); return cli; } |