flags: rewrite to allow [:0]const u8 arguments
This also cleans up the code by using @Type(), eliminating the need for the argFlag() and boolFlag() functions. Allowing [:0]const u8 arguments makes this parser useful for river-control commands as well.
This commit is contained in:
		
							
								
								
									
										165
									
								
								common/flags.zig
									
									
									
									
									
								
							
							
						
						
									
										165
									
								
								common/flags.zig
									
									
									
									
									
								
							| @ -1,105 +1,100 @@ | ||||
| // This file is part of river, a dynamic tiling wayland compositor. | ||||
| // Zero allocation argument parsing for unix-like systems. | ||||
| // Released under the Zero Clause BSD (0BSD) license: | ||||
| // | ||||
| // Copyright 2021 The River Developers | ||||
| // Copyright 2023 Isaac Freund | ||||
| // | ||||
| // 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, version 3. | ||||
| // Permission to use, copy, modify, and/or distribute this software for any | ||||
| // purpose with or without fee is hereby granted. | ||||
| // | ||||
| // 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 <https://www.gnu.org/licenses/>. | ||||
| // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||
| // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||
| // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||||
| // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||
| // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||||
| // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||||
| // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
|  | ||||
| const std = @import("std"); | ||||
| const cstr = std.cstr; | ||||
| const mem = std.mem; | ||||
|  | ||||
| pub const Flag = struct { | ||||
|     name: [*:0]const u8, | ||||
|     name: []const u8, | ||||
|     kind: enum { boolean, arg }, | ||||
| }; | ||||
|  | ||||
| pub fn ParseResult(comptime flags: []const Flag) type { | ||||
| pub fn parser(comptime Arg: type, comptime flags: []const Flag) type { | ||||
|     switch (Arg) { | ||||
|         // TODO consider allowing []const u8 | ||||
|         [:0]const u8, [*:0]const u8 => {}, // ok | ||||
|         else => @compileError("invalid argument type: " ++ @typeName(Arg)), | ||||
|     } | ||||
|     return struct { | ||||
|         const Self = @This(); | ||||
|         pub const Result = struct { | ||||
|             /// Remaining args after the recognized flags | ||||
|             args: []const Arg, | ||||
|             /// Data obtained from parsed flags | ||||
|             flags: Flags, | ||||
|  | ||||
|         const FlagData = struct { | ||||
|             name: [*:0]const u8, | ||||
|             value: union { | ||||
|                 boolean: bool, | ||||
|                 arg: ?[*:0]const u8, | ||||
|             }, | ||||
|             pub const Flags = flags_type: { | ||||
|                 var fields: []const std.builtin.TypeInfo.StructField = &.{}; | ||||
|                 inline for (flags) |flag| { | ||||
|                     const field: std.builtin.TypeInfo.StructField = switch (flag.kind) { | ||||
|                         .boolean => .{ | ||||
|                             .name = flag.name, | ||||
|                             .field_type = bool, | ||||
|                             .default_value = false, | ||||
|                             .is_comptime = false, | ||||
|                             .alignment = @alignOf(bool), | ||||
|                         }, | ||||
|                         .arg => .{ | ||||
|                             .name = flag.name, | ||||
|                             .field_type = ?[:0]const u8, | ||||
|                             .default_value = @as(??[:0]const u8, @as(?[:0]const u8, null)), | ||||
|                             .is_comptime = false, | ||||
|                             .alignment = @alignOf(?[:0]const u8), | ||||
|                         }, | ||||
|                     }; | ||||
|                     fields = fields ++ [_]std.builtin.TypeInfo.StructField{field}; | ||||
|                 } | ||||
|                 break :flags_type @Type(.{ .Struct = .{ | ||||
|                     .layout = .Auto, | ||||
|                     .fields = fields, | ||||
|                     .decls = &.{}, | ||||
|                     .is_tuple = false, | ||||
|                 } }); | ||||
|             }; | ||||
|         }; | ||||
|  | ||||
|         /// Remaining args after the recognized flags | ||||
|         args: [][*:0]const u8, | ||||
|         /// Data obtained from parsed flags | ||||
|         flag_data: [flags.len]FlagData = blk: { | ||||
|             // Init all flags to false/null | ||||
|             var flag_data: [flags.len]FlagData = undefined; | ||||
|             inline for (flags) |flag, i| { | ||||
|                 flag_data[i] = switch (flag.kind) { | ||||
|                     .boolean => .{ | ||||
|                         .name = flag.name, | ||||
|                         .value = .{ .boolean = false }, | ||||
|                     }, | ||||
|                     .arg => .{ | ||||
|                         .name = flag.name, | ||||
|                         .value = .{ .arg = null }, | ||||
|                     }, | ||||
|                 }; | ||||
|             } | ||||
|             break :blk flag_data; | ||||
|         }, | ||||
|         pub fn parse(args: []const Arg) !Result { | ||||
|             var result_flags: Result.Flags = .{}; | ||||
|  | ||||
|         pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool { | ||||
|             for (self.flag_data) |flag_data| { | ||||
|                 if (cstr.cmp(flag_data.name, flag_name) == 0) return flag_data.value.boolean; | ||||
|             } | ||||
|             unreachable; // Invalid flag_name | ||||
|         } | ||||
|  | ||||
|         pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[:0]const u8 { | ||||
|             for (self.flag_data) |flag_data| { | ||||
|                 if (cstr.cmp(flag_data.name, flag_name) == 0) { | ||||
|                     return std.mem.span(flag_data.value.arg); | ||||
|             var i: usize = 0; | ||||
|             while (i < args.len) : (i += 1) { | ||||
|                 var parsed_flag = false; | ||||
|                 inline for (flags) |flag| { | ||||
|                     if (mem.eql(u8, flag.name, mem.span(args[i]))) { | ||||
|                         switch (flag.kind) { | ||||
|                             .boolean => @field(result_flags, flag.name) = true, | ||||
|                             .arg => { | ||||
|                                 i += 1; | ||||
|                                 if (i == args.len) { | ||||
|                                     std.log.err("option '" ++ flag.name ++ | ||||
|                                         "' requires an argument but none was provided!", .{}); | ||||
|                                     return error.MissingFlagArgument; | ||||
|                                 } | ||||
|                                 @field(result_flags, flag.name) = mem.span(args[i]); | ||||
|                             }, | ||||
|                         } | ||||
|                         parsed_flag = true; | ||||
|                     } | ||||
|                 } | ||||
|                 if (!parsed_flag) break; | ||||
|             } | ||||
|             unreachable; // Invalid flag_name | ||||
|  | ||||
|             return Result{ | ||||
|                 .args = args[i..], | ||||
|                 .flags = result_flags, | ||||
|             }; | ||||
|         } | ||||
|     }; | ||||
| } | ||||
|  | ||||
| pub fn parse(args: [][*:0]const u8, comptime flags: []const Flag) !ParseResult(flags) { | ||||
|     var ret: ParseResult(flags) = .{ .args = undefined }; | ||||
|  | ||||
|     var arg_idx: usize = 0; | ||||
|     while (arg_idx < args.len) : (arg_idx += 1) { | ||||
|         var parsed_flag = false; | ||||
|         inline for (flags) |flag, flag_idx| { | ||||
|             if (cstr.cmp(flag.name, args[arg_idx]) == 0) { | ||||
|                 switch (flag.kind) { | ||||
|                     .boolean => ret.flag_data[flag_idx].value.boolean = true, | ||||
|                     .arg => { | ||||
|                         arg_idx += 1; | ||||
|                         if (arg_idx == args.len) { | ||||
|                             std.log.err("option '" ++ flag.name ++ | ||||
|                                 "' requires an argument but none was provided!", .{}); | ||||
|                             return error.MissingFlagArgument; | ||||
|                         } | ||||
|                         ret.flag_data[flag_idx].value.arg = args[arg_idx]; | ||||
|                     }, | ||||
|                 } | ||||
|                 parsed_flag = true; | ||||
|             } | ||||
|         } | ||||
|         if (!parsed_flag) break; | ||||
|     } | ||||
|  | ||||
|     ret.args = args[arg_idx..]; | ||||
|  | ||||
|     return ret; | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user