merge processes documentation files, merging source files and substituting documentation text for matching comments.

merge.h

Copyright © 2003, 2004 Dave Bayer. Subject to the terms and conditions of the MIT License.

Function prototypes:

void mergeDoc( line **pdoc, filterTable *table, unsigned start, char *path );

merge.c

Copyright © 2003, 2004 Dave Bayer. Subject to the terms and conditions of the MIT License.

#include "root.h"
#include "file.h"
#include "filter.h"
#include "rules.h"
#include "dict.h"
#include "annote.h"
#include "merge.h"

Delimiters for preformatted code:

static const char codeBegin[] = "<pre class=code>\n" ;
static const char codeEnd[] = "</pre>\n\n";

docText

typedef struct docText
{
    char *s;
    line *tag, *doc, **pdoc;
    BOOL used;
    unsigned mark;
    struct docText *next;
} docText;

docTextAlloc

static docText *docTextAlloc( void )
{
    docText *d;

    d = malloc( sizeof( *d ));
    d->next = 0;
    d->s = 0;
    d->tag = d->doc = 0;
    d->pdoc = 0;
    d->used = NO;
    d->mark = 0;
    return d;
}

docTextFree

docTextFree frees a linked list of docText structs. Routines that use docText structs have a responsibility to remove lines that are used elsewhere, setting the line pointers tag and doc to null, to insure that lines are not freed twice.

static void docTextFree( docText *d )
{
    docText *p;
    while ( d != 0 )
    {
        p = d->next;
        lineListFree( d->tag, markFree );
        lineListFree( d->doc, markFree );
        free( d );
        d = p;
    }
}

docTextAppend

static docText *docTextAppend( docText ***ppt )
{
    docText *t;

    **ppt = t = docTextAlloc();
    *ppt = &t->next;
    return t;
}

textAdvance

textAdvance manipulates a handle into a linked list of lines. Like many of the routines that follow, it takes as an argument a pointer to the handle, in order to be able to update the handle in the calling procedure.

textAdvance advances ppl to the next line marked other than mergeBlank. Leading and trailing blank lines up to this point are removed. Other runs of blank lines are combined into single blank lines. If par is YES then paragraphs are enclosed with the <par> element.

static void textAdvance( line ***ppl, BOOL par )
{
     filterMark *m;
     BOOL inPar, wasPar;

     inPar = wasPar = NO;
     while ( **ppl != 0 )
     {
         m = (**ppl)->data;
         if ( m != 0 )
         {
             if ( inPar )
             {
                 inPar = NO;
             }
             if ( m->mark == mergeBlank )
                 lineDelete( *ppl, markFree );
            else
                break;
         }
         else
         {
             if ( !inPar )
             {
                 if ( wasPar )
                     lineInsert( lineFromStr( "\n" ), ppl );
                 wasPar = inPar = YES;
             }
             lineAdvance( ppl );
         }
     }
     if ( par && wasPar ) lineInsert( lineFromStr( "\n" ), ppl );
}

docTextTransfer

static void docTextTransfer( line ***ppdoc, docText ***pptext )
{
    line **pstart;
    docText *t;
    filterMark *m;

    pstart = *ppdoc;
    t = docTextAppend( pptext );
    t->tag = **ppdoc;
    m = t->tag->data;
    assert( m != 0 && ( m->mark == mergeTag || m->mark == mergeGlobal || m->mark == mergeLocal ));
    t->mark = m->mark;
    t->s = t->tag->s + m->index;
    lineRemove( *ppdoc );
    textAdvance( ppdoc, YES );
    if ( *pstart != **ppdoc )
    {
        t->doc = *pstart;
        t->pdoc = *ppdoc;
        lineListRemove( pstart, ppdoc );
    }
    else
        t->doc = 0;
}

substituteDoc

static void substituteDoc( line ***ppsource, docText *text )
{
    filterMark *m;
    char *s, buf[LEN];
    line *source;
    unsigned mark;
    BOOL anchor, ok;

    source = **ppsource;
    m = source->data;
    assert( m != 0 );
    mark = m->mark;
    assert( m != 0 && ( m->mark == mergeTag || m->mark == mergeGlobal || m->mark == mergeLocal ));

    if ( mark == mergeGlobal || mark == mergeLocal )
    {
        anchor = YES;
        ok = dictDefine( source, buf );
    }
    else
    {
        anchor = NO;
        ok = YES;
    }
    s = source->s + m->index;

    if ( ok ) for ( ; text != 0; text = text->next )
        if ( text->used == NO && text->mark == mark && strcmp( s, text->s ) == 0 )
        {
            text->used = YES;
            lineDelete( *ppsource, markFree );
            lineInsert( lineFromStr( codeEnd ), ppsource );
            if ( anchor ) lineInsert( lineFromStr( buf ), ppsource );
            if ( text->doc != 0 )
            {
                lineListInsert( text->doc, text->pdoc, ppsource );
                text->doc = 0;
            }
            lineInsert( lineFromStr( codeBegin ), ppsource );
            return;
        }

    source->s[--source->len] = '\0';
    lineInsert( lineFromStr( "\n<strong>" ), ppsource );
    lineAdvance( ppsource );
    lineInsert( lineFromStr( "</strong>\n\n" ), ppsource );
}

