Skip to content

Commit

Permalink
add output_path to zsv writer options and use in desc/echo/sql (#310)
Browse files Browse the repository at this point in the history
* add output_path to zsv writer options and use in desc/echo/sql
* update test-sheet-12 to wait long enough; update test-sheet-12 expected output
  • Loading branch information
liquidaty authored Nov 29, 2024
1 parent ab33988 commit 4665014
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 43 deletions.
11 changes: 4 additions & 7 deletions app/desc.c
Original file line number Diff line number Diff line change
Expand Up @@ -505,15 +505,12 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op

struct zsv_csv_writer_options writer_opts = zsv_writer_get_default_opts();

for (int arg_i = 1; !data.err && arg_i < argc; arg_i++) {
for (int arg_i = 1; !err && !data.err && arg_i < argc; arg_i++) {
if (!strcmp(argv[arg_i], "-b") || !strcmp(argv[arg_i], "--with-bom"))
writer_opts.with_bom = 1;
else if (!strcmp(argv[arg_i], "-o") || !strcmp(argv[arg_i], "--output")) {
if (++arg_i >= argc)
data.err = zsv_printerr(zsv_desc_status_error, "%s option requires a filename", argv[arg_i - 1]);
else if (!(writer_opts.stream = fopen(argv[arg_i], "wb")))
data.err = zsv_printerr(zsv_desc_status_error, "Unable to open for write: %s", argv[arg_i]);
} else if (!strcmp(argv[arg_i], "-a") || !strcmp(argv[arg_i], "--all"))
else if (!strcmp(argv[arg_i], "-o") || !strcmp(argv[arg_i], "--output"))
writer_opts.output_path = zsv_next_arg(++arg_i, argc, argv, (int *)&data.err);
else if (!strcmp(argv[arg_i], "-a") || !strcmp(argv[arg_i], "--all"))
data.flags = 0xff;
else if (!strcmp(argv[arg_i], "-q") || !strcmp(argv[arg_i], "--quick"))
data.quick = 1;
Expand Down
10 changes: 3 additions & 7 deletions app/echo.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,9 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op
const char *arg = argv[arg_i];
if (!strcmp(arg, "-b"))
writer_opts.with_bom = 1;
else if (!strcmp(arg, "-o")) {
const char *output_filename = zsv_next_arg(++arg_i, argc, argv, &err);
if (output_filename && !err) {
if (!(writer_opts.stream = fopen(output_filename, "wb")))
perror(output_filename);
}
} else if (!strcmp(arg, "--contiguous"))
else if (!strcmp(arg, "-o"))
writer_opts.output_path = zsv_next_arg(++arg_i, argc, argv, &err);
else if (!strcmp(arg, "--contiguous"))
data.contiguous = 1;
else if (!strcmp(arg, "--trim-columns"))
data.trim_columns = 1;
Expand Down
3 changes: 1 addition & 2 deletions app/pretty.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

#define ZSV_COMMAND pretty
#include "zsv_command.h"
#include <zsv/utils/writer.h>
#include <zsv/utils/string.h>

#define ZSV_PRETTY_DEFAULT_LINE_MAX_WIDTH 160
Expand Down Expand Up @@ -686,7 +685,7 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *pa
zsv_pretty_flush(h);
zsv_pretty_destroy(h);
}
if (opts.out)
if (opts.out && opts.out != stdout)
fclose(opts.out);
if (in && in != stdin)
fclose(in);
Expand Down
18 changes: 6 additions & 12 deletions app/sql.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,9 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op
err = 1;
}
}
} else if (!strcmp(arg, "-o") || !strcmp(arg, "--output")) {
if (!(++arg_i < argc)) {
fprintf(stderr, "option %s requires a filename\n", arg);
err = 1;
} else if (!(writer_opts.stream = fopen(argv[arg_i], "wb"))) {
fprintf(stderr, "Could not open for writing: %s\n", argv[arg_i]);
err = 1;
}
} else if (!strcmp(arg, "--memory"))
} else if (!strcmp(arg, "-o") || !strcmp(arg, "--output"))
writer_opts.output_path = zsv_next_arg(++arg_i, argc, argv, &err);
else if (!strcmp(arg, "--memory"))
data.in_memory = 1;
else if (!strcmp(arg, "-b"))
writer_opts.with_bom = 1;
Expand Down Expand Up @@ -281,11 +275,11 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op
}
}

if (f) {
zsv_csv_writer cw = zsv_writer_new(&writer_opts);
if (f && cw) {
fclose(f); // to do: don't open in the first place
f = NULL;

zsv_csv_writer cw = zsv_writer_new(&writer_opts);
unsigned char cw_buff[1024];
zsv_writer_set_temp_buff(cw, cw_buff, sizeof(cw_buff));

Expand Down Expand Up @@ -440,14 +434,14 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op
else
err = 0;

zsv_writer_delete(cw);
zsv_sqlite3_db_delete(zdb);
}
}
if (f)
fclose(f);
zsv_sql_finalize(&data);
zsv_sql_cleanup(&data);
zsv_writer_delete(cw);

