home *** CD-ROM | disk | FTP | other *** search
/ Almathera Ten Pack 3: CDPD 3 / Almathera Ten on Ten - Disc 3: CDPD3.iso / scope / 101-125 / scopedisk106 / bbs-index / src / format.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-03-19  |  32.3 KB  |  688 lines

  1. /*
  2.  *     FORMAT.C
  3.  *
  4.  *     This is a general purpose format routine for BBSINDEX. It takes
  5.  *     as parameters a format string and a pointer to a file header and creates
  6.  *     a string, based on the format string, but containing information from
  7.  *     the file header.
  8.  *
  9.  *  The format string is similar to a printf format string. The following
  10.  *  special sequences are translated into values associated with the
  11.  *     fileheader.
  12.  *
  13.  *             %a              Number of accesses
  14.  *             %b{}    File type {Binary,Text}
  15.  *             %c              Comment
  16.  *             %d              Disk filename
  17.  *             %f              Full name of disk file
  18.  *             %i{}    File status {Online,Offline}
  19.  *             %k              File size in K
  20.  *             %l{}    File origin {Local,Remote}
  21.  *             %n              Filename
  22.  *             %o              Uploader's name (owner)
  23.  *             %p              Path name of disk file
  24.  *             %r              Directory number
  25.  *             %s              Section number or letter
  26.  *             %ux             Output x n times, where n is length of last {sub}format cmd
  27.  *             %v{}    File contents {Valid,Invalid}
  28.  *             %w              Upload date (when)
  29.  *             %x              File size
  30.  *             %y              Disk directory number
  31.  *             %{...}  Format substring in { }
  32.  *     
  33.  *     The following escape sequences are also recognised:
  34.  *     
  35.  *             \n              - End of line
  36.  *             \e              - Escape
  37.  *             \E              - CSI (0x9b)
  38.  *             \t              - Tab
  39.  *             \nnn    - Octal number with the appropriate value
  40.  *     
  41.  *     Any other characters after a \ are treated as normal. This can be used
  42.  *     to escape certain characters that have special meaning, such as \ itself,
  43.  *     %, and sometimes {, }, and comma.
  44.  *     
  45.  *     With the % flags, additional characters may appear between the % and the
  46.  *     value. Using %c as an example, %20c means make the comment 20 characters
  47.  *     wide, padding with spaces as necessary. The comment is aligned to the
  48.  *     left. %-20c is similar, but aligns the comment to the right. This can
  49.  *     be useful for padding. Items followed by {} are boolean flags, which
  50.  *     can be either true or false. Inside the {} are two items, seperated by
  51.  *     a comma. If the flag is true, the first value is used, else the second
  52.  *     value. The expressions inside {} may not include %.
  53.  *
  54.  *     The exception to this is %m, which can contain a complete format
  55.  *     string within the {}. In this case, the substring is formatted as
  56.  *     normal, and then the resulting string is formatted according to
  57.  *     the parameters between the % and m. For example %10m{(%s,%d)} formats
  58.  *     the section and directory number in brackets, while ensuring that
  59.  *     the total field width is 10 characters.
  60.  *
  61.  *     n.b. A file is said to be valid if its actual filesize is equivalent
  62.  *  to the filesize in the file catalogue.
  63.  *
  64.  */
  65.  
  66. #ifndef LATTICE_50
  67. #include "system.h"
  68. #endif
  69.  
  70. #include "bbsindex.h"
  71.  
  72. #define LEFT   0               /* Left alignment                               */
  73. #define RIGHT  1               /* Right alignment                              */
  74. #define MAXPATH        256             /* Maximum length of disk path  */
  75.  
  76. #define MIN(a,b) ((a) > (b) ? (b) : (a))
  77.  
  78. #define        LEFTBR  '{'             /* Left bracket                                 */
  79. #define RIGHTBR        '}'             /* Right bracket                                */
  80. #define BACKSLASH '\\' /* Backslash                                    */
  81.  
  82. static int len;                        /* Length of format option                      */
  83. static int align;              /* Text alignment - LEFT or RIGHT       */
  84. static int opos;               /* Position in output string            */
  85. static int omaxlen;            /* Maximum length of output string      */
  86.  
  87. static char buf[256];
  88.  
  89. static char *sectnums[] = {
  90.        "0", "1", "2", "3", "4", "5", "6", "7",
  91.        "8", "9", "A", "B", "C", "D", "E", "F"
  92. };
  93.  
  94. static char *days[] = {
  95.        "Sunday",       "Monday",       "Tuesday",      "Wednesday",
  96.        "Thursday",     "Friday",       "Saturday"
  97. };
  98.  
  99. /*
  100.  *             doescape()
  101.  *             ----------
  102.  *             This function parses the escape sequence passed as a string,
  103.  *             and stores the character it corresponds to in the output character.
  104.  *             it returns the number of characters taken up by the escape sequence.
  105.  */
  106. int doescape(out, seq)
  107. unsigned char *out;
  108. unsigned char *seq;
  109. {
  110.        int num = 0;
  111.        char *p;
  112.  
  113.        if (*seq >= '0' && *seq <= '7') {
  114.                p = seq;
  115.                for (p = seq; *p >= '0' && *p <= '7'; p++)
  116.                        num = (num * 8) + (*seq - '0');
  117.                *out = num;
  118.                return (p-seq);
  119.        }
  120.        switch (*seq) {
  121.  
  122.                case 't':
  123.                        *out = '\t';
  124.                        break;
  125.  
  126.                case 'n':
  127.                        *out = '\n';
  128.                        break;
  129.  
  130.                case 'e':
  131.                        *out = '\033';
  132.                        break;
  133.  
  134.                case 'E':
  135.                        *out = '\233';
  136.                        break;
  137.  
  138.                default:
  139.                        *out = *seq;
  140.                        break;
  141.        }
  142.        return (1);
  143. }
  144.  
  145.  
  146. /*
  147.  *             makedate()
  148.  *             ----------
  149.  *             This function takes the passed day, month and year and returns
  150.  *             a pointer to a string containing dd-mmm-yy (e.g. 23-Mar-89).
  151.  */
  152.  
  153. #define ITOA(c1,c2,x) ((c1) = ((x)/10 + '0'),(c2) = (((x) % 10) + '0'))
  154.  
  155. char *makedate(day, month, year)
  156. int day, month, year;
  157. {
  158.        static char buf[10];
  159.  
  160.        ITOA(buf[0], buf[1], day);
  161.        buf[3] = months[month][0];
  162.        buf[4] = months[month][1];
  163.        buf[5] = months[month][2];
  164.        ITOA(buf[7], buf[8], year);
  165.        buf[2] = '-';
  166.        buf[6] = '-';
  167.        buf[9] = CHAR_NULL;
  168.        return (buf);
  169. }
  170.  
  171. /*
  172.  *             maketime()
  173.  *             ----------
  174.  *             Returns a pointer to a string containing the specified time, in
  175.  *             the form HH:MM:SS.
  176.  */
  177. char *maketime(secs, mins, hours)
  178. int secs, mins, hours;
  179. {
  180.        static char buf[9];
  181.        ITOA(buf[0], buf[1], hours);
  182.        ITOA(buf[3], buf[4], mins);
  183.        ITOA(buf[6], buf[7], secs);
  184.        buf[2] = ':';
  185.        buf[5] = ':';
  186.        buf[8] = CHAR_NULL;
  187.        return (buf);
  188. }
  189.  
  190. /*
  191.  *             itoa()
  192.  *             ------
  193.  *             This function returns a pointer to a string containing the ascii
  194.  *             representation of the number. The pointer is valid until the next
  195.  *             time itoa() is called. Negative numbers are not handled.
  196.  */
  197. char *itoa(n)
  198. int n;
  199. {
  200.        static char buf[20];
  201.        int i = 18;
  202.  
  203.        buf[19] = CHAR_NULL;
  204.        if (n == 0) {
  205.                buf[18] = '0';
  206.                return (buf + 18);
  207.        } else {
  208.                for ( ; n && (i > 0); i--) {
  209.                        buf[i] = (n % 10) + '0';
  210.                        n = n / 10;
  211.                }
  212.        }
  213.        return (buf+i+1);
  214. }
  215.  
  216.  
  217. /*
  218.  *             addstring()
  219.  *             -----------
  220.  *             This functions adds string 's' to string 'out' starting at position
  221.  *             (global) opos. If (global) len == 0, then no special formatting is
  222.  *             done. Else, the string is formatted in a field of width 'len'
  223.  *             characters, truncating or padding with spaces as appropriate. If
  224.  *             (global) align == LEFT, then the string is aligned to the left
  225.  *             of the field, else to the right.
  226.  *
  227.  *             If opos exceeds omaxlen at any point, then no more copying is done.
  228.  */
  229.  
  230. void addstring(out, s)
  231. char *out;
  232. char *s;
  233. {
  234.        int oleft = omaxlen - opos;     /* Number of chars left in output string */
  235.        int slen = strlen(s);
  236.  
  237.        /* Note: Be VERY careful the following hasn't got changed into tabs! */
  238.        static char pad[] = "\
  239.                                                                           \
  240.                                                                           \
  241.                                                                           ";
  242.        /* End of space definition! There should be ~200 spaces */
  243.  
  244.        if (len == 0) {                         /* No special alignment needed */
  245.                if (oleft > 0)
  246.                        strncpy(out+opos,s,MIN(slen,oleft));
  247.                opos = opos + slen;
  248.        } else {                                        /* Do special alignment */
  249.                if (slen > len)
  250.                        strncpy(out+opos, s, len);
  251.                else {
  252.                        if (align == LEFT) {
  253.                                if (oleft > 0)
  254.                                        /* Copy string into left of field */
  255.                                        strncpy(out+opos, s, MIN(slen,oleft));
  256.                                oleft = oleft - slen;
  257.                                if (oleft > 0 && len > slen)
  258.                                        /* Copy padding in to rest of field */
  259.                                        strncpy(out+opos+slen, pad, MIN(len-slen, oleft));
  260.                        } else {        /* Align == RIGHT */
  261.                                if (len > slen)
  262.                                        /* Copy padding into left of field */
  263.                                        strncpy(out+opos, pad, MIN(oleft, len-slen));
  264.                                if (oleft > 0)
  265.                                        /* Copy string into right of field */
  266.                                        strncpy(out+opos+(len-slen), s, MIN(slen,oleft-slen));
  267.                        }
  268.                }
  269.                opos = opos + len;
  270.        }
  271. }
  272.  
  273. /*
  274.  *             addnumber()
  275.  *             -----------
  276.  *             This macro adds the ascii representation of the number n
  277.  *             to the output buffer 'out', starting at position opos (global).
  278.  *             See addstring() for more details.
  279.  */
  280.  
  281. #define addnumber(out,n) addstring((out),itoa(n))
  282.  
  283. /*
  284.  *             format()
  285.  *             --------
  286.  *             This function converts the format string f and the file header into
  287.  *             an output string. See above for valid options in the format string.
  288.  *             A pointer to the output string is returned. Maxlen is the maximum
  289.  *             length of the output string. Checkfiles is a boolean which is true
  290.  *             if the dirnum, online and valid fields in the file header are valid.
  291.  *             These are normally NOT valid, unless requested by the user.
  292.  *
  293.  *             The external variable dirnames is expected to exist. Dirnames is
  294.  *             an array of disk dirctories (NOT BBS file directories).
  295.  */
  296.  
  297. char *format(out, maxlen, f, fhead, checkfiles)
  298. char *out;
  299. int maxlen;
  300. char *f;
  301. UDHEAD *fhead;
  302. int checkfiles;
  303. {
  304.        char subformat[MAXSUB];
  305.        static int lastlen;                     /* Length of last {sub}format string    */
  306.        int fpos = 0;                           /* Position in format string                    */
  307.        int flen = strlen(f);
  308.        int bool, numchars;
  309.        char *p, *s, *rightbr;
  310.  
  311.        omaxlen = maxlen-1;     /* Make this global for convenience */
  312.        opos = 0;
  313.  
  314.        for (fpos = 0; fpos < flen && opos < maxlen; fpos++) {
  315.                if (f[fpos] == BACKSLASH) {
  316.                        fpos += doescape(out+opos, f+fpos+1);
  317.                        opos++;
  318.                } else if (f[fpos] != '%')
  319.                        out[opos++] = f[fpos];
  320.                else {
  321.                        align = LEFT;
  322.                        if (f[++fpos] == '-') {
  323.                                align = RIGHT;
  324.                                fpos++;
  325.                        }
  326.                        len = 0;
  327.                        while (isdigit(f[fpos]))
  328.                                len = (len * 10) + f[fpos++] - '0';
  329.  
  330.                        switch (f[fpos]) {
  331.  
  332.                                case 'a':                       /* Number of file accesses */
  333.                                        addnumber(out,fhead->accesses);
  334.                                        break;
  335.  
  336.                                case 'c':                       /* File comment */
  337.                                        addstring(out,fhead->desc);
  338.                                        break;
  339.  
  340.                                case 'd':
  341.                                        addstring(out,fhead->disk_name);
  342.                                        break;
  343.  
  344.                                case 'f':
  345.                                        if (checkfiles) {
  346.                                                *buf = CHAR_NULL;
  347.                                                if (fhead->online) {
  348.                                                        char ch;
  349.                                                        strcat(buf, dirnames[fhead->dirnum]);
  350.                                                        ch = buf[strlen(buf)-1];
  351.                                                        if (ch != '/' && ch != ':')
  352.                                                                strcat(buf, "/");
  353.                                                }
  354.                                                strcat(buf,fhead->disk_name);
  355.                                                addstring(out,buf);
  356.                                        } else
  357.                                                addstring(out,fhead->disk_name); /* Maintain formatting */
  358.                                        break;
  359.  
  360.                                /*
  361.                                 *              Note that all the boolean-related options are grouped
  362.                                 *              together, so that they can have common error handling.
  363.                                 *              A bit nasty perhaps, but it works :-)
  364.                                 *
  365.                                 *              Note that 'i' and 'v' default to being online and
  366.                                 *              valid respectively, if checkfiles hasn't been
  367.                                 *              selected.
  368.                                 */
  369.  
  370.                                case 'b':               /* True if file is binary                       */
  371.                                case 'i':               /* True if file is online                       */
  372.                                case 'l':               /* True if file uploaded locally        */
  373.                                case 'v':               /* True if file is valid                        */
  374.  
  375.                                        switch (f[fpos]) {
  376.                                                case 'b':       bool = fhead->bin;
  377.                                                                        break;
  378.                                                case 'i':       bool = checkfiles ? fhead->online : 0; 
  379.                                                                        break;
  380.                                                case 'l':       bool = fhead->local;
  381.                                                                        break;
  382.                                                case 'v':       bool = checkfiles ? fhead->valid : 0;
  383.                                                                        break;
  384.                                        }
  385.                                        /* Make sure { and } surround arguments */
  386.                                        rightbr = strchr(f+fpos, RIGHTBR);
  387.                                        if (f[fpos+1] == LEFTBR && rightbr != NULL) {
  388.                                                p = f + (fpos+2);
  389.                                                s = buf;
  390.  
  391.                                                if (bool) {             /* Use first argument */
  392.                                                        /* Copy until comma reached */
  393.                                                        /* Handle any escaped characters (\n, \t etc) */
  394.                                                        while (*p && *p != ',' && *p != RIGHTBR) {
  395.                                                                if (*p == BACKSLASH) {
  396.                                                                        p = p + doescape(s++, p+1) + 1;
  397.                                                                } else
  398.                                                                        *s++ = *p++;
  399.                                                        }
  400.                                                } else {
  401.                                                        /* As above, but copy second argument */
  402.                                                        p = strchr(p,',');
  403.                                                        if (p) {
  404.                                                                p++;
  405.                                                                while (*p && *p != RIGHTBR) {
  406.                                                                        if (*p == BACKSLASH) {
  407.                                                                                p = p + doescape(s++, p+1) + 1;
  408.                                                                        } else
  409.                                                                                *s++ = *p++;
  410.                                                                }
  411.                                                        }
  412.                                                }
  413.                                                *s = CHAR_NULL;
  414.                                                addstring(out,buf);
  415.                                                /* Skip over braces */
  416.                                                fpos = (rightbr - f);
  417.                                        }
  418.                                        break;
  419.  
  420.                                case 'k':                                       /* File size in K */
  421.                                        addnumber(out, BTOK(fhead->length));
  422.                                        break;
  423.  
  424.                                case 'n':                                       /* Catalogue file name o file */
  425.                                        addstring(out, fhead->cat_name);
  426.                                        break;
  427.  
  428.                                case 'o':                                       /* Owner of upload */
  429.                                        addstring(out, fhead->owner);
  430.                                        break;
  431.  
  432.                                case 'p':                                       /* Path name to disk file */
  433.                                        if (checkfiles && fhead->online)
  434.                                                addstring(out, dirnames[fhead->dirnum]);
  435.                                        break;
  436.  
  437.                                case 'r':                                       /* Directory number */
  438.                                        addnumber(out, fhead->dir);
  439.                                        break;
  440.  
  441.                                case 's':                                       /* Section names */
  442.                                        addstring(out, sectnums[fhead->section]);
  443.                                        break;
  444.  
  445.                                case 'u':                                       /* Underline with next char     */
  446.                                        fpos++;
  447.                                        numchars = lastlen + ((align == RIGHT) ? -len : len);
  448.                                        while (numchars > 0 && opos < omaxlen) {
  449.                                                out[opos++] = f[fpos];
  450.                                                numchars--;
  451.                                        }
  452.                                        break;
  453.  
  454.                                case 'w':                                       /* Date */
  455.                                        addstring(out,
  456.                                          makedate( (fhead->date & 31),                 /* Day          */
  457.                                                                ((fhead->date>>5)) % 13,        /* Month        */
  458.                                                                ((fhead->date>>5)) / 13));      /* Year         */
  459.                                        break;
  460.  
  461.                                case 'x':
  462.                                        addnumber(out, fhead->length);
  463.                                        break;
  464.  
  465.                                case 'y':
  466.                                        addnumber(out, fhead->dirnum);
  467.                                        break;
  468.  
  469.                                case '{':                                       /* Format substring in { } */
  470.                                        {
  471.                                                int numbrackets = 0;
  472.                                                int savepos, savealign, savelen;
  473.                                                p = &f[fpos + 1];
  474.                                                /*
  475.                                                 *              Now, find the end of the substring. Nested
  476.                                                 *              brackets are skipped over.
  477.                                                 */
  478.                                                while (*p && !(*p == RIGHTBR && numbrackets == 0)) {
  479.                                                        switch (*p) {
  480.                                                                case LEFTBR:  numbrackets++; break;
  481.                                                                case RIGHTBR: numbrackets--; break;
  482.                                                        }
  483.                                                        p++;
  484.                                                }
  485.                                                if (*p == RIGHTBR) {
  486.                                                        /*
  487.                                                         *              To format the substring, we change the
  488.                                                         *              closing } into a NULL so that when
  489.                                                         *              we call format recursively, it will
  490.                                                         *              think that's the end of the string.
  491.                                                         *              After format()ing, we restore the
  492.                                                         *              bracket.
  493.                                                         */
  494.                                                        savepos   = opos;
  495.                                                        savelen   = len;
  496.                                                        savealign = align;
  497.                                                        *p = CHAR_NULL;
  498.                                                        /* Format substring */
  499.                                                        format(subformat, MAXSUB, &f[fpos + 1],
  500.                                                                                                                fhead, checkfiles);
  501.                                                        *p = RIGHTBR;
  502.                                                        opos  = savepos;
  503.                                                        len   = savelen;
  504.                                                        align = savealign;
  505.                                                        addstring(out, subformat);
  506.                                                        fpos = p - f;
  507.                                                }
  508.                                        }
  509.                                        break;
  510.  
  511.                                default:
  512.                                        len = 0;
  513.                                        buf[0] = '%';
  514.                                        buf[1] = f[fpos];
  515.                                        buf[2] = CHAR_NULL;
  516.                                        addstring(out, buf);
  517.                        }
  518.                }
  519.        }
  520.        out[opos] = CHAR_NULL;
  521.        lastlen = opos;
  522.        return (out);
  523. }
  524.  
  525. /*
  526.  *             echoformat()
  527.  *             ------------
  528.  *             This function takes a format string (as passed to the ECHO command)
  529.  *             and uses it to produce an output string. The format string can
  530.  *             contain the same escape sequences list under format(), and also
  531.  *             the following special sequences:
  532.  *
  533.  *               %b    - Number of bytes occupied by files in last LIST command
  534.  *               %k    - Number of kilobytes occupied by files in last LIST command
  535.  *               %m    - Number of megabytes occupied by files in last LIST command
  536.  *               %n    - Number of files in last LIST command
  537.  *
  538.  *             %B, %K, %M and %N are similar to the above except that they
  539.  *             represent the running totals for all files listed since the
  540.  *             last RESET command.
  541.  *
  542.  *               %w    - The current date, in dd-mmm-yy format.
  543.  *               %d    - The current day (Monday, Tuesday etc.)
  544.  *               %t    - The current time (24 hour clock)
  545.  *               %u    - As for format()
  546.  *               %{..} - As for format()
  547.  */
  548. char *echoformat(out, maxlen, f)
  549. char *out;
  550. int maxlen;
  551. char *f;
  552. {
  553.        static int lastlen;
  554.        char subformat[MAXSUB];
  555.        int fpos = 0;                           /* Position in format string */
  556.        int flen = strlen(f);
  557.        int value;
  558.        int numchars;
  559.        struct tm *today;
  560.        long seconds;
  561.        char *p;
  562.  
  563.        /*
  564.         *              First of all, setup the time array
  565.         */
  566.        time(&seconds);
  567.        today = localtime(&seconds);
  568.  
  569.        /*
  570.         *              Now format output string, according to format string
  571.         */
  572.        omaxlen = maxlen-1;
  573.        opos = 0;
  574.  
  575.        for (fpos = 0; fpos < flen && opos < maxlen; fpos++) {
  576.                if (f[fpos] == BACKSLASH) {
  577.                        fpos += doescape(out+opos, f+fpos+1);
  578.                        opos++;
  579.                } else if (f[fpos] != '%')
  580.                        out[opos++] = f[fpos];
  581.                else {
  582.                        align = LEFT;
  583.                        if (f[++fpos] == '-') {
  584.                                align = RIGHT;
  585.                                fpos++;
  586.                        }
  587.                        len = 0;
  588.                        while (isdigit(f[fpos]))
  589.                                len = (len * 10) + f[fpos++] - '0';
  590.  
  591.                        value = -1;
  592.                        switch (f[fpos]) {
  593.  
  594.                                case 'b': value = curbytes;                                     break;
  595.                                case 'B': value = totalbytes;                           break;
  596.                                case 'k': value = BTOK(curbytes);                       break;
  597.                                case 'K': value = BTOK(totalbytes);                     break;
  598.                                case 'n': value = curfiles;                                     break;
  599.                                case 'N': value = totalfiles;                           break;
  600.                                case 'm': value = BTOK(BTOK(curbytes));         break;
  601.                                case 'M': value = BTOK(BTOK(totalbytes));       break;
  602.  
  603.                                case 'd':               /* Today's day */
  604.                                        addstring(out, days[today->tm_wday]);
  605.                                        break;
  606.                                case 'w':               /* Today's date */
  607.                                        addstring(out, makedate(
  608.                                                today->tm_mday,                 /* Day          */
  609.                                                today->tm_mon+1,                /* Month        */
  610.                                                today->tm_year  ));             /* Year         */
  611.                                        break;
  612.  
  613.                                case 't':               /* Today's time */
  614.                                        addstring(out,
  615.                                          maketime(today->tm_sec, today->tm_min, today->tm_hour));
  616.                                        break;
  617.  
  618.                                case 'u':                                       /* Underline with next char     */
  619.                                        fpos++;
  620.                                        numchars = lastlen + ((align == RIGHT) ? -len : len);
  621.                                        while (numchars > 0 && opos < omaxlen) {
  622.                                                out[opos++] = f[fpos];
  623.                                                numchars--;
  624.                                        }
  625.                                        break;
  626.  
  627.                                /*
  628.                                 *              Note - I'm a little unhappy about including this code
  629.                                 *              twice, but a nice simple way of generalising it for
  630.                                 *              both format and echoformat doesn't spring to mind.
  631.                                 */
  632.                                case '{':                                       /* Format substring in { } */
  633.                                        {
  634.                                                int numbrackets = 0;
  635.                                                int savepos, savealign, savelen;
  636.                                                p = &f[fpos + 1];
  637.                                                /*
  638.                                                 *              Now, find the end of the substring. Nested
  639.                                                 *              brackets are skipped over.
  640.                                                 */
  641.                                                while (*p && !(*p == RIGHTBR && numbrackets == 0)) {
  642.                                                        switch (*p) {
  643.                                                                case LEFTBR:  numbrackets++; break;
  644.                                                                case RIGHTBR: numbrackets--; break;
  645.                                                        }
  646.                                                        p++;
  647.                                                }
  648.                                                if (*p == RIGHTBR) {
  649.                                                        /*
  650.                                                         *              To format the substring, we change the
  651.                                                         *              closing } into a NULL so that when
  652.                                                         *              we call format recursively, it will
  653.                                                         *              think that's the end of the string.
  654.                                                         *              After format()ing, we restore the
  655.                                                         *              bracket.
  656.                                                         */
  657.                                                        savepos   = opos;
  658.                                                        savelen   = len;
  659.                                                        savealign = align;
  660.                                                        *p = CHAR_NULL;
  661.                                                        /* Format substring */
  662.                                                        echoformat(subformat, MAXSUB, &f[fpos + 1]);
  663.                                                        *p = RIGHTBR;
  664.                                                        opos  = savepos;
  665.                                                        len   = savelen;
  666.                                                        align = savealign;
  667.                                                        addstring(out, subformat);
  668.                                                        fpos = p - f;
  669.                                                }
  670.                                        }
  671.                                        break;
  672.  
  673.                                default:
  674.                                        len = 0;
  675.                                        buf[0] = '%';
  676.                                        buf[1] = f[fpos];
  677.                                        buf[2] = CHAR_NULL;
  678.                                        addstring(out, buf);
  679.                        }
  680.                        if (value != -1)
  681.                                addnumber(out, value);
  682.                }
  683.        }
  684.        out[opos] = CHAR_NULL;
  685.        lastlen = opos;
  686.        return (out);
  687. }
  688.