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-2020, University of Amsterdam 7 VU University Amsterdam 8 CWI, Amsterdam 9 All rights reserved. 10 11 Redistribution and use in source and binary forms, with or without 12 modification, are permitted provided that the following conditions 13 are met: 14 15 1. Redistributions of source code must retain the above copyright 16 notice, this list of conditions and the following disclaimer. 17 18 2. Redistributions in binary form must reproduce the above copyright 19 notice, this list of conditions and the following disclaimer in 20 the documentation and/or other materials provided with the 21 distribution. 22 23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 29 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 31 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 32 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 33 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 POSSIBILITY OF SUCH DAMAGE. 35*/ 36 37:- module(settings, 38 [ setting/4, % :Name, +Type, +Default, +Comment 39 setting/2, % :Name, ?Value 40 set_setting/2, % :Name, +Value 41 set_setting_default/2, % :Name, +Value 42 restore_setting/1, % :Name 43 load_settings/1, % +File 44 load_settings/2, % +File, +Options 45 save_settings/0, 46 save_settings/1, % +File 47 current_setting/1, % Module:Name 48 setting_property/2, % ?Setting, ?Property 49 list_settings/0, 50 list_settings/1, % +Module 51 52 convert_setting_text/3 % +Type, +Text, -Value 53 ]). 54:- use_module(library(arithmetic), 55 [ arithmetic_function/1, 56 arithmetic_expression_value/2 57 ]). 58 59:- autoload(library(broadcast),[broadcast/1]). 60:- use_module(library(debug),[debug/3]). 61:- autoload(library(error),[must_be/2,existence_error/2,type_error/2]). 62:- autoload(library(option),[option/3]). 63 64:- set_prolog_flag(generate_debug_info, false).
94:- dynamic 95 st_value/3, % Name, Module, Value 96 st_default/3, % Name, Module, Value 97 local_file/1, % Path 98 st_modified/0. 99 100:- multifile 101 current_setting/6. % Name, Module, Type, Default, Comment, Source 102 103:- meta_predicate 104 setting( , , , ), 105 setting( , ), 106 set_setting( , ), 107 set_setting_default( , ), 108 current_setting( ), 109 restore_setting( ). 110 111:- predicate_options(load_settings/2, 2, [undefined(oneof([load,error]))]). 112 113curr_setting(Name, Module, Type, Default, Comment, Src) :- 114 current_setting(Name, Module, Type, Default0, Comment, Src), 115 ( st_default(Name, Module, Default1) 116 -> Default = Default1 117 ; Default = Default0 118 ).
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.
138setting(Name, Type, Default, Comment) :- 139 throw(error(context_error(nodirective, 140 setting(Name, Type, Default, Comment)), 141 _)). 142 143:- multifile 144 system:term_expansion/2. 145 146systemterm_expansion((:- setting(QName, Type, Default, Comment)), 147 Expanded) :- 148 \+ current_prolog_flag(xref, true), 149 prolog_load_context(module, M0), 150 strip_module(M0:QName, Module, Name), 151 must_be(atom, Name), 152 to_atom(Comment, CommentAtom), 153 eval_default(Default, Module, Type, Value), 154 check_type(Type, Value), 155 source_location(File, Line), 156 ( current_setting(Name, Module, OType, ODef, _, OldLoc), 157 ( OType \=@= Type 158 ; ODef \=@= Default 159 ), 160 OldLoc \= (File:_) 161 -> format(string(Message), 162 'Already defined at: ~w', [OldLoc]), 163 throw(error(permission_error(redefine, setting, Module:Name), 164 context(Message, _))) 165 ; Expanded = settings:current_setting(Name, Module, Type, Default, 166 CommentAtom, File:Line) 167 ). 168 169to_atom(Atom, Atom) :- 170 atom(Atom), 171 !. 172to_atom(String, Atom) :- 173 format(atom(Atom), '~s', String).
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.
185setting(Module:Name, Value) :- 186 ( nonvar(Name), nonvar(Module) 187 -> ( st_value(Name, Module, Value0) 188 -> Value = Value0 189 ; curr_setting(Name, Module, Type, Default, _, _) 190 -> eval_default(Default, Module, Type, Value) 191 ; existence_error(setting, Module:Name) 192 ) 193 ; current_setting(Name, Module, _, _, _, _), 194 setting(Module:Name, Value) 195 ). 196 197 198:- dynamic 199 setting_cache/3. 200:- volatile 201 setting_cache/3.
207clear_setting_cache :-
208 retractall(setting_cache(_,_,_)).
env(Name)
, but uses the value Default if the variable
is not defined.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)
.234:- multifile 235 eval_default/3. % +Default, +Type, -Value 236 237eval_default(Default, _, _Type, Value) :- 238 var(Default), 239 !, 240 Value = Default. 241eval_default(Default, _, Type, Value) :- 242 eval_default(Default, Type, Val), 243 !, 244 Value = Val. 245eval_default(Default, _, _, Value) :- 246 atomic(Default), 247 !, 248 Value = Default. 249eval_default(Default, _, Type, Value) :- 250 setting_cache(Default, Type, Val), 251 !, 252 Value = Val. 253eval_default(env(Name), _, Type, Value) :- 254 !, 255 ( getenv(Name, TextValue) 256 -> convert_setting_text(Type, TextValue, Val), 257 assert(setting_cache(env(Name), Type, Val)), 258 Value = Val 259 ; existence_error(environment_variable, Name) 260 ). 261eval_default(env(Name, Default), _, Type, Value) :- 262 !, 263 ( getenv(Name, TextValue) 264 -> convert_setting_text(Type, TextValue, Val) 265 ; Val = Default 266 ), 267 assert(setting_cache(env(Name), Type, Val)), 268 Value = Val. 269eval_default(setting(Name), Module, Type, Value) :- 270 !, 271 strip_module(Module:Name, M, N), 272 setting(M:N, Value), 273 must_be(Type, Value). 274eval_default(Expr, _, Type, Value) :- 275 numeric_type(Type, Basic), 276 !, 277 arithmetic_expression_value(Expr, Val0), 278 ( Basic == float 279 -> Val is float(Val0) 280 ; Basic = integer 281 -> Val is round(Val0) 282 ; Val = Val0 283 ), 284 assert(setting_cache(Expr, Type, Val)), 285 Value = Val. 286eval_default(A+B, Module, atom, Value) :- 287 !, 288 phrase(expr_to_list(A+B, Module), L), 289 atomic_list_concat(L, Val), 290 assert(setting_cache(A+B, atom, Val)), 291 Value = Val. 292eval_default(List, Module, list(Type), Value) :- 293 !, 294 eval_list_default(List, Module, Type, Val), 295 assert(setting_cache(List, list(Type), Val)), 296 Value = Val. 297eval_default(Default, _, _, Default).
304eval_list_default([], _, _, []). 305eval_list_default([H0|T0], Module, Type, [H|T]) :- 306 eval_default(H0, Module, Type, H), 307 eval_list_default(T0, Module, Type, T).
314expr_to_list(A+B, Module) --> 315 !, 316 expr_to_list(A, Module), 317 expr_to_list(B, Module). 318expr_to_list(env(Name), _) --> 319 !, 320 ( { getenv(Name, Text) } 321 -> [Text] 322 ; { existence_error(environment_variable, Name) } 323 ). 324expr_to_list(env(Name, Default), _) --> 325 !, 326 ( { getenv(Name, Text) } 327 -> [Text] 328 ; [Default] 329 ). 330expr_to_list(setting(Name), Module) --> 331 !, 332 { strip_module(Module:Name, M, N), 333 setting(M:N, Value) 334 }, 335 [ Value ]. 336expr_to_list(A, _) --> 337 [A].
345:- arithmetic_function(env/1). 346:- arithmetic_function(env/2). 347 348env(Name, Value) :- 349 ( getenv(Name, Text) 350 -> convert_setting_text(number, Text, Value) 351 ; existence_error(environment_variable, Name) 352 ). 353env(Name, Default, Value) :- 354 ( getenv(Name, Text) 355 -> convert_setting_text(number, Text, Value) 356 ; Value = Default 357 ).
integer
, float
or
number
.366numeric_type(integer, integer). 367numeric_type(nonneg, integer). 368numeric_type(float, float). 369numeric_type(between(L,_), Type) :- 370 ( integer(L) -> Type = integer ; Type = float ).
settings(changed(Module:Name, Old, New))
Note that modified settings are not automatically persistent. The application should call save_settings/0 to persist the changes.
387set_setting(QName, Value) :- 388 strip_module(QName, Module, Name), 389 must_be(atom, Name), 390 ( curr_setting(Name, Module, Type, Default0, _Comment, _Src), 391 eval_default(Default0, Module, Type, Default) 392 -> setting(Module:Name, Old), 393 ( Value == Default 394 -> retract_setting(Module:Name) 395 ; st_value(Name, Module, Value) 396 -> true 397 ; check_type(Type, Value) 398 -> retract_setting(Module:Name), 399 assert_setting(Module:Name, Value) 400 ), 401 ( Old == Value 402 -> true 403 ; broadcast(settings(changed(Module:Name, Old, Value))), 404 clear_setting_cache % might influence dependent settings 405 ) 406 ; existence_error(setting, Name) 407 ). 408 409retract_setting(Module:Name) :- 410 set_settings_modified, 411 retractall(st_value(Name, Module, _)). 412 413assert_setting(Module:Name, Value) :- 414 set_settings_modified, 415 assert(st_value(Name, Module, Value)). 416 417set_settings_modified :- 418 st_modified, !. 419set_settings_modified :- 420 assertz(st_modified).
429restore_setting(QName) :-
430 strip_module(QName, Module, Name),
431 must_be(atom, Name),
432 ( st_value(Name, Module, Old)
433 -> retract_setting(Module:Name),
434 setting(Module:Name, Value),
435 ( Old \== Value
436 -> broadcast(settings(changed(Module:Name, Old, Value)))
437 ; true
438 )
439 ; true
440 ).
449set_setting_default(QName, Default) :- 450 strip_module(QName, Module, Name), 451 must_be(atom, Name), 452 ( current_setting(Name, Module, Type, Default0, _Comment, _Src) 453 -> retractall(settings:st_default(Name, Module, _)), 454 retract_setting(Module:Name), 455 ( Default == Default0 456 -> true 457 ; assert(settings:st_default(Name, Module, Default)) 458 ), 459 eval_default(Default, Module, Type, Value), 460 set_setting(Module:Name, Value) 461 ; existence_error(setting, Module:Name) 462 ). 463 464 465 /******************************* 466 * TYPES * 467 *******************************/
474check_type(Type, Term) :- 475 must_be(Type, Term). 476 477 478 /******************************* 479 * FILE * 480 *******************************/
error
, an error is printed and the setting is ignored.
when load
, the setting is loaded anyway, waiting for a
definition.
If possibly changed settings need to be persistent, the application
must call save_settings/0 as part of its shutdown. In simple cases
calling at_halt(save_settings)
is sufficient.
498load_settings(File) :- 499 load_settings(File, []). 500 501load_settings(File, Options) :- 502 absolute_file_name(File, Path, 503 [ access(read), 504 file_errors(fail) 505 ]), 506 !, 507 assert(local_file(Path)), 508 open(Path, read, In, [encoding(utf8)]), 509 read_setting(In, T0), 510 call_cleanup(load_settings(T0, In, Options), close(In)), 511 clear_setting_cache. 512load_settings(File, _) :- 513 absolute_file_name(File, Path, 514 [ access(write), 515 file_errors(fail) 516 ]), 517 !, 518 assert(local_file(Path)). 519load_settings(_, _). 520 521load_settings(end_of_file, _, _) :- !. 522load_settings(Setting, In, Options) :- 523 catch(store_setting(Setting, Options), E, 524 print_message(warning, E)), 525 read_setting(In, Next), 526 load_settings(Next, In, Options). 527 528read_setting(In, Term) :- 529 read_term(In, Term, 530 [ syntax_errors(dec10) 531 ]).
537store_setting(setting(Module:Name, Value), _) :- 538 curr_setting(Name, Module, Type, Default0, _Commentm, _Src), 539 !, 540 eval_default(Default0, Module, Type, Default), 541 ( Value == Default 542 -> true 543 ; check_type(Type, Value) 544 -> retractall(st_value(Name, Module, _)), 545 assert(st_value(Name, Module, Value)), 546 broadcast(settings(changed(Module:Name, Default, Value))) 547 ). 548store_setting(setting(Module:Name, Value), Options) :- 549 !, 550 ( option(undefined(load), Options, load) 551 -> retractall(st_value(Name, Module, _)), 552 assert(st_value(Name, Module, Value)) 553 ; existence_error(setting, Module:Name) 554 ). 555store_setting(Term, _) :- 556 type_error(setting, Term).
568save_settings :- 569 st_modified, 570 !, 571 ( local_file(File) 572 -> save_settings(File) 573 ; throw(error(context_error(settings, no_default_file), _)) 574 ). 575save_settings. 576 577save_settings(File) :- 578 absolute_file_name(File, Path, 579 [ access(write) 580 ]), 581 setup_call_cleanup( 582 open(Path, write, Out, 583 [ encoding(utf8), 584 bom(true) 585 ]), 586 ( write_setting_header(Out), 587 forall(current_setting(Name, Module, _, _, _, _), 588 save_setting(Out, Module:Name)) 589 ), 590 close(Out)). 591 592write_setting_header(Out) :- 593 get_time(Now), 594 format_time(string(Date), '%+', Now), 595 format(Out, '/* Saved settings~n', []), 596 format(Out, ' Date: ~w~n', [Date]), 597 format(Out, '*/~n~n', []). 598 599save_setting(Out, Module:Name) :- 600 curr_setting(Name, Module, Type, Default, Comment, _Src), 601 ( st_value(Name, Module, Value), 602 \+ ( eval_default(Default, Module, Type, DefValue), 603 debug(setting, '~w <-> ~w~n', [DefValue, Value]), 604 DefValue =@= Value 605 ) 606 -> format(Out, '~n%\t~w~n', [Comment]), 607 format(Out, 'setting(~q:~q, ~q).~n', [Module, Name, Value]) 608 ; true 609 ).
615current_setting(Setting) :- 616 ground(Setting), 617 !, 618 strip_module(Setting, Module, Name), 619 current_setting(Name, Module, _, _, _, _). 620current_setting(Module:Name) :- 621 current_setting(Name, Module, _, _, _, _).
637setting_property(Setting, Property) :- 638 ground(Setting), 639 !, 640 Setting = Module:Name, 641 curr_setting(Name, Module, Type, Default, Comment, Src), 642 !, 643 setting_property(Property, Module, Type, Default, Comment, Src). 644setting_property(Setting, Property) :- 645 Setting = Module:Name, 646 curr_setting(Name, Module, Type, Default, Comment, Src), 647 setting_property(Property, Module, Type, Default, Comment, Src). 648 649setting_property(type(Type), _, Type, _, _, _). 650setting_property(default(Default), M, Type, Default0, _, _) :- 651 eval_default(Default0, M, Type, Default). 652setting_property(comment(Comment), _, _, _, Comment, _). 653setting_property(source(Src), _, _, _, _, Src).
current_output
. The second form only lists
settings on the matching module.
663list_settings :- 664 list_settings(_). 665 666list_settings(Spec) :- 667 spec_term(Spec, Term), 668 TS1 = 25, 669 TS2 = 40, 670 format('~`=t~72|~n'), 671 format('~w~t~*| ~w~w~t~*| ~w~n', 672 ['Name', TS1, 'Value (*=modified)', '', TS2, 'Comment']), 673 format('~`=t~72|~n'), 674 forall(current_setting(Term), 675 list_setting(Term, TS1, TS2)). 676 677spec_term(M:S, M:S) :- !. 678spec_term(M, M:_). 679 680 681list_setting(Module:Name, TS1, TS2) :- 682 curr_setting(Name, Module, Type, Default0, Comment, _Src), 683 eval_default(Default0, Module, Type, Default), 684 setting(Module:Name, Value), 685 ( Value \== Default 686 -> Modified = (*) 687 ; Modified = '' 688 ), 689 format('~w~t~*| ~q~w~t~*| ~w~n', 690 [Module:Name, TS1, Value, Modified, TS2, Comment]). 691 692 693 /******************************* 694 * TYPES * 695 *******************************/
705:- multifile 706 convert_text/3. % +Type, +Text, -Value 707 708convert_setting_text(Type, Text, Value) :- 709 convert_text(Type, Text, Value), 710 !. 711convert_setting_text(atom, Value, Value) :- 712 !, 713 must_be(atom, Value). 714convert_setting_text(boolean, Value, Value) :- 715 !, 716 must_be(boolean, Value). 717convert_setting_text(integer, Atom, Number) :- 718 !, 719 term_to_atom(Term, Atom), 720 Number is round(Term). 721convert_setting_text(float, Atom, Number) :- 722 !, 723 term_to_atom(Term, Atom), 724 Number is float(Term). 725convert_setting_text(between(L,U), Atom, Number) :- 726 !, 727 ( integer(L) 728 -> convert_setting_text(integer, Atom, Number) 729 ; convert_setting_text(float, Atom, Number) 730 ), 731 must_be(between(L,U), Number). 732convert_setting_text(Type, Atom, Term) :- 733 term_to_atom(Term, Atom), 734 must_be(Type, Term). 735 736 737 /******************************* 738 * SANDBOX * 739 *******************************/ 740 741:- multifile 742 sandbox:safe_meta_predicate/1. 743 744sandbox:safe_meta_predicate(settings:setting/2). 745 746 747 /******************************* 748 * MESSAGES * 749 *******************************/ 750 751:- multifile 752 prolog:error_message//1. 753 754prologerror_message(context_error(settings, no_default_file)) --> 755 [ 'save_settings/0: no default file' ]
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:
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.
library(config)
distributed with XPCE provides an alternative aimed at graphical applications.