Skip to content

Commit 04f2dd6

Browse files
authored
Improve argument parsing (#14)
1 parent 04d74cb commit 04f2dd6

File tree

7 files changed

+588
-101
lines changed

7 files changed

+588
-101
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ if(TL_BUILD_TESTS)
8787
tl_config_test
8888
tl_debug_test
8989
tl_error_test
90+
tl_flag_test
9091
tl_test_test
9192
)
9293

include/tl_app.h

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
#ifndef TL_APP_H
44
#define TL_APP_H
55

6-
#include <stdbool.h>
7-
86
/**
97
* @brief Initializes the running app with the given command line arguments.
108
*
@@ -15,32 +13,4 @@
1513
*/
1614
void tl_init_app(int argc, char *argv[]);
1715

18-
/**
19-
* @brief Parses the given command line arguments.
20-
*
21-
* @param argc The number of command line arguments.
22-
* @param argv The command line arguments.
23-
*
24-
* @return void
25-
*/
26-
void tl_parse_args(int argc, char *argv[]);
27-
28-
/**
29-
* @brief Looks up a specific flag.
30-
*
31-
* @param flag The flag to look up.
32-
*
33-
* @return true if the flag is found, false otherwise.
34-
*/
35-
bool tl_lookup_flag(const char *flag);
36-
37-
/**
38-
* @brief Returns the value of a specific flag.
39-
*
40-
* @param flag The flag to get.
41-
*
42-
* @return The value of the flag, or NULL if not found.
43-
*/
44-
const char *tl_get_flag(const char *flag);
45-
4616
#endif // TL_APP_H

include/tl_flag.h

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// See LICENSE.txt and CONTRIBUTING.md for details.
2+
3+
#ifndef TL_FLAG_H
4+
#define TL_FLAG_H
5+
6+
#include <stdbool.h>
7+
#include <stddef.h>
8+
9+
/**
10+
* @brief Represents a parsed flag.
11+
*
12+
* `name` points into argv (or the tokenizer buffer) at the first '-' of
13+
* the flag. `name_len` is the length up to '\0' or '='. For the '=' form
14+
* `name` is NOT a NUL-terminated C string at name_len — name[name_len] is
15+
* '=', so comparisons must use memcmp with name_len, never strcmp.
16+
*
17+
* argv entry: "--foo=bar"
18+
* - - f o o = b a r \0
19+
* ^ ^
20+
* name value
21+
* name_len = 5
22+
* value = "bar"
23+
*
24+
* argv entry: "--foo" "bar"
25+
* - - f o o \0 b a r \0
26+
* ^ ^
27+
* name value
28+
* name_len = 5
29+
* value = "bar"
30+
*
31+
* argv entry: "--foo" (boolean, no value)
32+
* - - f o o \0
33+
* ^
34+
* name
35+
* name_len = 5
36+
* value = NULL
37+
*/
38+
typedef struct {
39+
const char *name; // points at the first '-' of the flag in argv
40+
size_t name_len; // length of the flag name up to '\0' or '='
41+
const char *value; // value after first '=', or NULL if none
42+
} tl_flag_t;
43+
44+
/**
45+
* @brief Parses the given command line arguments.
46+
*
47+
* Parses argv into flags. A flag is anything starting with "--". It can carry
48+
* a value written as --name=value, or as --name value in the next entry.
49+
* A bare "--" ends flag parsing; everything after it is a positional, even if
50+
* it starts with dashes. Any previously parsed state is thrown away first.
51+
*
52+
* @param argc The number of command line arguments.
53+
* @param argv The command line arguments.
54+
*
55+
* @return void
56+
*/
57+
void tl_parse_args(int argc, char *argv[]);
58+
59+
/**
60+
* @brief Parses a raw command line string.
61+
*
62+
* Splits the line into tokens. Double quotes group text with spaces into
63+
* one token, and a backslash keeps the next character as-is. The first
64+
* token is the program name and is skipped, like argv[0].
65+
*
66+
* @param line The command line string to parse.
67+
*
68+
* @return true on success, false otherwise.
69+
*/
70+
bool tl_parse_line(const char *line);
71+
72+
/**
73+
* @brief Releases memory held by the argument parser.
74+
*
75+
* Safe to call when nothing has been parsed. Called implicitly by
76+
* tl_parse_args and tl_parse_line.
77+
*
78+
* @return void
79+
*/
80+
void tl_free_args(void);
81+
82+
/**
83+
* @brief Looks up a specific flag.
84+
*
85+
* @param flag The flag to look up.
86+
*
87+
* @return true if the flag is found, false otherwise.
88+
*/
89+
bool tl_lookup_flag(const char *flag);
90+
91+
/**
92+
* @brief Returns the value of a specific flag.
93+
*
94+
* Returns the value of the first occurrence of flag. For repeated flags
95+
* use tl_count_flag and tl_get_flag_at.
96+
*
97+
* @param flag The flag to get.
98+
*
99+
* @return The value of the flag, or NULL if not found or no value.
100+
*/
101+
const char *tl_get_flag(const char *flag);
102+
103+
/**
104+
* @brief Returns the number of times a flag was given.
105+
*
106+
* @param flag The flag to count.
107+
*
108+
* @return The occurrence count (0 if not given).
109+
*/
110+
size_t tl_count_flag(const char *flag);
111+
112+
/**
113+
* @brief Returns the value of a repeated flag at a given index.
114+
*
115+
* Occurrences are indexed in the order they appeared on the command line.
116+
*
117+
* @param flag The flag to get.
118+
* @param index The occurrence index (0-based).
119+
*
120+
* @return The value, or NULL if out of range or no value at that index.
121+
*/
122+
const char *tl_get_flag_at(const char *flag, size_t index);
123+
124+
/**
125+
* @brief Returns the number of positional arguments.
126+
*
127+
* Positionals are bare arguments (not starting with `--`) and everything
128+
* after a bare `--` terminator, in the order they appeared.
129+
*
130+
* @return The positional argument count.
131+
*/
132+
size_t tl_count_positional(void);
133+
134+
/**
135+
* @brief Returns the positional argument at the given index.
136+
*
137+
* @param index The positional index (0-based).
138+
*
139+
* @return The positional value, or NULL if out of range.
140+
*/
141+
const char *tl_get_positional(size_t index);
142+
143+
#endif // TL_FLAG_H

