28 Sep 2018

expandfile

This note describes expandfile, a simple command line program for expanding templates. It is useful for many purposes.

What expandfile Does

expandfile is a command line program that reads input files and writes output on standard output.

Characters in the input are copied to the output, except that when expandfile sees %[... something ...]% it expands it: that is, replaces the bracketed expression by its value in the output. Values come from variables set by input files, command line parameters, builtin functions, macro execution, external calls, and shell environment variables.

One of the main uses for expandfile is translating an extension language for HTML called HTMX into regular HTML. expandfile enables you to

HTMX->expandfile->HTML

expandfile does not know the syntax of the text it is expanding. It can be used for many kinds of text transformation, such as structured data to data transformations, e.g. CSV => CSV; CSV => XML, SQL => XML (e.g. sitemaps), SQL => procmail, SQL => GraphViz, etc. Some Web APIs return their values in XML, which expandfile can translate into reports or into SQL that can be joined with other data.

expandfile is Open Source software, MIT license. Share and enjoy.

Command Usage

NAME

expandfile -- expand a template file

SYNOPSIS

expandfile [var=value] ... file ...

DESCRIPTION

expandfile reads template files and writes out text with variables and builtin functions expanded.

If a filename is specified as "-", standard input will be read.

You can optionally specify variable bindings on the command line in the format varname=value.

It is often useful to create a file containing a series of %[*set]% commands to set parameters, and provide it as the first filename on the command line.

How To Use expandfile

When you have expandfile installed, you can type the command line

  expandfile template.htmx > template.html

to expand template.htmx and write its output into the file template.html. expandfile doesn't know the syntax of HTML. It just sees character input and writes character output.

I rarely type expandfile on a command line; mostly I use it in scripts that expand template source files into object files. For example, most HTML files on my websites are updated by the make utility, driven by a Makefile that invokes expandfile to expand a source template if the corresponding object file is out of date.

File Suffixes

Files with any suffix can be used as input or output files with expandfile: the program doesn't care. You can call your files whatever you wish. I use a set of conventions to remind myself of what files contain:

Use whatever file suffix helps you organize your source.

Variables

expandfile has an internal table of variables. Variables have names and values. A variable's name should not be blank or contain "|" or ",". Letters, digits, and "()_+-" are allowed in variable names. Values are strings of characters, of any length.

The value of a variable is set by

expandfile builtin functions that set a variable require a "&" character in front of the variable name, to make it clear that the function will modify the value.

Expansion

The rules for expandfile's expansion are:

The PRE and POST strings may contain "\\n" which will become a new line in the output.

HTMX Language

expandfile was originally written in Perl, and inherits some Perl semantics.

Expansion

expandfile reads its input and writes output. When reading input, the character \ is an escape character that removes the special meaning from the following character. Use "\\" to output "\". As described above, expandfile replaces named variables inside %[...]% by their values, and executes builtin functions like %[*builtin...]% that may take arguments, and may or may not output values.

Literal Syntax

Literal values are preceded by = in order to distinguish them from variable names. For example, %[*set,&foo,=1]%.

expandfile will print a warning if it encounters a variable name that is all digits, because this may be an incorrectly formatted literal.

Quotes in a literal value prevent the interpretation of special characters. They are required if the literal contains special characters such as , or %.

The literal value 14 can be expressed as =14 or ="14". Usually the second form is clearer.

Example:
If mike has the value nancy and nancy has the value 14, then
%[*set,&fred,="%[mike]%"]% will set fred's value to %[mike]%.

Outside of %[ ]%, quotes are just characters.

Values and Types

Variable values are character strings. They can be null. A reference to a variable that was never set is not an error: the value returned is a null string.

Some operations perform arithmetic operations on values, using Perl semantics. Addition, subtraction, and so forth are defined for values that contain digits only.

Example:
If variable nancy has the value 14 then
%[*set,&fred,nancy]% will set fred's value to 14.

Example:
If variable carol has the value aaaa then
%[*increment,&carol,=1]% will set carol's value to 1
because Perl addition of an alphabetic string and an integer tries to convert "aaa" to an integer and gets a value of zero. This is not an error.

Nested Brackets

If expressions contain nested %[...]% sequences, the innermost set is expanded first.

Example:
If mike has the value nancy and nancy has the value 14, then
%[*set,&fred,%[mike]%]% will set fred's value to 14.
%[*set,&fred,=%[mike]%]% will set fred's value to nancy.

Builtin Functions