mergeSource

static void mergeSource( line ***ppdoc, line *source )
{
    line *doc, **psource;
    docText *text, **ptext, *t;
    filterMark *m;

Save documentation in docText records.

    textAdvance( ppdoc, YES );
    text = 0;
    ptext = &text;
    while ( (doc = **ppdoc) != 0 )
    {
        m = doc->data;
        assert( m != 0 );
        if ( m->mark != mergeTag && m->mark != mergeGlobal && m->mark != mergeLocal ) break;
        docTextTransfer( ppdoc, &ptext );
    }

Merge documentation into source lines.

    psource = &source;
    lineInsert( lineFromStr( codeBegin ), &psource );
    for ( textAdvance( &psource, NO ); *psource != 0; textAdvance( &psource, NO ))
        substituteDoc( &psource, text );
    lineInsert( lineFromStr( codeEnd ), &psource );

Remove empty <pre class=code> elements.

    for ( psource = &source; *psource!=0; lineAdvance( &psource ))
        while ( *psource !=0 && (*psource)->next != 0
            && strcmp( (*psource)->s, codeBegin ) == 0
            && strcmp( (*psource)->next->s, codeEnd ) == 0 )
        {
            lineDelete( psource, 0 );
            lineDelete( psource, 0 );
        }

Splice source into documentation.

    lineListInsert( source, psource, ppdoc );

Output unused documentation.

    for ( t=text; t!=0; t=t->next )
    {
        if ( t->used ) continue;
        lineInsert( lineFromStr( "<p><strong>\n" ), ppdoc );
        lineInsert( t->tag, ppdoc );
        t->tag = 0;
        lineInsert( lineFromStr( "</strong></p>\n\n" ), ppdoc );
        if ( t->doc != 0 )
        {
            lineListInsert( t->doc, t->pdoc, ppdoc );
            t->doc = 0;
        }
    }
    docTextFree( text );
}

readSource

static BOOL readSource( line ***ppdoc, line **psource, lineFilter *pfilter, char *path )
{
    line *doc;
    FILE *fin;
    filterMark *m;
    char c, *s, *t;
    char buf[BUFLEN];

    *psource = 0;
    doc = **ppdoc;
    m = doc->data;
    assert( m != 0 && m->mark == mergeCmd );

    s = doc->s + m->index;
    if ( *s == '\n' )
    {
        lineDelete( *ppdoc, markFree );
        return YES;
    }
    m = m->next;
    if ( m != 0 )
    {
        assert( m->mark == mergeHtml );
        doc->s[m->index] = '\0';
        t = strrchr( s, '/' );
        t != 0 ? ++t : ( t = s );
        sprintf( buf, "<h3>\n<a href=\"%s.html\">%s</a>\n</h3>\n\n", s, t );
        lineDelete( *ppdoc, markFree );
        lineInsert( lineFromStr( buf ), ppdoc );
        return YES;
    }
    else
    {
        for ( t=s; isgraph( *t ); ++t ) {}
        c = *t;
        *t = '\0';
        sprintf( buf, "%s%s", path, s );
        fin = fileOpen( buf, "r", NO );
        if ( fin == 0 )
        {
            *t = c;
            return NO;
        }

Insert link to included file.

        sprintf( buf, "<h3>\n<a href=\"%s\">%s</a>\n</h3>\n\n", s, s );
        lineDelete( *ppdoc, markFree );
        lineInsert( lineFromStr( buf ), ppdoc );
        *psource = fileRead( fin, pfilter, detab );
        return YES;
    }
}

mergeError

Report an error by enclosing the wayward line in a <strong> element.

static void mergeError( line ***ppl )
{
    lineInsert( lineFromStr( "<p><strong>\n" ), ppl );
    lineAdvance( ppl );
    lineInsert( lineFromStr( "</strong></p>\n\n" ), ppl );
}

mergeDoc

mergeDoc uses table with initial state start to read each source file requested by pdoc, and weaves in their contents to form the annotated source listing.

void mergeDoc( line **pdoc, filterTable *table, unsigned start, char *path )
{
    filterMark *m;
    line *source;
    lineFilter filter;
    filterState state;

    filter.filt = stateFilter;
    filter.next = 0;
    filter.data = &state;
    state.table = table;
    state.filt = 0;
    for ( textAdvance( &pdoc, YES ); *pdoc != 0; textAdvance( &pdoc, YES ))
    {
        m = (*pdoc)->data;
        assert( m->mark != 0 );
        if ( m->mark == mergeCmd )
        {
            state.state = start;
            if ( !readSource( &pdoc, &source, &filter, path ))
                mergeError( &pdoc );
            else if ( source != 0 )
                mergeSource( &pdoc, source );
        }
        else
            mergeError( &pdoc );
    }
}