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