View source with raw comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2007-2016, University of Amsterdam
    7                              VU University Amsterdam
    8    All rights reserved.
    9
   10    Redistribution and use in source and binary forms, with or without
   11    modification, are permitted provided that the following conditions
   12    are met:
   13
   14    1. Redistributions of source code must retain the above copyright
   15       notice, this list of conditions and the following disclaimer.
   16
   17    2. Redistributions in binary form must reproduce the above copyright
   18       notice, this list of conditions and the following disclaimer in
   19       the documentation and/or other materials provided with the
   20       distribution.
   21
   22    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   23    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   24    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   25    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   26    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   27    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   28    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   29    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   30    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   31    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   32    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   33    POSSIBILITY OF SUCH DAMAGE.
   34*/
   35
   36:- module(settings,
   37          [ setting/4,                  % :Name, +Type, +Default, +Comment
   38            setting/2,                  % :Name, ?Value
   39            set_setting/2,              % :Name, +Value
   40            set_setting_default/2,      % :Name, +Value
   41            restore_setting/1,          % :Name
   42            load_settings/1,            % +File
   43            load_settings/2,            % +File, +Options
   44            save_settings/0,
   45            save_settings/1,            % +File
   46            current_setting/1,          % Module:Name
   47            setting_property/2,         % ?Setting, ?Property
   48            list_settings/0,
   49            list_settings/1,            % +Module
   50
   51            convert_setting_text/3      % +Type, +Text, -Value
   52          ]).   53:- use_module(library(error)).   54:- use_module(library(broadcast)).   55:- use_module(library(debug)).   56:- use_module(library(option)).   57:- use_module(library(arithmetic)).   58:- set_prolog_flag(generate_debug_info, false).

Setting management

This library allows management of configuration settings for Prolog applications. Applications define settings in one or multiple files using the directive setting/4 as illustrated below:

:- use_module(library(settings)).

:- setting(version, atom,   '1.0', 'Current version').
:- setting(timeout, number,    20, 'Timeout in seconds').

The directive is subject to term_expansion/2, which guarantees proper synchronisation of the database if source-files are reloaded. This implies it is not possible to call setting/4 as a predicate.

Settings are local to a module. This implies they are defined in a two-level namespace. Managing settings per module greatly simplifies assembling large applications from multiple modules that configuration through settings. This settings management library ensures proper access, loading and saving of settings.

author
- Jan Wielemaker */
See also
- library(config) distributed with XPCE provides an alternative aimed at graphical applications.
   88:- dynamic
   89    st_value/3,                     % Name, Module, Value
   90    st_default/3,                   % Name, Module, Value
   91    local_file/1.                   % Path
   92
   93:- multifile
   94    current_setting/6.              % Name, Module, Type, Default, Comment, Source
   95
   96:- meta_predicate
   97    setting(:, +, +, +),
   98    setting(:, ?),
   99    set_setting(:, +),
  100    set_setting_default(:, +),
  101    current_setting(:),
  102    restore_setting(:).  103
  104:- predicate_options(load_settings/2, 2, [undefined(oneof([load,error]))]).  105
  106curr_setting(Name, Module, Type, Default, Comment, Src) :-
  107    current_setting(Name, Module, Type, Default0, Comment, Src),
  108    (   st_default(Name, Module, Default1)
  109    ->  Default = Default1
  110    ;   Default = Default0
  111    ).
 setting(:Name, +Type, +Default, +Comment) is det
Define a setting. Name denotes the name of the setting, Type its type. Default is the value before it is modified. Default can refer to environment variables and can use arithmetic expressions as defined by eval_default/4.

If a second declaration for a setting is encountered, it is ignored if Type and Default are the same. Otherwise a permission_error is raised.

Arguments:
Name- Name of the setting (an atom)
Type- Type for setting. One of any or a type defined by must_be/2.
Default- Default value for the setting.
Comment- Atom containing a (short) descriptive note.
  131setting(Name, Type, Default, Comment) :-
  132    throw(error(context_error(nodirective,
  133                              setting(Name, Type, Default, Comment)),
  134                _)).
  135
  136:- multifile
  137    system:term_expansion/2.  138
  139system:term_expansion((:- setting(QName, Type, Default, Comment)),
  140                    Expanded) :-
  141    \+ current_prolog_flag(xref, true),
  142    prolog_load_context(module, M0),
  143    strip_module(M0:QName, Module, Name),
  144    must_be(atom, Name),
  145    to_atom(Comment, CommentAtom),
  146    eval_default(Default, Module, Type, Value),
  147    check_type(Type, Value),
  148    source_location(File, Line),
  149    (   current_setting(Name, Module, OType, ODef, _, OldLoc),
  150        (   OType \=@= Type
  151        ;    ODef \=@= Default
  152        ),
  153        OldLoc \= (File:_)
  154    ->  format(string(Message),
  155               'Already defined at: ~w', [OldLoc]),
  156        throw(error(permission_error(redefine, setting, Module:Name),
  157                    context(Message, _)))
  158    ;   Expanded = settings:current_setting(Name, Module, Type, Default,
  159                                            CommentAtom, File:Line)
  160    ).
  161
  162to_atom(Atom, Atom) :-
  163    atom(Atom),
  164    !.
  165to_atom(String, Atom) :-
  166    format(atom(Atom), '~s', String).
 setting(:Name, ?Value) is nondet
