Annotation of coherent/d/usr/bin/elvis/cut.c, revision 1.1

1.1     ! root        1: /* cut.c */
        !             2: 
        !             3: /* Author:
        !             4:  *     Steve Kirkendall
        !             5:  *     16820 SW Tallac Way
        !             6:  *     Beaverton, OR 97006
        !             7:  *     [email protected], or ...uunet!tektronix!psueea!jove!kirkenda
        !             8:  */
        !             9: 
        !            10: 
        !            11: /* This file contains function which manipulate the cut buffers. */
        !            12: 
        !            13: #include "config.h"
        !            14: #include "vi.h"
        !            15: #if TURBOC
        !            16: #include <process.h>           /* needed for getpid */
        !            17: #endif
        !            18: #if TOS
        !            19: #include <osbind.h>
        !            20: #define        rename(a,b)     Frename(0,a,b)
        !            21: #endif
        !            22: 
        !            23: # define NANNONS       9       /* number of annonymous buffers */
        !            24: 
        !            25: static struct cutbuf
        !            26: {
        !            27:        short   *phys;  /* pointer to an array of #s of BLKs containing text */
        !            28:        int     nblks;  /* number of blocks in phys[] array */
        !            29:        int     start;  /* offset into first block of start of cut */
        !            30:        int     end;    /* offset into last block of end of cut */
        !            31:        int     fd;     /* fd of tmp file, or -1 to use tmpfd */
        !            32:        char    lnmode; /* boolean: line-mode cut? (as opposed to char-mode) */
        !            33: }
        !            34:        named[27],      /* cut buffers "a through "z and ". */
        !            35:        annon[NANNONS]; /* annonymous cut buffers */
        !            36: 
        !            37: static char    cbname; /* name chosen for next cut/paste operation */
        !            38: 
        !            39: 
        !            40: #ifndef NO_RECYCLE
        !            41: /* This function builds a list of all blocks needed in the current tmp file
        !            42:  * for the contents of cut buffers.
        !            43:  * !!! WARNING: if you have more than ~450000 bytes of text in all of the
        !            44:  * cut buffers, then this will fail disastrously, because buffer overflow
        !            45:  * is *not* allowed for.
        !            46:  */
        !            47: int cutneeds(need)
        !            48:        BLK             *need;  /* this is where we deposit the list */
        !            49: {
        !            50:        struct cutbuf   *cb;    /* used to count through cut buffers */
        !            51:        int             i;      /* used to count through blocks of a cut buffer */
        !            52:        int             n;      /* total number of blocks in list */
        !            53: 
        !            54:        n = 0;
        !            55: 
        !            56:        /* first the named buffers... */
        !            57:        for (cb = named; cb < &named[27]; cb++)
        !            58:        {
        !            59:                if (cb->fd > 0)
        !            60:                        continue;
        !            61: 
        !            62:                for (i = cb->nblks; i-- > 0; )
        !            63:                {
        !            64:                        need->n[n++] = cb->phys[i];
        !            65:                }
        !            66:        }
        !            67: 
        !            68:        /* then the anonymous buffers */
        !            69:        for (cb = annon; cb < &annon[NANNONS]; cb++)
        !            70:        {
        !            71:                if (cb->fd > 0)
        !            72:                        continue;
        !            73: 
        !            74:                for (i = cb->nblks; i-- > 0; )
        !            75:                {
        !            76:                        need->n[n++] = cb->phys[i];
        !            77:                }
        !            78:        }
        !            79: 
        !            80:        return n;
        !            81: }
        !            82: #endif
        !            83: 
        !            84: /* This function frees a cut buffer */
        !            85: static void cutfree(buf)
        !            86:        struct cutbuf   *buf;
        !            87: {
        !            88:        char    cutfname[50];
        !            89:        int     i;
        !            90: 
        !            91:        /* return immediately if the buffer is already empty */
        !            92:        if (buf->nblks <= 0)
        !            93:        {
        !            94:                return;
        !            95:        }
        !            96: 
        !            97:        /* else free up stuff */
        !            98:        buf->nblks = 0;
        !            99: #ifdef DEBUG
        !           100:        if (!buf->phys)
        !           101:                msg("cutfree() tried to free an NULL buf->phys pointer.");
        !           102: #endif
        !           103:        free((char *)buf->phys);
        !           104: 
        !           105:        /* see if anybody else needs this tmp file */
        !           106:        if (buf->fd >= 0)
        !           107:        {
        !           108:                for (i = 0; i < 27; i++)
        !           109:                {
        !           110:                        if (named[i].nblks > 0 && named[i].fd == buf->fd)
        !           111:                        {
        !           112:                                break;
        !           113:                        }
        !           114:                }
        !           115:        }
        !           116: 
        !           117:        /* if nobody else needs it, then discard the tmp file */
        !           118:        if (buf->fd >= 0 && i == 27)
        !           119:        {
        !           120:                close(buf->fd);
        !           121: #if MSDOS || TOS
        !           122:                strcpy(cutfname, o_directory);
        !           123:                if ((i = strlen(cutfname)) && !strchr(":/\\", cutfname[i-1]))
        !           124:                        cutfname[i++]=SLASH;
        !           125:                sprintf(cutfname+i, CUTNAME+3, getpid(), buf->fd);
        !           126: #else
        !           127:                sprintf(cutfname, CUTNAME, o_directory, getpid(), buf->fd);
        !           128: #endif
        !           129:                unlink(cutfname);
        !           130:        }
        !           131: }
        !           132: 
        !           133: /* This function is called when we are about to abort a tmp file.  If any
        !           134:  * cut buffers still need the file, then a copy of the file should be
        !           135:  * created for use by the cut buffers.
        !           136:  *
        !           137:  * To minimize the number of extra files lying around, only named cut buffers
        !           138:  * are preserved in a file switch; the annonymous buffers just go away.
        !           139:  */
        !           140: void cutswitch(tmpname)
        !           141:        char    *tmpname; /* name of the tmp file */
        !           142: {
        !           143:        char    cutfname[50];   /* used to build a new name for the tmp file */
        !           144:        int     fd;             /* a new fd for the current tmp file */
        !           145:        int     i;
        !           146: #if MSDOS || TOS
        !           147:        int     j;
        !           148: #endif
        !           149: 
        !           150:        /* discard all annonymous cut buffers */
        !           151:        for (i = 0; i < NANNONS; i++)
        !           152:        {
        !           153:                cutfree(&annon[i]);
        !           154:        }
        !           155: 
        !           156:        /* find the first named buffer that uses this tmp file */
        !           157:        for (i = 0; i < 27; i++)
        !           158:        {
        !           159:                if (named[i].nblks > 0 && named[i].fd < 0)
        !           160:                {
        !           161:                        break;
        !           162:                }
        !           163:        }
        !           164: 
        !           165:        /* if none of them use this tmp file, then we're done */
        !           166:        if (i == 27)
        !           167:        {
        !           168:                return;
        !           169:        }
        !           170: 
        !           171:        /* else we'll need this file and an fd a little longer */
        !           172: #if MSDOS || TOS
        !           173:        strcpy(cutfname, o_directory);
        !           174:        if ((j = strlen(cutfname)) && !strchr(":/\\", cutfname[j-1]))
        !           175:                cutfname[j++]=SLASH;
        !           176:        close(tmpfd);
        !           177:        fd = open(tmpname, O_RDONLY|O_BINARY);
        !           178:        close(fd);
        !           179:        sprintf(cutfname+j, CUTNAME+3, getpid(), fd);
        !           180:        rename(tmpname, cutfname);
        !           181:        fd = open(cutfname, O_RDONLY|O_BINARY);
        !           182:        tmpfd = -1; /* we'll try to close this in tmp.c, but who cares? */
        !           183: #else
        !           184:        fd = dup(tmpfd);
        !           185: # if OSK
        !           186:        sprintf(cutfname, CUTNAME, "", getpid(), fd);
        !           187:        if (!link(tmpname, &cutfname[1])) /* skip slash */
        !           188:                unlink(tmpname);
        !           189: # else 
        !           190:        sprintf(cutfname, CUTNAME, o_directory, getpid(), fd);
        !           191:        link(tmpname, cutfname) || unlink(tmpname);
        !           192: # endif
        !           193: #endif
        !           194: 
        !           195:        /* have all cut buffers use the new fd instead */
        !           196:        for (; i < 27; i++)
        !           197:        {
        !           198:                if (named[i].nblks > 0 && named[i].fd < 0)
        !           199:                {
        !           200:                        named[i].fd = fd;
        !           201:                }
        !           202:        }
        !           203: }
        !           204: 
        !           205: /* This function should be called just before termination of vi */
        !           206: void cutend()
        !           207: {
        !           208:        int     i;
        !           209: 
        !           210:        /* free all named cut buffers, since they might be forcing an older
        !           211:         * tmp file to be retained.
        !           212:         */
        !           213:        for (i = 0; i < 27; i++)
        !           214:        {
        !           215:                cutfree(&named[i]);
        !           216:        }
        !           217: }
        !           218: 
        !           219: 
        !           220: /* This function is used to select the cut buffer to be used next */
        !           221: void cutname(name)
        !           222:        int     name;   /* a single character */
        !           223: {
        !           224:        cbname = name;
        !           225: }
        !           226: 
        !           227: 
        !           228: 
        !           229: 
        !           230: /* This function copies a selected segment of text to a cut buffer */
        !           231: void cut(from, to)
        !           232:        MARK    from;           /* start of text to cut */
        !           233:        MARK    to;             /* end of text to cut */
        !           234: {
        !           235:        int             first;  /* logical number of first block in cut */
        !           236:        int             last;   /* logical number of last block used in cut */
        !           237:        long            line;   /* a line number */
        !           238:        int             lnmode; /* boolean: will this be a line-mode cut? */
        !           239:        MARK            delthru;/* end of text temporarily inserted for apnd */
        !           240:        REG struct cutbuf *cb;
        !           241:        REG long        l;
        !           242:        REG int         i;
        !           243:        REG char        *scan;
        !           244:        char            *blkc;
        !           245: 
        !           246:        /* detect whether this must be a line-mode cut or char-mode cut */
        !           247:        if (markidx(from) == 0 && markidx(to) == 0)
        !           248:                lnmode = TRUE;
        !           249:        else
        !           250:                lnmode = FALSE;
        !           251: 
        !           252:        /* by default, we don't "delthru" anything */
        !           253:        delthru = MARK_UNSET;
        !           254: 
        !           255:        /* decide which cut buffer to use */
        !           256:        if (!cbname)
        !           257:        {
        !           258:                /* free up the last annonymous cut buffer */
        !           259:                cutfree(&annon[NANNONS - 1]);
        !           260: 
        !           261:                /* shift the annonymous cut buffers */
        !           262:                for (i = NANNONS - 1; i > 0; i--)
        !           263:                {
        !           264:                        annon[i] = annon[i - 1];
        !           265:                }
        !           266: 
        !           267:                /* use the first annonymous cut buffer */
        !           268:                cb = annon;
        !           269:                cb->nblks = 0;
        !           270:        }
        !           271:        else if (cbname >= 'a' && cbname <= 'z')
        !           272:        {
        !           273:                cb = &named[cbname - 'a'];
        !           274:                cutfree(cb);
        !           275:        }
        !           276: #ifndef CRUNCH
        !           277:        else if (cbname >= 'A' && cbname <= 'Z')
        !           278:        {
        !           279:                cb = &named[cbname - 'A'];
        !           280:                if (cb->nblks > 0)
        !           281:                {
        !           282:                        /* resolve linemode/charmode differences */
        !           283:                        if (!lnmode && cb->lnmode)
        !           284:                        {
        !           285:                                from &= ~(BLKSIZE - 1);
        !           286:                                if (markidx(to) != 0 || to == from)
        !           287:                                {
        !           288:                                        to = to + BLKSIZE - markidx(to);
        !           289:                                }
        !           290:                                lnmode = TRUE;
        !           291:                        }
        !           292: 
        !           293:                        /* insert the old cut-buffer before the new text */
        !           294:                        mark[28] = to;
        !           295:                        delthru = paste(from, FALSE, TRUE);
        !           296:                        if (delthru == MARK_UNSET)
        !           297:                        {
        !           298:                                return;
        !           299:                        }
        !           300:                        delthru++;
        !           301:                        to = mark[28];
        !           302:                }
        !           303:                cutfree(cb);
        !           304:        }
        !           305: #endif /* not CRUNCH */
        !           306:        else if (cbname == '.')
        !           307:        {
        !           308:                cb = &named[26];
        !           309:                cutfree(cb);
        !           310:        }
        !           311:        else
        !           312:        {
        !           313:                msg("Invalid cut buffer name: \"%c", cbname);
        !           314:                cbname = '\0';
        !           315:                return;
        !           316:        }
        !           317:        cbname = '\0';
        !           318:        cb->fd = -1;
        !           319: 
        !           320:        /* detect whether we're doing a line mode cut */
        !           321:        cb->lnmode = lnmode;
        !           322: 
        !           323:        /* ---------- */
        !           324: 
        !           325:        /* Reporting... */      
        !           326:        if (markidx(from) == 0 && markidx(to) == 0)
        !           327:        {
        !           328:                rptlines = markline(to) - markline(from);
        !           329:                rptlabel = "yanked";
        !           330:        }
        !           331: 
        !           332:        /* ---------- */
        !           333: 
        !           334:        /* make sure each block has a physical disk address */
        !           335:        blksync();
        !           336: 
        !           337:        /* find the first block in the cut */
        !           338:        line = markline(from);
        !           339:        for (first = 1; line > lnum[first]; first++)
        !           340:        {
        !           341:        }
        !           342: 
        !           343:        /* fetch text of the block containing that line */
        !           344:        blkc = scan = blkget(first)->c;
        !           345: 
        !           346:        /* find the mark in the block */
        !           347:        for (l = lnum[first - 1]; ++l < line; )
        !           348:        {
        !           349:                while (*scan++ != '\n')
        !           350:                {
        !           351:                }
        !           352:        }
        !           353:        scan += markidx(from);
        !           354: 
        !           355:        /* remember the offset of the start */
        !           356:        cb->start = scan - blkc;
        !           357: 
        !           358:        /* ---------- */
        !           359: 
        !           360:        /* find the last block in the cut */
        !           361:        line = markline(to);
        !           362:        for (last = first; line > lnum[last]; last++)
        !           363:        {
        !           364:        }
        !           365: 
        !           366:        /* fetch text of the block containing that line */
        !           367:        if (last != first)
        !           368:        {
        !           369:                blkc = scan = blkget(last)->c;
        !           370:        }
        !           371:        else
        !           372:        {
        !           373:                scan = blkc;
        !           374:        }
        !           375: 
        !           376:        /* find the mark in the block */
        !           377:        for (l = lnum[last - 1]; ++l < line; )
        !           378:        {
        !           379:                while (*scan++ != '\n')
        !           380:                {
        !           381:                }
        !           382:        }
        !           383:        if (markline(to) <= nlines)
        !           384:        {
        !           385:                scan += markidx(to);
        !           386:        }
        !           387: 
        !           388:        /* remember the offset of the end */
        !           389:        cb->end = scan - blkc;
        !           390: 
        !           391:        /* ------- */
        !           392: 
        !           393:        /* remember the physical block numbers of all included blocks */
        !           394:        cb->nblks = last - first;
        !           395:        if (cb->end > 0)
        !           396:        {
        !           397:                cb->nblks++;
        !           398:        }
        !           399: #ifdef lint
        !           400:        cb->phys = (short *)0;
        !           401: #else
        !           402:        cb->phys = (short *)malloc((unsigned)(cb->nblks * sizeof(short)));
        !           403: #endif
        !           404:        for (i = 0; i < cb->nblks; i++)
        !           405:        {
        !           406:                cb->phys[i] = hdr.n[first++];
        !           407:        }
        !           408: 
        !           409: #ifndef CRUNCH
        !           410:        /* if we temporarily inserted text for appending, then delete that
        !           411:         * text now -- before the user sees it.
        !           412:         */
        !           413:        if (delthru)
        !           414:        {
        !           415:                line = rptlines;
        !           416:                delete(from, delthru);
        !           417:                rptlines = line;
        !           418:                rptlabel = "yanked";
        !           419:        }
        !           420: #endif /* not CRUNCH */
        !           421: }
        !           422: 
        !           423: 
        !           424: static void readcutblk(cb, blkno)
        !           425:        struct cutbuf   *cb;
        !           426:        int             blkno;
        !           427: {
        !           428:        int             fd;     /* either tmpfd or cb->fd */
        !           429: 
        !           430:        /* decide which fd to use */
        !           431:        if (cb->fd >= 0)
        !           432:        {
        !           433:                fd = cb->fd;
        !           434:        }
        !           435:        else
        !           436:        {
        !           437:                fd = tmpfd;
        !           438:        }
        !           439: 
        !           440:        /* get the block */
        !           441:        lseek(fd, (long)cb->phys[blkno] * (long)BLKSIZE, 0);
        !           442:        if (read(fd, tmpblk.c, (unsigned)BLKSIZE) != BLKSIZE)
        !           443:        {
        !           444:                msg("Error reading back from tmp file for pasting!");
        !           445:        }
        !           446: }
        !           447: 
        !           448: 
        !           449: /* This function inserts text from a cut buffer, and returns the MARK where
        !           450:  * insertion ended.  Return MARK_UNSET on errors.
        !           451:  */
        !           452: MARK paste(at, after, retend)
        !           453:        MARK    at;     /* where to insert the text */
        !           454:        int     after;  /* boolean: insert after mark? (rather than before) */
        !           455:        int     retend; /* boolean: return end of text? (rather than start) */
        !           456: {
        !           457:        REG struct cutbuf       *cb;
        !           458:        REG int                 i;
        !           459: 
        !           460:        /* decide which cut buffer to use */
        !           461:        if (cbname >= 'A' && cbname <= 'Z')
        !           462:        {
        !           463:                cb = &named[cbname - 'A'];
        !           464:        }
        !           465:        else if (cbname >= 'a' && cbname <= 'z')
        !           466:        {
        !           467:                cb = &named[cbname - 'a'];
        !           468:        }
        !           469:        else if (cbname >= '1' && cbname <= '9')
        !           470:        {
        !           471:                cb = &annon[cbname - '1'];
        !           472:        }
        !           473:        else if (cbname == '.')
        !           474:        {
        !           475:                cb = &named[26];
        !           476:        }
        !           477:        else if (!cbname)
        !           478:        {
        !           479:                cb = annon;
        !           480:        }
        !           481:        else
        !           482:        {
        !           483:                msg("Invalid cut buffer name: \"%c", cbname);
        !           484:                cbname = '\0';
        !           485:                return MARK_UNSET;
        !           486:        }
        !           487: 
        !           488:        /* make sure it isn't empty */
        !           489:        if (cb->nblks == 0)
        !           490:        {
        !           491:                if (cbname)
        !           492:                        msg("Cut buffer \"%c is empty", cbname);
        !           493:                else
        !           494:                        msg("Cut buffer is empty");
        !           495:                cbname = '\0';
        !           496:                return MARK_UNSET;
        !           497:        }
        !           498:        cbname = '\0';
        !           499: 
        !           500:        /* adjust the insertion MARK for "after" and line-mode cuts */
        !           501:        if (cb->lnmode)
        !           502:        {
        !           503:                at &= ~(BLKSIZE - 1);
        !           504:                if (after)
        !           505:                {
        !           506:                        at += BLKSIZE;
        !           507:                }
        !           508:        }
        !           509:        else if (after)
        !           510:        {
        !           511:                /* careful! if markidx(at) == 0 we might be pasting into an
        !           512:                 * empty line -- so we can't blindly increment "at".
        !           513:                 */
        !           514:                if (markidx(at) == 0)
        !           515:                {
        !           516:                        pfetch(markline(at));
        !           517:                        if (plen != 0)
        !           518:                        {
        !           519:                                at++;
        !           520:                        }
        !           521:                }
        !           522:                else
        !           523:                {
        !           524:                        at++;
        !           525:                }
        !           526:        }
        !           527: 
        !           528:        /* put a copy of the "at" mark in the mark[] array, so it stays in
        !           529:         * sync with changes made via add().
        !           530:         */
        !           531:        mark[27] = at;
        !           532: 
        !           533:        /* simple one-block paste? */
        !           534:        if (cb->nblks == 1)
        !           535:        {
        !           536:                /* get the block */
        !           537:                readcutblk(cb, 0);
        !           538: 
        !           539:                /* isolate the text we need within it */
        !           540:                if (cb->end)
        !           541:                {
        !           542:                        tmpblk.c[cb->end] = '\0';
        !           543:                }
        !           544: 
        !           545:                /* insert it */
        !           546:                ChangeText
        !           547:                {
        !           548:                        add(at, &tmpblk.c[cb->start]);
        !           549:                }
        !           550:        }
        !           551:        else
        !           552:        {
        !           553:                /* multi-block paste */
        !           554: 
        !           555:                ChangeText
        !           556:                {
        !           557:                        i = cb->nblks - 1;
        !           558: 
        !           559:                        /* add text from the last block first */
        !           560:                        if (cb->end > 0)
        !           561:                        {
        !           562:                                readcutblk(cb, i);
        !           563:                                tmpblk.c[cb->end] = '\0';
        !           564:                                add(at, tmpblk.c);
        !           565:                                i--;
        !           566:                        }
        !           567: 
        !           568:                        /* add intervening blocks */
        !           569:                        while (i > 0)
        !           570:                        {
        !           571:                                readcutblk(cb, i);
        !           572:                                add(at, tmpblk.c);
        !           573:                                i--;
        !           574:                        }
        !           575: 
        !           576:                        /* add text from the first cut block */
        !           577:                        readcutblk(cb, 0);
        !           578:                        add(at, &tmpblk.c[cb->start]);
        !           579:                }
        !           580:        }
        !           581: 
        !           582:        /* Reporting... */
        !           583:        rptlines = markline(mark[27]) - markline(at);
        !           584:        rptlabel = "pasted";
        !           585: 
        !           586:        /* return the mark at the beginning/end of inserted text */
        !           587:        if (retend)
        !           588:        {
        !           589:                return mark[27] - 1L;
        !           590:        }
        !           591:        return at;
        !           592: }
        !           593: 
        !           594: 
        !           595: 
        !           596: 
        !           597: #ifndef NO_AT
        !           598: 
        !           599: /* This function copies characters from a cut buffer into a string.
        !           600:  * It returns the number of characters in the cut buffer.  If the cut
        !           601:  * buffer is too large to fit in the string (i.e. if cb2str() returns
        !           602:  * a number >= size) then the characters will not have been copied.
        !           603:  * It returns 0 if the cut buffer is empty, and -1 for invalid cut buffers.
        !           604:  */
        !           605: int cb2str(name, buf, size)
        !           606:        int     name;   /* the name of a cut-buffer to get: a-z only! */
        !           607:        char    *buf;   /* where to put the string */
        !           608:        unsigned size;  /* size of buf */
        !           609: {
        !           610:        REG struct cutbuf       *cb;
        !           611:        REG char                *src;
        !           612:        REG char                *dest;
        !           613: 
        !           614:        /* decide which cut buffer to use */
        !           615:        if (name >= 'a' && name <= 'z')
        !           616:        {
        !           617:                cb = &named[name - 'a'];
        !           618:        }
        !           619:        else
        !           620:        {
        !           621:                return -1;
        !           622:        }
        !           623: 
        !           624:        /* if the buffer is empty, return 0 */
        !           625:        if (cb->nblks == 0)
        !           626:        {
        !           627:                return 0;
        !           628:        }
        !           629: 
        !           630:        /* !!! if not a single-block cut, then fail */
        !           631:        if (cb->nblks != 1)
        !           632:        {
        !           633:                return size;
        !           634:        }
        !           635: 
        !           636:        /* if too big, return the size now, without doing anything */
        !           637:        if (cb->end - cb->start >= size)
        !           638:        {
        !           639:                return cb->end - cb->start;
        !           640:        }
        !           641: 
        !           642:        /* get the block */
        !           643:        readcutblk(cb, 0);
        !           644: 
        !           645:        /* isolate the string within that blk */
        !           646:        if (cb->start == 0)
        !           647:        {
        !           648:                tmpblk.c[cb->end] = '\0';
        !           649:        }
        !           650:        else
        !           651:        {
        !           652:                for (dest = tmpblk.c, src = dest + cb->start; src < tmpblk.c + cb->end; )
        !           653:                {
        !           654:                        *dest++ = *src++;
        !           655:                }
        !           656:                *dest = '\0';
        !           657:        }
        !           658: 
        !           659:        /* copy the string into the buffer */
        !           660:        if (buf != tmpblk.c)
        !           661:        {
        !           662:                strcpy(buf, tmpblk.c);
        !           663:        }
        !           664: 
        !           665:        /* return the length */
        !           666:        return cb->end - cb->start;
        !           667: }
        !           668: #endif

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.