There are 36 builtin functions. They are pretty simple, mostly less than 10 lines of code each.

%[** anycomment]%

** introduces a comment.

Outputs nothing.

Example:
%[** this is a remark]%.

%[*set,&result,val]%

If val begins with an = then it is a literal value. Otherwise it is a variable name. Set the value of result to val.

Sets its first argument to the value specified by the second. Outputs nothing.

Example: set a variable to a constant.
%[*set,&title,="Expandfile Usage"]%.
Example: set a variable to the value of another variable.
%[*set,&title,datafield]%.

%[*block,&result,regex]%

Read input lines following this command until a line matching the regular expression {:regex} is found. For regex, use something like ^EOF$ Sets the value of regex to all of the lines read, and removes them from the input. Text in %[...]% is copied with its brackets without expansion into result: when the block is expanded later these constructs will be processed. Specifying multiple blocks with the same {:result} will append content to the block. Blocks do not nest. This builtin should be used alone on a line.

This construct is used to set up a variable that can be expanded later, or expanded many times as an iterator, or called as a macro.

Sets its first argument. Outputs nothing.

Example:
%[*block,&iterblock,^END]%
<tr><td>%[table1.name]%</td><td>%[table1.addr]%</td></tr>
END

%[*include,=filen]%

Insert the expanded contents of the file named filen into the output. If the file is not found, this is a fatal error and expandfile exits.

Outputs the file's content, with blocks and builtin functions expanded.

Example:
%[*include,=page-wrapper.htmi]%.

%[*includeraw,=filen]%

Insert the raw contents of the file named filen into the output. If the file is not found, this is a fatal error and expandfile exits.

Outputs the file content, without expanding blocks and builtin functions.

Example:
%[*includeraw,=data_table.txt]%.

%[*if,rel,v1,v2,statement]%

Perform the comparison v1 rel v2 and if it is TRUE, expand statement. statement can be any set of HTMX evaluations, including more "if" builtins. rel is the name of a comparison operator. The supported operators are:

  • eq numeric equality
  • ne numeric inequality
  • gt numeric greater-than
  • lt numeric less-than
  • ge numeric greater-equal
  • le numeric less-equal
  • =~ regular expression equality
  • !~ regular expression equality
  • teq string equality
  • tne string inequality
  • tgt string greater-than
  • tlt string less-than
  • eqlc string equality, case independent
  • nelc string equality, case independent

Outputs whatever statement outputs.

Example:
%[*if,eq,city,="Chicago",*set,&airport,="ORD"]%.
Nested *if statements:
%[*if,ne,gnm,param1,*if,ne,tnms,="",*callv,rec_grpname2grpid,param1]%
Another nested *if statement, setting up a format string for the *format builtin:
%[*if,gt,x3,param3,*if,gt,x1,=0,*set,&fmt,=" <span class="inred">($1$2%)</a>"]%

%[*expand,val]%

Expand a variable and output the result. val may contain variable names and builtin invocations in %[... ]%. Recursive expansions are possible if the variable invokes %[*expand,...]%.

Outputs the value of expanding val. If val contains nested %[ ... something ...]% constructs, they will be expanded.

Example: output the contents of a variable
%[*expand,footnote_separator]%.
Example: output the contents of a variable whose name depends on another variable
%[*expand,footnote_%[ftn]%]%.

%[*expandv,&result,val]%

Expand a variable's contents and store the value in the variable result. val may contain text or variable names and builtin invocations in %[... ]%. Recursive expansions are possible if the variable invokes %[*expandv,...]%.

Sets its first argument. Outputs nothing. If val contains %[ ... something ...]% constructs, they will be expanded.

Example:
%[*expandv,&formatted_name,convert_cityname]%

%[*concat,&result,val]%

Concatenate the value of val onto the value in result.

Rewrites its first argument with the result. Outputs nothing.

Example:
%[*concat,&line,cityname]%
%[*concat,&line,=", "]%
%[*concat,&line,statename]%

%[*format,&result,fmtstring,v1,v2,v3,...]%

Replace placeholders in fmtstring of the form "$1" .. "$n" with values of the vi. Store the result into result.

Sets its first argument. Outputs nothing.

Example: combine cityname and statename:
%[*format,&line,="$1, $2",cityname,statename]%
Example: Generate an HTML IMG tag:
%[*format,&imgtag,="<img src=\"$1\" width=\"$2\" height=\"$3\">",filename,height,width]%
Example: Generate GraphViz input:
%[*if,ne,t.st,="invis",*format,&x,="$1 -> $2 [xlabel="$3",style="$4",color="$5"];\n",y.n1,y.n2,y.b,y.e,t.o]%