True when Name is a currently defined setting with Value. Note that setting(Name, Value) only enumerates the settings of the current module. All settings can be enumerated using setting(Module:Name, Value). This predicate is det if Name is ground.
Errors
- existence_error(setting, Name)
  178setting(Module:Name, Value) :-
  179    (   nonvar(Name), nonvar(Module)
  180    ->  (   st_value(Name, Module, Value0)
  181        ->  Value = Value0
  182        ;   curr_setting(Name, Module, Type, Default, _, _)
  183        ->  eval_default(Default, Module, Type, Value)
  184        ;   existence_error(setting, Module:Name)
  185        )
  186    ;   current_setting(Name, Module, _, _, _, _),
  187        setting(Module:Name, Value)
  188    ).
  189
  190
  191:- dynamic
  192    setting_cache/3.  193:- volatile
  194    setting_cache/3.
 clear_setting_cache is det
Clear the cache for evaluation of default values.
  200clear_setting_cache :-
  201    retractall(setting_cache(_,_,_)).
 eval_default(+Default, +Module, +Type, -Value) is det
Convert the settings default value. The notation allows for some `function-style' notations to make the library more generic:
env(Name)
Get value from the given environment variable. The value is handed to convert_setting_text/3 to convert the textual representation into a Prolog term. Raises an existence_error of the variable is not defined.
env(Name, Default)
As env(Name), but uses the value Default if the variable is not defined.
setting(Name)
Ask the value of another setting.
Expression
If Type is numeric, evaluate the expression. env(Var) evaluates to the value of an environment variable. If Type is atom, concatenate A+B+.... Elements of the expression can be env(Name).
  227:- multifile
  228    eval_default/3.                 % +Default, +Type, -Value
  229
  230eval_default(Default, _, _Type, Value) :-
  231    var(Default),
  232    !,
  233    Value = Default.
  234eval_default(Default, _, Type, Value) :-
  235    eval_default(Default, Type, Val),
  236    !,
  237    Value = Val.
  238eval_default(Default, _, _, Value) :-
  239    atomic(Default),
  240    !,
  241    Value = Default.
  242eval_default(Default, _, Type, Value) :-
  243    setting_cache(Default, Type, Val),
  244    !,
  245    Value = Val.
  246eval_default(env(Name), _, Type, Value) :-
  247    !,
  248    (   getenv(Name, TextValue)
  249    ->  convert_setting_text(Type, TextValue, Val),
  250        assert(setting_cache(env(Name), Type, Val)),
  251        Value = Val
  252    ;   existence_error(environment_variable, Name)
  253    ).
  254eval_default(env(Name, Default), _, Type, Value) :-
  255    !,
  256    (   getenv(Name, TextValue)
  257    ->  convert_setting_text(Type, TextValue, Val)
  258    ;   Val = Default
  259    ),
  260    assert(setting_cache(env(Name), Type, Val)),
  261    Value = Val.
  262eval_default(setting(Name), Module, Type, Value) :-
  263    !,
  264    strip_module(Module:Name, M, N),
  265    setting(M:N, Value),
  266    must_be(Type, Value).
  267eval_default(Expr, _, Type, Value) :-
  268    numeric_type(Type, Basic),
  269    !,
  270    arithmetic_expression_value(Expr, Val0),
  271    (   Basic == float
  272    ->  Val is float(Val0)
  273    ;   Basic = integer
  274    ->  Val is round(Val0)
  275    ;   Val = Val0
  276    ),
  277    assert(setting_cache(Expr, Type, Val)),
  278    Value = Val.
  279eval_default(A+B, Module, atom, Value) :-
  280    !,
  281    phrase(expr_to_list(A+B, Module), L),
  282    atomic_list_concat(L, Val),
  283    assert(setting_cache(A+B, atom, Val)),
  284    Value = Val.
  285eval_default(List, Module, list(Type), Value) :-
  286    !,
  287    eval_list_default(List, Module, Type, Val),
  288    assert(setting_cache(List, list(Type), Val)),
  289    Value = Val.
  290eval_default(Default, _, _, Default).
 eval_list_default(+List, +Module, +ElementType, -DefaultList)
Evaluate the default for a list of values.
  297eval_list_default([], _, _, []).
  298eval_list_default([H0|T0], Module, Type, [H|T]) :-
  299    eval_default(H0, Module, Type, H),
  300    eval_list_default(T0, Module, Type, T).
 expr_to_list(+Expression, +Module)// is det
Process the components to create an atom. Atom concatenation is expressed as A+B. Components may refer to envrionment variables.
  307expr_to_list(A+B, Module) -->
  308    !,
  309    expr_to_list(A, Module),
  310    expr_to_list(B, Module).
  311expr_to_list(env(Name), _) -->
  312    !,
  313    (   { getenv(Name, Text) }
  314    ->  [Text]
  315    ;   { existence_error(environment_variable, Name) }
  316    ).
  317expr_to_list(env(Name, Default), _) -->
  318    !,
  319    (   { getenv(Name, Text) }
  320    ->  [Text]
  321    ;   [Default]
  322    ).
  323expr_to_list(setting(Name), Module) -->
  324    !,
  325    { strip_module(Module:Name, M, N),
  326      setting(M:N, Value)
  327    },
  328    [ Value ].
  329expr_to_list(A, _) -->
  330    [A].
 env(+Name:atom, -Value:number) is det
 env(+Name:atom, +Default:number, -Value:number) is det
Evaluate environment variables on behalf of arithmetic expressions.
  338:- arithmetic_function(env/1).  339:- arithmetic_function(env/2).  340
  341env(Name, Value) :-
  342    (   getenv(Name, Text)
  343    ->  convert_setting_text(number, Text, Value)
  344    ;   existence_error(environment_variable, Name)
  345    ).
  346env(Name, Default, Value) :-
  347    (   getenv(Name, Text)
  348    ->  convert_setting_text(number, Text, Value)
  349    ;   Value = Default
  350    ).
 numeric_type(+Type, -BaseType)
True if Type is a numeric type and BaseType is the associated basic Prolog type. BaseType is one of integer, float or number.
  359numeric_type(integer, integer).
  360numeric_type(nonneg, integer).
  361numeric_type(float, float).
  362numeric_type(between(L,_), Type) :-
  363    ( integer(L) -> Type = integer ; Type = float ).
 set_setting(:Name, +Value) is det
Change a setting. Performs existence and type-checking for the setting. If the effective value of the setting is changed it broadcasts the event below.
settings(changed(Module:Name, Old, New))
Errors
- existence_error(setting, Name)
- type_error(Type, Value)
  377set_setting(QName, Value) :-
  378    strip_module(QName, Module, Name),
  379    must_be(atom, Name),
  380    (   curr_setting(Name, Module, Type, Default0, _Comment, _Src),
  381        eval_default(Default0, Module, Type, Default)
  382    ->  setting(Module:Name, Old),
  383        (   Value == Default
  384        ->  retract_setting(Module:Name)
  385        ;   st_value(Name, Module, Value)
  386        ->  true
  387        ;   check_type(Type, Value)
  388        ->  retract_setting(Module:Name),
  389            assert_setting(Module:Name, Value)
  390        ),
  391        (   Old == Value
  392        ->  true
  393        ;   broadcast(settings(changed(Module:Name, Old, Value))),
  394            clear_setting_cache     % might influence dependent settings
  395        )
  396    ;   existence_error(setting, Name)
  397    ).
  398
  399retract_setting(Module:Name) :-
  400    retractall(st_value(Name, Module, _)).
  401
  402assert_setting(Module:Name, Value) :-
  403    assert(st_value(Name, Module, Value)).
 restore_setting(:Name) is det
Restore the value of setting Name to its default. Broadcast a change like set_setting/2 if the current value is not the default.
  411restore_setting(QName) :-
  412    strip_module(QName, Module, Name),
  413    must_be(atom, Name),
  414    (   st_value(Name, Module, Old)
  415    ->  retract_setting(Module:Name),
  416        setting(Module:Name, Value),
  417        (   Old \== Value
  418        ->  broadcast(settings(changed(Module:Name, Old, Value)))
  419        ;   true
  420        )
  421    ;   true
  422    ).
 set_setting_default(:Name, +Default) is det
Change the default for a setting. The effect is the same as set_setting/2, but the new value is considered the default when saving and restoring a setting. It is intended to change application defaults in a particular context.
  431set_setting_default(QName, Default) :-
  432    strip_module(QName, Module, Name),
  433    must_be(atom, Name),
  434    (   current_setting(Name, Module, Type, Default0, _Comment, _Src)
  435    ->  retractall(settings:st_default(Name, Module, _)),
  436        retract_setting(Module:Name),
  437        (   Default == Default0
  438        ->  true
  439        ;   assert(settings:st_default(Name, Module, Default))
  440        ),
  441        eval_default(Default, Module, Type, Value),
  442        set_setting(Module:Name, Value)
  443    ;   existence_error(setting, Module:Name)
  444    ).
  445
  446
  447                 /*******************************
  448                 *             TYPES            *
  449                 *******************************/
 check_type(+Type, +Term)
Type checking for settings. Currently simply forwarded to must_be/2.
  456check_type(Type, Term) :-
  457    must_be(Type, Term).
  458
  459
  460                 /*******************************
  461                 *             FILE             *
  462                 *******************************/
 load_settings(File) is det
 load_settings(File, +Options) is det
Load local settings from File. Succeeds if File does not exist, setting the default save-file to File. Options are:
undefined(+Action)
Define how to handle settings that are not defined. When error, an error is printed and the setting is ignored. when load, the setting is loaded anyway, waiting for a definition.
  476load_settings(File) :-
  477    load_settings(File, []).
  478
  479load_settings(File, Options) :-
  480    absolute_file_name(File, Path,
  481                       [ access(read),
  482                         file_errors(fail)
  483                       ]),
  484    !,
  485    assert(local_file(Path)),
  486    open(Path, read, In, [encoding(utf8)]),
  487    read_setting(In, T0),
  488    call_cleanup(load_settings(T0, In, Options), close(In)),
  489    clear_setting_cache.
  490load_settings(File, _) :-
  491    absolute_file_name(File, Path,
  492                       [ access(write),
  493                         file_errors(fail)
  494                       ]),
  495    !,
  496    assert(local_file(Path)).
  497load_settings(_, _).
  498
  499load_settings(end_of_file, _, _) :- !.
  500load_settings(Setting, In, Options) :-
  501    catch(store_setting(Setting, Options), E,
  502          print_message(warning, E)),
  503    read_setting(In, Next),
  504    load_settings(Next, In, Options).
  505
  506read_setting(In, Term) :-
  507    read_term(In, Term,
  508              [ syntax_errors(dec10)
  509              ]).
 store_setting(Term, +Options)
Store setting loaded from file in the Prolog database.
  515store_setting(setting(Module:Name, Value), _) :-
  516    curr_setting(Name, Module, Type, Default0, _Commentm, _Src),
  517    !,
  518    eval_default(Default0, Module, Type, Default),
  519    (   Value == Default
  520    ->  true
  521    ;   check_type(Type, Value)
  522    ->  retractall(st_value(Name, Module, _)),
  523        assert(st_value(Name, Module, Value)),
  524        broadcast(settings(changed(Module:Name, Default, Value)))
  525    ).
  526store_setting(setting(Module:Name, Value), Options) :-
  527    !,
  528    (   option(undefined(load), Options, load)
  529    ->  retractall(st_value(Name, Module, _)),
  530        assert(st_value(Name, Module, Value))
  531    ;   existence_error(setting, Module:Name)
  532    ).
  533store_setting(Term, _) :-
  534    type_error(setting, Term).
 save_settings is det
 save_settings(+File) is det
Save modified settings to File.
  541save_settings :-
  542    local_file(File),
  543    !,
  544    save_settings(File).
  545
  546save_settings(File) :-
  547    absolute_file_name(File, Path,
  548                       [ access(write)
  549                       ]),
  550    !,
  551    open(Path, write, Out,
  552         [ encoding(utf8),
  553           bom(true)
  554         ]),
  555    write_setting_header(Out),
  556    forall(current_setting(Name, Module, _, _, _, _),
  557           save_setting(Out, Module:Name)),
  558    close(Out).
  559
  560
  561write_setting_header(Out) :-
  562    get_time(Now),
  563    format_time(string(Date), '%+', Now),
  564    format(Out, '/*  Saved settings~n', []),
  565    format(Out, '    Date: ~w~n', [Date]),
  566    format(Out, '*/~n~n', []).
  567
  568save_setting(Out, Module:Name) :-
  569    curr_setting(Name, Module, Type, Default, Comment, _Src),
  570    (   st_value(Name, Module, Value),
  571        \+ ( eval_default(Default, Module, Type, DefValue),
  572             debug(setting, '~w <-> ~w~n', [DefValue, Value]),
  573             DefValue =@= Value
  574           )
  575    ->  format(Out, '~n%\t~w~n', [Comment]),
  576        format(Out, 'setting(~q:~q, ~q).~n', [Module, Name, Value])
  577    ;   true
  578    ).
 current_setting(?Setting) is nondet
True if Setting is a currently defined setting
  584current_setting(Setting) :-
  585    ground(Setting),
  586    !,
  587    strip_module(Setting, Module, Name),
  588    current_setting(Name, Module, _, _, _, _).
  589current_setting(Module:Name) :-
  590    current_setting(Name, Module, _, _, _, _).
 setting_property(+Setting, +Property) is det
setting_property(?Setting, ?Property) is nondet
Query currently defined settings. Property is one of
comment(-Atom)
type(-Type)
Type of the setting.
default(-Default)
Default value. If this is an expression, it is evaluated.
source((-File:-Line))
Location where the setting is defined.
  606setting_property(Setting, Property) :-
  607    ground(Setting),
  608    !,
  609    Setting = Module:Name,
  610    curr_setting(Name, Module, Type, Default, Comment, Src),
  611    !,
  612    setting_property(Property, Module, Type, Default, Comment, Src).
  613setting_property(Setting, Property) :-
  614    Setting = Module:Name,
  615    curr_setting(Name, Module, Type, Default, Comment, Src),
  616    setting_property(Property, Module, Type, Default, Comment, Src).
  617
  618setting_property(type(Type), _, Type, _, _, _).
  619setting_property(default(Default), M, Type, Default0, _, _) :-
  620    eval_default(Default0, M, Type, Default).
  621setting_property(comment(Comment), _, _, _, Comment, _).
  622setting_property(source(Src), _, _, _, _, Src).
 list_settings is det
 list_settings(+Module) is det
List settings to current_output. The second form only lists settings on the matching module.
To be done
- Compute the required column widths
  632list_settings :-
  633    list_settings(_).
  634
  635list_settings(Spec) :-
  636    spec_term(Spec, Term),
  637    TS1 = 25,
  638    TS2 = 40,
  639    format('~`=t~72|~n'),
  640    format('~w~t~*| ~w~w~t~*| ~w~n',
  641           ['Name', TS1, 'Value (*=modified)', '', TS2, 'Comment']),
  642    format('~`=t~72|~n'),
  643    forall(current_setting(Term),
  644           list_setting(Term, TS1, TS2)).
  645
  646spec_term(M:S, M:S) :- !.
  647spec_term(M, M:_).
  648
  649
  650list_setting(Module:Name, TS1, TS2) :-
  651    curr_setting(Name, Module, Type, Default0, Comment, _Src),
  652    eval_default(Default0, Module, Type, Default),
  653    setting(Module:Name, Value),
  654    (   Value \== Default
  655    ->  Modified = (*)
  656    ;   Modified = ''
  657    ),
  658    format('~w~t~*| ~q~w~t~*| ~w~n', [Module:Name, TS1, Value, Modified, TS2, Comment]).
  659
  660
  661                 /*******************************
  662                 *            TYPES             *
  663                 *******************************/
 convert_setting_text(+Type, +Text, -Value)
