allow options between command and -- on commandline
[ntk/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 #include<config.h>
15
16 #include <apt-pkg/configuration.h>
17 #include <apt-pkg/cmndline.h>
18 #include <apt-pkg/error.h>
19 #include <apt-pkg/strutl.h>
20
21 #include <stddef.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <string>
25
26 #include <apti18n.h>
27 /*}}}*/
28 using namespace std;
29
30 // CommandLine::CommandLine - Constructor /*{{{*/
31 // ---------------------------------------------------------------------
32 /* */
33 CommandLine::CommandLine(Args *AList,Configuration *Conf) : ArgList(AList),
34 Conf(Conf), FileList(0)
35 {
36 }
37 /*}}}*/
38 // CommandLine::~CommandLine - Destructor /*{{{*/
39 // ---------------------------------------------------------------------
40 /* */
41 CommandLine::~CommandLine()
42 {
43 delete [] FileList;
44 }
45 /*}}}*/
46 // CommandLine::GetCommand - return the first non-option word /*{{{*/
47 char const * CommandLine::GetCommand(Dispatch const * const Map,
48 unsigned int const argc, char const * const * const argv)
49 {
50 // if there is a -- on the line there must be the word we search for either
51 // before it (as -- marks the end of the options) or right after it (as we can't
52 // decide if the command is actually an option, given that in theory, you could
53 // have parameters named like commands)
54 for (size_t i = 1; i < argc; ++i)
55 {
56 if (strcmp(argv[i], "--") != 0)
57 continue;
58 // check if command is before --
59 for (size_t k = 1; k < i; ++k)
60 for (size_t j = 0; Map[j].Match != NULL; ++j)
61 if (strcmp(argv[k], Map[j].Match) == 0)
62 return Map[j].Match;
63 // see if the next token after -- is the command
64 ++i;
65 if (i < argc)
66 for (size_t j = 0; Map[j].Match != NULL; ++j)
67 if (strcmp(argv[i], Map[j].Match) == 0)
68 return Map[j].Match;
69 // we found a --, but not a command
70 return NULL;
71 }
72 // no --, so search for the first word matching a command
73 // FIXME: How like is it that an option parameter will be also a valid Match ?
74 for (size_t i = 1; i < argc; ++i)
75 {
76 if (*(argv[i]) == '-')
77 continue;
78 for (size_t j = 0; Map[j].Match != NULL; ++j)
79 if (strcmp(argv[i], Map[j].Match) == 0)
80 return Map[j].Match;
81 }
82 return NULL;
83 }
84 /*}}}*/
85 // CommandLine::Parse - Main action member /*{{{*/
86 // ---------------------------------------------------------------------
87 /* */
88 bool CommandLine::Parse(int argc,const char **argv)
89 {
90 delete [] FileList;
91 FileList = new const char *[argc];
92 const char **Files = FileList;
93 int I;
94 for (I = 1; I != argc; I++)
95 {
96 const char *Opt = argv[I];
97
98 // It is not an option
99 if (*Opt != '-')
100 {
101 *Files++ = Opt;
102 continue;
103 }
104
105 Opt++;
106
107 // Double dash signifies the end of option processing
108 if (*Opt == '-' && Opt[1] == 0)
109 {
110 I++;
111 break;
112 }
113
114 // Single dash is a short option
115 if (*Opt != '-')
116 {
117 // Iterate over each letter
118 while (*Opt != 0)
119 {
120 // Search for the option
121 Args *A;
122 for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
123 if (A->end() == true)
124 return _error->Error(_("Command line option '%c' [from %s] is not known."),*Opt,argv[I]);
125
126 if (HandleOpt(I,argc,argv,Opt,A) == false)
127 return false;
128 if (*Opt != 0)
129 Opt++;
130 }
131 continue;
132 }
133
134 Opt++;
135
136 // Match up to a = against the list
137 Args *A;
138 const char *OptEnd = strchrnul(Opt, '=');
139 for (A = ArgList; A->end() == false &&
140 (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0);
141 ++A);
142
143 // Failed, look for a word after the first - (no-foo)
144 bool PreceedMatch = false;
145 if (A->end() == true)
146 {
147 Opt = (const char*) memchr(Opt, '-', OptEnd - Opt);
148 if (Opt == NULL)
149 return _error->Error(_("Command line option %s is not understood"),argv[I]);
150 Opt++;
151
152 for (A = ArgList; A->end() == false &&
153 (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0);
154 ++A);
155
156 // Failed again..
157 if (A->end() == true && OptEnd - Opt != 1)
158 return _error->Error(_("Command line option %s is not understood"),argv[I]);
159
160 // The option could be a single letter option prefixed by a no-..
161 if (A->end() == true)
162 {
163 for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
164
165 if (A->end() == true)
166 return _error->Error(_("Command line option %s is not understood"),argv[I]);
167 }
168
169 // The option is not boolean
170 if (A->IsBoolean() == false)
171 return _error->Error(_("Command line option %s is not boolean"),argv[I]);
172 PreceedMatch = true;
173 }
174
175 // Deal with it.
176 OptEnd--;
177 if (HandleOpt(I,argc,argv,OptEnd,A,PreceedMatch) == false)
178 return false;
179 }
180
181 // Copy any remaining file names over
182 for (; I != argc; I++)
183 *Files++ = argv[I];
184 *Files = 0;
185
186 SaveInConfig(argc, argv);
187
188 return true;
189 }
190 /*}}}*/
191 // CommandLine::HandleOpt - Handle a single option including all flags /*{{{*/
192 // ---------------------------------------------------------------------
193 /* This is a helper function for parser, it looks at a given argument
194 and looks for specific patterns in the string, it gets tokanized
195 -ruffly- like -*[yes|true|enable]-(o|longopt)[=][ ][argument] */
196 bool CommandLine::HandleOpt(int &I,int argc,const char *argv[],
197 const char *&Opt,Args *A,bool PreceedMatch)
198 {
199 const char *Argument = 0;
200 bool CertainArg = false;
201 int IncI = 0;
202
203 /* Determine the possible location of an option or 0 if their is
204 no option */
205 if (Opt[1] == 0 || (Opt[1] == '=' && Opt[2] == 0))
206 {
207 if (I + 1 < argc && argv[I+1][0] != '-')
208 Argument = argv[I+1];
209
210 // Equals was specified but we fell off the end!
211 if (Opt[1] == '=' && Argument == 0)
212 return _error->Error(_("Option %s requires an argument."),argv[I]);
213 if (Opt[1] == '=')
214 CertainArg = true;
215
216 IncI = 1;
217 }
218 else
219 {
220 if (Opt[1] == '=')
221 {
222 CertainArg = true;
223 Argument = Opt + 2;
224 }
225 else
226 Argument = Opt + 1;
227 }
228
229 // Option is an argument set
230 if ((A->Flags & HasArg) == HasArg)
231 {
232 if (Argument == 0)
233 return _error->Error(_("Option %s requires an argument."),argv[I]);
234 Opt += strlen(Opt);
235 I += IncI;
236
237 // Parse a configuration file
238 if ((A->Flags & ConfigFile) == ConfigFile)
239 return ReadConfigFile(*Conf,Argument);
240
241 // Arbitrary item specification
242 if ((A->Flags & ArbItem) == ArbItem)
243 {
244 const char *J = strchr(Argument, '=');
245 if (J == NULL)
246 return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
247
248 // = is trailing
249 if (J[1] == 0)
250 {
251 if (I+1 >= argc)
252 return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
253 Conf->Set(string(Argument,J-Argument),string(argv[I++ +1]));
254 }
255 else
256 Conf->Set(string(Argument,J-Argument),string(J+1));
257
258 return true;
259 }
260
261 const char *I = strchrnul(A->ConfName, ' ');
262 if (*I == ' ')
263 Conf->Set(string(A->ConfName,0,I-A->ConfName),string(I+1) + Argument);
264 else
265 Conf->Set(A->ConfName,string(I) + Argument);
266
267 return true;
268 }
269
270 // Option is an integer level
271 if ((A->Flags & IntLevel) == IntLevel)
272 {
273 // There might be an argument
274 if (Argument != 0)
275 {
276 char *EndPtr;
277 unsigned long Value = strtol(Argument,&EndPtr,10);
278
279 // Conversion failed and the argument was specified with an =s
280 if (EndPtr == Argument && CertainArg == true)
281 return _error->Error(_("Option %s requires an integer argument, not '%s'"),argv[I],Argument);
282
283 // Conversion was ok, set the value and return
284 if (EndPtr != 0 && EndPtr != Argument && *EndPtr == 0)
285 {
286 Conf->Set(A->ConfName,Value);
287 Opt += strlen(Opt);
288 I += IncI;
289 return true;
290 }
291 }
292
293 // Increase the level
294 Conf->Set(A->ConfName,Conf->FindI(A->ConfName)+1);
295 return true;
296 }
297
298 // Option is a boolean
299 int Sense = -1; // -1 is unspecified, 0 is yes 1 is no
300
301 // Look for an argument.
302 while (1)
303 {
304 // Look at preceding text
305 char Buffer[300];
306 if (Argument == 0)
307 {
308 if (PreceedMatch == false)
309 break;
310
311 if (strlen(argv[I]) >= sizeof(Buffer))
312 return _error->Error(_("Option '%s' is too long"),argv[I]);
313
314 // Skip the leading dash
315 const char *J = argv[I];
316 for (; *J != 0 && *J == '-'; J++);
317
318 const char *JEnd = strchr(J, '-');
319 if (JEnd != NULL)
320 {
321 strncpy(Buffer,J,JEnd - J);
322 Buffer[JEnd - J] = 0;
323 Argument = Buffer;
324 CertainArg = true;
325 }
326 else
327 break;
328 }
329
330 // Check for boolean
331 Sense = StringToBool(Argument);
332 if (Sense >= 0)
333 {
334 // Eat the argument
335 if (Argument != Buffer)
336 {
337 Opt += strlen(Opt);
338 I += IncI;
339 }
340 break;
341 }
342
343 if (CertainArg == true)
344 return _error->Error(_("Sense %s is not understood, try true or false."),Argument);
345
346 Argument = 0;
347 }
348
349 // Indeterminate sense depends on the flag
350 if (Sense == -1)
351 {
352 if ((A->Flags & InvBoolean) == InvBoolean)
353 Sense = 0;
354 else
355 Sense = 1;
356 }
357
358 Conf->Set(A->ConfName,Sense);
359 return true;
360 }
361 /*}}}*/
362 // CommandLine::FileSize - Count the number of filenames /*{{{*/
363 // ---------------------------------------------------------------------
364 /* */
365 unsigned int CommandLine::FileSize() const
366 {
367 unsigned int Count = 0;
368 for (const char **I = FileList; I != 0 && *I != 0; I++)
369 Count++;
370 return Count;
371 }
372 /*}}}*/
373 // CommandLine::DispatchArg - Do something with the first arg /*{{{*/
374 // ---------------------------------------------------------------------
375 /* */
376 bool CommandLine::DispatchArg(Dispatch *Map,bool NoMatch)
377 {
378 int I;
379 for (I = 0; Map[I].Match != 0; I++)
380 {
381 if (strcmp(FileList[0],Map[I].Match) == 0)
382 {
383 bool Res = Map[I].Handler(*this);
384 if (Res == false && _error->PendingError() == false)
385 _error->Error("Handler silently failed");
386 return Res;
387 }
388 }
389
390 // No matching name
391 if (Map[I].Match == 0)
392 {
393 if (NoMatch == true)
394 _error->Error(_("Invalid operation %s"),FileList[0]);
395 }
396
397 return false;
398 }
399 /*}}}*/
400 // CommandLine::SaveInConfig - for output later in a logfile or so /*{{{*/
401 // ---------------------------------------------------------------------
402 /* We save the commandline here to have it around later for e.g. logging.
403 It feels a bit like a hack here and isn't bulletproof, but it is better
404 than nothing after all. */
405 void CommandLine::SaveInConfig(unsigned int const &argc, char const * const * const argv)
406 {
407 char cmdline[100 + argc * 50];
408 memset(cmdline, 0, sizeof(cmdline));
409 unsigned int length = 0;
410 bool lastWasOption = false;
411 bool closeQuote = false;
412 for (unsigned int i = 0; i < argc && length < sizeof(cmdline); ++i, ++length)
413 {
414 for (unsigned int j = 0; argv[i][j] != '\0' && length < sizeof(cmdline)-1; ++j, ++length)
415 {
416 cmdline[length] = argv[i][j];
417 if (lastWasOption == true && argv[i][j] == '=')
418 {
419 // That is possibly an option: Quote it if it includes spaces,
420 // the benefit is that this will eliminate also most false positives
421 const char* c = strchr(&argv[i][j+1], ' ');
422 if (c == NULL) continue;
423 cmdline[++length] = '"';
424 closeQuote = true;
425 }
426 }
427 if (closeQuote == true)
428 cmdline[length++] = '"';
429 // Problem: detects also --hello
430 if (cmdline[length-1] == 'o')
431 lastWasOption = true;
432 cmdline[length] = ' ';
433 }
434 cmdline[--length] = '\0';
435 _config->Set("CommandLine::AsString", cmdline);
436 }
437 /*}}}*/
438 CommandLine::Args CommandLine::MakeArgs(char ShortOpt, char const *LongOpt, char const *ConfName, unsigned long Flags)/*{{{*/
439 {
440 /* In theory, this should be a constructor for CommandLine::Args instead,
441 but this breaks compatibility as gcc thinks this is a c++11 initializer_list */
442 CommandLine::Args arg;
443 arg.ShortOpt = ShortOpt;
444 arg.LongOpt = LongOpt;
445 arg.ConfName = ConfName;
446 arg.Flags = Flags;
447 return arg;
448 }
449 /*}}}*/