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-2022, 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:- autoload(library(apply), [maplist/3]).   43:- autoload(library(error), [must_be/2]).   44:- autoload(library(isub), [isub/4]).   45:- autoload(library(lists), [append/3, sum_list/2]).   46:- autoload(library(pairs), [pairs_values/2]).   47:- autoload(library(porter_stem), [tokenize_atom/2]).   48:- autoload(library(process), [process_create/3]).   49:- autoload(library(sgml), [load_html/3]).   50:- autoload(library(solution_sequences), [distinct/1]).   51:- autoload(library(http/html_write), [html/3, print_html/1]).   52:- autoload(library(lynx/html_text), [html_text/2]).   53:- autoload(pldoc(doc_man), [man_page/4]).   54:- autoload(pldoc(doc_modes), [mode/2]).   55:- autoload(pldoc(doc_words), [doc_related_word/3]).   56:- autoload(pldoc(man_index), [man_object_property/2, doc_object_identifier/2]).   57:- autoload(library(prolog_code), [pi_head/2]).   58:- autoload(library(prolog_xref), [xref_source/2]).   59
   60:- 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:

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