36
37:- module(settings,
38 [ setting/4, 39 setting/2, 40 set_setting/2, 41 set_setting_default/2, 42 restore_setting/1, 43 load_settings/1, 44 load_settings/2, 45 save_settings/0,
46 save_settings/1, 47 current_setting/1, 48 setting_property/2, 49 list_settings/0,
50 list_settings/1, 51
52 convert_setting_text/3 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). 65
93
94:- dynamic
95 st_value/3, 96 st_default/3, 97 local_file/1, 98 st_modified/0. 99
100:- multifile
101 current_setting/6. 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 ).
119
136
137
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
146system:term_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).
174
184
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. 202
206
207clear_setting_cache :-
208 retractall(setting_cache(_,_,_)).
209
233
234:- multifile
235 eval_default/3. 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).
298
299
303
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).
308
313
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].
338
344
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 ).
358
359
365
366numeric_type(integer, integer).
367numeric_type(nonneg, integer).
368numeric_type(float, float).
369numeric_type(between(L,_), Type) :-
370 ( integer(L) -> Type = integer ; Type = float ).
371
372
386
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 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).
421
422
428
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 ).
441
448
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 468
473
474check_type(Type, Term) :-
475 must_be(Type, Term).
476
477
478 481
497
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 ]).
532
536
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).
557
567
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
(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 ).
610
614
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, _, _, _, _).
622
636
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).
654
662
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 696
704
705:- multifile
706 convert_text/3. 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 740
741:- multifile
742 sandbox:safe_meta_predicate/1. 743
744sandbox:safe_meta_predicate(settings:setting/2).
745
746
747 750
751:- multifile
752 prolog:error_message//1. 753
754prolog:error_message(context_error(settings, no_default_file)) -->
755 [ 'save_settings/0: no default file' ]