From 3172920c9c8e7f665b093c0d73ad417a5440c1ea Mon Sep 17 00:00:00 2001 From: Stephen Seo Date: Mon, 1 Jul 2024 15:13:06 +0900 Subject: [PATCH] Some work on arg parser Also added unit tests for arg parser. --- .forgejo/workflows/unittest.yml | 2 + CMakeLists.txt | 5 ++ cosmopolitan/Makefile | 1 + src/parser.c | 68 +++++++++++++++++-- src/parser_internal.h | 26 ++++++++ src/test.c | 115 ++++++++++++++++++++++++++++++++ 6 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 src/parser_internal.h create mode 100644 src/test.c diff --git a/.forgejo/workflows/unittest.yml b/.forgejo/workflows/unittest.yml index 95069a8..42cebc5 100644 --- a/.forgejo/workflows/unittest.yml +++ b/.forgejo/workflows/unittest.yml @@ -18,3 +18,5 @@ jobs: name: Build - run: ./buildDebug/test_datastructures name: Run test_datastructures + - run: ./buildDebug/test_simplearchiver + name: Run test_simplearchiver diff --git a/CMakeLists.txt b/CMakeLists.txt index 3542f02..5939603 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,3 +33,8 @@ add_executable(test_datastructures src/data_structures/priority_heap.c src/algorithms/linear_congruential_gen.c ) + +add_executable(test_simplearchiver + src/test.c + src/parser.c +) diff --git a/cosmopolitan/Makefile b/cosmopolitan/Makefile index 098c0a3..25b0188 100644 --- a/cosmopolitan/Makefile +++ b/cosmopolitan/Makefile @@ -13,6 +13,7 @@ SOURCES = \ HEADERS = \ ../src/parser.h \ + ../src/parser_internal.h \ ../src/algorithms/linear_congruential_gen.h \ ../src/data_structures/linked_list.h \ ../src/data_structures/hash_map.h \ diff --git a/src/parser.c b/src/parser.c index fbe91c7..0a31d3f 100644 --- a/src/parser.c +++ b/src/parser.c @@ -22,6 +22,59 @@ #include #include +#include "parser_internal.h" + +/// Gets the first non "./"-like character in the filename. +unsigned int simple_archiver_parser_internal_filename_idx( + const char *filename) { + unsigned int idx = 0; + unsigned int known_good_idx = 0; + const unsigned int length = strlen(filename); + + // 0b0001 - checked that idx char is '.' + // 0b0010 - checked that idx char is '/' + unsigned int flags = 0; + + for (; idx < length; ++idx) { + if ((flags & 3) == 0) { + if (filename[idx] == 0) { + return known_good_idx; + } else if (filename[idx] == '.') { + flags |= 1; + } else { + return idx; + } + } else if ((flags & 3) == 1) { + if (filename[idx] == 0) { + return known_good_idx; + } else if (filename[idx] == '/') { + flags |= 2; + } else { + return idx - 1; + } + } else if ((flags & 3) == 3) { + if (filename[idx] == 0) { + return known_good_idx; + } else if (filename[idx] == '/') { + continue; + } else if (filename[idx] == '.') { + flags &= 0xFFFFFFFC; + known_good_idx = idx; + --idx; + continue; + } else { + break; + } + } + } + + if (filename[idx] == 0) { + return known_good_idx; + } + + return idx; +} + void simple_archiver_print_usage(void) { puts("Usage flags:"); puts("-c : create archive file"); @@ -29,6 +82,7 @@ void simple_archiver_print_usage(void) { puts("-f : filename to work on"); puts("--compressor : requires --decompressor"); puts("--decompressor : requires --compressor"); + puts("-- : specifies remaining arguments are files to archive/extract"); puts("If creating archive file, remaining args specify files to archive."); puts("If extracting archive file, remaining args specify files to extract."); } @@ -92,6 +146,8 @@ int simple_archiver_parse_args(int argc, const char **argv, strncpy(out->decompressor, argv[1], size); --argc; ++argv; + } else if (argv[0][0] == '-' && argv[0][1] == '-' && argv[0][2] == 0) { + is_remaining_args = 1; } else if (argv[0][0] != '-') { is_remaining_args = 1; continue; @@ -99,9 +155,11 @@ int simple_archiver_parse_args(int argc, const char **argv, } else { if (out->working_files == NULL) { out->working_files = malloc(sizeof(char *) * 2); - int arg_length = strlen(argv[0]) + 1; + unsigned int arg_idx = + simple_archiver_parser_internal_filename_idx(argv[0]); + int arg_length = strlen(argv[0] + arg_idx) + 1; out->working_files[0] = malloc(arg_length); - strncpy(out->working_files[0], argv[0], arg_length); + strncpy(out->working_files[0], argv[0] + arg_idx, arg_length); out->working_files[1] = NULL; } else { int working_size = 1; @@ -117,10 +175,12 @@ int simple_archiver_parse_args(int argc, const char **argv, // Set new actual last element to NULL. out->working_files[working_size] = NULL; - int size = strlen(argv[0]) + 1; + unsigned int arg_idx = + simple_archiver_parser_internal_filename_idx(argv[0]); + int size = strlen(argv[0] + arg_idx) + 1; // Set last element to the arg. out->working_files[working_size - 1] = malloc(size); - strncpy(out->working_files[working_size - 1], argv[0], size); + strncpy(out->working_files[working_size - 1], argv[0] + arg_idx, size); } } diff --git a/src/parser_internal.h b/src/parser_internal.h new file mode 100644 index 0000000..b2ea7ba --- /dev/null +++ b/src/parser_internal.h @@ -0,0 +1,26 @@ +/* + * Copyright 2024 Stephen Seo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * `parser_internal.h` is the header for parsing args with internal functions. + */ + +#ifndef SEODISPARATE_COM_SIMPLE_ARCHIVER_PARSER_INTERNAL_H_ +#define SEODISPARATE_COM_SIMPLE_ARCHIVER_PARSER_INTERNAL_H_ + +#include "parser.h" + +unsigned int simple_archiver_parser_internal_filename_idx(const char *filename); + +#endif diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..cf6cb4f --- /dev/null +++ b/src/test.c @@ -0,0 +1,115 @@ +/* + * Copyright 2024 Stephen Seo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * `test.c` is the source for testing code. + */ + +#include +#include +#include + +#include "parser_internal.h" + +static int checks_checked = 0; +static int checks_passed = 0; + +#define CHECK_TRUE(x) \ + do { \ + ++checks_checked; \ + if (!(x)) { \ + printf("CHECK_TRUE at line %u failed: %s\n", __LINE__, #x); \ + } else { \ + ++checks_passed; \ + } \ + } while (0); +#define CHECK_FALSE(x) \ + do { \ + ++checks_checked; \ + if (x) { \ + printf("CHECK_FALSE at line %u failed: %s\n", __LINE__, #x); \ + } else { \ + ++checks_passed; \ + } \ + } while (0); + +int main(void) { + // Test parser. + { + unsigned int idx = simple_archiver_parser_internal_filename_idx("test"); + CHECK_TRUE(idx == 0); + + idx = simple_archiver_parser_internal_filename_idx("./test"); + CHECK_TRUE(idx == 2); + + idx = simple_archiver_parser_internal_filename_idx("././test"); + CHECK_TRUE(idx == 4); + + idx = simple_archiver_parser_internal_filename_idx("././//././//./test"); + CHECK_TRUE(idx == 14); + + idx = simple_archiver_parser_internal_filename_idx("/././//././//./test"); + CHECK_TRUE(idx == 0); + + idx = simple_archiver_parser_internal_filename_idx(".derp/.//././//./test"); + CHECK_TRUE(idx == 0); + + idx = simple_archiver_parser_internal_filename_idx("././/.derp/.///./test"); + CHECK_TRUE(idx == 5); + + idx = simple_archiver_parser_internal_filename_idx("././/.//.//./"); + CHECK_TRUE(idx == 11); + + idx = simple_archiver_parser_internal_filename_idx("././/.//.//."); + CHECK_TRUE(idx == 11); + + idx = simple_archiver_parser_internal_filename_idx("././/.//.//"); + CHECK_TRUE(idx == 8); + + SDArchiverParsed parsed = simple_archiver_create_parsed(); + simple_archiver_parse_args( + 4, + (const char *[]){"parser", "--", "././/././//./derp", "./doop", NULL}, + &parsed); + + CHECK_TRUE(strcmp("derp", parsed.working_files[0]) == 0); + CHECK_TRUE(strcmp("doop", parsed.working_files[1]) == 0); + CHECK_TRUE(parsed.working_files[2] == NULL); + CHECK_TRUE(parsed.filename == NULL); + CHECK_TRUE(parsed.flags == 0); + + simple_archiver_free_parsed(&parsed); + + parsed = simple_archiver_create_parsed(); + simple_archiver_parse_args( + 7, + (const char *[]){"parser", "-x", "-f", "the_filename", + "././/././//./.derp", "././//./_doop", + "./../../.prev_dir_file", NULL}, + &parsed); + + CHECK_TRUE(strcmp(".derp", parsed.working_files[0]) == 0); + CHECK_TRUE(strcmp("_doop", parsed.working_files[1]) == 0); + CHECK_TRUE(strcmp("../../.prev_dir_file", parsed.working_files[2]) == 0); + CHECK_TRUE(parsed.working_files[3] == NULL); + CHECK_TRUE(strcmp("the_filename", parsed.filename) == 0); + CHECK_TRUE(parsed.flags == 1); + + simple_archiver_free_parsed(&parsed); + } + + printf("Checks checked: %u\n", checks_checked); + printf("Checks passed: %u\n", checks_passed); + return checks_passed == checks_checked ? 0 : 1; +}