home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / misc / volume26 / tclx / part18 < prev    next >
Encoding:
Text File  |  1991-11-19  |  37.9 KB  |  1,231 lines

  1. Newsgroups: comp.sources.misc
  2. From: karl@sugar.neosoft.com (Karl Lehenbauer)
  3. Subject:  v26i018:  tclx - extensions and on-line help for tcl 6.1, Part18/23
  4. Message-ID: <1991Nov19.135631.1399@sparky.imd.sterling.com>
  5. X-Md4-Signature: 90a3e37a1a942fd4e204c8a6ea32ee0f
  6. Date: Tue, 19 Nov 1991 13:56:31 GMT
  7. Approved: kent@sparky.imd.sterling.com
  8.  
  9. Submitted-by: karl@sugar.neosoft.com (Karl Lehenbauer)
  10. Posting-number: Volume 26, Issue 18
  11. Archive-name: tclx/part18
  12. Environment: UNIX
  13.  
  14. #! /bin/sh
  15. # This is a shell archive.  Remove anything before this line, then unpack
  16. # it by saving it into a file and typing "sh file".  To overwrite existing
  17. # files, type "sh file -c".  You can also feed this as standard input via
  18. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  19. # will see the following message at the end:
  20. #        "End of archive 18 (of 23)."
  21. # Contents:  extended/src/extendUtil.c extended/src/filescan.c
  22. # Wrapped by karl@one on Wed Nov 13 21:50:30 1991
  23. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  24. if test -f 'extended/src/extendUtil.c' -a "${1}" != "-c" ; then 
  25.   echo shar: Will not clobber existing file \"'extended/src/extendUtil.c'\"
  26. else
  27. echo shar: Extracting \"'extended/src/extendUtil.c'\" \(16951 characters\)
  28. sed "s/^X//" >'extended/src/extendUtil.c' <<'END_OF_FILE'
  29. X/*
  30. X * extendUtil.c
  31. X *
  32. X * Utility functions for Extended Tcl.
  33. X *---------------------------------------------------------------------------
  34. X * Copyright 1991 Karl Lehenbauer and Mark Diekhans.
  35. X *
  36. X * Permission to use, copy, modify, and distribute this software and its
  37. X * documentation for any purpose and without fee is hereby granted, provided
  38. X * that the above copyright notice appear in all copies.  Karl Lehenbauer and
  39. X * Mark Diekhans make no representations about the suitability of this
  40. X * software for any purpose.  It is provided "as is" without express or
  41. X * implied warranty.
  42. X */
  43. X
  44. X#include "tclExtdInt.h"
  45. X
  46. X#ifdef TCL_NO_TOLOWER_MACRO
  47. X#  define _tolower tolower
  48. X#  define _toupper toupper
  49. X#endif
  50. X
  51. X/*
  52. X * Prototypes of internal functions.
  53. X */
  54. Xvoid
  55. XExpandDynBuf _ANSI_ARGS_((dynamicBuf_t *dynBufPtr,
  56. X                          int           appendSize));
  57. X
  58. X
  59. X/*
  60. X *----------------------------------------------------------------------
  61. X *
  62. X * Tcl_StrToLong --
  63. X *      Convert an Ascii string to an long number of the specified base.
  64. X *
  65. X * Parameters:
  66. X *   o string (I) - String containing a number.
  67. X *   o base (I) - The base to use for the number 8, 10 or 16 or zero to decide
  68. X *     based on the leading characters of the number.  Zero to let the number
  69. X *     determine the base.
  70. X *   o longPtr (O) - Place to return the converted number.  Will be 
  71. X *     unchanged if there is an error.
  72. X *
  73. X * Returns:
  74. X *      Returns 1 if the string was a valid number, 0 invalid.
  75. X *----------------------------------------------------------------------
  76. X */
  77. Xint
  78. XTcl_StrToLong (string, base, longPtr)
  79. X    CONST char *string;
  80. X    int         base;
  81. X    long       *longPtr;
  82. X{
  83. X    char *end;
  84. X    long  num;
  85. X
  86. X    num = strtol(string, &end, base);
  87. X    while ((*end != '\0') && isspace(*end)) {
  88. X        end++;
  89. X    }
  90. X    if ((end == string) || (*end != 0))
  91. X        return FALSE;
  92. X    *longPtr = num;
  93. X    return TRUE;
  94. X
  95. X} /* Tcl_StrToLong */
  96. X
  97. X/*
  98. X *----------------------------------------------------------------------
  99. X *
  100. X * Tcl_StrToInt --
  101. X *      Convert an Ascii string to an number of the specified base.
  102. X *
  103. X * Parameters:
  104. X *   o string (I) - String containing a number.
  105. X *   o base (I) - The base to use for the number 8, 10 or 16 or zero to decide
  106. X *     based on the leading characters of the number.  Zero to let the number
  107. X *     determine the base.
  108. X *   o intPtr (O) - Place to return the converted number.  Will be 
  109. X *     unchanged if there is an error.
  110. X *
  111. X * Returns:
  112. X *      Returns 1 if the string was a valid number, 0 invalid.
  113. X *----------------------------------------------------------------------
  114. X */
  115. Xint
  116. XTcl_StrToInt (string, base, intPtr)
  117. X    CONST char *string;
  118. X    int         base;
  119. X    int        *intPtr;
  120. X{
  121. X    char *end;
  122. X    int   num;
  123. X
  124. X    num = strtol(string, &end, base);
  125. X    while ((*end != '\0') && isspace(*end)) {
  126. X        end++;
  127. X    }
  128. X    if ((end == string) || (*end != 0))
  129. X        return FALSE;
  130. X    *intPtr = num;
  131. X    return TRUE;
  132. X
  133. X} /* Tcl_StrToInt */
  134. X
  135. X/*
  136. X *----------------------------------------------------------------------
  137. X *
  138. X * Tcl_StrToUnsigned --
  139. X *      Convert an Ascii string to an unsigned int of the specified base.
  140. X *
  141. X * Parameters:
  142. X *   o string (I) - String containing a number.
  143. X *   o base (I) - The base to use for the number 8, 10 or 16 or zero to decide
  144. X *     based on the leading characters of the number.  Zero to let the number
  145. X *     determine the base.
  146. X *   o unsignedPtr (O) - Place to return the converted number.  Will be 
  147. X *     unchanged if there is an error.
  148. X *
  149. X * Returns:
  150. X *      Returns 1 if the string was a valid number, 0 invalid.
  151. X *----------------------------------------------------------------------
  152. X */
  153. Xint
  154. XTcl_StrToUnsigned (string, base, unsignedPtr)
  155. X    CONST char *string;
  156. X    int         base;
  157. X    unsigned   *unsignedPtr;
  158. X{
  159. X    char          *end;
  160. X    unsigned long  num;
  161. X
  162. X    num = strtoul (string, &end, base);
  163. X    while ((*end != '\0') && isspace(*end)) {
  164. X        end++;
  165. X    }
  166. X    if ((end == string) || (*end != 0))
  167. X        return FALSE;
  168. X    *unsignedPtr = num;
  169. X    return TRUE;
  170. X
  171. X} /* Tcl_StrToUnsigned */
  172. X
  173. X/*
  174. X *----------------------------------------------------------------------
  175. X *
  176. X * Tcl_StrToDouble --
  177. X *   Convert a string to a double percision floating point number.
  178. X *
  179. X * Parameters:
  180. X *   string (I) - Buffer containing double value to convert.
  181. X *   doublePtr (O) - The convert floating point number.
  182. X * Returns:
  183. X *   TRUE if the number is ok, FALSE if it is illegal.
  184. X *-----------------------------------------------------------------------------
  185. X */
  186. Xint
  187. XTcl_StrToDouble (string, doublePtr)
  188. X    CONST char *string;
  189. X    double     *doublePtr;
  190. X{
  191. X    char   *end;
  192. X    double  num;
  193. X
  194. X    num = strtod (string, &end);
  195. X    while ((*end != '\0') && isspace(*end)) {
  196. X        end++;
  197. X    }
  198. X    if ((end == string) || (*end != 0))
  199. X        return FALSE;
  200. X
  201. X    *doublePtr = num;
  202. X    return TRUE;
  203. X
  204. X} /* Tcl_StrToDouble */
  205. X
  206. X/*
  207. X *----------------------------------------------------------------------
  208. X *
  209. X * Tcl_DownShift --
  210. X *     Utility procedure to down-shift a string.  It is written in such
  211. X *     a way as that the target string maybe the same as the source string.
  212. X *
  213. X * Parameters:
  214. X *   o targetStr (I) - String to store the down-shifted string in.  Must
  215. X *     have enough space allocated to store the string.  If NULL is specified,
  216. X *     then the string will be dynamicly allocated and returned as the
  217. X *     result of the function. May also be the same as the source string to
  218. X *     shift in place.
  219. X *   o sourceStr (I) - The string to down-shift.
  220. X *
  221. X * Returns:
  222. X *   A pointer to the down-shifted string
  223. X *----------------------------------------------------------------------
  224. X */
  225. Xchar *
  226. XTcl_DownShift (targetStr, sourceStr)
  227. X    char       *targetStr;
  228. X    CONST char *sourceStr;
  229. X{
  230. X    register char theChar;
  231. X
  232. X    if (targetStr == NULL)
  233. X        targetStr = ckalloc (strlen ((char *) sourceStr) + 1);
  234. X
  235. X    for (; (theChar = *sourceStr) != '\0'; sourceStr++) {
  236. X        if (isupper (theChar))
  237. X            theChar = tolower (theChar);
  238. X        *targetStr++ = theChar;
  239. X    }
  240. X    *targetStr = '\0';
  241. X    return targetStr;
  242. X}
  243. X
  244. X/*
  245. X *----------------------------------------------------------------------
  246. X *
  247. X * Tcl_UpShift --
  248. X *     Utility procedure to up-shift a string.
  249. X *
  250. X * Parameters:
  251. X *   o targetStr (I) - String to store the up-shifted string in.  Must
  252. X *     have enough space allocated to store the string.  If NULL is specified,
  253. X *     then the string will be dynamicly allocated and returned as the
  254. X *     result of the function. May also be the same as the source string to
  255. X *     shift in place.
  256. X *   o sourceStr (I) - The string to up-shift.
  257. X *
  258. X * Returns:
  259. X *   A pointer to the up-shifted string
  260. X *----------------------------------------------------------------------
  261. X */
  262. Xchar *
  263. XTcl_UpShift (targetStr, sourceStr)
  264. X    char       *targetStr;
  265. X    CONST char *sourceStr;
  266. X{
  267. X    register char theChar;
  268. X
  269. X    if (targetStr == NULL)
  270. X        targetStr = ckalloc (strlen ((char *) sourceStr) + 1);
  271. X
  272. X    for (; (theChar = *sourceStr) != '\0'; sourceStr++) {
  273. X        if (islower (theChar))
  274. X            theChar = toupper (theChar);
  275. X        *targetStr++ = theChar;
  276. X    }
  277. X    *targetStr = '\0';
  278. X    return targetStr;
  279. X}
  280. X
  281. X/*
  282. X *----------------------------------------------------------------------
  283. X *
  284. X * ExpandDynBuf --
  285. X *
  286. X *    Expand a dynamic buffer so that it will have room to hold the 
  287. X *    specified additional space.  If `appendSize' is zero, the buffer
  288. X *    size will just be doubled.
  289. X *
  290. X *----------------------------------------------------------------------
  291. X */
  292. Xstatic void
  293. XExpandDynBuf (dynBufPtr, appendSize)
  294. X    dynamicBuf_t *dynBufPtr;
  295. X    int           appendSize;
  296. X{
  297. X    int   newSize;
  298. X    char *oldBufPtr;
  299. X
  300. X    newSize = dynBufPtr->size * 2;
  301. X    if (newSize < (dynBufPtr->used + appendSize)) {
  302. X        newSize = dynBufPtr->used + appendSize;
  303. X    }
  304. X    oldBufPtr = dynBufPtr->ptr;
  305. X    dynBufPtr->ptr = ckalloc (newSize);
  306. X    memcpy (dynBufPtr->ptr, oldBufPtr, dynBufPtr->used);
  307. X    if (oldBufPtr != dynBufPtr->buf)
  308. X        ckfree ((char *) oldBufPtr);
  309. X    dynBufPtr->size = newSize;
  310. X}
  311. X
  312. X/*
  313. X *----------------------------------------------------------------------
  314. X *
  315. X * Tcl_DynBufInit --
  316. X *
  317. X *    Initializes a dynamic buffer.
  318. X *
  319. X *----------------------------------------------------------------------
  320. X */
  321. Xvoid
  322. XTcl_DynBufInit (dynBufPtr)
  323. X    dynamicBuf_t *dynBufPtr;
  324. X{
  325. X    dynBufPtr->buf [0] = '\0';
  326. X    dynBufPtr->ptr = dynBufPtr->buf;
  327. X    dynBufPtr->size = INIT_DYN_BUFFER_SIZE;
  328. X    dynBufPtr->used = 0;
  329. X}
  330. X
  331. X/*
  332. X *----------------------------------------------------------------------
  333. X *
  334. X * Tcl_DynBufFree --
  335. X *
  336. X *    Clean up a dynamic buffer, release space if it was dynamicly
  337. X *    allocated.
  338. X *
  339. X *----------------------------------------------------------------------
  340. X */
  341. Xvoid
  342. XTcl_DynBufFree (dynBufPtr)
  343. X    dynamicBuf_t *dynBufPtr;
  344. X{
  345. X    if (dynBufPtr->ptr != dynBufPtr->buf)
  346. X        ckfree (dynBufPtr->ptr);
  347. X}
  348. X
  349. X/*
  350. X *----------------------------------------------------------------------
  351. X *
  352. X * Tcl_DynBufReturn --
  353. X *
  354. X *    Return the contents of the dynamic buffer as an interpreter result.
  355. X *    The dynamic buffer must be re-initialized to reuse it.
  356. X *
  357. X *----------------------------------------------------------------------
  358. X */
  359. Xvoid
  360. XTcl_DynBufReturn (interp, dynBufPtr)
  361. X    Tcl_Interp    *interp;
  362. X    dynamicBuf_t *dynBufPtr;
  363. X{
  364. X    if (dynBufPtr->ptr != dynBufPtr->buf)
  365. X        Tcl_SetResult (interp, dynBufPtr->ptr, TCL_DYNAMIC);
  366. X    else
  367. X        Tcl_SetResult (interp, dynBufPtr->ptr, TCL_VOLATILE);
  368. X}
  369. X
  370. X/*
  371. X *----------------------------------------------------------------------
  372. X *
  373. X * Tcl_DynBufAppend --
  374. X *
  375. X *    Append the specified string to the dynamic buffer, expanding if
  376. X *    necessary. Assumes the string in the buffer is zero terminated.
  377. X *
  378. X *----------------------------------------------------------------------
  379. X */
  380. Xvoid
  381. XTcl_DynBufAppend (dynBufPtr, newStr)
  382. X    dynamicBuf_t *dynBufPtr;
  383. X    char         *newStr;
  384. X{
  385. X    int newLen, currentUsed;
  386. X
  387. X    newLen = strlen (newStr) + 1;
  388. X    currentUsed = (dynBufPtr->used == 0) ? 0 : dynBufPtr->used - 1;
  389. X    if ((currentUsed + newLen) > dynBufPtr->size)
  390. X        ExpandDynBuf (dynBufPtr, newLen);  /* Don't double count '\0' */
  391. X    strcpy (dynBufPtr->ptr + currentUsed, newStr);
  392. X}
  393. X
  394. X/*
  395. X *----------------------------------------------------------------------
  396. X *
  397. X * Tcl_DynamicFgets --
  398. X *
  399. X *    Reads a line from a file into a dynamic buffer.  The buffer will be
  400. X *    expanded, if necessary and reads are done until EOL or EOF is reached.
  401. X *    Any data already in the buffer will be overwritten.  Even if an error
  402. X *    or EOF is encountered, the buffer should be cleaned up, as storage 
  403. X *    may have still been allocated.
  404. X *
  405. X * Results:
  406. X *    If data was transfered, returns > 0, if EOF was encountered without
  407. X *    transfering any data, returns 0.  If an error occured, returns, < 0.
  408. X *
  409. X *----------------------------------------------------------------------
  410. X */
  411. Xint
  412. XTcl_DynamicFgets (dynBufPtr, filePtr)
  413. X    dynamicBuf_t *dynBufPtr;
  414. X    FILE         *filePtr;
  415. X{
  416. X    int   readVal;
  417. X
  418. X    dynBufPtr->used = 0;
  419. X
  420. X    while (TRUE) {
  421. X        if (dynBufPtr->used == dynBufPtr->size)
  422. X            ExpandDynBuf (dynBufPtr, 0);
  423. X
  424. X        readVal = getc (filePtr);
  425. X        if (readVal == '\n')      /* Is it a new-line? */
  426. X            break;
  427. X        if (readVal == EOF) {     /* Is it an EOF or an error? */
  428. X            if (feof (filePtr)) {
  429. X                break;
  430. X            }
  431. X            return -1;   /* Error */
  432. X        }
  433. X        dynBufPtr->ptr [dynBufPtr->used++] = readVal;
  434. X    }
  435. X    dynBufPtr->ptr [dynBufPtr->used++] = '\0';
  436. X    return (readVal == EOF) ? 0 : 1;
  437. X}
  438. X
  439. X/*
  440. X *----------------------------------------------------------------------
  441. X *
  442. X * Tcl_GetLong --
  443. X *
  444. X *      Given a string, produce the corresponding long value.
  445. X *
  446. X * Results:
  447. X *      The return value is normally TCL_OK;  in this case *intPtr
  448. X *      will be set to the integer value equivalent to string.  If
  449. X *      string is improperly formed then TCL_ERROR is returned and
  450. X *      an error message will be left in interp->result.
  451. X *
  452. X * Side effects:
  453. X *      None.
  454. X *
  455. X *----------------------------------------------------------------------
  456. X */
  457. Xint
  458. XTcl_GetLong(interp, string, longPtr)
  459. X    Tcl_Interp *interp;         /* Interpreter to use for error reporting. */
  460. X    CONST char *string;         /* String containing a (possibly signed)
  461. X                                 * integer in a form acceptable to strtol. */
  462. X    long       *longPtr;        /* Place to store converted result. */
  463. X{
  464. X    char *end;
  465. X    long  i;
  466. X
  467. X    i = strtol(string, &end, 0);
  468. X    while ((*end != '\0') && isspace(*end)) {
  469. X        end++;
  470. X    }
  471. X    if ((end == string) || (*end != 0)) {
  472. X        Tcl_AppendResult (interp, "expected integer but got \"", string,
  473. X                          "\"", (char *) NULL);
  474. X        return TCL_ERROR;
  475. X    }
  476. X    *longPtr = i;
  477. X    return TCL_OK;
  478. X}
  479. X
  480. X/*
  481. X *----------------------------------------------------------------------
  482. X *
  483. X * Tcl_GetUnsigned --
  484. X *
  485. X *      Given a string, produce the corresponding unsigned integer value.
  486. X *
  487. X * Results:
  488. X *      The return value is normally TCL_OK;  in this case *intPtr
  489. X *      will be set to the integer value equivalent to string.  If
  490. X *      string is improperly formed then TCL_ERROR is returned and
  491. X *      an error message will be left in interp->result.
  492. X *
  493. X * Side effects:
  494. X *      None.
  495. X *
  496. X *----------------------------------------------------------------------
  497. X */
  498. Xint
  499. XTcl_GetUnsigned(interp, string, unsignedPtr)
  500. X    Tcl_Interp *interp;         /* Interpreter to use for error reporting. */
  501. X    CONST char *string;         /* String containing a (possibly signed)
  502. X                                 * integer in a form acceptable to strtoul. */
  503. X    unsigned   *unsignedPtr;    /* Place to store converted result. */
  504. X{
  505. X    char          *end;
  506. X    unsigned long  i;
  507. X
  508. X    i = strtoul(string, &end, 0);
  509. X    while ((*end != '\0') && isspace(*end)) {
  510. X        end++;
  511. X    }
  512. X    if ((end == string) || (*end != 0)) {
  513. X        Tcl_AppendResult (interp, "expected unsigned integer but got \"", 
  514. X                          string, "\"", (char *) NULL);
  515. X        return TCL_ERROR;
  516. X    }
  517. X    *unsignedPtr = i;
  518. X    return TCL_OK;
  519. X}
  520. X
  521. X/*
  522. X *----------------------------------------------------------------------
  523. X *
  524. X * Tcl_ConvertFileHandle --
  525. X *
  526. X * Convert a file handle to its file number. The file handle maybe one 
  527. X * of "stdin", "stdout" or "stderr" or "fileNNN", were NNN is the file
  528. X * number.  If the handle is invalid, -1 is returned and a error message
  529. X * will be returned in interp->result.  This is used when the file may
  530. X * not be currently open.
  531. X *
  532. X *----------------------------------------------------------------------
  533. X */
  534. Xint
  535. XTcl_ConvertFileHandle (interp, handle)
  536. X    Tcl_Interp *interp;
  537. X    char       *handle;
  538. X{
  539. X    int fileId = -1;
  540. X
  541. X    if (handle [0] == 's') {
  542. X        if (STREQU (handle, "stdin"))
  543. X            fileId = 0;
  544. X        else if (STREQU (handle, "stdout"))
  545. X            fileId = 1;
  546. X        else if (STREQU (handle, "stderr"))
  547. X            fileId = 2;
  548. X    } else {
  549. X       if (STRNEQU (handle, "file", 4))
  550. X           Tcl_StrToInt (&handle [4], 10, &fileId);
  551. X    }
  552. X    if (fileId < 0)
  553. X        Tcl_AppendResult (interp, "invalid file handle: ", handle,
  554. X                          (char *) NULL);
  555. X    return fileId;
  556. X}
  557. X
  558. X/*
  559. X *----------------------------------------------------------------------
  560. X *
  561. X * Tcl_System --
  562. X *     does the equivalent of the Unix "system" library call, but
  563. X *     uses waitpid to wait on the correct process, rather than
  564. X *     waiting on all processes and throwing the exit statii away
  565. X *     for the processes it isn't interested in, plus does it with
  566. X *     a Tcl flavor
  567. X *
  568. X * Results:
  569. X *  Standard TCL results, may return the UNIX system error message.
  570. X *
  571. X *----------------------------------------------------------------------
  572. X */
  573. Xint 
  574. XTcl_System (interp, command)
  575. X    Tcl_Interp *interp;
  576. X    char       *command;
  577. X{
  578. X    int processID, waitStatus, processStatus;
  579. X
  580. X    if ((processID = Tcl_Fork()) < 0) {
  581. X        Tcl_AppendResult (interp, "Tcl_System: fork error:",
  582. X                          Tcl_UnixError (interp), (char*) NULL);
  583. X        return -1;
  584. X    }
  585. X    if (processID == 0) {
  586. X        int         closeFd, maxFiles;
  587. X        struct stat statBuf;
  588. X        
  589. X        /*
  590. X         * Close all pipe file descriptors.  Yuk.
  591. X         */
  592. X        maxFiles = ulimit (4, 0);
  593. X        for (closeFd = 3; closeFd < maxFiles; closeFd++)
  594. X            if ((fstat (closeFd, &statBuf) >= 0) && 
  595. X                    (S_IFIFO & statBuf.st_mode))
  596. X                close(closeFd);
  597. X
  598. X        execl("/bin/sh", "sh", "-c", command, (char *)NULL);
  599. X        _exit (256);
  600. X    } else {
  601. X        if (Tcl_WaitPids(1, &processID, &processStatus) == -1) {
  602. X            Tcl_AppendResult (interp, "Tcl_System: wait error",
  603. X                              Tcl_UnixError (interp), (char*) NULL);
  604. X            return -1;
  605. X        }
  606. X        return(WEXITSTATUS(processStatus));
  607. X    }
  608. X}
  609. END_OF_FILE
  610. if test 16951 -ne `wc -c <'extended/src/extendUtil.c'`; then
  611.     echo shar: \"'extended/src/extendUtil.c'\" unpacked with wrong size!
  612. fi
  613. # end of 'extended/src/extendUtil.c'
  614. fi
  615. if test -f 'extended/src/filescan.c' -a "${1}" != "-c" ; then 
  616.   echo shar: Will not clobber existing file \"'extended/src/filescan.c'\"
  617. else
  618. echo shar: Extracting \"'extended/src/filescan.c'\" \(17911 characters\)
  619. sed "s/^X//" >'extended/src/filescan.c' <<'END_OF_FILE'
  620. X/*
  621. X * filescan.c --
  622. X *
  623. X * Tcl file scanning: regular expression matching on lines of a file.  
  624. X * Implements awk.
  625. X *---------------------------------------------------------------------------
  626. X * Copyright 1991 Karl Lehenbauer and Mark Diekhans.
  627. X *
  628. X * Permission to use, copy, modify, and distribute this software and its
  629. X * documentation for any purpose and without fee is hereby granted, provided
  630. X * that the above copyright notice appear in all copies.  Karl Lehenbauer and
  631. X * Mark Diekhans make no representations about the suitability of this
  632. X * software for any purpose.  It is provided "as is" without express or
  633. X * implied warranty.
  634. X */
  635. X
  636. X#include "tclExtdInt.h"
  637. X#include "regexp.h"
  638. X
  639. X/*
  640. X * A scan context describes a collection of match patterns and commands,
  641. X * along with a match default command to apply to a file on a scan.
  642. X */
  643. X#define CONTEXT_A_CASE_INSENSITIVE_FLAG 2
  644. X#define MATCH_CASE_INSENSITIVE_FLAG 4
  645. X
  646. Xtypedef struct matchDef_t {
  647. X    regexp_t            regExpInfo;
  648. X    char               *command;
  649. X    struct matchDef_t  *nextMatchDefPtr;
  650. X    short               matchflags;
  651. X    } matchDef_t;
  652. Xtypedef struct matchDef_t *matchDef_pt;
  653. X
  654. Xtypedef struct scanContext_t {
  655. X    matchDef_pt  matchListHead;
  656. X    matchDef_pt  matchListTail;
  657. X    char        *defaultAction;
  658. X    short        flags;
  659. X    } scanContext_t;
  660. Xtypedef struct scanContext_t *scanContext_pt;
  661. X
  662. X/*
  663. X * Global data structure, pointer to by clientData.
  664. X */
  665. X
  666. Xtypedef struct {
  667. X    int             useCount;      /* Commands that current share globals */
  668. X    void_pt         tblHdrPtr;     /* Scan context handle table           */
  669. X    char            curName [16];  /* Current context name.               */ 
  670. X    } scanGlob_t;
  671. Xtypedef scanGlob_t *scanGlob_pt;
  672. X
  673. X/*
  674. X * Prototypes of internal functions.
  675. X */
  676. Xint
  677. XCleanUpContext _ANSI_ARGS_((scanGlob_pt    scanGlobPtr,
  678. X                            scanContext_pt contextPtr));
  679. X
  680. Xint
  681. XCreateScanContext _ANSI_ARGS_((Tcl_Interp  *interp,
  682. X                               scanGlob_pt  scanGlobPtr));
  683. X
  684. Xint
  685. XSelectScanContext _ANSI_ARGS_((Tcl_Interp  *interp,
  686. X                               scanGlob_pt  scanGlobPtr,
  687. X                               char        *contextHandle));
  688. X
  689. Xint
  690. XTcl_Delete_scancontextCmd _ANSI_ARGS_((Tcl_Interp  *interp,
  691. X                                       scanGlob_pt  scanGlobPtr,
  692. X                                       char        *contextHandle));
  693. X
  694. Xint
  695. XSetMatchVar _ANSI_ARGS_((Tcl_Interp *interp,
  696. X                         char       *fileLine,
  697. X                         long        fileOffset,
  698. X                         long        scanLineNum,
  699. X                         char       *fileHandle));
  700. X
  701. Xvoid
  702. XFileScanCleanUp _ANSI_ARGS_((ClientData clientData));
  703. X
  704. X
  705. X/*
  706. X *----------------------------------------------------------------------
  707. X *
  708. X * CleanUpContext
  709. X *     Release all resources allocated to the specified scan context
  710. X *     entry.  The entry itself is not released.
  711. X *----------------------------------------------------------------------
  712. X */
  713. Xstatic int
  714. XCleanUpContext (scanGlobPtr, contextPtr)
  715. X    scanGlob_pt    scanGlobPtr;
  716. X    scanContext_pt contextPtr;
  717. X{
  718. X    matchDef_pt  matchPtr, oldMatchPtr;
  719. X
  720. X    for (matchPtr = contextPtr->matchListHead; matchPtr != NULL;) {
  721. X        Tcl_RegExpClean (&matchPtr->regExpInfo);
  722. X        if (matchPtr->command != NULL)
  723. X            ckfree(matchPtr->command);
  724. X        oldMatchPtr = matchPtr;
  725. X        matchPtr = matchPtr->nextMatchDefPtr;
  726. X        ckfree ((char *) oldMatchPtr);
  727. X        }
  728. X    contextPtr->matchListHead = NULL;
  729. X    contextPtr->matchListTail = NULL;
  730. X
  731. X    if (contextPtr->defaultAction != NULL) {
  732. X        ckfree(contextPtr->defaultAction);
  733. X        contextPtr->defaultAction = NULL;
  734. X    }
  735. X}
  736. X
  737. X/*
  738. X *----------------------------------------------------------------------
  739. X *
  740. X * CreateScanContext --
  741. X *     Create a new scan context, implements the subcommand:
  742. X *         scancontext create
  743. X *
  744. X *----------------------------------------------------------------------
  745. X */
  746. Xstatic int
  747. XCreateScanContext (interp, scanGlobPtr)
  748. X    Tcl_Interp  *interp;
  749. X    scanGlob_pt  scanGlobPtr;
  750. X{
  751. X    scanContext_pt contextPtr;
  752. X
  753. X    contextPtr = (scanContext_pt)Tcl_HandleAlloc (scanGlobPtr->tblHdrPtr, 
  754. X                                                  scanGlobPtr->curName);
  755. X    contextPtr->flags = 0;
  756. X    contextPtr->matchListHead = NULL;
  757. X    contextPtr->matchListTail = NULL;
  758. X    contextPtr->defaultAction = NULL;
  759. X
  760. X    Tcl_SetResult (interp, scanGlobPtr->curName, TCL_STATIC);
  761. X    return TCL_OK;
  762. X}
  763. X
  764. X/*
  765. X *----------------------------------------------------------------------
  766. X *
  767. X * DeleteScanContext --
  768. X *     Deletes the specified scan context, implements the subcommand:
  769. X *         scancontext delete contexthandle
  770. X *
  771. X *----------------------------------------------------------------------
  772. X */
  773. Xstatic int
  774. XDeleteScanContext (interp, scanGlobPtr, contextHandle)
  775. X    Tcl_Interp  *interp;
  776. X    scanGlob_pt  scanGlobPtr;
  777. X    char        *contextHandle;
  778. X{
  779. X    scanContext_pt contextPtr;
  780. X
  781. X    if ((contextPtr = Tcl_HandleXlate (interp, scanGlobPtr->tblHdrPtr, 
  782. X                                       contextHandle)) == NULL)
  783. X        return TCL_ERROR;
  784. X
  785. X    CleanUpContext (scanGlobPtr, contextPtr);
  786. X    Tcl_HandleFree (scanGlobPtr->tblHdrPtr, contextPtr);
  787. X
  788. X    return TCL_OK;
  789. X}
  790. X
  791. X/*
  792. X *----------------------------------------------------------------------
  793. X *
  794. X * Tcl_ScancontextCmd --
  795. X *     Implements the TCL scancontext Tcl command, which has the 
  796. X *     following forms.
  797. X *         scancontext create
  798. X *         scancontext delete
  799. X *
  800. X * Results:
  801. X *    Standard TCL results.
  802. X *
  803. X *----------------------------------------------------------------------
  804. X */
  805. Xstatic int
  806. XTcl_ScancontextCmd (clientData, interp, argc, argv)
  807. X    char       *clientData;
  808. X    Tcl_Interp *interp;
  809. X    int         argc;
  810. X    char      **argv;
  811. X{
  812. X    scanGlob_pt  scanGlobPtr = (scanGlob_pt) clientData;
  813. X
  814. X    if (argc < 2) {
  815. X        Tcl_AppendResult (interp, "wrong # args: ", argv [0], " option",
  816. X                          (char *) NULL);
  817. X        return TCL_ERROR;
  818. X    }
  819. X    /*
  820. X     * Create a new scan context.
  821. X     */
  822. X    if (STREQU (argv [1], "create")) {
  823. X        if (argc != 2) {
  824. X            Tcl_AppendResult (interp, "wrong # args: ", argv [0], " create",
  825. X                              (char *) NULL);
  826. X            return TCL_ERROR;
  827. X        }
  828. X        return CreateScanContext (interp, scanGlobPtr);        
  829. X    }
  830. X    
  831. X    /*
  832. X     * Delete a scan context.
  833. X     */
  834. X    if (STREQU (argv [1], "delete")) {
  835. X        if (argc != 3) {
  836. X            Tcl_AppendResult (interp, "wrong # args: ", argv [0],
  837. X                              "delete contexthandle", (char *) NULL);
  838. X            return TCL_ERROR;
  839. X        }
  840. X        return DeleteScanContext (interp, scanGlobPtr, argv [2]);
  841. X    }
  842. X    
  843. X    Tcl_AppendResult (interp, "invalid argument, expected one of: ",
  844. X                      "create or delete", (char *) NULL);
  845. X    return TCL_ERROR;
  846. X}
  847. X
  848. X/*
  849. X *----------------------------------------------------------------------
  850. X *
  851. X * Tcl_ScanmatchCmd --
  852. X *     Implements the TCL command:
  853. X *         scanmatch [-nocase] contexthandle [regexp] commands
  854. X *     This uses both Boyer_Moore and regular expressions matching.
  855. X *
  856. X * Results:
  857. X *    Standard TCL results.
  858. X *
  859. X *----------------------------------------------------------------------
  860. X */
  861. Xstatic int
  862. XTcl_ScanmatchCmd (clientData, interp, argc, argv)
  863. X    char       *clientData;
  864. X    Tcl_Interp *interp;
  865. X    int         argc;
  866. X    char      **argv;
  867. X{
  868. X    scanGlob_pt     scanGlobPtr = (scanGlob_pt) clientData;
  869. X    scanContext_pt  contextPtr;
  870. X    char           *result;
  871. X    matchDef_pt     newmatch;
  872. X    int             compFlags = REXP_BOTH_ALGORITHMS;
  873. X    int             firstArg = 1;
  874. X
  875. X    if (argc < 3)
  876. X        goto argError;
  877. X    if (STREQU (argv[1], "-nocase")) {
  878. X        compFlags |= REXP_NO_CASE;
  879. X        firstArg = 2;
  880. X    }
  881. X      
  882. X    /*
  883. X     * If firstArg == 2 (-nocase), the both a regular expression and a command
  884. X     * string must be specified, otherwise the regular expression is optional.
  885. X     */
  886. X    if (((firstArg == 2) && (argc != 5)) || ((firstArg == 1) && (argc > 4)))
  887. X        goto argError;
  888. X
  889. X    if ((contextPtr = Tcl_HandleXlate (interp, scanGlobPtr->tblHdrPtr, 
  890. X                                       argv [firstArg])) == NULL)
  891. X        return TCL_ERROR;
  892. X
  893. X    /*
  894. X     * Handle the default case (no regular expression).
  895. X     */
  896. X    if (argc == 3) {
  897. X        if (contextPtr->defaultAction) {
  898. X            Tcl_AppendResult (interp, argv [0], ": default match already ",
  899. X                              "specified in this scan context", (char *) NULL);
  900. X            return TCL_ERROR;
  901. X        }
  902. X        contextPtr->defaultAction = ckalloc (strlen (argv [2]) + 1);
  903. X        strcpy (contextPtr->defaultAction, argv [2]);
  904. X
  905. X        return TCL_OK;
  906. X    }
  907. X
  908. X    /*
  909. X     * Add a regular expression to the context.
  910. X     */
  911. X
  912. X    newmatch = (matchDef_pt) ckalloc(sizeof (matchDef_t));
  913. X    newmatch->matchflags = 0;
  914. X
  915. X    if (compFlags & REXP_NO_CASE) {
  916. X        newmatch->matchflags |= MATCH_CASE_INSENSITIVE_FLAG;
  917. X        contextPtr->flags |= CONTEXT_A_CASE_INSENSITIVE_FLAG;
  918. X    }
  919. X
  920. X    if (Tcl_RegExpCompile (interp, &newmatch->regExpInfo, argv [firstArg + 1], 
  921. X                           compFlags) != TCL_OK) {
  922. X        ckfree ((char *) newmatch);
  923. X        return (TCL_ERROR);
  924. X    }
  925. X
  926. X    newmatch->command = ckalloc (strlen (argv[firstArg + 2]) + 1);
  927. X    strcpy(newmatch->command, argv [firstArg + 2]);
  928. X
  929. X    /*
  930. X     * Link in the new match.
  931. X     */
  932. X    newmatch->nextMatchDefPtr = NULL;
  933. X    if (contextPtr->matchListHead == NULL)
  934. X        contextPtr->matchListHead = newmatch;
  935. X    else
  936. X        contextPtr->matchListTail->nextMatchDefPtr = newmatch;
  937. X    contextPtr->matchListTail = newmatch;
  938. X
  939. X    return TCL_OK;
  940. X
  941. XargError:
  942. X    Tcl_AppendResult (interp, "wrong # args: ", argv [0],
  943. X                      " [-nocase] contexthandle [regexp] command",
  944. X                      (char *) NULL);
  945. X    return TCL_ERROR;
  946. X}
  947. X
  948. X/*
  949. X *----------------------------------------------------------------------
  950. X *
  951. X * SetMatchVar --
  952. X *     Sets the TCL array variable matchInfo to contain information 
  953. X *     about the line that is matched.
  954. X * Results:
  955. X *     TCL_OK if all is ok, TCL_ERROR if an error occures setting the
  956. X *     variables.
  957. X * Side effects:
  958. X *     A TCL array variable is created or altered.
  959. X * 
  960. X *----------------------------------------------------------------------
  961. X */
  962. Xstatic int
  963. XSetMatchVar (interp, fileLine, fileOffset, scanLineNum, fileHandle)
  964. X    Tcl_Interp *interp;
  965. X    char       *fileLine;
  966. X    long        fileOffset;
  967. X    long        scanLineNum;
  968. X    char       *fileHandle;
  969. X{
  970. X    char numBuf [20];
  971. X
  972. X    if (Tcl_SetVar2 (interp, "matchInfo", "line", fileLine, 
  973. X                     TCL_LEAVE_ERR_MSG) == NULL)
  974. X        return TCL_ERROR;
  975. X
  976. X    sprintf (numBuf, "%ld", fileOffset);
  977. X    if (Tcl_SetVar2 (interp, "matchInfo", "offset", numBuf,
  978. X                     TCL_LEAVE_ERR_MSG) == NULL)
  979. X        return TCL_ERROR;
  980. X
  981. X    sprintf (numBuf, "%ld", scanLineNum);
  982. X    if (Tcl_SetVar2 (interp, "matchInfo", "linenum", numBuf,
  983. X                     TCL_LEAVE_ERR_MSG) == NULL)
  984. X        return TCL_ERROR;
  985. X
  986. X    if (Tcl_SetVar2 (interp, "matchInfo", "handle", fileHandle, 
  987. X                     TCL_LEAVE_ERR_MSG) == NULL)
  988. X        return TCL_ERROR;
  989. X    return TCL_OK;
  990. X}
  991. X
  992. X/*
  993. X *----------------------------------------------------------------------
  994. X *
  995. X * Tcl_ScanfileCmd --
  996. X *     Implements the TCL command:
  997. X *         scanfile contexthandle filehandle
  998. X *
  999. X * Results:
  1000. X *    Standard TCL results.
  1001. X *
  1002. X *----------------------------------------------------------------------
  1003. X */
  1004. Xstatic int
  1005. XTcl_ScanfileCmd (clientData, interp, argc, argv)
  1006. X    char       *clientData;
  1007. X    Tcl_Interp *interp;
  1008. X    int         argc;
  1009. X    char      **argv;
  1010. X{
  1011. X    scanGlob_pt     scanGlobPtr = (scanGlob_pt) clientData;
  1012. X    scanContext_pt  contextPtr;
  1013. X    dynamicBuf_t    dynBuf, lowerDynBuf;
  1014. X    OpenFile       *filePtr;
  1015. X    matchDef_pt     matchPtr;
  1016. X    int             result;
  1017. X    int             matchedAtLeastOne;
  1018. X    long            fileOffset;
  1019. X    long            matchOffset;
  1020. X    long            scanLineNum = 0;
  1021. X    char           *fileHandle;
  1022. X
  1023. X    if ((argc < 2) || (argc > 3)) {
  1024. X        Tcl_AppendResult (interp, "wrong # args: ", argv [0], 
  1025. X                          " contexthandle filehandle", (char *) NULL);
  1026. X        return TCL_ERROR;
  1027. X    }
  1028. X    if ((contextPtr = Tcl_HandleXlate (interp, scanGlobPtr->tblHdrPtr, 
  1029. X                                       argv [1])) == NULL)
  1030. X        return TCL_ERROR;
  1031. X
  1032. X    if (TclGetOpenFile (interp, argv [2], &filePtr) != TCL_OK)
  1033. X            return TCL_ERROR;
  1034. X
  1035. X    if (contextPtr->matchListHead == NULL) {
  1036. X        Tcl_AppendResult (interp, "no patterns in current scan context",
  1037. X                          (char *) NULL);
  1038. X        return TCL_ERROR;
  1039. X    }
  1040. X
  1041. X    Tcl_DynBufInit (&dynBuf);
  1042. X    Tcl_DynBufInit (&lowerDynBuf);
  1043. X
  1044. X    result = TCL_OK;  /* Assume the best */
  1045. X
  1046. X    fileOffset = ftell (filePtr->f);  /* Get starting offset */
  1047. X
  1048. X    while ((result == TCL_OK)) {
  1049. X        int storedThisLine = FALSE;
  1050. X
  1051. X        switch (Tcl_DynamicFgets (&dynBuf, filePtr->f)) {
  1052. X            case -1:  /* Error */
  1053. X                Tcl_AppendResult (interp, argv [0], ": ", fileHandle, ": ",
  1054. X                                  Tcl_UnixError (interp), (char *) NULL);
  1055. X                goto scanExit;
  1056. X
  1057. X            case 0:  /* EOF */
  1058. X                goto scanExit;
  1059. X        }
  1060. X        scanLineNum++;
  1061. X        matchOffset = fileOffset;
  1062. X        fileOffset += strlen(dynBuf.ptr) + 1;
  1063. X        storedThisLine = 0;
  1064. X        matchedAtLeastOne = 0;
  1065. X        if (contextPtr->flags & CONTEXT_A_CASE_INSENSITIVE_FLAG) {
  1066. X            lowerDynBuf.used = 0;
  1067. X            Tcl_DynBufAppend (&lowerDynBuf, dynBuf.ptr);
  1068. X            Tcl_DownShift (lowerDynBuf.ptr, dynBuf.ptr);
  1069. X        }
  1070. X        for (matchPtr = contextPtr->matchListHead; matchPtr != NULL; 
  1071. X                 matchPtr = matchPtr->nextMatchDefPtr) {
  1072. X
  1073. X            if (!Tcl_RegExpExecute (interp, &matchPtr->regExpInfo, dynBuf.ptr, 
  1074. X                                    lowerDynBuf.ptr))
  1075. X                continue;  /* Try next match pattern */
  1076. X
  1077. X            matchedAtLeastOne = TRUE;
  1078. X            if (!storedThisLine) {
  1079. X                result = SetMatchVar (interp, dynBuf.ptr, matchOffset, 
  1080. X                                      scanLineNum, argv[2]);
  1081. X                if (result != TCL_OK)
  1082. X                    goto scanExit;
  1083. X                storedThisLine = TRUE;
  1084. X            }
  1085. X
  1086. X            result = Tcl_Eval(interp, matchPtr->command, 0, (char **)NULL);
  1087. X            if (result == TCL_ERROR) {
  1088. X                Tcl_AddErrorInfo (interp, 
  1089. X                    "\n    while executing a match command");
  1090. X                goto scanExit;
  1091. X            }
  1092. X            if (result == TCL_CONTINUE) {
  1093. X                /* 
  1094. X                 * Don't process any more matches for this line.
  1095. X                 */
  1096. X                result = TCL_OK;
  1097. X                goto matchLineExit;
  1098. X            }
  1099. X            if (result == TCL_BREAK) {
  1100. X                /*
  1101. X                 * Terminate scan.
  1102. X                 */
  1103. X                result = TCL_OK;
  1104. X                goto scanExit;
  1105. X            }
  1106. X        }
  1107. X
  1108. X        matchLineExit:
  1109. X        /*
  1110. X         * Process default action if required.
  1111. X         */
  1112. X        if ((contextPtr->defaultAction != NULL) && (!matchedAtLeastOne)) {
  1113. X
  1114. X            result = SetMatchVar (interp, dynBuf.ptr, matchOffset, 
  1115. X                                  scanLineNum, argv[2]);
  1116. X            if (result != TCL_OK)
  1117. X                goto scanExit;
  1118. X
  1119. X            result = Tcl_Eval (interp, contextPtr->defaultAction, 0, 
  1120. X                               (char **)NULL);
  1121. X            if (result == TCL_CONTINUE)
  1122. X                result = TCL_OK;    /* This doesn't mean anything, but  */
  1123. X                                    /* don't break the user.            */
  1124. X            if (result == TCL_ERROR)
  1125. X                Tcl_AddErrorInfo (interp, 
  1126. X                    "\n    while executing a match default command");
  1127. X        }
  1128. X    }
  1129. XscanExit:
  1130. X    Tcl_DynBufFree (&dynBuf);
  1131. X    Tcl_DynBufFree (&lowerDynBuf);
  1132. X    if (result == TCL_RETURN)
  1133. X        result = TCL_OK;
  1134. X    return result;
  1135. X}
  1136. X
  1137. X/*
  1138. X *----------------------------------------------------------------------
  1139. X *
  1140. X *  FileScanCleanUp --
  1141. X *      Decrements the use count on the globals when a command is deleted.
  1142. X *      If it goes to zero, all resources are released.      
  1143. X *
  1144. X *----------------------------------------------------------------------
  1145. X */
  1146. Xstatic void
  1147. XFileScanCleanUp (clientData)
  1148. X    ClientData clientData;
  1149. X{
  1150. X    scanGlob_pt    scanGlobPtr = (scanGlob_pt) clientData;
  1151. X    scanContext_pt contextPtr;
  1152. X    int            walkKey;
  1153. X    
  1154. X    scanGlobPtr->useCount--;
  1155. X    if (scanGlobPtr->useCount > 0)
  1156. X        return;
  1157. X
  1158. X    walkKey = -1;
  1159. X    while ((contextPtr = Tcl_HandleWalk (scanGlobPtr->tblHdrPtr, 
  1160. X            &walkKey)) != NULL)
  1161. X        CleanUpContext (scanGlobPtr, contextPtr);
  1162. X
  1163. X    Tcl_HandleTblRelease (scanGlobPtr->tblHdrPtr);
  1164. X    ckfree ((char *) scanGlobPtr);
  1165. X}
  1166. X
  1167. X/*
  1168. X *----------------------------------------------------------------------
  1169. X *
  1170. X *  Tcl_InitFilescan --
  1171. X *      Initialize the TCL file scanning facility..
  1172. X *
  1173. X *----------------------------------------------------------------------
  1174. X */
  1175. Xvoid
  1176. XTcl_InitFilescan (interp)
  1177. XTcl_Interp *interp;
  1178. X{
  1179. X    scanGlob_pt    scanGlobPtr;
  1180. X    void_pt        fileCbTblPtr;
  1181. X
  1182. X    scanGlobPtr = (scanGlob_pt) ckalloc (sizeof (scanGlob_t));
  1183. X    scanGlobPtr->tblHdrPtr = 
  1184. X        Tcl_HandleTblInit ("context", sizeof (scanContext_t), 5);
  1185. X
  1186. X    /*
  1187. X     * Initialize the commands.
  1188. X     */
  1189. X    scanGlobPtr->useCount = 3;  /* Number of commands */
  1190. X
  1191. X    Tcl_CreateCommand (interp, "scanfile", Tcl_ScanfileCmd, 
  1192. X                       (ClientData)scanGlobPtr, FileScanCleanUp);
  1193. X    Tcl_CreateCommand (interp, "scanmatch", Tcl_ScanmatchCmd, 
  1194. X                       (ClientData)scanGlobPtr, FileScanCleanUp);
  1195. X    Tcl_CreateCommand (interp, "scancontext", Tcl_ScancontextCmd,
  1196. X                       (ClientData)scanGlobPtr, FileScanCleanUp);
  1197. X}
  1198. X
  1199. END_OF_FILE
  1200. if test 17911 -ne `wc -c <'extended/src/filescan.c'`; then
  1201.     echo shar: \"'extended/src/filescan.c'\" unpacked with wrong size!
  1202. fi
  1203. # end of 'extended/src/filescan.c'
  1204. fi
  1205. echo shar: End of archive 18 \(of 23\).
  1206. cp /dev/null ark18isdone
  1207. MISSING=""
  1208. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ; do
  1209.     if test ! -f ark${I}isdone ; then
  1210.     MISSING="${MISSING} ${I}"
  1211.     fi
  1212. done
  1213. if test "${MISSING}" = "" ; then
  1214.     echo You have unpacked all 23 archives.
  1215.     echo "Now cd to "extended", edit the makefile, then do a "make""
  1216.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  1217. else
  1218.     echo You still need to unpack the following archives:
  1219.     echo "        " ${MISSING}
  1220. fi
  1221. ##  End of shell archive.
  1222. exit 0
  1223.  
  1224. exit 0 # Just in case...
  1225. -- 
  1226. Kent Landfield                   INTERNET: kent@sparky.IMD.Sterling.COM
  1227. Sterling Software, IMD           UUCP:     uunet!sparky!kent
  1228. Phone:    (402) 291-8300         FAX:      (402) 291-4362
  1229. Please send comp.sources.misc-related mail to kent@uunet.uu.net.
  1230.