src/tl_app.c

Lines changed: 1 addition & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,12 @@
22

33
#include "tl_app.h"
44
#include "tl_config.h"
5-
#include <stdio.h>
5+
#include "tl_flag.h"
66
#include <stdlib.h>
7-
#include <string.h>
8-
9-
// Init vars
10-
static int arg_count = 0;
11-
static char **args = NULL;
127

138
void tl_init_app(int argc, char *argv[]) {
149
tl_parse_args(argc, argv);
1510
if (tl_get_flag("--debug-level")) {
1611
tl_set_debug_level((int)strtol(tl_get_flag("--debug-level"), NULL, 10));
1712
}
1813
}
19-
20-
void tl_parse_args(int argc, char *argv[]) {
21-
arg_count = argc;
22-
args = argv;
23-
}
24-
25-
bool tl_lookup_flag(const char *flag) {
26-
for (int i = 1; i < arg_count; i++) {
27-
// If the argument starts with the flag and is followed by either '\0' or '=' then
28-
if (strncmp(args[i], flag, strlen(flag)) == 0 &&
29-
(args[i][strlen(flag)] == '\0' || args[i][strlen(flag)] == '=')) {
30-
return true;
31-
}
32-
}
33-
return false;
34-
}
35-
36-
const char *tl_get_flag(const char *flag) {
37-
size_t flag_len = strlen(flag);
38-
for (int i = 1; i < arg_count; i++) {
39-
if (strncmp(args[i], flag, flag_len) != 0) {
40-
continue;
41-
}
42-
// If the argument is followed by '=' then return the value after '='
43-
if (args[i][flag_len] == '=') {
44-
return args[i] + flag_len + 1;
45-
}
46-
// If the argument is an exact match and the next argument exists then return it
47-
if (args[i][flag_len] == '\0' && i + 1 < arg_count) {
48-
return args[i + 1];
49-
}
50-
}
51-
return NULL;
52-
}

0 commit comments

Comments
 (0)