Some small changes in configuration_manager output - added (c/f) info.
[h316.git] / pc-tools / ldc2 / src / configuration_manager.cpp
1 /******************************************************************************
2 *
3 * LDC2 source code
4 *
5 * $Date: 2007/06/15 12:46:04 $
6 * $Author: hachti $
7 *
8 * $Log: configuration_manager.cpp,v $
9 * Revision 2.2 2007/06/15 12:46:04 hachti
10 * Some small changes in configuration_manager output - added (c/f) info.
11 * Changed order in tool.hh:add_unique....
12 *
13 * Revision 2.1 2007-03-26 04:05:37 hachti
14 * *** empty log message ***
15 *
16 * Revision 2.0 2007-03-26 01:00:38 hachti
17 * *** empty log message ***
18 *
19 *
20 ******************************************************************************/
21
22 #include "configuration_manager.hh"
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <fcntl.h>
26
27 #define MAX_LINE_LENGTH 80
28
29 /*!
30 *\brief Constructor.
31 *
32 * This constructor makes a new configuration_manager ready to use.
33 *\arg name Name of the application as mentioned in the
34 * "Use: <appname> ..." help message.
35 */
36 configuration_manager::configuration_manager(string name){
37 app_name=name;
38 }
39
40
41 /*!
42 *\brief Add a new configuration value to be searched for.
43 *\param shortname A character for the short form.
44 * For example the o in -o
45 *\param longname The double dash longname. For example
46 * output_file in --output_file=
47 *\param description A detailed description of the value.
48 *\param status Pointer to an integer. Will be set to 1 if arg found.
49 *\param target Pointer to string to put the value in.
50 *\param placeholder A placeholder for the documentation.
51 * For example <filename> in -f<filename>
52 *\param allow_cmdline Specifies if this value may be specified on the
53 * command line.
54 *\param allow_conffile Specifies if this value may be specified in a
55 * configuration file.
56 *
57 */
58 void configuration_manager::add_option_value (const string & shortname,
59 const string & longname,
60 const string & description,
61 int * status,
62 string * target,
63 const string & placeholder,
64 const bool & allow_cmdline,
65 const bool & allow_conffile
66 ){
67
68 if(status!=NULL) if (target!=NULL)
69 option_values.insert(option_values.end(),
70 opt_value_t(shortname,
71 longname,
72 description,
73 status,
74 target,
75 placeholder,
76 allow_cmdline,
77 allow_conffile
78 )
79 );
80 }
81
82
83
84 /*!
85 *\brief Add a new configuration switch to be searched for.
86 *\param shortname A character for the short form.
87 * For example the h in -h
88 *\param longname The double dash longname. For example
89 * help in --help
90 *\param description A detailed description of the value.
91 *\param status Pointer to an integer. Will be set to 1 if arg found.
92 *\param allow_cmdline Specifies whether the switch is acceptable on the
93 * command line.
94 *\param allow_conffile Specifies whether the switch is acceptable
95 * in a configuration file.
96 */
97 void configuration_manager::add_option_switch (const string & shortname,
98 const string & longname,
99 const string & description,
100 int * status,
101 const bool & allow_cmdline,
102 const bool & allow_conffile
103 ){
104
105 if(status!=NULL)
106 option_switches.insert(option_switches.end(),
107 opt_switch_t(shortname,
108 longname,
109 description,
110 status,
111 allow_cmdline,
112 allow_conffile)
113 );
114 }
115
116 /*!
117 *\brief Add an accepted argument to the argument reader.
118 *\param placeholder Something like "<input-file>".
119 *\param description Text describing the argument.
120 *\param status A pointer to a status variable. Will be set to 0 by default.
121 * If the argument is filled in, it will be set to 1.
122 *\param target A pointer to a c++ string where the argument goes to.
123 *
124 *\note Arguments are filled in the order of adding them to the
125 * argument reader.
126 * There would be no other way to determine the order.
127 */
128 void configuration_manager::add_argument(const string & description,
129 int * status,
130 string * target,
131 const string & placeholder){
132
133
134
135 if (target!=NULL) if(status!=NULL)
136 cmd_args.insert(cmd_args.end(),cmd_arg_t(placeholder,
137 description,
138 status,
139 target));
140 }
141
142
143 /*!
144 *\brief Read in the args passed to main().
145 *\returns empty vector on success or the error messages to be output.
146 */
147 vector<string> configuration_manager::read_args(int argc, char ** args){
148
149 vector<string> messages;
150 vector<string> argv;
151
152 // First we make a C++ string vector out of the **argv.
153 for (char ** akt=args+1; *akt ;akt++) argv.insert(argv.end(),string(*akt));
154
155 // Counter for the free command line parameters.
156 unsigned int free_parms_count=0;
157
158 // Loop through the command line arguments
159 for (unsigned int arg_no=0;arg_no<argv.size();arg_no++){
160 bool found=false;
161 string argstring;
162
163 // Look for long parameters.
164 // if ((argv[arg_no].substr(0,2)=="--")&&(free_parms_count==0)){
165 if ((argv[arg_no].substr(0,2)=="--")){ // Changed for comfort.
166 argstring=argv[arg_no].substr(2);
167
168 // Look for long switches.
169 for (unsigned int switch_no=0;switch_no<option_switches.size();switch_no++){
170 string switch_name=option_switches[switch_no].longname;
171 if (argstring.compare(0,switch_name.length(),switch_name,0,switch_name.length())==0){
172 string value=analyse_string(argstring);
173 bool result=analyse_bool(value);
174 if(option_switches[switch_no].allow_cmdline==true){
175 *(option_switches[switch_no].status)=result;
176 }else{
177 messages.insert(messages.end(),"Switch \"--"
178 +option_switches[switch_no].longname
179 +"\" is not allowed on the command line!");
180 }
181 found=true;
182 break;
183 }
184 }
185
186 // Look for long values.
187 for (unsigned int value_no=0;value_no<option_values.size();value_no++){
188 string value_name=option_values[value_no].longname;
189 if (argstring.compare(0,value_name.length(),value_name,0,value_name.length())==0){
190 string value=analyse_string(argstring);
191 if(option_values[value_no].allow_cmdline==true){
192 *(option_values[value_no].status)=1;
193 *(option_values[value_no].target)=value;
194 }else{
195 messages.insert(messages.end(),"Option value \"--"
196 +option_values[value_no].longname
197 +"\" is not allowed on the command line!");
198 }
199 found=true;
200 break;
201 }
202 }
203 if (! found) {
204 messages.insert(messages.begin(),"Unknown option: --"+argstring+"!");
205 found=true; // Oh!
206 }
207 }
208
209 // Look for short parameters
210 // if ((argv[arg_no].substr(0,1)=="-")&&(free_parms_count==0)&&(!found)){ // Short parameters
211 if ((argv[arg_no].substr(0,1)=="-")&&(!found)){ // Changed for comfort.
212 found=true;
213 argstring=argv[arg_no].substr(1); // Reassign, with one more character now
214 for (unsigned int pos=0;pos < argstring.length();pos++){
215 bool short_found=false;
216
217 // First, find short switches
218 for (unsigned int switch_no=0;switch_no<option_switches.size();switch_no++){
219 string short_name=option_switches[switch_no].shortname;
220 if (short_name.compare(0,short_name.length(),argstring,pos,short_name.length())==0){
221 if(option_switches[switch_no].allow_cmdline==true){
222 *(option_switches[switch_no].status)=1;
223 }else{
224 messages.insert(messages.end(),"Switch \"-"
225 +option_switches[switch_no].shortname
226 +"\" is not allowed on the command line!");
227 }
228 short_found=true;
229 pos+=short_name.length()-1;
230 break;
231 }
232 }
233
234 // Now, find short values
235 if (!short_found){
236 for (unsigned int value_no=0;value_no<option_values.size();value_no++){
237 string short_name=option_values[value_no].shortname;
238
239 if (short_name.compare(0,short_name.length(),argstring,pos,short_name.length())==0){
240 if (pos==argstring.length()-1){ // Last character, where's the value?
241 if (arg_no<argv.size()-1){ // Take next complete argument as value!
242 *(option_values[value_no].status)=1;
243 *(option_values[value_no].target)=argv[arg_no+1];
244 arg_no+=1; // Consume the next argument
245 short_found=true;
246 } else { // No argument left!
247 messages.insert(messages.begin(),"Missing value for -"+short_name+"!");
248 short_found=true;
249 }
250 } else {
251 if(option_values[value_no].allow_cmdline==true){
252 *(option_values[value_no].status)=1;
253 *(option_values[value_no].target)=argstring.substr(pos+1);
254 }else{
255 messages.insert(messages.end(),"Option value \"-"
256 +option_values[value_no].shortname
257 +"\" is not allowed on the command line!");
258 }
259 pos=argstring.length(); // Do not analyse other characters.
260 short_found=true;
261 break;
262 }
263 }
264 }
265 }
266 if (!short_found) messages.insert(messages.begin(),"Unknown Option: -"+argstring.substr(pos,1)+"!");
267 }
268 }
269
270 if (!found) { // Must be a free form parameter...
271 if (free_parms_count<cmd_args.size()){ // Space available
272 *(cmd_args[free_parms_count].target)=argv[arg_no];
273 *(cmd_args[free_parms_count].status)=1;
274 free_parms_count++;
275 } else { // No more space for free form parameters!
276 messages.insert(messages.begin(),"Too many arguments!");
277 }
278 }
279 }
280 if (!messages.empty()){
281 messages.insert(messages.begin(),"Error!");
282 messages.insert(messages.begin(),"");
283 }
284 return messages;
285 }
286
287
288 /*!
289 *\brief Extract a value from a configuration file line or
290 * a command line argument
291 */
292 string configuration_manager::analyse_string(const string & line){
293 string result="";
294 unsigned int pos=line.find("=");
295 if (pos!=string::npos) result=line.substr(pos+1);
296 return result;
297 }
298
299 /*!
300 *\brief Extract a boolean value out of a string, defaulting to true
301 *
302 * Used for commandline switches
303 */
304 bool configuration_manager::analyse_bool(const string & data){
305 if ((data.find("false",0)!=string::npos)||
306 (data.find("0",0)!=string::npos)||
307 (data.find("no",0)!=string::npos)) return false;
308 else return true;
309 }
310
311
312 /*!
313 *\brief Extract a boolean value out of a string, defaulting to false
314 *
315 * Used for configuration file switches
316 */
317 bool configuration_manager::analyse_bool_false(const string & data){
318 if ((data.find("true",0)!=string::npos)||
319 (data.find("1",0)!=string::npos)||
320 (data.find("yes",0)!=string::npos)) return true;
321 else return false;
322 }
323
324
325 /*!
326 *brief Read in and parse a configuration file.
327 *\arg file String containing the filename
328 *\return Empty Vector or Vector full of strings containing
329 * the error message(s).
330 */
331 vector<string> configuration_manager::read_file(string filename){
332 vector<string>result;
333 FILE * fp=fopen(filename.c_str(),"r");
334 if (! fp) {
335 result.insert(result.end(),"Could not open file: "+filename);
336 } else {
337 char buffer[1000];
338 while(fgets(buffer,1000,fp)){
339 if (buffer[strlen(buffer)-1]=='\n')// Cut trailing newline
340 buffer[strlen(buffer)-1]=0;
341 if (strlen (buffer)){
342 char*l2=buffer+strspn(buffer," \t");
343 if (l2[0]!='#'){
344 string argstring=l2;
345 unsigned int pos=argstring.find("=");
346 if (pos==0){
347 result.insert(result.end(),"In File "+filename+": Line beginning with '='!");
348 }
349
350 bool found=false;
351
352 // Look for long switches.
353 for (unsigned int switch_no=0;switch_no<option_switches.size();switch_no++){
354 string switch_name=option_switches[switch_no].longname;
355 if (argstring.compare(0,switch_name.length(),switch_name,0,switch_name.length())==0){
356 string value=analyse_string(argstring);
357 //result.insert(result.end(),"argstring:\""+argstring+"\"");
358 bool res=analyse_bool_false(value);
359 if(option_switches[switch_no].allow_conffile==true){
360 *(option_switches[switch_no].status)=res;
361 }else{
362 result.insert(result.end(),"In File "+filename+": Switch \""
363 +option_switches[switch_no].longname
364 +"\" is not allowed in config files!");
365 }
366 found=true;
367 break;
368 }
369 }
370
371 // Look for long values.
372 for (unsigned int value_no=0;value_no<option_values.size();value_no++){
373 string value_name=option_values[value_no].longname;
374 if (argstring.compare(0,value_name.length(),value_name,0,value_name.length())==0){
375 string value=analyse_string(argstring);
376 *(option_values[value_no].status)=1;
377
378 if(option_values[value_no].allow_conffile==true){
379 *(option_values[value_no].status)=1;
380 *(option_values[value_no].target)=value;
381 }else{
382 result.insert(result.end(),"In File "+filename+": Option value \""
383 +option_values[value_no].longname
384 +"\" is not allowed in config files!");
385 }
386 found=true;
387 break;
388 }
389 }
390 if (! found) {
391 result.insert(result.end(),"In File "+filename+": Unknown option: "+argstring+"!");
392 }
393
394 } // if not #
395 } // if (strlen())
396 } // while(...)
397 fclose (fp);
398 } // fp==0 else branch
399
400 if (!result.empty()){
401 result.insert(result.begin(),"Error!");
402 result.insert(result.begin(),"");
403 }
404 return result;
405 }
406
407
408 /*!
409 *\brief Generate help.
410 *\arg target Reference to a vector<string> to which lots of helpful
411 * strings are appended.
412 */
413 void configuration_manager::get_help(vector<string> & target){
414 target.insert(target.end(),"");
415 string line="Usage: "+app_name;
416
417 for (vector<opt_switch_t>::iterator parm_p=option_switches.begin();parm_p<option_switches.end();parm_p++){
418 string addstr=" [-"+parm_p->shortname;
419 addstr+="]";
420 if (line.length()+addstr.length()>MAX_LINE_LENGTH){
421 target.insert(target.end(),line);
422 line=string(7+app_name.length(),' ');
423 }
424 line+=addstr;
425 }
426
427 for (vector<opt_value_t>::iterator parm_p=option_values.begin();parm_p<option_values.end();parm_p++){
428 string addstr=" [-"+parm_p->shortname;
429 addstr+=parm_p->placeholder;
430 addstr+="]";
431 if (line.length()+addstr.length()>MAX_LINE_LENGTH){
432 target.insert(target.end(),line);
433 line=string(7+app_name.length(),' ');
434 }
435 line+=addstr;
436 }
437
438 for (vector<cmd_arg_t>::iterator parm_p=cmd_args.begin();parm_p<cmd_args.end();parm_p++){
439 if (line.length()+parm_p->placeholder.length()>MAX_LINE_LENGTH){
440 target.insert(target.end(),line);
441 line=string(7+app_name.length(),' ');
442 }
443 line+=" ["+parm_p->placeholder+"]";
444 }
445 target.insert(target.end(),line);
446
447 /* Here comes the documentation output. */
448
449 vector<string> left,right;
450 unsigned int max_width;
451
452 /* Output switches? */
453 if (option_switches.size()){
454 target.insert(target.end(),"");
455 target.insert(target.end(),"Switches:");
456
457 left=vector<string>();
458 right=vector<string>();
459 max_width=0;
460
461 /* Now lets insert switches into left and right */
462 for (unsigned int sw=0; sw<option_switches.size();sw++){
463 opt_switch_t akt_sw=option_switches[sw];
464 string rline="(" ;
465 if (akt_sw.allow_cmdline) rline+="c/";
466 else rline+="-/";
467 if (akt_sw.allow_conffile) rline+="f)";
468 else rline+="-)";
469 rline+=" "+akt_sw.description;
470 string lline="-"+akt_sw.shortname+", --"+akt_sw.longname;
471 left.insert(left.end(),lline);
472 right.insert(right.end(),rline);
473 }
474
475 // Determine maximal width for left column
476 max_width=0;
477 for (unsigned int c=0; c<left.size();c++)
478 if(left[c].length()>max_width) max_width=left[c].length();
479
480 /* output all the mess */
481 for (unsigned int c=0; c<left.size();c++){
482 string nl(max_width,' ');
483 nl.replace(0,left[c].length(),left[c]);
484 nl+=" "+right[c];
485 while (nl.length()>MAX_LINE_LENGTH){ // Too long???
486 int limit=nl.find_last_of(' ',MAX_LINE_LENGTH);
487 target.insert(target.end(),nl.substr(0,limit));
488 nl=string(max_width+2+6,' ')+nl.substr(limit+1);
489 }
490 target.insert(target.end(),nl);
491 }
492 }
493
494 // Output values
495 if (option_values.size()){
496 target.insert(target.end(),"");
497 target.insert(target.end(),"Option values:");
498
499 left=vector<string>();
500 right=vector<string>();
501 max_width=0;
502
503 for (unsigned int val=0; val<option_values.size();val++){
504 opt_value_t akt_val=option_values[val];
505 string rline="(" ;
506 if (akt_val.allow_cmdline) rline+="c/";
507 else rline+="-/";
508 if (akt_val.allow_conffile) rline+="f)";
509 else rline+="-)";
510 rline+=" "+akt_val.description;
511
512 string lline="-"+akt_val.shortname+akt_val.placeholder+", --"+
513 akt_val.longname+"="+akt_val.placeholder;
514 left.insert(left.end(),lline);
515 right.insert(right.end(),rline);
516 }
517
518 // Determine maximal width for left column
519 for (unsigned int c=0; c<left.size();c++)
520 if(left[c].length()>max_width) max_width=left[c].length();
521 /* output all the mess */
522 for (unsigned int c=0; c<left.size();c++){
523 string nl(max_width,' '); // Empty left side.
524 nl.replace(0,left[c].length(),left[c]); // Print in left side.
525 nl+=" "+right[c]; // Add right side.
526
527 while (nl.length()>MAX_LINE_LENGTH){ // Too long???
528 int limit=nl.find_last_of(' ',MAX_LINE_LENGTH);
529 target.insert(target.end(),nl.substr(0,limit));
530 nl=string(max_width+2+6,' ')+nl.substr(limit+1);
531 }
532 target.insert(target.end(),nl);
533 }
534 }
535
536 target.insert(target.end(),"");
537 target.insert(target.end(),"Legend (c/f):");
538 target.insert(target.end()," c: Allowed on command line.");
539 target.insert(target.end()," f: Allowed in configuration file.");
540
541 /* Output the Arguments */
542 if (cmd_args.size()){
543 target.insert(target.end(),"");
544 target.insert(target.end(),"Arguments:");
545
546 left=vector<string>();
547 right=vector<string>();
548 max_width=0;
549
550 for (unsigned int arg=0; arg<cmd_args.size();arg++){
551 cmd_arg_t akt_arg=cmd_args[arg];
552 left.insert(left.end(),akt_arg.placeholder);
553 right.insert(right.end(),akt_arg.description);
554 }
555
556 // Determine maximal width for left column
557 max_width=0;
558 for (unsigned int c=0; c<left.size();c++)
559 if(left[c].length()>max_width) max_width=left[c].length();
560
561 for (unsigned int c=0; c<left.size();c++){
562 string nl(max_width,' ');
563 nl.replace(0,left[c].length(),left[c]);
564 nl+=" "+right[c];
565 while (nl.length()>MAX_LINE_LENGTH){ // Too long???
566 int limit=nl.find_last_of(' ',MAX_LINE_LENGTH+1);
567 target.insert(target.end(),nl.substr(0,limit));
568 nl=string(max_width+2,' ')+nl.substr(limit+1);
569 }
570 target.insert(target.end(),nl);
571 }
572 target.insert(target.end(),"");
573 }
574 }
575
576 /*!
577 *\brief Generate help.
578 *\return A vector containing many helpful strings for the user.
579 */
580 vector<string> configuration_manager::get_help(){
581 vector<string> result;
582 get_help(result);
583 return result;
584 }
585
586
587 /**************************************************/
588
589
590 configuration_manager::opt_value_t::opt_value_t(const string & shortname,
591 const string & longname,
592 const string & description,
593 int * status,
594 string * target,
595 const string & placeholder,
596 const bool & allow_conffile,
597 const bool & allow_cmdline
598 ){
599 this->shortname=shortname;
600 this->longname=longname;
601 this->description=description;
602 this->status=status;
603 this->target=target;
604 this->placeholder=placeholder;
605 this->allow_conffile=allow_conffile;
606 this->allow_cmdline=allow_cmdline;
607 if (status) *status=0;
608 }
609
610
611 configuration_manager::opt_switch_t::opt_switch_t(const string & shortname,
612 const string & longname,
613 const string & description,
614 int * status,
615 const bool & allow_conffile,
616 const bool & allow_cmdline){
617 this->shortname=shortname;
618 this->longname=longname;
619 this->description=description;
620 this->status=status;
621 this->target=target;
622 this->placeholder=placeholder;
623 this->allow_conffile=allow_conffile;
624 this->allow_cmdline=allow_cmdline;
625 }
626
627
628 configuration_manager::cmd_arg_t::cmd_arg_t(const string & placeholder,
629 const string & description,
630 int * status,
631 string * target){
632 this->placeholder=placeholder;
633 this->description=description;
634 this->status=status;
635 this->target=target;
636 }