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>
|
---|
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 | 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 | }}
|
---|