home *** CD-ROM | disk | FTP | other *** search
/ Nebula / nebula.bin / SourceCode / Classes / SampleClasses / ReadConfig.m < prev    next >
Text File  |  1992-08-04  |  29KB  |  968 lines

  1. // -------------------------------------------------------------------------------------
  2. // ReadConfig.m
  3. // Martin D. Flynn, NeXT Computer, Inc.
  4. // Read config file and send methods to target
  5. // Note: See the header file for usage information and examples.
  6. // -------------------------------------------------------------------------------------
  7. // Permission is granted to freely redistribute this source code, and to use fragments
  8. // of this code in your own applications if you find them to be useful.  This class,
  9. // along with the source code, come with no warranty of any kind, and the user assumes
  10. // all responsibility for its use.
  11. // -------------------------------------------------------------------------------------
  12.  
  13. #import <stdlib.h>
  14. #import <stdio.h>
  15. #import <string.h>
  16. #import <libc.h>
  17. #import <sys/time.h>
  18. #import <objc/objc-runtime.h>
  19. #import <objc/Storage.h>
  20. #import <defaults/defaults.h>
  21. #import <appkit/Panel.h>
  22. #import <appkit/Application.h>
  23. #import "ReadConfig.h"
  24.  
  25. // -------------------------------------------------------------------------------------
  26. extern char *getwd(char *path);
  27.  
  28. // -------------------------------------------------------------------------------------
  29. // misc defines
  30. #define maxLEN              512         // max allowable record length
  31. #define prefixMethodNAME    "*cfg"      // internal method prefix "cfg_<methName>"
  32. #define isCOMMENT(T)        ((*T == ' ') || (*T == '\t') || (*T == '\n') || (*T == '/'))
  33. #define parseFORMAT         "%s%[^\n]"
  34.  
  35. // -------------------------------------------------------------------------------------
  36. // internal config private method definition
  37. @interface ReadConfig(_Private)
  38. - profileElapsedTime:(float)elapsedTime;
  39. - configErrorTrap:(int)errCode inFile:(char*)fileName atLine:(int)lineNbr;
  40. - configWillPerform:(SEL)selector with:anArg;
  41. - setTarget:targetObject;
  42. - (int)readConfigFile:(char*)file;
  43. @end
  44.  
  45. // -------------------------------------------------------------------------------------
  46. // internal config private method definition
  47. @interface ReadConfig(_InternalMethod)
  48. - (int)cfg_globalMethodTrace:(BOOL)yesNo;
  49. - (int)cfg_globalPrintError:(BOOL)yesNo;
  50. - (int)cfg_globalCheckReturn:(BOOL)yesNo;
  51. - (int)cfg_methodTrace:(BOOL)yesNo;
  52. - (int)cfg_printError:(BOOL)yesNo;
  53. - (int)cfg_checkReturn:(BOOL)yesNo;
  54. - (int)cfg_terminateOnError:(BOOL)yesNo;
  55. - (int)cfg_print:(char*)text;
  56. - (int)cfg_goto:(char*)label;
  57. - (int)cfg_ifTrue:(char*)label;
  58. - (int)cfg_ifFalse:(char*)label;
  59. - (int)cfg_terminate;
  60. - (int)cfg_alertPanel:(char*)message;
  61. - (int)cfg_abortPanel:(char*)message;
  62. - (int)cfg_optionPanel:(char*)message;
  63. - (int)cfg_ifOptionA:(char*)label;
  64. - (int)cfg_ifOptionB:(char*)label;
  65. - (int)cfg_endModalPanel;
  66. - (int)cfg_infoPanel:(char*)message;
  67. - (int)cfg_endInfoPanel;
  68. - (int)cfg_stopPanel:(char*)message;
  69. - (int)cfg_endStopPanel;
  70. - (int)cfg_ifStop:(char*)label;
  71. @end
  72.  
  73. // -------------------------------------------------------------------------------------
  74. @implementation ReadConfig
  75.  
  76. // -------------------------------------------------------------------------------------
  77. // global defaults
  78. static BOOL             dftExitOnError      = YES;
  79. static BOOL             dftPrintError       = YES;
  80. static BOOL             dftMethodTrace      = NO;
  81. static BOOL             dftCheckAll         = YES;
  82. static BOOL             dftBackupOnWrite    = NO;
  83.  
  84. // -------------------------------------------------------------------------------------
  85. // line/level data on last call to ReadConfig
  86. static int              globalLevel = 0;
  87. static int              globalLine  = 0;
  88.  
  89. // -------------------------------------------------------------------------------------
  90. // global error trap
  91. static id               errorTrap = (id)nil;    // error trap target
  92.  
  93. // -------------------------------------------------------------------------------------
  94. // time lapse profile variables
  95. static double           profileStart = -1.0;    // profile start time
  96. static int              profileCount = 0;       // profile 'ping' count
  97. static float            profileLapse = 0.0;     // latched profile lapse time
  98. static BOOL             profileActive = NO;
  99. static id               profileTarget = (id)nil;
  100. #define profilePING     @selector(profileElapsedTime:)
  101.  
  102. // -------------------------------------------------------------------------------------
  103. // constant data
  104. static char             *errorDesc[] = {         // error descriptions
  105.                           "Successful",
  106.                           "Unable to open config file",
  107.                           "Invalid number of configuration parameters",
  108.                           "Selector not found for method",
  109.                           "Method returned TRUE error condition",
  110.                           "Goto label not found",
  111.                           "Invalid error code specified",
  112.                           "Target does not respond to method",
  113.                           0
  114.                         };
  115.  
  116. // -------------------------------------------------------------------------------------
  117. // class initialization
  118. + initialize
  119. {
  120.   errorTrap = self;
  121.   return self;
  122. }
  123.  
  124. // -------------------------------------------------------------------------------------
  125. // Return the full path to the current application
  126. // Note: the application name is removed from the path
  127. + (char*)appPath
  128. {
  129.   int           i;
  130.   static char   *appPathName = (char*)nil;
  131.   if (!appPathName) {
  132.     for (i = strlen(NXArgv[0]) - 1; (i >= 0) && (NXArgv[0][i] != '/'); i--);
  133.     appPathName = (char*)malloc(i + 2);
  134.     strncpy(appPathName, NXArgv[0], i + 1);
  135.     appPathName[i + 1] = 0;
  136.     if (NXArgv[0][0] != '/') {
  137.       char path[1024], *aPtr;
  138.       aPtr = (char*)malloc(strlen(getwd(path)) + strlen(appPathName) + 2);
  139.       sprintf(aPtr, "%s/%s", path, appPathName);
  140.       free(appPathName);
  141.       appPathName = aPtr;
  142.     }
  143.   }
  144.   return appPathName;
  145. }
  146.  
  147. // -------------------------------------------------------------------------------------
  148. // Build a fully qualified path to the specified file name
  149.  
  150. /* build full path to specified file name and extension */
  151. + (char*)configPath:(const char*)fileName
  152. {
  153.   char  *fullPath = (char*)malloc(strlen([self appPath]) + strlen(fileName) + 1);
  154.   sprintf(fullPath, "%s%s", [self appPath], fileName);
  155.   return fullPath;
  156. }
  157.  
  158. // -------------------------------------------------------------------------------------
  159. // Return status info
  160. // Note:
  161. //   - These functions may be used to get information about configuration file reads
  162. //     when an error condition has occurred.
  163.  
  164. /* return current line number (usually used in error conditions) */
  165. + (int)lineNumber
  166. {
  167.   return globalLine;
  168. }
  169.  
  170. /* return current level number (usually used in error conditions) */
  171. + (int)levelNumber
  172. {
  173.   return globalLevel;
  174. }
  175.  
  176. /* return error description text for specified error code */
  177. + (char*)errorDescription:(int)errorCode
  178. {
  179.   if ((errorCode < 0) || (errorCode > cfgLAST_ERROR)) errorCode = cfgINVALID_CODE;
  180.   return errorDesc[errorCode];
  181. }
  182.  
  183. // -------------------------------------------------------------------------------------
  184. // Set global error trap target object
  185. // Note:
  186. //   - The 'setErrorTrap:' may be used to set the target object that will be notified
  187. //     when error conditions occur during a readConfigFile: call.
  188. //   - The method that will be called in the target object will be the following:
  189. //          configErrorTrap:(int) inFile:(char*) atLine:(int)
  190. //     See definition below for more information.
  191.  
  192. /* set trap to specified target */
  193. + setErrorTrap:trapId
  194. {
  195.   if (![trapId respondsTo:@selector(configErrorTrap:inFile:atLine:)])
  196.     return (id)nil;
  197.   errorTrap = trapId;
  198.   return self;
  199. }
  200.  
  201. /* example error trap method */
  202. //  errCode  - the error condition that occurred
  203. //  fileName - the current configuration file name that had the error
  204. //  lineNbr  - the line number that caused the error
  205. + configErrorTrap:(int)errCode inFile:(char*)fileName atLine:(int)lineNbr
  206. {
  207.   
  208.   char  *temp = fileName? rindex(fileName, '/') : (char*)nil, *fName = temp? temp : fileName;
  209.   char  errMsg[256], *title = "ReadConfig Error", *fmt = "File %s (line %d)\n%s";
  210.   
  211.   /* build error message */
  212.   sprintf(errMsg, fmt, fName, lineNbr, [ReadConfig errorDescription:errCode]);
  213.  
  214.   /* defautl alert panel */
  215.   NXRunAlertPanel(title, errMsg, "Quit", (char*)nil, (char*)nil);
  216.   exit(-1);
  217.  
  218.   return (id)nil;
  219. }
  220.  
  221. /* default error trap method */
  222. - configErrorTrap:(int)errCode inFile:(char*)fileName atLine:(int)lineNbr
  223. {
  224.   return [ReadConfig configErrorTrap:errCode inFile:fileName atLine:lineNbr];
  225. }
  226.  
  227. // -------------------------------------------------------------------------------------
  228. // Set global defaults
  229. // Notes:
  230. //   - These methods may be used to set global defaults that will be in effect for all
  231. //     configuration files read.
  232.  
  233. /* set global default Terminate-On-Error flag */
  234. + setTerminateOnError:(BOOL)yesNo
  235. {
  236.   dftExitOnError = yesNo;
  237.   return NO;
  238. }
  239.  
  240. /* set global default error print mode */
  241. + setPrintError:(BOOL)yesNo
  242. {
  243.   dftPrintError = yesNo;
  244.   return NO;
  245. }
  246.  
  247. /* set global default return status checking mode */
  248. + setCheckReturn:(BOOL)yesNo
  249. {
  250.   dftCheckAll = yesNo;
  251.   return NO;
  252. }
  253.  
  254. /* set global default method trace mode */
  255. + (int)setMethodTrace:(BOOL)yesNo
  256. {
  257.   dftMethodTrace = yesNo;
  258.   return NO;
  259. }
  260.  
  261. /* set global default backup-on-write flag */
  262. + setBackupOnWrite:(BOOL)yesNo
  263. {
  264.   dftBackupOnWrite = yesNo;
  265.   return NO;
  266. }
  267.  
  268. // -------------------------------------------------------------------------------------
  269. // read specified config file
  270. // -------------------------------------------------------------------------------------
  271. #define         breakERROR(err)         { error = err; break; }
  272.  
  273. /* open/read config file */
  274. + (int)readConfigFile:(char*)file target:cfgTarget
  275. {
  276.   int           rtnError;
  277.   id            mySelf = [[self alloc] init];
  278.   [mySelf setTarget:cfgTarget];
  279.   rtnError = [mySelf readConfigFile:file];
  280.   [mySelf free];
  281.   return rtnError;
  282. }
  283.  
  284. /* open/read config file relative to application directory */
  285. + (int)readAppConfig:(char*)file target:cfgTarget
  286. {
  287.   char      *fileName = [self configPath:file];
  288.   int       error = [self readConfigFile:fileName target:cfgTarget];
  289.   free(fileName);
  290.   return error;
  291. }
  292.  
  293. // -------------------------------------------------------------------------------------
  294. // Update config file methods with new parameters
  295. // Notes:
  296. //   - These methods allow for updating existing config-files method with new parameters.
  297. //   - If duplicate method name occur in the config file, they may be made unique for
  298. //     purposes of updating by placing a '$' followed by a character after the method name.
  299. //          ie. setParmValue:$0     parms
  300. //              setParmValue:$1     parms
  301. //     The '$' and character will be stripped prior to sending the method to the target.
  302. //   - Search for the desired method by including the '$' and character if necessary.
  303. // -------------------------------------------------------------------------------------
  304.  
  305. /* return Storage object containing entire specified config-file */
  306. + newConfigStorage:(char*)fileName
  307. {
  308.   FILE          *fNum;
  309.   char          txt[maxLEN + 1];
  310.   char          *cPtr;
  311.   id            storage;
  312.   
  313.   /* open file */
  314.   fNum = fopen(fileName, "r");
  315.   if (!fNum) return (id)nil;
  316.   
  317.   /* create storage */
  318.   storage = [[Storage alloc] initCount:10 elementSize:sizeof(char*) description:(char*)nil];
  319.   [storage empty];
  320.   
  321.   /* fill storage */
  322.   for (*txt = 0; fgets(txt, maxLEN, fNum);) {
  323.     cPtr = NXCopyStringBuffer(txt);
  324.     [storage addElement:&cPtr];
  325.   }
  326.     
  327.   /* close file */
  328.   fclose(fNum);
  329.   return storage;
  330.   
  331. }
  332.  
  333. /* read config file (relative to app) into storage class */
  334. + newAppConfigStorage:(char*)file
  335. {
  336.   char          *fileName = [self configPath:file];
  337.   id            storage = [self newConfigStorage:fileName];
  338.   free(fileName);
  339.   return storage;
  340. }
  341.  
  342. /* return index for matching string in Config-Storage object (return -1 if not found) */
  343. + (int)findIndexForMethodName:(char*)methName inCfgStorage:storage
  344. {
  345.   char          *cPtr;
  346.   int           i, methLen = strlen(methName);
  347.   for (i = 0; i < [storage count]; i++) {
  348.     cPtr = *((char**)[storage elementAt:i]);
  349.     if (!strncmp(cPtr, methName, methLen)) return i;
  350.   }
  351.   return -1;
  352. }
  353.   
  354. /* replace specified record with corresponding record in Config-Storage object */
  355. + replaceRecord:(char*)rcd inCfgStorage:storage
  356. {
  357.   int           ndx, len;
  358.   char          *cPtr, *nPtr, *fmt, name[maxLEN];
  359.   
  360.   /* find occurrance of record */
  361.   sscanf(rcd, "%s", name);
  362.   ndx = [self findIndexForMethodName:name inCfgStorage:storage];
  363.   if (ndx < 0) return (id)nil;
  364.  
  365.   /* make a static copy of the record */
  366.   len = strlen(rcd);
  367.   if (rcd[len - 1] != '\n') { len++; fmt = "%s\n"; } else fmt = "%s";
  368.   nPtr = (char*)malloc(len + 1);
  369.   sprintf(nPtr, fmt, rcd);
  370.   
  371.   /* index found, replace record */
  372.   cPtr = *((char**)[storage elementAt:ndx]);
  373.   [storage replaceElementAt:ndx with:&nPtr];
  374.   free(cPtr);
  375.   
  376.   return self;
  377. }
  378.  
  379. /* write Config-Storage object to specified file (return nil if error) */
  380. + writeConfigStorage:storage toFile:(char*)fileName
  381. {
  382.   int           i;
  383.   char          *cPtr;
  384.   FILE          *fNum;
  385.  
  386.   /* check for backup (bkuFile will first be removed if it exists) */
  387.   if (dftBackupOnWrite) {
  388.     char bkuFile[MAXPATHLEN + 1];
  389.     sprintf(bkuFile, "%s~", fileName);
  390.     if (rename(fileName, bkuFile))
  391.       fprintf(stderr, "ReadConfig: Unable to rename to %s\n", bkuFile);
  392.   }
  393.  
  394.   /* open output file */
  395.   fNum = fopen(fileName, "w");
  396.   if (!fNum) return (id)nil;
  397.   
  398.   /* write data to file */
  399.   for (i = 0; i < [storage count]; i++) {
  400.     cPtr = *((char**)[storage elementAt:i]);
  401.     fprintf(fNum, "%s", cPtr);
  402.   }
  403.     
  404.   /* close file and return */
  405.   fclose(fNum);
  406.   return self;
  407.   
  408. }
  409.  
  410. /* write config file to file (relative to app) */
  411. + writeAppConfigStorage:storage toFile:(char*)file
  412. {
  413.   char          *fileName = [self configPath:file];
  414.   id            obj = [self writeConfigStorage:storage toFile:fileName];
  415.   free(fileName);
  416.   return obj;
  417. }
  418.  
  419. /* free Config-Storage object */
  420. + freeConfigStorage:storage
  421. {
  422.   int           i;
  423.   for (i = 0; i < [storage count]; i++) free(*((char**)[storage elementAt:i]));
  424.   [storage free];
  425.   return self;
  426. }
  427.  
  428. /* print contents of Config-Storage object */
  429. + printConfigStorage:storage
  430. {
  431.   int           i;
  432.   for (i = 0; i < [storage count]; i++)
  433.     printf("%3d: %s", i, *((char**)[storage elementAt:i]));
  434.   return self;
  435. }
  436.  
  437. // -------------------------------------------------------------------------------------
  438. // parse and execute single config record
  439. // -------------------------------------------------------------------------------------
  440.  
  441. + (int)sendConfigMethod:(char*)txt toTarget:theTarget
  442. {
  443.   int           rtn;                        // method call record value
  444.   int           parms;                      // number of parsed parms
  445.   char          methName[maxLEN + 1];       // max method name length
  446.   char          parmString[maxLEN + 1];     // max parm length
  447.   char          *pPtr, *dPtr;               // char pointers
  448.   SEL           selector;                   // method selector
  449.  
  450.   /* skip record if comment */
  451.   if (isCOMMENT(txt) || (*txt==':') || (*txt=='*') || (*txt=='!')) return cfgSINGLE_MODE;
  452.  
  453.   /* parse record */
  454.   parms = sscanf(txt, parseFORMAT, methName, parmString);
  455.   if (parms < 1) return cfgINVALID_PARMS;
  456.  
  457.   /* convert name to selector */
  458.   if (dPtr = index(methName, '$')) *dPtr = 0;
  459.   selector = sel_getUid(methName);
  460.   if (!sel_isMapped(selector)) return cfgNO_METHOD;
  461.       
  462.   /* parse additional parameter */
  463.   if (parms < 2) dPtr = pPtr = (char*)nil;
  464.   else {
  465.     for (pPtr = parmString; *pPtr && ((*pPtr == ' ') || (*pPtr == '\t')); pPtr++);
  466.     dPtr = pPtr;
  467.     if (*pPtr == '*') {
  468.       if (!strcmp(pPtr, "*NIL") || !strcmp(pPtr, "*nil")) pPtr = (char*)nil;        else
  469.       if (!strcmp(pPtr, "*NO" ) || !strcmp(pPtr, "*no" )) pPtr = (char*)((int)NO ); else
  470.       if (!strcmp(pPtr, "*YES") || !strcmp(pPtr, "*yes")) pPtr = (char*)((int)YES); else
  471.       if (!strcmp(pPtr, "*ID" ) || !strcmp(pPtr, "*id") ) pPtr = (char*)nil;
  472.     }
  473.   }
  474.  
  475.   /* perform method */
  476.   if (parms == 2) rtn = (int)[theTarget perform:selector with:(id)pPtr];
  477.   else            rtn = (int)[theTarget perform:selector];
  478.       
  479.   /* check return status */
  480.   return (rtn)? cfgMETHOD_ERROR : cfgOK;
  481.   
  482. }
  483.  
  484. // -------------------------------------------------------------------------------------
  485. // profile / time-lapse methods
  486. // -------------------------------------------------------------------------------------
  487.  
  488. static double currentTimeStamp()
  489. {
  490.   struct timeval    tp;
  491.   struct timezone   tzp;
  492.   gettimeofday(&tp, &tzp);
  493.   return (double)tp.tv_sec + (double)tp.tv_usec / 1000000.00;
  494. }
  495.  
  496. /* return time lapse from last startProfile */
  497. + (float)profileElapsedTime
  498. {
  499.   return (profileActive)? (float)(currentTimeStamp() - profileStart) : profileLapse;
  500. }
  501.  
  502. /* return number of executed ReadConfig methods */
  503. + (int)profileCount
  504. {
  505.   return profileCount;
  506. }
  507.  
  508. /* set profiler target */
  509. + setProfileTarget:profile
  510. {
  511.   if ([profile respondsTo:profilePING]) profileTarget = profile;
  512.   return self;
  513. }
  514.  
  515. /* start profiler */
  516. + startProfile
  517. {
  518.   if (!profileTarget) [self setProfileTarget:NXApp];    // default profile target
  519.   profileStart = currentTimeStamp();
  520.   profileCount = 0;
  521.   profileActive = YES;
  522.   return self;
  523. }
  524.  
  525. /* stop profile */
  526. + stopProfile
  527. {
  528.   profileLapse = (float)(currentTimeStamp() - profileStart);
  529.   profileActive = NO;
  530.   return self;
  531. }
  532.  
  533. // -------------------------------------------------------------------------------------
  534. // read specified config file
  535. // Notes:
  536. //   - These methods are for INTERNAL USE ONLY and should not be called directly.
  537. // -------------------------------------------------------------------------------------
  538.  
  539. /* initialize the internal ReadConfig instance */
  540. - init
  541. {
  542.   [super init];
  543.   level         = 0;
  544.   modalPanel    = (id)nil;
  545.   methodRtn     = 0;
  546.   *findLabel    = 0;
  547.   [self cfg_methodTrace:dftMethodTrace];
  548.   [self cfg_printError:dftPrintError];
  549.   [self cfg_checkReturn:dftCheckAll];
  550.   [self cfg_terminateOnError:dftExitOnError];
  551.   return self;
  552. }
  553.  
  554. /* free internal ReadConfig instance */
  555. - free
  556. {
  557.   return [super free];
  558. }
  559.  
  560. /* set method target */
  561. - setTarget:obj
  562. {
  563.   mainTarget = obj;
  564.   return self;
  565. }
  566.  
  567. /* open/read config file */
  568. - (int)readConfigFile:(char*)file
  569. {
  570.   int           error = cfgOK;                  // returned error code
  571.   char          txt[2048];                      // max record length
  572.   char          method[maxLEN + 1], *methName;  // max method name length
  573.   char          parmString[2048];               // max parm length
  574.   char          *pPtr, *dPtr, *cPtr;            // char pointers
  575.   SEL           selector;                       // method selector
  576.   BOOL          checkRtn;                       // check return for method
  577.   int           parms;                          // number of parsed parms
  578.   id            msgTarget = (id)nil;            // method target (default target)
  579.   int           prefixLen;                      // length of internal method prefix
  580.     
  581.   /* open file for current recursion level */
  582.   exc[level].lineNumber = 0;
  583.   exc[level].fNum = fopen(file, "r");
  584.   if (!exc[level].fNum) {
  585.     error = cfgOPEN_ERROR;
  586.     printf("ReadConfig ERROR: Unable to open config file %s\n", file);
  587.     if (errorTrap) [errorTrap configErrorTrap:error inFile:file atLine:0];
  588.     return error;
  589.   }
  590.     
  591.   /* set up internal method name prefix */
  592.   strcpy(method, prefixMethodNAME);
  593.   prefixLen = strlen(method);
  594.  
  595.   /* read file records */
  596.   for (*txt = 0; fgets(txt, maxLEN, exc[level].fNum);) {
  597.       
  598.     /* count line number */
  599.     exc[level].lineNumber++;
  600.  
  601.     /* skip record if comment */
  602.     if (isCOMMENT(txt)) continue;
  603.  
  604.     /* parse record */
  605.     methName = &method[prefixLen];
  606.     parmString[0] = 0;
  607.     parms = sscanf(txt, parseFORMAT, methName, parmString);
  608.     if (parms < 1) breakERROR(cfgINVALID_PARMS);
  609.     
  610.     /* internal method check */
  611.     if (*methName == '*') { *methName = '_'; methName = method; }
  612.     
  613.     /* check for label match */
  614.     if (*findLabel || (*methName == ':')) {
  615.       if ((*methName == ':') && !strcmp(findLabel, &methName[1])) *findLabel = 0;
  616.       continue;
  617.     }
  618.       
  619.     /* parse additional parameter */
  620.     if (parms < 2) dPtr = pPtr = (char*)nil;
  621.     else {
  622.       for (pPtr = parmString; *pPtr && ((*pPtr == ' ') || (*pPtr == '\t')); pPtr++);
  623.       dPtr = pPtr;
  624.       if (*pPtr == '*') {
  625.         if (!strcmp(pPtr, "*NIL") || !strcmp(pPtr, "*nil")) pPtr = (char*)nil;        else
  626.         if (!strcmp(pPtr, "*NO" ) || !strcmp(pPtr, "*no" )) pPtr = (char*)((int)NO ); else
  627.         if (!strcmp(pPtr, "*YES") || !strcmp(pPtr, "*yes")) pPtr = (char*)((int)YES);
  628.       }
  629.     }
  630.  
  631.     /* return status checking */
  632.     checkRtn = (*methName == '!' || *methName == '*')? NO: YES;
  633.     
  634.     /* check for update keys in method name */
  635.     if (cPtr = index(methName, '$')) *cPtr = 0;
  636.  
  637.     /* convert name to selector */
  638.     selector = sel_getUid(&methName[(checkRtn)? 0: 1]);
  639.     if (!sel_isMapped(selector)) breakERROR(cfgNO_METHOD);
  640.     
  641.     /* determine real target */
  642.     if (*methName == '*') msgTarget = self;
  643.     else msgTarget = mainTarget;
  644.  
  645.     /* debug: show method performed */
  646.     if (methodTrace) {
  647.       printf("[%s perform:\"%s\"", [msgTarget name], sel_getName(selector));
  648.       if (parms == 2) printf(((pPtr==dPtr)? " with:\"%s\"": " with:(void*)%d"), (int)pPtr);
  649.       printf("];\n");
  650.     }
  651.  
  652.     /* precheck method performance */
  653.     if ( [msgTarget respondsTo:@selector(configWillPerform:with:)] &&
  654.         ![msgTarget configWillPerform:selector with:(id)pPtr]) continue;
  655.  
  656.     /* make sure target can respond to the selector */
  657.     if (![msgTarget respondsTo:selector]) breakERROR(cfgBADTARGET);
  658.     
  659.     /* perform method */
  660.     if (parms == 2) methodRtn = (int)[msgTarget perform:selector with:(id)pPtr];
  661.     else            methodRtn = (int)[msgTarget perform:selector];
  662.       
  663.     /* check return status */
  664.     if (checkAll && checkRtn && methodRtn) breakERROR(cfgMETHOD_ERROR);
  665.     
  666.     /* check for active elapsed time profiling */
  667.     if (profileActive && (msgTarget != self)) {
  668.       profileCount++;
  669.       if (profileTarget) [profileTarget profileElapsedTime:[[self class] profileElapsedTime]];
  670.     }
  671.  
  672.   }
  673.     
  674.   /* terminate any status modal loop */
  675.   if (modalPanel) [self cfg_endStopPanel];
  676.     
  677.   /* check for unmatched goto label */
  678.   if (!error && *findLabel) error = cfgGOTO_LABEL;
  679.  
  680.   /* close file */
  681.   fclose(exc[level].fNum);
  682.  
  683.   /* set global vars */
  684.   globalLevel = level;
  685.   globalLine  = exc[level].lineNumber;
  686.   
  687.   /* print error */
  688.   if (error) {
  689.     int lineNbr = exc[level].lineNumber;
  690.     if (printError) {
  691.       char *fName = rindex(file, '/');
  692.       printf("ReadConfig [%s] (%s): %s\n", [msgTarget name], ((fName)? (fName + 1): file), 
  693.         errorDesc[error]);
  694.       printf(" line %d: %s\n", lineNbr, txt);
  695.     }
  696.     if (errorTrap) [errorTrap configErrorTrap:error inFile:file atLine:lineNbr];
  697.     if (exitOnError) [self cfg_terminate];
  698.   }
  699.  
  700.   /* return error */
  701.   return error;
  702.   
  703. }
  704.   
  705. #undef          breakERROR
  706.   
  707. // -------------------------------------------------------------------------------------
  708. // Internal utility method routines
  709. // Notes:
  710. //   - These methods are for use from within the config file.  They are accessed by
  711. //     placing an '*' in front of the desired method call.  (ie. '*methodTrace: *YES')
  712. //   - See the header file for more information on these methods.
  713. // -------------------------------------------------------------------------------------
  714.  
  715. /* set method trace mode */
  716. - (int)cfg_methodTrace:(BOOL)yesNo
  717. {
  718.   methodTrace = yesNo;
  719.   return NO;
  720. }
  721.  
  722. /* set global method trace mode */
  723. - (int)cfg_globalMethodTrace:(BOOL)yesNo
  724. {
  725.   dftMethodTrace = yesNo;
  726.   return [self cfg_methodTrace:dftMethodTrace];
  727. }
  728.  
  729. /* set error print mode */
  730. - (int)cfg_printError:(BOOL)yesNo
  731. {
  732.   printError = yesNo;
  733.   return NO;
  734. }
  735.  
  736. /* set global error print mode */
  737. - (int)cfg_globalPrintError:(BOOL)yesNo
  738. {
  739.   dftPrintError = yesNo;
  740.   return [self cfg_printError:dftPrintError];
  741. }
  742.  
  743. /* set return status checking mode */
  744. - (int)cfg_checkReturn:(BOOL)yesNo
  745. {
  746.   checkAll = yesNo;
  747.   return NO;
  748. }
  749.  
  750. /* set global return status checking mode */
  751. - (int)cfg_globalCheckReturn:(BOOL)yesNo
  752. {
  753.   dftCheckAll = yesNo;
  754.   return [self cfg_checkReturn:dftCheckAll];
  755. }
  756.  
  757. /* set global return status checking mode */
  758. - (int)cfg_globalBackupOnWrite:(BOOL)yesNo
  759. {
  760.   dftBackupOnWrite = yesNo;
  761.   return NO;
  762. }
  763.  
  764. /* set terminate application on error flag */
  765. - (int)cfg_terminateOnError:(BOOL)yesNo
  766. {
  767.   exitOnError = yesNo;
  768.   return NO;
  769. }
  770.  
  771. /* set terminate application on error flag */
  772. - (int)cfg_globalTerminateOnError:(BOOL)yesNo
  773. {
  774.   dftExitOnError = yesNo;
  775.   return [self cfg_terminateOnError:dftExitOnError];
  776. }
  777.   
  778. // -------------------------------------------------------------------------------------
  779. // branching
  780.   
  781. /* goto label */
  782. - (int)cfg_goto:(char*)label
  783. {
  784.   if (!label) return YES;
  785.   strncpy(findLabel, label, sizeof(findLabel));
  786.   rewind(exc[level].fNum);      // rewind file
  787.   return NO;
  788. }
  789.  
  790. /* if True goto label */
  791. - (int)cfg_ifTrue:(char*)label
  792. {
  793.   if (methodRtn) return [self cfg_goto:label];
  794.   return NO;
  795. }
  796.  
  797. /* if False goto label */
  798. - (int)cfg_ifFalse:(char*)label
  799. {
  800.   if (!methodRtn) return [self cfg_goto:label];
  801.   return NO;
  802. }
  803.   
  804. // -------------------------------------------------------------------------------------
  805. // user panels
  806.  
  807. /* alert panel */
  808. - (int)cfg_alertPanel:(char*)message
  809. {
  810.   char  *title = "Configuration File Alert";
  811.   NXRunAlertPanel(title, message, "OK", (char*)nil, (char*)nil);
  812.   return NO;
  813. }
  814.  
  815. /* abort panel */
  816. - (int)cfg_abortPanel:(char*)message
  817. {
  818.   int   rtn;
  819.   char  *title = "Configuration File Abort";
  820.   rtn = NXRunAlertPanel(title, message, "Continue", "Terminate", (char*)nil);
  821.   if (!rtn) [self cfg_terminate];
  822.   return NO;
  823. }
  824.  
  825. /* option panel */
  826. - (int)cfg_optionPanel:(char*)message
  827. {
  828.   int   i;
  829.   int   alertRtn;
  830.   char  btnA[100], btnB[100];
  831.   char  *title = "Configuration File Option";
  832.   if ((message[0]=='<') || (message[0]=='(') || (message[0]=='{')) {
  833.     sscanf(message, "%*c%[^,/| ]%*c%[^>)}]%*c%n", btnA, btnB, &i);
  834.     while (message[i] == ' ') i++;
  835.     alertRtn = NXRunAlertPanel(title, &message[i], btnB, btnA, (char*)nil);
  836.   } else
  837.     alertRtn = NXRunAlertPanel(title, message, "B", "A", (char*)nil);
  838.   return alertRtn;
  839. }
  840.  
  841. /* if option-A (False) goto label */
  842. - (int)cfg_ifOptionA:(char*)label
  843. {
  844.   return [self cfg_ifFalse:label];
  845. }
  846.  
  847. /* if option-B (True) goto label */
  848. - (int)cfg_ifOptionB:(char*)label
  849. {
  850.   return [self cfg_ifTrue:label];
  851. }
  852.  
  853. /* end modal panel */
  854. - (int)cfg_endModalPanel
  855. {
  856.   if (!modalPanel) return YES;
  857.   [NXApp runModalSession:&modalSession];        // eat up any queued events
  858.   [NXApp endModalSession:&modalSession];
  859.   [modalPanel orderOut:nil];
  860.   NXFreeAlertPanel(modalPanel);
  861.   modalPanel = (id)nil;
  862.   return NO;
  863. }
  864.  
  865. /* begin info modal panel */
  866. - (int)cfg_infoPanel:(char*)message
  867. {
  868.   char          *title = "Configuration Information";
  869.   if (modalPanel) [self cfg_endModalPanel];
  870.   modalPanel = NXGetAlertPanel(title, message, "", 0, 0);
  871.   [NXApp beginModalSession:&modalSession for:modalPanel];
  872.   [NXApp runModalSession:&modalSession];        // show panel
  873.   return NO;
  874. }
  875.  
  876. /* end modal panel */
  877. - (int)cfg_endInfoPanel
  878. {
  879.   return [self cfg_endModalPanel];
  880. }
  881.  
  882. /* begin stop modal panel */
  883. - (int)cfg_stopPanel:(char*)message
  884. {
  885.   char          *title = "Configuration File Status";
  886.   if (modalPanel) [self cfg_endModalPanel];
  887.   modalPanel = NXGetAlertPanel(title, message, "Stop", 0, 0);
  888.   [NXApp beginModalSession:&modalSession for:modalPanel];
  889.   [NXApp runModalSession:&modalSession];        // show panel
  890.   return NO;
  891. }
  892.  
  893. /* end stop panel */
  894. - (int)cfg_endStopPanel
  895. {
  896.   return [self cfg_endModalPanel];
  897. }
  898.  
  899. /* check for modal stop button */
  900. - (int)ifStop:(char*)label
  901. {
  902.   int           modalRtn;
  903.   if (!modalPanel) return YES;
  904.   modalRtn = [NXApp runModalSession:&modalSession];
  905.   if (modalRtn == NX_ALERTDEFAULT) {
  906.     [self cfg_endStopPanel];
  907.     return [self cfg_goto:label];
  908.   }
  909.   return NO;
  910. }
  911.   
  912. // -------------------------------------------------------------------------------------
  913. // misc
  914.  
  915. /* print string */
  916. - (int)cfg_print:(char*)text
  917. {
  918.   printf("%s\n", text);
  919.   return NO;
  920. }
  921.  
  922. /* terminate application */
  923. - (int)cfg_terminate
  924. {
  925.   [NXApp terminate:self];
  926.   return YES;
  927. }
  928.  
  929. /* include another config file */
  930. - (int)cfg_include:(char*)cfgName
  931. {
  932.   int   error;
  933.   char  *fileName;
  934.   level++;
  935.   fileName = [[self class] configPath:cfgName];
  936.   error = [self readConfigFile:fileName];
  937.   free(fileName);
  938.   level--;
  939.   return error;
  940. }
  941.   
  942. // -------------------------------------------------------------------------------------
  943. // time lapse profiling
  944.  
  945. /* start time profile */
  946. - (int)cfg_startProfile
  947. {
  948.   [[self class] startProfile];
  949.   return NO;
  950. }
  951.  
  952. /* stop time profile */
  953. - (int)cfg_stopProfile
  954. {
  955.   [[self class] stopProfile];
  956.   return NO;
  957. }
  958.  
  959. /* print current elapsed time */
  960. - (int)cfg_printElapsedTime
  961. {
  962.   printf("ReadConfig: Profile elapsed time = %.2f seconds, executed methods = %d\n",
  963.     [[self class] profileElapsedTime], [[self class] profileCount]);
  964.   return NO;
  965. }
  966.  
  967. @end    
  968.