blocxx
CmdLineParser.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2 * Copyright (C) 2005, Vintela, Inc. All rights reserved.
3 * Copyright (C) 2006, Novell, Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of
14 * Vintela, Inc.,
15 * nor Novell, Inc.,
16 * nor the names of its contributors or employees may be used to
17 * endorse or promote products derived from this software without
18 * specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *******************************************************************************/
32 
33 
38 #include "blocxx/BLOCXX_config.h"
39 #include "blocxx/CmdLineParser.hpp"
40 #include "blocxx/Array.hpp"
41 #include "blocxx/ExceptionIds.hpp"
42 #include "blocxx/StringBuffer.hpp"
43 #include "blocxx/Assertion.hpp"
44 
45 #include <algorithm>
46 
47 
48 namespace BLOCXX_NAMESPACE
49 {
50 
52 
53 namespace
54 {
56  struct longOptIs
57  {
58  longOptIs(const String& longOpt) : m_longOpt(longOpt) {}
59 
60  bool operator()(const CmdLineParser::Option& x) const
61  {
62  if (x.longopt != 0)
63  {
64  return m_longOpt == x.longopt;
65  }
66  return false;
67  }
68 
69  String m_longOpt;
70  };
71 
73  struct shortOptIs
74  {
75  shortOptIs(char shortOpt) : m_shortOpt(shortOpt) {}
76 
77  bool operator()(const CmdLineParser::Option& x) const
78  {
79  return m_shortOpt == x.shortopt;
80  }
81 
82  char m_shortOpt;
83  };
84 
85 }
86 
88 CmdLineParser::CmdLineParser(int argc, char const* const* const argv_, const Option* options, EAllowNonOptionArgsFlag allowNonOptionArgs)
89 {
90  BLOCXX_ASSERT(argc > 0); // have to get at least the name
91  BLOCXX_ASSERT(argv_ != 0);
92  BLOCXX_ASSERT(options != 0);
93  char const* const* argv = argv_;
94  char const* const* argvEnd = argv + argc;
95 
96  // m_options is an array terminated by a final entry that has a '\0' shortopt && 0 longopt.
97  const Option* optionsEnd(options);
98  while (optionsEnd->shortopt != '\0' || optionsEnd->longopt != 0)
99  {
100  ++optionsEnd;
101  }
102 
103  // skip the first argv, which is the program name and loop through the rest
104  ++argv;
105  while (argv != argvEnd)
106  {
107  BLOCXX_ASSERT(*argv != 0);
108  String arg(*argv);
109 
110  // look for an option
111  if ((arg.length() >= 2) && (arg[0] == '-'))
112  {
113  const Option* theOpt(0);
114  bool longOpt = false;
115  if (arg[1] == '-')
116  {
117  // long option
118  longOpt = true;
119  arg = arg.substring(2); // erase the --
120  // get rid of =
121  size_t idx = arg.indexOf('=');
122  String argNoVal;
123  if (idx != String::npos)
124  {
125  argNoVal = arg.substring(0, idx);
126  }
127  else
128  {
129  argNoVal = arg;
130  }
131  theOpt = std::find_if (options, optionsEnd, longOptIs(argNoVal));
132  }
133  else // short option
134  {
135  longOpt = false;
136  arg = arg.substring(1); // erase the -
137  theOpt = std::find_if (options, optionsEnd, shortOptIs(arg[0]));
138  }
139 
140  if (theOpt == optionsEnd)
141  {
143  }
144 
145  if (theOpt->argtype == E_NO_ARG)
146  {
147  m_parsedOptions[theOpt->id]; // if one is already there, don't modify it, else insert a new one
148  ++argv;
149  continue;
150  }
151  // now see if we can get the value
152  String val;
153  if ((theOpt->argtype == E_OPTIONAL_ARG) && (theOpt->defaultValue != 0))
154  {
155  val = theOpt->defaultValue;
156  }
157 
158  const char* p = ::strchr(arg.c_str(), '=');
159  if (p)
160  {
161  // get everything after the =
162  val = String(p+1);
163  }
164  else
165  {
166  // a short option can have the value together with it (i.e. -I/opt/vintela/include)
167  if (longOpt == false && arg.length() > 1)
168  {
169  val = arg.substring(1);
170  }
171  // look at the next arg
172  else if (argv+1 != argvEnd)
173  {
174  // Save the next arg if it doesn't start with '-' or if is just '-' by itself.
175  if ( **(argv+1) != '-' || (**(argv+1) == '-' && String(*(argv+1)).length() == 1) )
176  {
177  val = *(argv+1);
178  ++argv;
179  }
180  }
181  }
182 
183  // make sure we got an arg if one is required
184  if (theOpt->argtype == E_REQUIRED_ARG && val.empty())
185  {
187  }
188 
189  m_parsedOptions[theOpt->id].push_back(val);
190  }
191  else
192  {
193  if (allowNonOptionArgs == E_NON_OPTION_ARGS_INVALID)
194  {
196  }
197  else
198  {
200  }
201  }
202  ++argv;
203  }
204 }
205 
207 // static
208 String
209 CmdLineParser::getUsage(const Option* options, unsigned int maxColumns)
210 {
211 // looks like this:
212 // "Options:\n"
213 // " -1, --one first description\n"
214 // " -2, --two [arg] second description (default is optional)\n"
215 // " -3, --three <arg> third description\n"
216 
217  const unsigned int NUM_OPTION_COLUMNS = 28;
218  StringBuffer usage("Options:\n");
219 
220  // m_options is an array terminated by a final entry that has a '\0' shortopt && 0 longOpt.
221  for (const Option* curOption = options; curOption->shortopt != '\0' || curOption->longopt != 0; ++curOption)
222  {
223  StringBuffer curLine;
224  curLine += " ";
225  if (curOption->shortopt != '\0')
226  {
227  curLine += '-';
228  curLine += curOption->shortopt;
229  if (curOption->longopt != 0)
230  {
231  curLine += ", ";
232  }
233  }
234  if (curOption->longopt != 0)
235  {
236  curLine += "--";
237  curLine += curOption->longopt;
238  }
239 
240  if (curOption->argtype == E_REQUIRED_ARG)
241  {
242  curLine += " <arg>";
243  }
244  else if (curOption->argtype == E_OPTIONAL_ARG)
245  {
246  curLine += " [arg]";
247  }
248 
249  size_t bufferlen = (curLine.length() >= NUM_OPTION_COLUMNS-1) ? 1 : (NUM_OPTION_COLUMNS - curLine.length());
250  for (size_t i = 0; i < bufferlen; ++i)
251  {
252  curLine += ' ';
253  }
254 
255  if (curOption->description != 0)
256  {
257  curLine += curOption->description;
258  }
259 
260  if (curOption->defaultValue != 0)
261  {
262  curLine += " (default is ";
263  curLine += curOption->defaultValue;
264  curLine += ')';
265  }
266 
267  // now if curLine is too long or contains newlines, we need to wrap it.
268  while (curLine.length() > maxColumns || curLine.toString().indexOf('\n') != String::npos)
269  {
270  String curLineStr(curLine.toString());
271  // first we look for a \n to cut at
272  size_t newlineIdx = curLineStr.indexOf('\n');
273 
274  // next look for the last space to cut at
275  size_t lastSpaceIdx = curLineStr.lastIndexOf(' ', maxColumns);
276 
277  size_t cutIdx = 0;
278  size_t nextLineBeginIdx = 0;
279  if (newlineIdx <= maxColumns)
280  {
281  cutIdx = newlineIdx;
282  nextLineBeginIdx = newlineIdx + 1; // skip the newline
283  }
284  else if (lastSpaceIdx > NUM_OPTION_COLUMNS)
285  {
286  cutIdx = lastSpaceIdx;
287  nextLineBeginIdx = lastSpaceIdx + 1; // skip the space
288  }
289  else
290  {
291  // no space to cut it, just cut it in the middle of a word
292  cutIdx = maxColumns;
293  nextLineBeginIdx = maxColumns;
294  }
295 
296  // found a place to cut, so do it.
297  usage += curLineStr.substring(0, cutIdx);
298  usage += '\n';
299 
300  // cut out the line from curLine
301  StringBuffer spaces;
302  for (size_t i = 0; i < NUM_OPTION_COLUMNS; ++i)
303  {
304  spaces += ' ';
305  }
306  curLine = spaces.releaseString() + curLineStr.substring(nextLineBeginIdx);
307  }
308 
309  curLine += '\n';
310  usage += curLine;
311  }
312  return usage.releaseString();
313 }
314 
316 String
317 CmdLineParser::getOptionValue(int id, const char* defaultValue) const
318 {
320  if (ci != m_parsedOptions.end() && ci->second.size() > 0)
321  {
322  // grab the last one
323  return ci->second[ci->second.size()-1];
324  }
325  return defaultValue;
326 }
327 
329 String
330 CmdLineParser::mustGetOptionValue(int id, const char* exceptionMessage) const
331 {
333  if (ci != m_parsedOptions.end() && ci->second.size() > 0)
334  {
335  // grab the last one
336  return ci->second[ci->second.size()-1];
337  }
339 }
340 
344 {
345  StringArray rval;
347  if (ci != m_parsedOptions.end() && ci->second.size() > 0)
348  {
349  rval = ci->second;
350  }
351  return rval;
352 }
353 
356 CmdLineParser::mustGetOptionValueList(int id, const char* exceptionMessage) const
357 {
359  if (ci != m_parsedOptions.end() && ci->second.size() > 0)
360  {
361  return ci->second;
362  }
364 }
365 
367 bool
368 CmdLineParser::isSet(int id) const
369 {
370  return m_parsedOptions.count(id) > 0;
371 }
372 
374 size_t
376 {
377  return m_nonOptionArgs.size();
378 }
379 
381 String
383 {
384  return m_nonOptionArgs[n];
385 }
386 
390 {
391  return m_nonOptionArgs;
392 }
393 
394 
395 
396 } // end namespace BLOCXX_NAMESPACE
397 
398 
399