EPICS Home

Experimental Physics and Industrial Control System


 
2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  Index 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: [Merge] lp:~epics-core/epics-base/msi-join into lp:epics-base
From: Andrew Johnson <[email protected]>
To: [email protected]
Date: Fri, 01 Jun 2012 20:40:23 -0000
Andrew Johnson has proposed merging lp:~epics-core/epics-base/msi-join into lp:epics-base.

Requested reviews:
  EPICS Core Developers (epics-core)

For more details, see:
https://code.launchpad.net/~epics-core/epics-base/msi-join/+merge/108420

This branch brings an updated version of the msi tool (and its HTML documentation) into Base.  Additions to both msi and dbLoadRecords support defining global macros in substition files, and a flag for msi that changes substitution macros from local (the new default) to global scope (the original behavior).

Since msi only uses the macro substitution code from Base, this branch also builds a simple test program called dbltExpand which calls the dbLoadTemplate substitution file parser to help prove that the parser modifications made to both msi and dbLoadTemplate are compatible.

I also added a series of automated tests of msi into the build.

-- 
https://code.launchpad.net/~epics-core/epics-base/msi-join/+merge/108420
Your team EPICS Core Developers is requested to review the proposed merge of lp:~epics-core/epics-base/msi-join into lp:epics-base.
=== modified file 'configure/CONFIG_BASE'
--- configure/CONFIG_BASE	2012-04-04 17:30:27 +0000
+++ configure/CONFIG_BASE	2012-06-01 20:39:23 +0000
@@ -109,7 +109,7 @@
 endif
 
 ifndef MSI
-MSI = msi
+MSI = $(EPICS_BASE_HOST_BIN)/msi
 endif
 
 

=== modified file 'src/ioc/dbtemplate/Makefile'
--- src/ioc/dbtemplate/Makefile	2011-09-15 19:05:05 +0000
+++ src/ioc/dbtemplate/Makefile	2012-06-01 20:39:23 +0000
@@ -14,9 +14,43 @@
 INC += dbLoadTemplate.h
 INC += dbtoolsIocRegister.h
 
+<<<<<<< TREE
 dbCore_SRCS += dbLoadTemplate.c
 dbCore_SRCS += dbtoolsIocRegister.c
 
 HTMLS += dbtemplate/dbLoadTemplate.html
 
 CLEANS += dbLoadTemplate_lex.c dbLoadTemplate.c
+=======
+LIBRARY_IOC = dbtoolsIoc
+
+LIB_SRCS += dbLoadTemplate.c
+LIB_SRCS += dbtoolsIocRegister.c
+dbtoolsIoc_LIBS = dbIoc dbStaticIoc Com
+dbtoolsIoc_RCS = dbtoolsIoc.rc
+HTMLS += dbLoadTemplate.html
+
+PROD_HOST = msi
+
+msi_SRCS = msi.c
+msi_LIBS += Com
+HTMLS = msi.html
+
+# For R3.13 compatibility only
+ifeq ($(strip $(COMPAT_313)),YES)
+OBJLIB_vxWorks=dbtoolsIoc
+OBJLIB_SRCS = $(LIB_SRCS)
+endif
+
+include $(TOP)/configure/RULES
+ 
+#
+#	These lex sources are included in some C sources,
+#	so they have to be created in time:
+#
+dbLoadTemplate.c: dbLoadTemplate_lex.c ../dbLoadTemplate.h
+
+clean::
+	@$(RM) dbLoadTemplate_lex.c dbLoadTemplate.c 
+
+>>>>>>> MERGE-SOURCE

=== modified file 'src/ioc/dbtemplate/dbLoadTemplate.h'
--- src/ioc/dbtemplate/dbLoadTemplate.h	2002-07-12 21:35:43 +0000
+++ src/ioc/dbtemplate/dbLoadTemplate.h	2012-06-01 20:39:23 +0000
@@ -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*/
-
-
-
-