Converts from textual form to Prolog Value. Used to convert values obtained from the environment. Public to provide support in user-interfaces to this library.
Errors
- type_error(Type, Value)
  673:- multifile
  674    convert_text/3.                 % +Type, +Text, -Value
  675
  676convert_setting_text(Type, Text, Value) :-
  677    convert_text(Type, Text, Value),
  678    !.
  679convert_setting_text(atom, Value, Value) :-
  680    !,
  681    must_be(atom, Value).
  682convert_setting_text(boolean, Value, Value) :-
  683    !,
  684    must_be(boolean, Value).
  685convert_setting_text(integer, Atom, Number) :-
  686    !,
  687    term_to_atom(Term, Atom),
  688    Number is round(Term).
  689convert_setting_text(float, Atom, Number) :-
  690    !,
  691    term_to_atom(Term, Atom),
  692    Number is float(Term).
  693convert_setting_text(between(L,U), Atom, Number) :-
  694    !,
  695    (   integer(L)
  696    ->  convert_setting_text(integer, Atom, Number)
  697    ;   convert_setting_text(float, Atom, Number)
  698    ),
  699    must_be(between(L,U), Number).
  700convert_setting_text(Type, Atom, Term) :-
  701    term_to_atom(Term, Atom),
  702    must_be(Type, Term).
  703
  704
  705                 /*******************************
  706                 *            SANDBOX           *
  707                 *******************************/
  708
  709:- multifile
  710    sandbox:safe_meta_predicate/1.  711
  712sandbox:safe_meta_predicate(settings:setting/2)