FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
arg_parser.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2024-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
7/**
8 * @file arg_parser.cpp
9 * @brief Implementation of the command-line argument parser for the application.
10 */
11
12#include "arg_parser.h"
13
14#include <algorithm>
15#include <exception>
16#include <filesystem>
17#include <iostream>
18#include <unordered_map>
19#include <utility>
20#include <vector>
21
22namespace
23{
24 /**
25 * @brief Checks if the given file has a valid log file extension.
26 *
27 * @param filePath The path of the log file.
28 * @return true if the file has a valid extension, false otherwise.
29 */
30 bool isValidLogFileExtension(const std::string& filePath) noexcept
31 {
32 static const std::vector<std::string> VALID_EXTENSIONS = {".log", ".txt"};
33 std::string extension = std::filesystem::path(filePath).extension().string();
34 std::ranges::transform(extension, extension.begin(), tolower);
35 return std::ranges::find(VALID_EXTENSIONS, extension) != VALID_EXTENSIONS.end();
36 }
37
38 /**
39 * @brief Parses the logging level from a string representation.
40 *
41 * @param level The string representation of the logging level.
42 * @return std::optional<Level> The corresponding logging level, or `std::nullopt` if invalid.
43 */
44 std::optional<fers_log_level_t> parseLogLevel(const std::string& level) noexcept
45 {
46 static const std::unordered_map<std::string, fers_log_level_t> LEVEL_MAP = {
47 {"TRACE", FERS_LOG_TRACE}, {"DEBUG", FERS_LOG_DEBUG}, {"INFO", FERS_LOG_INFO},
48 {"WARNING", FERS_LOG_WARNING}, {"ERROR", FERS_LOG_ERROR}, {"FATAL", FERS_LOG_FATAL}};
49
50 if (const auto it = LEVEL_MAP.find(level); it != LEVEL_MAP.end())
51 {
52 return it->second;
53 }
54 return std::nullopt;
55 }
56
57 /**
58 * @brief Handles the log-level argument and sets the logging level.
59 *
60 * @param arg The log-level argument string.
61 * @param config The configuration object to update.
62 * @return std::expected<void, std::string> An expected object with an error message if the log level is invalid.
63 */
64 std::expected<void, std::string> handleLogLevel(const std::string& arg, core::Config& config) noexcept
65 {
66 const std::string level_str = arg.substr(12);
67 if (const auto level = parseLogLevel(level_str))
68 {
69 config.log_level = *level;
70 return {};
71 }
72
73 std::cerr << "[ERROR] Invalid log level '" << level_str << "'\n";
74 return std::unexpected("Invalid log level: " + level_str);
75 }
76
77 /**
78 * @brief Handles the log-file argument and sets the log file path.
79 *
80 * @param arg The log-file argument string.
81 * @param config The configuration object to update.
82 * @return std::expected<void, std::string> An expected object with an error message if the log file path is
83 * invalid.
84 */
85 std::expected<void, std::string> handleLogFile(const std::string& arg, core::Config& config) noexcept
86 {
87 if (std::string log_file_path = arg.substr(11); isValidLogFileExtension(log_file_path))
88 {
89 config.log_file = log_file_path;
90 return {};
91 }
92 else
93 {
94 std::cerr << "[ERROR] Invalid log file extension. Must be .log or .txt\n";
95 return std::unexpected("Invalid log file extension: " + log_file_path);
96 }
97 }
98
99 /**
100 * @brief Handles the number of threads argument and sets the number of threads.
101 *
102 * @param arg The number of threads argument string.
103 * @param config The configuration object to update.
104 * @return std::expected<void, std::string> An expected object with an error message if the number of threads is
105 * invalid.
106 */
107 std::expected<void, std::string> handleNumThreads(const std::string& arg, core::Config& config) noexcept
108 {
109 try
110 {
111 config.num_threads = std::stoi(arg.substr(3));
112 if (config.num_threads == 0)
113 {
114 return std::unexpected("Number of threads must be greater than 0");
115 }
116
117 if (const unsigned max_threads = std::thread::hardware_concurrency();
118 max_threads > 0 && config.num_threads > max_threads)
119 {
120 std::cerr << "[WARNING] Thread count exceeds available processors. Clamping.\n";
121 config.num_threads = max_threads;
122 }
123 return {};
124 }
125 catch (const std::exception&)
126 {
127 return std::unexpected("Invalid number of threads specified.");
128 }
129 }
130
131 /**
132 * @brief Handles the command-line argument and updates the configuration.
133 *
134 * @param arg The command-line argument string.
135 * @param config The configuration object to update.
136 * @param scriptFileSet A flag indicating if the script file has been set.
137 * @param programName The name of the program executable.
138 * @return std::expected<void, std::string> An expected object with an error message if the argument is invalid.
139 */
140 std::expected<void, std::string> handleArgument(const std::string& arg, core::Config& config, bool& scriptFileSet,
141 const char* programName) noexcept
142 {
143 if (arg == "--help" || arg == "-h")
144 {
145 core::showHelp(programName);
146 return std::unexpected("Help requested.");
147 }
148 if (arg == "--version" || arg == "-v")
149 {
151 return std::unexpected("Version requested.");
152 }
153 if (arg.rfind("--log-level=", 0) == 0)
154 {
155 return handleLogLevel(arg, config);
156 }
157 if (arg.rfind("--log-file=", 0) == 0)
158 {
159 return handleLogFile(arg, config);
160 }
161 if (arg.rfind("-n=", 0) == 0)
162 {
163 return handleNumThreads(arg, config);
164 }
165 if (arg == "--no-validate")
166 {
167 config.validate = false;
168 return {};
169 }
170 if (arg == "--kml")
171 {
172 config.generate_kml = true;
173 return {};
174 }
175 if (arg[0] != '-' && !scriptFileSet)
176 {
177 config.script_file = arg;
178 scriptFileSet = true;
179 return {};
180 }
181
182 std::cerr << "[ERROR] Unrecognized option: '" << arg << "'\n";
183 return std::unexpected("Unrecognized argument: " + arg);
184 }
185}
186
187namespace core
188{
189 void showHelp(const char* programName) noexcept
190 {
191 std::cout << R"(/------------------------------------------------\
192| FERS - The Flexible Extensible Radar Simulator |
193| Version 1.0.0 |
194\------------------------------------------------/
195Usage: )" << programName
196 << R"( <scriptfile> [options]
197
198Options:
199 --help, -h Show this help message and exit
200 --version, -v Show version information and exit
201 --no-validate Disable XML schema validation before running.
202 --kml Generate a KML visualization of the scenario and exit. The output file
203 will have the same name as the input file with a .kml extension.
204 --log-level=<level> Set the logging level (TRACE, DEBUG, INFO, WARNING, ERROR, FATAL)
205 --log-file=<file> Log output to the specified .log or .txt file as well as the console.
206 -n=<threads> Number of threads to use
207
208Arguments:
209 <scriptfile> Path to the simulation script file (XML)
210
211Example:
212 )" << programName
213 << R"( simulation.fersxml --log-level=DEBUG --log-file=output.log -n=4
214
215This program runs radar simulations based on an XML script file.
216Make sure the script file follows the correct format to avoid errors.
217)";
218 }
219
220 void showVersion() noexcept
221 {
222 std::cout << R"(
223/------------------------------------------------\
224| FERS - The Flexible Extensible Radar Simulator |
225| Version 1.0.0 |
226| Author: Marc Brooker |
227\------------------------------------------------/
228)" << std::endl;
229 }
230
231 std::expected<Config, std::string> parseArguments(const int argc, char* argv[]) noexcept
232 {
233 Config config;
234 bool script_file_set = false;
235
236 if (argc < 2)
237 {
238 showHelp(argv[0]);
239 return std::unexpected("No arguments provided.");
240 }
241
242 for (int i = 1; i < argc; ++i)
243 {
244 if (const auto result = handleArgument(argv[i], config, script_file_set, argv[0]); !result)
245 {
246 return std::unexpected(result.error());
247 }
248 }
249
250 if (!script_file_set)
251 {
252 return std::unexpected("No script file provided.");
253 }
254 return config;
255 }
256}
@ FERS_LOG_FATAL
Definition api.h:82
@ FERS_LOG_DEBUG
Definition api.h:78
@ FERS_LOG_ERROR
Definition api.h:81
@ FERS_LOG_INFO
Definition api.h:79
@ FERS_LOG_TRACE
Definition api.h:77
@ FERS_LOG_WARNING
Definition api.h:80
Command-line argument parsing utilities for the application.
void showVersion() noexcept
Displays the version information.
std::expected< Config, std::string > parseArguments(const int argc, char *argv[]) noexcept
Parses command-line arguments.
void showHelp(const char *programName) noexcept
Displays the help message.
Configuration structure for the application.
Definition arg_parser.h:30