=== removed file 'src/ioc/dbtemplate/dbLoadTemplate.html'
--- src/ioc/dbtemplate/dbLoadTemplate.html	2002-07-12 21:35:43 +0000
+++ src/ioc/dbtemplate/dbLoadTemplate.html	1970-01-01 00:00:00 +0000
@@ -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. 
-\*************************************************************************/
-<HTML>
-<BODY>
-<PRE>
-<!-- Manpage converted by man2html 3.0.1 -->
-
-</PRE>
-<H2>NAME</H2><PRE>
-     dbLoadRecords, dbLoadTemplate - load ascii database  records
-     into an IOC
-
-
-</PRE>
-<H2>SYNOPSIS</H2><PRE>
-     dbLoadRecords(char* db_file, char* substitutions)
-
-     dbLoadTemplate(char* template_file)
-
-
-</PRE>
-<H2>DESCRIPTION</H2><PRE>
-     These routines are available from IOC core  on  the  vxWorks
-     command  line.  Both provide a way to load ascii ".db" files
-     (usually created by <B>gdct(1)</B> ) into the IOC. The ".db"  files
-     contain ascii versions of record instances and are described
-     in more detail in <B>dbfile(5)</B>.  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 <I>db</I>_<I>file</I> performing sub-
-     stitutions  specified in string <I>substitutions</I>. 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    <I>template</I>_<I>file</I>.    The
-     <I>template</I>_<I>file</I>  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  <B>templatefile(5)</B>.   The  EXAMPLES
-     section descibes how it can be used.
-
-
-</PRE>
-<H2>EXAMPLES</H2><PRE>
-     The next two  examples  of  dbLoadRecords()  and  dbLoadTem-
-     plate() will use the following ".db" file named <I>test</I>.<I>db</I> :
-
-     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")
-
-
-</PRE>
-<H2>NOTES</H2><PRE>
-     The binary file <I>default</I>.<I>dctsdr</I> 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.
-
-
-</PRE>
-<H2>SEE ALSO</H2><PRE>
-     <B>gdct(1)</B>, <B>templatefile(5)</B>, <B>dbfile(5)</B>
-
-
-
-
-
-
-
-
-
-
-
-
-</PRE>
-<HR>
-<ADDRESS>
-Man(1) output converted with
-<a href="http://www.oac.uci.edu/indiv/ehood/man2html.html";>man2html</a>
-</ADDRESS>
-</BODY>
-</HTML>

=== modified file 'src/ioc/dbtemplate/dbLoadTemplate.y'
--- src/ioc/dbtemplate/dbLoadTemplate.y	2009-05-05 15:19:00 +0000
+++ src/ioc/dbtemplate/dbLoadTemplate.y	2012-06-01 20:39:23 +0000
@@ -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 <Str> 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 @@
 
 %%
 
