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
18namespace base {
19
20
21struct 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
30struct 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
38ProgramOptions::ProgramOptions()
39{
40}
41
42ProgramOptions::~ProgramOptions()
43{
44 for (OptionList::const_iterator
45 it=m_options.begin(), end=m_options.end(); it != end; ++it)
46 delete *it;
47}
48
49ProgramOptions::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
56void 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
168void ProgramOptions::reset()
169{
170 m_values.clear();
171}
172
173bool 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
182std::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
193std::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