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(arithmetic),[arithmetic_expression_value/2]).   54
   55:- autoload(library(broadcast),[broadcast/1]).   56:- autoload(library(debug),[debug/3]).   57:- autoload(library(error),[must_be/2,existence_error/2,type_error/2]).   58:- autoload(library(option),[option/3]).   59
   60
   61:- 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.
   91:- dynamic
   92    st_value/3,                     % Name, Module, Value
   93    st_default/3,                   % Name, Module, Value
   94    local_file/1.                   % Path
   95
   96:- multifile
   97    current_setting/6.              % Name, Module, Type, Default, Comment, Source
   98
   99:- meta_predicate
  100    setting(:, +, +, +),
  101    setting(:, ?),
  102    set_setting(:, +),
  103    set_setting_default(:, +),
  104    current_setting(:),
  105    restore_setting(:).  106
  107:- predicate_options(load_settings/2, 2, [undefined(oneof([load,error]))]).  108
  109curr_setting(Name, Module, Type, Default, Comment, Src) :-
  110    current_setting(Name, Module, Type, Default0, Comment, Src),
  111    (   st_default(Name, Module, Default1)
  112    ->  Default = Default1
  113    ;   Default = Default0
  114    ).
 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.
  134setting(Name, Type, Default, Comment) :-
  135    throw(error(context_error(nodirective,
  136                              setting(Name, Type, Default, Comment)),
  137                _)).
  138
  139:- multifile
  140    system:term_expansion/2.  141
  142system:term_expansion((:- setting(QName, Type, Default, Comment)),
  143                    Expanded) :-
  144    \+ current_prolog_flag(xref, true),
  145    prolog_load_context(module, M0),
  146    strip_module(M0:QName, Module, Name),
  147    must_be(atom, Name),
  148    to_atom(Comment, CommentAtom),
  149    eval_default(Default, Module, Type, Value),
  150    check_type(Type, Value),
  151    source_location(File, Line),
  152    (   current_setting(Name, Module, OType, ODef, _, OldLoc),
  153        (   OType \=@= Type
  154        ;    ODef \=@= Default
  155        ),
  156        OldLoc \= (File:_)
  157    ->  format(string(Message),
  158               'Already defined at: ~w', [OldLoc]),
  159        throw(error(permission_error(redefine, setting, Module:Name),
  160                    context(Message, _)))
  161    ;   Expanded = settings:current_setting(Name, Module, Type, Default,
  162                                            CommentAtom, File:Line)
  163    ).
  164
  165to_atom(Atom, Atom) :-
  166    atom(Atom),
  167    !.
  168to_atom(String, Atom) :-
  169    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)
  181setting(Module:Name, Value) :-
  182    (   nonvar(Name), nonvar(Module)
  183    ->  (   st_value(Name, Module, Value0)
  184        ->  Value = Value0
  185        ;   curr_setting(Name, Module, Type, Default, _, _)
  186        ->  eval_default(Default, Module, Type, Value)
  187        ;   existence_error(setting, Module:Name)
  188        )
  189    ;   current_setting(Name, Module, _, _, _, _),
  190        setting(Module:Name, Value)
  191    ).
  192
  193
  194:- dynamic
  195    setting_cache/3.  196:- volatile
  197    setting_cache/3.
 clear_setting_cache is det