-template: templs
-	| subst
-	;
-
-templs: templs templ
-	| templ
-	;
-
-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");
-	}
-	;
-
-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);
-	}
-	;
-
-subst: PATTERN pattern subs
-	| PATTERN pattern
-	| var_subs
-	;
-
-pattern: O_BRACE vars C_BRACE
-	{ 
-#ifdef ERROR_STUFF
-		int i;
-		for(i=0;i<var_count;i++) fprintf(stderr,"variable=(%s)\n",vars[i]);
-		fprintf(stderr,"var_count=%d\n",var_count);
-#endif
-	}
-	;
-
-vars: vars var
-	| vars COMMA var
-	| var
-	;
-
-var: WORD
-	{
-	    vars[var_count] = dbmfMalloc(strlen($1)+1);
-	    strcpy(vars[var_count],$1);
-	    var_count++;
-	    dbmfFree($1);
-	}
-	;
-
-subs: subs sub
-	| sub
-	;
-
-sub: WORD O_BRACE vals C_BRACE
-	{
-		sub_collect[strlen(sub_collect)-1]='\0';
-#ifdef ERROR_STUFF
-		fprintf(stderr,"dbLoadRecords(%s)\n",sub_collect);
-#endif
-		if(db_file_name)
-			dbLoadRecords(db_file_name,sub_collect);
-		else
-			fprintf(stderr,"Error: no db file name given\n");
-		dbmfFree($1);
-		sub_collect[0]='\0';
-		sub_count=0;
-	}
-	| O_BRACE vals C_BRACE
-	{
-		sub_collect[strlen(sub_collect)-1]='\0';
-#ifdef ERROR_STUFF
-		fprintf(stderr,"dbLoadRecords(%s)\n",sub_collect);
-#endif
-		if(db_file_name)
-			dbLoadRecords(db_file_name,sub_collect);
-		else
-			fprintf(stderr,"Error: no db file name given\n");
-		sub_collect[0]='\0';
-		sub_count=0;
-	}
-	;
-
-vals: vals val
-	| vals COMMA val
-	| val
-	;
-
-val: QUOTE
-	{
-		if(sub_count<=var_count)
-		{
-			strcat(sub_collect,vars[sub_count]);
-			strcat(sub_collect,"=\"");
-			strcat(sub_collect,$1);
-			strcat(sub_collect,"\",");
-			sub_count++;
-		}
-		dbmfFree($1);
-	}
-	| WORD
-	{
-		if(sub_count<=var_count)
-		{
-			strcat(sub_collect,vars[sub_count]);
-			strcat(sub_collect,"=");
-			strcat(sub_collect,$1);
-			strcat(sub_collect,",");
-			sub_count++;
-		}
-		dbmfFree($1);
-	}
-	;
-
-var_subs: var_subs var_sub
-	| var_sub
-	;
-
-var_sub: WORD O_BRACE sub_pats C_BRACE
-	{
-		sub_collect[strlen(sub_collect)-1]='\0';
-#ifdef ERROR_STUFF
-		fprintf(stderr,"dbLoadRecords(%s)\n",sub_collect);
-#endif
-		if(db_file_name)
-			dbLoadRecords(db_file_name,sub_collect);
-		else
-			fprintf(stderr,"Error: no db file name given\n");
-		dbmfFree($1);
-		sub_collect[0]='\0';
-		sub_count=0;
-	}
-	| O_BRACE sub_pats C_BRACE
-	{
-		sub_collect[strlen(sub_collect)-1]='\0';
-#ifdef ERROR_STUFF
-		fprintf(stderr,"dbLoadRecords(%s)\n",sub_collect);
-#endif
-		if(db_file_name)
-			dbLoadRecords(db_file_name,sub_collect);
-		else
-			fprintf(stderr,"Error: no db file name given\n");
-		sub_collect[0]='\0';
-		sub_count=0;
-	}
-	;
-
-sub_pats: sub_pats sub_pat
-	| sub_pats COMMA sub_pat
-	| sub_pat
-	;
-
-sub_pat: WORD EQUALS WORD
-	{
-		strcat(sub_collect,$1);
-		strcat(sub_collect,"=");
-		strcat(sub_collect,$3);
-		strcat(sub_collect,",");
-		dbmfFree($1); dbmfFree($3);
-		sub_count++;
-	}
-	| WORD EQUALS QUOTE
-	{
-		strcat(sub_collect,$1);
-		strcat(sub_collect,"=\"");
-		strcat(sub_collect,$3);
-		strcat(sub_collect,"\",");
-		dbmfFree($1); dbmfFree($3);
-		sub_count++;
-	}
-	;
+substitution_file: global_or_template
+    | substitution_file global_or_template
+    ;
+
+global_or_template: global_definitions
+    | template_substitutions
+    ;
+
+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);
+    }
+    ;
+
+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;
+    }
+    ;
+
+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);
+    }
+    ;
+
+substitutions: pattern_substitutions
+    | variable_substitutions
+    ;
+
+pattern_substitutions: PATTERN O_BRACE C_BRACE
+    | PATTERN O_BRACE C_BRACE pattern_definitions
+    | PATTERN O_BRACE pattern_names C_BRACE
+    | PATTERN O_BRACE pattern_names C_BRACE pattern_definitions
+    ;
+
+pattern_names: pattern_name
+    | pattern_names COMMA
+    | pattern_names pattern_name
+    ;
+
+pattern_name: WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_name: [%d] = %s\n", var_count, $1);
+    #endif
+        vars[var_count] = dbmfMalloc(strlen($1)+1);
+        strcpy(vars[var_count], $1);
+        var_count++;
+        dbmfFree($1);
+    }
+    ;
+
+pattern_definitions: pattern_definition
+    | pattern_definitions pattern_definition
+    ;
+
+pattern_definition: global_definitions
+    | O_BRACE C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_definition: pattern_values empty\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+    }
+    | O_BRACE pattern_values C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_definition:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        *sub_locals = '\0';
+        sub_count = 0;
+    }
+    | WORD O_BRACE pattern_values C_BRACE
+    {   /* DEPRECATED SYNTAX */
+        fprintf(stderr,
+            "dbLoadTemplate: Substitution file uses deprecated syntax.\n"
+            "    the string '%s' on line %d that comes just before the\n"
+            "    '{' character is extraneous and should be removed.\n",
+            $1, line_num);
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_definition:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        dbmfFree($1);
+        *sub_locals = '\0';
+        sub_count = 0;
+    }
+    ;
+
+pattern_values: pattern_value
+    | pattern_values COMMA
+    | pattern_values pattern_value
+    ;
+
+pattern_value: QUOTE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_value: [%d] = \"%s\"\n", sub_count, $1);
+    #endif
+        if (sub_count < var_count) {
+            strcat(sub_locals, ",");
+            strcat(sub_locals, vars[sub_count]);
+            strcat(sub_locals, "=\"");
+            strcat(sub_locals, $1);
+            strcat(sub_locals, "\"");
+            sub_count++;
+        } else {
+            fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n",
+                line_num);
+        }
+        dbmfFree($1);
+    }
+    | WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "pattern_value: [%d] = %s\n", sub_count, $1);
+    #endif
+        if (sub_count < var_count) {
+            strcat(sub_locals, ",");
+            strcat(sub_locals, vars[sub_count]);
+            strcat(sub_locals, "=");
+            strcat(sub_locals, $1);
+            sub_count++;
+        } else {
+            fprintf(stderr, "dbLoadTemplate: Too many values given, line %d.\n",
+                line_num);
+        }
+        dbmfFree($1);
+    }
+    ;
+
+variable_substitutions: variable_substitution
+    | variable_substitutions variable_substitution
+    ;
+
+variable_substitution: global_definitions
+    | O_BRACE C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_substitution: variable_definitions empty\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+    }
+    | O_BRACE variable_definitions C_BRACE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_substitution:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        *sub_locals = '\0';
+    }
+    | WORD O_BRACE variable_definitions C_BRACE
+    {   /* DEPRECATED SYNTAX */
+        fprintf(stderr,
+            "dbLoadTemplate: Substitution file uses deprecated syntax.\n"
+            "    the string '%s' on line %d that comes just before the\n"
+            "    '{' character is extraneous and should be removed.\n",
+            $1, line_num);
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_substitution:\n");
+        fprintf(stderr, "    dbLoadRecords(%s)\n", sub_collect+1);
+    #endif
+        dbLoadRecords(db_file_name, sub_collect+1);
+        dbmfFree($1);
+        *sub_locals = '\0';
+    }
+    ;
+
+variable_definitions: variable_definition
+    | variable_definitions COMMA
+    | variable_definitions variable_definition
+    ;
+
+variable_definition: WORD EQUALS WORD
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_definition: %s = %s\n", $1, $3);
+    #endif
+        strcat(sub_locals, ",");
+        strcat(sub_locals, $1);
+        strcat(sub_locals, "=");
+        strcat(sub_locals, $3);
+        dbmfFree($1); dbmfFree($3);
+    }
+    | WORD EQUALS QUOTE
+    {
+    #ifdef ERROR_STUFF
+        fprintf(stderr, "variable_definition: %s = \"%s\"\n", $1, $3);
+    #endif
+        strcat(sub_locals, ",");
+        strcat(sub_locals, $1);
+        strcat(sub_locals, "=\"");
+        strcat(sub_locals, $3);
+        strcat(sub_locals, "\"");
+        dbmfFree($1); dbmfFree($3);
+    }
+    ;
 
 %%
  
