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, CWI Amsterdam 7 All rights reserved. 8 9 Redistribution and use in source and binary forms, with or without 10 modification, are permitted provided that the following conditions 11 are met: 12 13 1. Redistributions of source code must retain the above copyright 14 notice, this list of conditions and the following disclaimer. 15 16 2. Redistributions in binary form must reproduce the above copyright 17 notice, this list of conditions and the following disclaimer in 18 the documentation and/or other materials provided with the 19 distribution. 20 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 POSSIBILITY OF SUCH DAMAGE. 33*/ 34 35:- module(prolog_help, 36 [ help/0, 37 help/1, % +Object 38 apropos/1 % +Search 39 ]). 40:- use_module(library(pldoc), []). 41:- autoload(library(apply),[maplist/3]). 42:- autoload(library(error),[must_be/2]). 43:- autoload(library(isub),[isub/4]). 44:- autoload(library(lists),[append/3,sum_list/2]). 45:- autoload(library(pairs),[pairs_values/2]). 46:- autoload(library(porter_stem),[tokenize_atom/2]). 47:- autoload(library(process),[process_create/3]). 48:- autoload(library(sgml),[load_html/3]). 49:- autoload(library(solution_sequences),[distinct/1]). 50:- autoload(library(http/html_write),[html/3,print_html/1]). 51:- autoload(library(lynx/html_text),[html_text/2]). 52:- autoload(pldoc(doc_man),[man_page/4]). 53:- autoload(pldoc(doc_words),[doc_related_word/3]). 54:- autoload(pldoc(man_index), 55 [man_object_property/2,doc_object_identifier/2]). 56 57 58:- use_module(library(lynx/pldoc_style), []).
85:- meta_predicate 86 with_pager( ). 87 88:- multifile 89 show_html_hook/1. 90 91% one of `default`, `false`, an executable or executable(options), e.g. 92% less('-r'). 93:- create_prolog_flag(help_pager, default, 94 [ type(term), 95 keep(true) 96 ]).
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.
132help :- 133 notrace(show_matches([help/1, apropos/1], exact-help)). 134 135help(What) :- 136 notrace(help_no_trace(What)). 137 138help_no_trace(What) :- 139 help_objects_how(What, Matches, How), 140 !, 141 show_matches(Matches, How-What). 142help_no_trace(What) :- 143 print_message(warning, help(not_found(What))). 144 145show_matches(Matches, HowWhat) :- 146 help_html(Matches, HowWhat, HTML), 147 !, 148 show_html(HTML).
156show_html(HTML) :- 157 show_html_hook(HTML), 158 !. 159show_html(HTML) :- 160 setup_call_cleanup( 161 open_string(HTML, In), 162 load_html(stream(In), DOM, []), 163 close(In)), 164 page_width(PageWidth), 165 LineWidth is PageWidth - 4, 166 with_pager(html_text(DOM, [width(LineWidth)])). 167 168help_html(Matches, How, HTML) :- 169 phrase(html(html([ head([]), 170 body([ \match_type(How), 171 dl(\man_pages(Matches, 172 [ no_manual(fail), 173 links(false), 174 link_source(false), 175 navtree(false), 176 server(false) 177 ])) 178 ]) 179 ])), 180 Tokens), 181 !, 182 with_output_to(string(HTML), 183 print_html(Tokens)). 184 185match_type(exact-_) --> 186 []. 187match_type(dwim-For) --> 188 html(p(class(warning), 189 [ 'WARNING: No matches for "', span(class('help-query'), For), 190 '" Showing closely related results' 191 ])). 192 193man_pages([], _) --> 194 []. 195man_pages([H|T], Options) --> 196 man_page(H, Options), 197 man_pages(T, Options). 198 199page_width(Width) :- 200 tty_width(W), 201 Width is min(100,max(50,W)).
208tty_width(W) :- 209 \+ running_under_emacs, 210 catch(tty_size(_, W), _, fail), 211 !. 212tty_width(80). 213 214help_objects_how(Spec, Objects, exact) :- 215 help_objects(Spec, exact, Objects), 216 !. 217help_objects_how(Spec, Objects, dwim) :- 218 help_objects(Spec, dwim, Objects), 219 !. 220 221help_objects(Spec, How, Objects) :- 222 findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0), 223 Objects0 \== [], 224 sort(1, @>, Objects0, Objects1), 225 pairs_values(Objects1, Objects2), 226 sort(Objects2, Objects). 227 228help_object(Fuzzy/Arity, How, Name/Arity, ID) :- 229 match_name(How, Fuzzy, Name), 230 man_object_property(Name/Arity, id(ID)). 231help_object(Fuzzy//Arity, How, Name//Arity, ID) :- 232 match_name(How, Fuzzy, Name), 233 man_object_property(Name//Arity, id(ID)). 234help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :- 235 match_name(How, Fuzzy, Name), 236 man_object_property(f(Name/Arity), id(ID)). 237help_object(Fuzzy, How, Name/Arity, ID) :- 238 atom(Fuzzy), 239 match_name(How, Fuzzy, Name), 240 man_object_property(Name/Arity, id(ID)). 241help_object(Fuzzy, How, Name//Arity, ID) :- 242 atom(Fuzzy), 243 match_name(How, Fuzzy, Name), 244 man_object_property(Name//Arity, id(ID)). 245help_object(Fuzzy, How, f(Name/Arity), ID) :- 246 atom(Fuzzy), 247 match_name(How, Fuzzy, Name), 248 man_object_property(f(Name/Arity), id(ID)). 249help_object(Fuzzy, How, c(Name), ID) :- 250 atom(Fuzzy), 251 match_name(How, Fuzzy, Name), 252 man_object_property(c(Name), id(ID)). 253help_object(SecID, _How, section(Label), ID) :- 254 atom(SecID), 255 ( atom_concat('sec:', SecID, Label) 256 ; sub_atom(SecID, _, _, 0, '.html'), 257 Label = SecID 258 ), 259 man_object_property(section(_Level,_Num,Label,_File), id(ID)). 260help_object(Func, How, c(Name), ID) :- 261 compound(Func), 262 compound_name_arity(Func, Fuzzy, 0), 263 match_name(How, Fuzzy, Name), 264 man_object_property(c(Name), id(ID)). 265 266match_name(exact, Name, Name). 267match_name(dwim, Name, Fuzzy) :- 268 freeze(Fuzzy, dwim_match(Fuzzy, Name)).
276with_pager(Goal) :- 277 pager_ok(Pager, Options), 278 !, 279 Catch = error(io_error(_,_), _), 280 current_output(OldIn), 281 setup_call_cleanup( 282 process_create(Pager, Options, 283 [stdin(pipe(In))]), 284 ( set_stream(In, tty(true)), 285 set_output(In), 286 catch(Goal, Catch, true) 287 ), 288 ( set_output(OldIn), 289 close(In, [force(true)]) 290 )). 291with_pager(Goal) :- 292 call(Goal). 293 294pager_ok(_Path, _Options) :- 295 current_prolog_flag(help_pager, false), 296 !, 297 fail. 298pager_ok(Path, Options) :- 299 current_prolog_flag(help_pager, default), 300 !, 301 stream_property(current_output, tty(true)), 302 \+ running_under_emacs, 303 ( distinct(( getenv('PAGER', Pager) 304 ; Pager = less 305 )), 306 absolute_file_name(path(Pager), Path, 307 [ access(execute), 308 file_errors(fail) 309 ]) 310 -> pager_options(Path, Options) 311 ). 312pager_ok(Path, Options) :- 313 current_prolog_flag(help_pager, Term), 314 callable(Term), 315 compound_name_arguments(Term, Pager, Options), 316 absolute_file_name(path(Pager), Path, 317 [ access(execute), 318 file_errors(fail) 319 ]). 320 321pager_options(Path, Options) :- 322 file_base_name(Path, File), 323 file_name_extension(Base, _, File), 324 downcase_atom(Base, Id), 325 pager_default_options(Id, Options). 326 327pager_default_options(less, ['-r']).
335running_under_emacs :- 336 current_prolog_flag(emacs_inferior_process, true), 337 !. 338running_under_emacs :- 339 getenv('TERM', dumb), 340 !. 341running_under_emacs :- 342 current_prolog_flag(toplevel_prompt, P), 343 sub_atom(P, _, _, _, 'ediprolog'), 344 !.
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).
369apropos(Query) :- 370 notrace(apropos_no_trace(Query)). 371 372apropos_no_trace(Query) :- 373 findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs), 374 ( Pairs == [] 375 -> print_message(warning, help(no_apropos_match(Query))) 376 ; sort(1, >=, Pairs, Sorted), 377 length(Sorted, Len), 378 ( Len > 20 379 -> length(Truncated, 20), 380 append(Truncated, _, Sorted) 381 ; Truncated = Sorted 382 ), 383 pairs_values(Truncated, Matches), 384 print_message(information, help(apropos_matches(Matches, Len))) 385 ). 386 387apropos(Query, Obj, Summary, Q) :- 388 parse_query(Query, Type, Words), 389 man_object_property(Obj, summary(Summary)), 390 apropos_match(Type, Words, Obj, Summary, Q). 391 392parse_query(Type:String, Type, Words) :- 393 !, 394 must_be(atom, Type), 395 must_be(text, String), 396 tokenize_atom(String, Words). 397parse_query(String, _Type, Words) :- 398 must_be(text, String), 399 tokenize_atom(String, Words). 400 401apropos_match(Type, Query, Object, Summary, Q) :- 402 maplist(amatch(Object, Summary), Query, Scores), 403 match_object_type(Type, Object), 404 sum_list(Scores, Q). 405 406amatch(Object, Summary, Query, Score) :- 407 ( doc_object_identifier(Object, String) 408 ; String = Summary 409 ), 410 amatch(Query, String, Score), 411 !. 412 413amatch(Query, To, Quality) :- 414 doc_related_word(Query, Related, Distance), 415 sub_atom_icasechk(To, _, Related), 416 isub(Related, To, false, Quality0), 417 Quality is Quality0*Distance. 418 419match_object_type(Type, _Object) :- 420 var(Type), 421 !. 422match_object_type(Type, Object) :- 423 downcase_atom(Type, LType), 424 object_class(Object, Class), 425 match_object_class(LType, Class). 426 427match_object_class(Type, Class) :- 428 ( TheClass = Class 429 ; class_alias(Class, TheClass) 430 ), 431 sub_atom(TheClass, 0, _, _, Type), 432 !. 433 434class_alias(section, chapter). 435class_alias(function, arithmetic). 436class_alias(cfunction, c_function). 437class_alias(iso_predicate, predicate). 438class_alias(swi_builtin_predicate, predicate). 439class_alias(library_predicate, predicate). 440class_alias(dcg, predicate). 441class_alias(dcg, nonterminal). 442class_alias(dcg, non_terminal). 443 444class_tag(section, 'SEC'). 445class_tag(function, ' F'). 446class_tag(iso_predicate, 'ISO'). 447class_tag(swi_builtin_predicate, 'SWI'). 448class_tag(library_predicate, 'LIB'). 449class_tag(dcg, 'DCG'). 450 451object_class(section(_Level, _Num, _Label, _File), section). 452object_class(c(_Name), cfunction). 453object_class(f(_Name/_Arity), function). 454object_class(Name/Arity, Type) :- 455 functor(Term, Name, Arity), 456 ( current_predicate(system:Name/Arity), 457 predicate_property(system:Term, built_in) 458 -> ( predicate_property(system:Term, iso) 459 -> Type = iso_predicate 460 ; Type = swi_builtin_predicate 461 ) 462 ; Type = library_predicate 463 ). 464object_class(_M:_Name/_Arity, library_predicate). 465object_class(_Name//_Arity, dcg). 466object_class(_M:_Name//_Arity, dcg). 467 468 469 /******************************* 470 * MESSAGES * 471 *******************************/ 472 473:- multifile prolog:message//1. 474 475prologmessage(help(not_found(What))) --> 476 [ 'No help for ~p.'-[What], nl, 477 'Use ?- apropos(query). to search for candidates.'-[] 478 ]. 479prologmessage(help(no_apropos_match(Query))) --> 480 [ 'No matches for ~p'-[Query] ]. 481prologmessage(help(apropos_matches(Pairs, Total))) --> 482 { tty_width(W), 483 Width is max(30,W), 484 length(Pairs, Count) 485 }, 486 matches(Pairs, Width), 487 ( {Count =:= Total} 488 -> [] 489 ; [ nl, 490 ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl, 491 'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl, 492 'to restrict your search. For example:'-[], nl, nl, 493 ' ?- apropos(iso:open).'-[], nl, 494 ' ?- apropos(\'open file\').'-[] 495 ] 496 ). 497 498matches([], _) --> []. 499matches([H|T], Width) --> 500 match(H, Width), 501 ( {T == []} 502 -> [] 503 ; [nl], 504 matches(T, Width) 505 ). 506 507match(Obj-Summary, Width) --> 508 { Left is min(40, max(20, round(Width/3))), 509 Right is Width-Left-2, 510 man_object_summary(Obj, ObjS, Tag), 511 write_length(ObjS, LenObj, [portray(true), quoted(true)]), 512 Spaces0 is Left - LenObj - 4, 513 ( Spaces0 > 0 514 -> Spaces = Spaces0, 515 SummaryLen = Right 516 ; Spaces = 1, 517 SummaryLen is Right + Spaces0 - 1 518 ), 519 truncate(Summary, SummaryLen, SummaryE) 520 }, 521 [ ansi([fg(default)], '~w ~p', [Tag, ObjS]), 522 '~|~*+~w'-[Spaces, SummaryE] 523% '~*|~w'-[Spaces, SummaryE] % Should eventually work 524 ]. 525 526truncate(Summary, Width, SummaryE) :- 527 string_length(Summary, SL), 528 SL > Width, 529 !, 530 Pre is Width-4, 531 sub_string(Summary, 0, Pre, _, S1), 532 string_concat(S1, " ...", SummaryE). 533truncate(Summary, _, Summary). 534 535man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :- 536 atom_concat('sec:', Obj, Label), 537 !. 538man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !. 539man_object_summary(c(Name), Obj, ' C') :- !, 540 compound_name_arguments(Obj, Name, []). 541man_object_summary(f(Name/Arity), Name/Arity, ' F') :- !. 542man_object_summary(Obj, Obj, Tag) :- 543 ( object_class(Obj, Class), 544 class_tag(Class, Tag) 545 -> true 546 ; Tag = ' ?' 547 ). 548 549 /******************************* 550 * SANDBOX * 551 *******************************/ 552 553sandbox:safe_primitive(prolog_help:apropos(_)). 554sandbox: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. */