Skip to content

Commit d8401f4

Browse files
Strengthened support for argument parser on std::filesystem::path
1 parent a95b527 commit d8401f4

15 files changed

+326
-94
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ This project uses git submodules so it is required to clone it using the *--recu
7979

8080
```bash
8181
git clone --recurse-submodules https://github.com/TeiaCare/TeiaCareSDK.git
82+
git submodule update --init
8283
```
8384

8485
### Create Development Environment

sdk/examples/src/example_argparse.cpp

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -46,54 +46,69 @@ std::string to_string(const std::vector<T>& v)
4646

4747
int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
4848
{
49-
tc::sdk::argument_parser parser("SDK argparse example", "v1.0.0", "This is the program description");
49+
tc::sdk::argument_parser parser("example_argparse", "v1.0.0", "This is the program description of SDK argparse example");
5050

5151
std::string output_file;
52-
parser.add_positional("output_file", output_file, "The output file");
52+
// parser.add_positional("output_file", output_file, "The output file");
5353

5454
std::string input_file;
55-
parser.add_positional("input_file", input_file, "The input file");
55+
// parser.add_positional("input_file", input_file, "The input file");
56+
57+
int simple_option;
58+
parser.add_option("simple_option", simple_option);
59+
60+
bool simple_flag;
61+
parser.add_flag("simple_flag", simple_flag);
5662

5763
int iterations;
58-
parser.add_option("iterations", "i", iterations, 10, "Iterations count", false, "TC_ITERATIONS");
64+
parser.add_option("iterations", 'i', iterations, 10, "Iterations count", false, "TC_ITERATIONS");
5965

6066
float threshold;
61-
parser.add_option("threshold", "t", threshold, 0.5f, "Threshold");
67+
parser.add_option("threshold", 't', threshold, 0.5f, "Threshold");
6268

6369
std::vector<int> the_list;
64-
parser.add_option("list", "l", the_list, std::vector<int>{1, 2, 3}, "List");
70+
parser.add_option("list", 'l', the_list, std::vector<int>{1, 2, 3}, "List");
6571

6672
bool verbose;
67-
parser.add_flag("verbose", "x", verbose, "Enable verbose output", "TC_VERBOSE");
73+
parser.add_flag("verbose", 'x', verbose, "Enable verbose output", "TC_VERBOSE");
6874

6975
std::string str_opt;
70-
parser.add_option(std::make_unique<tc::sdk::optional_argument<std::string>>("str_opt", "s", str_opt, "default_value", "Threshold"));
76+
parser.add_option(std::make_unique<tc::sdk::optional_argument<std::string>>("str_opt", 's', str_opt, "default_value", "Threshold"));
7177

7278
bool flag;
73-
auto flag_arg = std::make_unique<tc::sdk::flag_argument>("flag", "f", flag, "Some random flag", "TC_FLAG");
79+
auto flag_arg = std::make_unique<tc::sdk::flag_argument>("flag", 'f', flag, "Some random flag", "TC_FLAG");
7480
parser.add_flag(std::move(flag_arg));
7581

7682
double double_opt;
77-
auto double_arg = std::make_unique<tc::sdk::optional_argument<double>>("double", "d", double_opt, 5.6798, "Double argument", true);
78-
double_arg->set_validator([](const double& value) {
79-
return value > 10.0;
83+
// auto double_arg = std::make_unique<tc::sdk::optional_argument<double>>("double", 'd', double_opt, 5.6798, "Double argument", true);
84+
// double_arg->set_validator([](const double& value) {
85+
// return value > 10.0;
86+
// });
87+
// parser.add_option(std::move(double_arg));
88+
89+
std::filesystem::path unix_path{"some/path/to/file.txt"};
90+
auto unix_path_arg = std::make_unique<tc::sdk::optional_argument<std::filesystem::path>>("unix_path", unix_path, unix_path, "Path to file");
91+
unix_path_arg->set_validator([](const std::filesystem::path& p) {
92+
return std::filesystem::exists(p);
8093
});
81-
parser.add_option(std::move(double_arg));
94+
parser.add_option(std::move(unix_path_arg));
8295

83-
std::filesystem::path file_path{"some/path/to/file.txt"};
84-
auto path_arg = std::make_unique<tc::sdk::optional_argument<std::filesystem::path>>("file_path", "p", file_path, file_path, "Path to file");
85-
path_arg->set_validator([](const std::filesystem::path& p) {
96+
std::filesystem::path windows_path{"C:\\TeiaCare\\Dummy Directory\\log.txt"};
97+
auto windows_path_arg = std::make_unique<tc::sdk::optional_argument<std::filesystem::path>>("win_path", windows_path, windows_path, "Path to file");
98+
windows_path_arg->set_validator([](const std::filesystem::path& p) {
8699
return std::filesystem::exists(p);
87100
});
88-
parser.add_option(std::move(path_arg));
101+
parser.add_option(std::move(windows_path_arg));
89102

90103
switch (parser.parse(argc, argv))
91104
{
92105
case tc::sdk::parse_result::success:
93106
break;
94107
case tc::sdk::parse_result::quit:
108+
spdlog::info("Quit");
95109
return EXIT_SUCCESS;
96110
case tc::sdk::parse_result::error:
111+
spdlog::error("Error");
97112
return EXIT_SUCCESS; // Normally here you will return EXIT_FAILURE, but we avoid failure since CI runs without any command line arguments.
98113
}
99114

@@ -102,14 +117,17 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
102117

103118
spdlog::info("Input file: {}", input_file);
104119
spdlog::info("Output file: {}", output_file);
120+
spdlog::info("Simple Option: {}", simple_option);
121+
spdlog::info("Simple Flag: {}", (simple_flag ? "true" : "false"));
105122
spdlog::info("Iterations: {}", iterations);
106123
spdlog::info("Threshold: {}", threshold);
107124
spdlog::info("Verbose: {}", (verbose ? "true" : "false"));
108125
spdlog::info("Str Opt: {}", str_opt);
109126
spdlog::info("Double Opt: ", double_opt);
110127
spdlog::info("Flag: {}", (flag ? "true" : "false"));
111128
spdlog::info("List: {}", to_string(the_list));
112-
spdlog::info("Path: {}", file_path.string());
129+
spdlog::info("Unix Path: {}", unix_path.string());
130+
spdlog::info("Windows Path: {}", windows_path.string());
113131

114132
return EXIT_SUCCESS;
115133
}

sdk/include/teiacare/sdk/argparse/argument_base.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <string>
2020
#include <type_traits>
2121
#include <vector>
22+
#include <filesystem>
2223

2324
namespace tc::sdk
2425
{
@@ -238,14 +239,30 @@ class argument_base
238239
return values;
239240
}
240241

241-
// Generic implementation (for every type different from int, float, double, std::string and std::vector)
242+
// Specialization for std::filesystem::path
243+
template <typename T>
244+
typename std::enable_if_t<std::is_same_v<T, std::filesystem::path>, T>
245+
convert(const std::string& str) const
246+
{
247+
try
248+
{
249+
return std::filesystem::path(str);
250+
}
251+
catch (...)
252+
{
253+
throw std::runtime_error("Conversion error: " + _name_long + "=" + str);
254+
}
255+
}
256+
257+
// Generic implementation (for every type different from int, float, double, std::string, std::vector and std::filesystem::path)
242258
template <typename T>
243259
typename std::enable_if_t<
244260
!std::is_same_v<T, int> &&
245261
!std::is_same_v<T, float> &&
246262
!std::is_same_v<T, double> &&
247263
!std::is_same_v<T, std::string> &&
248264
!std::is_same_v<T, const char*> &&
265+
!std::is_same_v<T, std::filesystem::path> &&
249266
!tc::sdk::is_vector<T>::value,
250267
T>
251268
convert(const std::string& str) const

sdk/include/teiacare/sdk/argparse/argument_parser.hpp

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class argument_parser
8080
/*!
8181
* \brief Adds a positional argument to the parser.
8282
* \tparam T Type of the argument.
83-
* \param name Name of the positional argument.
83+
* \param name Name of the positional argument. Any string without spaces is accepted.
8484
* \param var Variable to store the parsed value.
8585
* \param description Description of the argument (optional).
8686
*/
@@ -104,20 +104,38 @@ class argument_parser
104104
/*!
105105
* \brief Adds an optional argument to the parser.
106106
* \tparam T Type of the argument.
107-
* \param name_long Long name of the argument (e.g., "--option").
108-
* \param name_short Short name of the argument (e.g., "-o").
107+
* \param name_long Long name of the argument. Any string without spaces is accepted. The double dashes must be omitted (e.g., "option" will result in "--option").
108+
* \param name_short Short name of the argument. Single character. The dash must be omitted. (e.g., 'o' will result in '-o').
109109
* \param var Variable to store the parsed value.
110110
* \param default_value Default value for the argument.
111111
* \param description Description of the argument (optional).
112112
* \param required Whether the argument is required (default=false).
113113
* \param env_var Environment variable to use for the argument (optional).
114114
*/
115115
template <typename T>
116-
void add_option(const std::string& name_long, const std::string& name_short, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "")
116+
// void add_option(const std::string& name_long, const std::string& name_short, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "")
117+
void add_option(const std::string& name_long, const char name_short, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "")
117118
{
118119
add_option(std::make_unique<tc::sdk::optional_argument<T>>(name_long, name_short, var, default_value, description, required, env_var));
119120
}
120121

122+
/*!
123+
* \brief Adds an optional argument to the parser.
124+
* \tparam T Type of the argument.
125+
* \param name_long Long name of the argument. Any string without spaces is accepted. The double dashes must be omitted (e.g., "option" will result in "--option").
126+
* \param var Variable to store the parsed value.
127+
* \param default_value Default value for the argument.
128+
* \param description Description of the argument (optional).
129+
* \param required Whether the argument is required (default=false).
130+
* \param env_var Environment variable to use for the argument (optional).
131+
*/
132+
template <typename T>
133+
// void add_option(const std::string& name_long, const std::string& name_short, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "")
134+
void add_option(const std::string& name_long, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "")
135+
{
136+
add_option(std::make_unique<tc::sdk::optional_argument<T>>(name_long, var, default_value, description, required, env_var));
137+
}
138+
121139
/*!
122140
* \brief Adds an optional argument using a unique pointer.
123141
* \tparam T Type of the argument.
@@ -131,17 +149,29 @@ class argument_parser
131149

132150
/*!
133151
* \brief Adds a flag argument to the parser.
134-
* \param name_long Long name of the flag (e.g., "--flag").
135-
* \param name_short Short name of the flag (e.g., "-f").
152+
* \param name_long Long name of the flag (e.g., "flag" will result in "--flag").
153+
* \param name_short Short name of the flag (e.g., 'f', will result in '-f').
136154
* \param var Boolean variable to store the flag state.
137155
* \param description Description of the flag (optional).
138156
* \param env_var Environment variable to use for the flag (optional).
139157
*/
140-
void add_flag(const std::string& name_long, const std::string& name_short, bool& var, const std::string& description = "", const std::string& env_var = "")
158+
void add_flag(const std::string& name_long, const char name_short, bool& var, const std::string& description = "", const std::string& env_var = "")
141159
{
142160
add_flag(std::make_unique<tc::sdk::flag_argument>(name_long, name_short, var, description, env_var));
143161
}
144162

163+
/*!
164+
* \brief Adds a flag argument to the parser.
165+
* \param name_long Long name of the flag (e.g., "flag" will result in "--flag").
166+
* \param var Boolean variable to store the flag state.
167+
* \param description Description of the flag (optional).
168+
* \param env_var Environment variable to use for the flag (optional).
169+
*/
170+
void add_flag(const std::string& name_long, bool& var, const std::string& description = "", const std::string& env_var = "")
171+
{
172+
add_flag(std::make_unique<tc::sdk::flag_argument>(name_long, var, description, env_var));
173+
}
174+
145175
/*!
146176
* \brief Adds a flag argument using a unique pointer.
147177
* \param flag_arg Unique pointer to the flag argument object.

sdk/include/teiacare/sdk/argparse/flag_argument.hpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,27 @@ class flag_argument : public argument_base
2828
public:
2929
/*!
3030
* \brief Constructs a flag argument.
31-
* \param name_long Long name of the argument (e.g., "--flag").
32-
* \param name_short Short name of the argument (e.g., "-f").
31+
* \param name_long Long name of the argument (e.g., "flag").
32+
* \param name_short Short name of the argument (e.g., "f").
3333
* \param var Reference to the boolean variable that will be set.
3434
* \param description Description of the argument (optional).
3535
* \param env_var Name of the environment variable to parse (optional).
3636
*/
37-
explicit flag_argument(const std::string& name_long, const std::string& name_short, bool& var, const std::string& description = "", const std::string& env_var = "") noexcept
38-
: argument_base(name_long, name_short, description, false, env_var)
37+
explicit flag_argument(const std::string& name_long, char name_short, bool& var, const std::string& description = "", const std::string& env_var = "") noexcept
38+
: argument_base(name_long, std::string(1, name_short), description, false, env_var)
39+
, _var{var}
40+
{
41+
}
42+
43+
/*!
44+
* \brief Constructs a flag argument.
45+
* \param name_long Long name of the argument (e.g., "flag").
46+
* \param var Reference to the boolean variable that will be set.
47+
* \param description Description of the argument (optional).
48+
* \param env_var Name of the environment variable to parse (optional).
49+
*/
50+
explicit flag_argument(const std::string& name_long, bool& var, const std::string& description = "", const std::string& env_var = "") noexcept
51+
: argument_base(name_long, std::string(), description, false, env_var)
3952
, _var{var}
4053
{
4154
}

sdk/include/teiacare/sdk/argparse/optional_argument.hpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,32 @@ class optional_argument : public argument_base
3232
public:
3333
/*!
3434
* \brief Constructs an optional argument.
35-
* \param name_long Long name of the argument (e.g., "--option").
36-
* \param name_short Short name of the argument (e.g., "-o").
35+
* \param name_long Long name of the argument (e.g., "option").
36+
* \param name_short Short name of the argument (e.g., "o").
3737
* \param var Reference to the variable that will hold the parsed value.
3838
* \param default_value Default value for the argument.
3939
* \param description Description of the argument (optional).
4040
* \param required Indicates if the argument is required (default=false).
4141
* \param env_var Name of the environment variable to parse (optional).
4242
*/
43-
explicit optional_argument(const std::string& name_long, const std::string& name_short, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "") noexcept
44-
: argument_base(name_long, name_short, description, required, env_var)
43+
explicit optional_argument(const std::string& name_long, const char name_short, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "") noexcept
44+
: argument_base(name_long, std::string(1, name_short), description, required, env_var)
45+
, _var{var}
46+
{
47+
_var = default_value;
48+
}
49+
50+
/*!
51+
* \brief Constructs an optional argument.
52+
* \param name_long Long name of the argument (e.g., "option").
53+
* \param var Reference to the variable that will hold the parsed value.
54+
* \param default_value Default value for the argument.
55+
* \param description Description of the argument (optional).
56+
* \param required Indicates if the argument is required (default=false).
57+
* \param env_var Name of the environment variable to parse (optional).
58+
*/
59+
explicit optional_argument(const std::string& name_long, T& var, const T& default_value = T(), const std::string& description = "", bool required = false, const std::string& env_var = "") noexcept
60+
: argument_base(name_long, std::string(), description, required, env_var)
4561
, _var{var}
4662
{
4763
_var = default_value;

sdk/include/teiacare/sdk/argparse/positional_argument.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class positional_argument : public argument_base
3535
* \param description Description of the argument (optional).
3636
*/
3737
explicit positional_argument(const std::string& name, T& var, const std::string& description = "") noexcept
38-
: argument_base(name, "", description, true)
38+
: argument_base(name, std::string(), description, true)
3939
, _var(var)
4040
{
4141
_var = T();

sdk/src/argparse/argument_parser.cpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ argument_parser::argument_parser(const std::string& program_name, const std::str
2828
, _program_version{program_version}
2929
, _program_description{program_description}
3030
{
31-
add_flag("help", "h", _help_flag, "Display this help message");
32-
add_flag("version", "v", _version_flag, "Display program version");
31+
add_flag("help", 'h', _help_flag, "Display this help message");
32+
add_flag("version", 'v', _version_flag, "Display program version");
3333
}
3434

3535
parse_result argument_parser::parse(int argc, char* argv[]) const
@@ -202,7 +202,15 @@ void argument_parser::print_arguments() const
202202
for (const auto& opt : _optionals)
203203
{
204204
std::cout << pad;
205-
std::cout << std::setw(item_width) << "-" + opt->get_name_short() + ", --" + opt->get_name_long();
205+
if(opt->get_name_short().empty())
206+
{
207+
std::cout << std::setw(item_width) << "--" + opt->get_name_long();
208+
}
209+
else
210+
{
211+
std::cout << std::setw(item_width) << "-" + opt->get_name_short() + ", --" + opt->get_name_long();
212+
}
213+
206214
std::cout << std::setw(item_width) << opt->get_description();
207215
if (const auto env = opt->get_env(); !env.empty())
208216
{
@@ -214,7 +222,15 @@ void argument_parser::print_arguments() const
214222
for (const auto& flag : _flags)
215223
{
216224
std::cout << pad;
217-
std::cout << std::setw(item_width) << "-" + flag->get_name_short() + ", --" + flag->get_name_long();
225+
if(flag->get_name_short().empty())
226+
{
227+
std::cout << std::setw(item_width) << "--" + flag->get_name_long();
228+
}
229+
else
230+
{
231+
std::cout << std::setw(item_width) << "-" + flag->get_name_short() + ", --" + flag->get_name_long();
232+
}
233+
218234
std::cout << std::setw(item_width) << flag->get_description();
219235
if (const auto env = flag->get_env(); !env.empty())
220236
{

sdk/tests/src/test_argparse_argument_base.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,36 @@ TEST_F(test_argparse_argument_base, convert_vector_double)
254254
EXPECT_THROW(arg.convert_wrapper("1,a,b,c,d,e"), std::runtime_error);
255255
}
256256

257+
TEST_F(test_argparse_argument_base, convert_filesystem_path_unix)
258+
{
259+
using Type = std::filesystem::path;
260+
argument<Type> arg("name_long");
261+
262+
EXPECT_EQ(Type{}, arg.convert_wrapper(""));
263+
264+
const auto non_spaced_path = std::filesystem::path("/home/TeiaCare/DummyDirectory/log.txt");
265+
EXPECT_EQ(arg.convert_wrapper("/home/TeiaCare/DummyDirectory/log.txt"), non_spaced_path);
266+
267+
const auto spaced_path = std::filesystem::path("/home/TeiaCare/Dummy Directory/log.txt");
268+
EXPECT_EQ(arg.convert_wrapper("/home/TeiaCare/Dummy Directory/log.txt"), spaced_path);
269+
}
270+
271+
TEST_F(test_argparse_argument_base, convert_filesystem_path_windows)
272+
{
273+
using Type = std::filesystem::path;
274+
argument<Type> arg("name_long");
275+
276+
EXPECT_EQ(Type{}, arg.convert_wrapper(""));
277+
278+
const auto non_spaced_path = std::filesystem::path("C:\\TeiaCare\\DummyDirectory\\log.txt");
279+
EXPECT_EQ(arg.convert_wrapper(R"(C:\\TeiaCare\\DummyDirectory\\log.txt)"), non_spaced_path);
280+
EXPECT_EQ(arg.convert_wrapper(R"(C:\TeiaCare\DummyDirectory\log.txt)"), non_spaced_path);
281+
EXPECT_EQ(arg.convert_wrapper(R"(C:/TeiaCare/DummyDirectory/log.txt)"), non_spaced_path);
282+
283+
const auto spaced_path = std::filesystem::path("C:\\TeiaCare\\Dummy Directory\\log.txt");
284+
EXPECT_EQ(arg.convert_wrapper(R"(C:\\TeiaCare\\Dummy Directory\\log.txt)"), spaced_path);
285+
EXPECT_EQ(arg.convert_wrapper(R"(C:\TeiaCare\Dummy Directory\log.txt)"), spaced_path);
286+
EXPECT_EQ(arg.convert_wrapper(R"(C:/TeiaCare/Dummy Directory/log.txt)"), spaced_path);
287+
}
288+
257289
}

0 commit comments

Comments
 (0)