%[*ncopies,&result,val,n]%

Concatenate n copies of the value in val and store the result into result.

Rewrites its first argument. Outputs nothing.

Example:
%[*ncopies,&amt,="*",width]%

%[*increment,&result,val]%

Add the value in val to the value in result and store the result in result. Uses Perl syntax.

Rewrites its first argument. Outputs nothing.

Example:
%[*increment,&pageno,="1"]%

%[*decrement,&result,val]%

Subtract the value in val from the value in result and store the result in result. Uses Perl syntax.

Rewrites its first argument. Outputs nothing.

Example:
%[*decrement,&openfiles,="1"]%

%[*product,&result,val]%

Multiply the value in result by the value in val and store the result in result.

Rewrites its first argument. Outputs nothing.

Example:
%[*product,&mins,hours,=60]%

%[*quotient,&result,val]%

Divide the value in result by the value in vl and store the result in result. Discard the fractional part. If divide by zero is attempted, the result is 0.

Rewrites its first argument. Outputs nothing.

Example:
%[*quotient,&cows,hooves,=4]%

%[*quotientrounded,&result,val]%

Divide the value in result by the value invaland store the result in result, rounded to the nearest integer. That is, compute int((result / val) + 0.5); If divide by zero is attempted, the result is 0.

Rewrites its first argument. Outputs nothing.

Example:
%[*quotientrounded,&cows,hooves,=4]%

%[*scale,&result,n,base,range]%

Compute int((n*range)/base) and store the result in result.

Sets its first argument. Outputs nothing.

Example: Computing an image dimension for a bar graph:
%[*scale,&barwidth_pixels,wtdayhist.dhits,.maxdhits,rpt_summary_graphwidth]%
This uses the rpt_summary_graphwidth pixels to display a graph with longest bar representing .maxdhits.

%[*subst,&result,left,right]%

Replace the value in result by the result of the Perl substitution s/left/right/. Slashes in left and right must be escaped -- use \\.

left can contain parenthesized patterns, as in Perl substitutions; these patterns can be referenced in right as \1, \2, etc.

Rewrites its first argument. Outputs nothing.

Example: truncate a name to its first 40 characters
%[*subst,&name,="^(........................................).*$",="\\1"]%
Example: trim off the directory portions of a pathname
%[*subst,&pname,="^.*\/",=""]%

%[*fread,&result,=filen]%

Read the contents of file filen into result. If the input file is not found, set content to the empty string. Does not expand variables or blocks.

Sets its first argument. Outputs nothing.

Example:
%[*fread,&pienumber,=pienumberfile]%

%[*urlfetch,&result,=url]%

Fetch the contents of the Internet URL url into result. If the target is not found, set content to the empty string. Does not expand variables or blocks.

Be careful about security if you read external values from the Internet and expand them. Sanitize your values before expanding.

Sets its first argument. Outputs nothing.

Example:
%[*urlfetch,&contents,=%[_ssvitem]%]%

%[*fwrite,=filename,value]%

Write value to an external file filename. Replaces any previous contents of filename.

Outputs nothing.

