Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_34_1/libs/program_options/src/options_description.cpp @ 29

Last change on this file since 29 was 29, checked in by landauf, 16 years ago

updated boost from 1_33_1 to 1_34_1

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