|
1 | 1 | const builtin = @import("builtin");
|
2 | 2 | const Os = builtin.Os;
|
| 3 | +const is_windows = builtin.os == Os.windows; |
| 4 | + |
3 | 5 | pub const windows = @import("windows/index.zig");
|
4 | 6 | pub const darwin = @import("darwin.zig");
|
5 | 7 | pub const linux = @import("linux.zig");
|
@@ -37,6 +39,7 @@ const cstr = @import("../cstr.zig");
|
37 | 39 | const io = @import("../io.zig");
|
38 | 40 | const base64 = @import("../base64.zig");
|
39 | 41 | const ArrayList = @import("../array_list.zig").ArrayList;
|
| 42 | +const Buffer = @import("../buffer.zig").Buffer; |
40 | 43 |
|
41 | 44 | error Unexpected;
|
42 | 45 | error SystemResources;
|
@@ -513,18 +516,6 @@ pub fn getEnv(key: []const u8) -> ?[]const u8 {
|
513 | 516 | return null;
|
514 | 517 | }
|
515 | 518 |
|
516 |
| -pub const args = struct { |
517 |
| - pub var raw: []&u8 = undefined; |
518 |
| - |
519 |
| - pub fn count() -> usize { |
520 |
| - return raw.len; |
521 |
| - } |
522 |
| - pub fn at(i: usize) -> []const u8 { |
523 |
| - const s = raw[i]; |
524 |
| - return cstr.toSlice(s); |
525 |
| - } |
526 |
| -}; |
527 |
| - |
528 | 519 | /// Caller must free the returned memory.
|
529 | 520 | pub fn getCwd(allocator: &Allocator) -> %[]u8 {
|
530 | 521 | switch (builtin.os) {
|
@@ -1144,6 +1135,233 @@ pub fn posix_setregid(rgid: u32, egid: u32) -> %void {
|
1144 | 1135 | };
|
1145 | 1136 | }
|
1146 | 1137 |
|
| 1138 | +pub const ArgIteratorPosix = struct { |
| 1139 | + index: usize, |
| 1140 | + count: usize, |
| 1141 | + |
| 1142 | + pub fn init() -> ArgIteratorPosix { |
| 1143 | + return ArgIteratorPosix { |
| 1144 | + .index = 0, |
| 1145 | + .count = raw.len, |
| 1146 | + }; |
| 1147 | + } |
| 1148 | + |
| 1149 | + pub fn next(self: &ArgIteratorPosix) -> ?[]const u8 { |
| 1150 | + if (self.index == self.count) |
| 1151 | + return null; |
| 1152 | + |
| 1153 | + const s = raw[self.index]; |
| 1154 | + self.index += 1; |
| 1155 | + return cstr.toSlice(s); |
| 1156 | + } |
| 1157 | + |
| 1158 | + pub fn skip(self: &ArgIteratorPosix) -> bool { |
| 1159 | + if (self.index == self.count) |
| 1160 | + return false; |
| 1161 | + |
| 1162 | + self.index += 1; |
| 1163 | + return true; |
| 1164 | + } |
| 1165 | + |
| 1166 | + /// This is marked as public but actually it's only meant to be used |
| 1167 | + /// internally by zig's startup code. |
| 1168 | + pub var raw: []&u8 = undefined; |
| 1169 | +}; |
| 1170 | + |
| 1171 | +pub const ArgIteratorWindows = struct { |
| 1172 | + index: usize, |
| 1173 | + cmd_line: &const u8, |
| 1174 | + backslash_count: usize, |
| 1175 | + in_quote: bool, |
| 1176 | + quote_count: usize, |
| 1177 | + seen_quote_count: usize, |
| 1178 | + |
| 1179 | + pub fn init() -> ArgIteratorWindows { |
| 1180 | + return initWithCmdLine(windows.GetCommandLineA()); |
| 1181 | + } |
| 1182 | + |
| 1183 | + pub fn initWithCmdLine(cmd_line: &const u8) -> ArgIteratorWindows { |
| 1184 | + return ArgIteratorWindows { |
| 1185 | + .index = 0, |
| 1186 | + .cmd_line = cmd_line, |
| 1187 | + .backslash_count = 0, |
| 1188 | + .in_quote = false, |
| 1189 | + .quote_count = countQuotes(cmd_line), |
| 1190 | + .seen_quote_count = 0, |
| 1191 | + }; |
| 1192 | + } |
| 1193 | + |
| 1194 | + /// You must free the returned memory when done. |
| 1195 | + pub fn next(self: &ArgIteratorWindows, allocator: &Allocator) -> ?%[]u8 { |
| 1196 | + // march forward over whitespace |
| 1197 | + while (true) : (self.index += 1) { |
| 1198 | + const byte = self.cmd_line[self.index]; |
| 1199 | + switch (byte) { |
| 1200 | + 0 => return null, |
| 1201 | + ' ', '\t' => continue, |
| 1202 | + else => break, |
| 1203 | + } |
| 1204 | + } |
| 1205 | + |
| 1206 | + return self.internalNext(allocator); |
| 1207 | + } |
| 1208 | + |
| 1209 | + pub fn skip(self: &ArgIteratorWindows) -> bool { |
| 1210 | + // march forward over whitespace |
| 1211 | + while (true) : (self.index += 1) { |
| 1212 | + const byte = self.cmd_line[self.index]; |
| 1213 | + switch (byte) { |
| 1214 | + 0 => return false, |
| 1215 | + ' ', '\t' => continue, |
| 1216 | + else => break, |
| 1217 | + } |
| 1218 | + } |
| 1219 | + |
| 1220 | + while (true) : (self.index += 1) { |
| 1221 | + const byte = self.cmd_line[self.index]; |
| 1222 | + switch (byte) { |
| 1223 | + 0 => return true, |
| 1224 | + '"' => { |
| 1225 | + const quote_is_real = self.backslash_count % 2 == 0; |
| 1226 | + if (quote_is_real) { |
| 1227 | + self.seen_quote_count += 1; |
| 1228 | + } |
| 1229 | + }, |
| 1230 | + '\\' => { |
| 1231 | + self.backslash_count += 1; |
| 1232 | + }, |
| 1233 | + ' ', '\t' => { |
| 1234 | + if (self.seen_quote_count % 2 == 0 or self.seen_quote_count == self.quote_count) { |
| 1235 | + return true; |
| 1236 | + } |
| 1237 | + }, |
| 1238 | + else => continue, |
| 1239 | + } |
| 1240 | + } |
| 1241 | + } |
| 1242 | + |
| 1243 | + fn internalNext(self: &ArgIteratorWindows, allocator: &Allocator) -> %[]u8 { |
| 1244 | + var buf = %return Buffer.initSize(allocator, 0); |
| 1245 | + defer buf.deinit(); |
| 1246 | + |
| 1247 | + while (true) : (self.index += 1) { |
| 1248 | + const byte = self.cmd_line[self.index]; |
| 1249 | + switch (byte) { |
| 1250 | + 0 => return buf.toOwnedSlice(), |
| 1251 | + '"' => { |
| 1252 | + const quote_is_real = self.backslash_count % 2 == 0; |
| 1253 | + %return self.emitBackslashes(&buf, self.backslash_count / 2); |
| 1254 | + |
| 1255 | + if (quote_is_real) { |
| 1256 | + self.seen_quote_count += 1; |
| 1257 | + if (self.seen_quote_count == self.quote_count and self.seen_quote_count % 2 == 1) { |
| 1258 | + %return buf.appendByte('"'); |
| 1259 | + } |
| 1260 | + } else { |
| 1261 | + %return buf.appendByte('"'); |
| 1262 | + } |
| 1263 | + }, |
| 1264 | + '\\' => { |
| 1265 | + self.backslash_count += 1; |
| 1266 | + }, |
| 1267 | + ' ', '\t' => { |
| 1268 | + %return self.emitBackslashes(&buf, self.backslash_count); |
| 1269 | + if (self.seen_quote_count % 2 == 1 and self.seen_quote_count != self.quote_count) { |
| 1270 | + %return buf.appendByte(byte); |
| 1271 | + } else { |
| 1272 | + return buf.toOwnedSlice(); |
| 1273 | + } |
| 1274 | + }, |
| 1275 | + else => { |
| 1276 | + %return self.emitBackslashes(&buf, self.backslash_count); |
| 1277 | + %return buf.appendByte(byte); |
| 1278 | + }, |
| 1279 | + } |
| 1280 | + } |
| 1281 | + } |
| 1282 | + |
| 1283 | + fn emitBackslashes(self: &ArgIteratorWindows, buf: &Buffer, emit_count: usize) -> %void { |
| 1284 | + self.backslash_count = 0; |
| 1285 | + var i: usize = 0; |
| 1286 | + while (i < emit_count) : (i += 1) { |
| 1287 | + %return buf.appendByte('\\'); |
| 1288 | + } |
| 1289 | + } |
| 1290 | + |
| 1291 | + fn countQuotes(cmd_line: &const u8) -> usize { |
| 1292 | + var result: usize = 0; |
| 1293 | + var backslash_count: usize = 0; |
| 1294 | + var index: usize = 0; |
| 1295 | + while (true) : (index += 1) { |
| 1296 | + const byte = cmd_line[index]; |
| 1297 | + switch (byte) { |
| 1298 | + 0 => return result, |
| 1299 | + '\\' => backslash_count += 1, |
| 1300 | + '"' => { |
| 1301 | + result += 1 - (backslash_count % 2); |
| 1302 | + backslash_count = 0; |
| 1303 | + }, |
| 1304 | + else => { |
| 1305 | + backslash_count = 0; |
| 1306 | + }, |
| 1307 | + } |
| 1308 | + } |
| 1309 | + } |
| 1310 | + |
| 1311 | +}; |
| 1312 | + |
| 1313 | +pub const ArgIterator = struct { |
| 1314 | + inner: if (builtin.os == Os.windows) ArgIteratorWindows else ArgIteratorPosix, |
| 1315 | + |
| 1316 | + pub fn init() -> ArgIterator { |
| 1317 | + return ArgIterator { |
| 1318 | + .inner = if (builtin.os == Os.windows) ArgIteratorWindows.init() else ArgIteratorPosix.init(), |
| 1319 | + }; |
| 1320 | + } |
| 1321 | + |
| 1322 | + /// You must free the returned memory when done. |
| 1323 | + pub fn next(self: &ArgIterator, allocator: &Allocator) -> ?%[]u8 { |
| 1324 | + if (builtin.os == Os.windows) { |
| 1325 | + return self.inner.next(allocator); |
| 1326 | + } else { |
| 1327 | + return mem.dupe(allocator, u8, self.inner.next() ?? return null); |
| 1328 | + } |
| 1329 | + } |
| 1330 | + |
| 1331 | + /// If you only are targeting posix you can call this and not need an allocator. |
| 1332 | + pub fn nextPosix(self: &ArgIterator) -> ?[]const u8 { |
| 1333 | + return self.inner.next(); |
| 1334 | + } |
| 1335 | + |
| 1336 | + /// Parse past 1 argument without capturing it. |
| 1337 | + /// Returns `true` if skipped an arg, `false` if we are at the end. |
| 1338 | + pub fn skip(self: &ArgIterator) -> bool { |
| 1339 | + return self.inner.skip(); |
| 1340 | + } |
| 1341 | +}; |
| 1342 | + |
| 1343 | +pub fn args() -> ArgIterator { |
| 1344 | + return ArgIterator.init(); |
| 1345 | +} |
| 1346 | + |
| 1347 | +test "windows arg parsing" { |
| 1348 | + testWindowsCmdLine(c"a b\tc d", [][]const u8{"a", "b", "c", "d"}); |
| 1349 | + testWindowsCmdLine(c"\"abc\" d e", [][]const u8{"abc", "d", "e"}); |
| 1350 | + testWindowsCmdLine(c"a\\\\\\b d\"e f\"g h", [][]const u8{"a\\\\\\b", "de fg", "h"}); |
| 1351 | + testWindowsCmdLine(c"a\\\\\\\"b c d", [][]const u8{"a\\\"b", "c", "d"}); |
| 1352 | + testWindowsCmdLine(c"a\\\\\\\\\"b c\" d e", [][]const u8{"a\\\\b c", "d", "e"}); |
| 1353 | + testWindowsCmdLine(c"a b\tc \"d f", [][]const u8{"a", "b", "c", "\"d", "f"}); |
| 1354 | +} |
| 1355 | + |
| 1356 | +fn testWindowsCmdLine(input_cmd_line: &const u8, expected_args: []const []const u8) { |
| 1357 | + var it = ArgIteratorWindows.initWithCmdLine(input_cmd_line); |
| 1358 | + for (expected_args) |expected_arg| { |
| 1359 | + const arg = %%??it.next(&debug.global_allocator); |
| 1360 | + assert(mem.eql(u8, arg, expected_arg)); |
| 1361 | + } |
| 1362 | + assert(it.next(&debug.global_allocator) == null); |
| 1363 | +} |
| 1364 | + |
1147 | 1365 | test "std.os" {
|
1148 | 1366 | _ = @import("child_process.zig");
|
1149 | 1367 | _ = @import("darwin_errno.zig");
|
|
0 commit comments