Skip to content

Commit

Permalink
sheet: Use the transformation API to do filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
richiejp committed Nov 28, 2024
1 parent eaf0304 commit 431d94a
Show file tree
Hide file tree
Showing 20 changed files with 371 additions and 348 deletions.
26 changes: 23 additions & 3 deletions app/ext_example/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ ${BUILD_DIR}/objs/utils/%.o:

TESTS=test-1 test-2 test-3 test-4 test-5 test-thirdparty
ifeq ($(ZSVSHEET_BUILD),1)
TESTS+=test-sheet-extension
TESTS+=test-sheet-extension-1 test-sheet-extension-2
endif

test: ${TESTS}
Expand Down Expand Up @@ -147,7 +147,7 @@ test-3: test-%: ${CLI} ${TARGET}

TMP_DIR=/tmp
TMUX_TERM=xterm-256color
test-sheet-extension: ../test/worldcitiespop_mil.csv
test-sheet-extension-1: ${CLI} ${TARGET} ../test/worldcitiespop_mil.csv
@${TEST_INIT}
@rm -f ${TMP_DIR}/zsvext-$@.out tmux-*.log
@tmux kill-session -t $@ || echo 'No tmux session to kill'
Expand All @@ -156,14 +156,34 @@ test-sheet-extension: ../test/worldcitiespop_mil.csv
@echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf
@(ZSV_CONFIG_DIR=/tmp tmux -v new-session -x 80 -y 5 -d -s $@ && \
sleep 0.5 && \
tmux send-keys -t $@ "${CLI} sheet ../test/worldcitiespop_mil.csv" ENTER && \
tmux send-keys -t $@ "${RUN_CLI} sheet ../test/worldcitiespop_mil.csv" ENTER && \
sleep 0.5 && \
tmux send-keys -t $@ "t" "hello" Enter && \
tmux capture-pane -t $@ -p > ${TMP_DIR}/$@.out && \
tmux kill-session -t $@ && \
cmp ${TMP_DIR}/$@.out test/expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && \
if [ -f ${TMP_DIR}/$@.out ]; then cat ${TMP_DIR}/$@.out; fi && ${TEST_FAIL}))

test-sheet-extension-2: ${CLI} ${TARGET}
@${TEST_INIT}
@rm -f ${TMP_DIR}/zsvext-$@.out tmux-*.log
@tmux kill-session -t $@ || echo 'No tmux session to kill'
@${RUN_CLI} unregister my 2>/dev/null 1>/dev/null || [ 1==1 ]
@${RUN_CLI} register my 2>&1 | grep -v [.]so || [ 1==1 ]
@echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf
@(ZSV_CONFIG_DIR=/tmp tmux -v new-session -x 120 -y 5 -d -s $@ && \
sleep 0.5 && \
tmux send-keys -t $@ "${RUN_CLI} sheet -d 3 ../../data/test/mixed-line-endings.csv" ENTER && \
sleep 0.5 && \
tmux send-keys -t $@ T Enter && \
sleep 0.5 && \
tmux send-keys -t $@ G && \
sleep 0.5 && \
tmux capture-pane -t $@ -p > ${TMP_DIR}/$@.out && \
tmux kill-session -t $@ && \
cmp ${TMP_DIR}/$@.out test/expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && \
if [ -f ${TMP_DIR}/$@.out ]; then cat ${TMP_DIR}/$@.out; fi && ${TEST_FAIL}))

