| ... | @@ -2241,7 +2241,45 @@ test "string copy option" { | ... | @@ -2241,7 +2241,45 @@ test "string copy option" { |
| 2241 | } | 2241 | } |
| 2242 | | 2242 | |
| 2243 | pub const StringifyOptions = struct { | 2243 | pub const StringifyOptions = struct { |
| 2244 | // TODO: indentation options? | 2244 | pub const Whitespace = struct { |
| | 2245 | /// How many indentation levels deep are we? |
| | 2246 | indent_level: usize = 0, |
| | 2247 | |
| | 2248 | pub const Indentation = union(enum) { |
| | 2249 | Space: u8, |
| | 2250 | Tab: void, |
| | 2251 | }; |
| | 2252 | |
| | 2253 | /// What character(s) should be used for indentation? |
| | 2254 | indent: Indentation = Indentation{ .Space = 4 }, |
| | 2255 | |
| | 2256 | fn outputIndent( |
| | 2257 | whitespace: @This(), |
| | 2258 | out_stream: var, |
| | 2259 | ) @TypeOf(out_stream).Error!void { |
| | 2260 | var char: u8 = undefined; |
| | 2261 | var n_chars: usize = undefined; |
| | 2262 | switch (whitespace.indent) { |
| | 2263 | .Space => |n_spaces| { |
| | 2264 | char = ' '; |
| | 2265 | n_chars = n_spaces; |
| | 2266 | }, |
| | 2267 | .Tab => { |
| | 2268 | char = '\t'; |
| | 2269 | n_chars = 1; |
| | 2270 | }, |
| | 2271 | } |
| | 2272 | n_chars *= whitespace.indent_level; |
| | 2273 | try out_stream.writeByteNTimes(char, n_chars); |
| | 2274 | } |
| | 2275 | |
| | 2276 | /// After a colon, should whitespace be inserted? |
| | 2277 | separator: bool = true, |
| | 2278 | }; |
| | 2279 | |
| | 2280 | /// Controls the whitespace emitted |
| | 2281 | whitespace: ?Whitespace = null, |
| | 2282 | |
| 2245 | // TODO: make escaping '/' in strings optional? | 2283 | // TODO: make escaping '/' in strings optional? |
| 2246 | // TODO: allow picking if []u8 is string or array? | 2284 | // TODO: allow picking if []u8 is string or array? |
| 2247 | }; | 2285 | }; |
| ... | @@ -2297,8 +2335,12 @@ pub fn stringify( | ... | @@ -2297,8 +2335,12 @@ pub fn stringify( |
| 2297 | return value.jsonStringify(options, out_stream); | 2335 | return value.jsonStringify(options, out_stream); |
| 2298 | } | 2336 | } |
| 2299 | | 2337 | |
| 2300 | try out_stream.writeAll("{"); | 2338 | try out_stream.writeByte('{'); |
| 2301 | comptime var field_output = false; | 2339 | comptime var field_output = false; |
| | 2340 | var child_options = options; |
| | 2341 | if (child_options.whitespace) |*child_whitespace| { |
| | 2342 | child_whitespace.indent_level += 1; |
| | 2343 | } |
| 2302 | inline for (S.fields) |Field, field_i| { | 2344 | inline for (S.fields) |Field, field_i| { |
| 2303 | // don't include void fields | 2345 | // don't include void fields |
| 2304 | if (Field.field_type == void) continue; | 2346 | if (Field.field_type == void) continue; |
| ... | @@ -2306,14 +2348,28 @@ pub fn stringify( | ... | @@ -2306,14 +2348,28 @@ pub fn stringify( |
| 2306 | if (!field_output) { | 2348 | if (!field_output) { |
| 2307 | field_output = true; | 2349 | field_output = true; |
| 2308 | } else { | 2350 | } else { |
| 2309 | try out_stream.writeAll(","); | 2351 | try out_stream.writeByte(','); |
| | 2352 | } |
| | 2353 | if (child_options.whitespace) |child_whitespace| { |
| | 2354 | try out_stream.writeByte('\n'); |
| | 2355 | try child_whitespace.outputIndent(out_stream); |
| 2310 | } | 2356 | } |
| 2311 | | | |
| 2312 | try stringify(Field.name, options, out_stream); | 2357 | try stringify(Field.name, options, out_stream); |
| 2313 | try out_stream.writeAll(":"); | 2358 | try out_stream.writeByte(':'); |
| 2314 | try stringify(@field(value, Field.name), options, out_stream); | 2359 | if (child_options.whitespace) |child_whitespace| { |
| | 2360 | if (child_whitespace.separator) { |
| | 2361 | try out_stream.writeByte(' '); |
| | 2362 | } |
| | 2363 | } |
| | 2364 | try stringify(@field(value, Field.name), child_options, out_stream); |
| 2315 | } | 2365 | } |
| 2316 | try out_stream.writeAll("}"); | 2366 | if (field_output) { |
| | 2367 | if (options.whitespace) |whitespace| { |
| | 2368 | try out_stream.writeByte('\n'); |
| | 2369 | try whitespace.outputIndent(out_stream); |
| | 2370 | } |
| | 2371 | } |
| | 2372 | try out_stream.writeByte('}'); |
| 2317 | return; | 2373 | return; |
| 2318 | }, | 2374 | }, |
| 2319 | .Pointer => |ptr_info| switch (ptr_info.size) { | 2375 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| ... | @@ -2330,7 +2386,7 @@ pub fn stringify( | ... | @@ -2330,7 +2386,7 @@ pub fn stringify( |
| 2330 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) | 2386 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) |
| 2331 | .Slice => { | 2387 | .Slice => { |
| 2332 | if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { | 2388 | if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { |
| 2333 | try out_stream.writeAll("\""); | 2389 | try out_stream.writeByte('\"'); |
| 2334 | var i: usize = 0; | 2390 | var i: usize = 0; |
| 2335 | while (i < value.len) : (i += 1) { | 2391 | while (i < value.len) : (i += 1) { |
| 2336 | switch (value[i]) { | 2392 | switch (value[i]) { |
| ... | @@ -2368,18 +2424,32 @@ pub fn stringify( | ... | @@ -2368,18 +2424,32 @@ pub fn stringify( |
| 2368 | }, | 2424 | }, |
| 2369 | } | 2425 | } |
| 2370 | } | 2426 | } |
| 2371 | try out_stream.writeAll("\""); | 2427 | try out_stream.writeByte('\"'); |
| 2372 | return; | 2428 | return; |
| 2373 | } | 2429 | } |
| 2374 | | 2430 | |
| 2375 | try out_stream.writeAll("["); | 2431 | try out_stream.writeByte('['); |
| | 2432 | var child_options = options; |
| | 2433 | if (child_options.whitespace) |*whitespace| { |
| | 2434 | whitespace.indent_level += 1; |
| | 2435 | } |
| 2376 | for (value) |x, i| { | 2436 | for (value) |x, i| { |
| 2377 | if (i != 0) { | 2437 | if (i != 0) { |
| 2378 | try out_stream.writeAll(","); | 2438 | try out_stream.writeByte(','); |
| 2379 | } | 2439 | } |
| 2380 | try stringify(x, options, out_stream); | 2440 | if (child_options.whitespace) |child_whitespace| { |
| | 2441 | try out_stream.writeByte('\n'); |
| | 2442 | try child_whitespace.outputIndent(out_stream); |
| | 2443 | } |
| | 2444 | try stringify(x, child_options, out_stream); |
| 2381 | } | 2445 | } |
| 2382 | try out_stream.writeAll("]"); | 2446 | if (value.len != 0) { |
| | 2447 | if (options.whitespace) |whitespace| { |
| | 2448 | try out_stream.writeByte('\n'); |
| | 2449 | try whitespace.outputIndent(out_stream); |
| | 2450 | } |
| | 2451 | } |
| | 2452 | try out_stream.writeByte(']'); |
| 2383 | return; | 2453 | return; |
| 2384 | }, | 2454 | }, |
| 2385 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), | 2455 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), |
| ... | @@ -2486,6 +2556,46 @@ test "stringify struct" { | ... | @@ -2486,6 +2556,46 @@ test "stringify struct" { |
| 2486 | }{ .foo = 42 }, StringifyOptions{}); | 2556 | }{ .foo = 42 }, StringifyOptions{}); |
| 2487 | } | 2557 | } |
| 2488 | | 2558 | |
| | 2559 | test "stringify struct with indentation" { |
| | 2560 | try teststringify( |
| | 2561 | \\{ |
| | 2562 | \\ "foo": 42, |
| | 2563 | \\ "bar": [ |
| | 2564 | \\ 1, |
| | 2565 | \\ 2, |
| | 2566 | \\ 3 |
| | 2567 | \\ ] |
| | 2568 | \\} |
| | 2569 | , |
| | 2570 | struct { |
| | 2571 | foo: u32, |
| | 2572 | bar: [3]u32, |
| | 2573 | }{ |
| | 2574 | .foo = 42, |
| | 2575 | .bar = .{ 1, 2, 3 }, |
| | 2576 | }, |
| | 2577 | StringifyOptions{ |
| | 2578 | .whitespace = .{}, |
| | 2579 | }, |
| | 2580 | ); |
| | 2581 | try teststringify( |
| | 2582 | "{\n\t\"foo\":42,\n\t\"bar\":[\n\t\t1,\n\t\t2,\n\t\t3\n\t]\n}", |
| | 2583 | struct { |
| | 2584 | foo: u32, |
| | 2585 | bar: [3]u32, |
| | 2586 | }{ |
| | 2587 | .foo = 42, |
| | 2588 | .bar = .{ 1, 2, 3 }, |
| | 2589 | }, |
| | 2590 | StringifyOptions{ |
| | 2591 | .whitespace = .{ |
| | 2592 | .indent = .Tab, |
| | 2593 | .separator = false, |
| | 2594 | }, |
| | 2595 | }, |
| | 2596 | ); |
| | 2597 | } |
| | 2598 | |
| 2489 | test "stringify struct with void field" { | 2599 | test "stringify struct with void field" { |
| 2490 | try teststringify("{\"foo\":42}", struct { | 2600 | try teststringify("{\"foo\":42}", struct { |
| 2491 | foo: u32, | 2601 | foo: u32, |
| ... | @@ -2515,7 +2625,7 @@ test "stringify struct with custom stringifier" { | ... | @@ -2515,7 +2625,7 @@ test "stringify struct with custom stringifier" { |
| 2515 | ) !void { | 2625 | ) !void { |
| 2516 | try out_stream.writeAll("[\"something special\","); | 2626 | try out_stream.writeAll("[\"something special\","); |
| 2517 | try stringify(42, options, out_stream); | 2627 | try stringify(42, options, out_stream); |
| 2518 | try out_stream.writeAll("]"); | 2628 | try out_stream.writeByte(']'); |
| 2519 | } | 2629 | } |
| 2520 | }{ .foo = 42 }, StringifyOptions{}); | 2630 | }{ .foo = 42 }, StringifyOptions{}); |
| 2521 | } | 2631 | } |