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-2023, 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).
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.
135help :- 136 notrace(show_matches([help/1, apropos/1], exact-help)). 137 138help(What) :- 139 notrace(help_no_trace(What)). 140 141help_no_trace(What) :- 142 help_objects_how(What, Matches, How), 143 !, 144 show_matches(Matches, How-What). 145help_no_trace(What) :- 146 print_message(warning, help(not_found(What))). 147 148show_matches(Matches, HowWhat) :- 149 help_html(Matches, HowWhat, HTML), 150 !, 151 show_html(HTML).
159show_html(HTML) :- 160 show_html_hook(HTML), 161 !. 162show_html(HTML) :- 163 setup_call_cleanup( 164 open_string(HTML, In), 165 load_html(stream(In), DOM, []), 166 close(In)), 167 page_width(PageWidth), 168 LineWidth is PageWidth - 4, 169 with_pager(html_text(DOM, [width(LineWidth)])). 170 171help_html(Matches, How, HTML) :- 172 phrase(html(html([ head([]), 173 body([ \match_type(How), 174 dl(\man_pages(Matches, 175 [ no_manual(fail), 176 links(false), 177 link_source(false), 178 navtree(false), 179 server(false), 180 qualified(always) 181 ])) 182 ]) 183 ])), 184 Tokens), 185 !, 186 with_output_to(string(HTML), 187 print_html(Tokens)). 188 189match_type(exact-_) --> 190 []. 191match_type(dwim-For) --> 192 html(p(class(warning), 193 [ 'WARNING: No matches for "', span(class('help-query'), For), 194 '" Showing closely related results' 195 ])). 196 197man_pages([], _) --> 198 []. 199man_pages([H|T], Options) --> 200 ( man_page(H, Options) 201 -> [] 202 ; html(p(class(warning), 203 [ 'WARNING: No help for ~p'-[H] 204 ])) 205 ), 206 man_pages(T, Options). 207 208page_width(Width) :- 209 tty_width(W), 210 Width is min(100,max(50,W)).
217tty_width(W) :- 218 \+ running_under_emacs, 219 catch(tty_size(_, W), _, fail), 220 !. 221tty_width(80). 222 223help_objects_how(Spec, Objects, exact) :- 224 help_objects(Spec, exact, Objects), 225 !. 226help_objects_how(Spec, Objects, dwim) :- 227 help_objects(Spec, dwim, Objects), 228 !. 229 230help_objects(Spec, How, Objects) :- 231 findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0), 232 Objects0 \== [], 233 sort(1, @>, Objects0, Objects1), 234 pairs_values(Objects1, Objects2), 235 sort(Objects2, Objects). 236 237help_object(Fuzzy/Arity, How, Name/Arity, ID) :- 238 match_name(How, Fuzzy, Name), 239 man_object_property(Name/Arity, id(ID)). 240help_object(Fuzzy//Arity, How, Name//Arity, ID) :- 241 match_name(How, Fuzzy, Name), 242 man_object_property(Name//Arity, id(ID)). 243help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :- 244 match_name(How, Fuzzy, Name), 245 man_object_property(f(Name/Arity), id(ID)). 246help_object(Fuzzy, How, Name/Arity, ID) :- 247 atom(Fuzzy), 248 match_name(How, Fuzzy, Name), 249 man_object_property(Name/Arity, id(ID)). 250help_object(Fuzzy, How, Name//Arity, ID) :- 251 atom(Fuzzy), 252 match_name(How, Fuzzy, Name), 253 man_object_property(Name//Arity, id(ID)). 254help_object(Fuzzy, How, f(Name/Arity), ID) :- 255 atom(Fuzzy), 256 match_name(How, Fuzzy, Name), 257 man_object_property(f(Name/Arity), id(ID)). 258help_object(Fuzzy, How, c(Name), ID) :- 259 atom(Fuzzy), 260 match_name(How, Fuzzy, Name), 261 man_object_property(c(Name), id(ID)). 262help_object(SecID, _How, section(Label), ID) :- 263 atom(SecID), 264 ( atom_concat('sec:', SecID, Label) 265 ; sub_atom(SecID, _, _, 0, '.html'), 266 Label = SecID 267 ), 268 man_object_property(section(_Level,_Num,Label,_File), id(ID)). 269help_object(Func, How, c(Name), ID) :- 270 compound(Func), 271 compound_name_arity(Func, Fuzzy, 0), 272 match_name(How, Fuzzy, Name), 273 man_object_property(c(Name), id(ID)). 274% for currently loaded predicates 275help_object(Module, _How, Module:Name/Arity, _ID) :- 276 atom(Module), 277 current_module(Module), 278 atom_concat('sec:', Module, SecLabel), 279 \+ man_object_property(section(_,_,SecLabel,_), _), % not a section 280 current_predicate_help(Module:Name/Arity). 281help_object(Name/Arity, _How, Name/Arity, _ID) :- 282 atom(Name), 283 current_predicate_help(_:Name/Arity). 284help_object(Fuzzy, How, Module:Name/Arity, _ID) :- 285 atom(Fuzzy), 286 match_name(How, Fuzzy, Name), 287 current_predicate_help(Module:Name/Arity).
296current_predicate_help(M:Name/Arity) :- 297 current_predicate(M:Name/Arity), 298 pi_head(Name/Arity,Head), 299 \+ predicate_property(M:Head, imported_from(_)), 300 \+ man_object_property(Name/Arity, _), % must not be indexed already 301 ( mode(M:_, _) % Some predicates are documented 302 -> true 303 ; \+ module_property(M, class(system)), 304 predicate_property(M:Head,file(File)), 305 xref_source(File,[comments(store)]) 306 ), 307 mode(M:Head, _). % Test that our predicate is documented 308 309match_name(exact, Name, Name). 310match_name(dwim, Name, Fuzzy) :- 311 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
319with_pager(Goal) :- 320 pager_ok(Pager, Options), 321 !, 322 Catch = error(io_error(_,_), _), 323 current_output(OldIn), 324 setup_call_cleanup( 325 process_create(Pager, Options, 326 [stdin(pipe(In))]), 327 ( set_stream(In, tty(true)), 328 set_output(In), 329 catch(Goal, Catch, true) 330 ), 331 ( set_output(OldIn), 332 close(In, [force(true)]) 333 )). 334with_pager(Goal) :- 335 call(Goal). 336 337pager_ok(_Path, _Options) :- 338 current_prolog_flag(help_pager, false), 339 !, 340 fail. 341pager_ok(Path, Options) :- 342 current_prolog_flag(help_pager, default), 343 !, 344 stream_property(current_output, tty(true)), 345 \+ running_under_emacs, 346 ( distinct(( getenv('PAGER', Pager) 347 ; Pager = less 348 )), 349 absolute_file_name(path(Pager), Path, 350 [ access(execute), 351 file_errors(fail) 352 ]) 353 -> pager_options(Path, Options) 354 ). 355pager_ok(Path, Options) :- 356 current_prolog_flag(help_pager, Term), 357 callable(Term), 358 compound_name_arguments(Term, Pager, Options), 359 absolute_file_name(path(Pager), Path, 360 [ access(execute), 361 file_errors(fail) 362 ]). 363 364pager_options(Path, Options) :- 365 file_base_name(Path, File), 366 file_name_extension(Base, _, File), 367 downcase_atom(Base, Id), 368 pager_default_options(Id, Options). 369 370pager_default_options(less, ['-r']).
378running_under_emacs :- 379 current_prolog_flag(emacs_inferior_process, true), 380 !. 381running_under_emacs :- 382 getenv('TERM', dumb), 383 !. 384running_under_emacs :- 385 current_prolog_flag(toplevel_prompt, P), 386 sub_atom(P, _, _, _, 'ediprolog'), 387 !.
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).
412apropos(Query) :- 413 notrace(apropos_no_trace(Query)). 414 415apropos_no_trace(Query) :- 416 findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs), 417 ( Pairs == [] 418 -> print_message(warning, help(no_apropos_match(Query))) 419 ; sort(1, >=, Pairs, Sorted), 420 length(Sorted, Len), 421 ( Len > 20 422 -> length(Truncated, 20), 423 append(Truncated, _, Sorted) 424 ; Truncated = Sorted 425 ), 426 pairs_values(Truncated, Matches), 427 print_message(information, help(apropos_matches(Matches, Len))) 428 ). 429 430apropos(Query, Obj, Summary, Q) :- 431 parse_query(Query, Type, Words), 432 man_object_property(Obj, summary(Summary)), 433 apropos_match(Type, Words, Obj, Summary, Q). 434 435parse_query(Type:String, Type, Words) :- 436 !, 437 must_be(atom, Type), 438 must_be(text, String), 439 tokenize_atom(String, Words). 440parse_query(String, _Type, Words) :- 441 must_be(text, String), 442 tokenize_atom(String, Words). 443 444apropos_match(Type, Query, Object, Summary, Q) :- 445 maplist(amatch(Object, Summary), Query, Scores), 446 match_object_type(Type, Object), 447 sum_list(Scores, Q). 448 449amatch(Object, Summary, Query, Score) :- 450 ( doc_object_identifier(Object, String) 451 ; String = Summary 452 ), 453 amatch(Query, String, Score), 454 !. 455 456amatch(Query, To, Quality) :- 457 doc_related_word(Query, Related, Distance), 458 sub_atom_icasechk(To, _, Related), 459 isub(Related, To, false, Quality0), 460 Quality is Quality0*Distance. 461 462match_object_type(Type, _Object) :- 463 var(Type), 464 !. 465match_object_type(Type, Object) :- 466 downcase_atom(Type, LType), 467 object_class(Object, Class), 468 match_object_class(LType, Class). 469 470match_object_class(Type, Class) :- 471 ( TheClass = Class 472 ; class_alias(Class, TheClass) 473 ), 474 sub_atom(TheClass, 0, _, _, Type), 475 !. 476 477class_alias(section, chapter). 478class_alias(function, arithmetic). 479class_alias(cfunction, c_function). 480class_alias(iso_predicate, predicate). 481class_alias(swi_builtin_predicate, predicate). 482class_alias(library_predicate, predicate). 483class_alias(dcg, predicate). 484class_alias(dcg, nonterminal). 485class_alias(dcg, non_terminal). 486 487class_tag(section, 'SEC'). 488class_tag(function, ' F'). 489class_tag(iso_predicate, 'ISO'). 490class_tag(swi_builtin_predicate, 'SWI'). 491class_tag(library_predicate, 'LIB'). 492class_tag(dcg, 'DCG'). 493 494object_class(section(_Level, _Num, _Label, _File), section). 495object_class(c(_Name), cfunction). 496object_class(f(_Name/_Arity), function). 497object_class(Name/Arity, Type) :- 498 functor(Term, Name, Arity), 499 ( current_predicate(system:Name/Arity), 500 predicate_property(system:Term, built_in) 501 -> ( predicate_property(system:Term, iso) 502 -> Type = iso_predicate 503 ; Type = swi_builtin_predicate 504 ) 505 ; Type = library_predicate 506 ). 507object_class(_M:_Name/_Arity, library_predicate). 508object_class(_Name//_Arity, dcg). 509object_class(_M:_Name//_Arity, dcg). 510 511 512 /******************************* 513 * MESSAGES * 514 *******************************/ 515 516:- multifile prolog:message//1. 517 518prologmessage(help(not_found(What))) --> 519 [ 'No help for ~p.'-[What], nl, 520 'Use ?- apropos(query). to search for candidates.'-[] 521 ]. 522prologmessage(help(no_apropos_match(Query))) --> 523 [ 'No matches for ~p'-[Query] ]. 524prologmessage(help(apropos_matches(Pairs, Total))) --> 525 { tty_width(W), 526 Width is max(30,W), 527 length(Pairs, Count) 528 }, 529 matches(Pairs, Width), 530 ( {Count =:= Total} 531 -> [] 532 ; [ nl, 533 ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl, 534 'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl, 535 'to restrict your search. For example:'-[], nl, nl, 536 ' ?- apropos(iso:open).'-[], nl, 537 ' ?- apropos(\'open file\').'-[] 538 ] 539 ). 540 541matches([], _) --> []. 542matches([H|T], Width) --> 543 match(H, Width), 544 ( {T == []} 545 -> [] 546 ; [nl], 547 matches(T, Width) 548 ). 549 550match(Obj-Summary, Width) --> 551 { Left is min(40, max(20, round(Width/3))), 552 Right is Width-Left-2, 553 man_object_summary(Obj, ObjS, Tag), 554 write_length(ObjS, LenObj, [portray(true), quoted(true)]), 555 Spaces0 is Left - LenObj - 4, 556 ( Spaces0 > 0 557 -> Spaces = Spaces0, 558 SummaryLen = Right 559 ; Spaces = 1, 560 SummaryLen is Right + Spaces0 - 1 561 ), 562 truncate(Summary, SummaryLen, SummaryE) 563 }, 564 [ ansi([fg(default)], '~w ~p', [Tag, ObjS]), 565 '~|~*+~w'-[Spaces, SummaryE] 566% '~*|~w'-[Spaces, SummaryE] % Should eventually work 567 ]. 568 569truncate(Summary, Width, SummaryE) :- 570 string_length(Summary, SL), 571 SL > Width, 572 !, 573 Pre is Width-4, 574 sub_string(Summary, 0, Pre, _, S1), 575 string_concat(S1, " ...", SummaryE). 576truncate(Summary, _, Summary). 577 578man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :- 579 atom_concat('sec:', Obj, Label), 580 !. 581man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !. 582man_object_summary(c(Name), Obj, ' C') :- !, 583 compound_name_arguments(Obj, Name, []). 584man_object_summary(f(Name/Arity), Name/Arity, ' F') :- !. 585man_object_summary(Obj, Obj, Tag) :- 586 ( object_class(Obj, Class), 587 class_tag(Class, Tag) 588 -> true 589 ; Tag = ' ?' 590 ). 591 592 /******************************* 593 * SANDBOX * 594 *******************************/ 595 596sandbox:safe_primitive(prolog_help:apropos(_)). 597sandbox: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. */