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