merge processes documentation files, merging source files and substituting documentation text for matching comments.
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 );
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";
typedef struct docText
{
char *s;
line *tag, *doc, **pdoc;
BOOL used;
unsigned mark;
struct docText *next;
} docText;
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 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;
}
}
static docText *docTextAppend( docText ***ppt )
{
docText *t;
**ppt = t = docTextAlloc();
*ppt = &t->next;
return t;
}
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 );
}
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;
}
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 );
}
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 );
}
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;
}
}
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 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 );
}
}