1 | // LAF Base Library |
2 | // Copyright (c) 2020-2021 Igara Studio S.A. |
3 | // Copyright (c) 2001-2017 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "base/program_options.h" |
13 | |
14 | #include <algorithm> |
15 | #include <sstream> |
16 | #include <iomanip> |
17 | |
18 | namespace base { |
19 | |
20 | |
21 | struct same_name { |
22 | const std::string& name; |
23 | same_name(const std::string& name) : name(name) { } |
24 | bool operator()(const ProgramOptions::Option* a) { |
25 | return (a->name() == name || |
26 | a->alias() == name); |
27 | } |
28 | }; |
29 | |
30 | struct same_mnemonic { |
31 | char mnemonic; |
32 | same_mnemonic(char mnemonic) : mnemonic(mnemonic) { } |
33 | bool operator()(const ProgramOptions::Option* a) { |
34 | return a->mnemonic() == mnemonic; |
35 | } |
36 | }; |
37 | |
38 | ProgramOptions::ProgramOptions() |
39 | { |
40 | } |
41 | |
42 | ProgramOptions::~ProgramOptions() |
43 | { |
44 | for (OptionList::const_iterator |
45 | it=m_options.begin(), end=m_options.end(); it != end; ++it) |
46 | delete *it; |
47 | } |
48 | |
49 | ProgramOptions::Option& ProgramOptions::add(const std::string& name) |
50 | { |
51 | Option* option = new Option(name); |
52 | m_options.push_back(option); |
53 | return *option; |
54 | } |
55 | |
56 | void ProgramOptions::parse(int argc, const char* argv[]) |
57 | { |
58 | for (int i=1; i<argc; ++i) { |
59 | std::string arg(argv[i]); |
60 | |
61 | // n = number of dashes ('-') at the beginning of the argument. |
62 | size_t n = 0; |
63 | for (; arg[n] == '-'; ++n) |
64 | ; |
65 | size_t len = arg.size()-n; |
66 | |
67 | // Ignore process serial number argument (-psn...) when the app is run from command line |
68 | #if LAF_MACOS |
69 | if (arg.size() >= 4 && arg.substr(0, 4) == "-psn" ) |
70 | continue; |
71 | #endif |
72 | |
73 | if ((n > 0) && (len > 0)) { |
74 | // First we try to find the -optionName=optionValue pair |
75 | std::string optionName; |
76 | std::string optionValue; |
77 | size_t equalSignPos = arg.find('=', n); |
78 | |
79 | if (equalSignPos != std::string::npos) { |
80 | optionName = arg.substr(n, equalSignPos-n); |
81 | optionValue = arg.substr(equalSignPos+1); |
82 | } |
83 | else { |
84 | optionName = arg.substr(n); |
85 | } |
86 | |
87 | OptionList::iterator it = |
88 | find_if(m_options.begin(), m_options.end(), same_name(optionName)); |
89 | |
90 | // If we've found the -optionName or --optionName, we use it |
91 | if (it != m_options.end()) { |
92 | Option* option = *it; |
93 | |
94 | if (option->doesRequireValue()) { |
95 | // If the option was specified without '=', we can get the |
96 | // value from the next argument. |
97 | if (equalSignPos == std::string::npos) { |
98 | if (i+1 >= argc) { |
99 | std::stringstream msg; |
100 | msg << "Missing value in '--" << optionName |
101 | << "=" << option->getValueName() << "' option specification" ; |
102 | throw ProgramOptionNeedsValue(msg.str()); |
103 | } |
104 | optionValue = argv[++i]; |
105 | } |
106 | } |
107 | |
108 | m_values.push_back(Value(option, optionValue)); |
109 | } |
110 | // In case that the user specify to use mnemonics (it's options |
111 | // with one dash, e.g. -abc to enable -a, -b and -c options) |
112 | else if (n == 1) { |
113 | char usedBy = 0; |
114 | |
115 | for (size_t j=1; j<arg.size(); ++j) { |
116 | OptionList::iterator it = |
117 | find_if(m_options.begin(), m_options.end(), same_mnemonic(arg[j])); |
118 | |
119 | if (it == m_options.end()) { |
120 | std::stringstream msg; |
121 | // Show the whole option (arg) as invalid just in case the |
122 | // user specified a "-long-option" with one "-" (instead |
123 | // of mnemonics). |
124 | msg << "Invalid option " << arg; |
125 | throw InvalidProgramOption(msg.str()); |
126 | } |
127 | |
128 | Option* option = *it; |
129 | std::string optionValue; |
130 | |
131 | if (option->doesRequireValue()) { |
132 | if (usedBy != 0) { |
133 | std::stringstream msg; |
134 | msg << "You cannot use '-" << option->mnemonic() |
135 | << "' and '-" << usedBy << "' " |
136 | << "together, both options need one extra argument" ; |
137 | throw InvalidProgramOptionsCombination(msg.str()); |
138 | } |
139 | |
140 | if (i+1 >= argc) { |
141 | std::stringstream msg; |
142 | msg << "Option '-" << option->mnemonic() |
143 | << "' needs one extra argument" ; |
144 | throw ProgramOptionNeedsValue(msg.str()); |
145 | } |
146 | |
147 | // Set the value specified for this argument |
148 | optionValue = argv[++i]; |
149 | usedBy = option->mnemonic(); |
150 | } |
151 | |
152 | m_values.push_back(Value(option, optionValue)); |
153 | } |
154 | } |
155 | else { |
156 | std::stringstream msg; |
157 | msg << "Invalid option " << arg; |
158 | throw InvalidProgramOption(msg.str()); |
159 | } |
160 | } |
161 | // Add values without a related option. |
162 | else { |
163 | m_values.push_back(Value(NULL, arg)); |
164 | } |
165 | } |
166 | } |
167 | |
168 | void ProgramOptions::reset() |
169 | { |
170 | m_values.clear(); |
171 | } |
172 | |
173 | bool ProgramOptions::enabled(const Option& option) const |
174 | { |
175 | for (const auto& value : m_values) { |
176 | if (value.option() == &option) |
177 | return true; |
178 | } |
179 | return false; |
180 | } |
181 | |
182 | std::string ProgramOptions::value_of(const Option& option) const |
183 | { |
184 | for (const auto& value : m_values) { |
185 | if (value.option() == &option) |
186 | return value.value(); |
187 | } |
188 | return "" ; |
189 | } |
190 | |
191 | } // namespace base |
192 | |
193 | std::ostream& operator<<(std::ostream& os, const base::ProgramOptions& po) |
194 | { |
195 | std::size_t maxOptionWidth = 0; |
196 | for (base::ProgramOptions::OptionList::const_iterator |
197 | it=po.options().begin(), end=po.options().end(); it != end; ++it) { |
198 | const base::ProgramOptions::Option* option = *it; |
199 | std::size_t optionWidth = |
200 | 6+std::max(option->name().size(), option->alias().size())+1+ |
201 | (option->doesRequireValue() ? option->getValueName().size()+1: 0); |
202 | |
203 | if (maxOptionWidth < optionWidth) |
204 | maxOptionWidth = optionWidth; |
205 | } |
206 | |
207 | for (base::ProgramOptions::OptionList::const_iterator |
208 | it=po.options().begin(), end=po.options().end(); it != end; ++it) { |
209 | const base::ProgramOptions::Option* option = *it; |
210 | std::size_t optionWidth = 6+option->name().size()+1+ |
211 | (option->doesRequireValue() ? option->getValueName().size()+1: 0); |
212 | |
213 | if (option->mnemonic() != 0) |
214 | os << std::setw(3) << '-' << option->mnemonic() << ", " ; |
215 | else |
216 | os << std::setw(6) << ' '; |
217 | os << "--" << option->name(); |
218 | if (option->doesRequireValue()) |
219 | os << " " << option->getValueName(); |
220 | |
221 | // Show alias |
222 | if (!option->alias().empty()) { |
223 | os << " or\n" |
224 | << std::setw(6) << ' ' |
225 | << "--" << option->alias(); |
226 | if (option->doesRequireValue()) |
227 | os << " " << option->getValueName(); |
228 | |
229 | optionWidth = 6+option->alias().size()+1+ |
230 | (option->doesRequireValue() ? option->getValueName().size()+1: 0); |
231 | } |
232 | |
233 | if (!option->description().empty()) { |
234 | bool multilines = (option->description().find('\n') != std::string::npos); |
235 | |
236 | if (!multilines) { |
237 | os << std::setw(maxOptionWidth - optionWidth + 1) << ' ' << option->description() |
238 | << "\n" ; |
239 | } |
240 | else { |
241 | std::istringstream s(option->description()); |
242 | std::string line; |
243 | if (std::getline(s, line)) { |
244 | os << std::setw(maxOptionWidth - optionWidth + 1) << ' ' << line << '\n'; |
245 | while (std::getline(s, line)) { |
246 | os << std::setw(maxOptionWidth+2) << ' ' << line << '\n'; |
247 | } |
248 | } |
249 | } |
250 | } |
251 | else |
252 | os << "\n" ; |
253 | } |
254 | |
255 | return os; |
256 | } |
257 | |