1:- module(execution_context, [
    2    context_variable/3,             % +Name:atom, +Type, +Options:list
    3    context_variable_value/2,       % +Variable, -Value
    4    option_or_context_variable/2    % @Option, +Options
    5]).
Execution context provides means to define environment variable in variaty of ways. Similar to setting/4 you have to define register your context variable a priori by calling context_variable/3. The context variable can be then provided e.g. through command line arguments, environment variable or configuration env file. The value of the variable is resolved on the first call and then cached durring the runtime.
   12:- use_module(library(dcg/basics)).   13:- use_module(library(option)).   14:- use_module(library(pure_input)).   15
   16:- dynamic
   17    configuration/2,
   18    context_variable_def/3,
   19    variable_cache/2.   20
   21:- meta_predicate     
   22    context_variable(:, +, +),
   23    context_variable_value(:,-),    
   24    option_or_context_variable(:,+).   25
   26:- multifile 
   27    context_variable/3.   28
   29user:file_search_path(config, '.').
   30user:file_search_path(config, './config').
   31
   32:- create_prolog_flag(environment, production, [type(atom)]).   33:- initialization(set_prolog_flag(environment, production), prepare_state).   34:- set_prolog_flag(environment, development).   35
   36%%% PUBLIC PREDICATES %%%%%%%%%%%%%%%%%%%%%%%%%%%
 context_variable(+Name:atom, +Type, +Options:list) is det
