mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Merge pull request #442 from karlseguin/cli_commands
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Some checks are pending
e2e-test / zig build release (push) Waiting to run
e2e-test / puppeteer (push) Blocked by required conditions
wpt / web platform tests (push) Waiting to run
wpt / perf-fmt (push) Blocked by required conditions
zig-test / zig build dev (push) Waiting to run
zig-test / zig test (push) Waiting to run
zig-test / perf-fmt (push) Blocked by required conditions
Add explicit commands to binary
This commit is contained in:
2
.github/workflows/e2e-test.yml
vendored
2
.github/workflows/e2e-test.yml
vendored
@@ -85,7 +85,7 @@ jobs:
|
|||||||
- name: run puppeteer
|
- name: run puppeteer
|
||||||
run: |
|
run: |
|
||||||
python3 -m http.server 1234 -d ./public & echo $! > PYTHON.pid
|
python3 -m http.server 1234 -d ./public & echo $! > PYTHON.pid
|
||||||
./lightpanda & echo $! > LPD.pid
|
./lightpanda serve & echo $! > LPD.pid
|
||||||
RUNS=100 npm run bench-puppeteer-cdp > puppeteer.out || exit 1
|
RUNS=100 npm run bench-puppeteer-cdp > puppeteer.out || exit 1
|
||||||
cat /proc/`cat LPD.pid`/status |grep VmHWM|grep -oP '\d+' > LPD.VmHWM
|
cat /proc/`cat LPD.pid`/status |grep VmHWM|grep -oP '\d+' > LPD.VmHWM
|
||||||
kill `cat LPD.pid` `cat PYTHON.pid`
|
kill `cat LPD.pid` `cat PYTHON.pid`
|
||||||
|
|||||||
@@ -76,4 +76,4 @@ COPY --from=0 /browser/zig-out/bin/lightpanda /bin/lightpanda
|
|||||||
|
|
||||||
EXPOSE 9222/tcp
|
EXPOSE 9222/tcp
|
||||||
|
|
||||||
CMD ["/bin/lightpanda", "--host", "0.0.0.0", "--port", "9222"]
|
CMD ["/bin/lightpanda", "serve", "--host", "0.0.0.0", "--port", "9222"]
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ chmod a+x ./lightpanda
|
|||||||
### Dump an URL
|
### Dump an URL
|
||||||
|
|
||||||
```console
|
```console
|
||||||
./lightpanda --dump https://lightpanda.io
|
./lightpanda fetch --dump https://lightpanda.io
|
||||||
```
|
```
|
||||||
```console
|
```console
|
||||||
info(browser): GET https://lightpanda.io/ http.Status.ok
|
info(browser): GET https://lightpanda.io/ http.Status.ok
|
||||||
@@ -73,7 +73,7 @@ info(browser): eval remote https://api.website.lightpanda.io/js/script.js: TypeE
|
|||||||
### Start a CDP server
|
### Start a CDP server
|
||||||
|
|
||||||
```console
|
```console
|
||||||
./lightpanda --host 127.0.0.1 --port 9222
|
./lightpanda serve --host 127.0.0.1 --port 9222
|
||||||
```
|
```
|
||||||
```console
|
```console
|
||||||
info(websocket): starting blocking worker to listen on 127.0.0.1:9222
|
info(websocket): starting blocking worker to listen on 127.0.0.1:9222
|
||||||
|
|||||||
401
src/main.zig
401
src/main.zig
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const jsruntime = @import("jsruntime");
|
const jsruntime = @import("jsruntime");
|
||||||
|
|
||||||
@@ -31,10 +32,6 @@ pub const Types = jsruntime.reflect(apiweb.Interfaces);
|
|||||||
pub const UserContext = apiweb.UserContext;
|
pub const UserContext = apiweb.UserContext;
|
||||||
pub const IO = @import("asyncio").Wrapper(jsruntime.Loop);
|
pub const IO = @import("asyncio").Wrapper(jsruntime.Loop);
|
||||||
|
|
||||||
// Simple blocking websocket connection model
|
|
||||||
// ie. 1 thread per ws connection without thread pool and epoll/kqueue
|
|
||||||
pub const websocket_blocking = true;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.cli);
|
const log = std.log.scoped(.cli);
|
||||||
|
|
||||||
pub const std_options = .{
|
pub const std_options = .{
|
||||||
@@ -45,161 +42,7 @@ pub const std_options = .{
|
|||||||
.logFn = logFn,
|
.logFn = logFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
const usage =
|
|
||||||
\\usage: {s} [options] [URL]
|
|
||||||
\\
|
|
||||||
\\ start Lightpanda browser
|
|
||||||
\\
|
|
||||||
\\ * if an url is provided the browser will fetch the page and exit
|
|
||||||
\\ * otherwhise the browser starts a CDP server
|
|
||||||
\\
|
|
||||||
\\ -h, --help Print this help message and exit.
|
|
||||||
\\ --verbose Display all logs. By default only info, warn and err levels are displayed.
|
|
||||||
\\ --host Host of the CDP server (default "127.0.0.1")
|
|
||||||
\\ --port Port of the CDP server (default "9222")
|
|
||||||
\\ --timeout Timeout for incoming connections of the CDP server (in seconds, default "3")
|
|
||||||
\\ --dump Dump document in stdout (fetch mode only)
|
|
||||||
\\
|
|
||||||
;
|
|
||||||
|
|
||||||
fn printUsageExit(execname: []const u8, res: u8) anyerror {
|
|
||||||
std.io.getStdErr().writer().print(usage, .{execname}) catch |err| {
|
|
||||||
std.log.err("Print usage error: {any}", .{err});
|
|
||||||
return error.Cli;
|
|
||||||
};
|
|
||||||
if (res == 1) return error.Usage;
|
|
||||||
return error.NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CliModeTag = enum {
|
|
||||||
server,
|
|
||||||
fetch,
|
|
||||||
};
|
|
||||||
|
|
||||||
const CliMode = union(CliModeTag) {
|
|
||||||
server: Server,
|
|
||||||
fetch: Fetch,
|
|
||||||
|
|
||||||
const Server = struct {
|
|
||||||
execname: []const u8 = undefined,
|
|
||||||
args: *std.process.ArgIterator = undefined,
|
|
||||||
host: []const u8 = Host,
|
|
||||||
port: u16 = Port,
|
|
||||||
timeout: u8 = Timeout,
|
|
||||||
|
|
||||||
// default options
|
|
||||||
const Host = "127.0.0.1";
|
|
||||||
const Port = 9222;
|
|
||||||
const Timeout = 3; // in seconds
|
|
||||||
};
|
|
||||||
|
|
||||||
const Fetch = struct {
|
|
||||||
execname: []const u8 = undefined,
|
|
||||||
args: *std.process.ArgIterator = undefined,
|
|
||||||
url: []const u8 = "",
|
|
||||||
dump: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn init(alloc: std.mem.Allocator, args: *std.process.ArgIterator) !CliMode {
|
|
||||||
args.* = try std.process.argsWithAllocator(alloc);
|
|
||||||
errdefer args.deinit();
|
|
||||||
|
|
||||||
const execname = args.next().?;
|
|
||||||
var default_mode: CliModeTag = .server;
|
|
||||||
|
|
||||||
var _server = Server{};
|
|
||||||
var _fetch = Fetch{};
|
|
||||||
|
|
||||||
while (args.next()) |opt| {
|
|
||||||
if (std.mem.eql(u8, "-h", opt) or std.mem.eql(u8, "--help", opt)) {
|
|
||||||
return printUsageExit(execname, 0);
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--verbose", opt)) {
|
|
||||||
verbose = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--dump", opt)) {
|
|
||||||
_fetch.dump = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--host", opt)) {
|
|
||||||
if (args.next()) |arg| {
|
|
||||||
_server.host = arg;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
std.log.err("--host not provided\n", .{});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--port", opt)) {
|
|
||||||
if (args.next()) |arg| {
|
|
||||||
_server.port = std.fmt.parseInt(u16, arg, 10) catch |err| {
|
|
||||||
log.err("--port {any}\n", .{err});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
log.err("--port not provided\n", .{});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (std.mem.eql(u8, "--timeout", opt)) {
|
|
||||||
if (args.next()) |arg| {
|
|
||||||
_server.timeout = std.fmt.parseInt(u8, arg, 10) catch |err| {
|
|
||||||
log.err("--timeout {any}\n", .{err});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
log.err("--timeout not provided\n", .{});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unknown option
|
|
||||||
if (std.mem.startsWith(u8, opt, "--")) {
|
|
||||||
log.err("unknown option\n", .{});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// other argument is considered to be an URL, ie. fetch mode
|
|
||||||
default_mode = .fetch;
|
|
||||||
|
|
||||||
// allow only one url
|
|
||||||
if (_fetch.url.len != 0) {
|
|
||||||
log.err("more than 1 url provided\n", .{});
|
|
||||||
return printUsageExit(execname, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
_fetch.url = opt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (default_mode == .server) {
|
|
||||||
|
|
||||||
// server mode
|
|
||||||
_server.execname = execname;
|
|
||||||
_server.args = args;
|
|
||||||
return CliMode{ .server = _server };
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// fetch mode
|
|
||||||
_fetch.execname = execname;
|
|
||||||
_fetch.args = args;
|
|
||||||
return CliMode{ .fetch = _fetch };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: CliMode) void {
|
|
||||||
switch (self) {
|
|
||||||
inline .server, .fetch => |*_mode| {
|
|
||||||
_mode.args.deinit();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
|
|
||||||
// allocator
|
// allocator
|
||||||
// - in Debug mode we use the General Purpose Allocator to detect memory leaks
|
// - in Debug mode we use the General Purpose Allocator to detect memory leaks
|
||||||
// - in Release mode we use the c allocator
|
// - in Release mode we use the c allocator
|
||||||
@@ -210,23 +53,16 @@ pub fn main() !void {
|
|||||||
_ = gpa.detectLeaks();
|
_ = gpa.detectLeaks();
|
||||||
};
|
};
|
||||||
|
|
||||||
// args
|
var args_arena = std.heap.ArenaAllocator.init(alloc);
|
||||||
var args: std.process.ArgIterator = undefined;
|
defer args_arena.deinit();
|
||||||
const cli_mode = CliMode.init(alloc, &args) catch |err| {
|
const args = try parseArgs(args_arena.allocator());
|
||||||
if (err == error.NoError) {
|
|
||||||
std.posix.exit(0);
|
|
||||||
} else {
|
|
||||||
std.posix.exit(1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
defer cli_mode.deinit();
|
|
||||||
|
|
||||||
switch (cli_mode) {
|
switch (args.mode) {
|
||||||
.server => |opts| {
|
.help => args.printUsageAndExit(args.mode.help),
|
||||||
|
.serve => |opts| {
|
||||||
const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
|
const address = std.net.Address.parseIp4(opts.host, opts.port) catch |err| {
|
||||||
log.err("address (host:port) {any}\n", .{err});
|
log.err("address (host:port) {any}\n", .{err});
|
||||||
return printUsageExit(opts.execname, 1);
|
return args.printUsageAndExit(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
var loop = try jsruntime.Loop.init(alloc);
|
var loop = try jsruntime.Loop.init(alloc);
|
||||||
@@ -238,7 +74,6 @@ pub fn main() !void {
|
|||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
.fetch => |opts| {
|
.fetch => |opts| {
|
||||||
log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump });
|
log.debug("Fetch mode: url {s}, dump {any}", .{ opts.url, opts.dump });
|
||||||
|
|
||||||
@@ -264,11 +99,11 @@ pub fn main() !void {
|
|||||||
_ = page.navigate(opts.url, null) catch |err| switch (err) {
|
_ = page.navigate(opts.url, null) catch |err| switch (err) {
|
||||||
error.UnsupportedUriScheme, error.UriMissingHost => {
|
error.UnsupportedUriScheme, error.UriMissingHost => {
|
||||||
log.err("'{s}' is not a valid URL ({any})\n", .{ opts.url, err });
|
log.err("'{s}' is not a valid URL ({any})\n", .{ opts.url, err });
|
||||||
return printUsageExit(opts.execname, 1);
|
return args.printUsageAndExit(false);
|
||||||
},
|
},
|
||||||
else => {
|
else => {
|
||||||
log.err("'{s}' fetching error ({any})s\n", .{ opts.url, err });
|
log.err("'{s}' fetching error ({any})\n", .{ opts.url, err });
|
||||||
return printUsageExit(opts.execname, 1);
|
return err;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -282,6 +117,220 @@ pub fn main() !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Command = struct {
|
||||||
|
mode: Mode,
|
||||||
|
exec_name: []const u8,
|
||||||
|
|
||||||
|
const ModeType = enum {
|
||||||
|
help,
|
||||||
|
fetch,
|
||||||
|
serve,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Mode = union(ModeType) {
|
||||||
|
help: bool, // false when being printed because of an error
|
||||||
|
fetch: Fetch,
|
||||||
|
serve: Serve,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Serve = struct {
|
||||||
|
host: []const u8,
|
||||||
|
port: u16,
|
||||||
|
timeout: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Fetch = struct {
|
||||||
|
url: []const u8,
|
||||||
|
dump: bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn printUsageAndExit(self: *const Command, success: bool) void {
|
||||||
|
const usage =
|
||||||
|
\\usage: {s} command [options] [URL]
|
||||||
|
\\
|
||||||
|
\\Command can be either 'fetch', 'serve' or 'help'
|
||||||
|
\\
|
||||||
|
\\fetch command
|
||||||
|
\\Fetches the specified URL
|
||||||
|
\\Example: {s} fetch --dump https://lightpanda.io/
|
||||||
|
\\
|
||||||
|
\\Options:
|
||||||
|
\\--dump Dumps document to stdout.
|
||||||
|
\\ Defaults to false.
|
||||||
|
\\
|
||||||
|
\\serve command
|
||||||
|
\\Starts a websocket CDP server
|
||||||
|
\\Example: {s} 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
|
||||||
|
\\
|
||||||
|
\\--timeout Inactivity timeout in seconds before disconnecting clients
|
||||||
|
\\ Defaults to 3 (seconds)
|
||||||
|
\\
|
||||||
|
\\help command
|
||||||
|
\\Displays this message
|
||||||
|
;
|
||||||
|
std.debug.print(usage, .{ self.exec_name, self.exec_name, self.exec_name });
|
||||||
|
if (success) {
|
||||||
|
return std.process.cleanExit();
|
||||||
|
}
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn parseArgs(allocator: Allocator) !Command {
|
||||||
|
var args = try std.process.argsWithAllocator(allocator);
|
||||||
|
defer args.deinit();
|
||||||
|
|
||||||
|
const exec_name = std.fs.path.basename(args.next().?);
|
||||||
|
|
||||||
|
var cmd = Command{
|
||||||
|
.mode = .{ .help = false },
|
||||||
|
.exec_name = try allocator.dupe(u8, exec_name),
|
||||||
|
};
|
||||||
|
|
||||||
|
const mode_string = args.next() orelse "";
|
||||||
|
const mode = std.meta.stringToEnum(Command.ModeType, mode_string) orelse blk: {
|
||||||
|
const inferred_mode = inferMode(mode_string) orelse return cmd;
|
||||||
|
// "command" wasn't a command but an option. We can't reset args, but
|
||||||
|
// we can create a new one. Not great, but this fallback is temporary
|
||||||
|
// as we transition to this command mode approach.
|
||||||
|
args.deinit();
|
||||||
|
|
||||||
|
args = try std.process.argsWithAllocator(allocator);
|
||||||
|
// skip the exec_name
|
||||||
|
_ = args.skip();
|
||||||
|
|
||||||
|
break :blk inferred_mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
cmd.mode = switch (mode) {
|
||||||
|
.help => Command.Mode{ .help = true },
|
||||||
|
.serve => Command.Mode{ .serve = parseServeArgs(allocator, &args) catch return cmd },
|
||||||
|
.fetch => Command.Mode{ .fetch = parseFetchArgs(allocator, &args) catch return cmd },
|
||||||
|
};
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inferMode(opt: []const u8) ?Command.ModeType {
|
||||||
|
if (opt.len == 0) {
|
||||||
|
return .serve;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, opt, "--dump")) {
|
||||||
|
return .fetch;
|
||||||
|
}
|
||||||
|
if (std.mem.startsWith(u8, opt, "--") == false) {
|
||||||
|
return .fetch;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, opt, "--host")) {
|
||||||
|
return .serve;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, opt, "--port")) {
|
||||||
|
return .serve;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, opt, "--timeout")) {
|
||||||
|
return .serve;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseServeArgs(
|
||||||
|
allocator: Allocator,
|
||||||
|
args: *std.process.ArgIterator,
|
||||||
|
) !Command.Serve {
|
||||||
|
var host: []const u8 = "127.0.0.1";
|
||||||
|
var port: u16 = 9222;
|
||||||
|
var timeout: u8 = 3;
|
||||||
|
|
||||||
|
while (args.next()) |opt| {
|
||||||
|
if (std.mem.eql(u8, "--host", opt)) {
|
||||||
|
const str = args.next() orelse {
|
||||||
|
log.err("--host argument requires an value", .{});
|
||||||
|
return error.InvalidMissingHost;
|
||||||
|
};
|
||||||
|
host = try allocator.dupe(u8, str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, "--port", opt)) {
|
||||||
|
const str = args.next() orelse {
|
||||||
|
log.err("--port argument requires an value", .{});
|
||||||
|
return error.InvalidMissingPort;
|
||||||
|
};
|
||||||
|
|
||||||
|
port = std.fmt.parseInt(u16, str, 10) catch |err| {
|
||||||
|
log.err("--port value is invalid: {}", .{err});
|
||||||
|
return error.InvalidPort;
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, "--timeout", opt)) {
|
||||||
|
const str = args.next() orelse {
|
||||||
|
log.err("--timeout argument requires an value", .{});
|
||||||
|
return error.MissingTimeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
timeout = std.fmt.parseInt(u8, str, 10) catch |err| {
|
||||||
|
log.err("--timeout value is invalid: {}", .{err});
|
||||||
|
return error.InvalidTimeout;
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.err("Unknown option to serve command: '{s}'", .{opt});
|
||||||
|
return error.UnkownOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.host = host,
|
||||||
|
.port = port,
|
||||||
|
.timeout = timeout,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseFetchArgs(
|
||||||
|
allocator: Allocator,
|
||||||
|
args: *std.process.ArgIterator,
|
||||||
|
) !Command.Fetch {
|
||||||
|
var dump: bool = false;
|
||||||
|
var url: ?[]const u8 = null;
|
||||||
|
|
||||||
|
while (args.next()) |opt| {
|
||||||
|
if (std.mem.eql(u8, "--dump", opt)) {
|
||||||
|
dump = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, opt, "--")) {
|
||||||
|
log.err("Unknown option to serve command: '{s}'", .{opt});
|
||||||
|
return error.UnkownOption;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url != null) {
|
||||||
|
log.err("Can only fetch 1 URL", .{});
|
||||||
|
return error.TooManyURLs;
|
||||||
|
}
|
||||||
|
url = try allocator.dupe(u8, opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == null) {
|
||||||
|
log.err("A URL must be provided to the fetch command", .{});
|
||||||
|
return error.MissingURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .url = url.?, .dump = dump };
|
||||||
|
}
|
||||||
|
|
||||||
var verbose: bool = builtin.mode == .Debug; // In debug mode, force verbose.
|
var verbose: bool = builtin.mode == .Debug; // In debug mode, force verbose.
|
||||||
fn logFn(
|
fn logFn(
|
||||||
comptime level: std.log.Level,
|
comptime level: std.log.Level,
|
||||||
|
|||||||
Reference in New Issue
Block a user