| ... | ... | @@ -2241,7 +2241,45 @@ test "string copy option" { |
| 2241 | 2241 | } |
| 2242 | 2242 | |
| 2243 | 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 | 2283 | // TODO: make escaping '/' in strings optional? |
| 2246 | 2284 | // TODO: allow picking if []u8 is string or array? |
| 2247 | 2285 | }; |
| ... | ... | @@ -2297,8 +2335,12 @@ pub fn stringify( |
| 2297 | 2335 | return value.jsonStringify(options, out_stream); |
| 2298 | 2336 | } |
| 2299 | 2337 | |
| 2300 | | try out_stream.writeAll("{"); |
| 2338 | try out_stream.writeByte('{'); |
| 2301 | 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 | 2344 | inline for (S.fields) |Field, field_i| { |
| 2303 | 2345 | // don't include void fields |
| 2304 | 2346 | if (Field.field_type == void) continue; |
| ... | ... | @@ -2306,14 +2348,28 @@ pub fn stringify( |
| 2306 | 2348 | if (!field_output) { |
| 2307 | 2349 | field_output = true; |
| 2308 | 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 | 2357 | try stringify(Field.name, options, out_stream); |
| 2313 | | try out_stream.writeAll(":"); |
| 2314 | | try stringify(@field(value, Field.name), options, out_stream); |
| 2358 | try out_stream.writeByte(':'); |
| 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 | 2373 | return; |
| 2318 | 2374 | }, |
| 2319 | 2375 | .Pointer => |ptr_info| switch (ptr_info.size) { |
| ... | ... | @@ -2330,7 +2386,7 @@ pub fn stringify( |
| 2330 | 2386 | // TODO: .Many when there is a sentinel (waiting for https://github.com/ziglang/zig/pull/3972) |
| 2331 | 2387 | .Slice => { |
| 2332 | 2388 | if (ptr_info.child == u8 and std.unicode.utf8ValidateSlice(value)) { |
| 2333 | | try out_stream.writeAll("\""); |
| 2389 | try out_stream.writeByte('\"'); |
| 2334 | 2390 | var i: usize = 0; |
| 2335 | 2391 | while (i < value.len) : (i += 1) { |
| 2336 | 2392 | switch (value[i]) { |
| ... | ... | @@ -2368,18 +2424,32 @@ pub fn stringify( |
| 2368 | 2424 | }, |
| 2369 | 2425 | } |
| 2370 | 2426 | } |
| 2371 | | try out_stream.writeAll("\""); |
| 2427 | try out_stream.writeByte('\"'); |
| 2372 | 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 | 2436 | for (value) |x, i| { |
| 2377 | 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 | 2453 | return; |
| 2384 | 2454 | }, |
| 2385 | 2455 | else => @compileError("Unable to stringify type '" ++ @typeName(T) ++ "'"), |
| ... | ... | @@ -2486,6 +2556,46 @@ test "stringify struct" { |
| 2486 | 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 | 2599 | test "stringify struct with void field" { |
| 2490 | 2600 | try teststringify("{\"foo\":42}", struct { |
| 2491 | 2601 | foo: u32, |
| ... | ... | @@ -2515,7 +2625,7 @@ test "stringify struct with custom stringifier" { |
| 2515 | 2625 | ) !void { |
| 2516 | 2626 | try out_stream.writeAll("[\"something special\","); |
| 2517 | 2627 | try stringify(42, options, out_stream); |
| 2518 | | try out_stream.writeAll("]"); |
| 2628 | try out_stream.writeByte(']'); |
| 2519 | 2629 | } |
| 2520 | 2630 | }{ .foo = 42 }, StringifyOptions{}); |
| 2521 | 2631 | } |