Skip to content

Commit

Permalink
sheet: Use the transformation API to do filtering
Browse files Browse the repository at this point in the history
This uses the transformation API to produce a filter file and makes
some changes to the API to allow almost the same behavior that the feature
had before.

The behavior has changed because now executing filter repeatedly
filters the previously filtered file instead of replacing the filter.
  • Loading branch information
richiejp committed Nov 28, 2024
1 parent eaf0304 commit 3f38d92
Show file tree
Hide file tree
Showing 21 changed files with 389 additions and 358 deletions.
24 changes: 22 additions & 2 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 @@ -164,6 +164,26 @@ test-sheet-extension: ../test/worldcitiespop_mil.csv
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 $@ "${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
49 changes: 17 additions & 32 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,47 +396,27 @@ 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)
return zsvsheet_status_busy;

if (!current_ui_buffer->filename)
if (!zsvsheet_buffer_data_filename(current_ui_buffer))
goto out;

get_subcommand("Filter", prompt_buffer, sizeof(prompt_buffer), prompt_footer_row);
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,10 +433,11 @@ 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,
};
struct zsvsheet_ui_buffer *uib;
struct zsvsheet_ui_buffer *uib = NULL;
zsvsheet_screen_buffer_t buffer;
enum zsvsheet_priv_status pstat;
enum zsvsheet_status stat = zsvsheet_status_error;
Expand Down Expand Up @@ -512,7 +493,10 @@ static zsvsheet_status zsvsheet_help_handler(struct zsvsheet_proc_context *ctx)
goto out;

free_buffer:
zsvsheet_screen_buffer_delete(buffer);
if (uib)
zsvsheet_ui_buffer_delete(uib);
else
zsvsheet_screen_buffer_delete(buffer);
out:
return stat;
}
Expand Down Expand Up @@ -651,7 +635,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 +700,14 @@ 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;
}
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 3f38d92

Please sign in to comment.