summaryrefslogtreecommitdiffstats
blob: bfd113d707d83ae8069d8a05539a6057cd685781 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
// Copyright Vladimir Prus 2002-2004.
// Copyright Bertolt Mildner 2004.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)


#define BOOST_PROGRAM_OPTIONS_SOURCE
#include <boost/program_options/config.hpp>
#include <boost/program_options/options_description.hpp>
// FIXME: this is only to get multiple_occurences class
// should move that to a separate headers.
#include <boost/program_options/parsers.hpp>


#include <boost/lexical_cast.hpp>
#include <boost/tokenizer.hpp>
#include <boost/detail/workaround.hpp>
#include <boost/throw_exception.hpp>

#include <cassert>
#include <climits>
#include <cstring>
#include <cstdarg>
#include <sstream>
#include <iterator>
using namespace std;

namespace boost { namespace program_options {

   namespace {

       template< class charT >
       std::basic_string< charT >  tolower_(const std::basic_string< charT >& str)
       {
           std::basic_string< charT > result;
           for (typename std::basic_string< charT >::size_type i = 0; i < str.size(); ++i)
           {
               result.append(1, static_cast< charT >(std::tolower(str[i])));
           }   
           return result;
       }

    }  // unnamed namespace


    option_description::option_description()
    {
    }
    
    option_description::
    option_description(const char* name,
                       const value_semantic* s)
    : m_value_semantic(s)
    {
        this->set_name(name);
    }
                                           

    option_description::
    option_description(const char* name,
                       const value_semantic* s,
                       const char* description)
    : m_description(description), m_value_semantic(s)
    {
        this->set_name(name);
    }

    option_description::~option_description()
    {
    }

    option_description::match_result
    option_description::match(const std::string& option, 
                              bool approx, 
                              bool long_ignore_case,
                              bool short_ignore_case) const
    {
        match_result result = no_match;        
        
        std::string local_long_name((long_ignore_case ? tolower_(m_long_name) : m_long_name));
        
        if (!local_long_name.empty()) {
        
            std::string local_option = (long_ignore_case ? tolower_(option) : option);

            if (*local_long_name.rbegin() == '*')
            {
                // The name ends with '*'. Any specified name with the given
                // prefix is OK.
                if (local_option.find(local_long_name.substr(0, local_long_name.length()-1))
                    == 0)
                    result = approximate_match;
            }

            if (local_long_name == local_option)
            {
                result = full_match;
            }
            else if (approx)
            {
                if (local_long_name.find(local_option) == 0)
                {
                    result = approximate_match;
                }
            }
        }
         
        if (result != full_match)
        {
            std::string local_option(short_ignore_case ? tolower_(option) : option);
            std::string local_short_name(short_ignore_case ? tolower_(m_short_name) : m_short_name);

            if (local_short_name == local_option)
            {
                result = full_match;
            }
        }

        return result;        
    }

    const std::string& 
    option_description::key(const std::string& option) const
    {        
        if (!m_long_name.empty()) 
            if (m_long_name.find('*') != string::npos)
                // The '*' character means we're long_name
                // matches only part of the input. So, returning
                // long name will remove some of the information,
                // and we have to return the option as specified
                // in the source.
                return option;
            else
                return m_long_name;
        else
            return m_short_name;
    }

    const std::string&
    option_description::long_name() const
    {
        return m_long_name;
    }

    option_description&
    option_description::set_name(const char* _name)
    {
        std::string name(_name);
        string::size_type n = name.find(',');
        if (n != string::npos) {
            assert(n == name.size()-2);
            m_long_name = name.substr(0, n);
            m_short_name = '-' + name.substr(n+1,1);
        } else {
            m_long_name = name;
        }
        return *this;
    }

    const std::string&
    option_description::description() const
    {
        return m_description;
    }

    shared_ptr<const value_semantic>
    option_description::semantic() const
    {
        return m_value_semantic;
    }
    
