// 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 #include // FIXME: this is only to get multiple_occurences class // should move that to a separate headers. #include #include #include #include #include #include #include #include #include #include #include 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 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 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 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 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 desc) { m_options.push_back(desc); belong_to_group.push_back(false); } options_description& options_description::add(const options_description& desc) { shared_ptr 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 >& 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 { int found = -1; vector approximate_matches; vector full_matches; // We use linear search because matching specified option // name with the declaraed 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)); // Use this full match regardless of other matches found. found = i; } else { // FIXME: the use of 'key' here might not // be the best approach. approximate_matches.push_back(m_options[i]->key(name)); // If this is the first match found the use it. If // a later full match is found that will be used instead. if (found == -1) found = 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>=0 ? m_options[found].get() : 0; } 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(line_end), reverse_iterator(line_begin), ' ') .base(); if (last_space != line_begin) { // is last_space within the second half ot the // current line if (static_cast(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(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 > tok; tok paragraphs( desc, char_separator("\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(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]; } } }}