if (tmpfn) {
unlink(tmpfn);
Expand Down
1 change: 1 addition & 0 deletions app/test/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,7 @@ test-sheet-12: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv
tmux send-keys -t $@ "f" "el" "Enter" && \
sleep 0.5 && \
tmux send-keys -t $@ "f" "al" "Enter" && \
sleep 1 && \
tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \
tmux send-keys -t $@ "q" && \
${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL}))
Expand Down
2 changes: 1 addition & 1 deletion app/test/expected/test-sheet-12.out
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ Row # Country City AccentCit Region Populatio Latitude Longitude
415 mx el salado El Salado 07 29.644444 -101.7666
590 af abbaskhel Abbaskhel 29 32.831993 69.12336
807 af `abdul `a `Abdul `A 10 31.229677 64.206403
(59456 filtered rows) 117
(4950 filtered rows) 117
55 changes: 42 additions & 13 deletions app/utils/writer.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

// clang-format off

Expand Down Expand Up @@ -107,6 +108,8 @@ struct zsv_output_buff {
size_t (*write)(const void *restrict, size_t size, size_t nitems, void *restrict stream);
void *stream;
size_t used;
unsigned char close_on_delete : 1;
unsigned char _ : 7;
};

struct zsv_writer_data {
Expand Down Expand Up @@ -152,19 +155,40 @@ void zsv_writer_set_temp_buff(zsv_csv_writer w, unsigned char *buff, size_t buff
w->buffsize = buffsize;
}

static int writer_opts_ok(struct zsv_csv_writer_options *opts) {
if (opts) {
if (opts->output_path) {
if ((opts->write && opts->write != (size_t(*)(const void *restrict, size_t, size_t, void *restrict))fwrite) ||
(opts->stream && opts->stream != stdout)) {
fprintf(stderr, "Invalid zsv writer options: non-NULL 'output_path' with invalid 'write' and/or 'stream'\n");
errno = EINVAL;
return 0;
}
}
}
return 1;
}

zsv_csv_writer zsv_writer_new(struct zsv_csv_writer_options *opts) {
struct zsv_writer_data *w = calloc(1, sizeof(*w));
if (!(writer_opts_ok(opts)))
return NULL;
if (w) {
if (!(w->out.buff = malloc(ZSV_OUTPUT_BUFF_SIZE))) {
free(w); // out of memory!
return NULL;
}
if (!(w->out.buff = malloc(ZSV_OUTPUT_BUFF_SIZE)))
goto zsv_writer_new_err;

if (!opts) {
w->out.write = (size_t(*)(const void *restrict, size_t, size_t, void *restrict))fwrite;
w->out.stream = stdout;
} else {
if (opts->write) {
if (opts->output_path) {
if (!(w->out.stream = fopen(opts->output_path, "wb"))) {
perror(opts->output_path);
goto zsv_writer_new_err;
}
w->out.close_on_delete = 1;
w->out.write = (size_t(*)(const void *restrict, size_t, size_t, void *restrict))fwrite;
} else if (opts->write) {
w->out.write = opts->write;
w->out.stream = opts->stream;
} else {
Expand All @@ -178,6 +202,13 @@ zsv_csv_writer zsv_writer_new(struct zsv_csv_writer_options *opts) {
}
}
return w;

zsv_writer_new_err : {
int e = errno;
zsv_writer_delete(w);
errno = e;
}
return NULL;
}

enum zsv_writer_status zsv_writer_flush(zsv_csv_writer w) {
Expand All @@ -192,19 +223,17 @@ enum zsv_writer_status zsv_writer_delete(zsv_csv_writer w) {
if (!w)
return zsv_writer_status_missing_handle;

zsv_output_buff_flush(&w->out);
if (w->out.stream && w->out.write && w->out.buff)
zsv_output_buff_flush(&w->out);

if (w->started)
w->out.write("\n", 1, 1, w->out.stream);

/* closing the stream should be handled by whatever code first opened the stream
if (w->out.stream && w->out.stream != stdout) {
fclose(w->out.stream);
w->out.stream = NULL;
}
*/

if (w->out.buff)
free(w->out.buff);

if (w->out.close_on_delete && w->out.stream)
fclose(w->out.stream);
free(w);
return zsv_writer_status_ok;
}
Expand Down
12 changes: 11 additions & 1 deletion include/zsv/utils/writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
#define ZSV_WRITER_NEW_ROW 1
#define ZSV_WRITER_SAME_ROW 0

/*** csv writer ***/
/**
* csv writer options
*
* If `output_path` is non-NULL, `stream` must be NULL or stdout and `write` must be NULL or fwrite
***/
struct zsv_csv_writer_options {
char with_bom;
size_t (*write)(const void *restrict, size_t size, size_t nitems, void *restrict stream);
void *stream;
void (*table_init)(void *);
void *table_init_ctx;
const char *output_path; // if provided, will be created by zsv_writer_new() and closed by zsv_writer_delete()
};

void zsv_writer_set_default_opts(struct zsv_csv_writer_options opts);
Expand All @@ -36,6 +41,11 @@ enum zsv_writer_status {
struct zsv_writer_data;
typedef struct zsv_writer_data *zsv_csv_writer;

/**
* Get a new CSV writer
*
* @returns handle, or NULL on error, in which case errno will be set
*/
zsv_csv_writer zsv_writer_new(struct zsv_csv_writer_options *opts);
enum zsv_writer_status zsv_writer_delete(zsv_csv_writer w);

Expand Down

0 comments on commit 4665014

Please sign in to comment.