Example:
%[*fwrite,=pienumberfile,=1%

%[*fappend,val]%

Append value to the an external file filename.

Outputs nothing.

Example:
%[*fappend,=tracefile.txt,traceoutput]%

%[*shell,&result,cmd]%

Execute the shell command cmd and capture its output. If multiple lines are returned, replace the newline separators by the contents of _ssvsep. Store the result in result.

Be careful about security if you execute shell commands based on user input.

You can write your own functions, or use Unix programs such as grep, sed, awk, or sh.

Some useful utility functions that write output on STDOUT:

  • filemodshort file => 09/28/18
  • filemodyear file => 2018
  • filemodiso file => 2018-09-28
  • filedaysold file => 0
  • filesizek file => 49
  • gifsize2 file => 16 16 tinymultics.gif
  • firstletter abcdef => A
  • uppercase hello => HELLO
  • lowercase Boston => boston
  • fmtnum 1234567 num => 1,234,567
  • nargs 1 2 3 => 3

Sets its first argument to the captured STDOUT. Outputs nothing.

Example: get the date modified of a file
%[*shell,&filed,=filemodshort %[param1]%.htmx]%
Example: get the age of a file in days
%[*shell,&xage,=filedaysold %[obj_dir]%/%[param1]%/index.html]%
Example: modify a disk file to remove the string "NAMESP:" everywhere
%[*shell,&xout,=perl -pi -e "s/NAMESP://g" xyz.xml]%
Example: invoke mysql to load a disk file containing SQL statements
%[*shell,&xout,=mysql --execute "source %[sqlloadfile]%"]%
Example: invoke the curl command to download output from a web service API
%[*shell,&xout,=curl %[endpoint]%/%[cmd]% -X POST -d %[JSON_rq]% -H "Content-type: application/json" > xx.json]%

%[*callv,function_block,arg1,arg2,arg3,...]%

Perform a subroutine call of function_block.

  1. Save all the variables param1,param2,param3,....
  2. Assign each variable param1=arg1,param2=arg2,param3=arg3,...
  3. Expand block function_block, which will refer to param1 and so on.
  4. Restore all the variables param1,param2,param3,....

Outputs whatever function_block outputs. The saving and restoring means that one block can invoke another block without arguments getting mixed up.

Example:
%[*callv,twodigit,month%

See Macros in Expandfile for more information.

%[*sqlloop,&result,iterator_block,query]%

Execute the SQL query query, which returns a number of rows. Each row returns a set of variables. For each row, bind the variables in the symbol table using names like table.varname1, and then expand iterator_block, which will refer to these variables. Append the result of the expansion to result. Set _nrows to the number of rows returned and _colnames to the names of the variables bound. (Because _colnames is set before iterator_block is expanded, the iterator need not know the complete schema of the database.)

Computed values such as COUNT are bound to names like .count.

The variables hostname, database, username, and password must be set up to point to the database server.

Sets its first argument. Outputs nothing.

Example: generate a bar chart summarizing population by country name from table "s":
%[*sqlloop,&chartout,chart_iter,="SELECT country, COUNT(*) AS v FROM s WHERE fake=0 GROUP BY country"]%

To execute a query just to set a total, such as SELECT COUNT(*) AS minors FROM tbl WHERE age < 21, specify a variable for the output that you never use, and a null iterator, and just refer to the value %[.minors]%.

%[*csvloop,&result,iterator_block,=filen]%

Operate on each row in a CSV (comma separated values) file, like those used by spreadsheet programs. RFC-4180 defines this format. (The file may be gzipped.) The first row in the file is a heading row with the variable names for each column. For each row, bind the variables in the symbol table using names like colname1, and then expand iterator_block, which will refer to these variables. Append the result of the expansion to result.

The variable _nrows is set to the count of rows found by the query.

The variable _colnames is set to a space separated list of column names bound by the query.

Sets its first argument. Outputs nothing.

Example:
%[*csvloop,&report,btrr_iter,=btrr.csv]%

%[*xmlloop,&result,iterator_block,=filen,[Xpath]]%

Operate on each item in an XML file. The outermost structure in the file should be something like <list> ... </list> and it should contain repeated items <item> ... </item> which in turn contain multiple fields like <person> ... </person> and <address> ... </address>. For each item, bind the values of fields in the symbol table using names like person, and then expand iterator_block, which will refer to these variables. Append the result of the expansion to result.

If Xpath is provided, use it to access the items: the default is "/*/*". For each item found by the Xpath, the loop binds the values of sub-items "./*" and binds the values of attributes "./@*".

The variable _nxml is set to the count of items found by the query.

The variable _xmlfields is set to a space separated list of variable names bound by the query.

Sets its first argument. Outputs nothing.

Example:
%[*xmlloop,&junk,iter_gacc,=gacc.xml,="*/computers/computer"]%

%[*ssvloop,&result,iterator_block,varname]%

An SSV (space separated values) list is a variable value composed of tokens separated by the value in _ssvsep (usually space). Operate on each item in a SSV. For each token, bind _ssvitem to the value (null tokens are skipped), and then expand iterator_block, which will refer to _ssvitem. Append the result of the expansion to result. This loop works on a copy of varname, so the input SSV is not changed.

The variable _nssv is set to the count of items found by the query.

Sets its first argument. Outputs nothing.

Example:
%[*ssvloop,&nextstorybody,nextstory-iter,filenamesbydate]%

%[*popssv,&result,&ssv]%

Assumes that ssv contains a list of strings separated by a separator character whose value is in _ssvsep. (By default the character is a space -- SSV means "space separated values.") Remove the leftmost element of ssv, store it in result, and rewrite ssv with the rest. If ssv contains only one element, store it in result and leave ssv empty.

Sets its first argument; rewrites its second argument. Outputs nothing.

Example:
%[*popssv,&next_player,&jersey_numbers]%]%

%[*dirloop,&result,iterator_block,dirpath,starrex]%

Operate on each file system file in a directory whose name matches starrex. For each file, do a stat() operation on the file and bind variables to the values of the file attributes, and then expand iterator_block. Append the result of the expansion to result.

The variables bound are

file_namename of the file
file_type'f' for file, 'd' for directory, 'l' for link
file_devdevice number
file_inoinode number
file_modemode in Unix character format, e.g. "rwx------"
file_nlinknumber of hardlinks to the file
file_uidnumeric file owner userid
file_gidnumeric file owner groupid
file_rdevrdev (for special files)
file_sizesize in bytes
file_atimelast access time
file_mtimelast mod time
file_ctimeinode change time
file_blksizepreferred block size
file_blocksallocated blocks
file_secmtime seconds (2 digits)
file_minmtime minutes (2 digits)
file_hourmtime hour (2 digits)
file_mdaymtime day of month (2 digits)
file_monmtime month (2 digits)
file_yearmtime year (2 digits)
file_wdaymtime day of week (0-6, Sunday is 0))
file_ydaymtime day of year
file_isdst1 if mtime is DST
file_datemodmtime in the format "mm/dd/yy hh:mm"
file_modshortmtime in the format "mm/dd/yy"
file_sizekfile size in K
file_ageage in days