@@ -247,69 +299,73 @@
 static int yyerror(char* str)
 {
     if (str)
-	fprintf(stderr, "Substitution file error: %s\n", str);
+        fprintf(stderr, "Substitution file error: %s\n", str);
     else
-	fprintf(stderr, "Substitution file error.\n");
+        fprintf(stderr, "Substitution file error.\n");
     fprintf(stderr, "line %d: '%s'\n", line_num, yytext);
     return 0;
 }
 
 static int is_not_inited = 1;
 
-int epicsShareAPI dbLoadTemplate(char* sub_file)
+int epicsShareAPI dbLoadTemplate(const char *sub_file, const char *cmd_collect)
 {
-	FILE *fp;
-	int ind;
-
-	line_num=1;
-
-	if( !sub_file || !*sub_file)
-	{
-		fprintf(stderr,"must specify variable substitution file\n");
-		return -1;
-	}
-
-	if( !(fp=fopen(sub_file,"r")) )
-	{
-		fprintf(stderr,"dbLoadTemplate: error opening sub file %s\n",sub_file);
-		return -1;
-	}
-
-	vars = (char**)malloc(VAR_MAX_VARS * sizeof(char*));
-	sub_collect = malloc(VAR_MAX_VAR_STRING);
-	if (!vars || !sub_collect)
-	{
-		free(vars);
-		free(sub_collect);
-		fclose(fp);
-		fprintf(stderr, "dbLoadTemplate: Out of memory!\n");
-		return -1;
-	}
-
-	sub_collect[0]='\0';
-	var_count=0;
-	sub_count=0;
-
-	if(is_not_inited)
-	{
-		yyin=fp;
-		is_not_inited=0;
-	}
-	else
-	{
-		yyrestart(fp);
-	}
-
-	yyparse();
-	for(ind=0;ind<var_count;ind++) dbmfFree(vars[ind]);
-	free(vars);
-	free(sub_collect);
-	vars = NULL;
-	fclose(fp);
-	if(db_file_name){
-	    dbmfFree((void *)db_file_name);
-	    db_file_name = NULL;
-	}
-	return 0;
+    FILE *fp;
+    int i;
+
+    line_num = 1;
+
+    if (!sub_file || !*sub_file) {
+        fprintf(stderr, "must specify variable substitution file\n");
+        return -1;
+    }
+
+    fp = fopen(sub_file, "r");
+    if (!fp) {
+        fprintf(stderr, "dbLoadTemplate: error opening sub file %s\n", sub_file);
+        return -1;
+    }
+
+    vars = (char**)malloc(VAR_MAX_VARS * sizeof(char*));
+    sub_collect = malloc(VAR_MAX_VAR_STRING);
+    if (!vars || !sub_collect) {
+        free(vars);
+        free(sub_collect);
+        fclose(fp);
+        fprintf(stderr, "dbLoadTemplate: Out of memory!\n");
+        return -1;
+    }
+    strcpy(sub_collect, ",");
+
+    if (cmd_collect && *cmd_collect) {
+        strcat(sub_collect, cmd_collect);
+        sub_locals = sub_collect + strlen(sub_collect);
+    } else {
+        sub_locals = sub_collect;
+        *sub_locals = '\0';
+    }
+    var_count = 0;
+    sub_count = 0;
+
+    if (is_not_inited) {
+        yyin = fp;
+        is_not_inited = 0;
+    } else {
+        yyrestart(fp);
+    }
+
+    yyparse();
+
+    for (i = 0; i < var_count; i++) {
+        dbmfFree(vars[i]);
+    }
+    free(vars);
+    free(sub_collect);
+    vars = NULL;
+    fclose(fp);
+    if (db_file_name) {
+        dbmfFree(db_file_name);
+        db_file_name = NULL;
+    }
+    return 0;
 }
