/* The copyright in this software is being made available under the BSD * License, included below. This software may be subject to other third party * and contributor rights, including patent rights, and no such rights are * granted under this license. * * Copyright (c) 2010-2015, ITU/ISO/IEC * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include "program_options_lite.h" #include "../TLibCommon/CommonDef.h" using namespace std; //! \ingroup TAppCommon //! \{ namespace df { namespace program_options_lite { ErrorReporter default_error_reporter; ostream& ErrorReporter::error(const string& where) { is_errored = 1; cerr << where << " error: "; return cerr; } ostream& ErrorReporter::warn(const string& where) { cerr << where << " warning: "; return cerr; } Options::~Options() { for(Options::NamesPtrList::iterator it = opt_list.begin(); it != opt_list.end(); it++) { delete *it; } } void Options::addOption(OptionBase *opt) { Names* names = new Names(); names->opt = opt; string& opt_string = opt->opt_string; size_t opt_start = 0; for (size_t opt_end = 0; opt_end != string::npos;) { opt_end = opt_string.find_first_of(',', opt_start); bool force_short = 0; if (opt_string[opt_start] == '-') { opt_start++; force_short = 1; } string opt_name = opt_string.substr(opt_start, opt_end - opt_start); if (force_short || opt_name.size() == 1) { names->opt_short.push_back(opt_name); opt_short_map[opt_name].push_back(names); } else { names->opt_long.push_back(opt_name); opt_long_map[opt_name].push_back(names); } opt_start += opt_end + 1; } opt_list.push_back(names); } /* Helper method to initiate adding options to Options */ OptionSpecific Options::addOptions() { return OptionSpecific(*this); } static void setOptions(Options::NamesPtrList& opt_list, const string& value, ErrorReporter& error_reporter) { /* multiple options may be registered for the same name: * allow each to parse value */ for (Options::NamesPtrList::iterator it = opt_list.begin(); it != opt_list.end(); ++it) { (*it)->opt->parse(value, error_reporter); } } static const char spaces[41] = " "; /* format help text for a single option: * using the formatting: "-x, --long", * if a short/long option isn't specified, it is not printed */ static void doHelpOpt(ostream& out, const Options::Names& entry, unsigned pad_short = 0) { pad_short = min(pad_short, 8u); if (!entry.opt_short.empty()) { unsigned pad = max((int)pad_short - (int)entry.opt_short.front().size(), 0); out << "-" << entry.opt_short.front(); if (!entry.opt_long.empty()) { out << ", "; } out << &(spaces[40 - pad]); } else { out << " "; out << &(spaces[40 - pad_short]); } if (!entry.opt_long.empty()) { out << "--" << entry.opt_long.front(); } } /* format the help text */ void doHelp(ostream& out, Options& opts, unsigned columns) { const unsigned pad_short = 3; /* first pass: work out the longest option name */ unsigned max_width = 0; for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++) { #if NH_MV if ( (*it)->opt->opt_duplicate ) continue; #endif ostringstream line(ios_base::out); doHelpOpt(line, **it, pad_short); max_width = max(max_width, (unsigned) line.tellp()); } unsigned opt_width = min(max_width+2, 28u + pad_short) + 2; unsigned desc_width = columns - opt_width; /* second pass: write out formatted option and help text. * - align start of help text to start at opt_width * - if the option text is longer than opt_width, place the help * text at opt_width on the next line. */ for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++) { #if NH_MV if ( (*it)->opt->opt_duplicate ) continue; #endif ostringstream line(ios_base::out); line << " "; doHelpOpt(line, **it, pad_short); const string& opt_desc = (*it)->opt->opt_desc; if (opt_desc.empty()) { /* no help text: output option, skip further processing */ cout << line.str() << endl; continue; } size_t currlength = size_t(line.tellp()); if (currlength > opt_width) { /* if option text is too long (and would collide with the * help text, split onto next line */ line << endl; currlength = 0; } /* split up the help text, taking into account new lines, * (add opt_width of padding to each new line) */ for (size_t newline_pos = 0, cur_pos = 0; cur_pos != string::npos; currlength = 0) { /* print any required padding space for vertical alignment */ line << &(spaces[40 - opt_width + currlength]); newline_pos = opt_desc.find_first_of('\n', newline_pos); if (newline_pos != string::npos) { /* newline found, print substring (newline needn't be stripped) */ newline_pos++; line << opt_desc.substr(cur_pos, newline_pos - cur_pos); cur_pos = newline_pos; continue; } if (cur_pos + desc_width > opt_desc.size()) { /* no need to wrap text, remainder is less than avaliable width */ line << opt_desc.substr(cur_pos); break; } /* find a suitable point to split text (avoid spliting in middle of word) */ size_t split_pos = opt_desc.find_last_of(' ', cur_pos + desc_width); if (split_pos != string::npos) { /* eat up multiple space characters */ split_pos = opt_desc.find_last_not_of(' ', split_pos) + 1; } /* bad split if no suitable space to split at. fall back to width */ bool bad_split = split_pos == string::npos || split_pos <= cur_pos; if (bad_split) { split_pos = cur_pos + desc_width; } line << opt_desc.substr(cur_pos, split_pos - cur_pos); /* eat up any space for the start of the next line */ if (!bad_split) { split_pos = opt_desc.find_first_not_of(' ', split_pos); } cur_pos = newline_pos = split_pos; if (cur_pos >= opt_desc.size()) { break; } line << endl; } cout << line.str() << endl; } } struct OptionWriter { OptionWriter(Options& rOpts, ErrorReporter& err) : opts(rOpts), error_reporter(err) {} virtual ~OptionWriter() {} virtual const string where() = 0; bool storePair(bool allow_long, bool allow_short, const string& name, const string& value); bool storePair(const string& name, const string& value) { return storePair(true, true, name, value); } Options& opts; ErrorReporter& error_reporter; }; bool OptionWriter::storePair(bool allow_long, bool allow_short, const string& name, const string& value) { bool found = false; Options::NamesMap::iterator opt_it; if (allow_long) { opt_it = opts.opt_long_map.find(name); if (opt_it != opts.opt_long_map.end()) { found = true; } } /* check for the short list */ if (allow_short && !(found && allow_long)) { opt_it = opts.opt_short_map.find(name); if (opt_it != opts.opt_short_map.end()) { found = true; } } if (!found) { error_reporter.error(where()) << "Unknown option `" << name << "' (value:`" << value << "')\n"; return false; } setOptions((*opt_it).second, value, error_reporter); return true; } struct ArgvParser : public OptionWriter { ArgvParser(Options& rOpts, ErrorReporter& rError_reporter) : OptionWriter(rOpts, rError_reporter) {} const string where() { return "command line"; } unsigned parseGNU(unsigned argc, const char* argv[]); unsigned parseSHORT(unsigned argc, const char* argv[]); }; /** * returns number of extra arguments consumed */ unsigned ArgvParser::parseGNU(unsigned argc, const char* argv[]) { /* gnu style long options can take the forms: * --option=arg * --option arg */ string arg(argv[0]); size_t arg_opt_start = arg.find_first_not_of('-'); size_t arg_opt_sep = arg.find_first_of('='); string option = arg.substr(arg_opt_start, arg_opt_sep - arg_opt_start); unsigned extra_argc_consumed = 0; if (arg_opt_sep == string::npos) { /* no argument found => argument in argv[1] (maybe) */ /* xxx, need to handle case where option isn't required */ #if 0 /* commented out, to return to true GNU style processing * where longopts have to include an =, otherwise they are * booleans */ if (argc == 1) { return 0; /* run out of argv for argument */ } extra_argc_consumed = 1; #endif if(!storePair(true, false, option, "1")) { return 0; } } else { /* argument occurs after option_sep */ string val = arg.substr(arg_opt_sep + 1); storePair(true, false, option, val); } return extra_argc_consumed; } unsigned ArgvParser::parseSHORT(unsigned argc, const char* argv[]) { /* short options can take the forms: * --option arg * -option arg */ string arg(argv[0]); size_t arg_opt_start = arg.find_first_not_of('-'); string option = arg.substr(arg_opt_start); /* lookup option */ /* argument in argv[1] */ /* xxx, need to handle case where option isn't required */ if (argc == 1) { error_reporter.error(where()) << "Not processing option `" << option << "' without argument\n"; return 0; /* run out of argv for argument */ } storePair(false, true, option, string(argv[1])); return 1; } list scanArgv(Options& opts, unsigned argc, const char* argv[], ErrorReporter& error_reporter) { ArgvParser avp(opts, error_reporter); /* a list for anything that didn't get handled as an option */ list non_option_arguments; for(unsigned i = 1; i < argc; i++) { if (argv[i][0] != '-') { non_option_arguments.push_back(argv[i]); continue; } if (argv[i][1] == 0) { /* a lone single dash is an argument (usually signifying stdin) */ non_option_arguments.push_back(argv[i]); continue; } if (argv[i][1] != '-') { /* handle short (single dash) options */ i += avp.parseSHORT(argc - i, &argv[i]); continue; } if (argv[i][2] == 0) { /* a lone double dash ends option processing */ while (++i < argc) { non_option_arguments.push_back(argv[i]); } break; } /* handle long (double dash) options */ i += avp.parseGNU(argc - i, &argv[i]); } return non_option_arguments; } struct CfgStreamParser : public OptionWriter { CfgStreamParser(const string& rName, Options& rOpts, ErrorReporter& rError_reporter) : OptionWriter(rOpts, rError_reporter) , name(rName) , linenum(0) {} const string name; int linenum; const string where() { ostringstream os; os << name << ":" << linenum; return os.str(); } void scanLine(string& line); void scanStream(istream& in); }; void CfgStreamParser::scanLine(string& line) { /* strip any leading whitespace */ size_t start = line.find_first_not_of(" \t\n\r"); if (start == string::npos) { /* blank line */ return; } if (line[start] == '#') { /* comment line */ return; } /* look for first whitespace or ':' after the option end */ size_t option_end = line.find_first_of(": \t\n\r",start); string option = line.substr(start, option_end - start); /* look for ':', eat up any whitespace first */ start = line.find_first_not_of(" \t\n\r", option_end); if (start == string::npos) { /* error: badly formatted line */ error_reporter.warn(where()) << "line formatting error\n"; return; } if (line[start] != ':') { /* error: badly formatted line */ error_reporter.warn(where()) << "line formatting error\n"; return; } /* look for start of value string -- eat up any leading whitespace */ start = line.find_first_not_of(" \t\n\r", ++start); if (start == string::npos) { /* error: badly formatted line */ #if !NH_MV error_reporter.warn(where()) << "line formatting error\n"; #else // HTM also allows empty parameters. #endif return; } /* extract the value part, which may contain embedded spaces * by searching for a word at a time, until we hit a comment or end of line */ size_t value_end = start; do { if (line[value_end] == '#') { /* rest of line is a comment */ value_end--; break; } value_end = line.find_first_of(" \t\n\r", value_end); /* consume any white space, incase there is another word. * any trailing whitespace will be removed shortly */ value_end = line.find_first_not_of(" \t\n\r", value_end); } while (value_end != string::npos); /* strip any trailing space from value*/ value_end = line.find_last_not_of(" \t\n\r", value_end); string value; if (value_end >= start) { value = line.substr(start, value_end +1 - start); } else { /* error: no value */ #if !NH_MV error_reporter.warn(where()) << "no value found\n"; #else // This is ok for HTM. #endif return; } /* store the value in option */ storePair(true, false, option, value); } void CfgStreamParser::scanStream(istream& in) { do { linenum++; string line; getline(in, line); scanLine(line); } while(!!in); } /* for all options in opts, set their storage to their specified * default value */ void setDefaults(Options& opts) { for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++) { (*it)->opt->setDefault(); } } void parseConfigFile(Options& opts, const string& filename, ErrorReporter& error_reporter) { ifstream cfgstream(filename.c_str(), ifstream::in); if (!cfgstream) { error_reporter.error(filename) << "Failed to open config file\n"; return; } CfgStreamParser csp(filename, opts, error_reporter); csp.scanStream(cfgstream); } } } //! \}