The variable _nrows is set to the count of files found.

Sets its first argument. Outputs nothing.

Example:
%[*dirloop,&content,format_one_file,=".",="."]%

%[*onchange,var,statement]%

If the value of var has changed, execute the statement. It is useful in iterators invoked by *sqlloop, *csvloop, *xmlloop, *dirloop, and *csvloop.

Outputs whatever statement outputs.

Example:
%[*onchange,x,x|    <dt>|</dt>\\n]%
outputs a line which surrounds the value of x with <dt> and </dt> if x is nonempty and changed.

%[*onnochange,var,statement]%

If the value of var has NOT changed, execute the statement. It is useful in iterators invoked by *sqlloop, *csvloop, *xmlloop, *dirloop, and *csvloop.

If an iterator block uses both *onchange and *onnochange, put the onnochange call first.

Outputs whatever statement outputs.

Example:
%[*onnochange,x,*increment,&titles,=1]%

%[*exit]%

Exit from expandfile.

Outputs nothing.

Example:
%[*exit]%

%[*dump]%

Output the entire table of variables for debugging.

Outputs one line per defined variable.

Example:
%[*dump]%

%[*warn,mess]%

Write a warning message mess on STDERR.

Outputs nothing.

Example:
%[*warn,No results for %[query]%]%

%[*htmlescape,val]%

Output the HTML-escaped representation of val. For instance, html-escaping "<fred>" yields "&lt;fred&gt;".

Outputs the escaped value.

Example:
%[*htmlescape,filename]%

Builtin Summary

Some builtin functions write output to the output file. Others produce no output but instead change an argument variable. A few functions conditionally expand the rest of their arguments, so these produce output wherever their arguments direct.

Builtin functions that modify an argument use & before the variable name to remind you that the value will be modified. If you omit the &, expandfile prints a warning. Some builtins with & have in/out args, others are out.

BuiltinSets VarinoutFilenameResult
** anycommentnothing
*setYoutnothing
*blockYoutnothing
*includeYfile content, expanded
*includerawYfile content
*ifif true, whatever contained statement returns
*expandvalue of variable, expanded
*expandvYoutvalue of variable, expanded
*concatYoutnothing
*formatYoutnothing
*ncopiesYoutnothing
*incrementYinoutnothing
*decrementYinoutnothing
*productYoutnothing
*quotientYoutnothing
*quotientroundedYoutnothing
*scaleYoutnothing
*substYinoutnothing
*freadYoutYnothing
*urlfetchYoutnothing
*fwriteYnothing
*fappendYnothing
*shellYoutnothing
*callvwhatever is output by contained statements
*sqlloopYoutnothing
*csvloopYoutYnothing
*xmlloopYoutYnothing
*ssvloopYoutnothing
*popssvYinoutnothing
*dirloopYoutYnothing
*onchangewhatever is output by contained statements
*onnochangewhatever is output by contained statements
*exitaborts execution
*dumpformatted variable dump
*warnwrites to STDERR
*htmlescapeescaped string

