From 121c01d28fa47544515c3dae5e00ccd0b254ba45 Mon Sep 17 00:00:00 2001 From: liquidaty Date: Wed, 15 Mar 2023 01:04:59 -0700 Subject: [PATCH 1/3] add mv command --- app/Makefile | 6 +++--- app/builtin/help.c | 1 + app/cli.c | 4 +++- .../test/expected/zsvext-test-3.out | 2 ++ app/rm.c | 9 ++++----- app/test/Makefile | 19 ++++++++++++++++++- app/utils/file.c | 15 +++++++++++++++ include/zsv/utils/file.h | 8 ++++++++ 8 files changed, 54 insertions(+), 10 deletions(-) diff --git a/app/Makefile b/app/Makefile index e7a73057..a283fd13 100644 --- a/app/Makefile +++ b/app/Makefile @@ -163,8 +163,8 @@ endif ZSV=$(BINDIR)/zsv${EXE} -SOURCES= echo count count-pull select select-pull 2tsv 2json serialize flatten pretty stack desc sql 2db compare prop rm jq -CLI_SOURCES=echo select desc count 2tsv pretty sql flatten 2json serialize stack 2db compare prop rm jq +SOURCES= echo count count-pull select select-pull 2tsv 2json serialize flatten pretty stack desc sql 2db compare prop rm mv jq +CLI_SOURCES=echo select desc count 2tsv pretty sql flatten 2json serialize stack 2db compare prop rm mv jq CFLAGS+= -DUSE_JQ @@ -255,7 +255,7 @@ help: @echo "which will build and test all apps, or to build/test a single app:" @echo " ${MAKE} test-xx" @echo "where xx is any of:" - @echo " echo count count-pull select select-pull 2tsv 2json serialize flatten pretty stack desc sql 2db prop rm" + @echo " echo count count-pull select select-pull 2tsv 2json serialize flatten pretty stack desc sql 2db prop rm mv" @echo "" install: ${ZSV} diff --git a/app/builtin/help.c b/app/builtin/help.c index 86bbc3b2..277effd9 100644 --- a/app/builtin/help.c +++ b/app/builtin/help.c @@ -72,6 +72,7 @@ static int main_help(int argc, const char *argv[]) { " prop : save parsing options associated with a file that are subsequently", " applied by default when processing that file", " rm : remove a file and its related cache", + " mv : rename (move) a file and/or its related cache", #ifdef USE_JQ " jq : run a jq filter on json input", #endif diff --git a/app/cli.c b/app/cli.c index 9161e85a..762e71b3 100644 --- a/app/cli.c +++ b/app/cli.c @@ -69,6 +69,7 @@ ZSV_MAIN_DECL(compare); ZSV_MAIN_DECL(echo); ZSV_MAIN_NO_OPTIONS_DECL(prop); ZSV_MAIN_NO_OPTIONS_DECL(rm); +ZSV_MAIN_NO_OPTIONS_DECL(mv); #ifdef USE_JQ ZSV_MAIN_NO_OPTIONS_DECL(jq); @@ -99,7 +100,8 @@ struct builtin_cmd builtin_cmds[] = { CLI_BUILTIN_COMMAND(compare), CLI_BUILTIN_COMMAND(echo), CLI_BUILTIN_NO_OPTIONS_COMMAND(prop), - CLI_BUILTIN_NO_OPTIONS_COMMAND(rm) + CLI_BUILTIN_NO_OPTIONS_COMMAND(rm), + CLI_BUILTIN_NO_OPTIONS_COMMAND(mv) #ifdef USE_JQ , CLI_BUILTIN_NO_OPTIONS_COMMAND(jq) #endif diff --git a/app/ext_example/test/expected/zsvext-test-3.out b/app/ext_example/test/expected/zsvext-test-3.out index efea2a6f..a4740572 100644 --- a/app/ext_example/test/expected/zsvext-test-3.out +++ b/app/ext_example/test/expected/zsvext-test-3.out @@ -54,6 +54,7 @@ Other commands: prop : save parsing options associated with a file that are subsequently applied by default when processing that file rm : remove a file and its related cache + mv : rename (move) a file and/or its related cache jq : run a jq filter on json input (No extended commands) @@ -111,6 +112,7 @@ Other commands: prop : save parsing options associated with a file that are subsequently applied by default when processing that file rm : remove a file and its related cache + mv : rename (move) a file and/or its related cache jq : run a jq filter on json input Extended commands: diff --git a/app/rm.c b/app/rm.c index 85a31bbb..8829710c 100644 --- a/app/rm.c +++ b/app/rm.c @@ -27,9 +27,9 @@ const char *zsv_rm_usage_msg[] = { APPNAME ": remove a file and its related cache", "", - "Usage: " APPNAME " ", + "Usage: " APPNAME " [options] ", " where options may be:", - " -v,--verbose: do not prompt for confirmation", + " -v,--verbose: verbose output", #ifndef NO_STDIN " -f,--force : do not prompt for confirmation", #endif @@ -107,11 +107,10 @@ int ZSV_MAIN_NO_OPTIONS_FUNC(ZSV_COMMAND)(int argc, const char *argv[]) { fprintf(stderr, "Removing %s", filepath); err = unlink(filepath); if(err) { - perror(filepath); - if(force) + if(err == ENOENT && force) err = 0; else - fprintf(stderr, "Cached files (if any) not removed\n"); + perror(filepath); } } if(!err) { diff --git a/app/test/Makefile b/app/test/Makefile index 0621d4d3..d692ae78 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -48,7 +48,7 @@ TEST_DATA_DIR=${THIS_LIB_BASE}/data SOURCES= echo count count-pull select select-pull sql 2json serialize flatten pretty desc stack 2db 2tsv jq compare TARGETS=$(addprefix ${BUILD_DIR}/bin/zsv_,$(addsuffix ${EXE},${SOURCES})) -TESTS=test-blank-leading-rows $(addprefix test-,${SOURCES}) +TESTS=test-blank-leading-rows $(addprefix test-,${SOURCES}) test-rm test-mv COLOR_NONE=\033[0m COLOR_GREEN=\033[1;32m @@ -223,6 +223,23 @@ test-fixed-4-select test-fixed-4-select-pull: ${BUILD_DIR}/bin/zsv_select${EXE} @${PREFIX} $< ${TEST_DATA_DIR}/fixed-auto3.txt --fixed-auto ${REDIRECT} ${TMP_DIR}/$@.out @${CMP} ${TMP_DIR}/$@.out expected/test-fixed-4-select.out && ${TEST_PASS} || ${TEST_FAIL} +test-rm: ${BUILD_DIR}/bin/zsv_prop${EXE} ${BUILD_DIR}/bin/zsv_rm${EXE} + @${TEST_INIT} + @echo 'hi' > ${TMP_DIR}/$@.csv + @${PREFIX} ${BUILD_DIR}/bin/zsv_prop${EXE} ${TMP_DIR}/$@.csv -R 1 -d 2 ${REDIRECT} /dev/null + @find ${TMP_DIR}/.zsv/data/$@.csv/props.json -type f >/dev/null + @${PREFIX} ${BUILD_DIR}/bin/zsv_rm${EXE} ${TMP_DIR}/$@.csv -f ${REDIRECT} /dev/null + @find ${TMP_DIR}/.zsv/data/$@.csv/props.json -type f 2>/dev/null && ${TEST_FAIL} || ${TEST_PASS} + +test-mv: ${BUILD_DIR}/bin/zsv_prop${EXE} ${BUILD_DIR}/bin/zsv_mv${EXE} + @${TEST_INIT} + @echo 'hi' > ${TMP_DIR}/$@.csv + @rm -rf ${TMP_DIR}/.zsv/data/$@.csv ${TMP_DIR}/.zsv/data/$@-moved.csv + @${PREFIX} ${BUILD_DIR}/bin/zsv_prop${EXE} ${TMP_DIR}/$@.csv -R 1 -d 2 ${REDIRECT} /dev/null + @find ${TMP_DIR}/.zsv/data/$@.csv/props.json -type f >/dev/null + @${PREFIX} ${BUILD_DIR}/bin/zsv_mv${EXE} ${TMP_DIR}/$@.csv ${TMP_DIR}/$@-moved.csv ${REDIRECT} /dev/null + @find ${TMP_DIR}/.zsv/data/$@-moved.csv/props.json -type f >/dev/null && ${TEST_PASS} || ${TEST_FAIL} + test-blank-leading-rows: test-blank-leading-rows-1 test-blank-leading-rows-2 test-blank-leading-rows-3 test-blank-leading-rows-4 diff --git a/app/utils/file.c b/app/utils/file.c index d15d679a..f09be0df 100644 --- a/app/utils/file.c +++ b/app/utils/file.c @@ -91,6 +91,21 @@ void zsv_redirect_file_from_temp(FILE *f, int bak, int old_fd) { close(bak); } +#if defined(_WIN32) || defined(WIN32) || defined(WIN) +int zsv_file_exists(const char* filename) { + DWORD attributes = GetFileAttributes(filename); + return (attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY)); +} +#else +# include // S_IRUSR S_IWUSR + +int zsv_file_exists(const char* filename) { + struct stat buffer; + return (stat(filename, &buffer) == 0); +} +#endif + + int zsv_file_readable(const char *filename, int *err, FILE **f_out) { FILE *f; int rc; diff --git a/include/zsv/utils/file.h b/include/zsv/utils/file.h index d860fee4..0afeb76e 100644 --- a/include/zsv/utils/file.h +++ b/include/zsv/utils/file.h @@ -26,6 +26,14 @@ */ char *zsv_get_temp_filename(const char *prefix); +/** + * Check if a file exists and is readable (with fopen + "rb") + * + * @param filename + * @returns: true (1) if file exists + */ +int zsv_file_exists(const char* filename); + /** * Check if a file exists and is readable (with fopen + "rb") * From 909c930fbab19ea411643f8a58fa558a26d042ce Mon Sep 17 00:00:00 2001 From: liquidaty Date: Wed, 15 Mar 2023 09:41:31 -0700 Subject: [PATCH 2/3] add app/mv.c --- app/mv.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ app/prop.c | 4 ++ 2 files changed, 116 insertions(+) create mode 100644 app/mv.c diff --git a/app/mv.c b/app/mv.c new file mode 100644 index 00000000..80bcfe7e --- /dev/null +++ b/app/mv.c @@ -0,0 +1,112 @@ +/* Copyright (C) 2022 Guarnerix Inc dba Liquidaty - All Rights Reserved + * Unauthorized copying of this file, via any medium is strictly prohibited + * Proprietary and confidential + * Written by Matt Wong + */ + +/* + * Move a given file and its cache as follows: + * 1. check that the destination file doesn't exist. if it does, exit with an error + * 2. if a cache exists, check that the destination file cache dir doesn't exist. if it does, exit with an error + * 3. move the file. if it fails, exit with an error + * 4. move the cache, if it exists. if it fails, attempt to move the file back to its original location, and exit with an error + */ + +#include +#include +#include +#include // unlink() +#include + +#define ZSV_COMMAND_NO_OPTIONS +#define ZSV_COMMAND mv +#include "zsv_command.h" + +#include +#include +#include + +const char *zsv_mv_usage_msg[] = { + APPNAME ": move a file and its related cache", + "", + "Usage: " APPNAME " [options] ", + " where options may be:", + " -v,--verbose: verbose output", + " -C,--cache : only move related cache (not the file)", + NULL +}; + +static int zsv_mv_usage(FILE *target) { + for(int j = 0; zsv_mv_usage_msg[j]; j++) + fprintf(target, "%s\n", zsv_mv_usage_msg[j]); + return target == stdout ? 0 : 1; +} + +int ZSV_MAIN_NO_OPTIONS_FUNC(ZSV_COMMAND)(int argc, const char *argv[]) { + int err = 0; + if(argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) + err = zsv_mv_usage(stdout); + else if(argc < 2) + err = zsv_mv_usage(stderr); + else { + const char *source = NULL; + const char *dest = NULL; + + char move_file = 1; + char verbose = 0; + for(int i = 1; !err && i < argc; i++) { + const char *arg = argv[i]; + if(*arg == '-') { + if(!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) + verbose = 1; + else if(!strcmp(arg, "-C") || !strcmp(arg, "--cache")) + move_file = 0; + else + err = zsv_printerr(1, "Unrecognized option: %s", arg); + } else if(!source) + source = arg; + else if(!dest) + dest = arg; + else + err = zsv_printerr(1, "Unrecognized option: %s", arg); + } + + if(!err) { + unsigned char *source_cache_dir = zsv_cache_path((const unsigned char *)source, NULL, 0); + unsigned char *dest_cache_dir = zsv_cache_path((const unsigned char *)dest, NULL, 0); + if(!source || !dest) { + err = zsv_mv_usage(stderr); + } else if(move_file && !zsv_file_exists(source)) { + err = errno = ENOENT; + perror(source); + } else if(move_file && zsv_file_exists(dest)) { + err = errno = EEXIST; + perror(dest); + } else if(zsv_dir_exists((const char *)source_cache_dir) && zsv_dir_exists((const char *)dest_cache_dir)) { + err = errno = EEXIST; + perror((char*)dest_cache_dir); + fprintf(stderr, "Use `mv --cache %s ` to move or `rm --cache %s` to remove, then try again\n", + dest, dest); + } else if(move_file && (verbose ? fprintf(stderr, "Renaming files\n") : 1) + && rename(source, dest)) { + err = errno; + fprintf(stderr, "%s -> %s: ", source, dest); + perror(NULL); + } else if(zsv_dir_exists((const char *)source_cache_dir) && (verbose ? fprintf(stderr, "Moving caches\n") : 1) + && rename((char*)source_cache_dir, (char*)dest_cache_dir)) { + err = errno; + fprintf(stderr, "%s -> %s: ", source_cache_dir, dest_cache_dir); + perror(NULL); + + // try to revert the prior rename + if(rename(dest, source)) { + fprintf(stderr, "%s -> %s: ", dest, source); + perror(NULL); + } + } + free(source_cache_dir); + free(dest_cache_dir); + } + } + return err; +} diff --git a/app/prop.c b/app/prop.c index e4237592..a2772199 100644 --- a/app/prop.c +++ b/app/prop.c @@ -36,12 +36,16 @@ const char *zsv_property_usage_msg[] = { " and options may be one or more of:", " -d,--header-row-span : set/unset/auto-detect header depth (see below)", " -R,--skip-head : set/unset/auto-detect initial rows to skip (see below)", + " --list-files : x", // output a list of all cache files " --clear : delete all properties", + " --clear-orphans : delete properties of all orphaned files in the given file or directory path", " --auto : guess the best property values. This is equivalent to:", " -d auto -R auto", " when using this option, a dash (-) can be used instead", " of a filepath to read from stdin", " --save [-f,--overwrite] : (only applicable with --auto) save the detected result", + " --export : export all properties to a single JSON file (- for stdout)", // to do: add option to check for valid JSON + " --import : import properties from a single JSON file (- for stdin)", // to do: add option to check for valid JSON " -f,--overwrite : overwrite any previously-saved properties", "", "For --header-row-span or --skip-head options, can be:", From d9571d1b9ed0f356b2585072f94322bee7ac081f Mon Sep 17 00:00:00 2001 From: liquidaty Date: Wed, 15 Mar 2023 09:54:48 -0700 Subject: [PATCH 3/3] updated test-mv --- app/test/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/test/Makefile b/app/test/Makefile index d692ae78..2f82bdbd 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -234,7 +234,7 @@ test-rm: ${BUILD_DIR}/bin/zsv_prop${EXE} ${BUILD_DIR}/bin/zsv_rm${EXE} test-mv: ${BUILD_DIR}/bin/zsv_prop${EXE} ${BUILD_DIR}/bin/zsv_mv${EXE} @${TEST_INIT} @echo 'hi' > ${TMP_DIR}/$@.csv - @rm -rf ${TMP_DIR}/.zsv/data/$@.csv ${TMP_DIR}/.zsv/data/$@-moved.csv + @rm -rf ${TMP_DIR}/.zsv/data/$@.csv ${TMP_DIR}/.zsv/data/$@-moved.csv ${TMP_DIR}/$@-moved.csv @${PREFIX} ${BUILD_DIR}/bin/zsv_prop${EXE} ${TMP_DIR}/$@.csv -R 1 -d 2 ${REDIRECT} /dev/null @find ${TMP_DIR}/.zsv/data/$@.csv/props.json -type f >/dev/null @${PREFIX} ${BUILD_DIR}/bin/zsv_mv${EXE} ${TMP_DIR}/$@.csv ${TMP_DIR}/$@-moved.csv ${REDIRECT} /dev/null