test-4: test-%: ${CLI} ${TARGET}
@${TEST_INIT}
@${RUN_CLI} unregister my 2>/dev/null 1>/dev/null || [ 1==1 ]
Expand Down
19 changes: 11 additions & 8 deletions app/ext_example/my_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ zsvsheet_status my_test_command_handler(zsvsheet_proc_context_t ctx) {
// print a list of open buffers and filenames
for (zsvsheet_buffer_t buff = zsv_cb.ext_sheet_buffer_current(ctx); buff;
buff = zsv_cb.ext_sheet_buffer_prior(buff), i--) {
const char *buff_filename = zsv_cb.ext_sheet_buffer_filename(buff);
const char *buff_filename = zsv_cb.ext_sheet_buffer_data_filename(buff);
if (buff_filename)
fprintf(f, "%i,%s\n", i, buff_filename); // assumes no need for quoting or escaping buff_filename...
}
Expand All @@ -109,13 +109,13 @@ zsvsheet_status my_test_command_handler(zsvsheet_proc_context_t ctx) {
}

struct transformation_context {
zsvsheet_proc_context_t proc_ctx;
size_t col_count;
size_t row_count;
};

// Similar to a regular ZSV row handler used in ext_parse_all
void my_transformation_row_handler(void *ctx) {
zsvsheet_transformation trn = ctx;
void my_transformation_row_handler(zsvsheet_transformation trn) {
struct transformation_context *priv = zsv_cb.ext_sheet_transformation_user_context(trn);
zsv_parser parser = zsv_cb.ext_sheet_transformation_parser(trn);
zsv_csv_writer writer = zsv_cb.ext_sheet_transformation_writer(trn);
Expand All @@ -137,11 +137,14 @@ void my_transformation_row_handler(void *ctx) {
}

zsvsheet_status my_transformation_command_handler(zsvsheet_proc_context_t ctx) {
struct transformation_context *my_ctx = calloc(1, sizeof(*my_ctx));

// TODO: This probably should happen in another worker thread and while that is happening the status should display
// that some work is in progress. The extension author will maybe want to have control over the status message.
return zsv_cb.ext_sheet_push_transformation(ctx, my_ctx, my_transformation_row_handler);
struct zsvsheet_buffer_transformation_opts opts = {
// Gets freed automatically
.user_context = calloc(1, sizeof(struct transformation_context)),
.row_handler = my_transformation_row_handler,
.on_done = NULL,
};

return zsv_cb.ext_sheet_push_transformation(ctx, opts);
}
#endif

Expand Down
1 change: 1 addition & 0 deletions app/ext_example/test/expected/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!*.out
5 changes: 5 additions & 0 deletions app/ext_example/test/expected/test-sheet-extension-2.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 Column count
4094 A4094 B4094 C4094 12285
4095 A4095 B4095 C4095 12288
4096 A4096 B4096 C4096 12291
? for help 4096
39 changes: 10 additions & 29 deletions app/sheet.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ struct zsvsheet_opts {

#include "sheet/utf8-width.c"
#include "sheet/ui_buffer.c"
#include "sheet/transformation.c"
#include "sheet/index.c"
#include "sheet/read-data.c"
#include "sheet/key-bindings.c"
Expand Down Expand Up @@ -194,6 +193,7 @@ static void zsvsheet_priv_set_status(const struct zsvsheet_display_dimensions *d
#include "sheet/handlers.c"
#include "sheet/file.c"
#include "sheet/usage.c"
#include "sheet/transformation.c"

struct zsvsheet_key_data *zsvsheet_key_handlers = NULL;
struct zsvsheet_key_data **zsvsheet_next_key_handler = &zsvsheet_key_handlers;
Expand Down Expand Up @@ -382,7 +382,7 @@ static zsvsheet_status zsvsheet_open_file_handler(struct zsvsheet_proc_context *
goto no_input;

const char *opts_used = NULL;
if ((err = zsvsheet_ui_buffer_open_file(prompt_buffer, NULL, NULL, state->custom_prop_handler, opts_used,
if ((err = zsvsheet_ui_buffer_open_file(prompt_buffer, NULL, state->custom_prop_handler, opts_used,
di->ui_buffers.base, di->ui_buffers.current))) {
if (err > 0)
zsvsheet_priv_set_status(di->dimensions, 1, "%s: %s", prompt_buffer, strerror(err));
Expand All @@ -396,14 +396,14 @@ static zsvsheet_status zsvsheet_open_file_handler(struct zsvsheet_proc_context *
return zsvsheet_status_ok;
}

// TODO: Use same API as transformation extensions
#include "sheet/filter.c"

static zsvsheet_status zsvsheet_filter_handler(struct zsvsheet_proc_context *ctx) {
char prompt_buffer[256] = {0};
struct zsvsheet_builtin_proc_state *state = (struct zsvsheet_builtin_proc_state *)ctx->subcommand_context;
struct zsvsheet_display_info *di = &state->display_info;
struct zsvsheet_ui_buffer *current_ui_buffer = *state->display_info.ui_buffers.current;
int prompt_footer_row = (int)(di->dimensions->rows - di->dimensions->footer_span);
int err;
struct zsvsheet_buffer_info binfo = zsvsheet_buffer_get_info(current_ui_buffer);

if (binfo.transform_started && !binfo.transform_done)
Expand All @@ -416,27 +416,7 @@ static zsvsheet_status zsvsheet_filter_handler(struct zsvsheet_proc_context *ctx
if (*prompt_buffer == '\0')
goto out;

const char *data_filename = zsvsheet_buffer_data_filename(current_ui_buffer);
char is_filtered_file = !(data_filename == current_ui_buffer->filename);
struct zsv_opts *zsv_opts = is_filtered_file ? NULL : &current_ui_buffer->zsv_opts;

if ((err = zsvsheet_ui_buffer_open_file(data_filename, zsv_opts, prompt_buffer, state->custom_prop_handler, NULL,
di->ui_buffers.base, di->ui_buffers.current))) {
if (err > 0)
zsvsheet_priv_set_status(di->dimensions, 1, "%s: %s", current_ui_buffer->filename, strerror(err));
else if (err < 0)
zsvsheet_priv_set_status(di->dimensions, 1, "Unexpected error");
else
zsvsheet_priv_set_status(di->dimensions, 1, "Not found: %s", prompt_buffer);
return zsvsheet_status_ignore;
}

struct zsvsheet_ui_buffer *new_ui_buffer = *state->display_info.ui_buffers.current;
if (is_filtered_file) {
// TO DO: move this into zsvsheet_ui_buffer_open_file()
free(new_ui_buffer->filename);
new_ui_buffer->filename = strdup(current_ui_buffer->filename);
}
return zsvsheet_filter_file(ctx, prompt_buffer);
out:
return zsvsheet_status_ok;
}
Expand All @@ -453,6 +433,7 @@ static zsvsheet_status zsvsheet_help_handler(struct zsvsheet_proc_context *ctx)
struct zsvsheet_ui_buffer_opts uibopts = {
.buff_opts = &bopts,
.filename = NULL,
.data_filename = NULL,
.no_rownum_col_offset = 1,
.transform = 0,
};
Expand Down Expand Up @@ -651,7 +632,7 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op

if (argc > 1) {
const char *filename = argv[1];
if ((err = zsvsheet_ui_buffer_open_file(filename, optsp, NULL, custom_prop_handler, opts_used, &ui_buffers,
if ((err = zsvsheet_ui_buffer_open_file(filename, optsp, custom_prop_handler, opts_used, &ui_buffers,
&current_ui_buffer))) {
if (err > 0)
perror(filename);
Expand Down Expand Up @@ -716,13 +697,13 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op
if (ub->transform_progressed) {
handler_state.display_info.update_buffer = true;
ub->transform_progressed = 0;
} else if (ub->index_ready && ub->dimensions.row_count != current_ui_buffer->index->row_count + 1) {
ub->dimensions.row_count = current_ui_buffer->index->row_count + 1;
} else if (ub->index_ready && ub->dimensions.row_count != ub->index->row_count + 1) {
ub->dimensions.row_count = ub->index->row_count + 1;
handler_state.display_info.update_buffer = true;
}
pthread_mutex_unlock(&ub->mutex);

if (handler_state.display_info.update_buffer && ub->filename) {
if (handler_state.display_info.update_buffer && zsvsheet_buffer_data_filename(ub)) {
struct zsvsheet_opts zsvsheet_opts = {0};
if (read_data(&ub, NULL, current_ui_buffer->input_offset.row, current_ui_buffer->input_offset.col, header_span,
&zsvsheet_opts, custom_prop_handler)) {
Expand Down
6 changes: 2 additions & 4 deletions app/sheet/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ int zsvsheet_ui_buffer_open_file_opts(struct zsvsheet_ui_buffer_opts *uibopts,
return 0;
}

int zsvsheet_ui_buffer_open_file(const char *filename, const struct zsv_opts *zsv_optsp, const char *row_filter,
int zsvsheet_ui_buffer_open_file(const char *filename, const struct zsv_opts *zsv_optsp,
struct zsv_prop_handler *custom_prop_handler, const char *opts_used,
struct zsvsheet_ui_buffer **ui_buffer_stack_bottom,
struct zsvsheet_ui_buffer **ui_buffer_stack_top) {
Expand All @@ -33,7 +33,6 @@ int zsvsheet_ui_buffer_open_file(const char *filename, const struct zsv_opts *zs
if (zsv_optsp)
uibopts.zsv_opts = *zsv_optsp;
uibopts.opts_used = opts_used;
uibopts.row_filter = row_filter;

return zsvsheet_ui_buffer_open_file_opts(&uibopts, custom_prop_handler, ui_buffer_stack_bottom, ui_buffer_stack_top);
}
Expand All @@ -48,8 +47,7 @@ zsvsheet_status zsvsheet_open_file(struct zsvsheet_proc_context *ctx, const char
struct zsvsheet_display_info *di = &state->display_info;
if (!di || !di->ui_buffers.base || !di->ui_buffers.current)
return zsvsheet_status_error;
int err =
zsvsheet_ui_buffer_open_file(filepath, zopts, NULL, NULL, NULL, di->ui_buffers.base, di->ui_buffers.current);
int err = zsvsheet_ui_buffer_open_file(filepath, zopts, NULL, NULL, di->ui_buffers.base, di->ui_buffers.current);
if (err)
return zsvsheet_status_error;
return zsvsheet_status_ok;
Expand Down
2 changes: 1 addition & 1 deletion app/sheet/file.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#ifndef ZSVSHEET_FILE_H
#define ZSVSHEET_FILE_H

int zsvsheet_ui_buffer_open_file(const char *filename, const struct zsv_opts *zsv_optsp, const char *row_filter,
int zsvsheet_ui_buffer_open_file(const char *filename, const struct zsv_opts *zsv_optsp,
struct zsv_prop_handler *custom_prop_handler, const char *opts_used,
struct zsvsheet_ui_buffer **ui_buffer_stack_bottom,
struct zsvsheet_ui_buffer **ui_buffer_stack_top);
Expand Down
89 changes: 89 additions & 0 deletions app/sheet/filter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <zsv/utils/mem.h>
#include <zsv/ext/sheet.h>
#include "transformation.h"
#include "handlers_internal.h"

struct filtered_file_ctx {
char *filter;
size_t filter_len;
size_t row_num; // 1-based row number (1 = header row, 2 = first data row)
size_t passed;
unsigned char seen_header : 1;
unsigned char has_row_num : 1;
unsigned char _ : 7;
};

static void zsvsheet_save_filtered_file_row_handler(zsvsheet_transformation trn) {
struct filtered_file_ctx *ctx = zsvsheet_transformation_user_context(trn);
zsv_parser parser = zsvsheet_transformation_parser(trn);
zsv_csv_writer writer = zsvsheet_transformation_writer(trn);
size_t col_count = zsv_cell_count(parser);
ctx->row_num++;
if (col_count == 0)
return;

struct zsv_cell first_cell = zsv_get_cell(parser, 0);
if (ctx->seen_header) {
struct zsv_cell last_cell = zsv_get_cell(parser, col_count - 1);
if (!memmem(first_cell.str, last_cell.str - first_cell.str + last_cell.len, ctx->filter, ctx->filter_len))
return;
// future enhancement: optionally, handle if row may have unusual quotes e.g. cell1,"ce"ll2,cell3
} else {
ctx->seen_header = 1;
if (first_cell.len == ZSVSHEET_ROWNUM_HEADER_LEN && !memcmp(first_cell.str, ZSVSHEET_ROWNUM_HEADER, first_cell.len))
ctx->has_row_num = 1;
}

char row_started = 0;
if (!ctx->has_row_num) {
// create our own rownum column
row_started = 1;
if (ctx->row_num == 1)
zsv_writer_cell_s(writer, 1, (const unsigned char *)"Row #", 0); // to do: consolidate "Row #"
else
zsv_writer_cell_zu(writer, 1, ctx->row_num - 1);
}
for (size_t i = 0; i < col_count; i++) {
struct zsv_cell cell = zsv_get_cell(parser, i);
zsv_writer_cell(writer, i == 0 && row_started == 0, cell.str, cell.len, cell.quoted);
}

ctx->passed++;
}

static void zsvsheet_filter_file_on_done(zsvsheet_transformation trn) {
struct filtered_file_ctx *ctx = zsvsheet_transformation_user_context(trn);
struct zsvsheet_ui_buffer *uib = trn->ui_buffer;

char *status = NULL;
asprintf(&status, "(%zu filtered rows) ", ctx->passed - 1);

pthread_mutex_lock(&uib->mutex);
char *old_status = uib->status;
uib->status = status;
pthread_mutex_unlock(&uib->mutex);

free(old_status);
free(ctx->filter);
}

static enum zsvsheet_status zsvsheet_filter_file(zsvsheet_proc_context_t proc_ctx, const char *row_filter) {
struct filtered_file_ctx ctx = {
.seen_header = 0,
.row_num = 0,
.passed = 0,
.has_row_num = 0,
.filter = strdup(row_filter),
.filter_len = strlen(row_filter),
};
struct zsvsheet_buffer_transformation_opts opts = {
.user_context = zsv_memdup(&ctx, sizeof(ctx)),
.row_handler = zsvsheet_save_filtered_file_row_handler,
.on_done = zsvsheet_filter_file_on_done,
};

return zsvsheet_push_transformation(proc_ctx, opts);
}
Loading

0 comments on commit 431d94a

Please sign in to comment.