From 552f1a66ac092bf3e957fa51803aa1049f07b7bf Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Thu, 14 Mar 2019 17:25:47 -0700 Subject: [PATCH 1/5] Implemented most of getlong_opt. --- include/aws/common/command_line_parser.h | 66 +++++++++ source/command_line_parser.c | 121 +++++++++++++++++ tests/CMakeLists.txt | 5 + tests/command_line_parser_test.c | 163 +++++++++++++++++++++++ 4 files changed, 355 insertions(+) create mode 100644 include/aws/common/command_line_parser.h create mode 100644 source/command_line_parser.c create mode 100644 tests/command_line_parser_test.c diff --git a/include/aws/common/command_line_parser.h b/include/aws/common/command_line_parser.h new file mode 100644 index 000000000..7dee39bf3 --- /dev/null +++ b/include/aws/common/command_line_parser.h @@ -0,0 +1,66 @@ +#ifndef AWS_COMMAND_LINE_PARSER_H +#define AWS_COMMAND_LINE_PARSER_H +/* + * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ +#include + +enum aws_cli_options_has_arg { + AWS_CLI_OPTIONS_NO_ARGUMENT = 0, + AWS_CLI_OPTIONS_REQUIRED_ARGUMENT = 1, + AWS_CLI_OPTIONS_OPTIONAL_ARGUMENT = 2, +}; + +struct aws_cli_option { + const char *name; + enum aws_cli_options_has_arg has_arg; + int *flag; + int val; +}; + +/** + * Initialized to 1 (for where the first argument would be). As arguments are parsed, this number is the index + * of the next argument to parse. Reset this to 1 to parse another set of arguments, or to rerun the parser. + */ +AWS_COMMON_API extern int aws_cli_optind; + +/** + * If an option has an argument, when the option is encountered, this will be set to the argument portion. + */ +AWS_COMMON_API extern const char *aws_cli_optarg; + +AWS_EXTERN_C_BEGIN +/** + * A mostly compliant implementation of posix getopt_long(). Parses command-line arguments. argc is the number of + * command line arguments passed in argv. optstring contains the legitamate option characters. The option characters + * coorespond to aws_cli_option::val. If the character is followed by a :, the option requires an argument. If it is + * followed by '::', the argument is optional (not implemented yet). + * + * longopts, is an array of struct aws_cli_option. These are the allowed options for the program. + * The last member of the array must be zero initialized. + * + * If longindex is non-null, it will be set to the index in longopts, for the found option. + * + * Returns option val if it was found, '?' if an option was encountered that was not specified in the option string, + * returns -1 when all arguments that can be parsed have been parsed. + */ +AWS_COMMON_API int aws_cli_getopt_long( + int argc, + char *const argv[], + const char *optstring, + const struct aws_cli_option *longopts, + int *longindex); +AWS_EXTERN_C_END + +#endif /* AWS_COMMAND_LINE_PARSER_H */ \ No newline at end of file diff --git a/source/command_line_parser.c b/source/command_line_parser.c new file mode 100644 index 000000000..1635685c8 --- /dev/null +++ b/source/command_line_parser.c @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ +#include + +int aws_cli_optind = 1; +int aws_cli_opterr = -1; +int aws_cli_optopt = 0; + +const char *aws_cli_optarg = NULL; + +static const struct aws_cli_option *s_find_option_from_char( + const struct aws_cli_option *longopts, + char search_for, + int *longindex) { + int index = 0; + const struct aws_cli_option *option = &longopts[index]; + + while (option->val != 0 && option->val != 0) { + if (option->val == search_for) { + if (longindex) { + *longindex = index; + } + return option; + } + + option = &longopts[++index]; + } + + return NULL; +} + +static const struct aws_cli_option *s_find_option_from_c_str( + const struct aws_cli_option *longopts, + const char *search_for, + int *longindex) { + int index = 0; + const struct aws_cli_option *option = &longopts[index]; + + while (option->name != NULL && option->val != 0) { + if (option->name && !strcmp(search_for, option->name)) { + if (longindex) { + *longindex = index; + } + return option; + } + + option = &longopts[++index]; + } + + return NULL; +} + +int aws_cli_getopt_long( + int argc, + char *const argv[], + const char *optstring, + const struct aws_cli_option *longopts, + int *longindex) { + aws_cli_optarg = NULL; + + if (aws_cli_optind == argc) { + return -1; + } + + char first_char = argv[aws_cli_optind][0]; + char second_char = argv[aws_cli_optind][1]; + char *option_start = NULL; + const struct aws_cli_option *option = NULL; + + if (first_char == '-' && second_char != '-') { + option_start = &argv[aws_cli_optind][1]; + option = s_find_option_from_char(longopts, *option_start, longindex); + } else if (first_char == '-' && second_char == '-') { + option_start = &argv[aws_cli_optind][2]; + option = s_find_option_from_c_str(longopts, option_start, longindex); + } else { + return -1; + } + + if (option) { + bool has_arg = false; + if (option) { + char *opt_value = memchr(optstring, option->val, strlen(optstring)); + if (!opt_value) { + return '?'; + } + + if (opt_value[1] == ':') { + has_arg = true; + } + } + + if (has_arg) { + aws_cli_optind += 1; + if (aws_cli_optind >= argc - 1) { + return '?'; + } + } + + if (aws_cli_optind < argc) { + aws_cli_optarg = argv[aws_cli_optind++]; + } + + return option->val; + } + + aws_cli_optind++; + return '?'; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 757abe222..bc8f747a0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -264,6 +264,11 @@ add_test_case(uuid_string_parse_malformed) add_test_case(test_environment_functions) +add_test_case(short_argument_parse) +add_test_case(long_argument_parse) +add_test_case(unqualified_argument_parse) +add_test_case(unknown_argument_parse) + generate_test_driver(${CMAKE_PROJECT_NAME}-tests) if (NOT MSVC) diff --git a/tests/command_line_parser_test.c b/tests/command_line_parser_test.c new file mode 100644 index 000000000..b0dad54ee --- /dev/null +++ b/tests/command_line_parser_test.c @@ -0,0 +1,163 @@ +/* + * Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file 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. + */ +#include +#include + +static int s_test_short_argument_parse_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + (void)ctx; + struct aws_cli_option options[] = { + {.name = NULL, .has_arg = AWS_CLI_OPTIONS_NO_ARGUMENT, .flag = NULL, .val = 'a'}, + {.name = "beeee", .has_arg = AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, .flag = NULL, .val = 'b'}, + {.name = NULL, .has_arg = AWS_CLI_OPTIONS_OPTIONAL_ARGUMENT, .flag = NULL, .val = 'c'}, + {.name = NULL, .has_arg = 0, .flag = NULL, .val = 0}, + }; + + char *const args[] = { + "prog-name", + "-a", + "-b", + "bval", + "-c", + }; + int argc = 5; + int longindex = 0; + int arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('a', arg); + ASSERT_INT_EQUALS(0, longindex); + ASSERT_INT_EQUALS(2, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('b', arg); + ASSERT_STR_EQUALS("bval", aws_cli_optarg); + ASSERT_INT_EQUALS(1, longindex); + ASSERT_INT_EQUALS(4, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('c', arg); + ASSERT_INT_EQUALS(2, longindex); + ASSERT_INT_EQUALS(-1, aws_cli_getopt_long(argc, args, "ab:c", options, &longindex)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(short_argument_parse, s_test_short_argument_parse_fn) + +static int s_test_long_argument_parse_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + (void)ctx; + struct aws_cli_option options[] = { + {.name = "aaee", .has_arg = AWS_CLI_OPTIONS_NO_ARGUMENT, .flag = NULL, .val = 'a'}, + {.name = "beeee", .has_arg = AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, .flag = NULL, .val = 'b'}, + {.name = "cceeee", .has_arg = AWS_CLI_OPTIONS_OPTIONAL_ARGUMENT, .flag = NULL, .val = 'c'}, + {.name = NULL, .has_arg = 0, .flag = NULL, .val = 0}, + }; + + char *const args[] = { + "prog-name", + "--aaee", + "--beeee", + "bval", + "-cceeee", + }; + int argc = 5; + int longindex = 0; + int arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('a', arg); + ASSERT_INT_EQUALS(0, longindex); + ASSERT_INT_EQUALS(2, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('b', arg); + ASSERT_STR_EQUALS("bval", aws_cli_optarg); + ASSERT_INT_EQUALS(1, longindex); + ASSERT_INT_EQUALS(4, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('c', arg); + ASSERT_INT_EQUALS(2, longindex); + + ASSERT_INT_EQUALS(-1, aws_cli_getopt_long(argc, args, "ab:c", options, &longindex)); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(long_argument_parse, s_test_long_argument_parse_fn) + +static int s_test_unqualified_argument_parse_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + (void)ctx; + struct aws_cli_option options[] = { + {.name = "aaee", .has_arg = AWS_CLI_OPTIONS_NO_ARGUMENT, .flag = NULL, .val = 'a'}, + {.name = "beeee", .has_arg = AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, .flag = NULL, .val = 'b'}, + {.name = "cceeee", .has_arg = AWS_CLI_OPTIONS_OPTIONAL_ARGUMENT, .flag = NULL, .val = 'c'}, + {.name = NULL, .has_arg = 0, .flag = NULL, .val = 0}, + }; + + char *const args[] = {"prog-name", "-a", "--beeee", "bval", "-c", "operand"}; + int argc = 6; + int longindex = 0; + int arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('a', arg); + ASSERT_INT_EQUALS(0, longindex); + ASSERT_INT_EQUALS(2, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('b', arg); + ASSERT_STR_EQUALS("bval", aws_cli_optarg); + ASSERT_INT_EQUALS(1, longindex); + ASSERT_INT_EQUALS(4, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('c', arg); + ASSERT_INT_EQUALS(2, longindex); + + ASSERT_INT_EQUALS(-1, aws_cli_getopt_long(argc, args, "ab:c", options, &longindex)); + ASSERT_TRUE(aws_cli_optind < argc); + ASSERT_STR_EQUALS("operand", args[aws_cli_optind++]); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(unqualified_argument_parse, s_test_unqualified_argument_parse_fn) + +static int s_test_unknown_argument_parse_fn(struct aws_allocator *allocator, void *ctx) { + (void)allocator; + (void)ctx; + struct aws_cli_option options[] = { + {.name = "aaee", .has_arg = AWS_CLI_OPTIONS_NO_ARGUMENT, .flag = NULL, .val = 'a'}, + {.name = "beeee", .has_arg = AWS_CLI_OPTIONS_REQUIRED_ARGUMENT, .flag = NULL, .val = 'b'}, + {.name = "cceeee", .has_arg = AWS_CLI_OPTIONS_OPTIONAL_ARGUMENT, .flag = NULL, .val = 'c'}, + {.name = NULL, .has_arg = 0, .flag = NULL, .val = 0}, + }; + + char *const args[] = {"prog-name", "-BOO!", "--beeee", "bval", "-c", "operand"}; + int argc = 6; + int longindex = 0; + int arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('?', arg); + ASSERT_INT_EQUALS(0, longindex); + ASSERT_INT_EQUALS(2, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('b', arg); + ASSERT_STR_EQUALS("bval", aws_cli_optarg); + ASSERT_INT_EQUALS(1, longindex); + ASSERT_INT_EQUALS(4, aws_cli_optind); + arg = aws_cli_getopt_long(argc, args, "ab:c", options, &longindex); + ASSERT_INT_EQUALS('c', arg); + ASSERT_INT_EQUALS(2, longindex); + + ASSERT_INT_EQUALS(-1, aws_cli_getopt_long(argc, args, "ab:c", options, &longindex)); + ASSERT_TRUE(aws_cli_optind < argc); + ASSERT_STR_EQUALS("operand", args[aws_cli_optind++]); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(unknown_argument_parse, s_test_unknown_argument_parse_fn) From d6665fd3334718c3fa075792d97e1b5412a50ab6 Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Thu, 14 Mar 2019 17:29:45 -0700 Subject: [PATCH 2/5] Added newline. --- include/aws/common/command_line_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/aws/common/command_line_parser.h b/include/aws/common/command_line_parser.h index 7dee39bf3..a609fe666 100644 --- a/include/aws/common/command_line_parser.h +++ b/include/aws/common/command_line_parser.h @@ -63,4 +63,4 @@ AWS_COMMON_API int aws_cli_getopt_long( int *longindex); AWS_EXTERN_C_END -#endif /* AWS_COMMAND_LINE_PARSER_H */ \ No newline at end of file +#endif /* AWS_COMMAND_LINE_PARSER_H */ From 406568069c9d5ef1ac8e74b9080e951f93d80db1 Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Thu, 14 Mar 2019 17:33:17 -0700 Subject: [PATCH 3/5] Fix spelling error in comment. --- include/aws/common/command_line_parser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/aws/common/command_line_parser.h b/include/aws/common/command_line_parser.h index a609fe666..ca3a267e5 100644 --- a/include/aws/common/command_line_parser.h +++ b/include/aws/common/command_line_parser.h @@ -43,7 +43,7 @@ AWS_COMMON_API extern const char *aws_cli_optarg; AWS_EXTERN_C_BEGIN /** * A mostly compliant implementation of posix getopt_long(). Parses command-line arguments. argc is the number of - * command line arguments passed in argv. optstring contains the legitamate option characters. The option characters + * command line arguments passed in argv. optstring contains the legitimate option characters. The option characters * coorespond to aws_cli_option::val. If the character is followed by a :, the option requires an argument. If it is * followed by '::', the argument is optional (not implemented yet). * From c56e34efa6a3e8e9ec709564b4358f35cb40a3bb Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Thu, 14 Mar 2019 17:51:57 -0700 Subject: [PATCH 4/5] Addressed PR feedback. --- source/command_line_parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/command_line_parser.c b/source/command_line_parser.c index 1635685c8..2e92d6ffc 100644 --- a/source/command_line_parser.c +++ b/source/command_line_parser.c @@ -27,7 +27,7 @@ static const struct aws_cli_option *s_find_option_from_char( int index = 0; const struct aws_cli_option *option = &longopts[index]; - while (option->val != 0 && option->val != 0) { + while (option->val != 0 && option->name != 0) { if (option->val == search_for) { if (longindex) { *longindex = index; @@ -70,7 +70,7 @@ int aws_cli_getopt_long( int *longindex) { aws_cli_optarg = NULL; - if (aws_cli_optind == argc) { + if (aws_cli_optind >= argc) { return -1; } From 9a6aa6f5a82885898c2db41508b9316c727246a6 Mon Sep 17 00:00:00 2001 From: "Jonathan M. Henson" Date: Thu, 14 Mar 2019 18:02:55 -0700 Subject: [PATCH 5/5] Fixed bug caused by clang-tidy fix. --- source/command_line_parser.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/source/command_line_parser.c b/source/command_line_parser.c index 2e92d6ffc..26492b7cb 100644 --- a/source/command_line_parser.c +++ b/source/command_line_parser.c @@ -27,7 +27,7 @@ static const struct aws_cli_option *s_find_option_from_char( int index = 0; const struct aws_cli_option *option = &longopts[index]; - while (option->val != 0 && option->name != 0) { + while (option->val != 0 || option->name) { if (option->val == search_for) { if (longindex) { *longindex = index; @@ -48,12 +48,14 @@ static const struct aws_cli_option *s_find_option_from_c_str( int index = 0; const struct aws_cli_option *option = &longopts[index]; - while (option->name != NULL && option->val != 0) { - if (option->name && !strcmp(search_for, option->name)) { - if (longindex) { - *longindex = index; + while (option->name || option->val != 0) { + if (option->name) { + if (option->name && !strcmp(search_for, option->name)) { + if (longindex) { + *longindex = index; + } + return option; } - return option; } option = &longopts[++index]; @@ -89,6 +91,7 @@ int aws_cli_getopt_long( return -1; } + aws_cli_optind++; if (option) { bool has_arg = false; if (option) { @@ -103,19 +106,15 @@ int aws_cli_getopt_long( } if (has_arg) { - aws_cli_optind += 1; if (aws_cli_optind >= argc - 1) { return '?'; } - } - if (aws_cli_optind < argc) { aws_cli_optarg = argv[aws_cli_optind++]; } return option->val; } - aws_cli_optind++; return '?'; }