Declares new contextual variable with the module specific name. Repeated declarations for the same Name are possible, the last call is taken into account but not after the value was queried (which would be served from the cache.

Type can be one of the atom, bool, number, string, list, list(atom), list(number). in case of list types, the list can be specified as a list of atoms or numbers separate by comma (,) or semicolon, and optionally enclosed into parenthesis or brackets (e.g. [ a, b,c, e], or (1;2; 3 ; 4), or o, p ; q, r ) will work. Elements in the list are trimmed of left and right spaces.

Options can be one of:

   66context_variable(Name, bool, Options) :-
   67    retractall(context_variable_def(Name,_,_)),
   68    assert(context_variable_def(Name, bool, [is_flag(true)|Options])),
   69    !.
   70context_variable(Name, Type, Options) :-
   71    retractall(context_variable_def(Name,_,_)),
   72    assert(context_variable_def(Name, Type, Options)).
 context_variable_value(+Variable, -Value) is semidet
Unifies Value with the contextual variable. The Variable must be declared using context_variable/3 call.

To resolve the contextual variable following steps are executed. The first step that succed will determine the Value

   91context_variable_value(Variable, Value) :-
   92    context_variable_def(Variable, Type, Options),
   93    once(resolve_context_variable(Variable, ValueAtom, Options)),
   94    once(adapt_type(ValueAtom, Type, Value)).
 option_or_context_variable(Option, Options) is semidet
As option/2, but if that fails then it behaves as context_variable(OptionFunctor, OptionArg). Options are checked for the module specific or local name of the option.
   99option_or_context_variable(Option, Options):-
  100    option(Option, Options), 
  101    !.
  102 option_or_context_variable( Option, Options):-
  103    Option =.. [ ':', _, LocalOption ],
  104    option( LocalOption, Options), 
  105    !.
  106option_or_context_variable( Option, _) :-
  107    Option =.. [':', Module, LocalOption],
  108    LocalOption =.. [ Var, Value],
  109    context_variable_value(Module:Var, Value).
  110
  111%%%  PRIVATE PREDICATES %%%%%%%%%%%%%%%%%%%%%%%%% 
  112
  113adapt_type(Value, atom, Value).
  114 adapt_type(Value, bool, true) :-
  115    downcase_atom(Value, Lower),
  116    memberchk(Lower, [true, yes, '1', t, y, ok, positive]), 
  117    !.
  118 adapt_type(_, bool, false).
  119 adapt_type(Atom, number, Value) :-
  120    atom_number(Atom, Value).
  121 adapt_type(Atom, string, Value) :-
  122    atom_string(Atom, Value).
  123 adapt_type(Atom, list, Value) :-
  124    adapt_type(Atom, list(atom), Value).
  125 adapt_type(Atom, list(atom), Value) :-
  126    atom_codes(Atom, Codes),
  127    phrase(value_list_grammar(Value), Codes).
  128 adapt_type(Atom, list(number), Value) :-
  129    adapt_type(Atom, list(atom), Atoms),
  130    maplist(atom_number, Atoms, Value).
  131
  132
  133comment -->
  134    "#", 
  135    string(_),
  136    nl.
  137
  138config([], Variable, Value) -->
  139    {        
  140        atom_codes(VariableName, Variable),
  141        atom_codes(ValueName, Value),        
  142        (
  143            retract(configuration(VariableName,_)),      
  144            fail
  145        ;
  146            true
  147        ),
  148        assertz(configuration(VariableName, ValueName))
  149    }.
  150 config(Module, Variable, Value) -->
  151    {   
  152        atom_codes(ModuleName, Module),     
  153        atom_codes(VariableName, Variable),
  154        (
  155            Value == true 
  156        ->  ValueName = true
  157        ;   atom_codes(ValueName, Value)
  158        ),          
  159        (
  160            retract(configuration(ModuleName:VariableName,_)),      
  161            fail
  162        ;
  163            true
  164        ),
  165        assertz(configuration(ModuleName:VariableName, ValueName))
  166    }.
  167
  168empty_line -->
  169    whites,
  170    nl.
  171
  172env_grammar --> eos.
  173env_grammar -->
  174    env_line,
  175    env_grammar.
  176
  177env_line -->
  178    comment,
  179    !.
  180 env_line -->
  181    empty_line,
  182    !.
  183 env_line -->
  184    module(Module),
  185    string_without("=\r\n", Variable),
  186    "=",
  187    string(Value),
  188    whites,
  189    nl,
  190    config(Module, Variable, Value),
  191    !.
  192 env_line -->   
  193    module(Module),
  194    string_without("\r\n", Variable),
  195    whites,
  196    nl,
  197    config(Module, Variable, true),
  198    !.
  199 
  200default_codes([], _, []) :- !.
  201 default_codes([I|In], C, [O|Out]) :-
  202    (
  203        member(I, [0' ,0'-, 0'_, 0'.])
  204    ->  O = C
  205    ;   O = I
  206    ),
  207    default_codes(In, C, Out).
  208    
  209default_name(Module:Variable, Separator, DefaultName) :-
  210    atomic_list_concat([Module, Variable], ' ', Default0),
  211    atom_codes(Default0, Codes0),
  212    atom_codes(Separator, [CodesSep|_]),
  213    default_codes(Codes0, CodesSep, Codes1),
  214    atom_codes(DefaultName, Codes1).
  215
  216find_argv([Arg| _], Long, _, true, false) :-
  217    atom_concat('--no-', Long, Arg).
  218 find_argv([Arg| _], Long, _, true, true) :-
  219    atom_concat('--', Long, Arg).
  220 find_argv([Arg| _], _, Short, true, true) :-
  221    Short \= [],
  222    atom_codes( Arg, ArgCodes),
  223    ArgCodes \= [ 0'-, 0'- | _ ],
  224    ArgCodes = [ 0'- | _ ],    
  225    atom_codes(Short, [Code|_]),
  226    memberchk(Code, ArgCodes).   
  227 find_argv([Arg| _], Long, _, _, Value) :-
  228    atomic_list_concat(['--', Long, '='], ArgPrefix),
  229    atom_concat(ArgPrefix, Value, Arg).
  230 find_argv([Arg, Value| _], Long, _, false, Value) :-
  231    atom_concat('--', Long, Arg).
  232 find_argv([Arg, Value| _], _, Short, false, Value) :-
  233    Short \= [],
  234    atom_concat('-', Shorts, Arg),
  235    \+ atom_concat('-', _, Shorts),
  236    atom_concat(_, Short, Shorts).
  237 find_argv([_| Args], Long, Short, IsFlag, Value) :-
  238    find_argv(Args, Long, Short, IsFlag, Value).
  239
  240load_configurations :-    
  241    abolish_module_tables(execution_context),
  242    retractall(variable_cache(_,_)),
  243    retractall(configuration(_,_)),
  244    load_configuration(config('config.env')),
  245    (   current_prolog_flag(environment, development)
  246    ->  load_configuration(project('config/config.dev.env')),
  247        load_configuration(project('config/config.user.env'))
  248    ;   true
  249    ),
  250    !.
  251
  252load_configuration(File) :-    
  253    absolute_file_name(File, AbsolutePath),
  254    exists_file(AbsolutePath),
  255    print_message(informational, format('Configurations loaded from the file ~w', [AbsolutePath])),
  256    setup_call_cleanup(
  257        open(AbsolutePath, read, Stream, [encoding(utf8)]),
  258        phrase_from_stream(env_grammar, Stream),
  259        close(Stream)).
  260 load_configuration(_).
  261
  262module(Module) -->
  263    string_without("\n\r:=", Module),
  264    ":", 
  265    !.
  266 module([]) --> [].
  267
  268nl --> "\r\n".
  269 nl --> "\n".
  270 nl --> eos.
  271
  272resolve_default(Module:_, Value, Options) :-
  273    option(default(context(ForwardVariable)), Options),
  274    ForwardVariable \= :(_,_),
  275    context_variable_value(Module:ForwardVariable, Value).
  276 resolve_default(_, Value, Options) :-
  277    option(default(context(Module:ForwardVariable)), Options),
  278    context_variable_value(Module:ForwardVariable, Value).
  279 
  280 resolve_default(_, Value, Options) :-
  281    option(default(Default), Options),
  282    Default \= context(_),
  283    format(atom(Value), '~w', Default).
  284
  285
  286resolve_command_line(Variable, Value, Options):-
  287    current_prolog_flag(os_argv, Argv),
  288    option(is_flag(IsFlag), Options, false),    
  289    option(short(Short), Options, []),
  290    default_name(Variable, '-', DefaultLong),
  291    option(long(Long), Options, DefaultLong),
  292    find_argv(Argv, Long, Short, IsFlag, Value).
  293
  294resolve_configuration(Variable, Value, _) :-
  295    configuration(Variable, Value).
  296 resolve_configuration(_, Value, Options) :-
  297    option(env(EnvName), Options),
  298    configuration(EnvName, Value).
  299
  300resolve_context_variable(Variable, Value, Options) :-
  301    variable_cache(Variable, Value)
  302    ->  true
  303    ;   (                
  304            resolve_command_line(Variable, Value, Options)
  305        ; 
  306            resolve_environment(Variable, Value, Options)
  307        ;
  308            resolve_configuration(Variable, Value, Options)
  309        ; 
  310            resolve_default(Variable, Value, Options)
  311        ),
  312        asserta(variable_cache(Variable, Value)).
  313
  314resolve_environment(Variable, Value, Options) :-
  315    default_name(Variable, '_', DefaultName),
  316    option(env(EnvName), Options,  DefaultName),
  317    getenv(EnvName, Value).
  318
  319
  320value_list_close --> whites, ("]" ; ")" ; []), whites, !.
  321
  322
  323value_list_element(Element) -->
  324    whites,
  325    string(String),
  326    whites,
  327    { atom_codes(Element, String)}.
  328
  329value_list_elements([Element|List]) -->    
  330    value_list_element(Element),
  331    value_list_separator,
  332    value_list_elements(List).
  333 value_list_elements([Element]) -->    
  334    value_list_element(Element).
  335
  336
  337value_list_grammar(List) -->
  338    value_list_open,
  339    value_list_elements(List),
  340    value_list_close.
  341 value_list_grammar([]) -->
  342    value_list_open,    
  343    value_list_close.
  344
  345value_list_open --> whites, ("[" ; "(" ; []), !.
  346
  347value_list_separator --> ",".
  348value_list_separator --> ";".
  349
  350:- initialization(load_configurations).