From 106eb4d50f97eb4dbe11949c2109294eb78d32f8 Mon Sep 17 00:00:00 2001 From: Marco Michele Mosca Date: Tue, 1 Jul 2025 17:13:16 +0100 Subject: [PATCH 1/3] Debug build in CI --- .github/workflows/run_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 4eef2bb..1b8b0e6 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -11,7 +11,7 @@ on: - devel env: - BUILD_TYPE: Release + BUILD_TYPE: Debug jobs: build: @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v3 - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -S . -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/install + run: cmake -B ${{github.workspace}}/build -S . -L -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/install - name: Build run: cmake --build ${{github.workspace}}/build --target install --config ${{env.BUILD_TYPE}} From aac4534c1736c47f6831ee4f1abd7bb221b35e34 Mon Sep 17 00:00:00 2001 From: Marco Michele Mosca Date: Wed, 2 Jul 2025 17:10:57 +0100 Subject: [PATCH 2/3] Make current option and argument available in a struct --- cmd-api/include/cmd-api.h | 73 +++++++-- cmd-api/src/cmd-api.c | 174 +++++++--------------- tests/longopt_basic.c | 16 +- tests/longopt_format_missedbar_error.c | 4 +- tests/longopt_format_missedendbar_error.c | 4 +- tests/longopt_missedarg_error.c | 4 +- tests/longopt_noargexpected_error.c | 4 +- tests/longopt_nodash_error.c | 4 +- tests/longopt_shortopt.c | 13 +- tests/longopt_unknownopt_error.c | 4 +- 10 files changed, 137 insertions(+), 163 deletions(-) diff --git a/cmd-api/include/cmd-api.h b/cmd-api/include/cmd-api.h index 7310676..2da88ba 100644 --- a/cmd-api/include/cmd-api.h +++ b/cmd-api/include/cmd-api.h @@ -17,25 +17,72 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com #ifndef CMD_H_ #define CMD_H_ -#ifdef DEBUG -#define PRINT_DATA() printf("CURRENT ---- OPTIND: %d ---- ARGVIND: %d ---- OPTION: %c ---- OPTARG: %s\n", optind, argvind, argv[optind][1], myoptarg); -#define PRINT_DATAW() printf("CURRENT ---- OPTIND: %d ---- ARGVIND: %d ---- OPTION: %s ---- OPTARG: %s\n", optind, argvind, argv[optind], myoptarg); -#else -#define PRINT_DATA() {}; -#define PRINT_DATAW() {}; -#endif - #include #include #include -static int optind = 1, argvind = 2, formatind = -1, argformatind = 0; -static char *optargW = NULL, *curr_option; +/** + * Struct for command line parser + * - opt -- Current option detected + * - arg -- Current option's argument + */ +typedef struct optarg { + char *opt; + char *arg; +} optarg_t; + +/** + * Set of variables for cmd-api-c + * - optind -- Index of the option. + * Starts with the first word after the executable. + */ +static int optind = 1; -int isCharInString(char c, char* str); +/** + * Find the shortest string among s1 and s2 in the other. + \param s1 string 1 + \param s2 string 2 + \param from index of the longest string where to start + comparing the shortest string. + + \retval 1 -- both strings are equal or the shortest is contained in the longest + \retval 0 -- the shortest string is not contained in the longest*/ int AreStringsEqualFrom(const char* s1, const char* s2, int from); + +/** + \param sub substring to search for in str + \param str string that is searched for substring sub + + \retval i -- index of str where sub substring starts + \retval -1 -- no substring found in str*/ int isSubstring(char* sub, char* str); -char* strsep(char** elem_pointer, char* pattern); -char* getoptW(int argc, char** argv, char* format); + +/** Cycles on the command line parameters and arguments + and returns the current option and its argument. + This function should be used in a while loop and + the returned struct should be freed after each call. + Usage: + while ((optarg = getoptW(argc, argv, "opt1:|opt2:|")) != NULL) { + if (strcmp(optarg->opt, "opt1") == 0) { + printf("%s\n", optarg->arg); + continue; + } + if (strcmp(optarg->opt, "opt2") == 0) { + printf("%s\n", optarg->arg); + continue; + } + free(optarg); + } + \param argc number of command line items (related to main argc) + \param argv array of command line words (related to main argv) + \param format format string, which contains the option name + followed by special characters to specify whether...: + ':' -> ...the option expects an argument + '|' -> ...the end of the option + + \retval optarg -- struct with current option and argument. + Needs deallocation after each getoptW call. + \retval NULL -- end of command line */ +optarg_t* getoptW(int argc, char** argv, char* format); #endif /* CMD_H_ */ diff --git a/cmd-api/src/cmd-api.c b/cmd-api/src/cmd-api.c index 54e0182..1aff25a 100644 --- a/cmd-api/src/cmd-api.c +++ b/cmd-api/src/cmd-api.c @@ -16,19 +16,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com */ #include "cmd-api.h" - -int isCharInString(char c, char* str) { - int i = 0, len=strlen(str); - while (i < len) { - if (str[i] == c) return i;/* return the index */ - i++; - } - return -1; /*not found*/ -} - int AreStringsEqualFrom(const char* s1, const char* s2,int from) { int len1, len2, i=0, lensub, lenbig; - const char* bigger, *smaller; + const char *bigger, *smaller; if ((s1 == NULL) || (s2 == NULL)) return 0; @@ -63,7 +53,7 @@ int isSubstring(char* sub, char* str) { int sublen= strlen(sub), len = strlen(str), i=0; while (i <= len-sublen) { - if (AreStringsEqualFrom(sub, str,0)) { + if (AreStringsEqualFrom(sub, str, 0)) { return i; } i++; @@ -73,130 +63,66 @@ int isSubstring(char* sub, char* str) { return -1; } -char* strsep(char** elem_pointer, char* pattern) { - char* current_tkn, *p; - int i = 0, j, len; - - if (*elem_pointer == NULL || pattern == NULL) exit(EXIT_FAILURE); - len = strlen(*elem_pointer); /*len of the entire string*/ - p = *elem_pointer; - while ((isCharInString(p[i], pattern) == -1) && (i < len)) { - i++; - } - len = i; /*len of the first token*/ - current_tkn = (char*) malloc(len * sizeof(char)+1); - - j = 0; - for (i = 0; i < len; i++) { - if (p[i] != ' ') { - current_tkn[j] = p[i]; - j++; - } - } - current_tkn[j] = '\0'; +optarg_t* getoptW(int argc, char** argv, char* format) { + /** + * - argvind -- Index of the option's argument. + * Start with the second word after the executable. + * - formatind -- Index of the option in the format string + * - argformatind -- Starting index of the argument types in the format string + */ + int argvind, formatind, argformatind; + optarg_t* optarg = (optarg_t*)malloc(sizeof(optarg_t)); - *elem_pointer += (len+1); - return current_tkn; -} - -char* getoptW(int argc, char** argv, char* format) { - - /*if optind is one means that there are no more options*/ if (optind == -1) { return NULL; } - - /*if optind goes over the number of argument commandline*/ if (optind > argc - 1) { - PRINT_DATAW(); puts("Error in parameters number!"); - return NULL; + exit(EXIT_FAILURE); } if (argv[optind][0] != '-') { - PRINT_DATAW(); - puts("It is not an option!"); + puts("It is not an option: missing dash"); + exit(EXIT_FAILURE); + } + optarg->opt = ++argv[optind]; + if ((formatind = isSubstring(optarg->opt, format)) == -1) { + optarg->arg = NULL; + puts("Unknown option!"); exit(EXIT_FAILURE); } - curr_option = ++argv[optind]; - if ((formatind = isSubstring(argv[optind], format)) >= 0) { - argformatind = formatind + strlen(argv[optind]); - /*Format substring is not at the end of the format line*/ - if (argformatind < (int)strlen(format)) { - argvind = optind + 1; - /*if the argument index (argvind) in the array is not out of bounds*/ - if (argvind <= argc - 1) { - /*if there is a ':' in format string, argument is expected after*/ - if (format[argformatind] == ':') { - /*argument expected, update the optargW*/ - if (argv[argvind][0] != '-') { - optargW = argv[argvind]; - PRINT_DATAW(); - optind += 2; - } - /*there is '-' in the argument*/ - else - { - optargW = argv[argvind]; - PRINT_DATAW(); - puts("This option should have a parameter!"); - exit(EXIT_FAILURE); - } - } - /*No argument expected for optind option*/ - else { - - /*check forward if there is no option*/ - if (argv[argvind][0] != '-') { - optargW = argv[argvind]; - PRINT_DATAW(); - puts("This option must not have a parameter!"); - exit(EXIT_FAILURE); - } - - if (format[argformatind] != '|') { - PRINT_DATAW(); - puts("Format and input are not valid!"); - exit(EXIT_FAILURE); - } - optargW = NULL; - PRINT_DATAW(); - optind++; - } - } - /*if the argument index (argvind) in the array is out of bounds*/ - else { - /*No arguments expected in format*/ - if (format[argformatind] != '|') { - PRINT_DATAW(); - puts("Format and input are not valid!"); - exit(EXIT_FAILURE); - } - optargW = NULL; - PRINT_DATAW(); - optind++; - } - - } - /*Format substring is at the end of the format line*/ - else { - if (format[argformatind] != '|') { - PRINT_DATAW(); - puts("Format and input are not valid!"); - exit(EXIT_FAILURE); - } - optargW = NULL; - PRINT_DATAW(); - optind++; - } - - if (optind > argc - 1) optind = -1; - return curr_option; + argvind = optind + 1; + argformatind = formatind + strlen(optarg->opt); + if ((argvind > argc - 1) && (format[argformatind] != '|')) { + puts("Missing end bar for the last option in format string"); + exit(EXIT_FAILURE); } - else { - optargW = NULL; - PRINT_DATAW(); - puts("Unknown option!"); + if (argformatind >= (int)strlen(format)) { + puts("Format string is wrong at the end"); + exit(EXIT_FAILURE); + } + if ((format[argformatind] == ':') && (argv[argvind][0] == '-')) { + puts("Argument expected when : is in format string"); + exit(EXIT_FAILURE); + } + if ((format[argformatind] != ':') && (argv[argvind][0] != '-')) { + puts("Argument not expected when : is not in format string"); + exit(EXIT_FAILURE); + } + if (format[argformatind] == ':') { + optarg->arg = argv[argvind]; + optind += 2; + ++argformatind; + } else { + optarg->arg = NULL; + ++optind; + } + + if (format[argformatind] != '|') { + puts("Missing end bar in option format"); exit(EXIT_FAILURE); } + + if (optind > argc - 1) optind = -1; + return optarg; } diff --git a/tests/longopt_basic.c b/tests/longopt_basic.c index bb6a292..d621577 100644 --- a/tests/longopt_basic.c +++ b/tests/longopt_basic.c @@ -15,24 +15,24 @@ along with this program. If not, see https://www.gnu.org/licenses/. Author Marco M. Mosca, email: marcomichele.mosca@gmail.com */ #include "cmd-api.h" -#include "string.h" #include "assert.h" void test_longopt_basic() { - char* arr[] = {"exe", "-opt1", "val1", "-opt2", "val2"}; - char* w; + char* arr[] = {"exe", "-opt1", "val1", "-opt2", "val2"}; + optarg_t* optarg; - while ((w = getoptW(5, arr, "opt1:|opt2:|")) != NULL) { - if (strcmp(w, "opt1") == 0) { - assert(strcmp(optargW, "val1") == 0); + while ((optarg = getoptW(5, arr, "opt1:|opt2:|")) != NULL) { + if (strcmp(optarg->opt, "opt1") == 0) { + assert(strcmp(optarg->arg, "val1") == 0); continue; } - if (strcmp(w, "opt2") == 0) { - assert(strcmp(optargW, "val2") == 0); + if (strcmp(optarg->opt, "opt2") == 0) { + assert(strcmp(optarg->arg, "val2") == 0); continue; } + free(optarg); } } diff --git a/tests/longopt_format_missedbar_error.c b/tests/longopt_format_missedbar_error.c index a956fda..749021f 100644 --- a/tests/longopt_format_missedbar_error.c +++ b/tests/longopt_format_missedbar_error.c @@ -20,9 +20,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_format_missedbar_error() { char* arr[] = {"exe", "-opt1", "-opt2", "val2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(5, arr, "opt1opt2:|")) != NULL); + while ((optarg = getoptW(5, arr, "opt1opt2:|")) != NULL); } int main() diff --git a/tests/longopt_format_missedendbar_error.c b/tests/longopt_format_missedendbar_error.c index 7465476..c5631f9 100644 --- a/tests/longopt_format_missedendbar_error.c +++ b/tests/longopt_format_missedendbar_error.c @@ -20,9 +20,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_format_missedendbar_error() { char* arr[] = {"exe", "-opt1", "val1", "-opt2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(4, arr, "opt1:|opt2")) != NULL); + while ((optarg = getoptW(4, arr, "opt1:|opt2")) != NULL); } int main() diff --git a/tests/longopt_missedarg_error.c b/tests/longopt_missedarg_error.c index d158f42..bd58530 100644 --- a/tests/longopt_missedarg_error.c +++ b/tests/longopt_missedarg_error.c @@ -20,9 +20,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_missedarg_error() { char* arr[] = {"exe", "-opt1", "-opt2", "val2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(4, arr, "opt1:|opt2:|")) != NULL); + while ((optarg = getoptW(4, arr, "opt1:|opt2:|")) != NULL); } int main() diff --git a/tests/longopt_noargexpected_error.c b/tests/longopt_noargexpected_error.c index 0de1b66..7dbf413 100644 --- a/tests/longopt_noargexpected_error.c +++ b/tests/longopt_noargexpected_error.c @@ -20,9 +20,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_noargexpected_error() { char* arr[] = {"exe", "-opt1", "val1", "-opt2", "val2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(5, arr, "opt1|opt2:|")) != NULL); + while ((optarg = getoptW(5, arr, "opt1|opt2:|")) != NULL); } int main() diff --git a/tests/longopt_nodash_error.c b/tests/longopt_nodash_error.c index b4c694d..86ce96b 100644 --- a/tests/longopt_nodash_error.c +++ b/tests/longopt_nodash_error.c @@ -20,9 +20,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_nodash_error() { char* arr[] = {"exe", "-opt1", "val1", "opt2", "val2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(5, arr, "opt1:|opt2:|")) != NULL); + while ((optarg = getoptW(5, arr, "opt1:|opt2:|")) != NULL); } int main() diff --git a/tests/longopt_shortopt.c b/tests/longopt_shortopt.c index 8e52cd2..3e1ba8c 100644 --- a/tests/longopt_shortopt.c +++ b/tests/longopt_shortopt.c @@ -21,17 +21,18 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_shortopt_error() { char* arr[] = {"exe", "-o", "val1", "-opt2", "val2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(5, arr, "o:|opt2:|")) != NULL) { - if (strcmp(w, "o") == 0) { - assert(strcmp(optargW, "val1") == 0); + while ((optarg = getoptW(5, arr, "o:|opt2:|")) != NULL) { + if (strcmp(optarg->opt, "o") == 0) { + assert(strcmp(optarg->arg, "val1") == 0); continue; } - if (strcmp(w, "opt2") == 0) { - assert(strcmp(optargW, "val2") == 0); + if (strcmp(optarg->opt, "opt2") == 0) { + assert(strcmp(optarg->arg, "val2") == 0); continue; } + free(optarg); } } diff --git a/tests/longopt_unknownopt_error.c b/tests/longopt_unknownopt_error.c index e19238b..a559c31 100644 --- a/tests/longopt_unknownopt_error.c +++ b/tests/longopt_unknownopt_error.c @@ -20,9 +20,9 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com void test_longopt_format_error() { char* arr[] = {"exe", "-notaopt", "val1", "-opt2", "val2"}; - char* w; + optarg_t* optarg; - while ((w = getoptW(5, arr, "opt1:|opt2:|")) != NULL); + while ((optarg = getoptW(5, arr, "opt1:|opt2:|")) != NULL); } int main() From a3b53134937a5c5a3eca3e386b179bd050fffae3 Mon Sep 17 00:00:00 2001 From: Marco Michele Mosca Date: Wed, 2 Jul 2025 17:17:16 +0100 Subject: [PATCH 3/3] Update version 0.2.0 --- CHANGELOG | 6 ++++++ CMakeLists.txt | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index de666fb..11e7d3a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,11 @@ All changes to the repository will be reported here +## [0.2.0] +### Changed +- Debug build type in CI +### Added +- Struct for opt and arg + ## [0.1.2] ### Changed - Move static variables in header diff --git a/CMakeLists.txt b/CMakeLists.txt index 942db46..cfb4217 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ Author Marco M. Mosca, email: marcomichele.mosca@gmail.com ]] cmake_minimum_required(VERSION 3.5.0) -project(cmd-api VERSION 0.1.2 LANGUAGES C) +project(cmd-api VERSION 0.2.0 LANGUAGES C) include(CTest)