1
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29
30 #pragma once
31
32 // appleseed.foundation headers.
33 #include "foundation/core/concepts/noncopyable.h"
34 #include "foundation/utility/commandlineparser/flagoptionhandler.h"
35 #include "foundation/utility/commandlineparser/messagelist.h"
36 #include "foundation/utility/commandlineparser/optionhandler.h"
37 #include "foundation/utility/commandlineparser/parseresults.h"
38 #include "foundation/utility/commandlineparser/valueoptionhandler.h"
39 #include "foundation/utility/foreach.h"
40 #include "foundation/utility/log.h"
41
42 // Standard headers.
43 #include <cassert>
44 #include <cstddef>
45 #include <string>
46 #include <vector>
47
48 namespace foundation
49 {
50
51 //
52 // Command line parser.
53 //
54
55 class CommandLineParser
56 : public NonCopyable
57 {
58 public:
59 // Constructor.
60 CommandLineParser();
61
62 // Add an option handler.
63 void add_option_handler(OptionHandler* handler);
64
65 // Set a default option handler.
66 void set_default_option_handler(OptionHandler* handler);
67
68 // Print the program usage.
69 void print_usage(Logger& logger) const;
70
71 // Parse a command line.
72 // Returns true on success, or false if one or multiple errors were detected.
73 void parse(
74 const int argc,
75 char* argv[],
76 ParseResults& results);
77
78 // Print the options that were recognized during parsing.
79 void print_recognized_options(Logger& logger);
80
81 private:
82 struct Option
83 {
84 std::string m_name; // option name, as found on the command line
85 OptionHandler* m_handler; // option handler
86 StringVector m_values; // option values
87 };
88
89 typedef std::vector<OptionHandler*> OptionHandlerVector;
90 typedef std::vector<Option> OptionVector;
91
92 OptionHandlerVector m_handlers; // option handlers
93 OptionVector m_options; // options
94 Option m_default_option; // default option
95
96 // Return the length of the longest header string.
97 size_t get_max_header_length() const;
98
99 // Find an option handler that accepts a given argument.
100 OptionHandler* find_option_handler(const std::string& arg) const;
101
102 // Return true if given handler is referenced by one of options.
103 bool is_handler_used(const OptionHandler* handler) const;
104
105 // Collect the options from a command line.
106 void collect_options(
107 const int argc,
108 char* argv[],
109 ParseResults& results);
110
111 // Check if the required options are present.
112 void check_required_options(ParseResults& results);
113
114 // Process the collected options.
115 void process_options(ParseResults& results);
116 };
117
118
119 //
120 // CommandLineParser class implementation.
121 //
122
CommandLineParser()123 inline CommandLineParser::CommandLineParser()
124 {
125 // No default option handler.
126 m_default_option.m_handler = nullptr;
127 }
128
add_option_handler(OptionHandler * handler)129 inline void CommandLineParser::add_option_handler(OptionHandler* handler)
130 {
131 assert(handler);
132 m_handlers.push_back(handler);
133 }
134
set_default_option_handler(OptionHandler * handler)135 inline void CommandLineParser::set_default_option_handler(OptionHandler* handler)
136 {
137 assert(handler);
138 m_default_option.m_handler = handler;
139 }
140
print_usage(Logger & logger)141 inline void CommandLineParser::print_usage(Logger& logger) const
142 {
143 const size_t max_header_length = get_max_header_length();
144
145 // Loop over the option handlers.
146 for (const_each<OptionHandlerVector> i = m_handlers; i; ++i)
147 {
148 // Fetch the option handler.
149 const OptionHandler* handler = *i;
150
151 // Skip hidden options.
152 if (handler->m_flags & OptionHandler::Hidden)
153 continue;
154
155 std::string line;
156
157 // Build the header string for this option.
158 for (const_each<StringVector> j = handler->m_names; j; ++j)
159 {
160 if (j > handler->m_names.begin())
161 line += ", ";
162 line += *j;
163 }
164
165 // Pad with spaces so that descriptions are aligned.
166 if (max_header_length > line.size())
167 line += std::string(max_header_length - line.size(), ' ');
168
169 // Append the description.
170 line += " " + handler->get_description();
171
172 // Emit the line.
173 LOG_INFO(logger, " %s", line.c_str());
174 }
175 }
176
parse(const int argc,char * argv[],ParseResults & results)177 inline void CommandLineParser::parse(
178 const int argc,
179 char* argv[],
180 ParseResults& results)
181 {
182 collect_options(argc, argv, results);
183 check_required_options(results);
184 process_options(results);
185 }
186
print_recognized_options(Logger & logger)187 inline void CommandLineParser::print_recognized_options(Logger& logger)
188 {
189 size_t found_options = 0;
190
191 for (const_each<OptionHandlerVector> i = m_handlers; i; ++i)
192 {
193 // Fetch the option handler.
194 const OptionHandler* handler = *i;
195
196 // Skip options that were not found on the command line.
197 if (handler->m_occurrence_count == 0)
198 continue;
199
200 ++found_options;
201
202 // Print this option.
203 std::string s;
204 handler->print(s);
205 LOG_INFO(logger, " %s", s.c_str());
206 }
207
208 if (m_default_option.m_handler)
209 {
210 for (const_each<StringVector> i = m_default_option.m_values; i; ++i)
211 {
212 ++found_options;
213 LOG_INFO(logger, " positional argument: %s", i->c_str());
214 }
215 }
216
217 if (found_options == 0)
218 LOG_INFO(logger, " (none)");
219 }
220
get_max_header_length()221 inline size_t CommandLineParser::get_max_header_length() const
222 {
223 size_t max_header_length = 0;
224
225 // Loop over the option handlers.
226 for (const_each<OptionHandlerVector> i = m_handlers; i; ++i)
227 {
228 // Fetch the option handler.
229 const OptionHandler* handler = *i;
230
231 // Skip hidden options.
232 if (handler->m_flags & OptionHandler::Hidden)
233 continue;
234
235 // Compute the length of the header string for this option.
236 size_t header_length = 0;
237 for (const_each<StringVector> j = handler->m_names; j; ++j)
238 {
239 if (j > handler->m_names.begin())
240 header_length += 2; // account for the separators
241 header_length += j->size();
242 }
243
244 // Keep track of the longest header string.
245 if (max_header_length < header_length)
246 max_header_length = header_length;
247 }
248
249 return max_header_length;
250 }
251
find_option_handler(const std::string & arg)252 inline OptionHandler* CommandLineParser::find_option_handler(const std::string& arg) const
253 {
254 for (const_each<OptionHandlerVector> i = m_handlers; i; ++i)
255 {
256 // Fetch the handler.
257 OptionHandler* handler = *i;
258
259 // Return this handler if one of its name matches the argument.
260 if (handler->match_name(arg))
261 return handler;
262 }
263
264 // No handler found for this argument.
265 return nullptr;
266 }
267
is_handler_used(const OptionHandler * handler)268 inline bool CommandLineParser::is_handler_used(const OptionHandler* handler) const
269 {
270 for (const_each<OptionVector> i = m_options; i; ++i)
271 {
272 if (i->m_handler == handler)
273 return true;
274 }
275
276 return false;
277 }
278
collect_options(const int argc,char * argv[],ParseResults & results)279 inline void CommandLineParser::collect_options(
280 const int argc,
281 char* argv[],
282 ParseResults& results)
283 {
284 assert(m_options.empty());
285
286 for (int i = 1; i < argc; ++i)
287 {
288 // Fetch the command line argument.
289 const std::string arg = argv[i];
290
291 // Find an option handler that accepts this argument.
292 OptionHandler* handler = find_option_handler(arg);
293
294 if (handler)
295 {
296 // Create a new option.
297 Option option;
298 option.m_name = arg;
299 option.m_handler = handler;
300 m_options.push_back(option);
301 }
302 else
303 {
304 if (m_options.empty() ||
305 m_options.back().m_values.size() >= m_options.back().m_handler->get_max_value_count())
306 {
307 if (m_default_option.m_handler == nullptr ||
308 m_default_option.m_values.size() >= m_default_option.m_handler->get_max_value_count())
309 {
310 // Error: unknown option.
311 results.m_messages.add(LogMessage::Warning, "unknown option: '%s'.", arg.c_str());
312 ++results.m_warnings;
313 }
314 else
315 {
316 // Append this value to the default option.
317 m_default_option.m_values.push_back(arg);
318 }
319 }
320 else
321 {
322 // Append this value to the current option.
323 m_options.back().m_values.push_back(arg);
324 }
325 }
326 }
327 }
328
check_required_options(ParseResults & results)329 inline void CommandLineParser::check_required_options(ParseResults& results)
330 {
331 for (const_each<OptionHandlerVector> i = m_handlers; i; ++i)
332 {
333 OptionHandler* handler = *i;
334
335 if ((handler->m_flags & OptionHandler::Required) != 0)
336 {
337 if (!is_handler_used(handler))
338 {
339 // Error: required option missing.
340 results.m_messages.add(
341 LogMessage::Error,
342 "required option missing: '%s'.",
343 handler->m_names.front().c_str());
344 ++results.m_errors;
345 }
346 }
347 }
348 }
349
process_options(ParseResults & results)350 inline void CommandLineParser::process_options(ParseResults& results)
351 {
352 for (const_each<OptionVector> i = m_options; i; ++i)
353 {
354 // Fetch the option.
355 const Option& option = *i;
356
357 // Let the option handler parse the option values.
358 option.m_handler->parse(
359 option.m_name,
360 option.m_values,
361 results);
362 }
363
364 // Parse the default option values.
365 if (m_default_option.m_handler)
366 {
367 m_default_option.m_handler->parse(
368 m_default_option.m_name,
369 m_default_option.m_values,
370 results);
371 }
372 }
373
374 } // namespace foundation
375