-

=== modified file 'src/ioc/dbtemplate/dbLoadTemplate_lex.l'
--- src/ioc/dbtemplate/dbLoadTemplate_lex.l	2006-11-17 22:18:35 +0000
+++ src/ioc/dbtemplate/dbLoadTemplate_lex.l	2012-06-01 20:39:23 +0000
@@ -18,39 +18,40 @@
 
 %%
 
-"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);
-    }
-
-"="	{ return(EQUALS);	}
-","	{ return(COMMA);	}
-"{"	{ return(O_BRACE);	}
-"}"	{ return(C_BRACE);	}
-
-{comment}.*	;
-{whitespace}	;
-{newline}	{ line_num++;	}
+    yylval.Str = dbmfStrdup(yytext);
+    return(WORD);
+}
+
+"="     { return(EQUALS);       }
+","     { return(COMMA);        }
+"{"     { return(O_BRACE);      }
+"}"     { return(C_BRACE);      }
+
+{comment}.*     ;
+{whitespace}    ;
+{newline}       { line_num++;   }
 
 . {
-	char message[40];
-
-	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);
-    }
+    char message[40];
+
+    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);
+}
 
 %%

=== modified file 'src/ioc/dbtemplate/dbtoolsIocRegister.c'
--- src/ioc/dbtemplate/dbtoolsIocRegister.c	2007-03-13 17:54:23 +0000
+++ src/ioc/dbtemplate/dbtoolsIocRegister.c	2012-06-01 20:39:23 +0000
@@ -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);
 }

