From f6fa3425de1efb16cc2b7967eb6420afac4fdab4 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Sat, 24 Jul 2021 16:44:11 +0200 Subject: river: use common CLI arg parsing code This makes river's main() function quite a bit cleaner. --- build.zig | 3 ++ common/args.zig | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++ river/main.zig | 79 +++++++++++++++------------------ rivertile/args.zig | 126 ----------------------------------------------------- rivertile/main.zig | 4 +- 5 files changed, 166 insertions(+), 172 deletions(-) create mode 100644 common/args.zig delete mode 100644 rivertile/args.zig diff --git a/build.zig b/build.zig index b432ca3..b792964 100644 --- a/build.zig +++ b/build.zig @@ -116,6 +116,7 @@ pub fn build(b: *zbs.Builder) !void { rivertile.step.dependOn(&scanner.step); rivertile.addPackage(scanner.getPkg()); + rivertile.addPackagePath("args", "common/args.zig"); rivertile.linkLibC(); rivertile.linkSystemLibrary("wayland-client"); @@ -208,6 +209,8 @@ fn addServerDeps(exe: *zbs.LibExeObjStep, scanner: *ScanProtocolsStep) void { exe.addPackage(wlroots); exe.linkSystemLibrary("wlroots"); + exe.addPackagePath("args", "common/args.zig"); + // TODO: remove when zig issue #131 is implemented scanner.addCSource(exe); } diff --git a/common/args.zig b/common/args.zig new file mode 100644 index 0000000..669f9f9 --- /dev/null +++ b/common/args.zig @@ -0,0 +1,126 @@ +// This file is part of river, a dynamic tiling wayland compositor. +// +// Copyright 2021 The River Developers +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const std = @import("std"); +const mem = std.mem; +const cstr = std.cstr; + +const root = @import("root"); + +pub const FlagDef = struct { + name: [*:0]const u8, + kind: enum { boolean, arg }, +}; + +pub fn Args(comptime num_positionals: comptime_int, comptime flag_defs: []const FlagDef) type { + return struct { + const Self = @This(); + + positionals: [num_positionals][*:0]const u8, + flags: [flag_defs.len]struct { + name: [*:0]const u8, + value: union { + boolean: bool, + arg: ?[*:0]const u8, + }, + }, + + pub fn parse(argv: [][*:0]const u8) Self { + var ret: Self = undefined; + + // Init all flags in the flags array to false/null + inline for (flag_defs) |flag_def, flag_idx| { + switch (flag_def.kind) { + .boolean => ret.flags[flag_idx] = .{ + .name = flag_def.name, + .value = .{ .boolean = false }, + }, + .arg => ret.flags[flag_idx] = .{ + .name = flag_def.name, + .value = .{ .arg = null }, + }, + } + } + + // Parse the argv in to the positionals and flags arrays + var arg_idx: usize = 0; + var positional_idx: usize = 0; + outer: while (arg_idx < argv.len) : (arg_idx += 1) { + var should_continue = false; + inline for (flag_defs) |flag_def, flag_idx| { + if (cstr.cmp(flag_def.name, argv[arg_idx]) == 0) { + switch (flag_def.kind) { + .boolean => ret.flags[flag_idx].value.boolean = true, + .arg => { + arg_idx += 1; + ret.flags[flag_idx].value.arg = if (arg_idx < argv.len) + argv[arg_idx] + else + root.fatal("flag '" ++ flag_def.name ++ + "' requires an argument but none was provided!", .{}); + }, + } + // TODO: this variable exists as a workaround for the fact that + // using continue :outer here crashes the stage1 compiler. + should_continue = true; + } + } + if (should_continue) continue; + + if (positional_idx == num_positionals) { + root.fatal( + "{} positional arguments expected but more were provided!", + .{num_positionals}, + ); + } + + // This check should not be needed as this code is unreachable + // if num_positionals is 0. Howevere the stage1 zig compiler does + // not seem to be smart enough to realize this. + if (num_positionals > 0) { + ret.positionals[positional_idx] = argv[arg_idx]; + } else { + unreachable; + } + positional_idx += 1; + } + + if (positional_idx < num_positionals) { + root.fatal( + "{} positional arguments expected but only {} were provided!", + .{ num_positionals, positional_idx }, + ); + } + + return ret; + } + + pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool { + for (self.flags) |flag| { + if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.boolean; + } + unreachable; // Invalid flag_name + } + + pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[*:0]const u8 { + for (self.flags) |flag| { + if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.arg; + } + unreachable; // Invalid flag_name + } + }; +} diff --git a/river/main.zig b/river/main.zig index 212cc69..56a9477 100644 --- a/river/main.zig +++ b/river/main.zig @@ -15,12 +15,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +const build_options = @import("build_options"); const std = @import("std"); const fs = std.fs; +const io = std.io; const os = std.os; const wlr = @import("wlroots"); - -const build_options = @import("build_options"); +const Args = @import("args").Args; +const FlagDef = @import("args").FlagDef; const c = @import("c.zig"); const util = @import("util.zig"); @@ -47,43 +49,35 @@ const usage: []const u8 = ; pub fn main() anyerror!void { - var startup_command: ?[:0]const u8 = null; - { - var it = std.process.args(); - // Skip our name - _ = it.nextPosix(); - while (it.nextPosix()) |arg| { - if (std.mem.eql(u8, arg, "-h")) { - const stdout = std.io.getStdOut().writer(); - try stdout.print(usage, .{}); - os.exit(0); - } else if (std.mem.eql(u8, arg, "-c")) { - if (it.nextPosix()) |command| { - // If the user used '-c' multiple times the variable - // already holds a path and needs to be freed. - if (startup_command) |cmd| util.gpa.free(cmd); - startup_command = try util.gpa.dupeZ(u8, command); - } else { - printErrorExit("Error: flag '-c' requires exactly one argument", .{}); - } - } else if (std.mem.eql(u8, arg, "-l")) { - if (it.nextPosix()) |level_str| { - const log_level = std.fmt.parseInt(u3, level_str, 10) catch - printErrorExit("Error: invalid log level '{s}'", .{level_str}); - level = @intToEnum(std.log.Level, log_level); - } else { - printErrorExit("Error: flag '-l' requires exactly one argument", .{}); - } - } else if (std.mem.eql(u8, arg, "-version")) { - try std.io.getStdOut().writeAll(build_options.version); - os.exit(0); - } else { - const stderr = std.io.getStdErr().writer(); - try stderr.print(usage, .{}); - os.exit(1); - } - } + // This line is here because of https://github.com/ziglang/zig/issues/7807 + const argv: [][*:0]const u8 = os.argv; + const args = Args(0, &[_]FlagDef{ + .{ .name = "-h", .kind = .boolean }, + .{ .name = "-version", .kind = .boolean }, + .{ .name = "-c", .kind = .arg }, + .{ .name = "-l", .kind = .arg }, + }).parse(argv[1..]); + + if (args.boolFlag("-h")) { + try io.getStdOut().writeAll(usage); + os.exit(0); + } + if (args.boolFlag("-version")) { + try io.getStdOut().writeAll(@import("build_options").version); + os.exit(0); } + if (args.argFlag("-l")) |level_str| { + const log_level = std.fmt.parseInt(u3, std.mem.span(level_str), 10) catch + fatal("Error: invalid log level '{s}'", .{level_str}); + level = @intToEnum(std.log.Level, log_level); + } + const startup_command = blk: { + if (args.argFlag("-c")) |command| { + break :blk try util.gpa.dupeZ(u8, std.mem.span(command)); + } else { + break :blk try defaultInitPath(); + } + }; wlr.log.init(switch (level) { .debug => .debug, @@ -91,8 +85,6 @@ pub fn main() anyerror!void { .warn, .err, .crit, .alert, .emerg => .err, }); - if (startup_command == null) startup_command = try defaultInitPath(); - std.log.info("initializing server", .{}); try server.init(); defer server.deinit(); @@ -125,9 +117,8 @@ pub fn main() anyerror!void { std.log.info("shutting down", .{}); } -fn printErrorExit(comptime format: []const u8, args: anytype) noreturn { - const stderr = std.io.getStdErr().writer(); - stderr.print(format ++ "\n", args) catch os.exit(1); +pub fn fatal(comptime format: []const u8, args: anytype) noreturn { + io.getStdErr().writer().print(format ++ "\n", args) catch {}; os.exit(1); } @@ -160,7 +151,7 @@ pub fn log( if (@enumToInt(message_level) <= @enumToInt(level)) { // Don't store/log messages in release small mode to save space if (std.builtin.mode != .ReleaseSmall) { - const stderr = std.io.getStdErr().writer(); + const stderr = io.getStdErr().writer(); stderr.print(@tagName(message_level) ++ ": (" ++ @tagName(scope) ++ ") " ++ format ++ "\n", args) catch return; } diff --git a/rivertile/args.zig b/rivertile/args.zig deleted file mode 100644 index 31b57bc..0000000 --- a/rivertile/args.zig +++ /dev/null @@ -1,126 +0,0 @@ -// This file is part of river, a dynamic tiling wayland compositor. -// -// Copyright 2021 The River Developers -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -const std = @import("std"); -const mem = std.mem; -const cstr = std.cstr; - -const root = @import("root"); - -pub const FlagDef = struct { - name: [*:0]const u8, - kind: enum { boolean, arg }, -}; - -pub fn Args(comptime num_positionals: comptime_int, comptime flag_defs: []const FlagDef) type { - return struct { - const Self = @This(); - - positionals: [num_positionals][*:0]const u8, - flags: [flag_defs.len]struct { - name: [*:0]const u8, - value: union { - boolean: bool, - arg: ?[*:0]const u8, - }, - }, - - pub fn parse(argv: [][*:0]const u8) Self { - var ret: Self = undefined; - - // Init all flags in the flags array to false/null - inline for (flag_defs) |flag_def, flag_idx| { - switch (flag_def.kind) { - .boolean => ret.flags[flag_idx] = .{ - .name = flag_def.name, - .value = .{ .boolean = false }, - }, - .arg => ret.flags[flag_idx] = .{ - .name = flag_def.name, - .value = .{ .arg = null }, - }, - } - } - - // Parse the argv in to the positionals and flags arrays - var arg_idx: usize = 0; - var positional_idx: usize = 0; - outer: while (arg_idx < argv.len) : (arg_idx += 1) { - var should_continue = false; - inline for (flag_defs) |flag_def, flag_idx| { - if (cstr.cmp(flag_def.name, argv[arg_idx]) == 0) { - switch (flag_def.kind) { - .boolean => ret.flags[flag_idx].value.boolean = true, - .arg => { - arg_idx += 1; - ret.flags[flag_idx].value.arg = if (arg_idx < argv.len) - argv[arg_idx] - else - root.fatal("flag '" ++ flag_def.name ++ - "' requires an argument but none was provided!", .{}); - }, - } - // TODO: this variable exists as a workaround for the fact that - // using continue :outer here crashes the stage1 compiler. - should_continue = true; - } - } - if (should_continue) continue; - - if (positional_idx == num_positionals) { - root.fatal( - "{} positional arguments expected but more were provided!", - .{num_positionals}, - ); - } - - // This check should not be needed as this code is unreachable - // if num_positionals is 0. Howevere the stage1 zig compiler does - // not seem to be smart enough to realize this. - if (num_positionals > 0) { - ret.positionals[positional_idx] = argv[arg_idx]; - } else { - unreachable; - } - positional_idx += 1; - } - - if (positional_idx < num_positionals) { - root.fatal( - "{} positional arguments expected but only {} were provided!", - .{ num_positionals, positional_idx }, - ); - } - - return ret; - } - - pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool { - for (self.flags) |flag| { - if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.boolean; - } - unreachable; - } - - pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[*:0]const u8 { - for (self.flags) |flag| { - if (cstr.cmp(flag.name, flag_name) == 0) return flag.value.arg; - } - unreachable; - } - }; -} diff --git a/rivertile/main.zig b/rivertile/main.zig index ff57475..cc977b4 100644 --- a/rivertile/main.zig +++ b/rivertile/main.zig @@ -45,8 +45,8 @@ const wayland = @import("wayland"); const wl = wayland.client.wl; const river = wayland.client.river; -const Args = @import("args.zig").Args; -const FlagDef = @import("args.zig").FlagDef; +const Args = @import("args").Args; +const FlagDef = @import("args").FlagDef; const usage = \\Usage: rivertile [options] -- cgit v1.2.3