nis-util
1.0.D108
|
00001 // 00002 // nis-util - NIS Administration Utilities 00003 // Copyright (C) 2001, 2003, 2008, 2009, 2011, 2012 Peter Miller 00004 // 00005 // This program is free software; you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License as published by 00007 // the Free Software Foundation; either version 2 of the License, or (at 00008 // your option) any later version. 00009 // 00010 // This program is distributed in the hope that it will be useful, 00011 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 // General Public License for more details. 00014 // 00015 // You should have received a copy of the GNU General Public License 00016 // along with this program. If not, see <http://www.gnu.org/licenses/>. 00017 // 00018 00019 #include <lib/ac/string.h> 00020 #include <lib/ac/ctype.h> 00021 #include <lib/ac/errno.h> 00022 #include <lib/ac/stdlib.h> 00023 #include <lib/ac/unistd.h> 00024 #include <libexplain/output.h> 00025 #include <libexplain/program_name.h> 00026 00027 #include <lib/arglex.h> 00028 #include <lib/arglex/source/command_line.h> 00029 #include <lib/arglex/source/file.h> 00030 #include <lib/arglex/source/pushback.h> 00031 #include <lib/configuration.h> 00032 #include <lib/versn_stamp.h> 00033 00034 00035 arglex::~arglex() 00036 { 00037 } 00038 00039 00040 arglex::arglex(int ac, char **av) : 00041 usage_tail_(0), 00042 number_of_stdin_references(0), 00043 number_of_stdout_references(0) 00044 { 00045 sources.push_back(arglex_source_command_line::create(ac, av)); 00046 00047 static const table_t default_table[] = 00048 { 00049 { "-", token_stdio }, 00050 { "-ConFiGuration", token_configuration_file }, 00051 { "-Group_Name_Maximum", token_group_name_maximum }, 00052 { "-Help", token_help }, 00053 { "-LICense", token_license }, 00054 { "-Page_Length", token_page_length }, 00055 { "-Page_Width", token_page_width }, 00056 { "-Passwd_Name_Maximum", token_passwd_name_maximum }, 00057 { "-Quiet", token_verbose_not }, 00058 { "-Not_Verbose", token_verbose_not }, // must be after -quiet 00059 { "-TRACIng", token_tracing }, 00060 { "-VERSion", token_version }, 00061 { "-Verbose", token_verbose }, 00062 { "-Not_Quiet", token_verbose }, // must be after -Verbose 00063 { "-Warnings", token_warning }, 00064 { "-Not_Warnings", token_warning_not }, 00065 ARGLEX_END_MARKER 00066 }; 00067 00068 table_set(default_table); 00069 explain_option_hanging_indent_set(4); 00070 } 00071 00072 00073 void 00074 arglex::table_set(const table_t *tp) 00075 { 00076 tables.push_back(tp); 00077 } 00078 00079 00080 static const char *partial; 00081 00082 bool 00083 arglex::compare(const char *formal, const char *actual) 00084 { 00085 for (;;) 00086 { 00087 unsigned char ac = *actual++; 00088 if (isupper(ac)) 00089 ac = tolower(ac); 00090 unsigned char fc = *formal++; 00091 switch (fc) 00092 { 00093 case 0: 00094 return !ac; 00095 00096 case '_': 00097 if (ac == '-') 00098 break; 00099 // fall through... 00100 00101 case 'a': case 'b': case 'c': case 'd': case 'e': 00102 case 'f': case 'g': case 'h': case 'i': case 'j': 00103 case 'k': case 'l': case 'm': case 'n': case 'o': 00104 case 'p': case 'q': case 'r': case 's': case 't': 00105 case 'u': case 'v': case 'w': case 'x': case 'y': 00106 case 'z': 00107 // 00108 // optional characters 00109 // 00110 if (ac == fc && arglex::compare(formal, actual)) 00111 return true; 00112 // 00113 // skip forward to next 00114 // mandatory character, or after '_' 00115 // 00116 while (islower(*formal)) 00117 ++formal; 00118 if (*formal == '_') 00119 { 00120 ++formal; 00121 if (ac == '_' || ac == '-') 00122 ++actual; 00123 } 00124 --actual; 00125 break; 00126 00127 case '*': 00128 // 00129 // This is a hack, it should really 00130 // check for a match match the stuff after 00131 // the '*', too, a la glob. 00132 // 00133 if (!ac) 00134 return false; 00135 partial = actual - 1; 00136 return true; 00137 00138 case '\\': 00139 if (actual[-1] != *formal++) 00140 return false; 00141 break; 00142 00143 case 'A': case 'B': case 'C': case 'D': case 'E': 00144 case 'F': case 'G': case 'H': case 'I': case 'J': 00145 case 'K': case 'L': case 'M': case 'N': case 'O': 00146 case 'P': case 'Q': case 'R': case 'S': case 'T': 00147 case 'U': case 'V': case 'W': case 'X': case 'Y': 00148 case 'Z': 00149 fc = tolower(fc); 00150 // fall through... 00151 00152 default: 00153 // 00154 // mandatory characters 00155 // 00156 if (fc != ac) 00157 return false; 00158 break; 00159 } 00160 } 00161 } 00162 00163 00181 static bool 00182 is_a_number(const rcstring &text, long &n) 00183 { 00184 const char *s = text.c_str(); 00185 char *ep = 0; 00186 n = strtol(s, &ep, 0); 00187 assert(ep); 00188 return (ep != s && !*ep); 00189 } 00190 00191 00192 void 00193 arglex::push_arg_back(const char *value) 00194 { 00195 sources.push_back(arglex_source_pushback::create(token_locn, value)); 00196 } 00197 00198 00199 int 00200 arglex::token_next(void) 00201 { 00202 for (;;) 00203 { 00204 // no sources means "end of line" 00205 if (sources.empty()) 00206 { 00207 token_locn = source_location("command line", 666); 00208 value_string = ""; 00209 token = token_eoln; 00210 return token; 00211 } 00212 00213 // see if we can get another token from this source 00214 if (!sources.back()->next(token_locn, value_string)) 00215 { 00216 if (sources.size() == 1) 00217 { 00218 // Special case the actual command line, 00219 // this will give better error messages. 00220 value_string = ""; 00221 token = token_eoln; 00222 return token; 00223 } 00224 sources.pop_back(); 00225 continue; 00226 } 00227 00228 // see if it is a "@filename" include 00229 if (value_string.front() == '@' && value_string.size() >= 2) 00230 { 00231 rcstring filename = 00232 value_string.substr(1, value_string.size() - 1); 00233 if (filename == "-") 00234 check_stdin_references(); 00235 sources.push_back(arglex_source_file::create(filename)); 00236 continue; 00237 } 00238 00239 // found our argument 00240 break; 00241 } 00242 00243 // 00244 // See if it looks like a GNU "-foo=bar" option. 00245 // Split it at the '=' to make it something the 00246 // rest of the code understands. 00247 // 00248 if (value_string[0] == '-' && value_string[1] != '=') 00249 { 00250 const char *cp = value_string.c_str(); 00251 const char *eqp = strchr(cp, '='); 00252 if (eqp) 00253 { 00254 push_arg_back(eqp + 1); 00255 value_string = value_string.substr(0, eqp - cp); 00256 } 00257 } 00258 00259 // 00260 // Turn the GNU-style leading "--" 00261 // into "-" if necessary. 00262 // 00263 if 00264 ( 00265 value_string.size() > 2 00266 && 00267 value_string[0] == '-' 00268 && 00269 value_string[1] == '-' 00270 ) 00271 { 00272 rcstring s = value_string.substr(1, value_string.size() - 1); 00273 long dummy; 00274 if (!is_a_number(s, dummy)) 00275 { 00276 value_string = s; 00277 } 00278 } 00279 00280 // 00281 // see if it is a number 00282 // 00283 if (is_a_number(value_string, value_number)) 00284 { 00285 token = arglex::token_number; 00286 return token; 00287 } 00288 00289 // 00290 // scan the tables to see what it matches 00291 // 00292 typedef std::vector<const table_t *> hits_t; 00293 hits_t hits; 00294 partial = 0; 00295 for 00296 ( 00297 tables_t::iterator it = tables.begin(); 00298 it != tables.end(); 00299 ++it 00300 ) 00301 { 00302 for (const table_t *tp = *it; tp->name; tp++) 00303 { 00304 if (arglex::compare(tp->name, value_string.c_str())) 00305 hits.push_back(tp); 00306 } 00307 } 00308 00309 // 00310 // If all the hits are the same token value, the token is not ambiguous. 00311 // This happens if you have two spellings for the same token, 00312 // e.g. Standard English vs American. 00313 // 00314 while (hits.size() >= 2 && hits.front()->token == hits.back()->token) 00315 hits.pop_back(); 00316 00317 // 00318 // deal with unknown or ambiguous options 00319 // 00320 switch (hits.size()) 00321 { 00322 case 0: 00323 // 00324 // not found in the tables 00325 // 00326 if (value_string.front() == '-') 00327 token = arglex::token_option; 00328 else 00329 token = arglex::token_string; 00330 break; 00331 00332 case 1: 00333 if (partial) 00334 { 00335 push_arg_back(partial); 00336 } 00337 value_string = hits.front()->name; 00338 token = hits.front()->token; 00339 break; 00340 00341 default: 00342 { 00343 rcstring candidates; 00344 for 00345 ( 00346 hits_t::const_iterator it = hits.begin(); 00347 it != hits.end(); 00348 ++it 00349 ) 00350 { 00351 if (!candidates.empty()) 00352 candidates += ", "; 00353 candidates += (*it)->name; 00354 } 00355 explain_output_error_and_die 00356 ( 00357 "option %s is ambiguous (%s)", 00358 value_string.quote_c().c_str(), 00359 candidates.c_str() 00360 ); 00361 } 00362 } 00363 return token; 00364 } 00365 00366 00367 const char * 00368 arglex::token_name(int n) 00369 const 00370 { 00371 switch (n) 00372 { 00373 case token_eoln: 00374 return "end of command line"; 00375 00376 case token_number: 00377 return "number"; 00378 00379 case token_option: 00380 return "option"; 00381 00382 case token_stdio: 00383 return "standard input or output"; 00384 00385 case token_string: 00386 return "string"; 00387 00388 default: 00389 break; 00390 } 00391 for 00392 ( 00393 tables_t::const_iterator it = tables.begin(); 00394 it != tables.end(); 00395 ++it 00396 ) 00397 { 00398 for (const table_t *tp = *it; tp->name; tp++) 00399 { 00400 if (tp->token == n) 00401 return tp->name; 00402 } 00403 } 00404 return "unknown command line token"; 00405 } 00406 00407 00408 void 00409 arglex::help(const char *name) 00410 const 00411 { 00412 if (!name) 00413 name = explain_program_name_get(); 00414 const char *cmd[3] = { "man", name, 0 }; 00415 execvp(cmd[0], (char *const *)cmd); 00416 std::cerr << cmd[0] << ": " << strerror(errno) << std::endl; 00417 exit(1); 00418 } 00419 00420 00421 void 00422 arglex::version(void) 00423 const 00424 { 00425 const char *prog = explain_program_name_get(); 00426 std::cout 00427 << prog << " version " << version_stamp() << std::endl 00428 << "Copyright (C) " << copyright_years() << " Peter Miller" << std::endl 00429 << std::endl 00430 << "The " << prog << " program comes with ABSOLUTELY NO WARRANTY; for " 00431 "details use" << std::endl 00432 << "the '" << prog << " -LICense' command. The " << prog << " program " 00433 "is free" << std::endl 00434 << "software, and you are welcome to redistribute it under certain " 00435 "conditions;" << std::endl 00436 << "for details use the '" << prog << " -LICense' command." 00437 << std::endl; 00438 exit(0); 00439 } 00440 00441 00442 void 00443 arglex::license(void) 00444 const 00445 { 00446 help("nis-util-license"); 00447 } 00448 00449 00450 void 00451 arglex::bad_argument(void) 00452 const 00453 { 00454 switch (token_cur()) 00455 { 00456 case token_string: 00457 explain_output_error 00458 ( 00459 "%s: misplaced file name %s", 00460 get_source_location().get_both().c_str(), 00461 get_string_value().quote_c().c_str() 00462 ); 00463 break; 00464 00465 case token_number: 00466 explain_output_error 00467 ( 00468 "%s: misplaced number %s", 00469 get_source_location().get_both().c_str(), 00470 get_string_value().quote_c().c_str() 00471 ); 00472 break; 00473 00474 case token_option: 00475 explain_output_error 00476 ( 00477 "%s: unknown %s option", 00478 get_source_location().get_both().c_str(), 00479 get_string_value().quote_c().c_str() 00480 ); 00481 break; 00482 00483 case token_eoln: 00484 explain_output_error 00485 ( 00486 "%s: command line too short", 00487 get_source_location().get_both().c_str() 00488 ); 00489 break; 00490 00491 default: 00492 explain_output_error 00493 ( 00494 "%s: misplaced %s option", 00495 get_source_location().get_both().c_str(), 00496 get_string_value().quote_c().c_str() 00497 ); 00498 break; 00499 } 00500 usage(); 00501 exit(1); 00502 } 00503 00504 00505 int 00506 arglex::token_first(void) 00507 { 00508 switch (token_next()) 00509 { 00510 default: 00511 return token_cur(); 00512 00513 case token_help: 00514 if (token_next() != token_eoln) 00515 bad_argument(); 00516 help(); 00517 break; 00518 00519 case token_version: 00520 if (token_next() != token_eoln) 00521 bad_argument(); 00522 version(); 00523 break; 00524 00525 case token_license: 00526 if (token_next() != token_eoln) 00527 bad_argument(); 00528 license(); 00529 break; 00530 } 00531 exit(0); 00532 } 00533 00534 00535 void 00536 arglex::usage_tail_set(const char *s) 00537 { 00538 usage_tail_ = s; 00539 } 00540 00541 00542 const char * 00543 arglex::usage_tail_get(void) 00544 const 00545 { 00546 return (usage_tail_ ? usage_tail_ : "<filename>..."); 00547 } 00548 00549 00550 void 00551 arglex::usage(void) 00552 const 00553 { 00554 const char *prog = explain_program_name_get(); 00555 std::cerr << "Usage: " << prog << " [ <option>... ] " << usage_tail_get() 00556 << std::endl; 00557 std::cerr << " " << prog << " -Help" << std::endl; 00558 std::cerr << " " << prog << " -VERSion" << std::endl; 00559 std::cerr << " " << prog << " -LICense" << std::endl; 00560 exit(1); 00561 } 00562 00563 00564 void 00565 arglex::check_stdin_references(void) 00566 { 00567 ++number_of_stdin_references; 00568 if (number_of_stdin_references > 1) 00569 { 00570 explain_output_error_and_die 00571 ( 00572 "%s: too many references to stdin, it may only be used once", 00573 get_source_location().get_both().c_str() 00574 ); 00575 } 00576 } 00577 00578 00579 rcstring 00580 arglex::get_filename_or_stdin(void) 00581 { 00582 int t = token; 00583 switch (token_next()) 00584 { 00585 default: 00586 explain_output_error 00587 ( 00588 "%s: the %s option requires a <filename> argument, " 00589 "or \"-\" to indicate the standard input", 00590 get_source_location().get_both().c_str(), 00591 token_name(t) 00592 ); 00593 usage(); 00594 // NOTREACHED 00595 00596 case token_string: 00597 if (value_string.empty()) 00598 { 00599 explain_output_error_and_die 00600 ( 00601 "%s: you may not give the empty string as the argument to " 00602 "the %s option", 00603 get_source_location().get_both().c_str(), 00604 token_name(t) 00605 ); 00606 } 00607 return get_string_value(); 00608 00609 case token_stdio: 00610 check_stdin_references(); 00611 return "-"; 00612 } 00613 } 00614 00615 00616 void 00617 arglex::get_filename_or_stdin(rcstring &fnvar) 00618 { 00619 if (!fnvar.empty()) 00620 { 00621 explain_output_error 00622 ( 00623 "%s: too many %s options specified", 00624 get_source_location().get_both().c_str(), 00625 token_name(token) 00626 ); 00627 usage(); 00628 } 00629 fnvar = get_filename_or_stdin(); 00630 } 00631 00632 00633 void 00634 arglex::check_stdout_references(void) 00635 { 00636 ++number_of_stdout_references; 00637 if (number_of_stdout_references > 1) 00638 { 00639 explain_output_error_and_die 00640 ( 00641 "%s: too many references to stdout, it may only be used once", 00642 get_source_location().get_both().c_str() 00643 ); 00644 } 00645 } 00646 00647 00648 rcstring 00649 arglex::get_filename_or_stdout(void) 00650 { 00651 int t = token; 00652 switch (token_next()) 00653 { 00654 default: 00655 explain_output_error 00656 ( 00657 "%s: the %s option requires a <filename> argument, " 00658 "or \"-\" to indicate the standard output", 00659 get_source_location().get_both().c_str(), 00660 token_name(t) 00661 ); 00662 usage(); 00663 // NOTREACHED 00664 00665 case token_string: 00666 if (value_string.empty()) 00667 { 00668 explain_output_error_and_die 00669 ( 00670 "%s: you may not give the empty string as the argument to " 00671 "the %s option", 00672 get_source_location().get_both().c_str(), 00673 token_name(t) 00674 ); 00675 } 00676 return get_string_value(); 00677 00678 case token_stdio: 00679 check_stdout_references(); 00680 return "-"; 00681 } 00682 } 00683 00684 00685 void 00686 arglex::get_filename_or_stdout(rcstring &fnvar) 00687 { 00688 if (!fnvar.empty()) 00689 { 00690 explain_output_error 00691 ( 00692 "%s: too many %s options specified", 00693 get_source_location().get_both().c_str(), 00694 token_name(token) 00695 ); 00696 usage(); 00697 } 00698 fnvar = get_filename_or_stdout(); 00699 } 00700 00701 00702 const source_location & 00703 arglex::get_source_location(void) 00704 const 00705 { 00706 return token_locn; 00707 } 00708 00709 00710 // vim: set ts=8 sw=4 et :