    std::string 
    option_description::format_name() const
    {
        if (!m_short_name.empty())
            return string(m_short_name).append(" [ --").
            append(m_long_name).append(" ]");
        else
            return string("--").append(m_long_name);
    }

    std::string 
    option_description::format_parameter() const
    {
        if (m_value_semantic->max_tokens() != 0)
            return m_value_semantic->name();
        else
            return "";
    }

    options_description_easy_init::
    options_description_easy_init(options_description* owner)
    : owner(owner)
    {}

    options_description_easy_init&
    options_description_easy_init::
    operator()(const char* name,
               const char* description)
    {
        // Create untypes semantic which accepts zero tokens: i.e. 
        // no value can be specified on command line.
        // FIXME: does not look exception-safe
        shared_ptr<option_description> d(
            new option_description(name, new untyped_value(true), description));

        owner->add(d);
        return *this;
    }

    options_description_easy_init&
    options_description_easy_init::
    operator()(const char* name,
               const value_semantic* s)
    {
        shared_ptr<option_description> d(new option_description(name, s));
        owner->add(d);
        return *this;
    }

    options_description_easy_init&
    options_description_easy_init::
    operator()(const char* name,
               const value_semantic* s,
               const char* description)
    {
        shared_ptr<option_description> d(new option_description(name, s, description));

        owner->add(d);
        return *this;
    }

    const unsigned options_description::m_default_line_length = 80;

    options_description::options_description(unsigned line_length,
                                             unsigned min_description_length)
    : m_line_length(line_length)
    , m_min_description_length(min_description_length)
    {
        // we require a space between the option and description parts, so add 1.
        assert(m_min_description_length < m_line_length - 1);    
    }

    options_description::options_description(const std::string& caption,
                                             unsigned line_length,
                                             unsigned min_description_length)
    : m_caption(caption)
    , m_line_length(line_length)
    , m_min_description_length(min_description_length)
    {
        // we require a space between the option and description parts, so add 1.
        assert(m_min_description_length < m_line_length - 1);
    }
    
    void
    options_description::add(shared_ptr<option_description> desc)
    {
        m_options.push_back(desc);
        belong_to_group.push_back(false);
    }

    options_description&
    options_description::add(const options_description& desc)
    {
        shared_ptr<options_description> d(new options_description(desc));
        groups.push_back(d);

        for (size_t i = 0; i < desc.m_options.size(); ++i) {
            add(desc.m_options[i]);
            belong_to_group.back() = true;
        }

        return *this;
    }

    options_description_easy_init
    options_description::add_options()
    {       
        return options_description_easy_init(this);
    }

    const option_description&
    options_description::find(const std::string& name, 
                              bool approx,
                              bool long_ignore_case,
                              bool short_ignore_case) const
    {
        const option_description* d = find_nothrow(name, approx, 
                                       long_ignore_case, short_ignore_case);
        if (!d)
            boost::throw_exception(unknown_option(name));
        return *d;
    }

    const std::vector< shared_ptr<option_description> >& 
    options_description::options() const
    {
        return m_options;
    }

    const option_description*
    options_description::find_nothrow(const std::string& name, 
                                      bool approx,
                                      bool long_ignore_case,
                                      bool short_ignore_case) const
    {
        shared_ptr<option_description> found;
        vector<string> approximate_matches;
        vector<string> full_matches;
        
        // We use linear search because matching specified option
        // name with the declared option name need to take care about
        // case sensitivity and trailing '*' and so we can't use simple map.
        for(unsigned i = 0; i < m_options.size(); ++i)
        {
            option_description::match_result r = 
                m_options[i]->match(name, approx, long_ignore_case, short_ignore_case);

            if (r == option_description::no_match)
                continue;

            if (r == option_description::full_match)
            {                
                full_matches.push_back(m_options[i]->key(name));
            } 
            else 
            {                        
                // FIXME: the use of 'key' here might not
                // be the best approach.
                approximate_matches.push_back(m_options[i]->key(name));
            }

            found = m_options[i];
        }
        if (full_matches.size() > 1) 
            boost::throw_exception(
                ambiguous_option(name, full_matches));
        
        // If we have a full match, and an approximate match,
        // ignore approximate match instead of reporting error.
        // Say, if we have options "all" and "all-chroots", then
        // "--all" on the command line should select the first one,
        // without ambiguity.
        if (full_matches.empty() && approximate_matches.size() > 1)
            boost::throw_exception(
                ambiguous_option(name, approximate_matches));

        return found.get();
    }

