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 ]). 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), []).
88:- meta_predicate 89 with_pager( ). 90 91:- multifile 92 show_html_hook/1. 93 94% one of `default`, `false`, an executable or executable(options), e.g. 95% less('-r'). 96:- create_prolog_flag(help_pager, default, 97 [ type(term), 98 keep(true) 99 ]).
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.
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).
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)).
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)). 285% for currently loaded predicates 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,_), _), % not a section 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).
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:_, _) % Some predicates are documented 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, _). % Test that our predicate is documented 325 326match_name(exact, Name, Name). 327match_name(dwim, Name, Fuzzy) :- 328 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
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).
352with_pager(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 370pager_ok(_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 397pager_options(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 403pager_default_options(less, ['-r']).
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 !.
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).
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 /******************************* 546 * MESSAGES * 547 *******************************/ 548 549:- multifile prolog:message//1. 550 551prologmessage(help(not_found(What))) --> 552 [ 'No help for ~p.'-[What], nl, 553 'Use ?- apropos(query). to search for candidates.'-[] 554 ]. 555prologmessage(help(no_apropos_match(Query))) --> 556 [ 'No matches for ~p'-[Query] ]. 557prologmessage(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] 599% '~*|~w'-[Spaces, SummaryE] % Should eventually work 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 /******************************* 626 * SANDBOX * 627 *******************************/ 628 629sandbox:safe_primitive(prolog_help:apropos(_)). 630sandbox: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. */