View source with raw comments or as raw
    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), []).

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:

   90:- meta_predicate
   91    with_pager(0).   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		      ]).
 help is det
 help(+What) is det
Show help for What. What is a term that describes the topics(s) to give help for. Notations for What are:
Atom
This ambiguous form is most commonly used and shows all matching documents. For example:
?- help(append).
Name / Arity
Give help on predicates with matching Name/Arity. Arity may be unbound.
Name // Arity
Give help on the matching DCG rule (non-terminal)
Module:Name
Give help on predicates with Name in Module and any arity. Used for loaded code only.
Module:Name/Arity
Give help on predicates with Name in Module and Arity. Used for loaded code only.
f(Name/Arity)
Give help on the matching Prolog arithmetic functions.
c(Name)
Give help on the matching C interface function
section(Label)
Show the section from the manual with matching Label.

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.

See also
- apropos/1 for searching the manual names and summaries.
  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).
 show_html_hook(+HTML:string) is semidet
Hook called to display the extracted HTML document. If this hook fails the HTML is rendered to the console as plain text using html_text/2.
  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)).
 tty_width(-Width) is det
Return the believed width of the terminal. If we do not know Width is bound to 80.
  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).
 current_predicate_help(?PI) is nondet
True when we have documentation on PI. First we decide we have a definition for PI, then we check whether or not we have documentation for the module in which PI resides. If not, we switch to documentation collect mode and reload the file that defines PI.
  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)).
 main_source_file(+Pred, -File) is semidet
True when File is the main (not included) file that defines Pred.
  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).
 with_pager(+Goal)
Send the current output of Goal through a pager. If no pager can be found we simply dump the output to the current output.
  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']).
 running_under_emacs
True when we believe to be running in Emacs. Unfortunately there is no easy unambiguous way to tell.
  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    !.
 apropos(+Query) is det
Print objects from the manual whose name or summary match with Query. Query takes one of the following forms:
Type:Text
Find objects matching Text and filter the results by Type. Type matching is a case intensitive prefix match. Defined types are 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).
Text
Text is broken into tokens. A topic matches if all tokens appear in the name or summary of the topic. Matching is case insensitive. Results are ordered depending on the quality of the match.
  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    ).
 help_apropos(+Query, -Obj, -Summary, -Score) is nondet
Find matching documented objects in the help database. Obj is the formal object identifier, Summary its summary description and Score is a number indicating the quality of the match.
  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).
 help_text(+Predicate:term, -HelpText:string) is semidet
When 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).
  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
  572prolog:message(help(not_found(What))) -->
  573    [ 'No help for ~p.'-[What], nl,
  574      'Use ?- apropos(query). to search for candidates.'-[]
  575    ].
  576prolog:message(help(no_apropos_match(Query))) -->
  577    [ 'No matches for ~p'-[Query] ].
  578prolog:message(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(_))