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