35
36:- module(prolog_help,
37 [ help/0,
38 help/1, 39 apropos/1, 40 help_apropos/4,
41 help_text/2 42 ]). 43:- use_module(library(pldoc), []). 44:- use_module(library(isub), [isub/4]). 45
46:- autoload(library(apply), [maplist/3]). 47:- autoload(library(error), [must_be/2]). 48:- autoload(library(lists), [append/3, sum_list/2]). 49:- autoload(library(pairs), [pairs_values/2]). 50:- autoload(library(porter_stem), [tokenize_atom/2]). 51:- autoload(library(process), [process_create/3]). 52:- autoload(library(sgml), [load_html/3]). 53:- autoload(library(solution_sequences), [distinct/1]). 54:- autoload(library(http/html_write), [html/3, print_html/1]). 55:- autoload(library(lynx/html_text), [html_text/2]). 56:- autoload(pldoc(doc_man), [man_page/4]). 57:- autoload(pldoc(doc_modes), [(mode)/2]). 58:- autoload(pldoc(doc_words), [doc_related_word/3]). 59:- autoload(pldoc(man_index), [man_object_property/2, doc_object_identifier/2]). 60:- autoload(library(prolog_code), [pi_head/2]). 61:- autoload(library(prolog_xref), [xref_source/2]). 62
63:- use_module(library(lynx/pldoc_style), []). 64
89
90:- meta_predicate
91 with_pager(0). 92
93:- multifile
94 show_html_hook/1. 95
98:- create_prolog_flag(help_pager, default,
99 [ type(term),
100 keep(true)
101 ]). 102
147
148help :-
149 notrace(show_matches([help/1, apropos/1], exact-help)).
150
151help(What) :-
152 notrace(help_no_trace(What)).
153
154help_no_trace(What) :-
155 help_objects_how(What, Matches, How),
156 !,
157 show_matches(Matches, How-What).
158help_no_trace(What) :-
159 print_message(warning, help(not_found(What))).
160
161show_matches(Matches, HowWhat) :-
162 help_html(Matches, HowWhat, HTML),
163 !,
164 show_html(HTML).
165
171
172show_html(HTML) :-
173 show_html_hook(HTML),
174 !.
175show_html(HTML) :-
176 setup_call_cleanup(
177 open_string(HTML, In),
178 load_html(stream(In), DOM, []),
179 close(In)),
180 page_width(PageWidth),
181 LineWidth is PageWidth - 4,
182 with_pager(html_text(DOM, [width(LineWidth)])).
183
184help_html(Matches, How, HTML) :-
185 phrase(html(html([ head([]),
186 body([ \match_type(How),
187 dl(\man_pages(Matches,
188 [ no_manual(fail),
189 links(false),
190 link_source(false),
191 navtree(false),
192 server(false),
193 qualified(always)
194 ]))
195 ])
196 ])),
197 Tokens),
198 !,
199 with_output_to(string(HTML),
200 print_html(Tokens)).
201
202match_type(exact-_) -->
203 [].
204match_type(dwim-For) -->
205 html(p(class(warning),
206 [ 'WARNING: No matches for "', span(class('help-query'), For),
207 '" Showing closely related results'
208 ])).
209
210man_pages([], _) -->
211 [].
212man_pages([H|T], Options) -->
213 ( man_page(H, Options)
214 -> []
215 ; html(p(class(warning),
216 [ 'WARNING: No help for ~p'-[H]
217 ]))
218 ),
219 man_pages(T, Options).
220
221page_width(Width) :-
222 tty_width(W),
223 Width is min(100,max(50,W)).
224
229
230tty_width(W) :-
231 \+ running_under_emacs,
232 catch(tty_size(_, W), _, fail),
233 !.
234tty_width(80).
235
236help_objects_how(Spec, Objects, exact) :-
237 help_objects(Spec, exact, Objects),
238 !.
239help_objects_how(Spec, Objects, dwim) :-
240 help_objects(Spec, dwim, Objects),
241 !.
242
243help_objects(Spec, How, Objects) :-
244 findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0),
245 Objects0 \== [],
246 sort(1, @>, Objects0, Objects1),
247 pairs_values(Objects1, Objects2),
248 sort(Objects2, Objects).
249
250help_object(Fuzzy/Arity, How, Name/Arity, ID) :-
251 match_name(How, Fuzzy, Name),
252 man_object_property(Name/Arity, id(ID)).
253help_object(Fuzzy//Arity, How, Name//Arity, ID) :-
254 match_name(How, Fuzzy, Name),
255 man_object_property(Name//Arity, id(ID)).
256help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :-
257 match_name(How, Fuzzy, Name),
258 man_object_property(f(Name/Arity), id(ID)).
259help_object(Fuzzy, How, Name/Arity, ID) :-
260 atom(Fuzzy),
261 match_name(How, Fuzzy, Name),
262 man_object_property(Name/Arity, id(ID)).
263help_object(Fuzzy, How, Name//Arity, ID) :-
264 atom(Fuzzy),
265 match_name(How, Fuzzy, Name),
266 man_object_property(Name//Arity, id(ID)).
267help_object(Fuzzy, How, f(Name/Arity), ID) :-
268 atom(Fuzzy),
269 match_name(How, Fuzzy, Name),
270 man_object_property(f(Name/Arity), id(ID)).
271help_object(Fuzzy, How, c(Name), ID) :-
272 atom(Fuzzy),
273 match_name(How, Fuzzy, Name),
274 man_object_property(c(Name), id(ID)).
275help_object(SecID, _How, section(Label), ID) :-
276 atom(SecID),
277 ( atom_concat('sec:', SecID, Label)
278 ; sub_atom(SecID, _, _, 0, '.html'),
279 Label = SecID
280 ),
281 man_object_property(section(_Level,_Num,Label,_File), id(ID)).
282help_object(Func, How, c(Name), ID) :-
283 compound(Func),
284 compound_name_arity(Func, Fuzzy, 0),
285 match_name(How, Fuzzy, Name),
286 man_object_property(c(Name), id(ID)).
288help_object(Module, _How, Module:Name/Arity, _ID) :-
289 atom(Module),
290 current_module(Module),
291 atom_concat('sec:', Module, SecLabel),
292 \+ man_object_property(section(_,_,SecLabel,_), _), 293 current_predicate_help(Module:Name/Arity).
294help_object(Module:Name, _How, Module:Name/Arity, _ID) :-
295 atom(Name),
296 current_predicate_help(Module:Name/Arity).
297help_object(Module:Name/Arity, _How, Module:Name/Arity, _ID) :-
298 atom(Name),
299 current_predicate_help(Module:Name/Arity).
300help_object(Name/Arity, _How, Module:Name/Arity, _ID) :-
301 atom(Name),
302 current_predicate_help(Module:Name/Arity).
303help_object(Fuzzy, How, Module:Name/Arity, _ID) :-
304 atom(Fuzzy),
305 match_name(How, Fuzzy, Name),
306 current_predicate_help(Module:Name/Arity).
307
314
315current_predicate_help(M:Name/Arity) :-
316 current_predicate(M:Name/Arity),
317 pi_head(Name/Arity,Head),
318 \+ predicate_property(M:Head, imported_from(_)),
319 module_property(M, class(user)),
320 ( mode(M:_, _) 321 -> true
322 ; \+ module_property(M, class(system)),
323 main_source_file(M:Head, File),
324 xref_source(File,[comments(store)])
325 ),
326 mode(M:Head, _). 327
328match_name(exact, Name, Name).
329match_name(dwim, Name, Fuzzy) :-
330 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
331
335
336main_source_file(Pred, File) :-
337 predicate_property(Pred, file(File0)),
338 main_source(File0, File).
339
340main_source(File, Main) :-
341 source_file(File),
342 !,
343 Main = File.
344main_source(File, Main) :-
345 source_file_property(File, included_in(Parent, _Time)),
346 main_source(Parent, Main).
347
348
353
(Goal) :-
355 pager_ok(Pager, Options),
356 !,
357 Catch = error(io_error(_,_), _),
358 current_output(OldIn),
359 setup_call_cleanup(
360 process_create(Pager, Options,
361 [stdin(pipe(In))]),
362 ( set_stream(In, tty(true)),
363 set_output(In),
364 catch(Goal, Catch, true)
365 ),
366 ( set_output(OldIn),
367 close(In, [force(true)])
368 )).
369with_pager(Goal) :-
370 call(Goal).
371
(_Path, _Options) :-
373 current_prolog_flag(help_pager, false),
374 !,
375 fail.
376pager_ok(Path, Options) :-
377 current_prolog_flag(help_pager, default),
378 !,
379 stream_property(current_output, tty(true)),
380 \+ running_under_emacs,
381 ( distinct(( getenv('PAGER', Pager)
382 ; Pager = less
383 )),
384 absolute_file_name(path(Pager), Path,
385 [ access(execute),
386 file_errors(fail)
387 ])
388 -> pager_options(Path, Options)
389 ).
390pager_ok(Path, Options) :-
391 current_prolog_flag(help_pager, Term),
392 callable(Term),
393 compound_name_arguments(Term, Pager, Options),
394 absolute_file_name(path(Pager), Path,
395 [ access(execute),
396 file_errors(fail)
397 ]).
398
(Path, Options) :-
400 file_base_name(Path, File),
401 file_name_extension(Base, _, File),
402 downcase_atom(Base, Id),
403 pager_default_options(Id, Options).
404
(less, ['-r']).
406
407
412
413running_under_emacs :-
414 current_prolog_flag(emacs_inferior_process, true),
415 !.
416running_under_emacs :-
417 getenv('TERM', dumb),
418 !.
419running_under_emacs :-
420 current_prolog_flag(toplevel_prompt, P),
421 sub_atom(P, _, _, _, 'ediprolog'),
422 !.
423
424
446
447apropos(Query) :-
448 notrace(apropos_no_trace(Query)).
449
450apropos_no_trace(Query) :-
451 findall(Q-(Obj-Summary), help_apropos(Query, Obj, Summary, Q), Pairs),
452 ( Pairs == []
453 -> print_message(warning, help(no_apropos_match(Query)))
454 ; sort(1, >=, Pairs, Sorted),
455 length(Sorted, Len),
456 ( Len > 20
457 -> length(Truncated, 20),
458 append(Truncated, _, Sorted)
459 ; Truncated = Sorted
460 ),
461 pairs_values(Truncated, Matches),
462 print_message(information, help(apropos_matches(Matches, Len)))
463 ).
464
470
471help_apropos(Query, Obj, Summary, Q) :-
472 parse_query(Query, Type, Words),
473 man_object_property(Obj, summary(Summary)),
474 apropos_match(Type, Words, Obj, Summary, Q).
475
476parse_query(Type:String, Type, Words) :-
477 !,
478 must_be(atom, Type),
479 must_be(text, String),
480 tokenize_atom(String, Words).
481parse_query(String, _Type, Words) :-
482 must_be(text, String),
483 tokenize_atom(String, Words).
484
485apropos_match(Type, Query, Object, Summary, Q) :-
486 maplist(amatch(Object, Summary), Query, Scores),
487 match_object_type(Type, Object),
488 sum_list(Scores, Q).
489
490amatch(Object, Summary, Query, Score) :-
491 ( doc_object_identifier(Object, String)
492 ; String = Summary
493 ),
494 amatch(Query, String, Score),
495 !.
496
497amatch(Query, To, Quality) :-
498 doc_related_word(Query, Related, Distance),
499 sub_atom_icasechk(To, _, Related),
500 isub(Related, To, false, Quality0),
501 Quality is Quality0*Distance.
502
503match_object_type(Type, _Object) :-
504 var(Type),
505 !.
506match_object_type(Type, Object) :-
507 downcase_atom(Type, LType),
508 object_class(Object, Class),
509 match_object_class(LType, Class).
510
511match_object_class(Type, Class) :-
512 ( TheClass = Class
513 ; class_alias(Class, TheClass)
514 ),
515 sub_atom(TheClass, 0, _, _, Type),
516 !.
517
518class_alias(section, chapter).
519class_alias(function, arithmetic).
520class_alias(cfunction, c_function).
521class_alias(iso_predicate, predicate).
522class_alias(swi_builtin_predicate, predicate).
523class_alias(library_predicate, predicate).
524class_alias(dcg, predicate).
525class_alias(dcg, nonterminal).
526class_alias(dcg, non_terminal).
527
528class_tag(section, 'SEC').
529class_tag(function, ' F').
530class_tag(iso_predicate, 'ISO').
531class_tag(swi_builtin_predicate, 'SWI').
532class_tag(library_predicate, 'LIB').
533class_tag(dcg, 'DCG').
534
535object_class(section(_Level, _Num, _Label, _File), section).
536object_class(c(_Name), cfunction).
537object_class(f(_Name/_Arity), function).
538object_class(Name/Arity, Type) :-
539 functor(Term, Name, Arity),
540 ( current_predicate(system:Name/Arity),
541 predicate_property(system:Term, built_in)
542 -> ( predicate_property(system:Term, iso)
543 -> Type = iso_predicate
544 ; Type = swi_builtin_predicate
545 )
546 ; Type = library_predicate
547 ).
548object_class(_M:_Name/_Arity, library_predicate).
549object_class(_Name//_Arity, dcg).
550object_class(_M:_Name//_Arity, dcg).
551
557
558help_text(Pred, HelpText) :-
559 help_objects(Pred, exact, Matches), !,
560 catch(help_html(Matches, exact-exact, HtmlDoc), _, fail),
561 setup_call_cleanup(open_string(HtmlDoc, In),
562 load_html(stream(In), Dom, []),
563 close(In)),
564 with_output_to(string(HelpText), html_text(Dom, [])).
565
566 569
570:- multifile prolog:message//1. 571
572prolog:message(help(not_found(What))) -->
573 [ 'No help for ~p.'-[What], nl,
574 'Use ?- apropos(query). to search for candidates.'-[]
575 ].
576prolog:message(help(no_apropos_match(Query))) -->
577 [ 'No matches for ~p'-[Query] ].
578prolog:message(help(apropos_matches(Pairs, Total))) -->
579 { tty_width(W),
580 Width is max(30,W),
581 length(Pairs, Count)
582 },
583 matches(Pairs, Width),
584 ( {Count =:= Total}
585 -> []
586 ; [ nl,
587 ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl,
588 'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl,
589 'to restrict your search. For example:'-[], nl, nl,
590 ' ?- apropos(iso:open).'-[], nl,
591 ' ?- apropos(\'open file\').'-[]
592 ]
593 ).
594
595matches([], _) --> [].
596matches([H|T], Width) -->
597 match(H, Width),
598 ( {T == []}
599 -> []
600 ; [nl],
601 matches(T, Width)
602 ).
603
604match(Obj-Summary, Width) -->
605 { Left is min(40, max(20, round(Width/3))),
606 Right is Width-Left-2,
607 man_object_summary(Obj, ObjS, Tag),
608 write_size(ObjS, LenObj, _Height, [portray(true), quoted(true)]),
609 Spaces0 is Left - LenObj - 4,
610 ( Spaces0 > 0
611 -> Spaces = Spaces0,
612 SummaryLen = Right
613 ; Spaces = 1,
614 SummaryLen is Right + Spaces0 - 1
615 ),
616 truncate(Summary, SummaryLen, SummaryE)
617 },
618 [ ansi([fg(default)], '~w ~p', [Tag, ObjS]),
619 '~|~*+~w'-[Spaces, SummaryE]
621 ].
622
623truncate(Summary, Width, SummaryE) :-
624 string_length(Summary, SL),
625 SL > Width,
626 !,
627 Pre is Width-4,
628 sub_string(Summary, 0, Pre, _, S1),
629 string_concat(S1, " ...", SummaryE).
630truncate(Summary, _, Summary).
631
632man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :-
633 atom_concat('sec:', Obj, Label),
634 !.
635man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !.
636man_object_summary(c(Name), Obj, ' C') :- !,
637 compound_name_arguments(Obj, Name, []).
638man_object_summary(f(Name/Arity), Name/Arity, ' F') :- !.
639man_object_summary(Obj, Obj, Tag) :-
640 ( object_class(Obj, Class),
641 class_tag(Class, Tag)
642 -> true
643 ; Tag = ' ?'
644 ).
645
646 649
650sandbox:safe_primitive(prolog_help:apropos(_)).
651sandbox:safe_primitive(prolog_help:help(_))