Clear the cache for evaluation of default values.
  203clear_setting_cache :-
  204    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).
  230:- multifile
  231    eval_default/3.                 % +Default, +Type, -Value
  232
  233eval_default(Default, _, _Type, Value) :-
  234    var(Default),
  235    !,
  236    Value = Default.
  237eval_default(Default, _, Type, Value) :-
  238    eval_default(Default, Type, Val),
  239    !,
  240    Value = Val.
  241eval_default(Default, _, _, Value) :-
  242    atomic(Default),
  243    !,
  244    Value = Default.
  245eval_default(Default, _, Type, Value) :-
  246    setting_cache(Default, Type, Val),
  247    !,
  248    Value = Val.
  249eval_default(env(Name), _, Type, Value) :-
  250    !,
  251    (   getenv(Name, TextValue)
  252    ->  convert_setting_text(Type, TextValue, Val),
  253        assert(setting_cache(env(Name), Type, Val)),
  254        Value = Val
  255    ;   existence_error(environment_variable, Name)
  256    ).
  257eval_default(env(Name, Default), _, Type, Value) :-
  258    !,
  259    (   getenv(Name, TextValue)
  260    ->  convert_setting_text(Type, TextValue, Val)
  261    ;   Val = Default
  262    ),
  263    assert(setting_cache(env(Name), Type, Val)),
  264    Value = Val.
  265eval_default(setting(Name), Module, Type, Value) :-
  266    !,
  267    strip_module(Module:Name, M, N),
  268    setting(M:N, Value),
  269    must_be(Type, Value).
  270eval_default(Expr, _, Type, Value) :-
  271    numeric_type(Type, Basic),
  272    !,
  273    arithmetic_expression_value(Expr, Val0),
  274    (   Basic == float
  275    ->  Val is float(Val0)
  276    ;   Basic = integer
  277    ->  Val is round(Val0)
  278    ;   Val = Val0
  279    ),
  280    assert(setting_cache(Expr, Type, Val)),
  281    Value = Val.
  282eval_default(A+B, Module, atom, Value) :-
  283    !,
  284    phrase(expr_to_list(A+B, Module), L),
  285    atomic_list_concat(L, Val),
  286    assert(setting_cache(A+B, atom, Val)),
  287    Value = Val.
  288eval_default(List, Module, list(Type), Value) :-
  289    !,
  290    eval_list_default(List, Module, Type, Val),
  291    assert(setting_cache(List, list(Type), Val)),
  292    Value = Val.
  293eval_default(Default, _, _, Default).
 eval_list_default(+List, +Module, +ElementType, -DefaultList)
