31 for (
const auto& item : *
this) {
33 using options::escapedIO::operator<<;
42 template <
class ... Types>
mapOption(Types ... args) :
47 for (
const auto& item : *
this) {
48 aStream <<
" [" << item.first <<
"]=";
49 using options::escapedIO::operator<<;
50 aStream << item.second;
64 for (
const auto& item : *
this) {
69 using options::escapedIO::operator<<;
86 std::string description;
90 aStream.ignore(std::numeric_limits<std::streamsize>::max(),
' ');
91 std::getline(aStream, description);
93 if (shortName ==
'-') {
102 return new mapOption<T>(shortName, longName, description);
110 int main(
int argc,
const char *argv[]) {
111 std::string description;
112 if (isatty(0) != 1) {
113 while (std::cin.good()) {
115 if (std::cin.eof()) {
118 std::getline(std::cin, line);
119 if (line.compare(
"options:") == 0) {
126 if (description.empty() || description ==
"\n") {
127 std::cout << argv[0] <<
": shell script option parser.\n"
128 "\treads description of options from stdandard input and writes shell\n"
129 "\tcommands that set variables to the parsed options to standard output\n"
130 "typical invocation looks like this:\n"
131 ". <(shellScriptOptionParser $\"(cd \"$(dirname \"$0\")\"; echo \"$(pwd)/$(basename \"$0\")\")\" \"$@\"<<EOF\n"
132 "description of script\n"
136 "trailing rest of explanation\n"
138 "test $? != 0 && echo exit\n"
141 "[export|array|map|list][positional number] type shortOpt longOpt descripton\n"
142 "\ttype may be one of 'int', 'uint', 'bool' or 'string'\n"
143 "\tfor (file)sizes the type 'size' which understands kMG.. postfixes\n"
144 "\tfor durations the type 'seconds' is provided\n"
145 "\tfor short durations the type 'milliseconds' is provided\n"
146 "\tfor very short durations the type 'microseconds' is provided\n"
147 "\tfor time stamps the type 'date' is provided with fractional seconds\n"
148 "\tfor time stamps the type 'idate' is provided with integer seconds\n"
149 "\tshortOpt is the one-letter variant, use '-' to have none\n"
150 "\tlongOpt is the long variant and the name of the shell variable\n"
151 "\tthe rest of the line is the description\n"
152 "\tif 'array' is set the option will be a shell array, which can be\n"
153 "\t expanded with \"${longOpt[@]}\" which will produce words preserving spaces\n"
154 "\tif 'map' is set the option will be a shell array, which can be\n"
155 "\t expanded with \"${longOpt[@]}\" which will produce words preserving spaces\n"
156 "\t other than the 'array' variant maps have string subscripts\n"
157 "\tif 'list' is set the option will be a list, i.e. a variable with the\n"
158 "\t values separated by spaces, i.e. values may not contain spaces\n"
159 "\tif 'export' is set the shell variable will be exported\n"
160 "\tif 'positional' is set the variable will be set as postional,\n"
161 "\t with 'number' defining the order in the positional parameter list.\n"
162 "\tif the next line starts with 'range' the values following are added\n"
163 "\tto the allowed value range of the option, many range lines may follow!\n"
164 "\tif only two are given they denote a true range in the closed interval\n"
165 "\tif the next line starts with 'default' a default value\n"
166 "\t (the rest of line) is set\n"
167 "\tThe keyword 'minusMinusSpecialTreatment' will put the parameters\n"
168 "\tfollowing '--' into the shell variable following that keyword\n"
169 "\tThe keyword 'noPath' clears the search path for config files\n"
170 "\tThe keyword 'path' adds the (escaped) rest of the line\n"
171 "\tto the search path for config files\n"
172 "\tThe keyword 'minUnusedParameters' sets the min number of params\n"
173 "\tThe keyword 'maxUnusedParameters' sets the max number of params\n";
178 std::vector<options::base*>
options;
179 std::set<const options::base*> exportedOptions;
180 std::string minusMinusSpecialTreatment =
"";
181 std::vector<std::string> searchPath({
"/etc/",
"~/.",
"~/.config/",
"./."});
183 unsigned int minUnusedParameters = 0;
184 unsigned int maxUnusedParameters = std::numeric_limits<unsigned int>::max();
188 bool exportNextOption =
false;
190 int nextOptionPositional = 0;
191 while (std::cin.good()) {
193 if (std::cin.eof()) {
196 if (keyWord ==
"string") {
197 options.push_back(fOptionFromStream<std::string>(std::cin,
"", nextOptionAsWhat));
198 }
else if (keyWord ==
"int") {
199 options.push_back(fOptionFromStream<int>(std::cin, 0, nextOptionAsWhat));
200 }
else if (keyWord ==
"uint") {
201 options.push_back(fOptionFromStream<unsigned int>(std::cin, 0, nextOptionAsWhat));
202 }
else if (keyWord ==
"size") {
204 }
else if (keyWord ==
"bool") {
205 options.push_back(fOptionFromStream<bool>(std::cin,
false, nextOptionAsWhat));
206 }
else if (keyWord ==
"seconds") {
207 options.push_back(
fOptionFromStream<std::chrono::duration<long long>>(std::cin, std::chrono::seconds(1), nextOptionAsWhat));
208 }
else if (keyWord ==
"milliseconds") {
209 options.push_back(
fOptionFromStream<std::chrono::duration<long long, std::milli>>(std::cin, std::chrono::seconds(1), nextOptionAsWhat));
210 }
else if (keyWord ==
"microseconds") {
211 options.push_back(
fOptionFromStream<std::chrono::duration<long long, std::micro>>(std::cin, std::chrono::seconds(1), nextOptionAsWhat));
212 }
else if (keyWord ==
"date") {
213 options.push_back(fOptionFromStream<std::chrono::system_clock::time_point>(std::cin, std::chrono::system_clock::now(), nextOptionAsWhat));
214 }
else if (keyWord ==
"idate") {
215 options.push_back(fOptionFromStream<std::chrono::system_clock::time_point>(std::cin, std::chrono::system_clock::now(), nextOptionAsWhat));
218 opt->
fSetValuePrinter([](std::ostream & aStream,
const std::chrono::system_clock::time_point & aValue)->void {aStream << std::chrono::duration_cast<std::chrono::duration<long>>(aValue.time_since_epoch()).count();});
220 }
else if (keyWord ==
"range") {
221 options.back()->fAddToRangeFromStream(std::cin);
222 }
else if (keyWord ==
"default") {
223 options.back()->fAddDefaultFromStream(std::cin);
224 }
else if (keyWord ==
"export") {
225 exportNextOption =
true;
227 }
else if (keyWord ==
"array") {
230 }
else if (keyWord ==
"map") {
231 nextOptionAsWhat =
kAsMap;
233 }
else if (keyWord ==
"list") {
236 }
else if (keyWord ==
"positional") {
237 std::cin >> nextOptionPositional;
239 }
else if (keyWord ==
"minusMinusSpecialTreatment") {
240 std::cin >> minusMinusSpecialTreatment;
242 }
else if (keyWord ==
"minUnusedParameters") {
243 std::cin >> minUnusedParameters;
245 }
else if (keyWord ==
"maxUnusedParameters") {
246 std::cin >> maxUnusedParameters;
248 }
else if (keyWord ==
"noPath") {
251 }
else if (keyWord ==
"path") {
253 using options::escapedIO::operator>>;
255 searchPath.push_back(buffer);
256 }
else if (keyWord ==
"trailer:") {
259 std::cerr <<
"illegal option type '" << keyWord <<
"', giving up" << std::endl;
262 if (nextOptionPositional != 0) {
265 if (exportNextOption) {
266 exportedOptions.insert(
options.back());
268 exportNextOption =
false;
270 nextOptionPositional = 0;
274 while (std::cin.good()) {
276 if (std::cin.eof()) {
279 std::getline(std::cin, line);
288 if (! minusMinusSpecialTreatment.empty()) {
292 auto unusedOptions = parser.
fParse(argc - 1, argv + 1);
294 if (unusedOptions.size() < minUnusedParameters ||
295 unusedOptions.size() > maxUnusedParameters) {
296 std::cerr <<
"illegal number of non-option parameters " << unusedOptions.size() <<
", must be between " << minUnusedParameters <<
" and " << maxUnusedParameters << std::endl;
303 if (exportedOptions.find(option) != exportedOptions.end()) {
304 std::cout <<
"export ";
306 std::cout << option->fGetLongName() <<
"=";
307 option->fWriteValue(std::cout);
310 std::cout <<
"shift $#\n";
311 if (unusedOptions.empty() ==
false) {
312 std::cout <<
"set --";
313 for (
auto & unusedOption : unusedOptions) {
314 std::cout <<
" " << unusedOption;
319 std::cout << minusMinusSpecialTreatment <<
"=\"";