@@ -4,7 +4,7 @@ const std = @import("std");
4
4
const builtin = @import ("builtin" );
5
5
const assert = std .debug .assert ;
6
6
7
- pub const CliError = error { InvalidArg , InvalidNumberOfArgs , CliArgumentNotFound , InvalidCommand , HelpCommand , IncorrectArgumentType , RequiredArgumentNotFound };
7
+ pub const CliError = error { InvalidArg , InvalidNumberOfArgs , CliArgumentNotFound , InvalidCommand , HelpCommand , IncorrectArgumentType , RequiredArgumentNotFound , UnexpectedCliType , NonStructPassed };
8
8
9
9
const ArgMetadata = struct {
10
10
key : []const u8 ,
@@ -99,7 +99,7 @@ pub fn Snek(comptime CliInterface: type) type {
99
99
pub fn parse (self : * Self ) CliError ! CliInterface {
100
100
if (! self .isStruct ()) {
101
101
std .debug .print ("Struct must be passed to parse function. Type: {any} found" , .{@TypeOf (CliInterface )});
102
- return ;
102
+ return CliError . NonStructPassed ;
103
103
}
104
104
105
105
const interface : CliInterface = undefined ;
@@ -135,38 +135,44 @@ pub fn Snek(comptime CliInterface: type) type {
135
135
continue :unwrap_for ;
136
136
},
137
137
else = > {
138
+ // Check if there is a default value, if there is, move on (same case as an optional). Else, error case
139
+ if (field .default_value ) continue :unwrap_for ;
140
+
138
141
std .debug .print ("Required arugment {s} was not found in CLI flags. Check -help menu for required flags" , .{field .name });
139
142
return CliError .RequiredArgumentNotFound ;
140
143
},
141
144
}
142
145
}
143
146
144
- // Write data to struct field based on typ witin arg. Arg, at this point, should never be null
147
+ // Write data to struct field based on typ witin arg. Arg, at this point, should never be null since we capture that case above
145
148
const serialized_arg = arg .? ;
146
149
switch (@typeInfo (serialized_arg .typ )) {
147
150
.Bool = > {
148
- @field (& cli_reflected , serialized_arg .key ) = try self .parseBool (serialized_arg .key );
151
+ @field (& interface , serialized_arg .key ) = try self .parseBool (serialized_arg .key );
149
152
},
150
153
.Int = > {
151
- @field (& cli_reflected , serialized_arg .key ) = try self .parseBool (serialized_arg .key );
154
+ @field (& interface , serialized_arg .key ) = try self .parseNumeric (serialized_arg .key );
152
155
},
153
156
.Float = > {
154
- @field (& cli_reflected , serialized_arg .key ) = try self .parseBool (serialized_arg .key );
157
+ @field (& interface , serialized_arg .key ) = try self .parseNumeric (serialized_arg .key );
155
158
},
156
159
.Pointer = > {
157
160
// .Pointer is for strings since the underlying type is []const u8 which is a .Pointer type
158
- if (serialized_arg .typ .Pointer .size == .Slice and serialized_arg .typ .Pointer .child == u8 ) {}
161
+ if (serialized_arg .typ .Pointer .size == .Slice and serialized_arg .typ .Pointer .child == u8 ) {
162
+ // At this point, just store the string.
163
+ @field (& interface , serialized_arg .key ) = serialized_arg .key ;
164
+ }
165
+ },
166
+ .Struct = > {
167
+ return CliError .UnexpectedCliType ;
159
168
},
160
- .Struct = > {},
161
169
else = > {
162
- @panic ( "unexpected type received in CLI parser" ) ;
170
+ return CliError . UnexpectedCliType ;
163
171
},
164
172
}
165
173
}
166
174
167
- // Check that all struct fields are set and we are not missing any required fields. If we are, error.
168
- // Track which fields are missing as well so we can reflect that back to the user
169
- try self .checkAllStructArgs (cli_reflected );
175
+ return interface ;
170
176
}
171
177
172
178
// ## Helper Functions ##
@@ -186,12 +192,12 @@ pub fn Snek(comptime CliInterface: type) type {
186
192
// Remove the - without calling std.mem
187
193
const arg_stripped = arg [1.. ];
188
194
189
- // Help command is treated as an exit case to display the help menu. This is the same way that Go does it in Flags
195
+ // Help command is treated as an exit case to display the help menu. This is the same way that Go does it in the Flag package
190
196
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.1:src/flag/flag.go;l=1111
191
- if (std .mem .eql (arg_stripped , "help" ) or std .mem .eql (u8 , arg_stripped , "h" )) return CliError .HelpCommand ;
197
+ if (std .mem .eql (u8 , arg_stripped , "help" ) or std .mem .eql (u8 , arg_stripped , "h" )) return CliError .HelpCommand ;
192
198
193
199
// Split on all data *after* the initial - and curate a roster of key/value arguments seerated by the =
194
- const split_arg = std .mem .split (u8 , arg_stripped , "=" );
200
+ var split_arg = std .mem .split (u8 , arg_stripped , "=" );
195
201
const arg_key = split_arg .next () orelse "" ;
196
202
const arg_val = split_arg .next () orelse "" ;
197
203
@@ -207,10 +213,10 @@ pub fn Snek(comptime CliInterface: type) type {
207
213
}
208
214
209
215
// No struct field of this name was found. Send error instead of moving on
210
- if (! self .checkForKey (arg_key_d )) CliError .InvalidCommand ;
216
+ if (! self .checkForKey (arg_key_d )) return CliError .InvalidCommand ;
211
217
212
218
// .typ is used to eventually switch when we marshal the type of the value into the struct field
213
- try self .arg_metadata .put (arg_key_d , .{ .key = arg_key_d , .value = arg_val_d , .optional = self .isOptional (arg_key_d ), .typ = extractTypeInfoFromKey (arg_key_d ) });
219
+ try self .arg_metadata .put (arg_key_d , .{ .key = arg_key_d , .value = std . mem . trim ( u8 , arg_val_d , " " ) , .optional = self .isOptional (arg_key_d ), .typ = extractTypeInfoFromKey (arg_key_d ) });
214
220
}
215
221
}
216
222
@@ -219,12 +225,6 @@ pub fn Snek(comptime CliInterface: type) type {
219
225
return @hasField (CliInterface , key );
220
226
}
221
227
222
- /// Check all fields of the struct to ensure that all non-optional values are set and non are missing
223
- fn checkAllStructArgs (self : * Self , comptime T : type ) CliError ! void {
224
- _ = self ;
225
- _ = T ;
226
- }
227
-
228
228
fn extractTypeInfoFromKey (key : []const u8 ) std.builtin.Type {
229
229
const s_enum = std .meta .stringToEnum (CliInterface , key );
230
230
@@ -257,16 +257,34 @@ pub fn Snek(comptime CliInterface: type) type {
257
257
}
258
258
259
259
// ## Parser Functions ##
260
+
260
261
fn parseBool (self : Self , parse_value : []const u8 ) ! void {
261
262
_ = self ;
262
- _ = parse_value ;
263
+ if (std .mem .eql (u8 , parse_value , "True" ) or std .mem .eql (u8 , parse_value , "true" ) or std .mem .eql (u8 , parse_value , "On" ) or std .mem .eql (u8 , parse_value , "on" )) {
264
+ return true ;
265
+ } else if (std .mem .eql (u8 , parse_value , "False" ) or std .mem .eql (u8 , parse_value , "false" ) or std .mem .eql (u8 , parse_value , "Off" ) or std .mem .eql (u8 , parse_value , "off" )) {
266
+ return false ;
267
+ }
268
+
269
+ return error .NotBoolean ;
263
270
}
264
271
265
- fn parseNumeric (self : Self , parse_value : []const u8 ) ! void {
272
+ fn parseNumeric (self : Self , comptime T : type , parse_value : []const u8 ) ! void {
266
273
_ = self ;
267
- _ = parse_value ;
274
+ switch (@typeInfo (T )) {
275
+ .Int = > {
276
+ return std .fmt .parseInt (T , parse_value , 10 );
277
+ },
278
+ .Float = > {
279
+ return std .fmt .parseFloat (T , parse_value );
280
+ },
281
+ else = > {
282
+ return error .UnrecognizedSimpleType ;
283
+ },
284
+ }
268
285
}
269
286
287
+ // Unused - Keep around in case of more advanced string parsing
270
288
fn parseString (self : Self , parse_value : []const u8 ) ! void {
271
289
_ = self ;
272
290
_ = parse_value ;
0 commit comments