Merge remote-tracking branch 'upstream/debian/sid' into feature/apt-manpage
[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 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 /*}}}*/
82 // CommandLine::Parse - Main action member /*{{{*/
83 // ---------------------------------------------------------------------
84 /* */
85 bool CommandLine::Parse(int argc,const char **argv)
86 {
87 delete [] FileList;
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)
106 {
107 I++;
108 break;
109 }
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)
121 return _error->Error(_("Command line option '%c' [from %s] is not known."),*Opt,argv[I]);
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
134 Args *A;
135 const char *OptEnd = strchrnul(Opt, '=');
136 for (A = ArgList; A->end() == false &&
137 (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0);
138 ++A);
139
140 // Failed, look for a word after the first - (no-foo)
141 bool PreceedMatch = false;
142 if (A->end() == true)
143 {
144 Opt = (const char*) memchr(Opt, '-', OptEnd - Opt);
145 if (Opt == NULL)
146 return _error->Error(_("Command line option %s is not understood"),argv[I]);
147 Opt++;
148
149 for (A = ArgList; A->end() == false &&
150 (A->LongOpt == 0 || stringcasecmp(Opt,OptEnd,A->LongOpt) != 0);
151 ++A);
152
153 // Failed again..
154 if (A->end() == true && OptEnd - Opt != 1)
155 return _error->Error(_("Command line option %s is not understood"),argv[I]);
156
157 // The option could be a single letter option prefixed by a no-..
158 if (A->end() == true)
159 {
160 for (A = ArgList; A->end() == false && A->ShortOpt != *Opt; A++);
161
162 if (A->end() == true)
163 return _error->Error(_("Command line option %s is not understood"),argv[I]);
164 }
165
166 // The option is not boolean
167 if (A->IsBoolean() == false)
168 return _error->Error(_("Command line option %s is not boolean"),argv[I]);
169 PreceedMatch = true;
170 }
171
172 // Deal with it.
173 OptEnd--;
174 if (HandleOpt(I,argc,argv,OptEnd,A,PreceedMatch) == false)
175 return false;
176 }
177
178 // Copy any remaining file names over
179 for (; I != argc; I++)
180 *Files++ = argv[I];
181 *Files = 0;
182
183 SaveInConfig(argc, argv);
184
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] */
193 bool CommandLine::HandleOpt(int &I,int argc,const char *argv[],
194 const char *&Opt,Args *A,bool PreceedMatch)
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)
209 return _error->Error(_("Option %s requires an argument."),argv[I]);
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 }
225
226 // Option is an argument set
227 if ((A->Flags & HasArg) == HasArg)
228 {
229 if (Argument == 0)
230 return _error->Error(_("Option %s requires an argument."),argv[I]);
231 Opt += strlen(Opt);
232 I += IncI;
233
234 // Parse a configuration file
235 if ((A->Flags & ConfigFile) == ConfigFile)
236 return ReadConfigFile(*Conf,Argument);
237
238 // Arbitrary item specification
239 if ((A->Flags & ArbItem) == ArbItem)
240 {
241 const char *J = strchr(Argument, '=');
242 if (J == NULL)
243 return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
244
245 // = is trailing
246 if (J[1] == 0)
247 {
248 if (I+1 >= argc)
249 return _error->Error(_("Option %s: Configuration item specification must have an =<val>."),argv[I]);
250 Conf->Set(string(Argument,J-Argument),string(argv[I++ +1]));
251 }
252 else
253 Conf->Set(string(Argument,J-Argument),string(J+1));
254
255 return true;
256 }
257
258 const char *I = strchrnul(A->ConfName, ' ');
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
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)
278 return _error->Error(_("Option %s requires an integer argument, not '%s'"),argv[I],Argument);
279
280 // Conversion was ok, set the value and return
281 if (EndPtr != 0 && EndPtr != Argument && *EndPtr == 0)
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 {
301 // Look at preceding text
302 char Buffer[300];
303 if (Argument == 0)
304 {
305 if (PreceedMatch == false)
306 break;
307
308 if (strlen(argv[I]) >= sizeof(Buffer))
309 return _error->Error(_("Option '%s' is too long"),argv[I]);
310
311 // Skip the leading dash
312 const char *J = argv[I];
313 for (; *J != 0 && *J == '-'; J++);
314
315 const char *JEnd = strchr(J, '-');
316 if (JEnd != NULL)
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
327 // Check for boolean
328 Sense = StringToBool(Argument);
329 if (Sense >= 0)
330 {
331 // Eat the argument
332 if (Argument != Buffer)
333 {
334 Opt += strlen(Opt);
335 I += IncI;
336 }
337 break;
338 }
339
340 if (CertainArg == true)
341 return _error->Error(_("Sense %s is not understood, try true or false."),Argument);
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 /*}}}*/
359 // CommandLine::FileSize - Count the number of filenames /*{{{*/
360 // ---------------------------------------------------------------------
361 /* */
362 unsigned 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 /*}}}*/
370 // CommandLine::DispatchArg - Do something with the first arg /*{{{*/
371 // ---------------------------------------------------------------------
372 /* */
373 bool CommandLine::DispatchArg(Dispatch *Map,bool NoMatch)
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)
389 {
390 if (NoMatch == true)
391 _error->Error(_("Invalid operation %s"),FileList[0]);
392 }
393
394 return false;
395 }
396 /*}}}*/
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. */
402 void CommandLine::SaveInConfig(unsigned int const &argc, char const * const * const argv)
403 {
404 char cmdline[100 + argc * 50];
405 memset(cmdline, 0, sizeof(cmdline));
406 unsigned int length = 0;
407 bool lastWasOption = false;
408 bool closeQuote = false;
409 for (unsigned int i = 0; i < argc && length < sizeof(cmdline); ++i, ++length)
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
418 const char* c = strchr(&argv[i][j+1], ' ');
419 if (c == NULL) continue;
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 /*}}}*/
435 CommandLine::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 /*}}}*/