Ticket #4512: options_description.cpp

File options_description.cpp, 21.2 KB (added by Soren Soe <soren.soe@…>, 12 years ago)
Line 
1// Copyright Vladimir Prus 2002-2004.
2// Copyright Bertolt Mildner 2004.
3// Distributed under the Boost Software License, Version 1.0.
4// (See accompanying file LICENSE_1_0.txt
5// or copy at http://www.boost.org/LICENSE_1_0.txt)
6
7
8#define BOOST_PROGRAM_OPTIONS_SOURCE
9#include <boost/program_options/config.hpp>
10#include <boost/program_options/options_description.hpp>
11// FIXME: this is only to get multiple_occurences class
12// should move that to a separate headers.
13#include <boost/program_options/parsers.hpp>
14
15
16#include <boost/lexical_cast.hpp>
17#include <boost/tokenizer.hpp>
18#include <boost/detail/workaround.hpp>
19#include <boost/throw_exception.hpp>
20
21#include <cassert>
22#include <climits>
23#include <cstring>
24#include <cstdarg>
25#include <sstream>
26#include <iterator>
27using namespace std;
28
29namespace boost { namespace program_options {
30
31 namespace {
32
33 template< class charT >
34 std::basic_string< charT > tolower_(const std::basic_string< charT >& str)
35 {
36 std::basic_string< charT > result;
37 for (typename std::basic_string< charT >::size_type i = 0; i < str.size(); ++i)
38 {
39 result.append(1, static_cast< charT >(std::tolower(str[i])));
40 }
41 return result;
42 }
43
44 } // unnamed namespace
45
46
47 option_description::option_description()
48 {
49 }
50
51 option_description::
52 option_description(const char* name,
53 const value_semantic* s)
54 : m_value_semantic(s)
55 {
56 this->set_name(name);
57 }
58
59
60 option_description::
61 option_description(const char* name,
62 const value_semantic* s,
63 const char* description)
64 : m_description(description), m_value_semantic(s)
65 {
66 this->set_name(name);
67 }
68
69 option_description::~option_description()
70 {
71 }
72
73 option_description::match_result
74 option_description::match(const std::string& option,
75 bool approx,
76 bool long_ignore_case,
77 bool short_ignore_case) const
78 {
79 match_result result = no_match;
80
81 std::string local_long_name((long_ignore_case ? tolower_(m_long_name) : m_long_name));
82
83 if (!local_long_name.empty()) {
84
85 std::string local_option = (long_ignore_case ? tolower_(option) : option);
86
87 if (*local_long_name.rbegin() == '*')
88 {
89 // The name ends with '*'. Any specified name with the given
90 // prefix is OK.
91 if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
92 == 0)
93 result = approximate_match;
94 }
95
96 if (local_long_name == local_option)
97 {
98 result = full_match;
99 }
100 else if (approx)
101 {
102 if (local_long_name.find(local_option) == 0)
103 {
104 result = approximate_match;
105 }
106 }
107 }
108
109 if (result != full_match)
110 {
111 std::string local_option(short_ignore_case ? tolower_(option) : option);
112 std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name);
113
114 if (local_short_name == local_option)
115 {
116 result = full_match;
117 }
118 }
119
120 return result;
121 }
122
123 const std::string&
124 option_description::key(const std::string& option) const
125 {
126 if (!m_long_name.empty())
127 if (m_long_name.find('*') != string::npos)
128 // The '*' character means we're long_name
129 // matches only part of the input. So, returning
130 // long name will remove some of the information,
131 // and we have to return the option as specified
132 // in the source.
133 return option;
134 else
135 return m_long_name;
136 else
137 return m_short_name;
138 }
139
140 const std::string&
141 option_description::long_name() const
142 {
143 return m_long_name;
144 }
145
146 option_description&
147 option_description::set_name(const char* _name)
148 {
149 std::string name(_name);
150 string::size_type n = name.find(',');
151 if (n != string::npos) {
152 assert(n == name.size()-2);
153 m_long_name = name.substr(0, n);
154 m_short_name = '-' + name.substr(n+1,1);
155 } else {
156 m_long_name = name;
157 }
158 return *this;
159 }
160
161 const std::string&
162 option_description::description() const
163 {
164 return m_description;
165 }
166
167 shared_ptr<const value_semantic>
168 option_description::semantic() const
169 {
170 return m_value_semantic;
171 }
172
173 std::string
174 option_description::format_name() const
175 {
176 if (!m_short_name.empty())
177 return string(m_short_name).append(" [ --").
178 append(m_long_name).append(" ]");
179 else
180 return string("--").append(m_long_name);
181 }
182
183 std::string
184 option_description::format_parameter() const
185 {
186 if (m_value_semantic->max_tokens() != 0)
187 return m_value_semantic->name();
188 else
189 return "";
190 }
191
192 options_description_easy_init::
193 options_description_easy_init(options_description* owner)
194 : owner(owner)
195 {}
196
197 options_description_easy_init&
198 options_description_easy_init::
199 operator()(const char* name,
200 const char* description)
201 {
202 // Create untypes semantic which accepts zero tokens: i.e.
203 // no value can be specified on command line.
204 // FIXME: does not look exception-safe
205 shared_ptr<option_description> d(
206 new option_description(name, new untyped_value(true), description));
207
208 owner->add(d);
209 return *this;
210 }
211
212 options_description_easy_init&
213 options_description_easy_init::
214 operator()(const char* name,
215 const value_semantic* s)
216 {
217 shared_ptr<option_description> d(new option_description(name, s));
218 owner->add(d);
219 return *this;
220 }
221
222 options_description_easy_init&
223 options_description_easy_init::
224 operator()(const char* name,
225 const value_semantic* s,
226 const char* description)
227 {
228 shared_ptr<option_description> d(new option_description(name, s, description));
229
230 owner->add(d);
231 return *this;
232 }
233
234 const unsigned options_description::m_default_line_length = 80;
235
236 options_description::options_description(unsigned line_length,
237 unsigned min_description_length)
238 : m_line_length(line_length)
239 , m_min_description_length(min_description_length)
240 {
241 // we require a space between the option and description parts, so add 1.
242 assert(m_min_description_length < m_line_length - 1);
243 }
244
245 options_description::options_description(const std::string& caption,
246 unsigned line_length,
247 unsigned min_description_length)
248 : m_caption(caption)
249 , m_line_length(line_length)
250 , m_min_description_length(min_description_length)
251 {
252 // we require a space between the option and description parts, so add 1.
253 assert(m_min_description_length < m_line_length - 1);
254 }
255
256 void
257 options_description::add(shared_ptr<option_description> desc)
258 {
259 m_options.push_back(desc);
260 belong_to_group.push_back(false);
261 }
262
263 options_description&
264 options_description::add(const options_description& desc)
265 {
266 shared_ptr<options_description> d(new options_description(desc));
267 groups.push_back(d);
268
269 for (size_t i = 0; i < desc.m_options.size(); ++i) {
270 add(desc.m_options[i]);
271 belong_to_group.back() = true;
272 }
273
274 return *this;
275 }
276
277 options_description_easy_init
278 options_description::add_options()
279 {
280 return options_description_easy_init(this);
281 }
282
283 const option_description&
284 options_description::find(const std::string& name,
285 bool approx,
286 bool long_ignore_case,
287 bool short_ignore_case) const
288 {
289 const option_description* d = find_nothrow(name, approx,
290 long_ignore_case, short_ignore_case);
291 if (!d)
292 boost::throw_exception(unknown_option(name));
293 return *d;
294 }
295
296 const std::vector< shared_ptr<option_description> >&
297 options_description::options() const
298 {
299 return m_options;
300 }
301
302 const option_description*
303 options_description::find_nothrow(const std::string& name,
304 bool approx,
305 bool long_ignore_case,
306 bool short_ignore_case) const
307 {
308 int found = -1;
309 vector<string> approximate_matches;
310 vector<string> full_matches;
311
312 // We use linear search because matching specified option
313 // name with the declaraed option name need to take care about
314 // case sensitivity and trailing '*' and so we can't use simple map.
315 for(unsigned i = 0; i < m_options.size(); ++i)
316 {
317 option_description::match_result r =
318 m_options[i]->match(name, approx, long_ignore_case, short_ignore_case);
319
320 if (r == option_description::no_match)
321 continue;
322
323 if (r == option_description::full_match)
324 {
325 full_matches.push_back(m_options[i]->key(name));
326
327 // Use this full match regardless of other matches found.
328 found = i;
329 }
330 else
331 {
332 // FIXME: the use of 'key' here might not
333 // be the best approach.
334 approximate_matches.push_back(m_options[i]->key(name));
335
336 // If this is the first match found the use it. If
337 // a later full match is found that will be used instead.
338 if (found == -1)
339 found = i;
340 }
341 }
342 if (full_matches.size() > 1)
343 boost::throw_exception(
344 ambiguous_option(name, full_matches));
345
346 // If we have a full match, and an approximate match,
347 // ignore approximate match instead of reporting error.
348 // Say, if we have options "all" and "all-chroots", then
349 // "--all" on the command line should select the first one,
350 // without ambiguity.
351 if (full_matches.empty() && approximate_matches.size() > 1)
352 boost::throw_exception(
353 ambiguous_option(name, approximate_matches));
354
355 return found>=0
356 ? m_options[found].get()
357 : 0;
358 }
359
360 BOOST_PROGRAM_OPTIONS_DECL
361 std::ostream& operator<<(std::ostream& os, const options_description& desc)
362 {
363 desc.print(os);
364 return os;
365 }
366
367 namespace {
368
369 /* Given a string 'par', that contains no newline characters
370 outputs it to 'os' with wordwrapping, that is, as several
371 line.
372
373 Each output line starts with 'indent' space characters,
374 following by characters from 'par'. The total length of
375 line is no longer than 'line_length'.
376
377 */
378 void format_paragraph(std::ostream& os,
379 std::string par,
380 unsigned indent,
381 unsigned line_length)
382 {
383 // Through reminder of this function, 'line_length' will
384 // be the length available for characters, not including
385 // indent.
386 assert(indent < line_length);
387 line_length -= indent;
388
389 // index of tab (if present) is used as additional indent relative
390 // to first_column_width if paragrapth is spanned over multiple
391 // lines if tab is not on first line it is ignored
392 string::size_type par_indent = par.find('\t');
393
394 if (par_indent == string::npos)
395 {
396 par_indent = 0;
397 }
398 else
399 {
400 // only one tab per paragraph allowed
401 if (count(par.begin(), par.end(), '\t') > 1)
402 {
403 boost::throw_exception(program_options::error(
404 "Only one tab per paragraph is allowed"));
405 }
406
407 // erase tab from string
408 par.erase(par_indent, 1);
409
410 // this assert may fail due to user error or
411 // environment conditions!
412 assert(par_indent < line_length);
413
414 // ignore tab if not on first line
415 if (par_indent >= line_length)
416 {
417 par_indent = 0;
418 }
419 }
420
421 if (par.size() < line_length)
422 {
423 os << par;
424 }
425 else
426 {
427 string::const_iterator line_begin = par.begin();
428 const string::const_iterator par_end = par.end();
429
430 bool first_line = true; // of current paragraph!
431
432 while (line_begin < par_end) // paragraph lines
433 {
434 if (!first_line)
435 {
436 // If line starts with space, but second character
437 // is not space, remove the leading space.
438 // We don't remove double spaces because those
439 // might be intentianal.
440 if ((*line_begin == ' ') &&
441 ((line_begin + 1 < par_end) &&
442 (*(line_begin + 1) != ' ')))
443 {
444 line_begin += 1; // line_begin != line_end
445 }
446 }
447
448 // Take care to never increment the iterator past
449 // the end, since MSVC 8.0 (brokenly), assumes that
450 // doing that, even if no access happens, is a bug.
451 unsigned remaining = distance(line_begin, par_end);
452 string::const_iterator line_end = line_begin +
453 ((remaining < line_length) ? remaining : line_length);
454
455 // prevent chopped words
456 // Is line_end between two non-space characters?
457 if ((*(line_end - 1) != ' ') &&
458 ((line_end < par_end) && (*line_end != ' ')))
459 {
460 // find last ' ' in the second half of the current paragraph line
461 string::const_iterator last_space =
462 find(reverse_iterator<string::const_iterator>(line_end),
463 reverse_iterator<string::const_iterator>(line_begin),
464 ' ')
465 .base();
466
467 if (last_space != line_begin)
468 {
469 // is last_space within the second half ot the
470 // current line
471 if (static_cast<unsigned>(distance(last_space, line_end)) <
472 (line_length / 2))
473 {
474 line_end = last_space;
475 }
476 }
477 } // prevent chopped words
478
479 // write line to stream
480 copy(line_begin, line_end, ostream_iterator<char>(os));
481
482 if (first_line)
483 {
484 indent += par_indent;
485 line_length -= par_indent; // there's less to work with now
486 first_line = false;
487 }
488
489 // more lines to follow?
490 if (line_end != par_end)
491 {
492 os << '\n';
493
494 for(unsigned pad = indent; pad > 0; --pad)
495 {
496 os.put(' ');
497 }
498 }
499
500 // next line starts after of this line
501 line_begin = line_end;
502 } // paragraph lines
503 }
504 }
505
506 void format_description(std::ostream& os,
507 const std::string& desc,
508 unsigned first_column_width,
509 unsigned line_length)
510 {
511 // we need to use one char less per line to work correctly if actual
512 // console has longer lines
513 assert(line_length > 1);
514 if (line_length > 1)
515 {
516 --line_length;
517 }
518
519 // line_length must be larger than first_column_width
520 // this assert may fail due to user error or environment conditions!
521 assert(line_length > first_column_width);
522
523 // Note: can't use 'tokenizer' as name of typedef -- borland
524 // will consider uses of 'tokenizer' below as uses of
525 // boost::tokenizer, not typedef.
526 typedef boost::tokenizer<boost::char_separator<char> > tok;
527
528 tok paragraphs(
529 desc,
530 char_separator<char>("\n", "", boost::keep_empty_tokens));
531
532 tok::const_iterator par_iter = paragraphs.begin();
533 const tok::const_iterator par_end = paragraphs.end();
534
535 while (par_iter != par_end) // paragraphs
536 {
537 format_paragraph(os, *par_iter, first_column_width,
538 line_length);
539
540 ++par_iter;
541
542 // prepair next line if any
543 if (par_iter != par_end)
544 {
545 os << '\n';
546
547 for(unsigned pad = first_column_width; pad > 0; --pad)
548 {
549 os.put(' ');
550 }
551 }
552 } // paragraphs
553 }
554
555 void format_one(std::ostream& os, const option_description& opt,
556 unsigned first_column_width, unsigned line_length)
557 {
558 stringstream ss;
559 ss << " " << opt.format_name() << ' ' << opt.format_parameter();
560
561 // Don't use ss.rdbuf() since g++ 2.96 is buggy on it.
562 os << ss.str();
563
564 if (!opt.description().empty())
565 {
566 if (ss.str().size() >= first_column_width)
567 {
568 os.put('\n'); // first column is too long, lets put description in new line
569 for (unsigned pad = first_column_width; pad > 0; --pad)
570 {
571 os.put(' ');
572 }
573 } else {
574 for(unsigned pad = first_column_width - ss.str().size(); pad > 0; --pad)
575 {
576 os.put(' ');
577 }
578 }
579
580 format_description(os, opt.description(),
581 first_column_width, line_length);
582 }
583 }
584 }
585
586 void
587 options_description::print(std::ostream& os) const
588 {
589 if (!m_caption.empty())
590 os << m_caption << ":\n";
591
592 /* Find the maximum width of the option column */
593 unsigned width(23);
594 unsigned i; // vc6 has broken for loop scoping
595 for (i = 0; i < m_options.size(); ++i)
596 {
597 const option_description& opt = *m_options[i];
598 stringstream ss;
599 ss << " " << opt.format_name() << ' ' << opt.format_parameter();
600 width = (max)(width, static_cast<unsigned>(ss.str().size()));
601 }
602 /* this is the column were description should start, if first
603 column is longer, we go to a new line */
604 const unsigned start_of_description_column = m_line_length - m_min_description_length;
605
606 width = (min)(width, start_of_description_column-1);
607
608 /* add an additional space to improve readability */
609 ++width;
610
611 /* The options formatting style is stolen from Subversion. */
612 for (i = 0; i < m_options.size(); ++i)
613 {
614 if (belong_to_group[i])
615 continue;
616
617 const option_description& opt = *m_options[i];
618
619 format_one(os, opt, width, m_line_length);
620
621 os << "\n";
622 }
623
624 for (unsigned j = 0; j < groups.size(); ++j) {
625 os << "\n" << *groups[j];
626 }
627 }
628
629}}