1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: jan@swi-prolog.org 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2018-2026, 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_apropos/4, 41 help_text/2 % :PI, -Text:string 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), []).
90:- meta_predicate 91 with_pager(). 92 93:- multifile 94 show_html_hook/1. 95 96% one of `default`, `false`, an executable or executable(options), e.g. 97% less('-r'). 98:- create_prolog_flag(help_pager, default, 99 [ type(term), 100 keep(true) 101 ]).
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.
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).
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)).
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)). 287% for currently loaded predicates 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,_), _), % not a section 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).
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:_, _) % Some predicates are documented 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, _). % Test that our predicate is documented 327 328match_name(exact, Name, Name). 329match_name(dwim, Name, Fuzzy) :- 330 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
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).
354with_pager(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 372pager_ok(_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 399pager_options(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 405pager_default_options(less, ['-r']).
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 !.
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).
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 ).
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).
Name/Arity for which
documentation exists, HelpText is the documentation in textual format
(parsed from the HTML help).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 /******************************* 567 * MESSAGES * 568 *******************************/ 569 570:- multifile prolog:message//1. 571 572prologmessage(help(not_found(What))) --> 573 [ 'No help for ~p.'-[What], nl, 574 'Use ?- apropos(query). to search for candidates.'-[] 575 ]. 576prologmessage(help(no_apropos_match(Query))) --> 577 [ 'No matches for ~p'-[Query] ]. 578prologmessage(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] 620% '~*|~w'-[Spaces, SummaryE] % Should eventually work 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 /******************************* 647 * SANDBOX * 648 *******************************/ 649 650sandbox:safe_primitive(prolog_help:apropos(_)). 651sandbox: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:PAGERor otherwise tries to find thelessprogram.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.