]> git.proxmox.com Git - ceph.git/blob - ceph/src/boost/libs/program_options/src/options_description.cpp
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / src / boost / libs / program_options / src / options_description.cpp
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_occurrences 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>
27 using namespace std;
28
29 namespace 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 std::string
141 option_description::canonical_display_name(int prefix_style) const
142 {
143 if (!m_long_name.empty())
144 {
145 if (prefix_style == command_line_style::allow_long)
146 return "--" + m_long_name;
147 if (prefix_style == command_line_style::allow_long_disguise)
148 return "-" + m_long_name;
149 }
150 // sanity check: m_short_name[0] should be '-' or '/'
151 if (m_short_name.length() == 2)
152 {
153 if (prefix_style == command_line_style::allow_slash_for_short)
154 return string("/") + m_short_name[1];
155 if (prefix_style == command_line_style::allow_dash_for_short)
156 return string("-") + m_short_name[1];
157 }
158 if (!m_long_name.empty())
159 return m_long_name;
160 else
161 return m_short_name;
162 }
163
164
165 const std::string&
166 option_description::long_name() const
167 {
168 return m_long_name;
169 }
170
171 option_description&
172 option_description::set_name(const char* _name)
173 {
174 std::string name(_name);
175 string::size_type n = name.find(',');
176 if (n != string::npos) {
177 assert(n == name.size()-2);
178 m_long_name = name.substr(0, n);
179 m_short_name = '-' + name.substr(n+1,1);
180 } else {
181 m_long_name = name;
182 }
183 return *this;
184 }
185
186 const std::string&
187 option_description::description() const
188 {
189 return m_description;
190 }
191
192 shared_ptr<const value_semantic>
193 option_description::semantic() const
194 {
195 return m_value_semantic;
196 }
197
198 std::string
199 option_description::format_name() const
200 {
201 if (!m_short_name.empty())
202 {
203 return m_long_name.empty()
204 ? m_short_name
205 : string(m_short_name).append(" [ --").
206 append(m_long_name).append(" ]");
207 }
208 return string("--").append(m_long_name);
209 }
210
211 std::string
212 option_description::format_parameter() const
213 {
214 if (m_value_semantic->max_tokens() != 0)
215 return m_value_semantic->name();
216 else
217 return "";
218 }
219
220 options_description_easy_init::
221 options_description_easy_init(options_description* owner)
222 : owner(owner)
223 {}
224
225 options_description_easy_init&
226 options_description_easy_init::
227 operator()(const char* name,
228 const char* description)
229 {
230 // Create untypes semantic which accepts zero tokens: i.e.
231 // no value can be specified on command line.
232 // FIXME: does not look exception-safe
233 shared_ptr<option_description> d(
234 new option_description(name, new untyped_value(true), description));
235
236 owner->add(d);
237 return *this;
238 }
239
240 options_description_easy_init&
241 options_description_easy_init::
242 operator()(const char* name,
243 const value_semantic* s)
244 {
245 shared_ptr<option_description> d(new option_description(name, s));
246 owner->add(d);
247 return *this;
248 }
249
250 options_description_easy_init&
251 options_description_easy_init::
252 operator()(const char* name,
253 const value_semantic* s,
254 const char* description)
255 {
256 shared_ptr<option_description> d(new option_description(name, s, description));
257
258 owner->add(d);
259 return *this;
260 }
261
262 const unsigned options_description::m_default_line_length = 80;
263
264 options_description::options_description(unsigned line_length,
265 unsigned min_description_length)
266 : m_line_length(line_length)
267 , m_min_description_length(min_description_length)
268 {
269 // we require a space between the option and description parts, so add 1.
270 assert(m_min_description_length < m_line_length - 1);
271 }
272
273 options_description::options_description(const std::string& caption,
274 unsigned line_length,
275 unsigned min_description_length)
276 : m_caption(caption)
277 , m_line_length(line_length)
278 , m_min_description_length(min_description_length)
279 {
280 // we require a space between the option and description parts, so add 1.
281 assert(m_min_description_length < m_line_length - 1);
282 }
283
284 void
285 options_description::add(shared_ptr<option_description> desc)
286 {
287 m_options.push_back(desc);
288 belong_to_group.push_back(false);
289 }
290
291 options_description&
292 options_description::add(const options_description& desc)
293 {
294 shared_ptr<options_description> d(new options_description(desc));
295 groups.push_back(d);
296
297 for (size_t i = 0; i < desc.m_options.size(); ++i) {
298 add(desc.m_options[i]);
299 belong_to_group.back() = true;
300 }
301
302 return *this;
303 }
304
305 options_description_easy_init
306 options_description::add_options()
307 {
308 return options_description_easy_init(this);
309 }
310
311 const option_description&
312 options_description::find(const std::string& name,
313 bool approx,
314 bool long_ignore_case,
315 bool short_ignore_case) const
316 {
317 const option_description* d = find_nothrow(name, approx,
318 long_ignore_case, short_ignore_case);
319 if (!d)
320 boost::throw_exception(unknown_option());
321 return *d;
322 }
323
324 const std::vector< shared_ptr<option_description> >&
325 options_description::options() const
326 {
327 return m_options;
328 }
329
330 const option_description*
331 options_description::find_nothrow(const std::string& name,
332 bool approx,
333 bool long_ignore_case,
334 bool short_ignore_case) const
335 {
336 shared_ptr<option_description> found;
337 bool had_full_match = false;
338 vector<string> approximate_matches;
339 vector<string> full_matches;
340
341 // We use linear search because matching specified option
342 // name with the declared option name need to take care about
343 // case sensitivity and trailing '*' and so we can't use simple map.
344 for(unsigned i = 0; i < m_options.size(); ++i)
345 {
346 option_description::match_result r =
347 m_options[i]->match(name, approx, long_ignore_case, short_ignore_case);
348
349 if (r == option_description::no_match)
350 continue;
351
352 if (r == option_description::full_match)
353 {
354 full_matches.push_back(m_options[i]->key(name));
355 found = m_options[i];
356 had_full_match = true;
357 }
358 else
359 {
360 // FIXME: the use of 'key' here might not
361 // be the best approach.
362 approximate_matches.push_back(m_options[i]->key(name));
363 if (!had_full_match)
364 found = m_options[i];
365 }
366 }
367 if (full_matches.size() > 1)
368 boost::throw_exception(ambiguous_option(full_matches));
369
370 // If we have a full match, and an approximate match,
371 // ignore approximate match instead of reporting error.
372 // Say, if we have options "all" and "all-chroots", then
373 // "--all" on the command line should select the first one,
374 // without ambiguity.
375 if (full_matches.empty() && approximate_matches.size() > 1)
376 boost::throw_exception(ambiguous_option(approximate_matches));
377
378 return found.get();
379 }
380
381 BOOST_PROGRAM_OPTIONS_DECL
382 std::ostream& operator<<(std::ostream& os, const options_description& desc)
383 {
384 desc.print(os);
385 return os;
386 }
387
388 namespace {
389
390 /* Given a string 'par', that contains no newline characters
391 outputs it to 'os' with wordwrapping, that is, as several
392 line.
393
394 Each output line starts with 'indent' space characters,
395 following by characters from 'par'. The total length of
396 line is no longer than 'line_length'.
397
398 */
399 void format_paragraph(std::ostream& os,
400 std::string par,
401 unsigned indent,
402 unsigned line_length)
403 {
404 // Through reminder of this function, 'line_length' will
405 // be the length available for characters, not including
406 // indent.
407 assert(indent < line_length);
408 line_length -= indent;
409
410 // index of tab (if present) is used as additional indent relative
411 // to first_column_width if paragrapth is spanned over multiple
412 // lines if tab is not on first line it is ignored
413 string::size_type par_indent = par.find('\t');
414
415 if (par_indent == string::npos)
416 {
417 par_indent = 0;
418 }
419 else
420 {
421 // only one tab per paragraph allowed
422 if (count(par.begin(), par.end(), '\t') > 1)
423 {
424 boost::throw_exception(program_options::error(
425 "Only one tab per paragraph is allowed in the options description"));
426 }
427
428 // erase tab from string
429 par.erase(par_indent, 1);
430
431 // this assert may fail due to user error or
432 // environment conditions!
433 assert(par_indent < line_length);
434
435 // ignore tab if not on first line
436 if (par_indent >= line_length)
437 {
438 par_indent = 0;
439 }
440 }
441
442 if (par.size() < line_length)
443 {
444 os << par;
445 }
446 else
447 {
448 string::const_iterator line_begin = par.begin();
449 const string::const_iterator par_end = par.end();
450
451 bool first_line = true; // of current paragraph!
452
453 while (line_begin < par_end) // paragraph lines
454 {
455 if (!first_line)
456 {
457 // If line starts with space, but second character
458 // is not space, remove the leading space.
459 // We don't remove double spaces because those
460 // might be intentianal.
461 if ((*line_begin == ' ') &&
462 ((line_begin + 1 < par_end) &&
463 (*(line_begin + 1) != ' ')))
464 {
465 line_begin += 1; // line_begin != line_end
466 }
467 }
468
469 // Take care to never increment the iterator past
470 // the end, since MSVC 8.0 (brokenly), assumes that
471 // doing that, even if no access happens, is a bug.
472 unsigned remaining = static_cast<unsigned>(std::distance(line_begin, par_end));
473 string::const_iterator line_end = line_begin +
474 ((remaining < line_length) ? remaining : line_length);
475
476 // prevent chopped words
477 // Is line_end between two non-space characters?
478 if ((*(line_end - 1) != ' ') &&
479 ((line_end < par_end) && (*line_end != ' ')))
480 {
481 // find last ' ' in the second half of the current paragraph line
482 string::const_iterator last_space =
483 find(reverse_iterator<string::const_iterator>(line_end),
484 reverse_iterator<string::const_iterator>(line_begin),
485 ' ')
486 .base();
487
488 if (last_space != line_begin)
489 {
490 // is last_space within the second half ot the
491 // current line
492 if (static_cast<unsigned>(std::distance(last_space, line_end)) <
493 (line_length / 2))
494 {
495 line_end = last_space;
496 }
497 }
498 } // prevent chopped words
499
500 // write line to stream
501 copy(line_begin, line_end, ostream_iterator<char>(os));
502
503 if (first_line)
504 {
505 indent += static_cast<unsigned>(par_indent);
506 line_length -= static_cast<unsigned>(par_indent); // there's less to work with now
507 first_line = false;
508 }
509
510 // more lines to follow?
511 if (line_end != par_end)
512 {
513 os << '\n';
514
515 for(unsigned pad = indent; pad > 0; --pad)
516 {
517 os.put(' ');
518 }
519 }
520
521 // next line starts after of this line
522 line_begin = line_end;
523 } // paragraph lines
524 }
525 }
526
527 void format_description(std::ostream& os,
528 const std::string& desc,
529 unsigned first_column_width,
530 unsigned line_length)
531 {
532 // we need to use one char less per line to work correctly if actual
533 // console has longer lines
534 assert(line_length > 1);
535 if (line_length > 1)
536 {
537 --line_length;
538 }
539
540 // line_length must be larger than first_column_width
541 // this assert may fail due to user error or environment conditions!
542 assert(line_length > first_column_width);
543
544 // Note: can't use 'tokenizer' as name of typedef -- borland
545 // will consider uses of 'tokenizer' below as uses of
546 // boost::tokenizer, not typedef.
547 typedef boost::tokenizer<boost::char_separator<char> > tok;
548
549 tok paragraphs(
550 desc,
551 char_separator<char>("\n", "", boost::keep_empty_tokens));
552
553 tok::const_iterator par_iter = paragraphs.begin();
554 const tok::const_iterator par_end = paragraphs.end();
555
556 while (par_iter != par_end) // paragraphs
557 {
558 format_paragraph(os, *par_iter, first_column_width,
559 line_length);
560
561 ++par_iter;
562
563 // prepair next line if any
564 if (par_iter != par_end)
565 {
566 os << '\n';
567
568 for(unsigned pad = first_column_width; pad > 0; --pad)
569 {
570 os.put(' ');
571 }
572 }
573 } // paragraphs
574 }
575
576 void format_one(std::ostream& os, const option_description& opt,
577 unsigned first_column_width, unsigned line_length)
578 {
579 stringstream ss;
580 ss << " " << opt.format_name() << ' ' << opt.format_parameter();
581
582 // Don't use ss.rdbuf() since g++ 2.96 is buggy on it.
583 os << ss.str();
584
585 if (!opt.description().empty())
586 {
587 if (ss.str().size() >= first_column_width)
588 {
589 os.put('\n'); // first column is too long, lets put description in new line
590 for (unsigned pad = first_column_width; pad > 0; --pad)
591 {
592 os.put(' ');
593 }
594 } else {
595 for(unsigned pad = first_column_width - static_cast<unsigned>(ss.str().size()); pad > 0; --pad)
596 {
597 os.put(' ');
598 }
599 }
600
601 format_description(os, opt.description(),
602 first_column_width, line_length);
603 }
604 }
605 }
606
607 unsigned
608 options_description::get_option_column_width() const
609 {
610 /* Find the maximum width of the option column */
611 unsigned width(23);
612 unsigned i; // vc6 has broken for loop scoping
613 for (i = 0; i < m_options.size(); ++i)
614 {
615 const option_description& opt = *m_options[i];
616 stringstream ss;
617 ss << " " << opt.format_name() << ' ' << opt.format_parameter();
618 width = (max)(width, static_cast<unsigned>(ss.str().size()));
619 }
620
621 /* Get width of groups as well*/
622 for (unsigned j = 0; j < groups.size(); ++j)
623 width = max(width, groups[j]->get_option_column_width());
624
625 /* this is the column were description should start, if first
626 column is longer, we go to a new line */
627 const unsigned start_of_description_column = m_line_length - m_min_description_length;
628
629 width = (min)(width, start_of_description_column-1);
630
631 /* add an additional space to improve readability */
632 ++width;
633 return width;
634 }
635
636 void
637 options_description::print(std::ostream& os, unsigned width) const
638 {
639 if (!m_caption.empty())
640 os << m_caption << ":\n";
641
642 if (!width)
643 width = get_option_column_width();
644
645 /* The options formatting style is stolen from Subversion. */
646 for (unsigned i = 0; i < m_options.size(); ++i)
647 {
648 if (belong_to_group[i])
649 continue;
650
651 const option_description& opt = *m_options[i];
652
653 format_one(os, opt, width, m_line_length);
654
655 os << "\n";
656 }
657
658 for (unsigned j = 0; j < groups.size(); ++j) {
659 os << "\n";
660 groups[j]->print(os, width);
661 }
662 }
663
664 }}