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
49namespace foundation
50{
51
52//
53// Command line parser.
54//
55
56class 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
124inline CommandLineParser::CommandLineParser()
125{
126 // No default option handler.
127 m_default_option.m_handler = 0;
128}
129
130inline void CommandLineParser::add_option_handler(OptionHandler* handler)
131{
132 assert(handler);
133 m_handlers.push_back(handler);
134}
135
136inline void CommandLineParser::set_default_option_handler(OptionHandler* handler)
137{
138 assert(handler);
139 m_default_option.m_handler = handler;
140}
141
142inline void CommandLineParser::print_usage(Logger& logger) const
143{
144 const size_t max_header_length = 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
178inline 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
188inline 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
222inline size_t CommandLineParser::get_max_header_length() const
223{
224 size_t max_header_length = 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 header_length = 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
253inline 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
269inline 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
280inline 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
330inline 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
351inline 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