module util.csv; import streams; import handy_http_primitives : Optional; import std.conv : to; /** * A utility that allows for convenient writing of data to an output * stream in CSV format. */ struct CsvStreamWriter(S) if (isByteOutputStream!S) { S stream; private bool atStartOfRow = true; private StreamResult result; this(S outputStream) { this.stream = outputStream; } /** * Appends a value to the current row. * Params: * value = The value to append. */ ref append(T)(T value) { if (!atStartOfRow) { result = stream.writeToStream([',']); throwIfError(result); } import std.regex : replaceAll, regex; import std.string : indexOf; string s = value.to!string(); bool shouldValueBeQuoted = indexOf(s, '"') != -1 || indexOf(s, ',') != -1 || indexOf(s, '\n') != -1; s = replaceAll(value.to!string(), regex("\""), "\"\""); if (shouldValueBeQuoted) { s = "\"" ~ s ~ "\""; } result = stream.writeToStream(cast(ubyte[]) s); throwIfError(result); atStartOfRow = false; return this; } /** * Appends a new line, moving to the next row. */ ref newLine() { result = stream.writeToStream(['\n']); throwIfError(result); static if (isFlushableStream!S) { auto optionalError = stream.flushStream(); throwIfError(optionalError); } atStartOfRow = true; return this; } private void throwIfError(StreamResult result) { if (result.hasError) { throw new Exception("Stream error: " ~ result.error.message.to!string); } } private void throwIfError(Optional!StreamError result) { if (!result.isNull) { throw new Exception("Stream error: " ~ result.value.message.to!string); } } }