1 // license:GPL-2.0+ 2 // copyright-holders:Couriersud 3 4 #include "poptions.h" 5 #include "pexception.h" 6 #include "pstrutil.h" 7 #include "ptypes.h" 8 9 namespace plib { 10 option_base(options & parent,const pstring & help)11 option_base::option_base(options &parent, const pstring &help) 12 : m_help(help) 13 { 14 parent.register_option(this); 15 } 16 option(options & parent,const pstring & ashort,const pstring & along,const pstring & help,bool has_argument)17 option::option(options &parent, const pstring &ashort, const pstring &along, const pstring &help, bool has_argument) 18 : option_base(parent, help), m_short(ashort), m_long(along), 19 m_has_argument(has_argument), m_specified(false) 20 { 21 } 22 parse(const pstring & argument)23 int option_str::parse(const pstring &argument) 24 { 25 m_val = argument; 26 return 0; 27 } 28 parse(const pstring & argument)29 int option_bool::parse(const pstring &argument) 30 { 31 unused_var(argument); 32 m_val = true; 33 return 0; 34 } 35 parse(const pstring & argument)36 int option_vec::parse(const pstring &argument) 37 { 38 bool err = false; 39 m_val.push_back(argument); 40 return (err ? 1 : 0); 41 } 42 options()43 options::options() 44 : m_other_args(nullptr) 45 { 46 } 47 options(option ** o)48 options::options(option **o) 49 : m_other_args(nullptr) 50 { 51 int i=0; 52 while (o[i] != nullptr) 53 { 54 register_option(o[i]); 55 i++; 56 } 57 } 58 register_option(option_base * opt)59 void options::register_option(option_base *opt) 60 { 61 m_opts.push_back(opt); 62 } 63 check_consistency()64 void options::check_consistency() 65 { 66 for (auto &opt : m_opts) 67 { 68 auto *o = dynamic_cast<option *>(opt); 69 if (o != nullptr) 70 { 71 if (o->short_opt().empty() && o->long_opt().empty()) 72 { 73 auto *ov = dynamic_cast<option_args *>(o); 74 if (ov != nullptr) 75 { 76 if (m_other_args != nullptr) 77 { 78 throw pexception("other args can only be specified once!"); 79 } 80 81 m_other_args = ov; 82 } 83 else 84 throw pexception("found option with neither short or long tag!" ); 85 } 86 } 87 } 88 } 89 parse(const std::vector<putf8string> & argv)90 std::size_t options::parse(const std::vector<putf8string> &argv) 91 { 92 check_consistency(); 93 m_app = argv[0]; 94 bool seen_other_args = false; 95 96 for (std::size_t i=1; i < argv.size(); ) 97 { 98 pstring arg(argv[i]); 99 option *opt = nullptr; 100 pstring opt_arg; 101 bool has_equal_arg = false; 102 103 if (!seen_other_args && plib::startsWith(arg, "--")) 104 { 105 auto v = psplit(arg.substr(2),'='); 106 if (!v.empty() && !v[0].empty()) 107 { 108 opt = getopt_long(v[0]); 109 has_equal_arg = (v.size() > 1); 110 if (has_equal_arg) 111 { 112 for (std::size_t j = 1; j < v.size() - 1; j++) 113 opt_arg += (v[j] + "="); 114 opt_arg += v[v.size()-1]; 115 } 116 } 117 else 118 { 119 opt = m_other_args; 120 seen_other_args = true; 121 } 122 } 123 else if (!seen_other_args && plib::startsWith(arg, "-")) 124 { 125 std::size_t p = 1; 126 opt = getopt_short(arg.substr(p, 1)); 127 ++p; 128 if (p < arg.length()) 129 { 130 has_equal_arg = true; 131 opt_arg = arg.substr(p); 132 } 133 } 134 else 135 { 136 seen_other_args = true; 137 if (m_other_args == nullptr) 138 return i; 139 opt = m_other_args; 140 i--; // we haven't had an option specifier; 141 } 142 if (opt == nullptr) 143 return i; 144 if (opt->has_argument()) 145 { 146 if (has_equal_arg) 147 { 148 if (opt->do_parse(opt_arg) != 0) 149 return i; 150 } 151 else 152 { 153 i++; 154 if (i >= argv.size()) 155 return i - 1; 156 if (opt->do_parse(pstring(argv[i])) != 0) 157 return i - 1; 158 } 159 } 160 else 161 { 162 if (has_equal_arg) 163 return i; 164 opt->do_parse(""); 165 } 166 i++; 167 } 168 return argv.size(); 169 } 170 split_paragraphs(const pstring & text,unsigned width,unsigned indent,unsigned firstline_indent,const pstring & line_end)171 pstring options::split_paragraphs(const pstring &text, unsigned width, unsigned indent, 172 unsigned firstline_indent, const pstring &line_end) 173 { 174 auto paragraphs = psplit(text,'\n'); 175 pstring ret(""); 176 177 for (auto &p : paragraphs) 178 { 179 pstring line = plib::rpad(pstring(""), pstring(" "), firstline_indent); 180 for (auto &s : psplit(p, ' ')) 181 { 182 if (line.length() + s.length() > width) 183 { 184 ret += line + line_end; 185 line = plib::rpad(pstring(""), pstring(" "), indent); 186 } 187 line += s + " "; 188 } 189 ret += line; 190 if (p != paragraphs.back()) 191 ret += line_end; 192 } 193 return ret; 194 } 195 help(const pstring & description,const pstring & usage,unsigned width,unsigned indent) const196 pstring options::help(const pstring &description, const pstring &usage, 197 unsigned width, unsigned indent) const 198 { 199 pstring ret; 200 201 ret = split_paragraphs(description, width, 0, 0) + "\n\n"; 202 ret += "Usage:\t" + usage + "\n\nOptions:\n\n"; 203 204 for (const auto & optbase : m_opts ) 205 { 206 // Skip anonymous inputs which are collected in option_args 207 if (dynamic_cast<option_args *>(optbase) != nullptr) 208 continue; 209 210 if (auto * const opt = dynamic_cast<option *>(optbase)) 211 { 212 pstring line = ""; 213 if (!opt->short_opt().empty()) 214 line += " -" + opt->short_opt(); 215 if (!opt->long_opt().empty()) 216 { 217 if (!line.empty()) 218 line += ", "; 219 else 220 line = " "; 221 line += "--" + opt->long_opt(); 222 if (opt->has_argument()) 223 { 224 line += "="; 225 auto *ol = dynamic_cast<option_str_limit_base *>(opt); 226 if (ol != nullptr) 227 { 228 for (const auto &v : ol->limit()) 229 { 230 line += v + "|"; 231 } 232 line = plib::left(line, line.length() - 1); 233 } 234 else 235 line += "Value"; 236 } 237 } 238 line = plib::rpad(line, pstring(" "), indent - 2) + " "; 239 if (line.length() > indent) 240 { 241 //ret += "TestGroup abc\n def gef\nxyz\n\n" ; 242 ret += line + "\n"; 243 ret += split_paragraphs(opt->help(), width, indent, indent) + "\n"; 244 } 245 else 246 ret += split_paragraphs(line + opt->help(), width, indent, 0) + "\n"; 247 } 248 else if (auto *grp = dynamic_cast<option_group *>(optbase)) 249 { 250 ret += "\n" + grp->group() + ":\n"; 251 if (!grp->help().empty()) 252 ret += split_paragraphs(grp->help(), width, 4, 4) + "\n\n"; 253 } 254 } 255 // FIXME: other help ... 256 pstring ex(""); 257 for (const auto & optbase : m_opts ) 258 { 259 if (auto *example = dynamic_cast<option_example *>(optbase)) 260 { 261 // help2man doesn't like \\ line continuation in output 262 ex += split_paragraphs(example->example(), width, 8, 0, "\n") + "\n\n"; 263 ex += split_paragraphs(example->help(), width, 4, 4) + "\n\n"; 264 } 265 } 266 if (!ex.empty()) 267 { 268 ret += "\n\nExamples:\n\n" + ex; 269 } 270 return ret; 271 } 272 getopt_short(const pstring & arg) const273 option *options::getopt_short(const pstring &arg) const 274 { 275 for (const auto & optbase : m_opts) 276 { 277 auto *opt = dynamic_cast<option *>(optbase); 278 if (opt != nullptr && !arg.empty() && opt->short_opt() == arg) 279 return opt; 280 } 281 return nullptr; 282 } getopt_long(const pstring & arg) const283 option *options::getopt_long(const pstring &arg) const 284 { 285 for (const auto & optbase : m_opts) 286 { 287 auto *opt = dynamic_cast<option *>(optbase); 288 if (opt != nullptr && !arg.empty() && opt->long_opt() == arg) 289 return opt; 290 } 291 return nullptr; 292 } 293 294 } // namespace plib 295 296