-
Notifications
You must be signed in to change notification settings - Fork 1
/
menu_db.pl
504 lines (393 loc) · 12.6 KB
/
menu_db.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/* <module> menu_db.pl
*
* based on a referential mx_choice_item/3
* (+MX:Integer, +Choice:Integer, +Name:String)
*
* to provide a simple selection with menu number + choice
* in two ways : numerical or hashtag.
*
* @version 1809.055
* @licence MIT
* @copyright Wiserman & Partners
* @author Thierry JAUNAY
* @arg creadate 2018/08/05
* @arg update 2018/10/01
* @arg comment menu_db.pl - Menu management
* @arg language SWI-Prolog
*
* ----------
* Thx to Paul Brown (@PaulBrownMagic) for his initial contribution
* from my spaghetti coding to his Prolog fluent coding.
*
* Thx to Anne OGBORN (@AnnieTheObscure) for her patience and'),
* great advices making me write a much better Prolog style code.
*
* ----------
* Use =
* - create the menu database (for example menu_db_x)
* - create do_it/2 for menu choices
* - use go/0 or ask_menu/1 or do_it/2 versions depending on needs
*
* Also added some useful "Internal Tools"
* ----------
*
* Copyright (c) 2018, Wiserman & Partners
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
% ----------------
% MODULES AND DATA
% ----------------
% menu_db module
:- module(menu_db,[
% cache management
retract_cached_mx/1,
% internal tools
mxw/3,
mxwc/1,
mxw_/1,
exist_mx/1,
cached_exist_mx/1,
% menu_db program
cached_make_menu/2,
do_it/2,
check_menu/2,
ask_menu/1,
% test
mx_test/1 ] ).
% toolbox module for predicates
:- use_module('toolbox', [
cls/0,
get_char_1/1,
if_empty_default/3,
known/3,
list_to_string/2,
print_matrix/1 ]
).
% load menu_db_msg
:- [menu_db_msg].
% menu_db settings
% ----------------
% Chosen design : [Menu_1] 0=Back,1=Option_1,2=Option_2,3=Option_3.
:- setting(mx_label_prefix, atom, '[', "menu label prefix").
:- setting(mx_label_suffix, atom, '] ', "menu label suffix").
:- setting(mx_subpart_suffix, atom, ',', "menu subpart suffix").
:- setting(mx_subpart_last_suffix, atom, '.', "menu subpart last suffix").
% Example of alternative template for menu UI
% <Menu_1>|0=Back|1=Option_1|2=Option_2|3=Option_3|
%
% :- setting(mx_label_prefix, atom, '<', "menu label prefix").
% :- setting(mx_label_suffix, atom, '>|', "menu label suffix").
% :- setting(mx_subpart_suffix, atom, '|', "menu subpart suffix").
% :- setting(mx_subpart_last_suffix, atom, '|', "menu subpart last suffix").
:- setting(mx_ext_char, char, '#', "extended menu prefix char").
:- setting(mx_exit_char, char, '.', "menu exit char").
:- load_settings('settings.db').
% ----------
% VOCABULARY
% ----------
% to gain time reading the code ...
% MX:integer = menu number
% MenuX:string = menu numbered MX
% MenuLabel:string = name of the menu
% Separator:char = char in between label and menu
% Str:string = temp variables used for strings
% Subparts:list = list of menu subparts
% UserChoice:char = menu choice selected by user
% C:integer = when specifically an integer choice
%
% Acronyms
% --------
% mxw stands for mx write
% -----------------
% RIGHTS MANAGEMENT
% -----------------
% etc. ... @tbd later
% --------
% MX TOOLS
% --------
% Misc internal tools for menu_db
% mxw/3
% (+MX:integer, +Choice:integer, +Item:string)
% Test tool to quickly display mx_choice_item/3
mxw(MX, Choice, Item) :-
findall([MX, Choice, Item], mx_choice_item(MX, Choice, Item), MXs),
mxw_(MXs).
% mxwc/1
% (+MX:integer)
% Write all in cache about menu number MX
mxwc(MX) :-
findall([X, MX, MenuX], known(X, MX, MenuX), Xs),
mxw_(Xs).
% mxw_/1
% ([A|Rest]:list)
% Print list or error message if empty
mxw_([H|T]) :-
print_matrix([H|T]).
mxw_([]) :-
nl, print_message(error, no_item),
!, fail.
% retract_cached_mx/1
% (+MX:integer)
% Purge cache from elements about menu number MX
retract_cached_mx(MX) :-
% retract cached menu string
retract(known(menux, MX, _)),
% retract cached list of standard choices
retract(known(mx_std_choices, MX, _)).
% exist_mx/1 is semidet
% (+MX:integer)
% Check if menu number is valid
% true if MX found once / false otherwise
% @tbd error messages
exist_mx(MX) :-
mx_choice_item(MX, _, _),
!.
cached_exist_mx(MX) :-
known(menux, MX, _),
!.
% ------
% LABELS
% ------
% make_menu_label/2
% (+MX:integer, -MenuLabel:atom)
make_menu_label(MX, MenuLabel) :-
% Make MenuLabel including suffix separator
% Replace menu label by default one if empty
mx_label(MX, MenuLabel1),
mx_label(-1, Default),
if_empty_default(MenuLabel1, Default, MenuLabel2),
% Grabs prefix and suffix from settings
setting(mx_label_prefix, Prefix),
setting(mx_label_suffix, Suffix),
% Make MenuLabel with Prefix and Suffix
format(atom(MenuLabel), "~w~w~w", [Prefix, MenuLabel2, Suffix]).
% ------------
% MAKING MENUS
% ------------
% PS: I could add horizontal / vertical display options
% but the idea is more to get a help style menu fitting on one line
% make_menu_list/2
% (+MX:integer, -Xs:list)
% Extract from mx_choice_item/3 the list XS of [X|Y] menu items
% where X is the choice and Y the menu item name
% Error if Xs is empty (no menu item)
make_menu_list(MX, Xs) :-
findall([X, Y], mx_choice_item(MX, X, Y), Xs),
make_menu_list_(Xs).
make_menu_list_([]) :-
nl, print_message(warning, no_item),
!, fail.
make_menu_list_(_).
% format_subparts/2
% (+XS:list, -Subparts:list)
% Put menu list into subparts ['0=Back ', '1=Option '] ready for
% joining ++ Thx to @PaulBrownMagic
% PS: added suffix separator from settings in spite of just space
format_subparts([], []) :- !.
% base case with empty lists
format_subparts([], _) :- !.
% no more subpart assembly to do
format_subparts([X|''], [SubpartHead|SubpartTrail]) :-
% concatenates menu choice and name from X to A
format(atom(A), "~w=~w", X),
% concat Str and last Suffix to do SubpartHead
setting(mx_subpart_last_suffix, Suffix),
atom_concat(A, Suffix, SubpartHead),
% recursion with the trail
format_subparts(_, SubpartTrail).
format_subparts([X|Xs], [SubpartHead|SubpartTrail]) :-
% concatenates menu choice and name from X to A
format(atom(A), "~w=~w", X),
% concat Str and Suffix to do SubpartHead
setting(mx_subpart_suffix, Suffix),
atom_concat(A, Suffix, SubpartHead),
% recursion with the trail
format_subparts(Xs, SubpartTrail).
% make_menu/2
% (+MX:integer, -MenuX:string)
% Check = fail if no number for the menu
% Make MenuX as a string = Label + Separator + Subparts
% Check = fail if no number for the menu
make_menu(MX, _) :-
\+ exist_mx(MX),
print_message(error, menu_not_found(MX)),
!, fail.
make_menu(MX, MenuX) :-
% extract subparts needed to build the menu string + check OK
make_menu_list(MX, Xs),
% extract menu label (replace by defaut if none)
make_menu_label(MX, MenuLabel),
% build the list of items ready for joining
format_subparts(Xs, Subparts),
% make the menu line MenuX
list_to_string([MenuLabel|Subparts], MenuX).
% cached_make_menu/2
% (+MX:integer, -MenuX:string)
% Caching optimization on MenuX string
% Check = false if no number for the menu
cached_make_menu(MX, MenuX) :-
known(menux, MX, MenuX),
!.
cached_make_menu(MX, MenuX) :-
make_menu(MX, MenuX),
assertz(known(menux, MX, MenuX)).
% -----
% DO_IT
% -----
% Program execution based on menu selection by user
% = to adapt depending on program needs
do_it(MX, _) :-
\+ exist_mx(MX),
print_message(error, menu_not_found(MX)),
!, fail.
do_it(MX, UserChoice) :-
nl, writeln('TBD - replace by real do_it/2'),
format("(Menu: ~w / Choice: ~w)~n~n", [MX, UserChoice]).
% ---------------------
% MANAGING MENU CHOICES
% ---------------------
% Check choices / errors before launching do_it
% make_std_choices/2
% (MX:integer, Choices:list)
% Make the list of valid Choices for menu number MX
make_std_choices(MX, _) :-
\+ exist_mx(MX),
print_message(error, menu_not_found(MX)),
!, fail.
make_std_choices(MX, Choices) :-
findall(Choice, mx_choice_item(MX, Choice, _), Choices).
% cached_std_choices/2
% (MX:integer, Choices:list)
% Caching optimization on Choices list
% Known or added to be known
cached_std_choices(MX, Choices) :-
known(mx_std_choices, MX, Choices),
!.
cached_std_choices(MX, Choices) :-
make_std_choices(MX, Choices),
assertz(known(mx_std_choices, MX, Choices)).
% is_std_choice/2
% (MX:integer, C:integer)
% Check if C is a valid menu standard choice
is_std_choice(MX, C) :-
% true if C appears once
cached_std_choices(MX, Choices),
member(C, Choices),
!.
% mx_choice_error/1
% (UserChoice:Char) can be either num or alpha
% Display the error message on num or alpha menu choice error
mx_choice_error(UserChoice) :-
% error message on bad num menu choice
number(UserChoice),
!,
print_message(error, bad_num_choice(UserChoice)),
print_message(warning, chose_again).
mx_choice_error(UserChoice) :-
% error message on bad alpha menu choice
print_message(error, bad_ext_choice(UserChoice)),
print_message(warning, chose_again).
% check_menu/2
% (MX:integer, UserChoice:char)
% Check choices exit / standard / extended / others
% Manage errors
check_menu(_, UserChoice) :-
% if mx_exit_char then exit
setting(mx_exit_char, UserChoice),
!.
check_menu(MX, UserChoice) :-
% if extended menu choice then stop searching and do it
setting(mx_ext_char, UserChoice),
!,
do_it(MX, UserChoice).
check_menu(MX, UserChoice) :-
% if num choice and valid then stop searching and do it
atom_number(UserChoice, C),
is_std_choice(MX, C),
!,
do_it(MX, C).
check_menu(MX, UserChoice) :-
% if num choice and not valid choice
% then error message, stop searching and fail to repeat
atom_number(UserChoice, C),
\+ is_std_choice(MX, C),
mx_choice_error(C),
!, fail.
check_menu(_, UserChoice) :-
% Latest check ending by a bad choice
% as neither exit or std choice or extended
% then error message, stop searching and fail to repeat
mx_choice_error(UserChoice),
!, fail.
% ---------
% GO / MENU
% ---------
% Launch program with go/0 or ask_menu/1
% write_menu/1
% (MX:integer)
% Display menu number MX, ask / control and launch choices
write_menu(MX) :-
cached_make_menu(MX, MenuX),
write(MenuX).
% ask_menu/1
% (MX:integer)
% Display menu number MX, ask / control and launch choices
ask_menu(MX) :-
% check MX validity
\+ exist_mx(MX),
print_message(error, menu_not_found(MX)),
!, fail.
ask_menu(MX) :-
% make, check and display menu
write_menu(MX),
% ask choice and repeat until valid choice
nl, print_message(information, what_choice),
repeat,
( get_char_1(UserChoice),
check_menu(MX, UserChoice),
! )
; !.
go :-
cls,
ask_menu(1).
% ---------------
% TEST CHECK-LIST
% ---------------
%% Typical choices to test with mx_test/2 and ask_menu/2,
% once menu_db_for_test loaded :
%
% MX = 1 / num choice 1 (all is fine)
% MX = 1 / num choice 5 (not existing choice)
% MX = 1 / ext choice = # (alpha extended choice)
% MX = 1 / ext choice = a (non existing alpha extended choice)
% MX = 3 (non existing menu = with label but no items)
%
mx_test(MX) :-
cls,
ask_menu(MX).
/* ********** END OF FILE ********** */