nis-util  1.0.D108
lib/arglex.cc
Go to the documentation of this file.
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 :