home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 October / usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso / unix / volume27 / trn-3.3 / part05 / rt-util.c < prev    next >
C/C++ Source or Header  |  1993-11-27  |  13KB  |  583 lines

  1. /* $Id: rt-util.c,v 3.0 1992/12/14 00:14:12 davison Trn $
  2. */
  3. /* The authors make no claims as to the fitness or correctness of this software
  4.  * for any use whatsoever, and it is provided as is. Any use of this software
  5.  * is at the user's own risk. 
  6.  */
  7.  
  8. #include "EXTERN.h"
  9. #include "common.h"
  10. #include "cache.h"
  11. #include "ngdata.h"
  12. #include "artio.h"
  13. #include "rthread.h"
  14. #include "rt-select.h"
  15. #include "term.h"
  16. #include "INTERN.h"
  17. #include "rt-util.h"
  18.  
  19. /* Name-munging routines written by Ross Ridge.
  20. ** Enhanced by Wayne Davison.
  21. */
  22.  
  23. /* Extract the full-name part of an email address, returning NULL if not
  24. ** found.
  25. */
  26. char *
  27. extract_name(name)
  28. char *name;
  29. {
  30.     char *s;
  31.     char *lparen, *rparen;
  32.     char *langle;
  33.  
  34.     while (isspace(*name)) {
  35.     name++;
  36.     }
  37.  
  38.     lparen = index(name, '(');
  39.     rparen = rindex(name, ')');
  40.     langle = index(name, '<');
  41.     if (!lparen && !langle) {
  42.     return NULL;
  43.     } else
  44.     if (langle && (!lparen || !rparen || lparen > langle || rparen < langle)) {
  45.     if (langle == name) {
  46.         return NULL;
  47.     }
  48.     *langle = '\0';
  49.     } else {
  50.     name = lparen;
  51.     *name++ = '\0';
  52.     while (isspace(*name)) {
  53.         name++;
  54.     }
  55.     if (name == rparen) {
  56.         return NULL;
  57.     }
  58.     if (rparen != NULL) {
  59.         *rparen = '\0';
  60.     }
  61.     }
  62.  
  63.     if (*name == '"') {
  64.     name++;
  65.     while (isspace(*name)) {
  66.         name++;
  67.     }
  68.     if ((s = rindex(name, '"')) != NULL) {
  69.         *s = '\0';
  70.     }
  71.     }
  72.     return name;
  73. }
  74.  
  75. /* If necessary, compress a net user's full name by playing games with
  76. ** initials and the middle name(s).  If we start with "Ross Douglas Ridge"
  77. ** we try "Ross D Ridge", "Ross Ridge", "R D Ridge" and finally "R Ridge"
  78. ** before simply truncating the thing.  We also turn "R. Douglas Ridge"
  79. ** into "Douglas Ridge" and "Ross Ridge D.D.S." into "Ross Ridge" as a
  80. ** first step of the compaction, if needed.
  81. */
  82. char *
  83. compress_name(name, max)
  84. char *name;
  85. int max;
  86. {
  87.     register char *s, *last, *mid, *d;
  88.     register int len, namelen, midlen;
  89.     int notlast;
  90.  
  91.     /* First remove white space from both ends. */
  92.     while (isspace(*name)) {
  93.     name++;
  94.     }
  95.     if ((len = strlen(name)) == 0) {
  96.     return name;
  97.     }
  98.     s = name + len - 1;
  99.     while (isspace(*s)) {
  100.     s--;
  101.     }
  102.     s[1] = '\0';
  103.     if (s - name + 1 <= max) {
  104.     return name;
  105.     }
  106.  
  107.     /* Look for characters that likely mean the end of the name
  108.     ** and the start of some hopefully uninteresting additional info.
  109.     ** Spliting at a comma is somewhat questionalble, but since
  110.     ** "Ross Ridge, The Great HTMU" comes up much more often than 
  111.     ** "Ridge, Ross" and since "R HTMU" is worse than "Ridge" we do
  112.     ** it anyways.
  113.     */
  114.     for (d = name + 1; *d; d++) {
  115.     if (*d == ',' || *d == ';' || *d == '(' || *d == '@'
  116.      || (*d == '-' && (d[1] == '-' || d[1] == ' '))) {
  117.         *d-- = '\0';
  118.         s = d;
  119.         break;
  120.     }
  121.     }
  122.  
  123.     /* Find the last name */
  124.     do {
  125.     notlast = 0;
  126.     while (isspace(*s)) {
  127.         s--;
  128.     }
  129.     s[1] = '\0';
  130.     len = s - name + 1;
  131.     if (len <= max) {
  132.         return name;
  133.     }
  134.     /* If the last name is an abbreviation it's not the one we want. */
  135.     if (*s == '.')
  136.         notlast = 1;
  137.     while (!isspace(*s)) {
  138.         if (s == name) {    /* only one name */
  139.         name[max] = '\0';
  140.         return name;
  141.         }
  142.         if (isdigit(*s)) {    /* probably a phone number */
  143.         notlast = 1;    /* so chuck it */
  144.         }
  145.         s--;
  146.     }
  147.     } while (notlast);
  148.  
  149.     last = s-- + 1;
  150.  
  151.     /* Look for a middle name */
  152.     while (isspace(*s)) {    /* get rid of any extra space */
  153.     len--;    
  154.     s--;
  155.     }
  156.     mid = name;
  157.     while (!isspace(*mid)) {
  158.     mid++;
  159.     }
  160.     namelen = mid - name + 1;
  161.     if (mid == s+1) {    /* no middle name */
  162.     mid = 0;
  163.     } else {
  164.     *mid++ = '\0';
  165.     while (isspace(*mid)) {
  166.         len--;
  167.         mid++;
  168.     }
  169.     midlen = s - mid + 2;
  170.     /* If first name is an initial and middle isn't and it all fits
  171.     ** without the first initial, drop it. */
  172.     if (len > max && mid != s && mid[1] != '.'
  173.      && (!name[1] || (name[1] == '.' && !name[2]))
  174.      && len - namelen <= max) {
  175.         len -= namelen;
  176.         name = mid;
  177.         mid = 0;
  178.     }
  179.     }
  180.     s[1] = '\0';
  181.     if (mid && len > max) {
  182.     /* Turn middle names into intials */
  183.     len -= s - mid + 2;
  184.     d = s = mid;
  185.     while (*s) {
  186.         if (isalpha(*s)) {
  187.         if (d != mid) {
  188.             *d++ = ' ';
  189.         }
  190.         *d++ = *s++;
  191.         }
  192.         while (*s && !isspace(*s)) {
  193.         s++;
  194.         }
  195.         while (isspace(*s)) {
  196.         s++;
  197.         }
  198.     }
  199.     if (d != mid) {
  200.         *d = '\0';
  201.         midlen = d - mid + 1;
  202.         len += midlen;
  203.     } else {
  204.         mid = 0;
  205.     }
  206.     }
  207.     if (len > max) {
  208.     /* If the first name fits without the middle initials, drop them */
  209.     if (mid && len - midlen <= max) {
  210.         len -= midlen;
  211.         mid = 0;
  212.     } else {
  213.         /* Turn the first name into an initial */
  214.         len -= namelen - 2;
  215.         name[1] = '\0';
  216.         namelen = 2;
  217.         if (len > max) {
  218.         /* Dump the middle initials (if present) */
  219.         if (mid) {
  220.             len -= midlen;
  221.             mid = 0;
  222.         }
  223.         if (len > max) {
  224.             /* Finally just truncate the last name */
  225.             last[max - 2] = '\0';
  226.         }
  227.         }
  228.     }
  229.     }
  230.  
  231.     /* Paste the names back together */
  232.     d = name + namelen;
  233.     if (mid) {
  234.     d[-1] = ' ';
  235.     strcpy(d, mid);
  236.     d += midlen;
  237.     }
  238.     d[-1] = ' ';
  239.     strcpy(d, last);
  240.     return name;
  241. }
  242.  
  243. /* Compress an email address, trying to keep as much of the local part of
  244. ** the addresses as possible.  The order of precence is @ ! %, but
  245. ** @ % ! may be better...
  246. */
  247. static char *
  248. compress_address(name, max)
  249. char *name;
  250. int max;
  251. {
  252.     char *s, *at, *bang, *hack, *start;
  253.     int len;
  254.  
  255.     /* Remove white space from both ends. */
  256.     while (isspace(*name)) {
  257.     name++;
  258.     }
  259.     if ((len = strlen(name)) == 0) {
  260.     return name;
  261.     }
  262.     s = name + len - 1;
  263.     while (isspace(*s)) {
  264.     s--;
  265.     }
  266.     s[1] = '\0';
  267.     if (*name == '<') {
  268.     name++;
  269.     if (*s == '>') {
  270.         *s-- = '\0';
  271.     }
  272.     }
  273.     if ((len = s - name + 1) <= max) {
  274.     return name;
  275.     }
  276.  
  277.     at = bang = hack = NULL;
  278.     for (s = name + 1; *s; s++) {
  279.     /* If there's whitespace in the middle then it's probably not
  280.     ** really an email address. */
  281.     if (isspace(*s)) {
  282.         name[max] = '\0';
  283.         return name;
  284.     }
  285.     switch (*s) {
  286.     case '@':
  287.         if (at == NULL) {
  288.         at = s;
  289.         }
  290.         break;
  291.     case '!':
  292.         if (at == NULL) {
  293.         bang = s;
  294.         hack = NULL;
  295.         }
  296.         break;
  297.     case '%':
  298.         if (at == NULL && hack == NULL) {
  299.         hack = s;
  300.         }
  301.         break;
  302.     }
  303.     }
  304.     if (at == NULL) {
  305.     at = name + len;
  306.     }
  307.  
  308.     if (hack != NULL) {
  309.     if (bang != NULL) {
  310.         if (at - bang - 1 >= max) {
  311.         start = bang + 1;
  312.         } else if (at - name >= max) {
  313.         start = at - max;
  314.         } else {
  315.         start = name;
  316.         }
  317.     } else {
  318.         start = name;
  319.     }
  320.     } else if (bang != NULL) {
  321.     if (at - name >= max) {
  322.         start = at - max;
  323.     } else {
  324.         start = name;
  325.     }
  326.     } else {
  327.     start = name;
  328.     }
  329.     if (len - (start - name) > max) {
  330.     start[max] = '\0';
  331.     }
  332.     return start;
  333. }
  334.  
  335. /* Fit the author name in <max> chars.  Uses the comment portion if present
  336. ** and pads with spaces.
  337. */
  338. char *
  339. compress_from(ap, size)
  340. ARTICLE *ap;
  341. int size;
  342. {
  343.     char *s, *t;
  344.     int len;
  345.  
  346.     for (t = cmd_buf, s = ap && ap->from? ap->from : nullstr; *s; ) {
  347.     if ((unsigned char)*s < ' ')
  348.         *t++ = ' ', s++;
  349.     else
  350.         *t++ = *s++;
  351.     }
  352.     *t = '\0';
  353.     if ((s = extract_name(cmd_buf)) != NULL)
  354.     s = compress_name(s, size);
  355.     else
  356.     s = compress_address(cmd_buf, size);
  357.     len = strlen(s);
  358.     if (!len) {
  359.     strcpy(s,"NO NAME");
  360.     len = 7;
  361.     }
  362.     while (len < size)
  363.     s[len++] = ' ';
  364.     s[size] = '\0';
  365.     return s;
  366. }
  367.  
  368. #define EQ(x,y) ((isupper(x) ? tolower(x) : (x)) == (y))
  369.  
  370. /* Parse the subject to skip past any "Re[:^]"s at the start.
  371. */
  372. char *
  373. get_subject_start(str)
  374. register char *str;
  375. {
  376.     while (*str && (unsigned char)*str <= ' ')
  377.     str++;
  378.     while (EQ(str[0], 'r') && EQ(str[1], 'e')) {    /* check for Re: */
  379.       register char *cp = str + 2;
  380.     if (*cp == '^') {                /* allow Re^2: */
  381.         while (*++cp <= '9' && *cp >= '0')
  382.         ;
  383.     }
  384.     if (*cp != ':')
  385.         break;
  386.     while (*++cp == ' ')
  387.         ;
  388.     str = cp;
  389.     }
  390.     return str;
  391. }
  392.  
  393. /* Output a subject in <max> chars.  Does intelligent trimming that tries to
  394. ** save the last two words on the line, excluding "(was: blah)" if needed.
  395. */
  396. char *
  397. compress_subj(ap, max)
  398. ARTICLE *ap;
  399. int max;
  400. {
  401.     register char *cp;
  402.     register int len;
  403.     ARTICLE *first;
  404.  
  405.     if (!ap)
  406.     return "<MISSING>";
  407.  
  408.     /* Put a preceeding '>' on subjects that are replies to other articles */
  409.     cp = buf;
  410.     first = (ThreadedGroup? ap->subj->thread : ap->subj->articles);
  411.     if (ap != first || (ap->flags & AF_HAS_RE)
  412.      || !(!(ap->flags&AF_READ) ^ sel_rereading))
  413.     *cp++ = '>';
  414.     strcpy(cp, ap->subj->str + 4);
  415.  
  416.     /* Remove "(was: oldsubject)", because we already know the old subjects.
  417.     ** Also match "(Re: oldsubject)".  Allow possible spaces after the ('s.
  418.     */
  419.     for (cp = buf; (cp = index(cp+1, '(')) != Nullch;) {
  420.     while (*++cp == ' ')
  421.         ;
  422.     if (EQ(cp[0], 'w') && EQ(cp[1], 'a') && EQ(cp[2], 's')
  423.      && (cp[3] == ':' || cp[3] == ' ')) {
  424.         *--cp = '\0';
  425.         break;
  426.     }
  427.     if (EQ(cp[0], 'r') && EQ(cp[1], 'e')
  428.      && ((cp[2]==':' && cp[3]==' ') || (cp[2]=='^' && cp[4]==':'))) {
  429.         *--cp = '\0';
  430.         break;
  431.     }
  432.     }
  433.     len = strlen(buf);
  434.     if (!unbroken_subjects && len > max) {
  435.     char *last_word;
  436.     /* Try to include the last two words on the line while trimming */ 
  437.     if ((last_word = rindex(buf, ' ')) != Nullch) {
  438.         char *next_to_last;
  439.         *last_word = '\0';
  440.         if ((next_to_last = rindex(buf, ' ')) != Nullch) {
  441.         if (next_to_last-buf >= len - max + 3 + 10-1)
  442.             cp = next_to_last;
  443.         else
  444.             cp = last_word;
  445.         } else
  446.         cp = last_word;
  447.         *last_word = ' ';
  448.         if (cp-buf >= len - max + 3 + 10-1) {
  449.         sprintf(buf + max - (len-(cp-buf)+3), "...%s", cp + 1);
  450.         len = max;
  451.         }
  452.     }
  453.     }
  454.     if (len > max)
  455.     buf[max] = '\0';
  456.     return buf;
  457. }
  458.  
  459. #ifndef HAS_STRCASECMP
  460. static unsigned char casemap[256] = {
  461.     0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,
  462.     0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,
  463.     0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,
  464.     0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,
  465.     0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
  466.     0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,
  467.     0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,
  468.     0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F,
  469.     0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
  470.     0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
  471.     0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
  472.     0x78,0x79,0x7A,0x7B,0x5C,0x5D,0x5E,0x5F,
  473.     0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,
  474.     0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F,
  475.     0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,
  476.     0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F,
  477.     0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
  478.     0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F,
  479.     0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
  480.     0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F,
  481.     0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,
  482.     0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,
  483.     0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,
  484.     0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF,
  485.     0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,
  486.     0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF,
  487.     0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,
  488.     0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF,
  489.     0xE0,0xE1,0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,
  490.     0xE8,0xE9,0xEA,0xEB,0xEC,0xED,0xEE,0xEF,
  491.     0xF0,0xF1,0xF2,0xF3,0xF4,0xF5,0xF6,0xF7,
  492.     0xF8,0xF9,0xFA,0xFB,0xFC,0xFD,0xFE,0xFF
  493. };
  494.  
  495. int
  496. strcasecmp(s1, s2)
  497. register char *s1, *s2;
  498. {
  499.     do {
  500.     if (casemap[(unsigned)*s1++] != casemap[(unsigned)*s2])
  501.         return casemap[(unsigned)s1[-1]] - casemap[(unsigned)*s2];
  502.     } while (*s2++ != '\0');
  503.     return 0;
  504. }
  505.  
  506. int
  507. strncasecmp(s1, s2, len)
  508. register char *s1, *s2;
  509. register int len;
  510. {
  511.     while (len--) {
  512.     if (casemap[(unsigned)*s1++] != casemap[(unsigned)*s2])
  513.         return casemap[(unsigned)s1[-1]] - casemap[(unsigned)*s2];
  514.     if (*s2++ == '\0')
  515.         break;
  516.     }
  517.     return 0;
  518. }
  519. #endif
  520.  
  521. /* Modified version of a spinner originally found in Clifford Adams' strn. */
  522.  
  523. static char spinchars[] = {'|','/','-','\\'};
  524. static int spin_place;        /* represents place in spinchars array */
  525. static int spin_count;        /* counter for when to spin */
  526. static int spin_level INIT(0);    /* used to allow non-interfering nested spins */
  527. static int spin_mode;
  528. static ART_NUM spin_art;
  529. static ART_POS spin_tell;
  530.  
  531. void
  532. setspin(mode)
  533. int mode;
  534. {
  535.     switch (mode) {
  536.     case SPIN_FOREGROUND:
  537.     case SPIN_BACKGROUND:
  538.     if (!spin_level++) {
  539.         if ((spin_art = openart) != 0)
  540.         spin_tell = ftell(artfp);
  541.         spin_count = 1;    /* not 0 to prevent immediate spin display */
  542.         spin_place = 1;    /* start with slash... */
  543.     }
  544.     spin_mode = mode;
  545.     break;
  546.     case SPIN_POP:
  547.     if (--spin_level > 0)
  548.         break;
  549.     /* FALL THROUGH */
  550.     case SPIN_OFF:
  551.     spin_level = 0;
  552.     if (spin_place > 1) {    /* we have spun at least once */
  553.         putchar(spin_char); /* get rid of spin character */
  554.         backspace();
  555.         fflush(stdout);
  556.         spin_place = 0;
  557.     }
  558.     if (spin_art) {
  559.         artopen(spin_art);
  560.         fseek(artfp,spin_tell,0);    /* do not screw up the pager */
  561.         spin_art = 0;
  562.     }
  563.     break;
  564.     }
  565. }
  566.  
  567. void
  568. spin(count)
  569. int count;        /* modulus for the spin... */
  570. {
  571.     if (!spin_level || (!bkgnd_spinner && spin_mode == SPIN_BACKGROUND))
  572.     return;
  573.     if (!(spin_count++%count)) {
  574.     if (spin_mode == SPIN_FOREGROUND)
  575.         putchar('.');
  576.     else {
  577.         putchar(spinchars[spin_place++%4]);
  578.         backspace();
  579.     }
  580.     fflush(stdout);
  581.     }
  582. }
  583.