=== added file 'src/ioc/dbtemplate/msi.c'
--- src/ioc/dbtemplate/msi.c	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/msi.c	2012-06-01 20:39:23 +0000
@@ -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 <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <dbDefs.h>
+#include <macLib.h>
+#include <ellLib.h>
+#include <epicsString.h>
+#include <osiFileName.h>
+
+#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; i<argc; i++) argv[i] = argv[i + narg];
+    }
+    if(argc>2) {
+        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<FILE>  Save output to <FILE>\n");
+    fprintf(stderr,"  -I<DIR>   Add <DIR> to include file search path\n");
+    fprintf(stderr,"  -M<SUBST> Add <SUBST> to (global) macro definitions\n");
+    fprintf(stderr,"            (<SUBST> takes the form VAR=VALUE,...)\n");
+    fprintf(stderr,"  -S<FILE>  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;
+}

=== added file 'src/ioc/dbtemplate/msi.html'
--- src/ioc/dbtemplate/msi.html	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/msi.html	2012-06-01 20:39:23 +0000
@@ -0,0 +1,438 @@
+<!DOCTYPE html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+  <title></title>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</head>
+
+<body>
+
+<h1>msi: Macro Substitution and Include Tool</h1>
+
+<h2>Introduction</h2>
+
+<p>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: <tt>include</tt> and <tt>substitute</tt>. 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.</p>
+
+<p>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.</p>
+
+<h2>Command Syntax:</h2>
+
+<pre>msi -V -g -o<i>outfile</i> -I<i>dir</i> -M<i>subs</i> -S<i>subfile</i> <i>template</i></pre>
+
+<p>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.</p>
+
+<p>Switches have the following meanings:</p>
+
+<dl>
+  <dt><tt>-V</tt></dt>
+    <dd>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.</dd>
+
+  <dt><tt>-g</tt></dt>
+    <dd>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.</dd>
+
+  <dt><tt>-o</tt> <i>file</i></dt>
+    <dd>Output will be written to the specifed file rather than to the standard
+    output.</dd>
+
+  <dt><tt>-I</tt> <i>dir</i></dt>
+    <dd>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:
+
+    <blockquote>
+    <pre>msi -I /home/mrk/examples:. -I.. template</pre>
+    </blockquote>
+
+    specifies that all named files should be searched for in the following
+    locations in the order given:
+
+    <ol>
+      <li><tt>/home/mrk/examples</tt></li>
+      <li><tt>.</tt> (the current directory)</li>
+      <li><tt>..</tt> (the parent of the current directory)</li>
+    </ol>
+  </dd>
+
+  <dt><tt>-M</tt> <i>substitutions</i></dt>
+    <dd>This parameter specifies macro values for the template instance.
+    Multiple macro values can be specified in one substitution parameter, or in
+    multiple <tt>-M</tt> parameters. For example:
+
+    <blockquote>
+    <pre>msi -M "a=aval,b=bval" -Mc=cval template</pre>
+    </blockquote>
+
+    specifies that in the template file each occurrence of:
+
+    <dl>
+      <dd><tt>$(a)</tt> or <tt>${a}</tt> is replaced by <tt>aval</tt></dd>
+      <dd><tt>$(b)</tt> or <tt>${b}</tt> is replaced by <tt>bval</tt></dd>
+      <dd><tt>$(c)</tt> or <tt>${c}</tt> is replaced by <tt>cval</tt></dd>
+    </dl>
+  </dd>
+
+  <dt><tt>-S</tt> <i>subfile</i></dt>
+    <dd>The substitution file. See below for format.</dd>
+
+  <dt><i>template</i></dt>
+    <dd> 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.</dd>
+</dl>
+
+<p>It is not possible to display usage by just typing <tt>msi</tt> since
+executing the command with no arguments is a valid command. To show usage
+specify an illegal switch, e.g.</p>
+
+<blockquote>
+<pre>msi -help</pre>
+</blockquote>
+
+<h2>Exit Status</h2>
+
+<dl>
+  <dt>0<dd>Success.
+  <dt>1<dd>Can't open/create file, or other I/O error.
+  <dt>2<dd>Undefined macros encountered with the <tt>-V</tt> option specified.
+</dl>
+
+<h2>Template File Format</h2>
+
+<p>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 <tt>$(</tt><i>name</i><tt>)</tt> or
+<tt>${</tt><i>name</i><tt>}</tt>. The template can also provide default values
+to be used when a macro has not been given a value, using the syntax
+<tt>$(</tt><i>name</i><tt>=</tt><i>default</i><tt>)</tt> or
+<tt>${</tt><i>name</i><tt>=</tt><i>default</i><tt>}</tt>.</p>
+
+<p>For example, using the command</p>
+
+<blockquote>
+<pre>msi -M name=Marty template</pre>
+</blockquote>
+
+<p>where the file template contains</p>
+
+<blockquote>
+<pre>My name is $(name)
+My age is $(age=none of your business)</pre>
+</blockquote>
+
+<p>results in this output:</p>
+
+<blockquote>
+<pre>My name is Marty
+My age is none of your business</pre>
+</blockquote>
+
+<p>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.</p>
+
+<p>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
+&mdash; 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 <tt>$</tt> is
+preceded by a back-slash character <tt>\</tt>, but as with the standard Unix
+shells, variables inside double quoted strings are expanded properly.</p>
+
+<p>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.</p>
+
+<h3>Template file commands</h3>
+
+<p>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:</p>
+
+<blockquote>
+<pre>include "file"
+substitute "var=value,var=value,..."</pre>
+</blockquote>
+
+<p>Lines containing commands must be in one of these forms:</p>
+
+<ul>
+  <li><tt>include "</tt><i>filename</i><tt>"</tt></li>
+  <li><tt>substitute "</tt><i>name1=value1, name2=value2, ...</i><tt>"</tt></li>
+</ul>
+
+<p>White space is allowed before and after the command verb, and after the
+quoted string. If embedded quotes are needed, the backslash character
+<tt>\</tt> can be used as an escape character. For example</p>
+
+<blockquote>
+<pre>substitute "a=\"val\""</pre>
+</blockquote>
+
+<p>specifies that (unless <tt>a</tt> is subsequently redefined) wherever a
+<tt>$(a)</tt> macro appears in the template below this point, the text
+<tt>"val"</tt> (including the double quote characters) will appear in the
+output instead.</p>
+
+<p>If a line does match either syntax above it is just passed to macLib for
+processing without any notification. Thus the input line:</p>
+
+<blockquote>
+<pre>include "myfile" #include file</pre>
+</blockquote>
+
+<p>would just be passed to macLib, i.e. it would <em>not</em> be considered an
+include command.</p>
+
+<p>As an example of these commands, let the Unix command be:</p>
+
+<blockquote>
+<pre>msi template</pre>
+</blockquote>
+
+<p>and file includeFile contain:</p>
+
+<blockquote>
+<pre>first name is ${first}
+family name is ${family}</pre>
+</blockquote>
+
+<p>and template is</p>
+
+<blockquote>
+<pre>substitute "first=Marty,family=Kraimer"
+include "includeFile"
+substitute "first=Irma,family=Kraimer"
+include "includeFile"</pre>
+</blockquote>
+
+<p>then the following is written to the output.</p>
+
+<blockquote>
+<pre>first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer</pre>
+</blockquote>
+
+<p>Note that the IOC's <tt>dbLoadTemplate</tt> command does not support the
+<tt>substitute</tt> syntax in template files, although the <tt>include</tt>
+syntax is supported.</p>
+
+<h2>Substitution File Format</h2>
+
+<p>The optional substitution file has three formats: regular, pattern, and
+dbTemplate format. We will discuss each separately.</p>
+
+<h3>Regular format</h3>
+
+<blockquote>
+<pre>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, ...}</pre>
+</blockquote>
+
+<p>The template file is output with macro substitutions performed once for each
+set of braces containing macro replacement values.</p> 
+
+<h3>Pattern format</h3>
+
+<blockquote>
+<pre>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, ...}</pre>
+</blockquote>
+
+<p>This produces the same result as the regular format example above.</p>
+
+<h3>dbLoadTemplate Format</h3>
+
+<p>This format is an extension of the format accepted by the EPICS IOC command
+<tt>dbLoadTemplate</tt>, and allows templates to be expanded on the host rather
+by using dbLoadTemplate at IOC boot time.</p>
+
+<blockquote>
+<pre>global {gbl_var1=gbl_val1, gbl_var2=gbl_val2, ...}
+file templatefile {
+    <i>pattern format or regular format</i>
+}
+file "${WHERE}/template2" {
+    <i>pattern format or regular format</i>
+}</pre>
+</blockquote>
+
+<p>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.</p>
+
+<h3>Syntax for all formats</h3>
+
+<p>A comment line may appear anywhere in a substitution file, and will be
+ignored. A comment line is any line beginning with the character <tt>#</tt>,
+which must be the very first character on the line.</p>
+
+<p>Global definitions may supplement or override the macro values supplied on
+the command-line using the <tt>-M</tt> switch, and set default values that will
+survive for the remainder of the file unless another global definition of the
+same macro changes it.</p>
+
+<p>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).</p>
+
+<p>Each item within braces can be an alphanumeric token, or a double-quoted
+string. A back-slash character <tt>\</tt> can be used to escape a quote
+character needed inside a quoted string. These three sets of substitutions are
+all equivalent:</p>
+
+<blockquote>
+<pre>{a=aa b=bb c="\"cc\""}
+{b="bb",a=aa,c="\"cc\""}
+{
+    c="\"cc\""
+    b=bb
+    a="aa"
+}</pre>
+</blockquote>
+
+<p>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.</p>
+
+<h3>Regular substitution example</h3>
+
+<p>Let the command be:</p>
+
+<blockquote>
+<pre>msi -S substitute template</pre>
+</blockquote>
+
+<p>The file <tt>template</tt> contains</p>
+
+<blockquote>
+<pre>first name is ${first}
+family name is ${family}</pre>
+</blockquote>
+
+<p> and the file <tt>substitute</tt> is</p>
+
+<blockquote>
+<pre>global {family=Kraimer}
+{first=Marty}
+{first=Irma}</pre>
+</blockquote>
+
+<p>The following is the output produced:</p>
+
+<blockquote>
+<pre>first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer</pre>
+</blockquote>
+
+<h3>Pattern substitution example</h3>
+
+<p>Let the command be:</p>
+
+<blockquote>
+<pre>msi -S pattern template</pre>
+</blockquote>
+
+<p>The file <tt>pattern</tt> contains</p>
+
+<blockquote>
+<pre>pattern {first,last}
+{Marty,Kraimer}
+{Irma,Kraimer}</pre>
+</blockquote>
+
+<p>and <tt>template</tt> is the same as the previous example:</p>
+
+<blockquote>
+<pre>first name is ${first}
+family name is ${family}</pre>
+</blockquote>
+
+<p>This is the output:</p>
+
+<blockquote>
+<pre>first name is Marty
+family name is Kraimer
+first name is Irma
+family name is Kraimer</pre>
+</blockquote>
+
+<h3>dbTemplate example</h3>
+Let the command be
+
+<blockquote>
+<pre>msi -S xxx.substitutions</pre>
+</blockquote>
+
+<tt>xxx.substitutions</tt> is
+
+<blockquote>
+<pre>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}
+}</pre>
+</blockquote>
+<tt>template</tt> is the same as in the previous example..
+
+<p>The following is written to the output</p>
+
+<blockquote>
+<pre>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</pre>
+</blockquote>
+
+</body>
+</html>

