20 #include <sys/types.h>
21 #ifdef IS_NONBROKEN_SYSTEM
23 #include <sys/ioctl.h>
32 #include <system_error>
45 std::runtime_error(aWhat),
46 offendingOption(*aOffendingOption) {}
48 std::runtime_error(aWhat),
49 offendingOption(*aOffendingOption) {}
65 static single<bool>
gOptionDebugOptions(
'\0',
"debugOptions",
"give debug output to option parsing");
67 static single<bool>
gOptionNoCfgFiles(
'\0',
"noCfgFiles",
"do not read the default config files, must be FIRST option");
74 if (it.second->fIsContainer()) {
75 throw std::logic_error(
"only one container type option allowed");
80 if (result.second ==
false) {
81 throw std::logic_error(
"non-unique numbered positional arg");
90 parser::parser(
const std::string& aDescription,
const std::string& aTrailer,
const std::vector<std::string>& aSearchPaths):
91 lDescription(aDescription),
93 lSearchPaths(aSearchPaths),
94 lParsingIsDone(false) {
96 std::cerr <<
"there may be only one parser" << std::endl;
155 auto tildePosition =
f.find_first_of(
'~');
156 if (tildePosition != std::string::npos) {
157 f.replace(tildePosition, 1, getenv(
"HOME"));
165 return fParse(argc,
const_cast<const char**
>(argv));
170 throw std::logic_error(
"parsing may be done only once");
174 #ifdef IS_NONBROKEN_SYSTEM
175 auto buf = strdup(argv[0]);
182 bool firstOptionNotSeen =
true;
184 for (
int i = 1; i < argc; i++) {
185 if (firstOptionNotSeen && !(argv[i][0] ==
'-' && argv[i][1] ==
'-' &&
internal::gOptionNoCfgFiles.lLongName.compare(argv[i] + 2) == 0)) {
188 if (argv[i][0] ==
'-' && argv[i][1] !=
'-') {
189 auto length = strlen(argv[i]);
190 for (
unsigned int j = 1; j < length; j++) {
193 throw std::runtime_error(
internal::conCat(
"unknown short option '", argv[i][j],
"'"));
195 auto opt = it->second;
196 if (opt->lNargs > 0 && strlen(argv[i]) > 2) {
199 opt->fHandleOption(argc, argv, &i);
202 }
else if (argv[i][0] ==
'-' && argv[i][1] ==
'-' && argv[i][2] !=
'-') {
203 auto length = strlen(argv[i]);
205 for (i++; i < argc; i++) {
217 throw std::runtime_error(
internal::conCat(
"unknown long option '", argv[i],
"'"));
219 auto opt = it->second;
220 opt->fHandleOption(argc, argv, &i);
222 auto buf = strdup(argv[i]);
227 throw std::runtime_error(
internal::conCat(
"unknown long option '", argv[i],
"'"));
229 auto opt = it->second;
230 std::stringstream sbuf(equalsAt + 1);
239 firstOptionNotSeen =
false;
241 if (firstOptionNotSeen) {
246 std::list<base*> positionalArgs;
248 positionalArgs.push_back(it.second);
251 auto opt = positionalArgs.front();
252 if (opt->fIsContainer()) {
254 auto opt2 = positionalArgs.back();
258 positionalArgs.pop_back();
260 std::stringstream sbuf(arg);
273 std::stringstream sbuf(arg);
277 if (! opt->fIsContainer()) {
278 positionalArgs.pop_front();
279 if (positionalArgs.empty()) {
296 }
catch (std::exception& e) {
304 auto opt = it.second;
308 if (opt->lSource.fIsUnset()) {
316 fGetErrorStream() <<
"unused option '" << unusedOption <<
"'" << std::endl;
324 std::set<const base*> optionsThatWereSet;
326 auto opt = it.second;
328 optionsThatWereSet.insert(opt);
332 std::vector<const base*> missing;
334 optionsThatWereSet.cbegin(), optionsThatWereSet.cend(),
335 std::inserter(missing, missing.begin()));
336 if (! missing.empty()) {
337 fGetErrorStream() <<
"The following options are required but were not given:";
338 for (
auto opt : missing) {
345 for (
auto opt : optionsThatWereSet) {
346 for (
auto forbidden : opt->lForbiddenOptions) {
347 if (optionsThatWereSet.count(forbidden) != 0) {
348 fGetErrorStream() <<
"The option " << opt->fGetLongName() <<
" forbids the use of " << forbidden->fGetLongName() <<
" but it is given.\n";
352 for (
auto required : opt->lRequiredOptions) {
353 if (optionsThatWereSet.count(required) == 0) {
354 fGetErrorStream() <<
"The option " << opt->fGetLongName() <<
" requires the use of " << required->fGetLongName() <<
" but it is not given.\n";
377 bool delimit = aString.find_first_of(
" \t,") != std::string::npos;
381 for (
auto c : aString) {
417 if (c >=
' ' && c < 127) {
420 aStream << '\\' << std::oct << std::setw(3) << std::setfill('0') << static_cast<unsigned int>(c) << std::setfill(
' ') << std::dec;
430 std::stringstream buffer(aSource);
433 namespace escapedIO {
435 std::istream&
operator>> (std::istream &aStream,
const char*& aCstring) {
436 auto buffer =
new std::string;
438 if (aStream.fail()) {
441 aCstring = buffer->c_str();
447 std::ostream&
operator<<(std::ostream& aStream,
const std::string& aString) {
451 std::istream&
operator>>(std::istream& aStream, std::string& aString) {
452 std::istream::sentry s(aStream);
455 auto delimiter = aStream.peek();
456 if (delimiter ==
'"' || delimiter ==
'\'') {
461 while (! aStream.eof()) {
462 auto c = aStream.peek();
463 if (c ==
'\n' || aStream.eof()) {
471 aString.push_back(
'\a');
474 aString.push_back(
'\b');
477 aString.push_back(
'\f');
480 aString.push_back(
'\n');
483 aString.push_back(
'\r');
486 aString.push_back(
'\t');
489 aString.push_back(
'\v');
492 if (c >=
'0' && c <=
'7') {
494 for (
int i = 0; i < 3 && c >=
'0' && c <=
'7'; i++) {
495 ch = (ch << 3) | ((c -
'0') & 0x7);
498 aString.push_back(ch);
500 aString.push_back(c);
504 }
else if (c == delimiter) {
507 aString.push_back(c);
511 auto eof = aStream.eof();
514 aStream.setstate(std::ios_base::eofbit);
529 base::base(
char aShortName,
const std::string& aLongName,
const std::string& aExplanation,
short aNargs) :
530 lShortName(aShortName),
531 lLongName(aLongName),
532 lExplanation(aExplanation),
538 if (p->fIsParsingDone()) {
567 if (*i +
lNargs >= argc) {
573 std::stringstream sbuf(argv[*i + 1]);
583 if (asOriginalStringKeeper) {
640 supressed(
'\0',
"noCfgFileRecursion",
"do not read config files recursively, must be set before use") {
646 void parser::fPrintOptionHelp(std::ostream& aMessageStream,
const base& aOption, std::size_t aMaxName, std::size_t aMaxExplain,
size_t lineLenght)
const {
647 std::size_t fullNameLength = 8 + aMaxName;
648 std::size_t descLength = std::min(lineLenght - 20 - fullNameLength, aMaxExplain);
650 aMessageStream <<
" -" << aOption.
lShortName <<
", ";
652 aMessageStream <<
" ";
655 bool firstLine =
true;
658 aMessageStream <<
"--" << std::setw(aMaxName) << std::left << aOption.
lLongName
661 aMessageStream <<
" " << std::setw(aMaxName) <<
" " <<
" ";
663 if (explanation.length() > descLength) {
665 bool keepBrokenChar =
false;
666 bool hardBreak =
false;
667 auto breakPos = explanation.find_last_of(
" -", descLength - 1);
668 if (breakPos == std::string::npos) {
671 breakPos = descLength - 2;
673 keepBrokenChar =
true;
676 if (explanation[breakPos] !=
' ') {
678 keepBrokenChar =
true;
681 aMessageStream << std::setw(descLength) << std::left << explanation.substr(0, breakPos) + (hardBreak ?
"-" :
"");
682 explanation = explanation.substr(breakPos + (keepBrokenChar ? 0 : 1));
684 aMessageStream << std::setw(descLength) << std::left << explanation;
689 aMessageStream <<
" default: ";
693 aMessageStream <<
"\n";
694 }
while (explanation.length() > 0);
696 [](
const base * opt) {
697 return ! opt->fIsHidden();
699 aMessageStream <<
" " << std::setw(aMaxName) <<
" " <<
" Requires the following other options:\n";
704 aMessageStream <<
" " << std::setw(aMaxName) <<
" " <<
" " << opt->
fGetLongName() <<
"\n";
708 [](
const base * opt) {
709 return ! opt->fIsHidden();
711 aMessageStream <<
" " << std::setw(aMaxName) <<
" " <<
" Forbids the following other options:\n";
716 aMessageStream <<
" " << std::setw(aMaxName) <<
" " <<
" " << opt->
fGetLongName() <<
"\n";
720 aMessageStream <<
" " << std::setw(aMaxName) <<
" " <<
" this option is required\n";
731 if (it.second->fIsContainer()) {
739 size_t maxExplain = 0;
740 size_t lineLenght = 132;
741 #ifdef IS_NONBROKEN_SYSTEM
743 struct winsize window;
744 if (ioctl(0, TIOCGWINSZ, &window) == 0) {
745 lineLenght = window.ws_col;
750 const auto opt = it.second;
751 maxName = std::max(opt->lLongName.length(), maxName);
752 maxExplain = std::max(opt->lExplanation.length(), maxExplain);
755 const auto opt = it.second;
756 if (! opt->fIsHidden()) {
772 std::ofstream cfgFile(aFileName, std::ofstream::out | std::ofstream::trunc);
774 #ifdef IS_NONBROKEN_SYSTEM
776 auto result = readlink(
"/proc/self/exe", buf,
sizeof(buf));
777 if (result > 0 && result < 128 - 2 - 14) {
778 cfgFile <<
"#!" << buf <<
" --readCfgFile\n";
785 cfgFile <<
"# written ";
786 std::time_t t = std::time(
nullptr);
788 if (std::strftime(mbstr,
sizeof(mbstr),
"%Y-%m-%d %H:%M:%S", std::localtime(&t))) {
791 cfgFile <<
" by " << getenv(
"USER") <<
" using " <<
lProgName <<
"\n";
792 cfgFile <<
"# only comments started by ## will be preserved on a re-write!\n";
793 cfgFile <<
"# to rewrite this file for a different executable location, use:\n";
794 cfgFile <<
"# " <<
lProgName <<
" --noCfgFileRecursion --readCfgFile " << aFileName <<
" --writeCfgFile " << aFileName <<
"\n";
795 cfgFile <<
"# Assuming " <<
lProgName <<
" is in your PATH\n";
798 const auto opt = it.second;
799 if (opt->lPreserveWorthyStuff !=
nullptr) {
800 for (
const auto& line : * (opt->lPreserveWorthyStuff)) {
801 cfgFile <<
"\n" << line;
804 cfgFile <<
"\n# " << opt->lExplanation <<
"\n";
806 if (opt->lSource.fIsUnset()) {
809 opt->fWriteCfgLines(cfgFile, prefix);
810 opt->fWriteRange(cfgFile);
811 if (!opt->lSource.fIsUnset()) {
812 cfgFile <<
"# set from " << opt->lSource <<
"\n";
820 std::ifstream cfgFile(aFileName);
821 if (!cfgFile.good() && !(aMayBeAbsent && errno == ENOENT)) {
822 throw std::system_error(errno, std::system_category(),
827 std::vector<std::string>* preserveWorthyStuff =
nullptr;
828 bool hideNextOption =
false;
829 bool disableNextOption =
false;
831 while (cfgFile.good()) {
833 std::getline(cfgFile, line);
836 if (line.length() == 0) {
837 hideNextOption =
false;
838 disableNextOption =
false;
840 }
else if (line[0] ==
'#') {
841 if (line[1] ==
'#') {
842 if (preserveWorthyStuff ==
nullptr) {
843 preserveWorthyStuff =
new std::vector<std::string>;
845 preserveWorthyStuff->push_back(line);
846 if (line ==
"## hide") {
847 hideNextOption =
true;
849 disableNextOption =
true;
851 }
else if (preserveWorthyStuff !=
nullptr) {
852 auto equalsAt = line.find_first_of(
'=');
853 if (equalsAt != std::string::npos) {
854 auto optionName = line.substr(2, equalsAt - 2);
857 auto option = it->second;
858 option->fSetPreserveWorthyStuff(preserveWorthyStuff);
859 preserveWorthyStuff =
nullptr;
860 if (hideNextOption) {
863 if (disableNextOption) {
871 auto equalsAt = line.find_first_of(
'=');
872 if (equalsAt == std::string::npos || equalsAt < 1 || equalsAt == line.length()) {
878 auto optionName = line.substr(0, equalsAt);
881 throw std::runtime_error(
internal::conCat(
"unknown option '", optionName,
"'"));
883 auto option = it->second;
884 if (hideNextOption) {
887 if (disableNextOption) {
891 std::stringstream sbuf(line.substr(equalsAt + 1));
892 option->fSetMe(sbuf, source);
893 if (preserveWorthyStuff !=
nullptr) {
894 option->fSetPreserveWorthyStuff(preserveWorthyStuff);
895 preserveWorthyStuff =
nullptr;
897 option->fCheckRange();
900 }
catch (
const std::exception& e) {
901 fGetErrorStream() << aFileName <<
":" << lineNumber <<
": error: " << e.what() <<
"\n";
917 aStream << std::boolalpha << lValue;
924 aStream >> std::boolalpha >> lValue;
928 aStream >> std::boolalpha >> lValue;
939 single(
'h',
"help",
"give this help") {
958 supressed(
'\0',
"writeCfgFile",
"write a config file",
"") {
972 single(
'\0', name, description,
"") {
985 static OptionReadCfgFile<false>
gReadCfgFile(
"readCfgFile",
"read a config file");
986 static OptionReadCfgFile<true>
gReadCfgFileIfThere(
"readCfgFileIfThere",
"read a config file if it's there");