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