/* catex.c cat and expand files. The list of arguments is interpreted as filenames. The files are read in turn and copied to standard output. During this process, certain special text strings are scanned for and processed without copying. If the string "%include name%" is found, the name is interpreted as a file name; reading of the original file is suspended, and the new file is inserted in the output stream. A file being included can itself contain "%include newname%" instructions, and such new files will be inserted appropriately. Calls to include files can be nested to any depth. (Including a file recursively will cause an infinite loop.) A file-name is interpreted relative to the file from which it was called. Names beginning with '..' are interpreted as being in a higher level directory. A string "%DATE%" within a file will be replaced by the current local date. Macros can be defined using the form "%DM,name:string:%", and such strings will not be copied to the output. These set up text substitutions that will apply to the remainder of the document. Such macros can be invoked with "%M,name%", so that this text will be replaced by the string in the definition. Invocation is recursive, by which I mean that the replacement text is not simply copied directly to standard output, but instead is processed as the input stream. Within this replacement text may be other macro invocations ("%M,othername%"), new macro definitions, or "%include.." directives. (A macro cannot be redefined within its own body; our simple syntax will not allow colons to appear in the 'string' part of a definition). Author: Graham Freeman Copyright: Graham Freeman, March 1994. */ #include #include #include #include #define Fnamelen 512 typedef struct { long int pos; char *name; } filepos; typedef struct dmac { char *name; char *subst; struct dmac *next; } macro; void copyout( filepos *file ); void processline(filepos *file, char line[], int *bcnt); void defmac(filepos *file, char line[], int *bcnt); void incproc(filepos *file, char line[], int *bcnt); void invmac(filepos *file, char line[], int *bcnt); void dateout(void); /* the program assumes the keywords are unique */ char *kword[] = {"%include ", "%DATE%", "%DM,", "%M,"}; void (*kwfn[])() = {incproc, dateout, defmac, invmac}; char *incl, *date, datefmt[] = "%Y %B %d"; /* This format gives Year Month Day */ #define delim '%' #define macdelim ':' char macdstr[2]= {macdelim,'\0'}; FILE *fp; FILE *output; macro *maclist = NULL; #if defined(__MSDOS__) || defined(WIN32) char filesep = '\\'; #else char filesep = '/'; #endif int main( int argc, char *argv[] ) { filepos *file; int i, j; output = stdout; incl = kword[0]; date = kword[1]; if (argc < 2) { fprintf(stderr, "catex expects arguments, the files to be concatenated to output\n" " If any of these contain the string %%include 'name'%%, the file\n" " 'name' will be inserted at that point.\n" " The name may begin with '..%c'. The path is interpreted\n" " relative to the file from which it was called.\n\n" " Also processed are %%DATE%%, %%DM,name:subst:%% and %%M,name%%\n" " DM defines a macro, which can be invoked with M.\n" " eg. %%DM,P1:%%P,10,-10,-.5%%:%% will cause any future %%M,P1%%\n" " to be replaced with %%P,10,-10,-.5%% in the output file.\n" ,filesep); exit(1); } file = (filepos *) malloc(sizeof(filepos)); for (i=1; i') { char *mode = "w"; j = 1; if (argv[i][j] == '>') { mode = "a"; ++j; } if (strlen(&argv[i][j]) == 0) { ++i; j = 0; } if ( (output = fopen(&argv[i][j], mode)) == NULL) { fprintf(stderr, "catex: unable to open output file %s\n", &argv[i][j]); exit(1); } break; } } for (i=1; i') { j = 1; if (argv[i][j] == '>') ++j; if (strlen(&argv[i][j]) == 0) ++i; continue; } file->name = argv[i]; file->pos = 0; #if defined(__MSDOS__) || defined(WIN32) if ((fp = fopen(file->name, "rb")) == NULL) #else if ((fp = fopen(file->name, "r")) == NULL) #endif { fprintf(stderr,"Unable to open file %s. catex abandoned\n", file->name); exit(1); } copyout(file); fclose(fp); } } void copyout( filepos *file ) /* Copy the given file to output, checking for strings such as '%include', which will need to be expanded. 'Include' processing is performed recursively. The global "fp" is the file actually read. */ { int size = 1024; char *line = (char*)malloc(size); while (fgets(line,size,fp) != NULL) { int len = strlen(line); int bcnt = 0; char * success ; while (len == size-1 && line[len-1] != '\r' && line[len-1] != '\n') { line = (char*) realloc( line, size*2); success = fgets(&line[size-1], size+1, fp); if (!success) break; size *= 2; len = strlen(line); } processline(file,line,&bcnt); } free(line); } void processline(filepos *file, char line[], int *bcnt) { char ch; unsigned long int match = 0; int ipos = 0; static int lastch; int lastmatch = 0; while ((ch = line[*bcnt]) != '\0') { ++ *bcnt; /* if a match is underway, continue it */ if (match != 0) { int wd = -1; for (wd = 0; wd < sizeof(kword)/sizeof(char *); ++wd) { if ((1<= Fnamelen) { fputs(incl,output); newname[++npos] = '\0'; fputs(newname,output); free(newname); return; } else if (ch != delim) npos++; else /* trailing % found */ { newname[npos] = '\0'; newfile = (filepos *) malloc(sizeof(filepos)); /* Build new file name from path of previous file */ { char *firstch; firstch = newname + strspn(newname," "); if (*firstch == filesep ) newfile->name = firstch; else { int i, m, n; char dotdotsl[4] = "../"; char *pt; m = strlen(firstch); for (i=0; i<=m; i++) /*move to RH end of newname*/ newname[Fnamelen-1-i] = firstch[m-i]; firstch = &newname[Fnamelen-1-m]; n = 0; newname[0] = '\0'; pt = strrchr(file->name,filesep); if (pt != NULL && #if defined(__MSDOS__) || defined(WIN32) *(firstch+1) != ':' && #endif *firstch != '~' && *firstch != filesep) { /* Process leading '../' strings */ /*copy name of calling file to front of newname*/ n = pt - file->name + 1; strncpy( newname, file->name, n); newname[n] = '\0'; dotdotsl[2] = filesep; while (strncmp(firstch,dotdotsl,3) == 0) { char *slashpt; firstch += 3; newname[n-1] = '\0'; slashpt = strrchr(newname,filesep); n = (slashpt==NULL)? 0: slashpt - newname + 1; newname[n] = '\0'; if (slashpt == NULL) break; } } if (*firstch == '~' && firstch[1] == filesep) { char *env; env = getenv("HOME"); strcpy(newname,env); n = strlen(newname); newname[n++] = filesep; newname[n] = '\0'; firstch = &firstch[2]; } /* Now fix any $ strings */ while ((pt = strchr(firstch,'$')) != NULL) { char *env; m = pt - firstch; strncpy( &newname[n], firstch, m); n += m; firstch = pt; pt = strchr(firstch,filesep); if (pt != NULL) *pt = '\0'; env = getenv(&firstch[1]); if (env == NULL) env = firstch; if (env[0] == filesep) n = 0; strcpy( &newname[n], env); n += strlen(env); if (pt != NULL) { newname[n++] = filesep; newname[n] = '\0'; firstch = pt+1; } else *firstch = '\0'; } strcpy( &newname[n], firstch); newfile->name = newname; } } file->pos = ftell(fp); newfile->pos = 0; fclose(fp); #if defined(__MSDOS__) || defined(WIN32) if ((fp = fopen(newfile->name, "rb")) == NULL) #else if ((fp = fopen(newfile->name, "r")) == NULL) #endif { fprintf(stderr, "Unable to open file \"%s\". catex abandoned\n", newfile->name); exit(1); } copyout(newfile); free(newfile); fclose(fp); #if defined(__MSDOS__) || defined(WIN32) fp = fopen(file->name, "rb"); #else fp = fopen(file->name, "r"); #endif fseek(fp,file->pos,0); break; } } free(newname); } void defmac(filepos *file, char line[], int *bpos) /* Define a new macro */ { int ipos = 0, colons[2]; char macbuf[1024], ch; int macstate = 0, mpos = 0; while ((ch = line[*bpos+mpos]) != '\0') { ++ mpos; if (ch == macdelim) { /* colons */ if (macstate > 1) break; colons[macstate] = ipos; ++macstate; } else if (macstate == 2 && ch == delim) { /* % after colons */ int i,j,k; macro *thismac; macbuf[ipos] = '\0'; if (ipos != colons[1]+1) break; i = strspn(macbuf," "); j = strcspn(&macbuf[i],macdstr); if (j == 0) break; *bpos += mpos; thismac = (macro *) malloc(sizeof(macro)); thismac->name = (char *) malloc(j+1); strncpy(thismac->name,&macbuf[i],j); thismac->name[j] = '\0'; k = colons[1] - colons[0]; thismac->subst = (char *) malloc(k); strncpy(thismac->subst,&macbuf[1+colons[0]],k-1); thismac->subst[k-1] = '\0'; if (maclist == NULL) { /* Place item at start of new list */ maclist = thismac; thismac->next = NULL; return; } else { /* Place in sorted position in list */ macro *pt; macro *prev = NULL; for (pt=maclist; pt!=NULL; pt=pt->next) { int cmp = strcmp(pt->name,thismac->name); if (cmp == 0) { /* the macro already exists; replace it */ free(pt->subst); pt->subst = thismac->subst; free(thismac->name); free(thismac); return; } if (cmp > 0) { if (prev == NULL) { /* Place it at the front of the list */ thismac->next = maclist; maclist = thismac; } else { thismac->next = prev->next; prev->next = thismac; } return; } if (pt->next == NULL) { pt->next = thismac; thismac->next = NULL; return; } prev = pt; } } } macbuf[ipos] = ch; ++ipos; if (ch == '\n') break; } /* improper macro; simply output keyword and reprocess the rest */ { int i; for (i=0; inext) { int cmp = strcmp(macname,pt->name); if (cmp == 0) { /* process the substitute text for any further macros etc */ int i=0; processline(file,pt->subst,&i); return; } if (cmp < 0) break; } } macname[ipos] = ch; ++ipos; if (ch == '\n' || ch == delim) break; } /* improper macro; simply output */ { int i; for (i=0; i