(If I had it to do over, I might make all functions take an output argument.)

Builtin Values

These values are set up by expandfile when it is invoked. Probably a bad idea to store into any of these.

%[year]%year (2018)
%[prevyear]%previous year (2017)
%[day]%day (28)
%[month]%month (Sep)
%[prevmonth]%previous month (Aug)
%[monthx]%numeric month (09)
%[hour]%hour (16)
%[min]%min (44)
%[date]%date in text format (28 Sep 2018)
%[timestamp]%timestamp (2018-09-28 16:44)
%[pct]%percent (%)
%[lbkt]%left bracket ([)
%[rbkt]%right bracket (])
%[quote]%quote (")
%[_currentfilename]%current file name being expanded (expandfile.htmx>xfwrapper.htmi)
%[_ssvsep]%SSV list separator (|)

Special Values

%[_tracebind]%if set to "yes", causes *sqlloop, *csvloop, *xmlloop, *dirloop, and *csvloop
to print a message on STDERR when they bind a value to a variable
%[_ssvsep]%separator between elements of a Space Separated Values list. Default is " "
%[_ssvitem]%current item inside an iterator block expanded by *ssvloop
%[_nrows]%Rows read, set by *sqlloop and *csvloop and *dirloop
%[_colnames]%SSV of column names bound, set by *sqlloop and *csvloop
%[_nxml]%XML items read, set by *xmlloop
%[_xmlfields]%SSV of column names bound, set by *xmlloop
%[_nssv]%SSV items read, set by *ssvloop
%[_currentfilename]%name of file currently being read by *expandfile

Macro Libraries

One way to extend the function of expandfile is to write macros. As mentioned above, you can define these macros using *block, and expand them using *callv. If you decide to use the macros for more than one file, it is natural to move the macros to a separate file (I use the file suffix .htmi meaning "HTMX Include") and incorporate them into the files that invoke them with a line like %[*include,=macro_lib.htmi]%.

A library of macros called htmxlib.htmi is provided with expandfile. It contains macros you can use or adapt to your own needs. For example, one macro is getimgdiv, which can take care of cases where a graphic image is represented in multiple sizes so that the best looking image can be shown, depending on the viewer's screen resolution. Call it by

  %[*callv,getimgdiv,path,target,alttag,titletag,class,caption]%

See "Macros in Expandfile" for more information.

Multics Mode Formatting Expansions

expandfile also expands nine special text formatting macros developed to simplify the source of the multicians.org website, its first application. See "expandfile Features for Multicians.org" for more information.

To prevent these macros from being expanded and interpret them as literal text, precede them by a backslash (\). For example, \{: ... :}, \{\= ... =}, \{\+ ... +}, \{\- ... -}, \{\* ... *}, \{\[ ... ]}, \{\{ ... }}, \{\! ... !}, \{\@ ... @} .

How To Install expandfile

Create a directory called /bin in your home directory, and then add it to your PATH environment variable. Type the following in a Terminal window:

  cd $HOME
  mkdir bin
  echo "export PATH=$HOME/bin:$PATH" >> .bash_profile
  . .bash_profile

expandfile is written in the Perl language. Your computer must have Perl installed, and your Perl library has to have a few modules installed. Macs and most Linux systems come with Perl, with the library set up.

Download expandfile-kit.tar.gz by clicking on the link. Move the downloaded file into your home directory and expand it by typing

  tar xzf expandfile-kit.tar.gz

Try it out

You should be able to type the command

  expandfile

and get no output, and no error messages.

Mac

  1. Install Apple command line tools by doing sudo xcode-select --install
  2. Get the kit and install in ~/bin
    • expandfile
    • expandfile.pm
    • readbindsql.pm
    • readbindxml.pm

    Helper programs

    • htmxlib.htmi
    • gifsize2
    • fmtnum
    • filedaysold
    • filemodiso
    • filemodshort
    • filesizek
    • firstletter
    • lowercase
    • uppercase
  3. Make sure ~/bin is on your PATH
  4. (Install MySQL if you are going to use it)
  5. Make sure Perl CPAN modules are installed:
    • Term::ANSIColor
    • Bundle::CPAN
    • LWP::Simple
    • DBI
    • DBD::mysql
    • XML::LibXML

Linux

Same as Mac except that the command line tools are already installed.

Windows

Use an environment like PowerShell, MINGW, MSYS2, or Cygwin to install a command shell, and then use the Mac instructions.