]>
git.saurik.com Git - apt.git/blob - apt-pkg/contrib/cmndline.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: cmndline.cc,v 1.15 2003/02/10 01:40:58 doogie Exp $
4 /* ######################################################################
6 Command Line Class - Sophisticated command line parser
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>.
11 ##################################################################### */
13 // Include files /*{{{*/
16 #include <apt-pkg/configuration.h>
17 #include <apt-pkg/cmndline.h>
18 #include <apt-pkg/error.h>
19 #include <apt-pkg/strutl.h>
30 // CommandLine::CommandLine - Constructor /*{{{*/
31 // ---------------------------------------------------------------------
33 CommandLine::CommandLine(Args
*AList
,Configuration
*Conf
) : ArgList(AList
),
34 Conf(Conf
), FileList(0)
37 CommandLine::CommandLine() : ArgList(NULL
), Conf(NULL
), FileList(0)
41 // CommandLine::~CommandLine - Destructor /*{{{*/
42 // ---------------------------------------------------------------------
44 CommandLine::~CommandLine()
49 // CommandLine::GetCommand - return the first non-option word /*{{{*/
50 char const * CommandLine::GetCommand(Dispatch
const * const Map
,
51 unsigned int const argc
, char const * const * const argv
)
53 // if there is a -- on the line there must be the word we search for either
54 // before it (as -- marks the end of the options) or right after it (as we can't
55 // decide if the command is actually an option, given that in theory, you could
56 // have parameters named like commands)
57 for (size_t i
= 1; i
< argc
; ++i
)
59 if (strcmp(argv
[i
], "--") != 0)
61 // check if command is before --
62 for (size_t k
= 1; k
< i
; ++k
)
63 for (size_t j
= 0; Map
[j
].Match
!= NULL
; ++j
)
64 if (strcmp(argv
[k
], Map
[j
].Match
) == 0)
66 // see if the next token after -- is the command
69 for (size_t j
= 0; Map
[j
].Match
!= NULL
; ++j
)
70 if (strcmp(argv
[i
], Map
[j
].Match
) == 0)
72 // we found a --, but not a command
75 // no --, so search for the first word matching a command
76 // FIXME: How like is it that an option parameter will be also a valid Match ?
77 for (size_t i
= 1; i
< argc
; ++i
)
79 if (*(argv
[i
]) == '-')
81 for (size_t j
= 0; Map
[j
].Match
!= NULL
; ++j
)
82 if (strcmp(argv
[i
], Map
[j
].Match
) == 0)
87 char const * CommandLine::GetCommand(DispatchWithHelp
const * const Map
,
88 unsigned int const argc
, char const * const * const argv
)
90 // if there is a -- on the line there must be the word we search for either
91 // before it (as -- marks the end of the options) or right after it (as we can't
92 // decide if the command is actually an option, given that in theory, you could
93 // have parameters named like commands)
94 for (size_t i
= 1; i
< argc
; ++i
)
96 if (strcmp(argv
[i
], "--") != 0)
98 // check if command is before --
99 for (size_t k
= 1; k
< i
; ++k
)
100 for (size_t j
= 0; Map
[j
].Match
!= NULL
; ++j
)
101 if (strcmp(argv
[k
], Map
[j
].Match
) == 0)
103 // see if the next token after -- is the command
106 for (size_t j
= 0; Map
[j
].Match
!= NULL
; ++j
)
107 if (strcmp(argv
[i
], Map
[j
].Match
) == 0)
109 // we found a --, but not a command
112 // no --, so search for the first word matching a command
113 // FIXME: How like is it that an option parameter will be also a valid Match ?
114 for (size_t i
= 1; i
< argc
; ++i
)
116 if (*(argv
[i
]) == '-')
118 for (size_t j
= 0; Map
[j
].Match
!= NULL
; ++j
)
119 if (strcmp(argv
[i
], Map
[j
].Match
) == 0)
125 // CommandLine::Parse - Main action member /*{{{*/
126 // ---------------------------------------------------------------------
128 bool CommandLine::Parse(int argc
,const char **argv
)
131 FileList
= new const char *[argc
];
132 const char **Files
= FileList
;
134 for (I
= 1; I
!= argc
; I
++)
136 const char *Opt
= argv
[I
];
138 // It is not an option
147 // Double dash signifies the end of option processing
148 if (*Opt
== '-' && Opt
[1] == 0)
154 // Single dash is a short option
157 // Iterate over each letter
160 // Search for the option
162 for (A
= ArgList
; A
->end() == false && A
->ShortOpt
!= *Opt
; A
++);
163 if (A
->end() == true)
164 return _error
->Error(_("Command line option '%c' [from %s] is not understood in combination with the other options."),*Opt
,argv
[I
]);
166 if (HandleOpt(I
,argc
,argv
,Opt
,A
) == false)
176 // Match up to a = against the list
178 const char *OptEnd
= strchrnul(Opt
, '=');
179 for (A
= ArgList
; A
->end() == false &&
180 (A
->LongOpt
== 0 || stringcasecmp(Opt
,OptEnd
,A
->LongOpt
) != 0);
183 // Failed, look for a word after the first - (no-foo)
184 bool PreceedMatch
= false;
185 if (A
->end() == true)
187 Opt
= (const char*) memchr(Opt
, '-', OptEnd
- Opt
);
189 return _error
->Error(_("Command line option %s is not understood in combination with the other options"),argv
[I
]);
192 for (A
= ArgList
; A
->end() == false &&
193 (A
->LongOpt
== 0 || stringcasecmp(Opt
,OptEnd
,A
->LongOpt
) != 0);
197 if (A
->end() == true && OptEnd
- Opt
!= 1)
198 return _error
->Error(_("Command line option %s is not understood in combination with the other options"),argv
[I
]);
200 // The option could be a single letter option prefixed by a no-..
201 if (A
->end() == true)
203 for (A
= ArgList
; A
->end() == false && A
->ShortOpt
!= *Opt
; A
++);
205 if (A
->end() == true)
206 return _error
->Error(_("Command line option %s is not understood in combination with the other options"),argv
[I
]);
209 // The option is not boolean
210 if (A
->IsBoolean() == false)
211 return _error
->Error(_("Command line option %s is not boolean"),argv
[I
]);
217 if (HandleOpt(I
,argc
,argv
,OptEnd
,A
,PreceedMatch
) == false)
221 // Copy any remaining file names over
222 for (; I
!= argc
; I
++)
226 SaveInConfig(argc
, argv
);
231 // CommandLine::HandleOpt - Handle a single option including all flags /*{{{*/
232 // ---------------------------------------------------------------------
233 /* This is a helper function for parser, it looks at a given argument
234 and looks for specific patterns in the string, it gets tokanized
235 -ruffly- like -*[yes|true|enable]-(o|longopt)[=][ ][argument] */
236 bool CommandLine::HandleOpt(int &I
,int argc
,const char *argv
[],
237 const char *&Opt
,Args
*A
,bool PreceedMatch
)
239 const char *Argument
= 0;
240 bool CertainArg
= false;
243 /* Determine the possible location of an option or 0 if their is
245 if (Opt
[1] == 0 || (Opt
[1] == '=' && Opt
[2] == 0))
247 if (I
+ 1 < argc
&& argv
[I
+1][0] != '-')
248 Argument
= argv
[I
+1];
250 // Equals was specified but we fell off the end!
251 if (Opt
[1] == '=' && Argument
== 0)
252 return _error
->Error(_("Option %s requires an argument."),argv
[I
]);
269 // Option is an argument set
270 if ((A
->Flags
& HasArg
) == HasArg
)
273 return _error
->Error(_("Option %s requires an argument."),argv
[I
]);
277 // Parse a configuration file
278 if ((A
->Flags
& ConfigFile
) == ConfigFile
)
279 return ReadConfigFile(*Conf
,Argument
);
281 // Arbitrary item specification
282 if ((A
->Flags
& ArbItem
) == ArbItem
)
284 const char *J
= strchr(Argument
, '=');
286 return _error
->Error(_("Option %s: Configuration item specification must have an =<val>."),argv
[I
]);
292 return _error
->Error(_("Option %s: Configuration item specification must have an =<val>."),argv
[I
]);
293 Conf
->Set(string(Argument
,J
-Argument
),string(argv
[I
++ +1]));
296 Conf
->Set(string(Argument
,J
-Argument
),string(J
+1));
301 const char *I
= strchrnul(A
->ConfName
, ' ');
303 Conf
->Set(string(A
->ConfName
,0,I
-A
->ConfName
),string(I
+1) + Argument
);
305 Conf
->Set(A
->ConfName
,string(I
) + Argument
);
310 // Option is an integer level
311 if ((A
->Flags
& IntLevel
) == IntLevel
)
313 // There might be an argument
317 unsigned long Value
= strtol(Argument
,&EndPtr
,10);
319 // Conversion failed and the argument was specified with an =s
320 if (EndPtr
== Argument
&& CertainArg
== true)
321 return _error
->Error(_("Option %s requires an integer argument, not '%s'"),argv
[I
],Argument
);
323 // Conversion was ok, set the value and return
324 if (EndPtr
!= 0 && EndPtr
!= Argument
&& *EndPtr
== 0)
326 Conf
->Set(A
->ConfName
,Value
);
333 // Increase the level
334 Conf
->Set(A
->ConfName
,Conf
->FindI(A
->ConfName
)+1);
338 // Option is a boolean
339 int Sense
= -1; // -1 is unspecified, 0 is yes 1 is no
341 // Look for an argument.
344 // Look at preceding text
348 if (PreceedMatch
== false)
351 if (strlen(argv
[I
]) >= sizeof(Buffer
))
352 return _error
->Error(_("Option '%s' is too long"),argv
[I
]);
354 // Skip the leading dash
355 const char *J
= argv
[I
];
356 for (; *J
!= 0 && *J
== '-'; J
++);
358 const char *JEnd
= strchr(J
, '-');
361 strncpy(Buffer
,J
,JEnd
- J
);
362 Buffer
[JEnd
- J
] = 0;
371 Sense
= StringToBool(Argument
);
375 if (Argument
!= Buffer
)
383 if (CertainArg
== true)
384 return _error
->Error(_("Sense %s is not understood, try true or false."),Argument
);
389 // Indeterminate sense depends on the flag
392 if ((A
->Flags
& InvBoolean
) == InvBoolean
)
398 Conf
->Set(A
->ConfName
,Sense
);
402 // CommandLine::FileSize - Count the number of filenames /*{{{*/
403 // ---------------------------------------------------------------------
405 unsigned int CommandLine::FileSize() const
407 unsigned int Count
= 0;
408 for (const char **I
= FileList
; I
!= 0 && *I
!= 0; I
++)
413 // CommandLine::DispatchArg - Do something with the first arg /*{{{*/
414 bool CommandLine::DispatchArg(DispatchWithHelp
const * const Map
,bool NoMatch
)
417 for (I
= 0; Map
[I
].Match
!= 0; I
++)
419 if (strcmp(FileList
[0],Map
[I
].Match
) == 0)
421 bool Res
= Map
[I
].Handler(*this);
422 if (Res
== false && _error
->PendingError() == false)
423 _error
->Error("Handler silently failed");
429 if (Map
[I
].Match
== 0)
432 _error
->Error(_("Invalid operation %s"),FileList
[0]);
437 bool CommandLine::DispatchArg(Dispatch
*Map
,bool NoMatch
)
440 for (I
= 0; Map
[I
].Match
!= 0; I
++)
442 if (strcmp(FileList
[0],Map
[I
].Match
) == 0)
444 bool Res
= Map
[I
].Handler(*this);
445 if (Res
== false && _error
->PendingError() == false)
446 _error
->Error("Handler silently failed");
452 if (Map
[I
].Match
== 0)
455 _error
->Error(_("Invalid operation %s"),FileList
[0]);
461 // CommandLine::SaveInConfig - for output later in a logfile or so /*{{{*/
462 // ---------------------------------------------------------------------
463 /* We save the commandline here to have it around later for e.g. logging.
464 It feels a bit like a hack here and isn't bulletproof, but it is better
465 than nothing after all. */
466 void CommandLine::SaveInConfig(unsigned int const &argc
, char const * const * const argv
)
468 char cmdline
[100 + argc
* 50];
469 memset(cmdline
, 0, sizeof(cmdline
));
470 unsigned int length
= 0;
471 bool lastWasOption
= false;
472 bool closeQuote
= false;
473 for (unsigned int i
= 0; i
< argc
&& length
< sizeof(cmdline
); ++i
, ++length
)
475 for (unsigned int j
= 0; argv
[i
][j
] != '\0' && length
< sizeof(cmdline
)-1; ++j
, ++length
)
477 cmdline
[length
] = argv
[i
][j
];
478 if (lastWasOption
== true && argv
[i
][j
] == '=')
480 // That is possibly an option: Quote it if it includes spaces,
481 // the benefit is that this will eliminate also most false positives
482 const char* c
= strchr(&argv
[i
][j
+1], ' ');
483 if (c
== NULL
) continue;
484 cmdline
[++length
] = '"';
488 if (closeQuote
== true)
489 cmdline
[length
++] = '"';
490 // Problem: detects also --hello
491 if (cmdline
[length
-1] == 'o')
492 lastWasOption
= true;
493 cmdline
[length
] = ' ';
495 cmdline
[--length
] = '\0';
496 _config
->Set("CommandLine::AsString", cmdline
);
499 CommandLine::Args
CommandLine::MakeArgs(char ShortOpt
, char const *LongOpt
, char const *ConfName
, unsigned long Flags
)/*{{{*/
501 /* In theory, this should be a constructor for CommandLine::Args instead,
502 but this breaks compatibility as gcc thinks this is a c++11 initializer_list */
503 CommandLine::Args arg
;
504 arg
.ShortOpt
= ShortOpt
;
505 arg
.LongOpt
= LongOpt
;
506 arg
.ConfName
= ConfName
;