Expand the substitutions in FILE\n");
+ exit(1);
+}
+
+static void addMacroReplacements(MAC_HANDLE *macPvt,char *pval)
+{
+ char **pairs;
+ long status;
+
+ status = macParseDefns(macPvt,pval,&pairs);
+ if(status==-1) {
+ fprintf(stderr,"msi: Error from macParseDefns\n");
+ usageExit();
+ }
+ if(status) {
+ status = macInstallMacros(macPvt,pairs);
+ if(!status) {
+ fprintf(stderr,"Error from macInstallMacros\n");
+ usageExit();
+ }
+ free(pairs);
+ }
+}
+
+typedef enum {cmdInclude,cmdSubstitute} cmdType;
+static const char *cmdNames[] = {"include","substitute"};
+
+static void makeSubstitutions(inputData *inputPvt, MAC_HANDLE *macPvt, char *templateName)
+{
+ char *input;
+ static char buffer[MAX_BUFFER_SIZE];
+ int n;
+
+ inputBegin(inputPvt,templateName);
+ while((input = inputNextLine(inputPvt))) {
+ int expand=1;
+ char *p;
+ char *command = 0;
+
+ p = input;
+ /*skip whitespace at beginning of line*/
+ while(*p && (isspace(*p))) ++p;
+ /*Look for i or s */
+ if(*p && (*p=='i' || *p=='s')) command = p;
+ if(command) {
+ char *pstart;
+ char *pend;
+ char *copy;
+ int cmdind=-1;
+ int i;
+
+ for(i=0; i< NELEMENTS(cmdNames); i++) {
+ if(strstr(command,cmdNames[i])) {
+ cmdind = i;
+ }
+ }
+ if(cmdind<0) goto endif;
+ p = command + strlen(cmdNames[cmdind]);
+ /*skip whitespace after command*/
+ while(*p && (isspace(*p))) ++p;
+ /*Next character must be quote*/
+ if((*p==0) || (*p!='"')) goto endif;
+ pstart = ++p;
+ /*Look for end quote*/
+ while(*p && (*p!='"')) {
+ /*allow escape for imbeded quote*/
+ if((*p=='\\') && *(p+1)=='"') {
+ p += 2; continue;
+ } else {
+ if(*p=='"') break;
+ }
+ ++p;
+ }
+ pend = p;
+ if(*p==0) goto endif;
+ /*skip quote and any trailing blanks*/
+ while(*++p==' ') ;
+ if(*p != '\n' && *p !=0) goto endif;
+ copy = calloc(pend-pstart+1,sizeof(char));
+ strncpy(copy,pstart,pend-pstart);
+ switch(cmdind) {
+ case cmdInclude:
+ inputNewIncludeFile(inputPvt,copy);
+ break;
+ case cmdSubstitute:
+ addMacroReplacements(macPvt,copy);
+ break;
+ default:
+ fprintf(stderr,"msi: Logic error in makeSubstitutions\n");
+ inputErrPrint(inputPvt);
+ exit(1);
+ }
+ free(copy);
+ expand = 0;
+ }
+endif:
+ if (expand) {
+ n = macExpandString(macPvt,input,buffer,MAX_BUFFER_SIZE-1);
+ fputs(buffer,stdout);
+ if (!dontWarnUndef && n<0) {
+ fprintf(stderr,"msi: Warning, undefined macros present\n");
+ dontWarnUndef++;
+ }
+ }
+ }
+}
+
+typedef struct inputFile{
+ ELLNODE node;
+ char *filename;
+ FILE *fp;
+ int lineNum;
+}inputFile;
+
+typedef struct pathNode {
+ ELLNODE node;
+ char *directory;
+} pathNode;
+
+struct inputData {
+ ELLLIST inputFileList;
+ ELLLIST pathList;
+ char inputBuffer[MAX_BUFFER_SIZE];
+};
+
+static void inputOpenFile(inputData *pinputData,char *filename);
+static void inputCloseFile(inputData *pinputData);
+static void inputCloseAllFiles(inputData *pinputData);
+
+static void inputConstruct(inputData **ppvt)
+{
+ inputData *pinputData;
+
+ pinputData = calloc(1,sizeof(inputData));
+ ellInit(&pinputData->inputFileList);
+ ellInit(&pinputData->pathList);
+ *ppvt = pinputData;
+}
+
+static void inputDestruct(inputData *pinputData)
+{
+ pathNode *ppathNode;
+
+ inputCloseAllFiles(pinputData);
+ while((ppathNode = (pathNode *)ellFirst(&pinputData->pathList))) {
+ ellDelete(&pinputData->pathList,&ppathNode->node);
+ free(ppathNode->directory);
+ free(ppathNode);
+ }
+ free(pinputData);
+}
+
+static void inputAddPath(inputData *pinputData, char *path)
+{
+ ELLLIST *ppathList = &pinputData->pathList;
+ pathNode *ppathNode;
+ const char *pcolon;
+ const char *pdir;
+ int len;
+ int emptyName;
+ const char sep = *OSI_PATH_LIST_SEPARATOR;
+
+ pdir = path;
+ /*an empty name at beginning, middle, or end means current directory*/
+ while(pdir && *pdir) {
+ emptyName = ((*pdir == sep) ? 1 : 0);
+ if(emptyName) ++pdir;
+ ppathNode = (pathNode *)calloc(1,sizeof(pathNode));
+ ellAdd(ppathList,&ppathNode->node);
+ if(!emptyName) {
+ pcolon = strchr(pdir,sep);
+ len = (pcolon ? (pcolon - pdir) : strlen(pdir));
+ if(len>0) {
+ ppathNode->directory = (char *)calloc(len+1,sizeof(char));
+ strncpy(ppathNode->directory,pdir,len);
+ pdir = pcolon;
+ /*unless at end skip past first colon*/
+ if(pdir && *(pdir+1)!=0) ++pdir;
+ } else { /*must have been trailing : */
+ emptyName=1;
+ }
+ }
+ if(emptyName) {
+ ppathNode->directory = (char *)calloc(2,sizeof(char));
+ strcpy(ppathNode->directory,".");
+ }
+ }
+ return;
+}
+
+static void inputBegin(inputData *pinputData, char *fileName)
+{
+ inputCloseAllFiles(pinputData);
+ inputOpenFile(pinputData,fileName);
+}
+
+static char *inputNextLine(inputData *pinputData)
+{
+ inputFile *pinputFile;
+ char *pline;
+
+ while((pinputFile = (inputFile *)ellFirst(&pinputData->inputFileList))) {
+ pline = fgets(pinputData->inputBuffer,MAX_BUFFER_SIZE,pinputFile->fp);
+ if(pline) {
+ ++pinputFile->lineNum;
+ return(pline);
+ }
+ inputCloseFile(pinputData);
+ }
+ return(0);
+}
+
+static void inputNewIncludeFile(inputData *pinputData, char *name)
+{
+ inputOpenFile(pinputData,name);
+}
+
+static void inputErrPrint(inputData *pinputData)
+{
+ inputFile *pinputFile;
+
+ fprintf(stderr,"input: '%s' at ",pinputData->inputBuffer);
+ pinputFile = (inputFile *)ellFirst(&pinputData->inputFileList);
+ while(pinputFile) {
+ fprintf(stderr,"line %d of ",pinputFile->lineNum);
+ if(pinputFile->filename) {
+ fprintf(stderr," file %s\n",pinputFile->filename);
+ } else {
+ fprintf(stderr,"stdin:\n");
+ }
+ pinputFile = (inputFile *)ellNext(&pinputFile->node);
+ if(pinputFile) {
+ fprintf(stderr," included from ");
+ } else {
+ fprintf(stderr,"\n");
+ }
+ }
+ fprintf(stderr,"\n");
+}
+
+static void inputOpenFile(inputData *pinputData,char *filename)
+{
+ ELLLIST *ppathList = &pinputData->pathList;
+ pathNode *ppathNode = 0;
+ inputFile *pinputFile;
+ char *fullname = 0;
+ FILE *fp = 0;
+
+ if(!filename) {
+ fp = stdin;
+ } else if((ellCount(ppathList)==0) || strchr(filename,'/')){
+ fp = fopen(filename,"r");
+ } else {
+ ppathNode = (pathNode *)ellFirst(ppathList);
+ while(ppathNode) {
+ fullname = calloc(strlen(filename)+strlen(ppathNode->directory) +2,
+ sizeof(char));
+ strcpy(fullname,ppathNode->directory);
+ strcat(fullname,"/");
+ strcat(fullname,filename);
+ fp = fopen(fullname,"r");
+ if(fp) break;
+ free(fullname);
+ ppathNode = (pathNode *)ellNext(&ppathNode->node);
+ }
+ }
+ if(!fp) {
+ fprintf(stderr,"msi: Can't open file '%s'\n",filename);
+ inputErrPrint(pinputData);
+ exit(1);
+ }
+ pinputFile = calloc(1,sizeof(inputFile));
+ if(ppathNode) {
+ pinputFile->filename = fullname;
+ } else if(filename) {
+ pinputFile->filename = epicsStrDup(filename);
+ } else {
+ pinputFile->filename = epicsStrDup("stdin");
+ }
+ pinputFile->fp = fp;
+ ellInsert(&pinputData->inputFileList,0,&pinputFile->node);
+}
+
+static void inputCloseFile(inputData *pinputData)
+{
+ inputFile *pinputFile;
+
+ pinputFile = (inputFile *)ellFirst(&pinputData->inputFileList);
+ if(!pinputFile) return;
+ ellDelete(&pinputData->inputFileList,&pinputFile->node);
+ if(fclose(pinputFile->fp))
+ fprintf(stderr,"msi: Can't close input file '%s'\n",pinputFile->filename);
+ free(pinputFile->filename);
+ free(pinputFile);
+}
+
+static void inputCloseAllFiles(inputData *pinputData)
+{
+ inputFile *pinputFile;
+
+ while((pinputFile=(inputFile *)ellFirst(&pinputData->inputFileList))){
+ inputCloseFile(pinputData);
+ }
+}
+
+/*start of code that handles substitution file*/
+typedef enum {
+ tokenLBrace,tokenRBrace,tokenSeparater,tokenString,tokenEOF
+}tokenType;
+
+typedef struct subFile {
+ char *substitutionName;
+ FILE *fp;
+ int lineNum;
+ char inputBuffer[MAX_BUFFER_SIZE];
+ char *pnextChar;
+ tokenType token;
+ char string[MAX_BUFFER_SIZE];
+} subFile;
+
+typedef struct patternNode {
+ ELLNODE node;
+ char *var;
+} patternNode;
+
+struct subInfo {
+ subFile *psubFile;
+ int isFile;
+ char *filename;
+ int isPattern;
+ ELLLIST patternList;
+ size_t size;
+ size_t curLength;
+ char *macroReplacements;
+};
+
+static char *subGetNextLine(subFile *psubFile);
+static tokenType subGetNextToken(subFile *psubFile);
+static void subFileErrPrint(subFile *psubFile,char * message);
+static void freeSubFile(subInfo *psubInfo);
+static void freePattern(subInfo *psubInfo);
+static void catMacroReplacements(subInfo *psubInfo,const char *value);
+
+void freeSubFile(subInfo *psubInfo)
+{
+ subFile *psubFile = psubInfo->psubFile;
+ if(psubFile->fp) {
+ if(fclose(psubFile->fp))
+ fprintf(stderr,"msi: Can't close substitution file\n");
+ }
+ free(psubFile);
+ free(psubInfo->filename);
+ psubInfo->psubFile = 0;
+}
+
+void freePattern(subInfo *psubInfo)
+{
+ patternNode *ppatternNode;
+ while((ppatternNode = (patternNode *)ellFirst(&psubInfo->patternList))) {
+ ellDelete(&psubInfo->patternList,&ppatternNode->node);
+ free(ppatternNode->var);
+ free(ppatternNode);
+ }
+ psubInfo->isPattern = 0;
+}
+
+static void substituteDestruct(subInfo *psubInfo)
+{
+ freeSubFile(psubInfo);
+ freePattern(psubInfo);
+ free(psubInfo);
+ return;
+}
+
+static void substituteOpen(subInfo **ppvt,char *substitutionName)
+{
+ subInfo *psubInfo;
+ subFile *psubFile;
+ FILE *fp;
+
+ psubInfo = calloc(1,sizeof(subInfo));
+ *ppvt = psubInfo;
+ psubFile = calloc(1,sizeof(subFile));
+ psubInfo->psubFile = psubFile;
+ ellInit(&psubInfo->patternList);
+ fp = fopen(substitutionName,"r");
+ if(!fp) {
+ fprintf(stderr,"msi: Can't open file '%s'\n",substitutionName);
+ exit(1);
+ }
+ psubFile->substitutionName = substitutionName;
+ psubFile->fp = fp;
+ psubFile->lineNum = 1;
+ psubFile->inputBuffer[0] = 0;
+ psubFile->pnextChar = &psubFile->inputBuffer[0];
+ subGetNextToken(psubFile);
+ return;
+}
+
+static int substituteGetGlobalSet(subInfo *psubInfo)
+{
+ subFile *psubFile = psubInfo->psubFile;
+
+ while(psubFile->token==tokenSeparater) subGetNextToken(psubFile);
+ if(psubFile->token==tokenString && strcmp(psubFile->string,"global")==0) {
+ subGetNextToken(psubFile);
+ return(1);
+ }
+ return(0);
+}
+
+static int substituteGetNextSet(subInfo *psubInfo,char **filename)
+{
+ subFile *psubFile = psubInfo->psubFile;
+ patternNode *ppatternNode;
+
+ *filename = 0;
+ while(psubFile->token==tokenSeparater) subGetNextToken(psubFile);
+ if(psubFile->token==tokenEOF) return(0);
+ if(psubFile->token==tokenString && strcmp(psubFile->string,"file")==0) {
+ psubInfo->isFile = 1;
+ if(subGetNextToken(psubFile)!=tokenString) {
+ subFileErrPrint(psubFile,"Parse error, expecting filename");
+ exit(1);
+ }
+ freePattern(psubInfo);
+ free(psubInfo->filename);
+ if(psubFile->string[0]=='"'&&psubFile->string[strlen(psubFile->string)-1]=='"') {
+ psubFile->string[strlen(psubFile->string)-1]='\0';
+ psubInfo->filename = macEnvExpand(psubFile->string+1);
+ }
+ else {
+ psubInfo->filename = macEnvExpand(psubFile->string);
+ }
+ while(subGetNextToken(psubFile)==tokenSeparater);
+ if(psubFile->token!=tokenLBrace) {
+ subFileErrPrint(psubFile,"Parse error, expecting {");
+ exit(1);
+ }
+ subGetNextToken(psubFile);
+ }
+ *filename = psubInfo->filename;
+ while(psubFile->token==tokenSeparater) subGetNextToken(psubFile);
+ if(psubFile->token==tokenLBrace) return(1);
+ if(psubFile->token==tokenRBrace) return(1);
+ if(psubFile->token!=tokenString
+ || strcmp(psubFile->string,"pattern")!=0) {
+ subFileErrPrint(psubFile,"Parse error, expecting pattern");
+ exit(1);
+ }
+ freePattern(psubInfo);
+ psubInfo->isPattern = 1;
+ while(subGetNextToken(psubFile)==tokenSeparater);
+ if(psubFile->token!=tokenLBrace) {
+ subFileErrPrint(psubFile,"Parse error, expecting {");
+ exit(1);
+ }
+ while(1) {
+ while(subGetNextToken(psubFile)==tokenSeparater);
+ if(psubFile->token!=tokenString) break;
+ ppatternNode = calloc(1,sizeof(patternNode));
+ ellAdd(&psubInfo->patternList,&ppatternNode->node);
+ ppatternNode->var = epicsStrDup(psubFile->string);
+ }
+ if(psubFile->token!=tokenRBrace) {
+ subFileErrPrint(psubFile,"Parse error, expecting }");
+ exit(1);
+ }
+ subGetNextToken(psubFile);
+ return(1);
+}
+
+static char *substituteGetGlobalReplacements(subInfo *psubInfo)
+{
+ subFile *psubFile = psubInfo->psubFile;
+
+ if(psubInfo->macroReplacements) psubInfo->macroReplacements[0] = 0;
+ psubInfo->curLength = 0;
+ while(psubFile->token==tokenSeparater) subGetNextToken(psubFile);
+ if(psubFile->token==tokenRBrace && psubInfo->isFile) {
+ psubInfo->isFile = 0;
+ free(psubInfo->filename);
+ psubInfo->filename = 0;
+ freePattern(psubInfo);
+ subGetNextToken(psubFile);
+ return(0);
+ }
+ if(psubFile->token==tokenEOF) return(0);
+ if(psubFile->token!=tokenLBrace) return(0);
+ while(1) {
+ switch(subGetNextToken(psubFile)) {
+ case tokenRBrace:
+ subGetNextToken(psubFile);
+ if (!psubInfo->macroReplacements) {
+ catMacroReplacements(psubInfo,"");
+ }
+ return(psubInfo->macroReplacements);
+ case tokenSeparater:
+ catMacroReplacements(psubInfo,",");
+ break;
+ case tokenString:
+ catMacroReplacements(psubInfo,psubFile->string);
+ break;
+ default:
+ subFileErrPrint(psubFile,"Parse error, illegal token");
+ exit(1);
+ }
+ }
+}
+
+static char *substituteGetReplacements(subInfo *psubInfo)
+{
+ subFile *psubFile = psubInfo->psubFile;
+ patternNode *ppatternNode;
+
+ if(psubInfo->macroReplacements) psubInfo->macroReplacements[0] = 0;
+ psubInfo->curLength = 0;
+ while(psubFile->token==tokenSeparater) subGetNextToken(psubFile);
+ if(psubFile->token==tokenRBrace && psubInfo->isFile) {
+ psubInfo->isFile = 0;
+ free(psubInfo->filename);
+ psubInfo->filename = 0;
+ freePattern(psubInfo);
+ subGetNextToken(psubFile);
+ return(0);
+ }
+ if(psubFile->token==tokenEOF) return(0);
+ if(psubFile->token!=tokenLBrace) return(0);
+ if(psubInfo->isPattern) {
+ int gotFirstPattern = 0;
+
+ while(subGetNextToken(psubFile)==tokenSeparater);
+ ppatternNode = (patternNode *)ellFirst(&psubInfo->patternList);
+ while(1) {
+ if(psubFile->token==tokenRBrace) {
+ subGetNextToken(psubFile);
+ return(psubInfo->macroReplacements);
+ }
+ if(psubFile->token!=tokenString) {
+ subFileErrPrint(psubFile,"Parse error, illegal token");
+ exit(-1);
+ }
+ if(gotFirstPattern) catMacroReplacements(psubInfo,",");
+ gotFirstPattern = 1;
+ if(ppatternNode) {
+ catMacroReplacements(psubInfo,ppatternNode->var);
+ catMacroReplacements(psubInfo,"=");
+ catMacroReplacements(psubInfo,psubFile->string);
+ ppatternNode = (patternNode *)ellNext(&ppatternNode->node);
+ } else {
+ subFileErrPrint(psubFile,"Warning, too many values given");
+ }
+ while(subGetNextToken(psubFile)==tokenSeparater);
+ }
+ } else while(1) {
+ switch(subGetNextToken(psubFile)) {
+ case tokenRBrace:
+ subGetNextToken(psubFile);
+ if (!psubInfo->macroReplacements) {
+ catMacroReplacements(psubInfo,"");
+ }
+ return(psubInfo->macroReplacements);
+ case tokenSeparater:
+ catMacroReplacements(psubInfo,",");
+ break;
+ case tokenString:
+ catMacroReplacements(psubInfo,psubFile->string);
+ break;
+ default:
+ subFileErrPrint(psubFile,"Parse error, illegal token");
+ exit(1);
+ }
+ }
+}
+
+static char *subGetNextLine(subFile *psubFile)
+{
+ char *pline;
+
+ do {
+ pline = fgets(psubFile->inputBuffer,MAX_BUFFER_SIZE,psubFile->fp);
+ ++psubFile->lineNum;
+ } while(pline && psubFile->inputBuffer[0]=='#');
+ if(!pline) {
+ psubFile->token = tokenEOF;
+ psubFile->inputBuffer[0] = 0;
+ psubFile->pnextChar = 0;
+ return(0);
+ }
+ psubFile->pnextChar = &psubFile->inputBuffer[0];
+ return(&psubFile->inputBuffer[0]);
+}
+
+static void subFileErrPrint(subFile *psubFile,char * message)
+{
+ fprintf(stderr,"msi: %s\n",message);
+ fprintf(stderr," in substitution file '%s' at line %d:\n %s",
+ psubFile->substitutionName,
+ psubFile->lineNum,psubFile->inputBuffer);
+}
+
+
+static tokenType subGetNextToken(subFile *psubFile)
+{
+ char *p;
+ char *pto;
+
+ p = psubFile->pnextChar;
+ if(!p) { psubFile->token = tokenEOF; return(tokenEOF);}
+ if(*p==0 || *p=='\n' || *p=='#') {
+ p = subGetNextLine(psubFile);
+ if(!p) { psubFile->token = tokenEOF; return(tokenEOF);}
+ else { psubFile->token = tokenSeparater; return(tokenSeparater);}
+ }
+ while(isspace(*p)) p++;
+ if(*p=='{') {
+ psubFile->token = tokenLBrace;
+ psubFile->pnextChar = ++p;
+ return(tokenLBrace);
+ }
+ if(*p=='}') {
+ psubFile->token = tokenRBrace;
+ psubFile->pnextChar = ++p;
+ return(tokenRBrace);
+ }
+ if(*p==0 || isspace(*p) || *p==',') {
+ while(isspace(*p) || *p==',') p++;
+ psubFile->token = tokenSeparater;
+ psubFile->pnextChar = p;
+ return(tokenSeparater);
+ }
+ /*now handle quoted strings*/
+ if(*p=='"') {
+ pto = &psubFile->string[0];
+ *pto++ = *p++;
+ while(*p!='"') {
+ if(*p==0 || *p=='\n') {
+ subFileErrPrint(psubFile,"Strings must be on single line\n");
+ exit(1);
+ }
+ /*allow escape for imbeded quote*/
+ if((*p=='\\') && *(p+1)=='"') {
+ *pto++ = *p++;
+ *pto++ = *p++;
+ continue;
+ }
+ *pto++ = *p++;
+ }
+ *pto++ = *p++;
+ psubFile->pnextChar = p;
+ *pto = 0;
+ psubFile->token = tokenString;
+ return(tokenString);
+ }
+ /*Now take anything up to next non String token and not space*/
+ pto = &psubFile->string[0];
+ while(!isspace(*p) && (strspn(p,"\",{}")==0)) *pto++ = *p++;
+ *pto = 0;
+ psubFile->pnextChar = p;
+ psubFile->token = tokenString;
+ return(tokenString);
+}
+
+static void catMacroReplacements(subInfo *psubInfo,const char *value)
+{
+ size_t len = strlen(value);
+
+ if(psubInfo->size <= (psubInfo->curLength + len)) {
+ size_t newsize = psubInfo->size + MAX_BUFFER_SIZE;
+ char *newbuf;
+
+ if(newsize <= psubInfo->curLength + len)
+ newsize = psubInfo->curLength + len + 1;
+ newbuf = calloc(1,newsize);
+ if(!newbuf) {
+ fprintf(stderr,"calloc failed for size %Zu\n",newsize);
+ exit(1);
+ }
+ if(psubInfo->macroReplacements) {
+ memcpy(newbuf,psubInfo->macroReplacements,psubInfo->curLength);
+ free(psubInfo->macroReplacements);
+ }
+ psubInfo->size = newsize;
+ psubInfo->macroReplacements = newbuf;
+ }
+ strcat(psubInfo->macroReplacements,value);
+ psubInfo->curLength += len;
+}
diff --git a/src/ioc/dbtemplate/msi.html b/src/ioc/dbtemplate/msi.html
new file mode 100644
index 000000000..b7e4e9e9e
--- /dev/null
+++ b/src/ioc/dbtemplate/msi.html
@@ -0,0 +1,438 @@
+
+
+
+
+
+
+
+
+
+msi: Macro Substitution and Include Tool
+
+Introduction
+
+msi is a general purpose macro substitution/include tool. It accepts as input
+an ascii template file. It looks for lines containing two reserved command
+names: include and substitute. It also looks for and performs
+substitutions on macros of the form $(var) and ${var}. It uses the macLib
+routines from EPICS Base to perform the substitutions, so it also accepts the
+default value and value definition syntax that macLib implements.
+
+msi also allows substitutions to be specified via a separate substitution
+file. This substitution file allows the same format as the substitution files
+accepted by the EPICS IOC's dbLoadTemplate command.
+
+Command Syntax:
+
+msi -V -g -ooutfile -Idir -Msubs -Ssubfile template
+
+All parameters are optional. The -o, -I, -M, and -S switches may be
+separated from their associated value string by spaces if desired. Output will
+be written to stdout unless the -o option is given.
+
+Switches have the following meanings:
+
+
+ - -V
+ - Verbose warnings; if this parameter is specified then any undefined
+ macro discovered in the template file which does not have an associated
+ default value is considered an error. An error message is generated, and
+ when msi terminates it will do so with an exit status of 2.
+
+ - -g
+ - When this flag is given all macros defined in a substitution file will
+ have global scope and thus their values will persist until a new value is
+ given for this macro. This flag is provided for backwards compatibility as
+ this was the behavior of previous versions of msi, but it does not follow
+ common scoping rules and is discouraged.
+
+ - -o file
+ - Output will be written to the specifed file rather than to the standard
+ output.
+
+ - -I dir
+ - This parameter, which may be repeated or contain a colon-separated (or
+ semi-colon separated on Windows) list of directory paths, specifies a search
+ path for include commands. For example:
+
+
+ msi -I /home/mrk/examples:. -I.. template
+
+
+ specifies that all named files should be searched for in the following
+ locations in the order given:
+
+
+ - /home/mrk/examples
+ - . (the current directory)
+ - .. (the parent of the current directory)
+
+
+
+ - -M substitutions
+ - This parameter specifies macro values for the template instance.
+ Multiple macro values can be specified in one substitution parameter, or in
+ multiple -M parameters. For example:
+
+
+ msi -M "a=aval,b=bval" -Mc=cval template
+
+
+ specifies that in the template file each occurrence of:
+
+
+ - $(a) or ${a} is replaced by aval
+ - $(b) or ${b} is replaced by bval
+ - $(c) or ${c} is replaced by cval
+
+
+
+ - -S subfile
+ - The substitution file. See below for format.
+
+ - template
+ - The input file. If no file is specified then input is taken from
+ stdin, i.e. msi can be used as a filter. See below for a description of
+ commands that can be embedded in the template file.
+
+
+It is not possible to display usage by just typing msi since
+executing the command with no arguments is a valid command. To show usage
+specify an illegal switch, e.g.
+
+
+msi -help
+
+
+Exit Status
+
+
+ - 0
- Success.
+
- 1
- Can't open/create file, or other I/O error.
+
- 2
- Undefined macros encountered with the -V option specified.
+
+
+Template File Format
+
+This file contains the text to be read and written to the output after macro
+substitution is performed. If no file is given then input is read from stdin.
+Variable instances to be substituted by macro values are expressed in the
+template using the syntax $(name) or
+${name}. The template can also provide default values
+to be used when a macro has not been given a value, using the syntax
+$(name=default) or
+${name=default}.
+
+For example, using the command
+
+
+msi -M name=Marty template
+
+
+where the file template contains
+
+
+My name is $(name)
+My age is $(age=none of your business)
+
+
+results in this output:
+
+
+My name is Marty
+My age is none of your business
+
+
+Macro variables and their default values can be expressed in terms of other
+macros if necessary, to almost any level of complexity. Recursive definitions
+will generate warning messages on stderr and result in undefined output.
+
+The template file is read and processed one line at a time, where the
+maximum length of a line before and/or after macro expansion is 1023 characters
+— longer input or output lines will cause msi to fail. Within the context
+of a single line, macro expansion does not occur when the variable instance
+appears inside a single-quoted string, or where the dollar sign $ is
+preceded by a back-slash character \, but as with the standard Unix
+shells, variables inside double quoted strings are expanded properly.
+
+However neither back-slash characters nor quotes of either variety are
+removed when generating the output file, so depending on what is being output
+the single quote behaviour may not be useful and may even be a hinderance. It
+cannot be disabled in the current version of msi.
+
+Template file commands
+
+In addition to the regular text and variable instances described above, the
+template file may also contain commands which allow the insertion of other
+template files and the ability to set macro values inside the template file
+itself. These commands are:
+
+
+include "file"
+substitute "var=value,var=value,..."
+
+
+Lines containing commands must be in one of these forms:
+
+
+ - include "filename"
+ - substitute "name1=value1, name2=value2, ..."
+
+
+White space is allowed before and after the command verb, and after the
+quoted string. If embedded quotes are needed, the backslash character
+\ can be used as an escape character. For example
+
+
+substitute "a=\"val\""
+
+
+specifies that (unless a is subsequently redefined) wherever a
+$(a) macro appears in the template below this point, the text
+"val" (including the double quote characters) will appear in the
+output instead.
+
+If a line does match either syntax above it is just passed to macLib for
+processing without any notification. Thus the input line:
+
+
+include "myfile" #include file
+
+
+would just be passed to macLib, i.e. it would not be considered an
+include command.
+
+As an example of these commands, let the Unix command be:
+
+
+msi template
+
+
+and file includeFile contain:
+
+
+first name is ${first}
+family name is ${family}
+
+
+and template is
+
+
+substitute "first=Marty,family=Kraimer"
+include "includeFile"
+substitute "first=Irma,family=Kraimer"
+include "includeFile"
+
+
+then the following is written to the output.
+
+
+first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+
+Note that the IOC's dbLoadTemplate command does not support the
+substitute syntax in template files, although the include
+syntax is supported.
+
+Substitution File Format
+
+The optional substitution file has three formats: regular, pattern, and
+dbTemplate format. We will discuss each separately.
+
+Regular format
+
+
+global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+{var1=set1_val1, var2=set1_val2, ...}
+{var2=set2_val2, var1=set2_val1, ...}
+global {gbl_var1=gbl_val3, gbl_var2=gbl_val4, ...}
+{var1=set3_val1, var2=set3_val2, ...}
+{var2=set4_val2, var1=set4_val1, ...}
+
+
+The template file is output with macro substitutions performed once for each
+set of braces containing macro replacement values.
+
+Pattern format
+
+
+global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+pattern {var1, var2, ...}
+{set1_val1, set1_val2, ...}
+{set2_val1, set2_val2, ...}
+pattern {var2, var1, ...}
+global {gbl_var1=gbl_val3, gbl_var2=gbl_val4, ...}
+{set3_val2, set3_val1, ...}
+{set4_val2, set4_val2, ...}
+
+
+This produces the same result as the regular format example above.
+
+dbLoadTemplate Format
+
+This format is an extension of the format accepted by the EPICS IOC command
+dbLoadTemplate, and allows templates to be expanded on the host rather
+by using dbLoadTemplate at IOC boot time.
+
+
+global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+file templatefile {
+ pattern format or regular format
+}
+file "${WHERE}/template2" {
+ pattern format or regular format
+}
+
+
+For the dbTemplate format, the template filename does not have to be given
+on the command line, and is usually specified in the substitutions file
+instead. If a template filename is given on the command line it will override
+the filenames listed in the substitutions files.
+
+Syntax for all formats
+
+A comment line may appear anywhere in a substitution file, and will be
+ignored. A comment line is any line beginning with the character #,
+which must be the very first character on the line.
+
+Global definitions may supplement or override the macro values supplied on
+the command-line using the -M switch, and set default values that will
+survive for the remainder of the file unless another global definition of the
+same macro changes it.
+
+For definitions within braces given in any of the file formats, a separator
+must be given between items. A separator is either a comma, or one or more of
+the standard white space characters (space, formfeed, newline, carriage return,
+tab or vertical tab).
+
+Each item within braces can be an alphanumeric token, or a double-quoted
+string. A back-slash character \ can be used to escape a quote
+character needed inside a quoted string. These three sets of substitutions are
+all equivalent:
+
+
+{a=aa b=bb c="\"cc\""}
+{b="bb",a=aa,c="\"cc\""}
+{
+ c="\"cc\""
+ b=bb
+ a="aa"
+}
+
+
+Within a substitutions file, the file name may appear inside double quotation
+marks; these are required if the name contains certain characters or environment
+variable macros of the form ${ENV_VAR} or $(ENV_VAR), which will be expanded
+before the file is opened.
+
+Regular substitution example
+
+Let the command be:
+
+
+msi -S substitute template
+
+
+The file template contains
+
+
+first name is ${first}
+family name is ${family}
+
+
+ and the file substitute is
+
+
+global {family=Kraimer}
+{first=Marty}
+{first=Irma}
+
+
+The following is the output produced:
+
+
+first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+
+Pattern substitution example
+
+Let the command be:
+
+
+msi -S pattern template
+
+
+The file pattern contains
+
+
+pattern {first,last}
+{Marty,Kraimer}
+{Irma,Kraimer}
+
+
+and template is the same as the previous example:
+
+
+first name is ${first}
+family name is ${family}
+
+
+This is the output:
+
+
+first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+
+dbTemplate example
+Let the command be
+
+
+msi -S xxx.substitutions
+
+
+xxx.substitutions is
+
+
+file template {
+pattern {first,last}
+{Marty,Kraimer}
+{Irma,Kraimer}
+pattern {last,first}
+{Smith,Bill}
+{Smith,Mary}
+}
+file template {
+{first=Marty,last=Kraimer}
+{first=Irma,last=Kraimer}
+}
+
+template is the same as in the previous example..
+
+The following is written to the output
+
+
+first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+first name is Bill
+last name is Smith
+first name is Mary
+last name is Smith
+first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer
+
+
+
+
diff --git a/src/ioc/dbtemplate/test/Makefile b/src/ioc/dbtemplate/test/Makefile
new file mode 100644
index 000000000..94c6613cb
--- /dev/null
+++ b/src/ioc/dbtemplate/test/Makefile
@@ -0,0 +1,21 @@
+#*************************************************************************
+# Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne
+# National Laboratory.
+# EPICS BASE is distributed subject to a Software License Agreement found
+# in the file LICENSE that is included with this distribution.
+#*************************************************************************
+TOP=../../../..
+
+include $(TOP)/configure/CONFIG
+
+TESTPROD_HOST_DEFAULT = dbltExpand
+TESTPROD_HOST_WIN32 = -nil-
+dbltExpand_SRCS += dbltExpand.c
+dbltExpand_LIBS += dbCore Com
+
+TESTS += msi
+
+TESTSCRIPTS_HOST += $(TESTS:%=%.t)
+
+include $(TOP)/configure/RULES
+
diff --git a/src/ioc/dbtemplate/test/dbltExpand.c b/src/ioc/dbtemplate/test/dbltExpand.c
new file mode 100644
index 000000000..6326d288e
--- /dev/null
+++ b/src/ioc/dbtemplate/test/dbltExpand.c
@@ -0,0 +1,100 @@
+/*************************************************************************\
+* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne
+* National Laboratory.
+* EPICS Base is distributed subject to a Software License Agreement found
+* in the file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+/* This is a simple version of msi for testing the dbLoadTemplate() code.
+ *
+ * It calls dbLoadTemplate() to parse the substitution file, but replaces
+ * dbLoadRecords() with its own version that reads the template file,
+ * expands any macros in the text and prints the result to stdout.
+ *
+ * This technique won't work on Windows, dbLoadRecords() has to be
+ * epicsShare... decorated and loaded from a shared library.
+ */
+
+#include
+#include
+#include
+#include
+
+#include "macLib.h"
+#include "dbLoadTemplate.h"
+
+
+#define BUFFER_SIZE 0x10000
+
+static char *input_buffer, *output_buffer;
+
+int dbLoadRecords(const char *file, const char *macros)
+{
+ MAC_HANDLE *macHandle = NULL;
+ char **macPairs;
+ FILE *fp;
+ size_t input_len;
+
+ if (macCreateHandle(&macHandle, NULL)) {
+ fprintf(stderr, "macCreateHandle failed\n");
+ exit(1);
+ }
+
+ macSuppressWarning(macHandle, 1);
+ macParseDefns(macHandle, macros, &macPairs);
+ if (!macPairs) {
+ macDeleteHandle(macHandle);
+ macHandle = NULL;
+ } else {
+ macInstallMacros(macHandle, macPairs);
+ free(macPairs);
+ }
+
+ fp = fopen(file, "r");
+ if (!fp) {
+ fprintf(stderr, "fopen('%s') failed: %s\n", file, strerror(errno));
+ exit(1);
+ }
+
+ input_len = fread(input_buffer, 1, BUFFER_SIZE, fp);
+ if (!feof(fp)) {
+ fprintf(stderr, "input file > 64K!\n");
+ fclose(fp);
+ exit(1);
+ }
+ input_buffer[input_len] = 0;
+
+ if (fclose(fp)) {
+ fprintf(stderr, "fclose('%s') failed: %s\n", file, strerror(errno));
+ exit(1);
+ }
+
+ macExpandString(macHandle, input_buffer, output_buffer, BUFFER_SIZE-1);
+ printf(output_buffer);
+
+ if (macHandle) macDeleteHandle(macHandle);
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ input_buffer = malloc(BUFFER_SIZE);
+ output_buffer = malloc(BUFFER_SIZE);
+
+ if (!input_buffer || !output_buffer) {
+ fprintf(stderr, "malloc(%d) failed\n", BUFFER_SIZE);
+ exit(1);
+ }
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s file.sub\n", argv[0]);
+ exit(1);
+ }
+
+ dbLoadTemplate(argv[1], NULL);
+
+ free(output_buffer);
+ free(input_buffer);
+ return 0;
+}
diff --git a/src/ioc/dbtemplate/test/msi.plt b/src/ioc/dbtemplate/test/msi.plt
new file mode 100644
index 000000000..e0104dbb3
--- /dev/null
+++ b/src/ioc/dbtemplate/test/msi.plt
@@ -0,0 +1,48 @@
+#!/usr/bin/perl
+#*************************************************************************
+# Copyright (c) 2012 UChicago Argonne LLC, as Operator of Argonne
+# National Laboratory.
+# EPICS BASE is distributed subject to a Software License Agreement found
+# in file LICENSE that is included with this distribution.
+#*************************************************************************
+
+# Script to run tests on the msi program
+
+use FindBin qw($Bin); # To find the msi executable
+
+use strict;
+use Test;
+
+BEGIN {plan tests => 7}
+
+ok(msi('-I .. ../t1-template.txt'), slurp('../t1-result.txt'));
+ok(msi('-I.. -S ../t2-substitution.txt'), slurp('../t2-result.txt'));
+ok(msi('-I. -I.. -S ../t3-substitution.txt'), slurp('../t3-result.txt'));
+ok(msi('-g -I.. -S ../t4-substitution.txt'), slurp('../t4-result.txt'));
+ok(msi('-S ../t5-substitute.txt ../t5-template.txt'), slurp('../t5-result.txt'));
+ok(msi('-S ../t6-substitute.txt ../t6-template.txt'), slurp('../t6-result.txt'));
+
+# Check -o works
+my $out = 't7-output.txt';
+unlink $out;
+msi("-I.. -o $out ../t1-template.txt");
+ok(slurp($out), slurp('../t1-result.txt'));
+
+
+# Support routines
+
+sub slurp {
+ my ($file) = @_;
+ open my $in, '<', $file
+ or die "Can't open file $file: $!\n";
+ my $contents = do { local $/; <$in> };
+ return $contents;
+}
+
+sub msi {
+ my ($args) = @_;
+ my $arch = $ENV{EPICS_HOST_ARCH};
+ my $exe = ($^O eq 'MSWin32') || ($^O eq 'cygwin') ? '.exe' : '';
+ my $msi = "$Bin/../../../O.$arch/msi$exe";
+ return `$msi $args`;
+}
diff --git a/src/ioc/dbtemplate/test/t1-include.txt b/src/ioc/dbtemplate/test/t1-include.txt
new file mode 100644
index 000000000..9793c73bd
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t1-include.txt
@@ -0,0 +1,5 @@
+This is t1-include.txt $(include-file-again=)
+ a = $(a=default value used when a is undefined)
+ b = $(b=default value used when b is undefined)
+substitute "include-file-again=again"
+End of t1-include.txt
diff --git a/src/ioc/dbtemplate/test/t1-result.txt b/src/ioc/dbtemplate/test/t1-result.txt
new file mode 100644
index 000000000..fcd065585
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t1-result.txt
@@ -0,0 +1,21 @@
+This is t1-template.txt
+
+With $(a,undefined) & $(b,undefined):
+This is t1-include.txt
+ a = default value used when a is undefined
+ b = default value used when b is undefined
+End of t1-include.txt
+
+On defining a=aaa & b=bbb:
+This is t1-include.txt again
+ a = aaa
+ b = bbb
+End of t1-include.txt
+
+On setting a="aa":
+This is t1-include.txt again
+ a = "aa"
+ b = bbb
+End of t1-include.txt
+
+End of t1-template.txt
diff --git a/src/ioc/dbtemplate/test/t1-template.txt b/src/ioc/dbtemplate/test/t1-template.txt
new file mode 100644
index 000000000..9ef49e8fe
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t1-template.txt
@@ -0,0 +1,14 @@
+This is t1-template.txt
+
+With $(a) & ${b}:
+include "t1-include.txt"
+
+substitute "a=aaa,b=bbb"
+On defining a=$(a) & b=${b}:
+include "t1-include.txt"
+
+substitute "a=\"aa\""
+On setting a=$(a):
+include "t1-include.txt"
+
+End of t1-template.txt
diff --git a/src/ioc/dbtemplate/test/t2-result.txt b/src/ioc/dbtemplate/test/t2-result.txt
new file mode 100644
index 000000000..5239e6a05
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t2-result.txt
@@ -0,0 +1,6 @@
+a = va1-a b = def-b c = def-c d = $(d,undefined)
+a = va2-a b = va2-b c = def-c d = $(d,undefined)
+a = va3-a b = va3-b c = va3-c d = $(d,undefined)
+a = va4-a b = va4-b c = def-c d = $(d,undefined)
+a = va5-a b = def-b c = def-c d = $(d,undefined)
+a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined)
diff --git a/src/ioc/dbtemplate/test/t2-substitution.txt b/src/ioc/dbtemplate/test/t2-substitution.txt
new file mode 100644
index 000000000..a3f3d6874
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t2-substitution.txt
@@ -0,0 +1,11 @@
+file t2-template.txt {
+ {a=va1-a}
+ {a=va2-a, b=va2-b}
+ {a=va3-a, b=va3-b, c=va3-c}
+ {a=va4-a, b=va4-b}
+ {a=va5-a}
+}
+file t2-template.txt {
+ pattern {a, b, c}
+ {pt3-a, pt3-b, pt3-c}
+}
diff --git a/src/ioc/dbtemplate/test/t2-template.txt b/src/ioc/dbtemplate/test/t2-template.txt
new file mode 100644
index 000000000..c4ac7b427
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t2-template.txt
@@ -0,0 +1 @@
+a = $(a=def-a) b = $(b=def-b) c = $(c=def-c) d = $(d,undef)
diff --git a/src/ioc/dbtemplate/test/t3-result.txt b/src/ioc/dbtemplate/test/t3-result.txt
new file mode 100644
index 000000000..c6961507f
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t3-result.txt
@@ -0,0 +1,28 @@
+a = gb1-a b = gb1-b c = def-c d = $(d,undefined)
+a = va1-a b = gb1-b c = def-c d = $(d,undefined)
+a = va2-a b = va2-b c = def-c d = $(d,undefined)
+a = va3-a b = va3-b c = va3-c d = $(d,undefined)
+a = va4-a b = va4-b c = def-c d = $(d,undefined)
+a = va5-a b = gb1-b c = def-c d = $(d,undefined)
+a = gb1-a b = gb1-b c = def-c d = $(d,undefined)
+a = gb2-a b = gb2-b c = def-c d = $(d,undefined)
+a = va1-a b = gb2-b c = def-c d = $(d,undefined)
+a = va2-a b = va2-b c = def-c d = $(d,undefined)
+a = va3-a b = va3-b c = va3-c d = $(d,undefined)
+a = va4-a b = va4-b c = def-c d = $(d,undefined)
+a = va5-a b = gb2-b c = def-c d = $(d,undefined)
+a = gb2-a b = gb2-b c = def-c d = $(d,undefined)
+a = gb3-a b = gb3-b c = def-c d = $(d,undefined)
+a = pt1-a b = gb3-b c = def-c d = $(d,undefined)
+a = pt2-a b = pt2-b c = def-c d = $(d,undefined)
+a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined)
+a = pt4-a b = pt4-b c = def-c d = $(d,undefined)
+a = pt5-a b = gb3-b c = def-c d = $(d,undefined)
+a = gb3-a b = gb3-b c = def-c d = $(d,undefined)
+a = gb4-a b = gb4-b c = def-c d = $(d,undefined)
+a = pt1-a b = gb4-b c = def-c d = $(d,undefined)
+a = pt2-a b = pt2-b c = def-c d = $(d,undefined)
+a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined)
+a = pt4-a b = pt4-b c = def-c d = $(d,undefined)
+a = pt5-a b = gb4-b c = def-c d = $(d,undefined)
+a = gb4-a b = gb4-b c = def-c d = $(d,undefined)
diff --git a/src/ioc/dbtemplate/test/t3-substitution.txt b/src/ioc/dbtemplate/test/t3-substitution.txt
new file mode 100644
index 000000000..30b0741d3
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t3-substitution.txt
@@ -0,0 +1,37 @@
+global {a=gb1-a, b=gb1-b}
+file t3-template.txt {
+ {}
+ {a=va1-a}
+ {a=va2-a, b=va2-b}
+ {a=va3-a, b=va3-b, c=va3-c}
+ {a=va4-a, b=va4-b}
+ {a=va5-a}
+ {}
+ global {a=gb2-a, b=gb2-b}
+ {}
+ {a=va1-a}
+ {a=va2-a, b=va2-b}
+ {a=va3-a, b=va3-b, c=va3-c}
+ {a=va4-a, b=va4-b}
+ {a=va5-a}
+ {}
+}
+global {b=gb3-b, a=gb3-a}
+file t3-template.txt {
+ pattern {a, b, c}
+ {}
+ {pt1-a}
+ {pt2-a, pt2-b}
+ {pt3-a, pt3-b, pt3-c}
+ {pt4-a, pt4-b}
+ {pt5-a}
+ {}
+ global {b=gb4-b, a=gb4-a}
+ {}
+ {pt1-a}
+ {pt2-a, pt2-b}
+ {pt3-a, pt3-b, pt3-c}
+ {pt4-a, pt4-b}
+ {pt5-a}
+ {}
+}
diff --git a/src/ioc/dbtemplate/test/t3-template.txt b/src/ioc/dbtemplate/test/t3-template.txt
new file mode 100644
index 000000000..c4ac7b427
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t3-template.txt
@@ -0,0 +1 @@
+a = $(a=def-a) b = $(b=def-b) c = $(c=def-c) d = $(d,undef)
diff --git a/src/ioc/dbtemplate/test/t4-result.txt b/src/ioc/dbtemplate/test/t4-result.txt
new file mode 100644
index 000000000..664872013
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t4-result.txt
@@ -0,0 +1,6 @@
+a = va1-a b = def-b c = def-c d = $(d,undefined)
+a = va2-a b = va2-b c = def-c d = $(d,undefined)
+a = va3-a b = va3-b c = va3-c d = $(d,undefined)
+a = va4-a b = va4-b c = va3-c d = $(d,undefined)
+a = va5-a b = va4-b c = va3-c d = $(d,undefined)
+a = pt3-a b = pt3-b c = pt3-c d = $(d,undefined)
diff --git a/src/ioc/dbtemplate/test/t4-substitution.txt b/src/ioc/dbtemplate/test/t4-substitution.txt
new file mode 100644
index 000000000..a3f3d6874
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t4-substitution.txt
@@ -0,0 +1,11 @@
+file t2-template.txt {
+ {a=va1-a}
+ {a=va2-a, b=va2-b}
+ {a=va3-a, b=va3-b, c=va3-c}
+ {a=va4-a, b=va4-b}
+ {a=va5-a}
+}
+file t2-template.txt {
+ pattern {a, b, c}
+ {pt3-a, pt3-b, pt3-c}
+}
diff --git a/src/ioc/dbtemplate/test/t5-result.txt b/src/ioc/dbtemplate/test/t5-result.txt
new file mode 100644
index 000000000..19a57c87c
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t5-result.txt
@@ -0,0 +1,20 @@
+# comment line
+a = 111
+b = 222
+c = xx
+d = $(d,undefined)
+# comment line
+a = aaa
+b = bbb
+c = ccc
+d = $(d,undefined)
+# comment line
+a = AA
+b = BB
+c = xx
+d = $(d,undefined)
+# comment line
+a = aaa
+b = bbb
+c = yy
+d = $(d,undefined)
diff --git a/src/ioc/dbtemplate/test/t5-substitute.txt b/src/ioc/dbtemplate/test/t5-substitute.txt
new file mode 100644
index 000000000..6b6ca985d
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t5-substitute.txt
@@ -0,0 +1,9 @@
+global {c=xx}
+{a=111,b="222"}
+{ a = aaa , b=bbb , c = ccc}
+{a=AA,b='BB'}
+global { c = yy }
+{
+ a= aaa
+ b= bbb
+}
diff --git a/src/ioc/dbtemplate/test/t5-template.txt b/src/ioc/dbtemplate/test/t5-template.txt
new file mode 100644
index 000000000..561b52a0a
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t5-template.txt
@@ -0,0 +1,5 @@
+# comment line
+a = $(a)
+b = $(b)
+c = $(c)
+d = $(d)
diff --git a/src/ioc/dbtemplate/test/t6-result.txt b/src/ioc/dbtemplate/test/t6-result.txt
new file mode 100644
index 000000000..19a57c87c
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t6-result.txt
@@ -0,0 +1,20 @@
+# comment line
+a = 111
+b = 222
+c = xx
+d = $(d,undefined)
+# comment line
+a = aaa
+b = bbb
+c = ccc
+d = $(d,undefined)
+# comment line
+a = AA
+b = BB
+c = xx
+d = $(d,undefined)
+# comment line
+a = aaa
+b = bbb
+c = yy
+d = $(d,undefined)
diff --git a/src/ioc/dbtemplate/test/t6-substitute.txt b/src/ioc/dbtemplate/test/t6-substitute.txt
new file mode 100644
index 000000000..7d80824fd
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t6-substitute.txt
@@ -0,0 +1,13 @@
+global {c=xx}
+pattern {b,a}
+{"222",111}
+pattern {a b c}
+{ aaa , bbb , ccc}
+pattern { a , b }
+{AA,'BB'}
+global { c = yy }
+pattern { a , b }
+{
+ aaa
+ bbb
+}
diff --git a/src/ioc/dbtemplate/test/t6-template.txt b/src/ioc/dbtemplate/test/t6-template.txt
new file mode 100644
index 000000000..561b52a0a
--- /dev/null
+++ b/src/ioc/dbtemplate/test/t6-template.txt
@@ -0,0 +1,5 @@
+# comment line
+a = $(a)
+b = $(b)
+c = $(c)
+d = $(d)
diff --git a/src/ioc/dbtemplate/test/template b/src/ioc/dbtemplate/test/template
new file mode 100644
index 000000000..561b52a0a
--- /dev/null
+++ b/src/ioc/dbtemplate/test/template
@@ -0,0 +1,5 @@
+# comment line
+a = $(a)
+b = $(b)
+c = $(c)
+d = $(d)