=== added directory 'src/ioc/dbtemplate/test'
=== added file 'src/ioc/dbtemplate/test/Makefile'
--- src/ioc/dbtemplate/test/Makefile	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/Makefile	2012-06-01 20:39:23 +0000
@@ -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 += dbtoolsIoc dbStaticHost Com
+
+TESTS += msi
+
+TESTSCRIPTS_HOST += $(TESTS:%=%.t)
+
+include $(TOP)/configure/RULES
+

=== added file 'src/ioc/dbtemplate/test/dbltExpand.c'
--- src/ioc/dbtemplate/test/dbltExpand.c	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/dbltExpand.c	2012-06-01 20:39:23 +0000
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#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;
+}

=== added file 'src/ioc/dbtemplate/test/msi.plt'
--- src/ioc/dbtemplate/test/msi.plt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/msi.plt	2012-06-01 20:39:23 +0000
@@ -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`;
+}

=== added file 'src/ioc/dbtemplate/test/t1-include.txt'
--- src/ioc/dbtemplate/test/t1-include.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t1-include.txt	2012-06-01 20:39:23 +0000
@@ -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

=== added file 'src/ioc/dbtemplate/test/t1-result.txt'
--- src/ioc/dbtemplate/test/t1-result.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t1-result.txt	2012-06-01 20:39:23 +0000
@@ -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

=== added file 'src/ioc/dbtemplate/test/t1-template.txt'
--- src/ioc/dbtemplate/test/t1-template.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t1-template.txt	2012-06-01 20:39:23 +0000
@@ -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

=== added file 'src/ioc/dbtemplate/test/t2-result.txt'
--- src/ioc/dbtemplate/test/t2-result.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t2-result.txt	2012-06-01 20:39:23 +0000
@@ -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)

=== added file 'src/ioc/dbtemplate/test/t2-substitution.txt'
--- src/ioc/dbtemplate/test/t2-substitution.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t2-substitution.txt	2012-06-01 20:39:23 +0000
@@ -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}
+}

=== added file 'src/ioc/dbtemplate/test/t2-template.txt'
--- src/ioc/dbtemplate/test/t2-template.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t2-template.txt	2012-06-01 20:39:23 +0000
@@ -0,0 +1,1 @@
+a = $(a=def-a)  b = $(b=def-b)  c = $(c=def-c)  d = $(d,undef)

=== added file 'src/ioc/dbtemplate/test/t3-result.txt'
--- src/ioc/dbtemplate/test/t3-result.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t3-result.txt	2012-06-01 20:39:23 +0000
@@ -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)

=== added file 'src/ioc/dbtemplate/test/t3-substitution.txt'
--- src/ioc/dbtemplate/test/t3-substitution.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t3-substitution.txt	2012-06-01 20:39:23 +0000
@@ -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}
+    {}
+}

=== added file 'src/ioc/dbtemplate/test/t3-template.txt'
--- src/ioc/dbtemplate/test/t3-template.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t3-template.txt	2012-06-01 20:39:23 +0000
@@ -0,0 +1,1 @@
+a = $(a=def-a)  b = $(b=def-b)  c = $(c=def-c)  d = $(d,undef)

=== added file 'src/ioc/dbtemplate/test/t4-result.txt'
--- src/ioc/dbtemplate/test/t4-result.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t4-result.txt	2012-06-01 20:39:23 +0000
@@ -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)

=== added file 'src/ioc/dbtemplate/test/t4-substitution.txt'
--- src/ioc/dbtemplate/test/t4-substitution.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t4-substitution.txt	2012-06-01 20:39:23 +0000
@@ -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}
+}

=== added file 'src/ioc/dbtemplate/test/t5-result.txt'
--- src/ioc/dbtemplate/test/t5-result.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t5-result.txt	2012-06-01 20:39:23 +0000
@@ -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)

=== added file 'src/ioc/dbtemplate/test/t5-substitute.txt'
--- src/ioc/dbtemplate/test/t5-substitute.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t5-substitute.txt	2012-06-01 20:39:23 +0000
@@ -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
+}

=== added file 'src/ioc/dbtemplate/test/t5-template.txt'
--- src/ioc/dbtemplate/test/t5-template.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t5-template.txt	2012-06-01 20:39:23 +0000
@@ -0,0 +1,5 @@
+# comment line
+a = $(a)
+b = $(b)
+c = $(c)
+d = $(d)

=== added file 'src/ioc/dbtemplate/test/t6-result.txt'
--- src/ioc/dbtemplate/test/t6-result.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t6-result.txt	2012-06-01 20:39:23 +0000
@@ -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)

=== added file 'src/ioc/dbtemplate/test/t6-substitute.txt'
--- src/ioc/dbtemplate/test/t6-substitute.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t6-substitute.txt	2012-06-01 20:39:23 +0000
@@ -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
+}

=== added file 'src/ioc/dbtemplate/test/t6-template.txt'
--- src/ioc/dbtemplate/test/t6-template.txt	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/t6-template.txt	2012-06-01 20:39:23 +0000
@@ -0,0 +1,5 @@
+# comment line
+a = $(a)
+b = $(b)
+c = $(c)
+d = $(d)

=== added file 'src/ioc/dbtemplate/test/template'
--- src/ioc/dbtemplate/test/template	1970-01-01 00:00:00 +0000
+++ src/ioc/dbtemplate/test/template	2012-06-01 20:39:23 +0000
@@ -0,0 +1,5 @@
+# comment line
+a = $(a)
+b = $(b)
+c = $(c)
+d = $(d)


Replies:
Re: [Merge] lp:~epics-core/epics-base/msi-join into lp:epics-base Andrew Johnson
[Merge] lp:~epics-core/epics-base/msi-join into lp:epics-base noreply

Navigate by Date:
Prev: [Merge] lp:~epics-core/epics-base/thread-pool into lp:epics-base mdavidsaver
Next: Re: [Merge] lp:~epics-core/epics-base/msi-join into lp:epics-base Andrew Johnson
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024 
Navigate by Thread:
Prev: Re: [Merge] lp:~epics-core/epics-base/thread-pool into lp:epics-base mdavidsaver
Next: Re: [Merge] lp:~epics-core/epics-base/msi-join into lp:epics-base Andrew Johnson
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024