35
36:- module(prolog_help,
37 [ help/0,
38 help/1, 39 apropos/1 40 ]). 41:- use_module(library(pldoc), []). 42:- autoload(library(apply), [maplist/3]). 43:- autoload(library(error), [must_be/2]). 44:- autoload(library(isub), [isub/4]). 45:- autoload(library(lists), [append/3, sum_list/2]). 46:- autoload(library(pairs), [pairs_values/2]). 47:- autoload(library(porter_stem), [tokenize_atom/2]). 48:- autoload(library(process), [process_create/3]). 49:- autoload(library(sgml), [load_html/3]). 50:- autoload(library(solution_sequences), [distinct/1]). 51:- autoload(library(http/html_write), [html/3, print_html/1]). 52:- autoload(library(lynx/html_text), [html_text/2]). 53:- autoload(pldoc(doc_man), [man_page/4]). 54:- autoload(pldoc(doc_modes), [mode/2]). 55:- autoload(pldoc(doc_words), [doc_related_word/3]). 56:- autoload(pldoc(man_index), [man_object_property/2, doc_object_identifier/2]). 57:- autoload(library(prolog_code), [pi_head/2]). 58:- autoload(library(prolog_xref), [xref_source/2]). 59
60:- use_module(library(lynx/pldoc_style), []). 61
86
87:- meta_predicate
88 with_pager(0). 89
90:- multifile
91 show_html_hook/1. 92
95:- create_prolog_flag(help_pager, default,
96 [ type(term),
97 keep(true)
98 ]). 99
133
134help :-
135 notrace(show_matches([help/1, apropos/1], exact-help)).
136
137help(What) :-
138 notrace(help_no_trace(What)).
139
140help_no_trace(What) :-
141 help_objects_how(What, Matches, How),
142 !,
143 show_matches(Matches, How-What).
144help_no_trace(What) :-
145 print_message(warning, help(not_found(What))).
146
147show_matches(Matches, HowWhat) :-
148 help_html(Matches, HowWhat, HTML),
149 !,
150 show_html(HTML).
151
157
158show_html(HTML) :-
159 show_html_hook(HTML),
160 !.
161show_html(HTML) :-
162 setup_call_cleanup(
163 open_string(HTML, In),
164 load_html(stream(In), DOM, []),
165 close(In)),
166 page_width(PageWidth),
167 LineWidth is PageWidth - 4,
168 with_pager(html_text(DOM, [width(LineWidth)])).
169
170help_html(Matches, How, HTML) :-
171 phrase(html(html([ head([]),
172 body([ \match_type(How),
173 dl(\man_pages(Matches,
174 [ no_manual(fail),
175 links(false),
176 link_source(false),
177 navtree(false),
178 server(false)
179 ]))
180 ])
181 ])),
182 Tokens),
183 !,
184 with_output_to(string(HTML),
185 print_html(Tokens)).
186
187match_type(exact-_) -->
188 [].
189match_type(dwim-For) -->
190 html(p(class(warning),
191 [ 'WARNING: No matches for "', span(class('help-query'), For),
192 '" Showing closely related results'
193 ])).
194
195man_pages([], _) -->
196 [].
197man_pages([H|T], Options) -->
198 man_page(H, Options),
199 man_pages(T, Options).
200
201page_width(Width) :-
202 tty_width(W),
203 Width is min(100,max(50,W)).
204
209
210tty_width(W) :-
211 \+ running_under_emacs,
212 catch(tty_size(_, W), _, fail),
213 !.
214tty_width(80).
215
216help_objects_how(Spec, Objects, exact) :-
217 help_objects(Spec, exact, Objects),
218 !.
219help_objects_how(Spec, Objects, dwim) :-
220 help_objects(Spec, dwim, Objects),
221 !.
222
223help_objects(Spec, How, Objects) :-
224 findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0),
225 Objects0 \== [],
226 sort(1, @>, Objects0, Objects1),
227 pairs_values(Objects1, Objects2),
228 sort(Objects2, Objects).
229
230help_object(Fuzzy/Arity, How, Name/Arity, ID) :-
231 match_name(How, Fuzzy, Name),
232 man_object_property(Name/Arity, id(ID)).
233help_object(Fuzzy//Arity, How, Name//Arity, ID) :-
234 match_name(How, Fuzzy, Name),
235 man_object_property(Name//Arity, id(ID)).
236help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :-
237 match_name(How, Fuzzy, Name),
238 man_object_property(f(Name/Arity), id(ID)).
239help_object(Fuzzy, How, Name/Arity, ID) :-
240 atom(Fuzzy),
241 match_name(How, Fuzzy, Name),
242 man_object_property(Name/Arity, id(ID)).
243help_object(Fuzzy, How, Name//Arity, ID) :-
244 atom(Fuzzy),
245 match_name(How, Fuzzy, Name),
246 man_object_property(Name//Arity, id(ID)).
247help_object(Fuzzy, How, f(Name/Arity), ID) :-
248 atom(Fuzzy),
249 match_name(How, Fuzzy, Name),
250 man_object_property(f(Name/Arity), id(ID)).
251help_object(Fuzzy, How, c(Name), ID) :-
252 atom(Fuzzy),
253 match_name(How, Fuzzy, Name),
254 man_object_property(c(Name), id(ID)).
255help_object(SecID, _How, section(Label), ID) :-
256 atom(SecID),
257 ( atom_concat('sec:', SecID, Label)
258 ; sub_atom(SecID, _, _, 0, '.html'),
259 Label = SecID
260 ),
261 man_object_property(section(_Level,_Num,Label,_File), id(ID)).
262help_object(Func, How, c(Name), ID) :-
263 compound(Func),
264 compound_name_arity(Func, Fuzzy, 0),
265 match_name(How, Fuzzy, Name),
266 man_object_property(c(Name), id(ID)).
268help_object(Module, _How, Name/Arity, _ID) :-
269 atom(Module),
270 current_module(Module),
271 atom_concat('sec:', Module, SecLabel),
272 \+ man_object_property(section(_,_,SecLabel,_), _), 273 current_predicate_help(Module:Name/Arity).
274help_object(Name/Arity, _How, Name/Arity, _ID) :-
275 atom(Name),
276 current_predicate_help(_:Name/Arity).
277help_object(Fuzzy, How, Name/Arity, _ID) :-
278 atom(Fuzzy),
279 match_name(How, Fuzzy, Name),
280 current_predicate_help(_:Name/Arity).
281
288
289current_predicate_help(M:Name/Arity) :-
290 current_predicate(M:Name/Arity),
291 pi_head(Name/Arity,Head),
292 \+ predicate_property(M:Head, imported_from(_)),
293 \+ man_object_property(Name/Arity, _), 294 ( mode(M:_, _) 295 -> true
296 ; \+ module_property(M, class(system)),
297 predicate_property(M:Head,file(File)),
298 xref_source(File,[comments(store)])
299 ),
300 mode(M:Head, _). 301
302match_name(exact, Name, Name).
303match_name(dwim, Name, Fuzzy) :-
304 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
305
306
311
(Goal) :-
313 pager_ok(Pager, Options),
314 !,
315 Catch = error(io_error(_,_), _),
316 current_output(OldIn),
317 setup_call_cleanup(
318 process_create(Pager, Options,
319 [stdin(pipe(In))]),
320 ( set_stream(In, tty(true)),
321 set_output(In),
322 catch(Goal, Catch, true)
323 ),
324 ( set_output(OldIn),
325 close(In, [force(true)])
326 )).
327with_pager(Goal) :-
328 call(Goal).
329
(_Path, _Options) :-
331 current_prolog_flag(help_pager, false),
332 !,
333 fail.
334pager_ok(Path, Options) :-
335 current_prolog_flag(help_pager, default),
336 !,
337 stream_property(current_output, tty(true)),
338 \+ running_under_emacs,
339 ( distinct(( getenv('PAGER', Pager)
340 ; Pager = less
341 )),
342 absolute_file_name(path(Pager), Path,
343 [ access(execute),
344 file_errors(fail)
345 ])
346 -> pager_options(Path, Options)
347 ).
348pager_ok(Path, Options) :-
349 current_prolog_flag(help_pager, Term),
350 callable(Term),
351 compound_name_arguments(Term, Pager, Options),
352 absolute_file_name(path(Pager), Path,
353 [ access(execute),
354 file_errors(fail)
355 ]).
356
(Path, Options) :-
358 file_base_name(Path, File),
359 file_name_extension(Base, _, File),
360 downcase_atom(Base, Id),
361 pager_default_options(Id, Options).
362
(less, ['-r']).
364
365
370
371running_under_emacs :-
372 current_prolog_flag(emacs_inferior_process, true),
373 !.
374running_under_emacs :-
375 getenv('TERM', dumb),
376 !.
377running_under_emacs :-
378 current_prolog_flag(toplevel_prompt, P),
379 sub_atom(P, _, _, _, 'ediprolog'),
380 !.
381
382
404
405apropos(Query) :-
406 notrace(apropos_no_trace(Query)).
407
408apropos_no_trace(Query) :-
409 findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs),
410 ( Pairs == []
411 -> print_message(warning, help(no_apropos_match(Query)))
412 ; sort(1, >=, Pairs, Sorted),
413 length(Sorted, Len),
414 ( Len > 20
415 -> length(Truncated, 20),
416 append(Truncated, _, Sorted)
417 ; Truncated = Sorted
418 ),
419 pairs_values(Truncated, Matches),
420 print_message(information, help(apropos_matches(Matches, Len)))
421 ).
422
423apropos(Query, Obj, Summary, Q) :-
424 parse_query(Query, Type, Words),
425 man_object_property(Obj, summary(Summary)),
426 apropos_match(Type, Words, Obj, Summary, Q).
427
428parse_query(Type:String, Type, Words) :-
429 !,
430 must_be(atom, Type),
431 must_be(text, String),
432 tokenize_atom(String, Words).
433parse_query(String, _Type, Words) :-
434 must_be(text, String),
435 tokenize_atom(String, Words).
436
437apropos_match(Type, Query, Object, Summary, Q) :-
438 maplist(amatch(Object, Summary), Query, Scores),
439 match_object_type(Type, Object),
440 sum_list(Scores, Q).
441
442amatch(Object, Summary, Query, Score) :-
443 ( doc_object_identifier(Object, String)
444 ; String = Summary
445 ),
446 amatch(Query, String, Score),
447 !.
448
449amatch(Query, To, Quality) :-
450 doc_related_word(Query, Related, Distance),
451 sub_atom_icasechk(To, _, Related),
452 isub(Related, To, false, Quality0),
453 Quality is Quality0*Distance.
454
455match_object_type(Type, _Object) :-
456 var(Type),
457 !.
458match_object_type(Type, Object) :-
459 downcase_atom(Type, LType),
460 object_class(Object, Class),
461 match_object_class(LType, Class).
462
463match_object_class(Type, Class) :-
464 ( TheClass = Class
465 ; class_alias(Class, TheClass)
466 ),
467 sub_atom(TheClass, 0, _, _, Type),
468 !.
469
470class_alias(section, chapter).
471class_alias(function, arithmetic).
472class_alias(cfunction, c_function).
473class_alias(iso_predicate, predicate).
474class_alias(swi_builtin_predicate, predicate).
475class_alias(library_predicate, predicate).
476class_alias(dcg, predicate).
477class_alias(dcg, nonterminal).
478class_alias(dcg, non_terminal).
479
480class_tag(section, 'SEC').
481class_tag(function, ' F').
482class_tag(iso_predicate, 'ISO').
483class_tag(swi_builtin_predicate, 'SWI').
484class_tag(library_predicate, 'LIB').
485class_tag(dcg, 'DCG').
486
487object_class(section(_Level, _Num, _Label, _File), section).
488object_class(c(_Name), cfunction).
489object_class(f(_Name/_Arity), function).
490object_class(Name/Arity, Type) :-
491 functor(Term, Name, Arity),
492 ( current_predicate(system:Name/Arity),
493 predicate_property(system:Term, built_in)
494 -> ( predicate_property(system:Term, iso)
495 -> Type = iso_predicate
496 ; Type = swi_builtin_predicate
497 )
498 ; Type = library_predicate
499 ).
500object_class(_M:_Name/_Arity, library_predicate).
501object_class(_Name//_Arity, dcg).
502object_class(_M:_Name//_Arity, dcg).
503
504
505 508
509:- multifile prolog:message//1. 510
511prolog:message(help(not_found(What))) -->
512 [ 'No help for ~p.'-[What], nl,
513 'Use ?- apropos(query). to search for candidates.'-[]
514 ].
515prolog:message(help(no_apropos_match(Query))) -->
516 [ 'No matches for ~p'-[Query] ].
517prolog:message(help(apropos_matches(Pairs, Total))) -->
518 { tty_width(W),
519 Width is max(30,W),
520 length(Pairs, Count)
521 },
522 matches(Pairs, Width),
523 ( {Count =:= Total}
524 -> []
525 ; [ nl,
526 ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl,
527 'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl,
528 'to restrict your search. For example:'-[], nl, nl,
529 ' ?- apropos(iso:open).'-[], nl,
530 ' ?- apropos(\'open file\').'-[]
531 ]
532 ).
533
534matches([], _) --> [].
535matches([H|T], Width) -->
536 match(H, Width),
537 ( {T == []}
538 -> []
539 ; [nl],
540 matches(T, Width)
541 ).
542
543match(Obj-Summary, Width) -->
544 { Left is min(40, max(20, round(Width/3))),
545 Right is Width-Left-2,
546 man_object_summary(Obj, ObjS, Tag),
547 write_length(ObjS, LenObj, [portray(true), quoted(true)]),
548 Spaces0 is Left - LenObj - 4,
549 ( Spaces0 > 0
550 -> Spaces = Spaces0,
551 SummaryLen = Right
552 ; Spaces = 1,
553 SummaryLen is Right + Spaces0 - 1
554 ),
555 truncate(Summary, SummaryLen, SummaryE)
556 },
557 [ ansi([fg(default)], '~w ~p', [Tag, ObjS]),
558 '~|~*+~w'-[Spaces, SummaryE]
560 ].
561
562truncate(Summary, Width, SummaryE) :-
563 string_length(Summary, SL),
564 SL > Width,
565 !,
566 Pre is Width-4,
567 sub_string(Summary, 0, Pre, _, S1),
568 string_concat(S1, " ...", SummaryE).
569truncate(Summary, _, Summary).
570
571man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :-
572 atom_concat('sec:', Obj, Label),
573 !.
574man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !.
575man_object_summary(c(Name), Obj, ' C') :- !,
576 compound_name_arguments(Obj, Name, []).
577man_object_summary(f(Name/Arity), Name/Arity, ' F') :- !.
578man_object_summary(Obj, Obj, Tag) :-
579 ( object_class(Obj, Class),
580 class_tag(Class, Tag)
581 -> true
582 ; Tag = ' ?'
583 ).
584
585 588
589sandbox:safe_primitive(prolog_help:apropos(_)).
590sandbox:safe_primitive(prolog_help:help(_))