From ec193ca63999451abea20592a60804f85781cc57 Mon Sep 17 00:00:00 2001
From: Daniil Medvedev <medvdanil@gmail.com>
Date: Fri, 17 Jul 2015 11:48:33 +0300
Subject: [PATCH] csv output(with tests)

---
 src/lib/csv/csv.c    | 23 +++++++++++++-
 src/lib/csv/csv.h    | 15 +++++-----
 src/lua/csv.lua      | 71 +++++++++++++++++++++++++++++++-------------
 test/unit/csv.c      | 18 +++++++++++
 test/unit/csv.result |  3 ++
 5 files changed, 102 insertions(+), 28 deletions(-)

diff --git a/src/lib/csv/csv.c b/src/lib/csv/csv.c
index d4856b97f0..25a8050eb4 100644
--- a/src/lib/csv/csv.c
+++ b/src/lib/csv/csv.c
@@ -242,7 +242,28 @@ csv_next(struct csv_iterator *it) {
 }
 
 void
-csv_feed(struct csv_iterator *it, const char *str) {
+csv_feed(struct csv_iterator *it, const char *str)
+{
 	it->buf_begin = str;
 	it->buf_end = str + strlen(str);
 }
+
+int
+csv_escape_field(struct csv *csv, const char *field, char *dst)
+{
+	char *p = dst;
+	int inquotes = 0;
+	if(strchr(field, csv->csv_delim) || strchr(field, '\n') || strchr(field, '\r')) {
+		inquotes = 1;
+		*p++ = csv->csv_quote;
+	}
+	while(*field) {
+		if(*field == csv->csv_quote)
+			*p++ = csv->csv_quote;
+		*p++ = *field++;
+	}
+	if(inquotes)
+		*p++ = csv->csv_quote;
+	*p = 0;
+	return p - dst;
+}
diff --git a/src/lib/csv/csv.h b/src/lib/csv/csv.h
index bfb699e645..fb2e9376a6 100644
--- a/src/lib/csv/csv.h
+++ b/src/lib/csv/csv.h
@@ -81,13 +81,6 @@ csv_parse_chunk(struct csv *csv, const char *s, const char *end);
 void
 csv_finish_parsing(struct csv *csv);
 
-/**
- * Format variadic arguments and print them into
- * a stream, adding CSV markup.
- */
-int
-csv_snprintf(struct csv *csv, FILE *f, const char *format, ...);
-
 /**
  * if quote not closed returns 0
  */
@@ -122,6 +115,14 @@ csv_next(struct csv_iterator *);
 void
 csv_feed(struct csv_iterator *, const char *);
 
+/**
+ * @brief csv_escape_field adds pair quote and
+ * if there is comma in field, adds surrounding quotes
+ */
+
+int
+csv_escape_field(struct csv *csv, const char *field, char *dst);
+
 #define CSV_ITERATOR_GET_FIELD(it) it->field
 #define CSV_ITERATOR_GET_FLEN(it)  it->field_len
 
diff --git a/src/lua/csv.lua b/src/lua/csv.lua
index 13f437ce84..92ac72a334 100644
--- a/src/lua/csv.lua
+++ b/src/lua/csv.lua
@@ -41,20 +41,21 @@ ffi.cdef[[
     void csv_iter_create(struct csv_iterator *it, struct csv *csv);
     int csv_next(struct csv_iterator *);
     void csv_feed(struct csv_iterator *, const char *);
+    int csv_escape_field(struct csv *, const char *, char *);
     enum {
-	    CSV_IT_OK,
-	    CSV_IT_EOL,
-	    CSV_IT_NEEDMORE,
-	    CSV_IT_EOF,
-	    CSV_IT_ERROR
+        CSV_IT_OK,
+        CSV_IT_EOL,
+        CSV_IT_NEEDMORE,
+        CSV_IT_EOF,
+        CSV_IT_ERROR
     };
 ]]
 
 csv = {
-loadcsv = function(readable, csv_chunk_size)
+load = function(readable, csv_chunk_size)
     csv_chunk_size = csv_chunk_size or 4096
     if type(readable.read) ~= "function" then
-       error("Usage: loadcsv(object with read method)")
+       error("Usage: load(object with read method)")
     end
     local log = require('log')
 
@@ -66,23 +67,53 @@ loadcsv = function(readable, csv_chunk_size)
     local tup = {}
     local result = {}
     while st ~= ffi.C.CSV_IT_EOF do
-	if st == ffi.C.CSV_IT_NEEDMORE then
-	    ffi.C.csv_feed(it, readable:read(csv_chunk_size))
-	elseif st == ffi.C.CSV_IT_EOL then
-	    table.insert(result, tup)
-	    tup = {}
-	elseif st == ffi.C.CSV_IT_OK then
-	    table.insert(tup, ffi.string(it[0].field, it[0].field_len))
-	elseif st == ffi.C.CSV_IT_ERROR then
-	    log.warn("CSV file has errors")
-	elseif st == ffi.C.CSV_IT_EOF then
-	    break
-	end
-	st = ffi.C.csv_next(it)
+        if st == ffi.C.CSV_IT_NEEDMORE then
+            ffi.C.csv_feed(it, readable:read(csv_chunk_size))
+        elseif st == ffi.C.CSV_IT_EOL then
+            table.insert(result, tup)
+            tup = {}
+        elseif st == ffi.C.CSV_IT_OK then
+            table.insert(tup, ffi.string(it[0].field, it[0].field_len))
+        elseif st == ffi.C.CSV_IT_ERROR then
+            log.warn("CSV file has errors")
+        elseif st == ffi.C.CSV_IT_EOF then
+            break
+        end
+        st = ffi.C.csv_next(it)
     end
 
     return result
 end
+,
+dump = function(writable, t)
+    if type(writable.write) ~= "function" or type(t) ~= "table" then
+       error("Usage: dump(writable, table)")
+    end
+    local csv = ffi.new('csv_t[1]')
+    ffi.C.csv_create(csv)
+    local bufsz = 256
+    --local buf = ffi.new('char[?]', bufsz)
+    local buf = csv[0].csv_realloc(ffi.cast(ffi.typeof('void *'), 0), bufsz)
+    for k, line in pairs(t) do
+        first = true
+        for k2, field in pairs(line) do
+            strf = tostring(field)
+            if (strf:len() + 1) * 2 > bufsz then
+                bufsz = (strf:len() + 1) * 2
+                buf = csv[0].csv_realloc(buf, bufsz)
+            end
+            local len = ffi.C.csv_escape_field(csv, strf, buf)
+            if first then
+                first = false
+            else
+                writable:write(',')
+            end              
+            writable:write(ffi.string(buf, len))
+        end
+        writable:write('\n')  
+    end
+    csv[0].csv_realloc(buf, 0)
+end
 
 }
 return csv
diff --git a/test/unit/csv.c b/test/unit/csv.c
index 2386568f46..8a36c7b6b6 100644
--- a/test/unit/csv.c
+++ b/test/unit/csv.c
@@ -247,6 +247,22 @@ void iter_test2() {
 	footer();
 }
 
+void csv_out() {
+	header();
+
+	const char fields[4][36] = { "abc", "with,comma", "\"in quotes\"", "1 \" quote"};
+	char buf[18];
+	int i;
+	struct csv csv;
+	csv_create(&csv);
+	for(i = 0; i < 4; i++) {
+		int len = csv_escape_field(&csv, fields[i], buf);
+		printf("%s<len=%d>%c", buf, len, i == 3 ? '\n' : ',');
+	}
+
+	footer();
+}
+
 int main() {
 	test1();
 	test2();
@@ -314,5 +330,7 @@ int main() {
 	iter_test1();
 	iter_test2();
 
+	//output test
+	csv_out();
 	return 0;
 }
diff --git a/test/unit/csv.result b/test/unit/csv.result
index 9e43199617..8d56fa9744 100644
--- a/test/unit/csv.result
+++ b/test/unit/csv.result
@@ -183,4 +183,7 @@ valid: yes
 |1|	
 |23|	
 	*** iter_test2: done ***
+ 	*** csv_out ***
+abc<len=3>,"with,comma"<len=12>,""in quotes""<len=13>,1 "" quote<len=10>
+	*** csv_out: done ***
  
\ No newline at end of file
-- 
GitLab