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 std::string log_file_path = arg.substr(11);
88 if (isValidLogFileExtension(log_file_path))
89 {
90 config.log_file = log_file_path;
91 return {};
92 }
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 * @brief Handles the number of threads argument and sets the number of threads.
100 *
101 * @param arg The number of threads argument string.
102 * @param config The configuration object to update.
103 * @return std::expected<void, std::string> An expected object with an error message if the number of threads is
104 * invalid.
105 */
106 std::expected<void, std::string> handleNumThreads(const std::string& arg, core::Config& config) noexcept
107 {
108 try
109 {
110 const int requested_threads = std::stoi(arg.substr(3));
111 if (requested_threads <= 0)
112 {
113 return std::unexpected("Number of threads must be greater than 0");
114 }
115
116 config.num_threads = static_cast<unsigned>(requested_threads);
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.rfind("--out-dir=", 0) == 0)
166 {
167 config.output_dir = arg.substr(10);
168 return {};
169 }
170 if (arg == "--no-validate")
171 {
172 config.validate = false;
173 return {};
174 }
175 if (arg == "--kml")
176 {
177 config.generate_kml = true;
178 return {};
179 }
180 if (arg.rfind("--kml=", 0) == 0)
181 {
182 config.generate_kml = true;
183 config.kml_file = arg.substr(6);
184 return {};
185 }
186 if (arg[0] != '-' && !scriptFileSet)
187 {
188 config.script_file = arg;
189 scriptFileSet = true;
190 return {};
191 }
192
193 std::cerr << "[ERROR] Unrecognized option: '" << arg << "'\n";
194 return std::unexpected("Unrecognized argument: " + arg);
195 }
196}
197
198namespace core
199{
200 void showHelp(const char* programName) noexcept
201 {
202 std::cout << R"(/------------------------------------------------\
203| FERS - The Flexible Extensible Radar Simulator |
204| Version 1.0.0 |
205\------------------------------------------------/
206Usage: )" << programName
207 << R"( <scriptfile> [options]
208
209Options:
210 --help, -h Show this help message and exit
211 --version, -v Show version information and exit
212 --no-validate Disable XML schema validation before running.
213 --kml[=<file>] Generate a KML visualization of the scenario and exit. If a filename
214 is provided, it will be used. Otherwise, it defaults to the scenario
215 name with a .kml extension in the output directory.
216 --out-dir=<dir> Set the output directory for simulation results and default KML output.
217 Defaults to the directory containing the script file.
218 --log-level=<level> Set the logging level (TRACE, DEBUG, INFO, WARNING, ERROR, FATAL)
219 --log-file=<file> Log output to the specified .log or .txt file as well as the console.
220 -n=<threads> Number of threads to use
221
222Arguments:
223 <scriptfile> Path to the simulation script file (XML)
224
225Example:
226 )" << programName
227 << R"( simulation.fersxml --out-dir=./results --log-level=DEBUG -n=4
228
229This program runs radar simulations based on an XML script file.
230Make sure the script file follows the correct format to avoid errors.
231)";
232 }
233
234 void showVersion() noexcept
235 {
236 std::cout << R"(
237/------------------------------------------------\
238| FERS - The Flexible Extensible Radar Simulator |
239| Version 1.0.0 |
240| Author: Marc Brooker |
241\------------------------------------------------/
242)" << '\n';
243 }
244
245 std::expected<Config, std::string> parseArguments(const int argc, char* argv[]) noexcept
246 {
247 Config config;
248 bool script_file_set = false;
249
250 if (argc < 2)
251 {
252 showHelp(argv[0]);
253 return std::unexpected("No arguments provided.");
254 }
255
256 for (int i = 1; i < argc; ++i)
257 {
258 if (const auto result = handleArgument(argv[i], config, script_file_set, argv[0]); !result)
259 {
260 return std::unexpected(result.error());
261 }
262 }
263
264 if (!script_file_set)
265 {
266 return std::unexpected("No script file provided.");
267 }
268 return config;
269 }
270}
@ FERS_LOG_FATAL
Definition api.h:92
@ FERS_LOG_DEBUG
Definition api.h:88
@ FERS_LOG_ERROR
Definition api.h:91
@ FERS_LOG_INFO
Definition api.h:89
@ FERS_LOG_TRACE
Definition api.h:87
@ FERS_LOG_WARNING
Definition api.h:90
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