diff --git a/configure/CONFIG_BASE b/configure/CONFIG_BASE index 6e525d2fd..b14d1efbd 100644 --- a/configure/CONFIG_BASE +++ b/configure/CONFIG_BASE @@ -109,7 +109,7 @@ DBST = dbst endif ifndef MSI -MSI = msi +MSI = $(EPICS_BASE_HOST_BIN)/msi endif diff --git a/configure/RULES_BUILD b/configure/RULES_BUILD index 50c7582b0..67b2bd8f4 100644 --- a/configure/RULES_BUILD +++ b/configure/RULES_BUILD @@ -21,6 +21,7 @@ vpath %.cc $(USR_VPATH) $(ALL_SRC_DIRS) vpath %.cpp $(USR_VPATH) $(ALL_SRC_DIRS) vpath %.rc $(USR_VPATH) $(ALL_SRC_DIRS) vpath %.h $(USR_VPATH) $(ALL_SRC_DIRS) +vpath %.html $(USR_VPATH) $(ALL_SRC_DIRS) vpath %.skel.static $(USR_VPATH) $(ALL_SRC_DIRS) vpath %.y $(USR_VPATH) $(ALL_SRC_DIRS) vpath %.l $(USR_VPATH) $(ALL_SRC_DIRS) diff --git a/src/Makefile b/src/Makefile index ad00bc90b..7d052650d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -64,6 +64,9 @@ ioc_DEPEND_DIRS = libCom ca/client DIRS += ioc/db/test ioc/db/test_DEPEND_DIRS = ioc libCom/RTEMS +DIRS += ioc/dbtemplate/test +ioc/dbtemplate/test_DEPEND_DIRS = ioc + # PDB Standard Record Definitions DIRS += std diff --git a/src/ioc/dbtemplate/Makefile b/src/ioc/dbtemplate/Makefile index 425a08be7..2aa5a0c78 100644 --- a/src/ioc/dbtemplate/Makefile +++ b/src/ioc/dbtemplate/Makefile @@ -11,12 +11,17 @@ SRC_DIRS += $(IOCDIR)/dbtemplate +PROD_HOST += msi + +msi_SRCS = msi.c +msi_LIBS += Com +HTMLS += msi.html + INC += dbLoadTemplate.h INC += dbtoolsIocRegister.h dbCore_SRCS += dbLoadTemplate.c dbCore_SRCS += dbtoolsIocRegister.c -HTMLS += dbtemplate/dbLoadTemplate.html - CLEANS += dbLoadTemplate_lex.c dbLoadTemplate.c + diff --git a/src/ioc/dbtemplate/dbLoadTemplate.h b/src/ioc/dbtemplate/dbLoadTemplate.h index a341b8098..0de0235bb 100644 --- a/src/ioc/dbtemplate/dbLoadTemplate.h +++ b/src/ioc/dbtemplate/dbLoadTemplate.h @@ -3,8 +3,7 @@ * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found +* EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* dbLoadTemplate.h */ @@ -13,10 +12,7 @@ #define INCdbLoadTemplateh #include "shareLib.h" -epicsShareFunc int epicsShareAPI dbLoadTemplate(char* sub_file); +epicsShareFunc int epicsShareAPI dbLoadTemplate( + const char *sub_file, const char *cmd_collect); #endif /*INCdbLoadTemplateh*/ - - - - diff --git a/src/ioc/dbtemplate/dbLoadTemplate.html b/src/ioc/dbtemplate/dbLoadTemplate.html deleted file mode 100644 index 4943b8c6f..000000000 --- a/src/ioc/dbtemplate/dbLoadTemplate.html +++ /dev/null @@ -1,137 +0,0 @@ -/*************************************************************************\ -* Copyright (c) 2002 The University of Chicago, as Operator of Argonne -* National Laboratory. -* Copyright (c) 2002 The Regents of the University of California, as -* Operator of Los Alamos National Laboratory. -* EPICS BASE Versions 3.13.7 -* and higher are distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. -\*************************************************************************/ - - -
-
-
-
-

