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

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:

   88:- meta_predicate
   89    with_pager(0).   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		      ]).
 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)
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.

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.
  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).
 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.
  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)).
 tty_width(-Width) is det
Return the believed width of the terminal. If we do not know Width is bound to 80.
  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).
 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.
  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)).
 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.
  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']).
 running_under_emacs
True when we believe to be running in Emacs. Unfortunately there is no easy unambiguous way to tell.
  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    !.
 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.
  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
  518prolog:message(help(not_found(What))) -->
  519    [ 'No help for ~p.'-[What], nl,
  520      'Use ?- apropos(query). to search for candidates.'-[]
  521    ].
  522prolog:message(help(no_apropos_match(Query))) -->
  523    [ 'No matches for ~p'-[Query] ].
  524prolog:message(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(_))