- initial import of revision 374 from cnc
[apt.git] / apt-pkg / contrib / cmndline.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: cmndline.cc,v 1.15 2003/02/10 01:40:58 doogie Exp $
4 /* ######################################################################
5
6    Command Line Class - Sophisticated command line parser
7    
8    This source is placed in the Public Domain, do with it what you will
9    It was originally written by Jason Gunthorpe <jgg@debian.org>.
10    
11    ##################################################################### */
12                                                                         /*}}}*/
13 // Include files                                                        /*{{{*/
14 #ifdef __GNUG__
15 #pragma implementation "apt-pkg/cmndline.h"
16 #endif
17 #include <apt-pkg/cmndline.h>
18 #include <apt-pkg/error.h>
19 #include <apt-pkg/strutl.h>
20
21 #include <apti18n.h>    
22                                                                         /*}}}*/
23 using namespace std;
24
25 // CommandLine::CommandLine - Constructor                               /*{{{*/
26 // ---------------------------------------------------------------------
27 /* */
28 CommandLine::CommandLine(Args *AList,Configuration *Conf) : ArgList(AList), 
29                                  Conf(Conf), FileList(0)
30 {
31 }
32                                                                         /*}}}*/
33 // CommandLine::~CommandLine - Destructor                               /*{{{*/
34 // ---------------------------------------------------------------------
35 /* */
36 CommandLine::~CommandLine()
37 {
38    delete [] FileList;
39 }
40                                                                         /*}}}*/
41 // CommandLine::Parse - Main action member                              /*{{{*/
42 // ---------------------------------------------------------------------
43 /* */
44 bool CommandLine::Parse(int argc,const char **argv)
45 {
46    delete [] FileList;
47    FileList = new const char *[argc];
48    const char **Files = FileList;
49    int I;
50    for (I = 1; I != argc; I++)
51    {
52       const char *Opt = argv[I];
53       
54       // It is not an option
55       if (*Opt != '-')
56       {
57          *Files++ = Opt;
58          continue;
59       }
60       
61       Opt++;
62       
63       // Double dash signifies the end of option processing
64       if (*Opt == '-' && Opt[1] == 0)
65       {
66          I++;
67          break;
68       }
69       
70       // Single dash is a short option
71       if (*Opt != '-')
72       {
73          // Iterate over each letter
74          while (*Opt != 0)
75          {          
76             // Search for the option
77             Args *A;
78             for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
79             if (A->end() == true)
80                return _error->Error(_("Command line option '%c' [from %s] is not known."),*Opt,argv[I]);
81
82             if (HandleOpt(I,argc,argv,Opt,A) == false)
83                return false;
84             if (*Opt != 0)
85                Opt++;          
86          }
87          continue;
88       }
89       
90       Opt++;
91
92       // Match up to a = against the list
93       const char *OptEnd = Opt;
94       Args *A;
95       for (; *OptEnd != 0 && *OptEnd != '='; OptEnd++);
96       for (A = ArgList; A->end() == false && 
97            stringcasecmp(Opt,OptEnd,A->LongOpt) != 0; A++);
98       
99       // Failed, look for a word after the first - (no-foo)
100       bool PreceedMatch = false;
101       if (A->end() == true)
102       {
103          for (; Opt != OptEnd && *Opt != '-'; Opt++);
104
105          if (Opt == OptEnd)
106             return _error->Error(_("Command line option %s is not understood"),argv[I]);
107          Opt++;
108          
109          for (A = ArgList; A->end() == false &&
110               stringcasecmp(Opt,OptEnd,A->LongOpt) != 0; A++);
111
112          // Failed again..
113          if (A->end() == true && OptEnd - Opt != 1)
114             return _error->Error(_("Command line option %s is not understood"),argv[I]);
115
116          // The option could be a single letter option prefixed by a no-..
117          if (A->end() == true)
118          {
119             for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
120             
121             if (A->end() == true)
122                return _error->Error(_("Command line option %s is not understood"),argv[I]);
123          }
124          
125          // The option is not boolean
126          if (A->IsBoolean() == false)
127             return _error->Error(_("Command line option %s is not boolean"),argv[I]);
128          PreceedMatch = true;
129       }
130       
131       // Deal with it.
132       OptEnd--;
133       if (HandleOpt(I,argc,argv,OptEnd,A,PreceedMatch) == false)
134          return false;
135    }
136    
137    // Copy any remaining file names over
138    for (; I != argc; I++)
139       *Files++ = argv[I];
140    *Files = 0;
141    
142    return true;
143 }
144                                                                         /*}}}*/
145 // CommandLine::HandleOpt - Handle a single option including all flags  /*{{{*/
146 // ---------------------------------------------------------------------
147 /* This is a helper function for parser, it looks at a given argument
148    and looks for specific patterns in the string, it gets tokanized
149    -ruffly- like -*[yes|true|enable]-(o|longopt)[=][ ][argument] */
150 bool CommandLine::HandleOpt(int &I,int argc,const char *argv[],
151                             const char *&Opt,Args *A,bool PreceedMatch)
152 {
153    const char *Argument = 0;
154    bool CertainArg = false;
155    int IncI = 0;
156
157    /* Determine the possible location of an option or 0 if their is
158       no option */
159    if (Opt[1] == 0 || (Opt[1] == '=' && Opt[2] == 0))
160    {
161       if (I + 1 < argc && argv[I+1][0] != '-')
162          Argument = argv[I+1];
163       
164       // Equals was specified but we fell off the end!
165       if (Opt[1] == '=' && Argument == 0)
166          return _error->Error(_("Option %s requires an argument."),argv[I]);
167       if (Opt[1] == '=')
168          CertainArg = true;
169          
170       IncI = 1;
171    }
172    else
173    {
174       if (Opt[1] == '=')
175       {
176          CertainArg = true;
177          Argument = Opt + 2;
178       }      
179       else
180          Argument = Opt + 1;
181    }
182    
183    // Option is an argument set
184    if ((A->Flags & HasArg) == HasArg)
185    {
186       if (Argument == 0)
187          return _error->Error(_("Option %s requires an argument."),argv[I]);
188       Opt += strlen(Opt);
189       I += IncI;
190       
191       // Parse a configuration file
192       if ((A->Flags & ConfigFile) == ConfigFile)
193          return ReadConfigFile(*Conf,Argument);
194
195       // Arbitary item specification
196       if ((A->Flags & ArbItem) == ArbItem)
197       {
198          const char *J;
199          for (J = Argument; *J != 0 && *J != '='; J++);
200          if (*J == 0)
201             return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
202
203          // = is trailing
204          if (J[1] == 0)
205          {
206             if (I+1 >= argc)
207                return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
208             Conf->Set(string(Argument,J-Argument),string(argv[I++ +1]));
209          }
210          else
211             Conf->Set(string(Argument,J-Argument),string(J+1));
212          
213          return true;
214       }
215       
216       const char *I = A->ConfName;
217       for (; *I != 0 && *I != ' '; I++);
218       if (*I == ' ')
219          Conf->Set(string(A->ConfName,0,I-A->ConfName),string(I+1) + Argument);
220       else
221          Conf->Set(A->ConfName,string(I) + Argument);
222          
223       return true;
224    }
225    
226    // Option is an integer level
227    if ((A->Flags & IntLevel) == IntLevel)
228    {
229       // There might be an argument
230       if (Argument != 0)
231       {
232          char *EndPtr;
233          unsigned long Value = strtol(Argument,&EndPtr,10);
234          
235          // Conversion failed and the argument was specified with an =s
236          if (EndPtr == Argument && CertainArg == true)
237             return _error->Error(_("Option %s requires an integer argument, not '%s'"),argv[I],Argument);
238
239          // Conversion was ok, set the value and return
240          if (EndPtr != 0 && EndPtr != Argument && *EndPtr == 0)
241          {
242             Conf->Set(A->ConfName,Value);
243             Opt += strlen(Opt);
244             I += IncI;
245             return true;
246          }       
247       }      
248       
249       // Increase the level
250       Conf->Set(A->ConfName,Conf->FindI(A->ConfName)+1);
251       return true;
252    }
253   
254    // Option is a boolean
255    int Sense = -1;  // -1 is unspecified, 0 is yes 1 is no
256
257    // Look for an argument.
258    while (1)
259    {
260       // Look at preceeding text
261       char Buffer[300];
262       if (Argument == 0)
263       {
264          if (PreceedMatch == false)
265             break;
266          
267          if (strlen(argv[I]) >= sizeof(Buffer))
268             return _error->Error(_("Option '%s' is too long"),argv[I]);
269
270          // Skip the leading dash
271          const char *J = argv[I];
272          for (; *J != 0 && *J == '-'; J++);
273          
274          const char *JEnd = J;
275          for (; *JEnd != 0 && *JEnd != '-'; JEnd++);
276          if (*JEnd != 0)
277          {
278             strncpy(Buffer,J,JEnd - J);
279             Buffer[JEnd - J] = 0;
280             Argument = Buffer;
281             CertainArg = true;
282          }       
283          else
284             break;
285       }
286
287       // Check for boolean
288       Sense = StringToBool(Argument);
289       if (Sense >= 0)
290       {
291          // Eat the argument     
292          if (Argument != Buffer)
293          {
294             Opt += strlen(Opt);
295             I += IncI;
296          }       
297          break;
298       }
299
300       if (CertainArg == true)
301          return _error->Error(_("Sense %s is not understood, try true or false."),Argument);
302       
303       Argument = 0;
304    }
305       
306    // Indeterminate sense depends on the flag
307    if (Sense == -1)
308    {
309       if ((A->Flags & InvBoolean) == InvBoolean)
310          Sense = 0;
311       else
312          Sense = 1;
313    }
314    
315    Conf->Set(A->ConfName,Sense);
316    return true;
317 }
318                                                                         /*}}}*/
319 // CommandLine::FileSize - Count the number of filenames                /*{{{*/
320 // ---------------------------------------------------------------------
321 /* */
322 unsigned int CommandLine::FileSize() const
323 {
324    unsigned int Count = 0;
325    for (const char **I = FileList; I != 0 && *I != 0; I++)
326       Count++;
327    return Count;
328 }
329                                                                         /*}}}*/
330 // CommandLine::DispatchArg - Do something with the first arg           /*{{{*/
331 // ---------------------------------------------------------------------
332 /* */
333 bool CommandLine::DispatchArg(Dispatch *Map,bool NoMatch)
334 {
335    int I;
336    for (I = 0; Map[I].Match != 0; I++)
337    {
338       if (strcmp(FileList[0],Map[I].Match) == 0)
339       {
340          bool Res = Map[I].Handler(*this);
341          if (Res == false && _error->PendingError() == false)
342             _error->Error("Handler silently failed");
343          return Res;
344       }
345    }
346    
347    // No matching name
348    if (Map[I].Match == 0)
349    {
350       if (NoMatch == true)
351          _error->Error(_("Invalid operation %s"),FileList[0]);
352    }
353    
354    return false;
355 }
356                                                                         /*}}}*/