    BOOST_PROGRAM_OPTIONS_DECL
    std::ostream& operator<<(std::ostream& os, const options_description& desc)
    {
        desc.print(os);
        return os;
    }

    namespace {

        /* Given a string 'par', that contains no newline characters
           outputs it to 'os' with wordwrapping, that is, as several
           line.

           Each output line starts with 'indent' space characters, 
           following by characters from 'par'. The total length of
           line is no longer than 'line_length'.
                                      
        */
        void format_paragraph(std::ostream& os,
                              std::string par,
                              unsigned indent,
                              unsigned line_length)
        {                    
            // Through reminder of this function, 'line_length' will
            // be the length available for characters, not including
            // indent.
            assert(indent < line_length);
            line_length -= indent;

            // index of tab (if present) is used as additional indent relative
            // to first_column_width if paragrapth is spanned over multiple
            // lines if tab is not on first line it is ignored
            string::size_type par_indent = par.find('\t');

            if (par_indent == string::npos)
            {
                par_indent = 0;
            }
            else
            {
                // only one tab per paragraph allowed
                if (count(par.begin(), par.end(), '\t') > 1)
                {
                    boost::throw_exception(program_options::error(
                        "Only one tab per paragraph is allowed"));
                }
          
                // erase tab from string
                par.erase(par_indent, 1);

                // this assert may fail due to user error or 
                // environment conditions!
                assert(par_indent < line_length);

                // ignore tab if not on first line
                if (par_indent >= line_length)
                {
                    par_indent = 0;
                }            
            }
          
            if (par.size() < line_length)
            {
                os << par;
            }
            else
            {
                string::const_iterator       line_begin = par.begin();
                const string::const_iterator par_end = par.end();

                bool first_line = true; // of current paragraph!        
            
                while (line_begin < par_end)  // paragraph lines
                {
                    if (!first_line)
                    {
                        // If line starts with space, but second character
                        // is not space, remove the leading space.
                        // We don't remove double spaces because those
                        // might be intentianal.
                        if ((*line_begin == ' ') &&
                            ((line_begin + 1 < par_end) &&
                             (*(line_begin + 1) != ' ')))
                        {
                            line_begin += 1;  // line_begin != line_end
                        }
                    }

                    // Take care to never increment the iterator past
                    // the end, since MSVC 8.0 (brokenly), assumes that
                    // doing that, even if no access happens, is a bug.
                    unsigned remaining = distance(line_begin, par_end);
                    string::const_iterator line_end = line_begin + 
                        ((remaining < line_length) ? remaining : line_length);
            
                    // prevent chopped words
                    // Is line_end between two non-space characters?
                    if ((*(line_end - 1) != ' ') &&
                        ((line_end < par_end) && (*line_end != ' ')))
                    {
                        // find last ' ' in the second half of the current paragraph line
                        string::const_iterator last_space =
                            find(reverse_iterator<string::const_iterator>(line_end),
                                 reverse_iterator<string::const_iterator>(line_begin),
                                 ' ')
                            .base();
                
                        if (last_space != line_begin)
                        {                 
                            // is last_space within the second half ot the 
                            // current line
                            if (static_cast<unsigned>(distance(last_space, line_end)) < 
                                (line_length / 2))
                            {
                                line_end = last_space;
                            }
                        }                                                
                    } // prevent chopped words
             
                    // write line to stream
                    copy(line_begin, line_end, ostream_iterator<char>(os));
              
                    if (first_line)
                    {
                        indent += par_indent;
                        line_length -= par_indent; // there's less to work with now
                        first_line = false;
                    }

                    // more lines to follow?
                    if (line_end != par_end)
                    {
                        os << '\n';
                
                        for(unsigned pad = indent; pad > 0; --pad)
                        {
                            os.put(' ');
                        }                                                        
                    }
              
                    // next line starts after of this line
                    line_begin = line_end;              
                } // paragraph lines
            }          
        }                              
        
