1 /*
2  * Copyright (c) 2018-2021, The OSKAR Developers.
3  * See the LICENSE file at the top-level directory of this distribution.
4  */
5 
6 #include "settings/extern/ezOptionParser/ezOptionParser.hpp"
7 #include "settings/oskar_option_parser.h"
8 #include <cstdarg>
9 #include <vector>
10 #include <string>
11 
12 using namespace std;
13 
14 namespace oskar {
15 
16 struct OptionParserPrivate : public ez::ezOptionParser
17 {
18     string title, version;
19     vector<string> optional_;
20     vector<string> optionalHelp_;
21     vector<string> required_;
22     vector<string> requiredHelp_;
23     vector<const char*> input_files_;
24     const char* settings_;
25     const char* version_;
26 };
27 
OptionParser(const char * title,const char * ver,const char * settings)28 OptionParser::OptionParser(const char* title, const char* ver,
29         const char* settings)
30 {
31     p = new OptionParserPrivate;
32     p->footer =
33             "\n" + string(79, '-') + "\n"
34             "OSKAR (version " + ver + ")\n"
35             "Copyright (c) 2021, The OSKAR Developers.\n"
36             "This program is free and without warranty.\n"
37             "" + string(79, '-') + "\n";
38     set_version(ver, false);
39     set_title(title);
40     set_settings(settings);
41 }
42 
~OptionParser()43 OptionParser::~OptionParser()
44 {
45     delete p;
46 }
47 
add_example(const char * text)48 void OptionParser::add_example(const char* text)
49 {
50     p->example += "  " + string(text) + "\n";
51 }
52 
53 // Wrapper to define flags with no arguments
add_flag(const char * flag1,const char * help,bool required,const char * flag2)54 void OptionParser::add_flag(const char* flag1, const char* help,
55         bool required, const char* flag2)
56 {
57     const char* defaults = "";
58     int expectedArgs = 0;
59     char delim = 0;
60     if (flag2)
61     {
62         p->add(defaults, required, expectedArgs, delim,
63                 help, flag1, flag2);
64     }
65     else
66     {
67         p->add(defaults, required, expectedArgs, delim, help, flag1);
68     }
69 }
70 
71 // Wrapper to define flags with arguments with default values.
add_flag(const char * flag1,const char * help,int expected_args,const char * defaults,bool required,const char * flag2)72 void OptionParser::add_flag(const char* flag1, const char* help,
73         int expected_args, const char* defaults, bool required,
74         const char* flag2)
75 {
76     char delim = 0;
77     string strHelp = help;
78     if (strlen(defaults) > 0 && expected_args == 1 && required == false)
79     {
80         strHelp += " (default = " + string(defaults) + ")";
81     }
82     if (flag2)
83     {
84         p->add(defaults, required, expected_args, delim,
85                 strHelp.c_str(), flag1, flag2);
86     }
87     else
88     {
89         p->add(defaults, required, expected_args, delim,
90                 strHelp.c_str(), flag1);
91     }
92 }
93 
add_optional(const char * name,const char * help)94 void OptionParser::add_optional(const char* name, const char* help)
95 {
96     // TODO(BM) Do something with the help field
97     p->optional_.push_back(string(name));
98     p->optionalHelp_.push_back(string(help));
99 }
100 
add_required(const char * name,const char * help)101 void OptionParser::add_required(const char* name, const char* help)
102 {
103     // TODO(BM) Do something with the help field
104     p->required_.push_back(string(name));
105     p->requiredHelp_.push_back(string(help));
106 }
107 
add_settings_options()108 void OptionParser::add_settings_options()
109 {
110     add_required("settings file");
111     add_optional("key");
112     add_optional("value");
113     add_flag("--get", "Print key value in settings file.");
114     add_flag("--set", "Set key value in settings file.");
115 }
116 
check_options(int argc,char ** argv)117 bool OptionParser::check_options(int argc, char** argv)
118 {
119     add_flag("--help", "Display usage instructions and exit.", false);
120     add_flag("--version", "Display the program name/version banner and exit.",
121             false);
122     add_flag("--settings", "Display settings and exit.", false);
123     p->parse(argc, argv);
124     if (is_set("--help"))
125     {
126         print_usage();
127         return false;
128     }
129     if (is_set("--version"))
130     {
131         cout << p->version_ << endl;
132         return false;
133     }
134     if (is_set("--settings"))
135     {
136         cout << string(p->settings_) << endl;
137         return false;
138     }
139     vector<string> bad_opts;
140     if (!p->gotRequired(bad_opts))
141     {
142         for (int i = 0; i < (int)bad_opts.size(); ++i)
143         {
144             error("Missing required option: %s", bad_opts[i].c_str());
145             return false;
146         }
147     }
148     if (!p->gotExpected(bad_opts))
149     {
150         for (int i = 0; i < (int)bad_opts.size(); ++i)
151         {
152             error("Got unexpected number of arguments for option: %s",
153                     bad_opts[i].c_str());
154             return false;
155         }
156     }
157     int min_req_args = (int)p->required_.size();
158     if (num_args() < min_req_args)
159     {
160         error("Expected >= %i input argument(s), %i given", min_req_args,
161                 num_args());
162         return false;
163     }
164     return true;
165 }
166 
error(const char * format,...)167 void OptionParser::error(const char* format, ...)
168 {
169     cerr << "ERROR:\n  ";
170     va_list args;
171     va_start(args, format);
172     vprintf(format, args);
173     va_end(args);
174     cerr << "\n\n";
175     print_usage();
176 }
177 
get_arg(int i) const178 const char* OptionParser::get_arg(int i) const
179 {
180     vector<string*>& first = p->firstArgs;
181     vector<string*>& last = p->lastArgs;
182     if ((int)first.size() - 1 > i)
183     {
184         return first[i + 1]->c_str();
185     }
186     // Requested index is in the last argument set.
187     else if (((int)first.size() - 1 + (int)last.size()) > i)
188     {
189         return last[i - ((int)first.size() - 1)]->c_str();
190     }
191     return 0;
192 }
193 
get_double(const char * name)194 double OptionParser::get_double(const char* name)
195 {
196     double val = 0.0;
197     p->get(name)->getDouble(val);
198     return val;
199 }
200 
get_int(const char * name)201 int OptionParser::get_int(const char* name)
202 {
203     int val = 0;
204     p->get(name)->getInt(val);
205     return val;
206 }
207 
get_string(const char * name)208 const char* OptionParser::get_string(const char* name)
209 {
210     const char* val = 0;
211     p->get(name)->getString(val);
212     return val;
213 }
214 
get_input_files(int min_required,int * num_files)215 const char* const* OptionParser::get_input_files(int min_required,
216         int* num_files)
217 {
218     p->input_files_.clear();
219     vector<string*>& first = p->firstArgs;
220     vector<string*>& last = p->lastArgs;
221     // Note: min_required + 1 because firstArgs[0] is the binary name
222     if (((int)first.size() >= min_required + 1) && ((int)last.size() == 0))
223     {
224         // Note: starts at 1 as index 0 is the binary name.
225         for (int i = 1; i < (int)first.size(); ++i)
226         {
227             p->input_files_.push_back(first[i]->c_str());
228         }
229     }
230     else
231     {
232         for (int i = 0; i < (int)last.size(); ++i)
233         {
234             p->input_files_.push_back(last[i]->c_str());
235         }
236     }
237     *num_files = (int) p->input_files_.size();
238     return (p->input_files_.size() > 0) ? &(p->input_files_)[0] : 0;
239 }
240 
is_set(const char * option)241 int OptionParser::is_set(const char* option)
242 {
243     return p->isSet(option);
244 }
245 
print_usage()246 void OptionParser::print_usage()
247 {
248     string usage;
249     p->syntax = p->title + " [OPTIONS]";
250     for (int i = 0; i < (int)p->required_.size(); ++i)
251     {
252         p->syntax += " <" + p->required_[i] + ">";
253     }
254     for (int i = 0; i < (int)p->optional_.size(); ++i)
255     {
256         p->syntax += " [" + p->optional_[i] + "]";
257     }
258     // TODO(BM) overload here rather than editing the library header...!
259     p->getUsage(usage);
260     cout << usage;
261 }
262 
num_args() const263 int OptionParser::num_args() const
264 {
265     return (int)(p->firstArgs.size() - 1 + p->lastArgs.size());
266 }
267 
set_description(const char * description)268 void OptionParser::set_description(const char* description)
269 {
270     p->overview = description;
271 }
272 
set_settings(const char * text)273 void OptionParser::set_settings(const char* text)
274 {
275     p->settings_ = text;
276 }
277 
set_title(const char * text)278 void OptionParser::set_title(const char* text)
279 {
280     p->title = text;
281 }
282 
set_version(const char * version,bool show)283 void OptionParser::set_version(const char* version, bool show)
284 {
285     if (show)
286     {
287         p->version = version;
288     }
289     p->version_ = version;
290 }
291 
292 } /* namespace oskar */
293