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-2024, 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)
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.
  146help :-
  147    notrace(show_matches([help/1, apropos/1], exact-help)).
  148
  149help(What) :-
  150    notrace(help_no_trace(What)).
  151
  152help_no_trace(What) :-
  153    help_objects_how(What, Matches, How),
  154    !,
  155    show_matches(Matches, How-What).
  156help_no_trace(What) :-
  157    print_message(warning, help(not_found(What))).
  158
  159show_matches(Matches, HowWhat) :-
  160    help_html(Matches, HowWhat, HTML),
  161    !,
  162    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.
  170show_html(HTML) :-
  171    show_html_hook(HTML),
  172    !.
  173show_html(HTML) :-
  174    setup_call_cleanup(
  175	open_string(HTML, In),
  176	load_html(stream(In), DOM, []),
  177	close(In)),
  178    page_width(PageWidth),
  179    LineWidth is PageWidth - 4,
  180    with_pager(html_text(DOM, [width(LineWidth)])).
  181
  182help_html(Matches, How, HTML) :-
  183    phrase(html(html([ head([]),
  184		       body([ \match_type(How),
  185			      dl(\man_pages(Matches,
  186					    [ no_manual(fail),
  187					      links(false),
  188					      link_source(false),
  189					      navtree(false),
  190					      server(false),
  191                                              qualified(always)
  192					    ]))
  193			    ])
  194		     ])),
  195	   Tokens),
  196    !,
  197    with_output_to(string(HTML),
  198		   print_html(Tokens)).
  199
  200match_type(exact-_) -->
  201    [].
  202match_type(dwim-For) -->
  203    html(p(class(warning),
  204	   [ 'WARNING: No matches for "', span(class('help-query'), For),
  205	     '" Showing closely related results'
  206	   ])).
  207
  208man_pages([], _) -->
  209    [].
  210man_pages([H|T], Options) -->
  211    (   man_page(H, Options)
  212    ->  []
  213    ;   html(p(class(warning),
  214               [ 'WARNING: No help for ~p'-[H]
  215               ]))
  216    ),
  217    man_pages(T, Options).
  218
  219page_width(Width) :-
  220    tty_width(W),
  221    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.
  228tty_width(W) :-
  229    \+ running_under_emacs,
  230    catch(tty_size(_, W), _, fail),
  231    !.
  232tty_width(80).
  233
  234help_objects_how(Spec, Objects, exact) :-
  235    help_objects(Spec, exact, Objects),
  236    !.
  237help_objects_how(Spec, Objects, dwim) :-
  238    help_objects(Spec, dwim, Objects),
  239    !.
  240
  241help_objects(Spec, How, Objects) :-
  242    findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0),
  243    Objects0 \== [],
  244    sort(1, @>, Objects0, Objects1),
  245    pairs_values(Objects1, Objects2),
  246    sort(Objects2, Objects).
  247
  248help_object(Fuzzy/Arity, How, Name/Arity, ID) :-
  249    match_name(How, Fuzzy, Name),
  250    man_object_property(Name/Arity, id(ID)).
  251help_object(Fuzzy//Arity, How, Name//Arity, ID) :-
  252    match_name(How, Fuzzy, Name),
  253    man_object_property(Name//Arity, id(ID)).
  254help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :-
  255    match_name(How, Fuzzy, Name),
  256    man_object_property(f(Name/Arity), id(ID)).
  257help_object(Fuzzy, How, Name/Arity, ID) :-
  258    atom(Fuzzy),
  259    match_name(How, Fuzzy, Name),
  260    man_object_property(Name/Arity, id(ID)).
  261help_object(Fuzzy, How, Name//Arity, ID) :-
  262    atom(Fuzzy),
  263    match_name(How, Fuzzy, Name),
  264    man_object_property(Name//Arity, id(ID)).
  265help_object(Fuzzy, How, f(Name/Arity), ID) :-
  266    atom(Fuzzy),
  267    match_name(How, Fuzzy, Name),
  268    man_object_property(f(Name/Arity), id(ID)).
  269help_object(Fuzzy, How, c(Name), ID) :-
  270    atom(Fuzzy),
  271    match_name(How, Fuzzy, Name),
  272    man_object_property(c(Name), id(ID)).
  273help_object(SecID, _How, section(Label), ID) :-
  274    atom(SecID),
  275    (   atom_concat('sec:', SecID, Label)
  276    ;   sub_atom(SecID, _, _, 0, '.html'),
  277	Label = SecID
  278    ),
  279    man_object_property(section(_Level,_Num,Label,_File), id(ID)).
  280help_object(Func, How, c(Name), ID) :-
  281    compound(Func),
  282    compound_name_arity(Func, Fuzzy, 0),
  283    match_name(How, Fuzzy, Name),
  284    man_object_property(c(Name), id(ID)).
  285% for currently loaded predicates
  286help_object(Module, _How, Module:Name/Arity, _ID) :-
  287    atom(Module),
  288    current_module(Module),
  289    atom_concat('sec:', Module, SecLabel),
  290    \+ man_object_property(section(_,_,SecLabel,_), _), % not a section
  291    current_predicate_help(Module:Name/Arity).
  292help_object(Module:Name, _How, Module:Name/Arity, _ID) :-
  293    atom(Name),
  294    current_predicate_help(Module:Name/Arity).
  295help_object(Module:Name/Arity, _How, Module:Name/Arity, _ID) :-
  296    atom(Name),
  297    current_predicate_help(Module:Name/Arity).
  298help_object(Name/Arity, _How, Module:Name/Arity, _ID) :-
  299    atom(Name),
  300    current_predicate_help(Module:Name/Arity).
  301help_object(Fuzzy, How, Module:Name/Arity, _ID) :-
  302    atom(Fuzzy),
  303    match_name(How, Fuzzy, Name),
  304    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.
  313current_predicate_help(M:Name/Arity) :-
  314    current_predicate(M:Name/Arity),
  315    pi_head(Name/Arity,Head),
  316    \+ predicate_property(M:Head, imported_from(_)),
  317    module_property(M, class(user)),
  318    (   mode(M:_, _)             % Some predicates are documented
  319    ->  true
  320    ;   \+ module_property(M, class(system)),
  321        main_source_file(M:Head, File),
  322	xref_source(File,[comments(store)])
  323    ),
  324    mode(M:Head, _).             % Test that our predicate is documented
  325
  326match_name(exact, Name, Name).
  327match_name(dwim,  Name, Fuzzy) :-
  328    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.
  334main_source_file(Pred, File) :-
  335    predicate_property(Pred, file(File0)),
  336    main_source(File0, File).
  337
  338main_source(File, Main) :-
  339    source_file(File),
  340    !,
  341    Main = File.
  342main_source(File, Main) :-
  343    source_file_property(File, included_in(Parent, _Time)),
  344    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.
  352with_pager(Goal) :-
  353    pager_ok(Pager, Options),
  354    !,
  355    Catch = error(io_error(_,_), _),
  356    current_output(OldIn),
  357    setup_call_cleanup(
  358	process_create(Pager, Options,
  359		       [stdin(pipe(In))]),
  360	( set_stream(In, tty(true)),
  361	  set_output(In),
  362	  catch(Goal, Catch, true)
  363	),
  364	( set_output(OldIn),
  365	  close(In, [force(true)])
  366	)).
  367with_pager(Goal) :-
  368    call(Goal).
  369
  370pager_ok(_Path, _Options) :-
  371    current_prolog_flag(help_pager, false),
  372    !,
  373    fail.
  374pager_ok(Path, Options) :-
  375    current_prolog_flag(help_pager, default),
  376    !,
  377    stream_property(current_output, tty(true)),
  378    \+ running_under_emacs,
  379    (   distinct((   getenv('PAGER', Pager)
  380		 ;   Pager = less
  381		 )),
  382	absolute_file_name(path(Pager), Path,
  383			   [ access(execute),
  384			     file_errors(fail)
  385			   ])
  386    ->  pager_options(Path, Options)
  387    ).
  388pager_ok(Path, Options) :-
  389    current_prolog_flag(help_pager, Term),
  390    callable(Term),
  391    compound_name_arguments(Term, Pager, Options),
  392    absolute_file_name(path(Pager), Path,
  393			   [ access(execute),
  394			     file_errors(fail)
  395			   ]).
  396
  397pager_options(Path, Options) :-
  398    file_base_name(Path, File),
  399    file_name_extension(Base, _, File),
  400    downcase_atom(Base, Id),
  401    pager_default_options(Id, Options).
  402
  403pager_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.
  411running_under_emacs :-
  412    current_prolog_flag(emacs_inferior_process, true),
  413    !.
  414running_under_emacs :-
  415    getenv('TERM', dumb),
  416    !.
  417running_under_emacs :-
  418    current_prolog_flag(toplevel_prompt, P),
  419    sub_atom(P, _, _, _, 'ediprolog'),
  420    !.
 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.
  445apropos(Query) :-
  446    notrace(apropos_no_trace(Query)).
  447
  448apropos_no_trace(Query) :-
  449    findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs),
  450    (   Pairs == []
  451    ->  print_message(warning, help(no_apropos_match(Query)))
  452    ;   sort(1, >=, Pairs, Sorted),
  453	length(Sorted, Len),
  454	(   Len > 20
  455	->  length(Truncated, 20),
  456	    append(Truncated, _, Sorted)
  457	;   Truncated = Sorted
  458	),
  459	pairs_values(Truncated, Matches),
  460	print_message(information, help(apropos_matches(Matches, Len)))
  461    ).
  462
  463apropos(Query, Obj, Summary, Q) :-
  464    parse_query(Query, Type, Words),
  465    man_object_property(Obj, summary(Summary)),
  466    apropos_match(Type, Words, Obj, Summary, Q).
  467
  468parse_query(Type:String, Type, Words) :-
  469    !,
  470    must_be(atom, Type),
  471    must_be(text, String),
  472    tokenize_atom(String, Words).
  473parse_query(String, _Type, Words) :-
  474    must_be(text, String),
  475    tokenize_atom(String, Words).
  476
  477apropos_match(Type, Query, Object, Summary, Q) :-
  478    maplist(amatch(Object, Summary), Query, Scores),
  479    match_object_type(Type, Object),
  480    sum_list(Scores, Q).
  481
  482amatch(Object, Summary, Query, Score) :-
  483    (   doc_object_identifier(Object, String)
  484    ;   String = Summary
  485    ),
  486    amatch(Query, String, Score),
  487    !.
  488
  489amatch(Query, To, Quality) :-
  490    doc_related_word(Query, Related, Distance),
  491    sub_atom_icasechk(To, _, Related),
  492    isub(Related, To, false, Quality0),
  493    Quality is Quality0*Distance.
  494
  495match_object_type(Type, _Object) :-
  496    var(Type),
  497    !.
  498match_object_type(Type, Object) :-
  499    downcase_atom(Type, LType),
  500    object_class(Object, Class),
  501    match_object_class(LType, Class).
  502
  503match_object_class(Type, Class) :-
  504    (   TheClass = Class
  505    ;   class_alias(Class, TheClass)
  506    ),
  507    sub_atom(TheClass, 0, _, _, Type),
  508    !.
  509
  510class_alias(section,               chapter).
  511class_alias(function,              arithmetic).
  512class_alias(cfunction,             c_function).
  513class_alias(iso_predicate,         predicate).
  514class_alias(swi_builtin_predicate, predicate).
  515class_alias(library_predicate,     predicate).
  516class_alias(dcg,                   predicate).
  517class_alias(dcg,                   nonterminal).
  518class_alias(dcg,                   non_terminal).
  519
  520class_tag(section,               'SEC').
  521class_tag(function,              '  F').
  522class_tag(iso_predicate,         'ISO').
  523class_tag(swi_builtin_predicate, 'SWI').
  524class_tag(library_predicate,     'LIB').
  525class_tag(dcg,                   'DCG').
  526
  527object_class(section(_Level, _Num, _Label, _File), section).
  528object_class(c(_Name), cfunction).
  529object_class(f(_Name/_Arity), function).
  530object_class(Name/Arity, Type) :-
  531    functor(Term, Name, Arity),
  532    (   current_predicate(system:Name/Arity),
  533	predicate_property(system:Term, built_in)
  534    ->  (   predicate_property(system:Term, iso)
  535	->  Type = iso_predicate
  536	;   Type = swi_builtin_predicate
  537	)
  538    ;   Type = library_predicate
  539    ).
  540object_class(_M:_Name/_Arity, library_predicate).
  541object_class(_Name//_Arity, dcg).
  542object_class(_M:_Name//_Arity, dcg).
  543
  544
  545		 /*******************************
  546		 *            MESSAGES		*
  547		 *******************************/
  548
  549:- multifile prolog:message//1.  550
  551prolog:message(help(not_found(What))) -->
  552    [ 'No help for ~p.'-[What], nl,
  553      'Use ?- apropos(query). to search for candidates.'-[]
  554    ].
  555prolog:message(help(no_apropos_match(Query))) -->
  556    [ 'No matches for ~p'-[Query] ].
  557prolog:message(help(apropos_matches(Pairs, Total))) -->
  558    { tty_width(W),
  559      Width is max(30,W),
  560      length(Pairs, Count)
  561    },
  562    matches(Pairs, Width),
  563    (   {Count =:= Total}
  564    ->  []
  565    ;   [ nl,
  566	  ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl,
  567	  'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl,
  568	  'to restrict your search.  For example:'-[], nl, nl,
  569	  '  ?- apropos(iso:open).'-[], nl,
  570	  '  ?- apropos(\'open file\').'-[]
  571	]
  572    ).
  573
  574matches([], _) --> [].
  575matches([H|T], Width) -->
  576    match(H, Width),
  577    (   {T == []}
  578    ->  []
  579    ;   [nl],
  580	matches(T, Width)
  581    ).
  582
  583match(Obj-Summary, Width) -->
  584    { Left is min(40, max(20, round(Width/3))),
  585      Right is Width-Left-2,
  586      man_object_summary(Obj, ObjS, Tag),
  587      write_length(ObjS, LenObj, [portray(true), quoted(true)]),
  588      Spaces0 is Left - LenObj - 4,
  589      (   Spaces0 > 0
  590      ->  Spaces = Spaces0,
  591	  SummaryLen = Right
  592      ;   Spaces = 1,
  593	  SummaryLen is Right + Spaces0 - 1
  594      ),
  595      truncate(Summary, SummaryLen, SummaryE)
  596    },
  597    [ ansi([fg(default)], '~w ~p', [Tag, ObjS]),
  598      '~|~*+~w'-[Spaces, SummaryE]
  599%     '~*|~w'-[Spaces, SummaryE]		% Should eventually work
  600    ].
  601
  602truncate(Summary, Width, SummaryE) :-
  603    string_length(Summary, SL),
  604    SL > Width,
  605    !,
  606    Pre is Width-4,
  607    sub_string(Summary, 0, Pre, _, S1),
  608    string_concat(S1, " ...", SummaryE).
  609truncate(Summary, _, Summary).
  610
  611man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :-
  612    atom_concat('sec:', Obj, Label),
  613    !.
  614man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !.
  615man_object_summary(c(Name), Obj, '  C') :- !,
  616    compound_name_arguments(Obj, Name, []).
  617man_object_summary(f(Name/Arity), Name/Arity, '  F') :- !.
  618man_object_summary(Obj, Obj, Tag) :-
  619    (   object_class(Obj, Class),
  620	class_tag(Class, Tag)
  621    ->  true
  622    ;   Tag = '  ?'
  623    ).
  624
  625		 /*******************************
  626		 *            SANDBOX		*
  627		 *******************************/
  628
  629sandbox:safe_primitive(prolog_help:apropos(_)).
  630sandbox:safe_primitive(prolog_help:help(_))