        void format_description(std::ostream& os,
                                const std::string& desc, 
                                unsigned first_column_width,
                                unsigned line_length)
        {
            // we need to use one char less per line to work correctly if actual
            // console has longer lines
            assert(line_length > 1);
            if (line_length > 1)
            {
                --line_length;
            }

            // line_length must be larger than first_column_width
            // this assert may fail due to user error or environment conditions!
            assert(line_length > first_column_width);

            // Note: can't use 'tokenizer' as name of typedef -- borland
            // will consider uses of 'tokenizer' below as uses of
            // boost::tokenizer, not typedef.
            typedef boost::tokenizer<boost::char_separator<char> > tok;
          
            tok paragraphs(
                desc,
                char_separator<char>("\n", "", boost::keep_empty_tokens));
          
            tok::const_iterator       par_iter = paragraphs.begin();                
            const tok::const_iterator par_end = paragraphs.end();

            while (par_iter != par_end)  // paragraphs
            {
                format_paragraph(os, *par_iter, first_column_width, 
                                 line_length);
            
                ++par_iter;
            
                // prepair next line if any
                if (par_iter != par_end)
                {
                    os << '\n';
              
                    for(unsigned pad = first_column_width; pad > 0; --pad)
                    {
                        os.put(' ');
                    }                    
                }            
            }  // paragraphs
        }
    
        void format_one(std::ostream& os, const option_description& opt, 
                        unsigned first_column_width, unsigned line_length)
        {
            stringstream ss;
            ss << "  " << opt.format_name() << ' ' << opt.format_parameter();
            
            // Don't use ss.rdbuf() since g++ 2.96 is buggy on it.
            os << ss.str();

            if (!opt.description().empty())
            {
                if (ss.str().size() >= first_column_width)
                {
                   os.put('\n'); // first column is too long, lets put description in new line
                   for (unsigned pad = first_column_width; pad > 0; --pad)
                   {
                      os.put(' ');
                   }
                } else {
                   for(unsigned pad = first_column_width - ss.str().size(); pad > 0; --pad)
                   {
                      os.put(' ');
                   }
                }
            
                format_description(os, opt.description(),
                                   first_column_width, line_length);
            }
        }
    }

    void 
    options_description::print(std::ostream& os) const
    {
        if (!m_caption.empty())
            os << m_caption << ":\n";

        /* Find the maximum width of the option column */
        unsigned width(23);
        unsigned i; // vc6 has broken for loop scoping
        for (i = 0; i < m_options.size(); ++i)
        {
            const option_description& opt = *m_options[i];
            stringstream ss;
            ss << "  " << opt.format_name() << ' ' << opt.format_parameter();
            width = (max)(width, static_cast<unsigned>(ss.str().size()));            
        }
        /* this is the column were description should start, if first
           column is longer, we go to a new line */
        const unsigned start_of_description_column = m_line_length - m_min_description_length;

        width = (min)(width, start_of_description_column-1);
        
        /* add an additional space to improve readability */
        ++width;
            
        /* The options formatting style is stolen from Subversion. */
        for (i = 0; i < m_options.size(); ++i)
        {
            if (belong_to_group[i])
                continue;

            const option_description& opt = *m_options[i];

            format_one(os, opt, width, m_line_length);

            os << "\n";
        }

        for (unsigned j = 0; j < groups.size(); ++j) {            
            os << "\n" << *groups[j];
        }
    }

}}