NAME

-     dbLoadRecords, dbLoadTemplate - load ascii database  records
-     into an IOC
-
-
-
-

SYNOPSIS

-     dbLoadRecords(char* db_file, char* substitutions)
-
-     dbLoadTemplate(char* template_file)
-
-
-
-

DESCRIPTION

-     These routines are available from IOC core  on  the  vxWorks
-     command  line.  Both provide a way to load ascii ".db" files
-     (usually created by gdct(1) ) into the IOC. The ".db"  files
-     contain ascii versions of record instances and are described
-     in more detail in dbfile(5).  In  addition  to  loading  the
-     ".db"  ascii  files  into  the  IOC, both routines provide a
-     method of performing variable substitution on  record  names
-     and field values.
-
-     dbLoadRecords() reads the ".db" file db_file performing sub-
-     stitutions  specified in string substitutions. The substitu-
-     tion must be a string specified as follows:
-
-     "var1=sub1,var2=sub3,..."
-
-     Variables   are   specified   in   the   ".db"    file    as
-     $(variable_name).      If     the     substitution    string
-     "a=1,b=2,c=\"this is a  test\""  were  used,  any  variables
-     $(a),  $(b), or $(c) would be substituted with the appropri-
-     ate data.  See the EXAMPLES section for more details.
-
-     dbLoadTemplate()   will   read    a    template_file.    The
-     template_file  resides  in  the  your IOC boot directory and
-     contains rules about loading ".db" files and performing sub-
-     stitutions.   The  template_file must be in the form used by
-     an IOC and is described in  templatefile(5).   The  EXAMPLES
-     section descibes how it can be used.
-
-
-
-

EXAMPLES

-     The next two  examples  of  dbLoadRecords()  and  dbLoadTem-
-     plate() will use the following ".db" file named test.db :
-
-     database(test)
-     {
-          record(ai,"$(pre)testrec1")
-          record(ai,"$(pre)testrec2")
-          record(stringout,"$(pre)testrec3")
-          {
-               field(VAL,"$(STRING)")
-               field(SCAN,"$(SCAN)")
-          }
-     }
-     Running dbLoadRecords ("test.db","pre=TEST,STRING=\"this  is
-     a  test\",SCAN=Passive")  will produce the following records
-     in the IOC's database:
-
-          TESTtestrec1
-          TESTtestrec2
-          TESTtestrec3
-
-     The third record will have VAL set to "this is a  test"  and
-     SCAN set to "Passive".
-
-     Running dbLoadTemplate ("test.template") with  test.template
-     containing:
-     file test.db
-     {
-          {pre=TEST1, STRING = "this is a test two", SCAN="1 Second" }
-          {pre=TEST2, STRING = "this is a test one", SCAN=Passive }
-          {pre=TEST3, STRING = "this is a test three", SCAN=Passive }
-     }
-     will produce a total of nine records in the IOC's database:
-          TEST1testrec1
-          TEST1testrec2
-          TEST1testrec3 - (VAL="this is a test two", SCAN="1 Second")
-          TEST2testrec1
-          TEST2testrec2
-          TEST2testrec3 - (VAL="this is a test one", SCAN="Passive")
-          TEST3testrec1
-          TEST3testrec2
-          TEST3testrec3 - (VAL="this is a test three", SCAN="Passive")
-
-
-
-

NOTES

-     The binary file default.dctsdr must be loaded prior to  run-
-     ning either of these routines.  This file contains the rules
-     on how to construct records and change field values.
-
-     After the default.dctsdr file is loaded, these routines  can
-     be run as many times as desired until iocInit is run.
-
-
-
-

SEE ALSO

