Skip to content

Commit

Permalink
Implemented most of posix getlong_opt() (#279)
Browse files Browse the repository at this point in the history
x-platform implementation of posix getopt_long()
  • Loading branch information
JonathanHenson authored Mar 15, 2019
1 parent 83c132b commit 56e3549
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 0 deletions.
66 changes: 66 additions & 0 deletions include/aws/common/command_line_parser.h
Original file line number Diff line number Diff line change
@@ -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 <aws/common/common.h>

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 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).
*
* 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 */
120 changes: 120 additions & 0 deletions source/command_line_parser.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* 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 <aws/common/command_line_parser.h>

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->name) {
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 || option->val != 0) {
if (option->name) {
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;
}

aws_cli_optind++;
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) {
if (aws_cli_optind >= argc - 1) {
return '?';
}

aws_cli_optarg = argv[aws_cli_optind++];
}

return option->val;
}

return '?';
}
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
163 changes: 163 additions & 0 deletions tests/command_line_parser_test.c
Original file line number Diff line number Diff line change
@@ -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 <aws/common/command_line_parser.h>
#include <aws/testing/aws_test_harness.h>

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)

0 comments on commit 56e3549

Please sign in to comment.