Friday, 1 April 2016

Taming configuration files - making an executable

Overview

In my last blog I showed how to test that an Erlang term matched an arbitrary pattern, including testing for an iolist, a recursive structure.  The objective here is to package this code in an executable that can be deployed in our projects.  The output of a successful check will be a new config file with an ".eft" suffix.  This is to allow tools like make to do consistency checks.

To do this we package this code into an escript.  This requires a short driver program that presents the main/1 interface that escript requires.

The driver program

The name of the escript, and the name of the escript's main module must be the same, and this module must export main/1.  Having decided to name this script "config_check" then the main module bears that name. 

The main/1 entry point takes all of the parameters of the script as a list.  Here is the code.

-module (config_check).
-export([main/1,test/0]).

-purpose (
<<"Write etf version of Config file if valid.",
  "Other requirements are: runable as escript">>).

main([ConfigFile,SchemaFile]) ->
    DataDef = get_schema(SchemaFile),
    {ok,Config}  = file:consult(ConfigFile),
    Result       = term_defs:validate(DataDef,Config),
    write_results(Result,ConfigFile,Config).

write_results(true,ConfigFile,Config) ->
    OutputFile = [ConfigFile,".etf"],
    EtfData = term_to_binary(Config,[]),
    ok = file:write_file(OutputFile,EtfData);

write_results(false,_,_) ->
    ok.

get_schema(SchemaFile) ->
    get_schema_by_ext(filename:extension(SchemaFile),SchemaFile).

get_schema_by_ext(".etf",SchemaFile) ->
    {ok,Etf} = file:readfile(SchemaFile),
    binary_to_term(Etf);
get_schema_by_ext(_,SchemaFile) ->
    {ok,[DataDef]} = file:consult(SchemaFile),
    DataDef.
    

test() ->
    DataDef = "{list,{value,valid}}.",
    DefFile = "main_test.config_def",
    ok = file:write_file(DefFile,DataDef),
    ok = file:write_file("main_test_valid","valid."),
    ok = file:write_file("main_test_invalid","invalid."),
    main(["main_test_valid",DefFile]),
    %% correct behaviour of second test is to throw an exception
    %% catch it and clean up
    catch main(["main_test_invalid",DefFile]),
    true = filelib:is_regular("main_test_valid.etf"),
    false = filelib:is_regular("main_test_invalid.etf"),
    file:delete(DefFile),
    file:delete("main_test_valid"),
    file:delete("main_test_invalid"),
    file:delete("main_test_valid.etf"),
    pass.
    
As usual I have placed a test routine at the end of the module.  Note that as yet there is not test for definition files with ".etf" suffix, this is very naughty.  Notice too that the test DataDef is of the form
{list, {valid,value}}, not {valid,value}.  This is because all files read by file:consult are returned as lists.  In general any configuration file will be a list (or a property list which is still a list).

Making the script

Here I use my own customised version of mad available from https://github.com/tonywallace64/mad.  It does need a rebar.config but that can be empty.  To make the release using the mad executeable:

./mad release script config_check

To automate the process I then made the Makefile:

# Author Tony Wallace
all:config_check

config_check:ebin/config_check.beam ebin/term_defs.beam rebar.config
./mad release script config_check

ebin/%.beam: src/%.erl
erlc -o ebin $< 

clean:
rm config_check ebin/*.beam




No comments:

Post a Comment

Your comments are welcome.