-     gdct(1), templatefile(5), dbfile(5)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Man(1) output converted with -man2html -
- - diff --git a/src/ioc/dbtemplate/dbLoadTemplate.y b/src/ioc/dbtemplate/dbLoadTemplate.y index 0a919dda6..85be9e4e3 100644 --- a/src/ioc/dbtemplate/dbLoadTemplate.y +++ b/src/ioc/dbtemplate/dbLoadTemplate.y @@ -22,30 +22,32 @@ #include "dbLoadTemplate.h" static int line_num; -static int yyerror(); +static int yyerror(char* str); #define VAR_MAX_VAR_STRING 5000 #define VAR_MAX_VARS 100 -static char *sub_collect = NULL; +static char *sub_collect; +static char *sub_locals; static char** vars = NULL; static char* db_file_name = NULL; -static int var_count,sub_count; +static int var_count, sub_count; %} -%start template +%start substitution_file %token WORD QUOTE %token DBFILE %token PATTERN +%token GLOBAL %token EQUALS COMMA %left O_PAREN C_PAREN %left O_BRACE C_BRACE %union { - int Int; + int Int; char Char; char *Str; double Real; @@ -53,192 +55,242 @@ static int var_count,sub_count; %% -template: templs - | subst - ; +substitution_file: global_or_template + | substitution_file global_or_template + ; -templs: templs templ - | templ - ; +global_or_template: global_definitions + | template_substitutions + ; -templ: templ_head O_BRACE subst C_BRACE - | templ_head - { - if(db_file_name) - dbLoadRecords(db_file_name,NULL); - else - fprintf(stderr,"Error: no db file name given\n"); - } - ; +global_definitions: GLOBAL O_BRACE C_BRACE + | GLOBAL O_BRACE variable_definitions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "global_definitions: %s\n", sub_collect+1); + #endif + sub_locals += strlen(sub_locals); + } + ; -templ_head: DBFILE WORD - { - var_count=0; - if(db_file_name) dbmfFree(db_file_name); - db_file_name = dbmfMalloc(strlen($2)+1); - strcpy(db_file_name,$2); - dbmfFree($2); - } - | DBFILE QUOTE - { - var_count=0; - if(db_file_name) dbmfFree(db_file_name); - db_file_name = dbmfMalloc(strlen($2)+1); - strcpy(db_file_name,$2); - dbmfFree($2); - } - ; +template_substitutions: template_filename O_BRACE C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_substitutions: %s unused\n", db_file_name); + #endif + dbmfFree(db_file_name); + db_file_name = NULL; + } + | template_filename O_BRACE substitutions C_BRACE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_substitutions: %s finished\n", db_file_name); + #endif + dbmfFree(db_file_name); + db_file_name = NULL; + } + ; -subst: PATTERN pattern subs - | PATTERN pattern - | var_subs - ; +template_filename: DBFILE WORD + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_filename: %s\n", $2); + #endif + var_count = 0; + db_file_name = dbmfMalloc(strlen($2)+1); + strcpy(db_file_name, $2); + dbmfFree($2); + } + | DBFILE QUOTE + { + #ifdef ERROR_STUFF + fprintf(stderr, "template_filename: \"%s\"\n", $2); + #endif + var_count = 0; + db_file_name = dbmfMalloc(strlen($2)+1); + strcpy(db_file_name, $2); + dbmfFree($2); + } + ; -pattern: O_BRACE vars C_BRACE - { -#ifdef ERROR_STUFF - int i; - for(i=0;i;] %% -"pattern" { return(PATTERN); } -"file" { return(DBFILE); } +"pattern" { return(PATTERN); } +"file" { return(DBFILE); } +"global" { return(GLOBAL); } {doublequote}({dstringchar}|{escape})*{doublequote} | {singlequote}({sstringchar}|{escape})*{singlequote} { - yylval.Str = dbmfStrdup(yytext+1); - yylval.Str[strlen(yylval.Str)-1] = '\0'; - return(QUOTE); - } + yylval.Str = dbmfStrdup(yytext+1); + yylval.Str[strlen(yylval.Str)-1] = '\0'; + return(QUOTE); +} {bareword}+ { - yylval.Str = dbmfStrdup(yytext); - return(WORD); - } + yylval.Str = dbmfStrdup(yytext); + return(WORD); +} -"=" { return(EQUALS); } -"," { return(COMMA); } -"{" { return(O_BRACE); } -"}" { return(C_BRACE); } +"=" { return(EQUALS); } +"," { return(COMMA); } +"{" { return(O_BRACE); } +"}" { return(C_BRACE); } -{comment}.* ; -{whitespace} ; -{newline} { line_num++; } +{comment}.* ; +{whitespace} ; +{newline} { line_num++; } . { - char message[40]; + char message[40]; - sprintf(message,"invalid character '%c'", yytext[0]); - yyerror(message); + sprintf(message, "invalid character '%c'", yytext[0]); + yyerror(message); - /* Suppress compiler warning messages */ - if (0) yyunput('c',NULL); - if (0) yy_switch_to_buffer(NULL); - } + /* Suppress compiler warning messages */ + if (0) yyunput('c',NULL); + if (0) yy_switch_to_buffer(NULL); +} %% diff --git a/src/ioc/dbtemplate/dbtoolsIocRegister.c b/src/ioc/dbtemplate/dbtoolsIocRegister.c index 9fbe1096b..3ff446872 100644 --- a/src/ioc/dbtemplate/dbtoolsIocRegister.c +++ b/src/ioc/dbtemplate/dbtoolsIocRegister.c @@ -13,17 +13,20 @@ /* dbLoadTemplate */ -static const iocshArg dbLoadTemplateArg0 = { "file name",iocshArgString}; -static const iocshArg * const dbLoadTemplateArgs[1] = {&dbLoadTemplateArg0}; +static const iocshArg dbLoadTemplateArg0 = {"filename", iocshArgString}; +static const iocshArg dbLoadTemplateArg1 = {"var=value", iocshArgString}; +static const iocshArg * const dbLoadTemplateArgs[2] = { + &dbLoadTemplateArg0, &dbLoadTemplateArg1 +}; static const iocshFuncDef dbLoadTemplateFuncDef = - {"dbLoadTemplate",1,dbLoadTemplateArgs}; + {"dbLoadTemplate", 2, dbLoadTemplateArgs}; static void dbLoadTemplateCallFunc(const iocshArgBuf *args) { - dbLoadTemplate(args[0].sval); + dbLoadTemplate(args[0].sval, args[1].sval); } void epicsShareAPI dbtoolsIocRegister(void) { - iocshRegister(&dbLoadTemplateFuncDef,dbLoadTemplateCallFunc); + iocshRegister(&dbLoadTemplateFuncDef, dbLoadTemplateCallFunc); } diff --git a/src/ioc/dbtemplate/msi.c b/src/ioc/dbtemplate/msi.c new file mode 100644 index 000000000..b2733199b --- /dev/null +++ b/src/ioc/dbtemplate/msi.c @@ -0,0 +1,841 @@ +/*************************************************************************\ +* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne +* National Laboratory. +* Copyright (c) 2002 The Regents of the University of California, as +* Operator of Los Alamos National Laboratory. +* EPICS Base is distributed subject to a Software License Agreement found +* in the file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* msi - macro substitutions and include */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MAX_BUFFER_SIZE 4096 + +/* Module to read the template files */ +typedef struct inputData inputData; + +static void inputConstruct(inputData **ppvt); +static void inputDestruct(inputData *pvt); +static void inputAddPath(inputData *pvt, char *pval); +static void inputBegin(inputData *pvt, char *fileName); +static char *inputNextLine(inputData *pvt); +static void inputNewIncludeFile(inputData *pvt, char *name); +static void inputErrPrint(inputData *pvt); + +/* Module to read the substitution file */ +typedef struct subInfo subInfo; + +static void substituteOpen(subInfo **ppvt, char *substitutionName); +static void substituteDestruct(subInfo *pvt); +static int substituteGetNextSet(subInfo *pvt, char **filename); +static int substituteGetGlobalSet(subInfo *pvt); +static char *substituteGetReplacements(subInfo *pvt); +static char *substituteGetGlobalReplacements(subInfo *pvt); + +/* Forward references to local routines */ +static void usageExit(void); +static void addMacroReplacements(MAC_HANDLE *macPvt, char *pval); +static void makeSubstitutions(inputData *inputPvt, MAC_HANDLE *macPvt, char *templateName); + +/*Global variables */ +static int dontWarnUndef = 1; + + +int main(int argc,char **argv) +{ + inputData *inputPvt; + MAC_HANDLE *macPvt; + char *pval; + int narg; + char *substitutionName=0; + char *templateName=0; + int i; + int localScope = 1; + + inputConstruct(&inputPvt); + macCreateHandle(&macPvt,0); + macSuppressWarning(macPvt,1); + while((argc>1) && (argv[1][0] == '-')) { + narg = (strlen(argv[1])==2) ? 2 : 1; + pval = (narg==1) ? (argv[1]+2) : argv[2]; + if(strncmp(argv[1],"-I",2)==0) { + inputAddPath(inputPvt,pval); + } else if(strncmp(argv[1],"-o",2)==0) { + if(freopen(pval,"w",stdout)==NULL) { + fprintf(stderr,"msi: Can't open %s for writing: %s\n", + pval, strerror(errno)); + exit(1); + } + } else if(strncmp(argv[1],"-M",2)==0) { + addMacroReplacements(macPvt,pval); + } else if(strncmp(argv[1],"-S",2)==0) { + substitutionName = epicsStrDup(pval); + } else if(strncmp(argv[1],"-V",2)==0) { + macSuppressWarning(macPvt,0); + dontWarnUndef = 0; + narg = 1; /* no argument for this option */ + } else if(strncmp(argv[1],"-g",2)==0) { + localScope = 0; + narg = 1; /* no argument for this option */ + } else { + usageExit(); + } + argc -= narg; + for(i=1; i2) { + fprintf(stderr,"msi: Too many arguments\n"); + usageExit(); + } + if(argc==2) { + templateName = epicsStrDup(argv[1]); + } + if(!substitutionName) { + makeSubstitutions(inputPvt,macPvt,templateName); + } else { + subInfo *substitutePvt; + char *filename = 0; + int isGlobal, isFile; + + substituteOpen(&substitutePvt,substitutionName); + do { + if ((isGlobal = substituteGetGlobalSet(substitutePvt))) { + pval = substituteGetGlobalReplacements(substitutePvt); + if(pval) { + addMacroReplacements(macPvt,pval); + } + } else if ((isFile = substituteGetNextSet(substitutePvt,&filename))) { + if(templateName) filename = templateName; + if(!filename) { + fprintf(stderr,"msi: No template file\n"); + usageExit(); + } + while((pval = substituteGetReplacements(substitutePvt))){ + if (localScope) macPushScope(macPvt); + addMacroReplacements(macPvt,pval); + makeSubstitutions(inputPvt,macPvt,filename); + if (localScope) macPopScope(macPvt); + } + } + } while (isGlobal || isFile); + substituteDestruct(substitutePvt); + } + macDeleteHandle(macPvt); + inputDestruct(inputPvt); + free(templateName); + free(substitutionName); + return 0; +} + +void usageExit(void) +{ + fprintf(stderr,"usage: msi [options] [template]\n"); + fprintf(stderr,"stdin is used if neither template nor substitution file is given\n"); + fprintf(stderr,"options:\n"); + fprintf(stderr," -V Verbose warnings\n"); + fprintf(stderr," -g All macros have global scope\n"); + fprintf(stderr," -o Save output to \n"); + fprintf(stderr," -I Add to include file search path\n"); + fprintf(stderr," -M Add to (global) macro definitions\n"); + fprintf(stderr," ( takes the form VAR=VALUE,...)\n"); + fprintf(stderr," -S 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: + +
    +
  1. /home/mrk/examples
  2. +
  3. . (the current directory)
  4. +
  5. .. (the parent of the current directory)
  6. +
+
+ +
-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)