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, 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), []).

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:

   85:- meta_predicate
   86    with_pager(0).   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                      ]).
 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.
  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).
 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.
  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                              \man_pages(Matches,
  172                                         [ no_manual(fail),
  173                                           links(false),
  174                                           link_source(false),
  175                                           navtree(false)
  176                                         ])
  177                            ])
  178                     ])),
  179           Tokens),
  180    !,
  181    with_output_to(string(HTML),
  182                   print_html(Tokens)).
  183
  184match_type(exact-_) -->
  185    [].
  186match_type(dwim-For) -->
  187    html(p(class(warning),
  188           [ 'WARNING: No matches for "', span(class('help-query'), For),
  189             '" Showing closely related results'
  190           ])).
  191
  192man_pages([], _) -->
  193    [].
  194man_pages([H|T], Options) -->
  195    man_page(H, Options),
  196    man_pages(T, Options).
  197
  198page_width(Width) :-
  199    tty_width(W),
  200    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.
  207tty_width(W) :-
  208    \+ running_under_emacs,
  209    catch(tty_size(_, W), _, fail),
  210    !.
  211tty_width(80).
  212
  213help_objects_how(Spec, Objects, exact) :-
  214    help_objects(Spec, exact, Objects),
  215    !.
  216help_objects_how(Spec, Objects, dwim) :-
  217    help_objects(Spec, dwim, Objects),
  218    !.
  219
  220help_objects(Spec, How, Objects) :-
  221    findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0),
  222    Objects0 \== [],
  223    sort(1, @>, Objects0, Objects1),
  224    pairs_values(Objects1, Objects2),
  225    sort(Objects2, Objects).
  226
  227help_object(Fuzzy/Arity, How, Name/Arity, ID) :-
  228    match_name(How, Fuzzy, Name),
  229    man_object_property(Name/Arity, id(ID)).
  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, f(Name/Arity), ID) :-
  234    match_name(How, Fuzzy, Name),
  235    man_object_property(f(Name/Arity), id(ID)).
  236help_object(Fuzzy, How, Name/Arity, ID) :-
  237    atom(Fuzzy),
  238    match_name(How, Fuzzy, Name),
  239    man_object_property(Name/Arity, id(ID)).
  240help_object(Fuzzy, How, Name//Arity, ID) :-
  241    atom(Fuzzy),
  242    match_name(How, Fuzzy, Name),
  243    man_object_property(Name//Arity, id(ID)).
  244help_object(Fuzzy, How, f(Name/Arity), ID) :-
  245    atom(Fuzzy),
  246    match_name(How, Fuzzy, Name),
  247    man_object_property(f(Name/Arity), id(ID)).
  248help_object(Fuzzy, How, c(Name), ID) :-
  249    atom(Fuzzy),
  250    match_name(How, Fuzzy, Name),
  251    man_object_property(c(Name), id(ID)).
  252help_object(SecID, _How, section(Label), ID) :-
  253    atom(SecID),
  254    (   atom_concat('sec:', SecID, Label)
  255    ;   sub_atom(SecID, _, _, 0, '.html'),
  256        Label = SecID
  257    ),
  258    man_object_property(section(_Level,_Num,Label,_File), id(ID)).
  259help_object(Func, How, c(Name), ID) :-
  260    compound(Func),
  261    compound_name_arity(Func, Fuzzy, 0),
  262    match_name(How, Fuzzy, Name),
  263    man_object_property(c(Name), id(ID)).
  264
  265match_name(exact, Name, Name).
  266match_name(dwim,  Name, Fuzzy) :-
  267    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.
  275with_pager(Goal) :-
  276    pager_ok(Pager, Options),
  277    !,
  278    Catch = error(io_error(_,_), _),
  279    current_output(OldIn),
  280    setup_call_cleanup(
  281        process_create(Pager, Options,
  282                       [stdin(pipe(In))]),
  283        ( set_stream(In, tty(true)),
  284          set_output(In),
  285          catch(Goal, Catch, true)
  286        ),
  287        ( set_output(OldIn),
  288          close(In, [force(true)])
  289        )).
  290with_pager(Goal) :-
  291    call(Goal).
  292
  293pager_ok(_Path, _Options) :-
  294    current_prolog_flag(help_pager, false),
  295    !,
  296    fail.
  297pager_ok(Path, Options) :-
  298    current_prolog_flag(help_pager, default),
  299    !,
  300    stream_property(current_output, tty(true)),
  301    \+ running_under_emacs,
  302    (   distinct((   getenv('PAGER', Pager)
  303                 ;   Pager = less
  304                 )),
  305        absolute_file_name(path(Pager), Path,
  306                           [ access(execute),
  307                             file_errors(fail)
  308                           ])
  309    ->  pager_options(Path, Options)
  310    ).
  311pager_ok(Path, Options) :-
  312    current_prolog_flag(help_pager, Term),
  313    callable(Term),
  314    compound_name_arguments(Term, Pager, Options),
  315    absolute_file_name(path(Pager), Path,
  316                           [ access(execute),
  317                             file_errors(fail)
  318                           ]).
  319
  320pager_options(Path, Options) :-
  321    file_base_name(Path, File),
  322    file_name_extension(Base, _, File),
  323    downcase_atom(Base, Id),
  324    pager_default_options(Id, Options).
  325
  326pager_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.
  334running_under_emacs :-
  335    current_prolog_flag(emacs_inferior_process, true),
  336    !.
  337running_under_emacs :-
  338    getenv('TERM', dumb),
  339    !.
  340running_under_emacs :-
  341    current_prolog_flag(toplevel_prompt, P),
  342    sub_atom(P, _, _, _, 'ediprolog'),
  343    !.
 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.
  368apropos(Query) :-
  369    notrace(apropos_no_trace(Query)).
  370
  371apropos_no_trace(Query) :-
  372    findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs),
  373    (   Pairs == []
  374    ->  print_message(warning, help(no_apropos_match(Query)))
  375    ;   sort(1, >=, Pairs, Sorted),
  376        length(Sorted, Len),
  377        (   Len > 20
  378        ->  length(Truncated, 20),
  379            append(Truncated, _, Sorted)
  380        ;   Truncated = Sorted
  381        ),
  382        pairs_values(Truncated, Matches),
  383        print_message(information, help(apropos_matches(Matches, Len)))
  384    ).
  385
  386apropos(Query, Obj, Summary, Q) :-
  387    parse_query(Query, Type, Words),
  388    man_object_property(Obj, summary(Summary)),
  389    apropos_match(Type, Words, Obj, Summary, Q).
  390
  391parse_query(Type:String, Type, Words) :-
  392    !,
  393    must_be(atom, Type),
  394    must_be(text, String),
  395    tokenize_atom(String, Words).
  396parse_query(String, _Type, Words) :-
  397    must_be(text, String),
  398    tokenize_atom(String, Words).
  399
  400apropos_match(Type, Query, Object, Summary, Q) :-
  401    maplist(amatch(Object, Summary), Query, Scores),
  402    match_object_type(Type, Object),
  403    sum_list(Scores, Q).
  404
  405amatch(Object, Summary, Query, Score) :-
  406    (   doc_object_identifier(Object, String)
  407    ;   String = Summary
  408    ),
  409    amatch(Query, String, Score),
  410    !.
  411
  412amatch(Query, To, Quality) :-
  413    doc_related_word(Query, Related, Distance),
  414    sub_atom_icasechk(To, _, Related),
  415    isub(Related, To, false, Quality0),
  416    Quality is Quality0*Distance.
  417
  418match_object_type(Type, _Object) :-
  419    var(Type),
  420    !.
  421match_object_type(Type, Object) :-
  422    downcase_atom(Type, LType),
  423    object_class(Object, Class),
  424    match_object_class(LType, Class).
  425
  426match_object_class(Type, Class) :-
  427    (   TheClass = Class
  428    ;   class_alias(Class, TheClass)
  429    ),
  430    sub_atom(TheClass, 0, _, _, Type),
  431    !.
  432
  433class_alias(section,               chapter).
  434class_alias(function,              arithmetic).
  435class_alias(cfunction,             c_function).
  436class_alias(iso_predicate,         predicate).
  437class_alias(swi_builtin_predicate, predicate).
  438class_alias(library_predicate,     predicate).
  439class_alias(dcg,                   predicate).
  440class_alias(dcg,                   nonterminal).
  441class_alias(dcg,                   non_terminal).
  442
  443class_tag(section,               'SEC').
  444class_tag(function,              '  F').
  445class_tag(iso_predicate,         'ISO').
  446class_tag(swi_builtin_predicate, 'SWI').
  447class_tag(library_predicate,     'LIB').
  448class_tag(dcg,                   'DCG').
  449
  450object_class(section(_Level, _Num, _Label, _File), section).
  451object_class(c(_Name), cfunction).
  452object_class(f(_Name/_Arity), function).
  453object_class(Name/Arity, Type) :-
  454    functor(Term, Name, Arity),
  455    (   current_predicate(system:Name/Arity),
  456        predicate_property(system:Term, built_in)
  457    ->  (   predicate_property(system:Term, iso)
  458        ->  Type = iso_predicate
  459        ;   Type = swi_builtin_predicate
  460        )
  461    ;   Type = library_predicate
  462    ).
  463object_class(_M:_Name/_Arity, library_predicate).
  464object_class(_Name//_Arity, dcg).
  465object_class(_M:_Name//_Arity, dcg).
  466
  467
  468		 /*******************************
  469		 *            MESSAGES		*
  470		 *******************************/
  471
  472:- multifile prolog:message//1.  473
  474prolog:message(help(not_found(What))) -->
  475    [ 'No help for ~p.'-[What], nl,
  476      'Use ?- apropos(query). to search for candidates.'-[]
  477    ].
  478prolog:message(help(no_apropos_match(Query))) -->
  479    [ 'No matches for ~p'-[Query] ].
  480prolog:message(help(apropos_matches(Pairs, Total))) -->
  481    { tty_width(W),
  482      Width is max(30,W),
  483      length(Pairs, Count)
  484    },
  485    matches(Pairs, Width),
  486    (   {Count =:= Total}
  487    ->  []
  488    ;   [ nl,
  489          ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl,
  490          'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl,
  491          'to restrict your search.  For example:'-[], nl, nl,
  492          '  ?- apropos(iso:open).'-[], nl,
  493          '  ?- apropos(\'open file\').'-[]
  494        ]
  495    ).
  496
  497matches([], _) --> [].
  498matches([H|T], Width) -->
  499    match(H, Width),
  500    (   {T == []}
  501    ->  []
  502    ;   [nl],
  503        matches(T, Width)
  504    ).
  505
  506match(Obj-Summary, Width) -->
  507    { Left is min(40, max(20, round(Width/3))),
  508      Right is Width-Left-2,
  509      man_object_summary(Obj, ObjS, Tag),
  510      write_length(ObjS, LenObj, [portray(true), quoted(true)]),
  511      Spaces0 is Left - LenObj - 4,
  512      (   Spaces0 > 0
  513      ->  Spaces = Spaces0,
  514          SummaryLen = Right
  515      ;   Spaces = 1,
  516          SummaryLen is Right + Spaces0 - 1
  517      ),
  518      truncate(Summary, SummaryLen, SummaryE)
  519    },
  520    [ ansi([fg(default)], '~w ~p', [Tag, ObjS]),
  521      '~|~*+~w'-[Spaces, SummaryE]
  522%     '~*|~w'-[Spaces, SummaryE]		% Should eventually work
  523    ].
  524
  525truncate(Summary, Width, SummaryE) :-
  526    string_length(Summary, SL),
  527    SL > Width,
  528    !,
  529    Pre is Width-4,
  530    sub_string(Summary, 0, Pre, _, S1),
  531    string_concat(S1, " ...", SummaryE).
  532truncate(Summary, _, Summary).
  533
  534man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :-
  535    atom_concat('sec:', Obj, Label),
  536    !.
  537man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !.
  538man_object_summary(c(Name), Obj, '  C') :- !,
  539    compound_name_arguments(Obj, Name, []).
  540man_object_summary(f(Name/Arity), Name/Arity, '  F') :- !.
  541man_object_summary(Obj, Obj, Tag) :-
  542    (   object_class(Obj, Class),
  543        class_tag(Class, Tag)
  544    ->  true
  545    ;   Tag = '  ?'
  546    ).
  547
  548		 /*******************************
  549		 *            SANDBOX		*
  550		 *******************************/
  551
  552sandbox:safe_primitive(prolog_help:apropos(_)).
  553sandbox:safe_primitive(prolog_help:help(_))