1 | |
2 | // |
3 | // This source file is part of appleseed. |
4 | // Visit http://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-2017 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 | #ifndef APPLESEED_FOUNDATION_UTILITY_COMMANDLINEPARSER_COMMANDLINEPARSER_H |
31 | #define APPLESEED_FOUNDATION_UTILITY_COMMANDLINEPARSER_COMMANDLINEPARSER_H |
32 | |
33 | // appleseed.foundation headers. |
34 | #include "foundation/core/concepts/noncopyable.h" |
35 | #include "foundation/utility/commandlineparser/flagoptionhandler.h" |
36 | #include "foundation/utility/commandlineparser/messagelist.h" |
37 | #include "foundation/utility/commandlineparser/optionhandler.h" |
38 | #include "foundation/utility/commandlineparser/parseresults.h" |
39 | #include "foundation/utility/commandlineparser/valueoptionhandler.h" |
40 | #include "foundation/utility/foreach.h" |
41 | #include "foundation/utility/log.h" |
42 | |
43 | // Standard headers. |
44 | #include <cassert> |
45 | #include <cstddef> |
46 | #include <string> |
47 | #include <vector> |
48 | |
49 | namespace foundation |
50 | { |
51 | |
52 | // |
53 | // Command line parser. |
54 | // |
55 | |
56 | class CommandLineParser |
57 | : public NonCopyable |
58 | { |
59 | public: |
60 | // Constructor. |
61 | CommandLineParser(); |
62 | |
63 | // Add an option handler. |
64 | void add_option_handler(OptionHandler* handler); |
65 | |
66 | // Set a default option handler. |
67 | void set_default_option_handler(OptionHandler* handler); |
68 | |
69 | // Print the program usage. |
70 | void print_usage(Logger& logger) const; |
71 | |
72 | // Parse a command line. |
73 | // Returns true on success, or false if one or multiple errors were detected. |
74 | void parse( |
75 | const int argc, |
76 | const char* argv[], |
77 | ParseResults& results); |
78 | |
79 | // Print the options that were recognized during parsing. |
80 | void print_recognized_options(Logger& logger); |
81 | |
82 | private: |
83 | struct Option |
84 | { |
85 | std::string m_name; // option name, as found on the command line |
86 | OptionHandler* m_handler; // option handler |
87 | StringVector m_values; // option values |
88 | }; |
89 | |
90 | typedef std::vector<OptionHandler*> OptionHandlerVector; |
91 | typedef std::vector<Option> OptionVector; |
92 | |
93 | OptionHandlerVector m_handlers; // option handlers |
94 | OptionVector m_options; // options |
95 | Option m_default_option; // default option |
96 | |
97 | // Return the length of the longest header string. |
98 | size_t get_max_header_length() const; |
99 | |
100 | // Find an option handler that accepts a given argument. |
101 | OptionHandler* find_option_handler(const std::string& arg) const; |
102 | |
103 | // Return true if given handler is referenced by one of options. |
104 | bool is_handler_used(const OptionHandler* handler) const; |
105 | |
106 | // Collect the options from a command line. |
107 | void collect_options( |
108 | const int argc, |
109 | const char* argv[], |
110 | ParseResults& results); |
111 | |
112 | // Check if the required options are present. |
113 | void check_required_options(ParseResults& results); |
114 | |
115 | // Process the collected options. |
116 | void process_options(ParseResults& results); |
117 | }; |
118 | |
119 | |
120 | // |
121 | // CommandLineParser class implementation. |
122 | // |
123 | |
124 | inline CommandLineParser::CommandLineParser() |
125 | { |
126 | // No default option handler. |
127 | m_default_option.m_handler = 0; |
128 | } |
129 | |
130 | inline void CommandLineParser::add_option_handler(OptionHandler* handler) |
131 | { |
132 | assert(handler); |
133 | m_handlers.push_back(handler); |
134 | } |
135 | |
136 | inline void CommandLineParser::set_default_option_handler(OptionHandler* handler) |
137 | { |
138 | assert(handler); |
139 | m_default_option.m_handler = handler; |
140 | } |
141 | |
142 | inline void CommandLineParser::print_usage(Logger& logger) const |
143 | { |
144 | const size_t = get_max_header_length(); |
145 | |
146 | // Loop over the option handlers. |
147 | for (const_each<OptionHandlerVector> i = m_handlers; i; ++i) |
148 | { |
149 | // Fetch the option handler. |
150 | const OptionHandler* handler = *i; |
151 | |
152 | // Skip hidden options. |
153 | if (handler->m_flags & OptionHandler::Hidden) |
154 | continue; |
155 | |
156 | std::string line; |
157 | |
158 | // Build the header string for this option. |
159 | for (const_each<StringVector> j = handler->m_names; j; ++j) |
160 | { |
161 | if (j > handler->m_names.begin()) |
162 | line += ", " ; |
163 | line += *j; |
164 | } |
165 | |
166 | // Pad with spaces so that descriptions are aligned. |
167 | if (max_header_length > line.size()) |
168 | line += std::string(max_header_length - line.size(), ' '); |
169 | |
170 | // Append the description. |
171 | line += " " + handler->get_description(); |
172 | |
173 | // Emit the line. |
174 | LOG_INFO(logger, " %s" , line.c_str()); |
175 | } |
176 | } |
177 | |
178 | inline void CommandLineParser::parse( |
179 | const int argc, |
180 | const char* argv[], |
181 | ParseResults& results) |
182 | { |
183 | collect_options(argc, argv, results); |
184 | check_required_options(results); |
185 | process_options(results); |
186 | } |
187 | |
188 | inline void CommandLineParser::print_recognized_options(Logger& logger) |
189 | { |
190 | size_t found_options = 0; |
191 | |
192 | for (const_each<OptionHandlerVector> i = m_handlers; i; ++i) |
193 | { |
194 | // Fetch the option handler. |
195 | const OptionHandler* handler = *i; |
196 | |
197 | // Skip options that were not found on the command line. |
198 | if (handler->m_occurrence_count == 0) |
199 | continue; |
200 | |
201 | ++found_options; |
202 | |
203 | // Print this option. |
204 | std::string s; |
205 | handler->print(s); |
206 | LOG_INFO(logger, " %s" , s.c_str()); |
207 | } |
208 | |
209 | if (m_default_option.m_handler) |
210 | { |
211 | for (const_each<StringVector> i = m_default_option.m_values; i; ++i) |
212 | { |
213 | ++found_options; |
214 | LOG_INFO(logger, " positional argument: %s" , i->c_str()); |
215 | } |
216 | } |
217 | |
218 | if (found_options == 0) |
219 | LOG_INFO(logger, " (none)" ); |
220 | } |
221 | |
222 | inline size_t CommandLineParser::get_max_header_length() const |
223 | { |
224 | size_t = 0; |
225 | |
226 | // Loop over the option handlers. |
227 | for (const_each<OptionHandlerVector> i = m_handlers; i; ++i) |
228 | { |
229 | // Fetch the option handler. |
230 | const OptionHandler* handler = *i; |
231 | |
232 | // Skip hidden options. |
233 | if (handler->m_flags & OptionHandler::Hidden) |
234 | continue; |
235 | |
236 | // Compute the length of the header string for this option. |
237 | size_t = 0; |
238 | for (const_each<StringVector> j = handler->m_names; j; ++j) |
239 | { |
240 | if (j > handler->m_names.begin()) |
241 | header_length += 2; // account for the separators |
242 | header_length += j->size(); |
243 | } |
244 | |
245 | // Keep track of the longest header string. |
246 | if (max_header_length < header_length) |
247 | max_header_length = header_length; |
248 | } |
249 | |
250 | return max_header_length; |
251 | } |
252 | |
253 | inline OptionHandler* CommandLineParser::find_option_handler(const std::string& arg) const |
254 | { |
255 | for (const_each<OptionHandlerVector> i = m_handlers; i; ++i) |
256 | { |
257 | // Fetch the handler. |
258 | OptionHandler* handler = *i; |
259 | |
260 | // Return this handler if one of its name matches the argument. |
261 | if (handler->match_name(arg)) |
262 | return handler; |
263 | } |
264 | |
265 | // No handler found for this argument. |
266 | return 0; |
267 | } |
268 | |
269 | inline bool CommandLineParser::is_handler_used(const OptionHandler* handler) const |
270 | { |
271 | for (const_each<OptionVector> i = m_options; i; ++i) |
272 | { |
273 | if (i->m_handler == handler) |
274 | return true; |
275 | } |
276 | |
277 | return false; |
278 | } |
279 | |
280 | inline void CommandLineParser::collect_options( |
281 | const int argc, |
282 | const char* argv[], |
283 | ParseResults& results) |
284 | { |
285 | assert(m_options.empty()); |
286 | |
287 | for (int i = 1; i < argc; ++i) |
288 | { |
289 | // Fetch the command line argument. |
290 | const std::string arg = argv[i]; |
291 | |
292 | // Find an option handler that accepts this argument. |
293 | OptionHandler* handler = find_option_handler(arg); |
294 | |
295 | if (handler) |
296 | { |
297 | // Create a new option. |
298 | Option option; |
299 | option.m_name = arg; |
300 | option.m_handler = handler; |
301 | m_options.push_back(option); |
302 | } |
303 | else |
304 | { |
305 | if (m_options.empty() || |
306 | m_options.back().m_values.size() >= m_options.back().m_handler->get_max_value_count()) |
307 | { |
308 | if (m_default_option.m_handler == 0 || |
309 | m_default_option.m_values.size() >= m_default_option.m_handler->get_max_value_count()) |
310 | { |
311 | // Error: unknown option. |
312 | results.m_messages.add(LogMessage::Warning, "unknown option: '%s'." , arg.c_str()); |
313 | ++results.m_warnings; |
314 | } |
315 | else |
316 | { |
317 | // Append this value to the default option. |
318 | m_default_option.m_values.push_back(arg); |
319 | } |
320 | } |
321 | else |
322 | { |
323 | // Append this value to the current option. |
324 | m_options.back().m_values.push_back(arg); |
325 | } |
326 | } |
327 | } |
328 | } |
329 | |
330 | inline void CommandLineParser::check_required_options(ParseResults& results) |
331 | { |
332 | for (const_each<OptionHandlerVector> i = m_handlers; i; ++i) |
333 | { |
334 | OptionHandler* handler = *i; |
335 | |
336 | if ((handler->m_flags & OptionHandler::Required) != 0) |
337 | { |
338 | if (!is_handler_used(handler)) |
339 | { |
340 | // Error: required option missing. |
341 | results.m_messages.add( |
342 | LogMessage::Error, |
343 | "required option missing: '%s'." , |
344 | handler->m_names.front().c_str()); |
345 | ++results.m_errors; |
346 | } |
347 | } |
348 | } |
349 | } |
350 | |
351 | inline void CommandLineParser::process_options(ParseResults& results) |
352 | { |
353 | for (const_each<OptionVector> i = m_options; i; ++i) |
354 | { |
355 | // Fetch the option. |
356 | const Option& option = *i; |
357 | |
358 | // Let the option handler parse the option values. |
359 | option.m_handler->parse( |
360 | option.m_name, |
361 | option.m_values, |
362 | results); |
363 | } |
364 | |
365 | // Parse the default option values. |
366 | if (m_default_option.m_handler) |
367 | { |
368 | m_default_option.m_handler->parse( |
369 | m_default_option.m_name, |
370 | m_default_option.m_values, |
371 | results); |
372 | } |
373 | } |
374 | |
375 | } // namespace foundation |
376 | |
377 | #endif // !APPLESEED_FOUNDATION_UTILITY_COMMANDLINEPARSER_COMMANDLINEPARSER_H |
378 | |