1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: J.Wielemaker@vu.nl 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2018-2024, CWI Amsterdam 7 SWI-Prolog Solutions b.v. 8 All rights reserved. 9 10 Redistribution and use in source and binary forms, with or without 11 modification, are permitted provided that the following conditions 12 are met: 13 14 1. Redistributions of source code must retain the above copyright 15 notice, this list of conditions and the following disclaimer. 16 17 2. Redistributions in binary form must reproduce the above copyright 18 notice, this list of conditions and the following disclaimer in 19 the documentation and/or other materials provided with the 20 distribution. 21 22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 23 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 24 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 25 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 26 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 27 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 28 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 29 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 32 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 33 POSSIBILITY OF SUCH DAMAGE. 34*/ 35 36:- module(prolog_help, 37 [ help/0, 38 help/1, % +Object 39 apropos/1, % +Search 40 help_text/2 41 ]). 42:- use_module(library(pldoc), []). 43:- use_module(library(isub), [isub/4]). 44 45:- autoload(library(apply), [maplist/3]). 46:- autoload(library(error), [must_be/2]). 47:- autoload(library(lists), [append/3, sum_list/2]). 48:- autoload(library(pairs), [pairs_values/2]). 49:- autoload(library(porter_stem), [tokenize_atom/2]). 50:- autoload(library(process), [process_create/3]). 51:- autoload(library(sgml), [load_html/3]). 52:- autoload(library(solution_sequences), [distinct/1]). 53:- autoload(library(http/html_write), [html/3, print_html/1]). 54:- autoload(library(lynx/html_text), [html_text/2]). 55:- autoload(pldoc(doc_man), [man_page/4]). 56:- autoload(pldoc(doc_modes), [(mode)/2]). 57:- autoload(pldoc(doc_words), [doc_related_word/3]). 58:- autoload(pldoc(man_index), [man_object_property/2, doc_object_identifier/2]). 59:- autoload(library(prolog_code), [pi_head/2]). 60:- autoload(library(prolog_xref), [xref_source/2]). 61 62:- use_module(library(lynx/pldoc_style), []).
89:- meta_predicate 90 with_pager( ). 91 92:- multifile 93 show_html_hook/1. 94 95% one of `default`, `false`, an executable or executable(options), e.g. 96% less('-r'). 97:- create_prolog_flag(help_pager, default, 98 [ type(term), 99 keep(true) 100 ]).
topics(s)
to
give help for. Notations for What are:
?- help(append).
help/1 shows documentation from the manual as well as from loaded user code if the code is documented using PlDoc. To show only the documentatoion of the loaded predicate we may prefix predicate indicator with the module in which it is defined.
If an exact match fails this predicates attempts fuzzy matching and, when successful, display the results headed by a warning that the matches are based on fuzzy matching.
If possible, the results are sent through a pager such as the
less
program. This behaviour is controlled by the Prolog flag
help_pager
. See section level documentation.
147help :- 148 notrace(show_matches([help/1, apropos/1], exact-help)). 149 150help(What) :- 151 notrace(help_no_trace(What)). 152 153help_no_trace(What) :- 154 help_objects_how(What, Matches, How), 155 !, 156 show_matches(Matches, How-What). 157help_no_trace(What) :- 158 print_message(warning, help(not_found(What))). 159 160show_matches(Matches, HowWhat) :- 161 help_html(Matches, HowWhat, HTML), 162 !, 163 show_html(HTML).
171show_html(HTML) :- 172 show_html_hook(HTML), 173 !. 174show_html(HTML) :- 175 setup_call_cleanup( 176 open_string(HTML, In), 177 load_html(stream(In), DOM, []), 178 close(In)), 179 page_width(PageWidth), 180 LineWidth is PageWidth - 4, 181 with_pager(html_text(DOM, [width(LineWidth)])). 182 183help_html(Matches, How, HTML) :- 184 phrase(html(html([ head([]), 185 body([ \match_type(How), 186 dl(\man_pages(Matches, 187 [ no_manual(fail), 188 links(false), 189 link_source(false), 190 navtree(false), 191 server(false), 192 qualified(always) 193 ])) 194 ]) 195 ])), 196 Tokens), 197 !, 198 with_output_to(string(HTML), 199 print_html(Tokens)). 200 201match_type(exact-_) --> 202 []. 203match_type(dwim-For) --> 204 html(p(class(warning), 205 [ 'WARNING: No matches for "', span(class('help-query'), For), 206 '" Showing closely related results' 207 ])). 208 209man_pages([], _) --> 210 []. 211man_pages([H|T], Options) --> 212 ( man_page(H, Options) 213 -> [] 214 ; html(p(class(warning), 215 [ 'WARNING: No help for ~p'-[H] 216 ])) 217 ), 218 man_pages(T, Options). 219 220page_width(Width) :- 221 tty_width(W), 222 Width is min(100,max(50,W)).
229tty_width(W) :- 230 \+ running_under_emacs, 231 catch(tty_size(_, W), _, fail), 232 !. 233tty_width(80). 234 235help_objects_how(Spec, Objects, exact) :- 236 help_objects(Spec, exact, Objects), 237 !. 238help_objects_how(Spec, Objects, dwim) :- 239 help_objects(Spec, dwim, Objects), 240 !. 241 242help_objects(Spec, How, Objects) :- 243 findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0), 244 Objects0 \== [], 245 sort(1, @>, Objects0, Objects1), 246 pairs_values(Objects1, Objects2), 247 sort(Objects2, Objects). 248 249help_object(Fuzzy/Arity, How, Name/Arity, ID) :- 250 match_name(How, Fuzzy, Name), 251 man_object_property(Name/Arity, id(ID)). 252help_object(Fuzzy//Arity, How, Name//Arity, ID) :- 253 match_name(How, Fuzzy, Name), 254 man_object_property(Name//Arity, id(ID)). 255help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :- 256 match_name(How, Fuzzy, Name), 257 man_object_property(f(Name/Arity), id(ID)). 258help_object(Fuzzy, How, Name/Arity, ID) :- 259 atom(Fuzzy), 260 match_name(How, Fuzzy, Name), 261 man_object_property(Name/Arity, id(ID)). 262help_object(Fuzzy, How, Name//Arity, ID) :- 263 atom(Fuzzy), 264 match_name(How, Fuzzy, Name), 265 man_object_property(Name//Arity, id(ID)). 266help_object(Fuzzy, How, f(Name/Arity), ID) :- 267 atom(Fuzzy), 268 match_name(How, Fuzzy, Name), 269 man_object_property(f(Name/Arity), id(ID)). 270help_object(Fuzzy, How, c(Name), ID) :- 271 atom(Fuzzy), 272 match_name(How, Fuzzy, Name), 273 man_object_property(c(Name), id(ID)). 274help_object(SecID, _How, section(Label), ID) :- 275 atom(SecID), 276 ( atom_concat('sec:', SecID, Label) 277 ; sub_atom(SecID, _, _, 0, '.html'), 278 Label = SecID 279 ), 280 man_object_property(section(_Level,_Num,Label,_File), id(ID)). 281help_object(Func, How, c(Name), ID) :- 282 compound(Func), 283 compound_name_arity(Func, Fuzzy, 0), 284 match_name(How, Fuzzy, Name), 285 man_object_property(c(Name), id(ID)). 286% for currently loaded predicates 287help_object(Module, _How, Module:Name/Arity, _ID) :- 288 atom(Module), 289 current_module(Module), 290 atom_concat('sec:', Module, SecLabel), 291 \+ man_object_property(section(_,_,SecLabel,_), _), % not a section 292 current_predicate_help(Module:Name/Arity). 293help_object(Module:Name, _How, Module:Name/Arity, _ID) :- 294 atom(Name), 295 current_predicate_help(Module:Name/Arity). 296help_object(Module:Name/Arity, _How, Module:Name/Arity, _ID) :- 297 atom(Name), 298 current_predicate_help(Module:Name/Arity). 299help_object(Name/Arity, _How, Module:Name/Arity, _ID) :- 300 atom(Name), 301 current_predicate_help(Module:Name/Arity). 302help_object(Fuzzy, How, Module:Name/Arity, _ID) :- 303 atom(Fuzzy), 304 match_name(How, Fuzzy, Name), 305 current_predicate_help(Module:Name/Arity).
314current_predicate_help(M:Name/Arity) :- 315 current_predicate(M:Name/Arity), 316 pi_head(Name/Arity,Head), 317 \+ predicate_property(M:Head, imported_from(_)), 318 module_property(M, class(user)), 319 ( mode(M:_, _) % Some predicates are documented 320 -> true 321 ; \+ module_property(M, class(system)), 322 main_source_file(M:Head, File), 323 xref_source(File,[comments(store)]) 324 ), 325 mode(M:Head, _). % Test that our predicate is documented 326 327match_name(exact, Name, Name). 328match_name(dwim, Name, Fuzzy) :- 329 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
335main_source_file(Pred, File) :- 336 predicate_property(Pred, file(File0)), 337 main_source(File0, File). 338 339main_source(File, Main) :- 340 source_file(File), 341 !, 342 Main = File. 343main_source(File, Main) :- 344 source_file_property(File, included_in(Parent, _Time)), 345 main_source(Parent, Main).
353with_pager(Goal) :- 354 pager_ok(Pager, Options), 355 !, 356 Catch = error(io_error(_,_), _), 357 current_output(OldIn), 358 setup_call_cleanup( 359 process_create(Pager, Options, 360 [stdin(pipe(In))]), 361 ( set_stream(In, tty(true)), 362 set_output(In), 363 catch(Goal, Catch, true) 364 ), 365 ( set_output(OldIn), 366 close(In, [force(true)]) 367 )). 368with_pager(Goal) :- 369 call(Goal). 370 371pager_ok(_Path, _Options) :- 372 current_prolog_flag(help_pager, false), 373 !, 374 fail. 375pager_ok(Path, Options) :- 376 current_prolog_flag(help_pager, default), 377 !, 378 stream_property(current_output, tty(true)), 379 \+ running_under_emacs, 380 ( distinct(( getenv('PAGER', Pager) 381 ; Pager = less 382 )), 383 absolute_file_name(path(Pager), Path, 384 [ access(execute), 385 file_errors(fail) 386 ]) 387 -> pager_options(Path, Options) 388 ). 389pager_ok(Path, Options) :- 390 current_prolog_flag(help_pager, Term), 391 callable(Term), 392 compound_name_arguments(Term, Pager, Options), 393 absolute_file_name(path(Pager), Path, 394 [ access(execute), 395 file_errors(fail) 396 ]). 397 398pager_options(Path, Options) :- 399 file_base_name(Path, File), 400 file_name_extension(Base, _, File), 401 downcase_atom(Base, Id), 402 pager_default_options(Id, Options). 403 404pager_default_options(less, ['-r']).
412running_under_emacs :- 413 current_prolog_flag(emacs_inferior_process, true), 414 !. 415running_under_emacs :- 416 getenv('TERM', dumb), 417 !. 418running_under_emacs :- 419 current_prolog_flag(toplevel_prompt, P), 420 sub_atom(P, _, _, _, 'ediprolog'), 421 !.
section
, cfunction
, function
,
iso_predicate
, swi_builtin_predicate
, library_predicate
,
dcg
and aliases chapter
, arithmetic
, c_function
,
predicate
, nonterminal
and non_terminal
. For example:
?- apropos(c:close). ?- apropos(f:min).
446apropos(Query) :- 447 notrace(apropos_no_trace(Query)). 448 449apropos_no_trace(Query) :- 450 findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs), 451 ( Pairs == [] 452 -> print_message(warning, help(no_apropos_match(Query))) 453 ; sort(1, >=, Pairs, Sorted), 454 length(Sorted, Len), 455 ( Len > 20 456 -> length(Truncated, 20), 457 append(Truncated, _, Sorted) 458 ; Truncated = Sorted 459 ), 460 pairs_values(Truncated, Matches), 461 print_message(information, help(apropos_matches(Matches, Len))) 462 ). 463 464apropos(Query, Obj, Summary, Q) :- 465 parse_query(Query, Type, Words), 466 man_object_property(Obj, summary(Summary)), 467 apropos_match(Type, Words, Obj, Summary, Q). 468 469parse_query(Type:String, Type, Words) :- 470 !, 471 must_be(atom, Type), 472 must_be(text, String), 473 tokenize_atom(String, Words). 474parse_query(String, _Type, Words) :- 475 must_be(text, String), 476 tokenize_atom(String, Words). 477 478apropos_match(Type, Query, Object, Summary, Q) :- 479 maplist(amatch(Object, Summary), Query, Scores), 480 match_object_type(Type, Object), 481 sum_list(Scores, Q). 482 483amatch(Object, Summary, Query, Score) :- 484 ( doc_object_identifier(Object, String) 485 ; String = Summary 486 ), 487 amatch(Query, String, Score), 488 !. 489 490amatch(Query, To, Quality) :- 491 doc_related_word(Query, Related, Distance), 492 sub_atom_icasechk(To, _, Related), 493 isub(Related, To, false, Quality0), 494 Quality is Quality0*Distance. 495 496match_object_type(Type, _Object) :- 497 var(Type), 498 !. 499match_object_type(Type, Object) :- 500 downcase_atom(Type, LType), 501 object_class(Object, Class), 502 match_object_class(LType, Class). 503 504match_object_class(Type, Class) :- 505 ( TheClass = Class 506 ; class_alias(Class, TheClass) 507 ), 508 sub_atom(TheClass, 0, _, _, Type), 509 !. 510 511class_alias(section, chapter). 512class_alias(function, arithmetic). 513class_alias(cfunction, c_function). 514class_alias(iso_predicate, predicate). 515class_alias(swi_builtin_predicate, predicate). 516class_alias(library_predicate, predicate). 517class_alias(dcg, predicate). 518class_alias(dcg, nonterminal). 519class_alias(dcg, non_terminal). 520 521class_tag(section, 'SEC'). 522class_tag(function, ' F'). 523class_tag(iso_predicate, 'ISO'). 524class_tag(swi_builtin_predicate, 'SWI'). 525class_tag(library_predicate, 'LIB'). 526class_tag(dcg, 'DCG'). 527 528object_class(section(_Level, _Num, _Label, _File), section). 529object_class(c(_Name), cfunction). 530object_class(f(_Name/_Arity), function). 531object_class(Name/Arity, Type) :- 532 functor(Term, Name, Arity), 533 ( current_predicate(system:Name/Arity), 534 predicate_property(system:Term, built_in) 535 -> ( predicate_property(system:Term, iso) 536 -> Type = iso_predicate 537 ; Type = swi_builtin_predicate 538 ) 539 ; Type = library_predicate 540 ). 541object_class(_M:_Name/_Arity, library_predicate). 542object_class(_Name//_Arity, dcg). 543object_class(_M:_Name//_Arity, dcg).
Predicate
is a term of the form Name/Arity
for which
documentation exists, HelpText
is the documentation in textual
format (parsed from the HTML help).550help_text(Pred, HelpText) :- 551 help_objects(Pred, exact, Matches), !, 552 catch(help_html(Matches, exact-exact, HtmlDoc), _, fail), 553 setup_call_cleanup(open_string(HtmlDoc, In), 554 load_html(stream(In), Dom, []), 555 close(In)), 556 with_output_to(string(HelpText), html_text(Dom, [])). 557 558 /******************************* 559 * MESSAGES * 560 *******************************/ 561 562:- multifile prolog:message//1. 563 564prologmessage(help(not_found(What))) --> 565 [ 'No help for ~p.'-[What], nl, 566 'Use ?- apropos(query). to search for candidates.'-[] 567 ]. 568prologmessage(help(no_apropos_match(Query))) --> 569 [ 'No matches for ~p'-[Query] ]. 570prologmessage(help(apropos_matches(Pairs, Total))) --> 571 { tty_width(W), 572 Width is max(30,W), 573 length(Pairs, Count) 574 }, 575 matches(Pairs, Width), 576 ( {Count =:= Total} 577 -> [] 578 ; [ nl, 579 ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl, 580 'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl, 581 'to restrict your search. For example:'-[], nl, nl, 582 ' ?- apropos(iso:open).'-[], nl, 583 ' ?- apropos(\'open file\').'-[] 584 ] 585 ). 586 587matches([], _) --> []. 588matches([H|T], Width) --> 589 match(H, Width), 590 ( {T == []} 591 -> [] 592 ; [nl], 593 matches(T, Width) 594 ). 595 596match(Obj-Summary, Width) --> 597 { Left is min(40, max(20, round(Width/3))), 598 Right is Width-Left-2, 599 man_object_summary(Obj, ObjS, Tag), 600 write_length(ObjS, LenObj, [portray(true), quoted(true)]), 601 Spaces0 is Left - LenObj - 4, 602 ( Spaces0 > 0 603 -> Spaces = Spaces0, 604 SummaryLen = Right 605 ; Spaces = 1, 606 SummaryLen is Right + Spaces0 - 1 607 ), 608 truncate(Summary, SummaryLen, SummaryE) 609 }, 610 [ ansi([fg(default)], '~w ~p', [Tag, ObjS]), 611 '~|~*+~w'-[Spaces, SummaryE] 612% '~*|~w'-[Spaces, SummaryE] % Should eventually work 613 ]. 614 615truncate(Summary, Width, SummaryE) :- 616 string_length(Summary, SL), 617 SL > Width, 618 !, 619 Pre is Width-4, 620 sub_string(Summary, 0, Pre, _, S1), 621 string_concat(S1, " ...", SummaryE). 622truncate(Summary, _, Summary). 623 624man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :- 625 atom_concat('sec:', Obj, Label), 626 !. 627man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !. 628man_object_summary(c(Name), Obj, ' C') :- !, 629 compound_name_arguments(Obj, Name, []). 630man_object_summary(f(Name/Arity), Name/Arity, ' F') :- !. 631man_object_summary(Obj, Obj, Tag) :- 632 ( object_class(Obj, Class), 633 class_tag(Class, Tag) 634 -> true 635 ; Tag = ' ?' 636 ). 637 638 /******************************* 639 * SANDBOX * 640 *******************************/ 641 642sandbox:safe_primitive(prolog_help:apropos(_)). 643sandbox:safe_primitive(prolog_help:help(_))
Text based manual
This module provides help/1 and apropos/1 that give help on a topic or searches the manual for relevant topics.
By default the result of help/1 is sent through a pager such as
less
. This behaviour is controlled by the following:help_pager
, which can be set to one of the following values:PAGER
or otherwise tries to find theless
program.program_name(Arg, ...)
. For example,less('-r')
would be the default. Note that the program name can be an absolute path if single quotes are used. */