Evaluate the default for a list of values.
  300eval_list_default([], _, _, []).
  301eval_list_default([H0|T0], Module, Type, [H|T]) :-
  302    eval_default(H0, Module, Type, H),
  303    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.
  310expr_to_list(A+B, Module) -->
  311    !,
  312    expr_to_list(A, Module),
  313    expr_to_list(B, Module).
  314expr_to_list(env(Name), _) -->
  315    !,
  316    (   { getenv(Name, Text) }
  317    ->  [Text]
  318    ;   { existence_error(environment_variable, Name) }
  319    ).
  320expr_to_list(env(Name, Default), _) -->
  321    !,
  322    (   { getenv(Name, Text) }
  323    ->  [Text]
  324    ;   [Default]
  325    ).
  326expr_to_list(setting(Name), Module) -->
  327    !,
  328    { strip_module(Module:Name, M, N),
  329      setting(M:N, Value)
  330    },
  331    [ Value ].
  332expr_to_list(A, _) -->
  333    [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.
  341:- arithmetic_function(env/1).  342:- arithmetic_function(env/2).  343
  344env(Name, Value) :-
  345    (   getenv(Name, Text)
  346    ->  convert_setting_text(number, Text, Value)
  347    ;   existence_error(environment_variable, Name)
  348    ).
  349env(Name, Default, Value) :-
  350    (   getenv(Name, Text)
  351    ->  convert_setting_text(number, Text, Value)
  352    ;   Value = Default
  353    ).
 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.
  362numeric_type(integer, integer).
  363numeric_type(nonneg, integer).
  364numeric_type(float, float).
  365numeric_type(between(L,_), Type) :-
  366    ( 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)
  380set_setting(QName, Value) :-
  381    strip_module(QName, Module, Name),
  382    must_be(atom, Name),
  383    (   curr_setting(Name, Module, Type, Default0, _Comment, _Src),
  384        eval_default(Default0, Module, Type, Default)
  385    ->  setting(Module:Name, Old),
  386        (   Value == Default
  387        ->  retract_setting(Module:Name)
  388        ;   st_value(Name, Module, Value)
  389        ->  true
  390        ;   check_type(Type, Value)
  391        ->  retract_setting(Module:Name),
  392            assert_setting(Module:Name, Value)
  393        ),
  394        (   Old == Value
  395        ->  true
  396        ;   broadcast(settings(changed(Module:Name, Old, Value))),
  397            clear_setting_cache     % might influence dependent settings
  398        )
  399    ;   existence_error(setting, Name)
  400    ).
  401
  402retract_setting(Module:Name) :-
  403    retractall(st_value(Name, Module, _)).
  404
  405assert_setting(Module:Name, Value) :-
  406    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.
  414restore_setting(QName) :-
  415    strip_module(QName, Module, Name),
  416    must_be(atom, Name),
  417    (   st_value(Name, Module, Old)
  418    ->  retract_setting(Module:Name),
  419        setting(Module:Name, Value),
  420        (   Old \== Value
  421        ->  broadcast(settings(changed(Module:Name, Old, Value)))
  422        ;   true
  423        )
  424    ;   true
  425    ).
 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.
  434set_setting_default(QName, Default) :-
  435    strip_module(QName, Module, Name),
  436    must_be(atom, Name),
  437    (   current_setting(Name, Module, Type, Default0, _Comment, _Src)
  438    ->  retractall(settings:st_default(Name, Module, _)),
  439        retract_setting(Module:Name),
  440        (   Default == Default0
  441        ->  true
  442        ;   assert(settings:st_default(Name, Module, Default))
  443        ),
  444        eval_default(Default, Module, Type, Value),
  445        set_setting(Module:Name, Value)
  446    ;   existence_error(setting, Module:Name)
  447    ).
  448
  449
  450                 /*******************************
  451                 *             TYPES            *
  452                 *******************************/
 check_type(+Type, +Term)
Type checking for settings. Currently simply forwarded to must_be/2.
  459check_type(Type, Term) :-
  460    must_be(Type, Term).
  461
  462
  463                 /*******************************
  464                 *             FILE             *
  465                 *******************************/
 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.
  479load_settings(File) :-
  480    load_settings(File, []).
  481
  482load_settings(File, Options) :-
  483    absolute_file_name(File, Path,
  484                       [ access(read),
  485                         file_errors(fail)
  486                       ]),
  487    !,
  488    assert(local_file(Path)),
  489    open(Path, read, In, [encoding(utf8)]),
  490    read_setting(In, T0),
  491    call_cleanup(load_settings(T0, In, Options), close(In)),
  492    clear_setting_cache.
  493load_settings(File, _) :-
  494    absolute_file_name(File, Path,
  495                       [ access(write),
  496                         file_errors(fail)
  497                       ]),
  498    !,
  499    assert(local_file(Path)).
  500load_settings(_, _).
  501
  502load_settings(end_of_file, _, _) :- !.
  503load_settings(Setting, In, Options) :-
  504    catch(store_setting(Setting, Options), E,
  505          print_message(warning, E)),
  506    read_setting(In, Next),
  507    load_settings(Next, In, Options).
  508
  509read_setting(In, Term) :-
  510    read_term(In, Term,
  511              [ syntax_errors(dec10)
  512              ]).
 store_setting(Term, +Options)
Store setting loaded from file in the Prolog database.
  518store_setting(setting(Module:Name, Value), _) :-
  519    curr_setting(Name, Module, Type, Default0, _Commentm, _Src),
  520    !,
  521    eval_default(Default0, Module, Type, Default),
  522    (   Value == Default
  523    ->  true
  524    ;   check_type(Type, Value)
  525    ->  retractall(st_value(Name, Module, _)),
  526        assert(st_value(Name, Module, Value)),
  527        broadcast(settings(changed(Module:Name, Default, Value)))
  528    ).
  529store_setting(setting(Module:Name, Value), Options) :-
  530    !,
  531    (   option(undefined(load), Options, load)
  532    ->  retractall(st_value(Name, Module, _)),
  533        assert(st_value(Name, Module, Value))
  534    ;   existence_error(setting, Module:Name)
  535    ).
  536store_setting(Term, _) :-
  537    type_error(setting, Term).
 save_settings is det
 save_settings(+File) is det
Save modified settings to File.
  544save_settings :-
  545    local_file(File),
  546    !,
  547    save_settings(File).
  548
  549save_settings(File) :-
  550    absolute_file_name(File, Path,
  551                       [ access(write)
  552                       ]),
  553    !,
  554    open(Path, write, Out,
  555         [ encoding(utf8),
  556           bom(true)
  557         ]),
  558    write_setting_header(Out),
  559    forall(current_setting(Name, Module, _, _, _, _),
  560           save_setting(Out, Module:Name)),
  561    close(Out).
  562
  563
  564write_setting_header(Out) :-
  565    get_time(Now),
  566    format_time(string(Date), '%+', Now),
  567    format(Out, '/*  Saved settings~n', []),
  568    format(Out, '    Date: ~w~n', [Date]),
  569    format(Out, '*/~n~n', []).
  570
  571save_setting(Out, Module:Name) :-
  572    curr_setting(Name, Module, Type, Default, Comment, _Src),
  573    (   st_value(Name, Module, Value),
  574        \+ ( eval_default(Default, Module, Type, DefValue),
  575             debug(setting, '~w <-> ~w~n', [DefValue, Value]),
  576             DefValue =@= Value
  577           )
  578    ->  format(Out, '~n%\t~w~n', [Comment]),
  579        format(Out, 'setting(~q:~q, ~q).~n', [Module, Name, Value])
  580    ;   true
  581    ).
 current_setting(?Setting) is nondet
True if Setting is a currently defined setting
  587current_setting(Setting) :-
  588    ground(Setting),
  589    !,
  590    strip_module(Setting, Module, Name),
  591    current_setting(Name, Module, _, _, _, _).
  592current_setting(Module:Name) :-
  593    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.
  609setting_property(Setting, Property) :-
  610    ground(Setting),
  611    !,
  612    Setting = Module:Name,
  613    curr_setting(Name, Module, Type, Default, Comment, Src),
  614    !,
  615    setting_property(Property, Module, Type, Default, Comment, Src).
  616setting_property(Setting, Property) :-
  617    Setting = Module:Name,
  618    curr_setting(Name, Module, Type, Default, Comment, Src),
  619    setting_property(Property, Module, Type, Default, Comment, Src).
  620
  621setting_property(type(Type), _, Type, _, _, _).
  622setting_property(default(Default), M, Type, Default0, _, _) :-
  623    eval_default(Default0, M, Type, Default).
  624setting_property(comment(Comment), _, _, _, Comment, _).
  625setting_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
  635list_settings :-
  636    list_settings(_).
  637
  638list_settings(Spec) :-
  639    spec_term(Spec, Term),
  640    TS1 = 25,
  641    TS2 = 40,
  642    format('~`=t~72|~n'),
  643    format('~w~t~*| ~w~w~t~*| ~w~n',
  644           ['Name', TS1, 'Value (*=modified)', '', TS2, 'Comment']),
  645    format('~`=t~72|~n'),
  646    forall(current_setting(Term),
  647           list_setting(Term, TS1, TS2)).
  648
  649spec_term(M:S, M:S) :- !.
  650spec_term(M, M:_).
  651
  652
  653list_setting(Module:Name, TS1, TS2) :-
  654    curr_setting(Name, Module, Type, Default0, Comment, _Src),
  655    eval_default(Default0, Module, Type, Default),
  656    setting(Module:Name, Value),
  657    (   Value \== Default
  658    ->  Modified = (*)
  659    ;   Modified = ''
  660    ),
  661    format('~w~t~*| ~q~w~t~*| ~w~n', [Module:Name, TS1, Value, Modified, TS2, Comment]).
  662
  663
  664                 /*******************************
  665                 *            TYPES             *
  666                 *******************************/
 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)
  676:- multifile
  677    convert_text/3.                 % +Type, +Text, -Value
  678
  679convert_setting_text(Type, Text, Value) :-
  680    convert_text(Type, Text, Value),
  681    !.
  682convert_setting_text(atom, Value, Value) :-
  683    !,
  684    must_be(atom, Value).
  685convert_setting_text(boolean, Value, Value) :-
  686    !,
  687    must_be(boolean, Value).
  688convert_setting_text(integer, Atom, Number) :-
  689    !,
  690    term_to_atom(Term, Atom),
  691    Number is round(Term).
  692convert_setting_text(float, Atom, Number) :-
  693    !,
  694    term_to_atom(Term, Atom),
  695    Number is float(Term).
  696convert_setting_text(between(L,U), Atom, Number) :-
  697    !,
  698    (   integer(L)
  699    ->  convert_setting_text(integer, Atom, Number)
  700    ;   convert_setting_text(float, Atom, Number)
  701    ),
  702    must_be(between(L,U), Number).
  703convert_setting_text(Type, Atom, Term) :-
  704    term_to_atom(Term, Atom),
  705    must_be(Type, Term).
  706
  707
  708                 /*******************************
  709                 *            SANDBOX           *
  710                 *******************************/
  711
  712:- multifile
  713    sandbox:safe_meta_predicate/1.  714
  715sandbox:safe_meta_predicate(settings:setting/2)