/* $Id: robohdrs.c,v 1.13 2005/03/02 21:06:41 gumpu Exp $ */ /****h* Docuwala/ROBOhdrs * NAME * robohdrs * DESCRIPTION * Standalone program to insert ROBODoc headers to source code files. * This program processes one source file at the time. Existing * ROBODoc headers, if any, are not checked for. Beware since this * may result in double headers. Current working directory should * be the same as where the source file is located. * USES * Exuberant Ctags 5.3.1 or newer required * USAGE * robohdrs [options] * EXAMPLE * robohdrs -p myproj test1.c * robohdrs -s -p myproj -i "MODIFICATION HISTORY" -i IDEAS test2.c * * Type `robohdrs -h' to see all command line options. * TODO * - garbage collection * - support for other languages which ctags program supports * SEE ALSO * ROBODoc https://sourceforge.net/projects/robodoc/ * Exuberant Ctags http://ctags.sourceforge.net/ * COPYRIGHT * (c) 2003 Frans Slothouber and Petteri Kettunen * Copying policy: GPL * HISTORY * 2003-08-08/petterik: #define `d' header (bug rep. from Anand Dhanakshirur) * 2003-08-04/petterik: -t option (suggestion from Anand Dhanakshirur) * 2003-02-21/petterik: -l option, script option tested ******* */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #ifndef HAVE_FORK int main( int argc, char** argv ) { printf( "Not supported on this platform\n" ); printf( "This program requires fork()\n" ); return EXIT_SUCCESS; } #else #include #include #include #include "headers.h" /****v* ROBOhdrs/PROGNAME * NAME * PROGNAME * SOURCE */ #define PROGNAME "robohdrs" /********** PROGNAME */ /****v* ROBOhdrs/PROGVERSION * NAME * PROGVERSION * SOURCE */ #define PROGVERSION "0.01" /********** PROGVERSION */ /****v* ROBOhdrs/MAXLINE * NAME * MAXLINE * SOURCE */ #define MAXLINE 10240 /********** MAXLINE */ /****v* ROBOhdrs/MAXNAME * NAME * MAXNAME * SOURCE */ #define MAXNAME 1024 /********** MAXNAME */ /****v* ROBOhdrs/ctagsBin * NAME * ctagsBin * SOURCE */ static char ctagsBin[MAXNAME]; /********** ctagsBin */ /****v* ROBOhdrs/srcSta * NAME * srcSta * SEE ALSO * src_constants * SOURCE */ static short srcSta = SRC_C; static short srcRem = SRC_R_C; static short srcEnd = SRC_E_C; /********** srcSta */ typedef struct _ctag_t { void *prev; void *next; char fname[MAXNAME]; char name[MAXNAME]; char decl[MAXLINE]; char type[MAXNAME]; int linenum; } ctag_t; typedef struct _custhdr_t { void *next; char name[MAXNAME]; } custhdr_t; typedef struct _ctags_t { ctag_t *ctag; int cnt; } ctags_t; /****v* ROBOhdrs/myctags * NAME * myctags * SYNOPSIS * static ctags_t myctags; * SOURCE */ static ctags_t myctags; /********** myctags */ /****v* ROBOhdrs/ctags * NAME * ctags * SYNOPSIS * static ctags_t *ctags; * SOURCE */ static ctags_t *ctags; /********** ctags */ /****v* ROBOhdrs/custhdrs * NAME * custhdrs * SYNOPSIS * static custhdr_t *custhdrs = 0; * SOURCE */ static custhdr_t *custhdrs = 0; /********** custhdrs */ /****v* ROBOhdrs/projName * NAME * projName * SYNOPSIS * static char projName[MAXNAME]; * SOURCE */ static char projName[MAXNAME]; /********** projName */ /****v* ROBOhdrs/vcTag * NAME * vcTag * DESCRIPTION * Version control tag string. This is always specified by the user. * SYNOPSIS * static char vcTag[MAXNAME]; * SOURCE */ static char vcTag[MAXNAME]; /********** projName */ /****v* ROBOhdrs/incSrc * NAME * incSrc * SYNOPSIS * static char incSrc = 0; * SOURCE */ static char incSrc = 0; /********** incSrc */ /****f* ROBOhdrs/usage * NAME * usage * SYNOPSIS * static void usage(void) * SOURCE */ static void usage( void ) { printf( "%s version %s, robodoc header insertor\n", PROGNAME, PROGVERSION ); printf( "(c) 2003 Frans Slothouber and Petteri Kettunen\n" ); printf( "%s comes with ABSOLUTELY NO WARRANTY.\n", PROGNAME ); printf ( "This is free software, and you are welcome to redistribute it\n" ); printf( "under the GNU GENERAL PUBLIC LICENSE terms and conditions.\n" ); printf( "usage: %s [options] \n", PROGNAME ); printf( "Options are as follows:\n" ); printf( " -h show this help text\n" ); printf ( " -i specify header item (repeat to include multiple items)\n" ); printf( " -l specify source code language (default C/C++)\n" ); printf ( " Supported options are: fortran, fortran90, script, and tex.\n" ); printf( " -p specify project name\n" ); printf( " -s include SOURCE item\n" ); printf( " -t specify CVS/RCS tag to be inserted into a file\n" ); printf( " -x specify path to ctags binary\n" ); printf( "NOTE: requires Exuberant Ctags 5.3.1 (or newer)\n" ); printf( "EXAMPLES:\n" ); printf ( "robohdrs -s -p myproj -i \"MODIFICATION HISTORY\" -i IDEAS test.c\n" ); printf ( "robohdrs -s -p myproj -l script -t '%cHeader:%c' test.tcl\n", '$', '$' ); exit( 1 ); } /********** usage */ /****f* ROBOhdrs/cmdLine * NAME * cmdLine * SYNOPSIS * static void cmdLine(int argc, char **argv) * SOURCE */ static void cmdLine( int argc, char **argv ) { int ch; custhdr_t *c, *nc; while ( ( ch = getopt( argc, argv, "i:l:p:st:x:" ) ) != -1 ) switch ( ch ) { case 's': /* include source item */ incSrc = 1; break; case 't': /* specify version control tag */ strncpy( vcTag, optarg, MAXNAME ); break; case 'p': /* specify project name */ strncpy( projName, optarg, MAXNAME ); break; case 'i': if ( !custhdrs ) { assert( ( custhdrs = ( custhdr_t * ) malloc( sizeof( custhdr_t ) ) ) ); custhdrs->next = 0; c = custhdrs; } else { c = custhdrs; while ( c->next ) { c = c->next; } assert( ( nc = ( custhdr_t * ) malloc( sizeof( custhdr_t ) ) ) ); nc->next = 0; c = c->next = nc; } strncpy( c->name, optarg, MAXNAME ); break; case 'l': if ( optarg[0] == 's' && strcmp( optarg, "script" ) == 0 ) { srcSta = SRC_SCRIPT; srcRem = SRC_R_SCRIPT; srcEnd = SRC_E_SCRIPT; } else if ( optarg[0] == 'f' && strcmp( optarg, "fortran" ) == 0 ) { srcSta = SRC_FORTRAN; srcRem = SRC_R_FORTRAN; srcEnd = SRC_E_FORTRAN; } else if ( optarg[0] == 'f' && strcmp( optarg, "fortran90" ) == 0 ) { srcSta = SRC_F90; srcRem = SRC_R_F90; srcEnd = SRC_E_F90; } else if ( optarg[0] == 't' && strcmp( optarg, "tex" ) == 0 ) { srcSta = SRC_TEX; srcRem = SRC_R_TEX; srcEnd = SRC_E_TEX; } else if ( optarg[0] == 'a' && strcmp( optarg, "acm" ) == 0 ) { srcSta = SRC_ACM; srcRem = SRC_R_ACM; srcEnd = SRC_E_ACM; } else { usage( ); } break; case 'x': strncpy( ctagsBin, optarg, MAXNAME ); break; case '?': case 'h': default: usage( ); break; } } /********** cmdLine */ /****f* ROBOhdrs/linenumCompare * NAME * linenumCompare * SYNOPSIS * static int linenumCompare(void const * a, void const * b) * SOURCE */ static int linenumCompare( void const *a, void const *b ) { ctag_t *ea = ( ctag_t * ) a, *eb = ( ctag_t * ) b; return ( ea->linenum - eb->linenum ); } /********** linenumCompare */ /****f* ROBOhdrs/arrangeCtags * NAME * arrangeCtags * SYNOPSIS * static void arrangeCtags(ctags_t *e) * SOURCE */ static void arrangeCtags( ctags_t * e ) { ctag_t *tmp, *ctag = e->ctag, *ep; assert( e && e->cnt && e->ctag ); tmp = ( ctag_t * ) malloc( e->cnt * sizeof( ctag_t ) ); assert( tmp ); for ( ep = tmp;; ) { memcpy( ep++, ctag, sizeof( ctag_t ) ); if ( ctag->next ) { ctag = ctag->next; } else { break; } } qsort( tmp, ( size_t ) ( e->cnt ), sizeof( ctag_t ), linenumCompare ); /* TODO: free ctag */ e->ctag = tmp; } /********** arrangeCtags */ /****f* ROBOhdrs/typeOk * NAME * typeOk * SYNOPSIS * static int typeOk(char *t) * SOURCE */ static int typeOk( char *t ) { /* return 1 if supported type for headers */ if ( t[0] == 'f' && strncmp( t, "function", 8 ) == 0 ) { return 1; } else if ( t[0] == 'v' && strncmp( t, "variable", 8 ) == 0 ) { return 1; /* global variable */ } #if 0 else if ( t[0] == 's' && strncmp( t, "struct", 6 ) == 0 ) { return 1; } #endif else if ( t[0] == 'm' && strncmp( t, "macro", 5 ) == 0 ) { return 1; } return 0; } /********** typeOk */ /****f* ROBOhdrs/initMe * NAME * initMe * SYNOPSIS * static void initMe(void) * SOURCE */ static void initMe( void ) { ctags = &myctags; memset( ctags, 0, sizeof( ctags_t ) ); projName[0] = '\0'; ctagsBin[0] = '\0'; vcTag[0] = '\0'; } /********** initMe */ /****f* ROBOhdrs/parseCtagsXLine * NAME * parseCtagsXLine * SYNOPSIS * static int parseCtagsXLine(char *buf, char *fname, char *name, char *decl, char *type, int *linenum) * SOURCE */ static int parseCtagsXLine( char *buf, char *fname, char *name, char *decl, char *type, int *linenum ) { char *t, *s; /* ctags -x output is: */ /* usage function 56 test.c void usage(void) */ sscanf( buf, "%s%s%d%s", name, type, linenum, fname ); s = strstr( buf, fname ); while ( *s++ != ' ' ) { } while ( *s == ' ' ) { ++s; } t = decl; while ( ( *t = *s++ ) != '\n' ) { ++t; } *t = '\0'; return 0; } /********** parseCtagsXLine */ /****f* ROBOhdrs/addList * NAME * addList * SYNOPSIS * static void addList(ctags_t *e, char *fname, char *name, char *decl, char *type, int linenum) * SOURCE */ static void addList( ctags_t * e, char *fname, char *name, char *decl, char *type, int linenum ) { ctag_t *newctag, *ctag = e->ctag; if ( !ctag ) { /* empty list */ ctag = ( ctag_t * ) malloc( sizeof( ctag_t ) ); assert( ctag ); memset( ctag, 0, sizeof( ctag_t ) ); e->ctag = ctag; } else { while ( ctag->next ) { ctag = ctag->next; } newctag = ( ctag_t * ) malloc( sizeof( ctag_t ) ); assert( newctag ); memset( newctag, 0, sizeof( ctag_t ) ); ctag->next = newctag; newctag->prev = ctag; ctag = newctag; } e->cnt++; strncpy( ctag->fname, fname, MAXNAME ); strncpy( ctag->name, name, MAXNAME ); strncpy( ctag->decl, decl, MAXLINE ); strncpy( ctag->type, type, MAXNAME ); ctag->linenum = linenum; } /********** addList */ /****f* ROBOhdrs/parseCtagsX * NAME * parseCtagsX * SYNOPSIS * static int parseCtagsX(FILE *fp) * SOURCE */ static int parseCtagsX( FILE * fp ) { char buf[MAXLINE + 1]; int lnum = 0, tagsParsed = 0; while ( fgets( buf, MAXLINE, fp ) != NULL ) { char decl[MAXNAME + 1], name[MAXNAME + 1]; char fname[MAXNAME + 1], type[MAXNAME + 1]; int linenum; lnum++; /* extract info from a line */ if ( parseCtagsXLine( buf, fname, name, decl, type, &linenum ) ) { printf( "error parsing line (%d)", lnum ); } else { addList( ctags, fname, name, decl, type, linenum ); tagsParsed++; } } /* end while() */ fclose( fp ); return tagsParsed; } /********** parseCtagsX */ /****f* ROBOhdrs/roboFileHeader * NAME * roboFileHeader * FUNCTION * Insert source file header. * SYNOPSIS * static void roboFileHeader(FILE *fp, char *proj, char *fname) * SOURCE */ static void roboFileHeader( FILE * fp, char *proj, char *fname ) { char *s; s = remark_markers[srcRem]; fprintf( fp, "%sh* %s/%s\n", header_markers[srcSta], ( proj[0] ? proj : fname ), fname ); fprintf( fp, "%s NAME\n", s ); fprintf( fp, "%s %s\n", s, fname ); if ( *vcTag ) { fprintf( fp, "%s %s\n", s, vcTag ); } fprintf( fp, "%s DESCRIPTION\n", s ); fprintf( fp, "%s*******%s\n", s, ( end_remark_markers[srcEnd] ? end_remark_markers[srcEnd] : "" ) ); } /********** roboFileHeader */ /****f* ROBOhdrs/roboHeader * NAME * roboHeader * SYNOPSIS * static void roboHeader(FILE *fp, char *fname, char *name, char *type, char *decl) * SOURCE */ static void roboHeader( FILE * fp, char *fname, char *name, char *type, char *decl ) { custhdr_t *c; char *s; s = remark_markers[srcRem]; if ( type[0] == 'v' ) { fprintf( fp, "%sv* %s/%s\n", header_markers[srcSta], fname, name ); } else if ( type[0] == 'm' ) { fprintf( fp, "%sd* %s/%s\n", header_markers[srcSta], fname, name ); } #if 0 else if ( type[0] == 's' ) { fprintf( fp, "/****s* %s/%s\n", fname, name ); } #endif else { fprintf( fp, "%sf* %s/%s\n", header_markers[srcSta], fname, name ); } fprintf( fp, "%s NAME\n%s %s\n", s, s, name ); if ( type[0] != 'm' ) { fprintf( fp, "%s SYNOPSIS\n%s %s\n", s, s, decl ); } if ( custhdrs ) { for ( c = custhdrs;; ) { fprintf( fp, "%s %s\n", s, c->name ); if ( c->next ) { c = c->next; } else { break; } } } if ( incSrc ) { fprintf( fp, "%s SOURCE\n%s\n", s, ( end_remark_markers[srcSta] ? end_remark_markers[srcSta] : s ) ); } else { fprintf( fp, "%s***%s\n", s, ( end_remark_markers[srcSta] ? end_remark_markers[srcSta] : "" ) ); } } /********** roboHeader */ /****f* ROBOhdrs/insertSrcEnd * NAME * insertSrcEnd * SYNOPSIS * static void insertSrcEnd(FILE *fp, char *funcname) * SOURCE */ static void insertSrcEnd( FILE * fp, char *funcname ) { fprintf( fp, "%s********* %s %s\n", end_markers[srcEnd], funcname, ( end_remark_markers[srcSta] ? end_remark_markers[srcSta] : "" ) ); } /********** insertSrcEnd */ /****f* ROBOhdrs/insertHeaders * NAME * insertHeaders * SYNOPSIS * static void insertHeaders(ctags_t *e, char *project, char *dstpath, char *srcpath) * SOURCE */ static void insertHeaders( ctags_t * e, char *project, char *dstpath, char *srcpath ) { FILE *ifp, *ofp; ctag_t *ctag = e->ctag; int lnum = 0, funcline = 0; char buf[MAXLINE], *funcname = 0; if ( !ctag || !dstpath || !srcpath ) { return; } assert( ofp = fopen( dstpath, "w" ) ); assert( ifp = fopen( srcpath, "r" ) ); /* include file header only if project name is defined */ if ( project ) { roboFileHeader( ofp, project, dstpath ); } while ( fgets( buf, MAXLINE, ifp ) != NULL ) { lnum++; while ( ctag->prev ) { ctag = ctag->prev; } for ( ;; ) { if ( incSrc && funcline && lnum >= funcline && ctag->linenum == lnum ) { funcline = 0; insertSrcEnd( ofp, funcname ); } if ( ctag->linenum == lnum ) { if ( typeOk( ctag->type ) ) { roboHeader( ofp, ctag->fname, ctag->name, ctag->type, ctag->decl ); funcline = lnum; funcname = ctag->name; } break; } else if ( ctag->next ) { ctag = ctag->next; } else { break; } } /* end ctag loop */ fprintf( ofp, "%s", buf ); } if ( incSrc && funcline ) { insertSrcEnd( ofp, funcname ); } fclose( ifp ); fclose( ofp ); } /********** insertHeaders */ /****f* ROBOhdrs/doCtagsExec * NAME * doCtagsExec * SYNOPSIS * static FILE * doCtagsExec(char *fname) * SOURCE */ static FILE * doCtagsExec( char *fname ) { int fd[2], pid; FILE *incoming; char *mybin, *bin = "ctags"; mybin = ( ctagsBin[0] ? ctagsBin : bin ); if ( pipe( fd ) == -1 ) { fprintf( stderr, "pipe failed\n" ); exit( 1 ); } if ( ( pid = fork( ) ) == 0 ) { close( 1 ); dup( fd[1] ); close( fd[0] ); if ( execlp( mybin, mybin, "-x", fname, NULL ) == -1 ) { fprintf( stderr, "execlp failed\n" ); exit( 1 ); } } else if ( pid == -1 ) { fprintf( stderr, "fork failed\n" ); exit( 1 ); } else { close( 0 ); dup( fd[0] ); close( fd[1] ); if ( ( incoming = fdopen( 0, "r" ) ) == NULL ) { fprintf( stderr, "fdopen failed\n" ); exit( 1 ); } } return incoming; } /********** doCtagsExec */ /****f* ROBOhdrs/doFile * NAME * doFile * SYNOPSIS * static void doFile(char *proj, char *fname) * SOURCE */ static void doFile( char *proj, char *fname ) { char buf[MAXLINE]; /* backup */ sprintf( buf, "/bin/cp -p %s %s~", fname, fname ); system( buf ); if ( parseCtagsX( doCtagsExec( fname ) ) < 1 ) { fprintf( stderr, "no tags\n" ); exit( 1 ); } arrangeCtags( ctags ); sprintf( buf, "%s~", fname ); insertHeaders( ctags, proj, fname, buf ); } /********** doFile */ /****f* ROBOhdrs/cleanUp * NAME * cleanUp * SYNOPSIS * static void cleanUp(void) * SOURCE */ static void cleanUp( void ) { } /********** cleanUp */ /****f* ROBOhdrs/main * NAME * main * SYNOPSIS * int main(int argc, char **argv) * SOURCE */ int main( int argc, char **argv ) { initMe( ); cmdLine( argc, argv ); argc -= optind; argv += optind; if ( argc == 1 ) { doFile( projName, argv[0] ); } else { usage( ); } cleanUp( ); return 0; } /********** main */ #endif /* HAVE_FORK */