View source with formatted 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	    help_text/2
   41	  ]).   42:- use_module(library(pldoc), []).   43:- use_module(library(isub), [isub/4]).   44
   45:- autoload(library(apply), [maplist/3]).   46:- autoload(library(error), [must_be/2]).   47:- autoload(library(lists), [append/3, sum_list/2]).   48:- autoload(library(pairs), [pairs_values/2]).   49:- autoload(library(porter_stem), [tokenize_atom/2]).   50:- autoload(library(process), [process_create/3]).   51:- autoload(library(sgml), [load_html/3]).   52:- autoload(library(solution_sequences), [distinct/1]).   53:- autoload(library(http/html_write), [html/3, print_html/1]).   54:- autoload(library(lynx/html_text), [html_text/2]).   55:- autoload(pldoc(doc_man), [man_page/4]).   56:- autoload(pldoc(doc_modes), [(mode)/2]).   57:- autoload(pldoc(doc_words), [doc_related_word/3]).   58:- autoload(pldoc(man_index), [man_object_property/2, doc_object_identifier/2]).   59:- autoload(library(prolog_code), [pi_head/2]).   60:- autoload(library(prolog_xref), [xref_source/2]).   61
   62:- use_module(library(lynx/pldoc_style), []).   63
   64/** <module> Text based manual
   65
   66This module provides help/1 and apropos/1 that   give help on a topic or
   67searches the manual for relevant topics.
   68
   69By default the result of  help/1  is   sent  through  a  _pager_ such as
   70`less`. This behaviour is controlled by the following:
   71
   72  - The Prolog flag `help_pager`, which can be set to one of the
   73    following values:
   74
   75    - false
   76    Never use a pager.
   77    - default
   78    Use default behaviour.  This tries to determine whether Prolog
   79    is running interactively in an environment that allows for
   80    a pager.  If so it examines the environment variable =PAGER=
   81    or otherwise tries to find the `less` program.
   82    - Callable
   83    A Callable term is interpreted as program_name(Arg, ...).  For
   84    example, `less('-r')` would be the default.  Note that the
   85    program name can be an absolute path if single quotes are
   86    used.
   87*/
   88
   89:- meta_predicate
   90    with_pager(0).   91
   92:- multifile
   93    show_html_hook/1.   94
   95% one of `default`, `false`, an executable or executable(options), e.g.
   96% less('-r').
   97:- create_prolog_flag(help_pager, default,
   98		      [ type(term),
   99			keep(true)
  100		      ]).  101
  102%!  help is det.
  103%!  help(+What) is det.
  104%
  105%   Show help for What. What is a   term that describes the topics(s) to
  106%   give help for.  Notations for What are:
  107%
  108%     - Atom
  109%       This ambiguous form is most commonly used and shows all
  110%       matching documents.  For example:
  111%
  112%           ?- help(append).
  113%
  114%     - Name/Arity
  115%       Give help on predicates with matching Name/Arity.  Arity may
  116%       be unbound.
  117%     - Name//Arity
  118%       Give help on the matching DCG rule (non-terminal)
  119%     - Module:Name
  120%       Give help on predicates with Name in Module and any arity.
  121%       Used for loaded code only.
  122%     - Module:Name/Arity
  123%       Give help on predicates with Name in Module and Arity.
  124%       Used for loaded code only.
  125%     - f(Name/Arity)
  126%       Give help on the matching Prolog arithmetic functions.
  127%     - c(Name)
  128%       Give help on the matching C interface function
  129%     - section(Label)
  130%       Show the section from the manual with matching Label.
  131%
  132%   help/1 shows documentation from the manual   as  well as from loaded
  133%   user code if the code is documented   using  PlDoc. To show only the
  134%   documentatoion of the  loaded  predicate   we  may  prefix predicate
  135%   indicator with the module in which it is defined.
  136%
  137%   If an exact match fails this predicates attempts fuzzy matching and,
  138%   when successful, display the results headed   by  a warning that the
  139%   matches are based on fuzzy matching.
  140%
  141%   If possible, the results are sent  through   a  _pager_  such as the
  142%   `less` program. This behaviour is  controlled   by  the  Prolog flag
  143%   `help_pager`. See section level documentation.
  144%
  145%   @see apropos/1 for searching the manual names and summaries.
  146
  147help :-
  148    notrace(show_matches([help/1, apropos/1], exact-help)).
  149
  150help(What) :-
  151    notrace(help_no_trace(What)).
  152
  153help_no_trace(What) :-
  154    help_objects_how(What, Matches, How),
  155    !,
  156    show_matches(Matches, How-What).
  157help_no_trace(What) :-
  158    print_message(warning, help(not_found(What))).
  159
  160show_matches(Matches, HowWhat) :-
  161    help_html(Matches, HowWhat, HTML),
  162    !,
  163    show_html(HTML).
  164
  165%!  show_html_hook(+HTML:string) is semidet.
  166%
  167%   Hook called to display the  extracted   HTML  document. If this hook
  168%   fails the HTML is rendered  to  the   console  as  plain  text using
  169%   html_text/2.
  170
  171show_html(HTML) :-
  172    show_html_hook(HTML),
  173    !.
  174show_html(HTML) :-
  175    setup_call_cleanup(
  176	open_string(HTML, In),
  177	load_html(stream(In), DOM, []),
  178	close(In)),
  179    page_width(PageWidth),
  180    LineWidth is PageWidth - 4,
  181    with_pager(html_text(DOM, [width(LineWidth)])).
  182
  183help_html(Matches, How, HTML) :-
  184    phrase(html(html([ head([]),
  185		       body([ \match_type(How),
  186			      dl(\man_pages(Matches,
  187					    [ no_manual(fail),
  188					      links(false),
  189					      link_source(false),
  190					      navtree(false),
  191					      server(false),
  192                                              qualified(always)
  193					    ]))
  194			    ])
  195		     ])),
  196	   Tokens),
  197    !,
  198    with_output_to(string(HTML),
  199		   print_html(Tokens)).
  200
  201match_type(exact-_) -->
  202    [].
  203match_type(dwim-For) -->
  204    html(p(class(warning),
  205	   [ 'WARNING: No matches for "', span(class('help-query'), For),
  206	     '" Showing closely related results'
  207	   ])).
  208
  209man_pages([], _) -->
  210    [].
  211man_pages([H|T], Options) -->
  212    (   man_page(H, Options)
  213    ->  []
  214    ;   html(p(class(warning),
  215               [ 'WARNING: No help for ~p'-[H]
  216               ]))
  217    ),
  218    man_pages(T, Options).
  219
  220page_width(Width) :-
  221    tty_width(W),
  222    Width is min(100,max(50,W)).
  223
  224%!  tty_width(-Width) is det.
  225%
  226%   Return the believed width of the terminal.   If we do not know Width
  227%   is bound to 80.
  228
  229tty_width(W) :-
  230    \+ running_under_emacs,
  231    catch(tty_size(_, W), _, fail),
  232    !.
  233tty_width(80).
  234
  235help_objects_how(Spec, Objects, exact) :-
  236    help_objects(Spec, exact, Objects),
  237    !.
  238help_objects_how(Spec, Objects, dwim) :-
  239    help_objects(Spec, dwim, Objects),
  240    !.
  241
  242help_objects(Spec, How, Objects) :-
  243    findall(ID-Obj, help_object(Spec, How, Obj, ID), Objects0),
  244    Objects0 \== [],
  245    sort(1, @>, Objects0, Objects1),
  246    pairs_values(Objects1, Objects2),
  247    sort(Objects2, Objects).
  248
  249help_object(Fuzzy/Arity, How, Name/Arity, ID) :-
  250    match_name(How, Fuzzy, Name),
  251    man_object_property(Name/Arity, id(ID)).
  252help_object(Fuzzy//Arity, How, Name//Arity, ID) :-
  253    match_name(How, Fuzzy, Name),
  254    man_object_property(Name//Arity, id(ID)).
  255help_object(Fuzzy/Arity, How, f(Name/Arity), ID) :-
  256    match_name(How, Fuzzy, Name),
  257    man_object_property(f(Name/Arity), id(ID)).
  258help_object(Fuzzy, How, Name/Arity, ID) :-
  259    atom(Fuzzy),
  260    match_name(How, Fuzzy, Name),
  261    man_object_property(Name/Arity, id(ID)).
  262help_object(Fuzzy, How, Name//Arity, ID) :-
  263    atom(Fuzzy),
  264    match_name(How, Fuzzy, Name),
  265    man_object_property(Name//Arity, id(ID)).
  266help_object(Fuzzy, How, f(Name/Arity), ID) :-
  267    atom(Fuzzy),
  268    match_name(How, Fuzzy, Name),
  269    man_object_property(f(Name/Arity), id(ID)).
  270help_object(Fuzzy, How, c(Name), ID) :-
  271    atom(Fuzzy),
  272    match_name(How, Fuzzy, Name),
  273    man_object_property(c(Name), id(ID)).
  274help_object(SecID, _How, section(Label), ID) :-
  275    atom(SecID),
  276    (   atom_concat('sec:', SecID, Label)
  277    ;   sub_atom(SecID, _, _, 0, '.html'),
  278	Label = SecID
  279    ),
  280    man_object_property(section(_Level,_Num,Label,_File), id(ID)).
  281help_object(Func, How, c(Name), ID) :-
  282    compound(Func),
  283    compound_name_arity(Func, Fuzzy, 0),
  284    match_name(How, Fuzzy, Name),
  285    man_object_property(c(Name), id(ID)).
  286% for currently loaded predicates
  287help_object(Module, _How, Module:Name/Arity, _ID) :-
  288    atom(Module),
  289    current_module(Module),
  290    atom_concat('sec:', Module, SecLabel),
  291    \+ man_object_property(section(_,_,SecLabel,_), _), % not a section
  292    current_predicate_help(Module:Name/Arity).
  293help_object(Module:Name, _How, Module:Name/Arity, _ID) :-
  294    atom(Name),
  295    current_predicate_help(Module:Name/Arity).
  296help_object(Module:Name/Arity, _How, Module:Name/Arity, _ID) :-
  297    atom(Name),
  298    current_predicate_help(Module:Name/Arity).
  299help_object(Name/Arity, _How, Module:Name/Arity, _ID) :-
  300    atom(Name),
  301    current_predicate_help(Module:Name/Arity).
  302help_object(Fuzzy, How, Module:Name/Arity, _ID) :-
  303    atom(Fuzzy),
  304    match_name(How, Fuzzy, Name),
  305    current_predicate_help(Module:Name/Arity).
  306
  307%!  current_predicate_help(?PI) is nondet.
  308%
  309%   True when we have documentation on  PI.   First  we decide we have a
  310%   definition  for  PI,  then  we  check    whether   or  not  we  have
  311%   documentation for the module in which PI  resides. If not, we switch
  312%   to documentation collect mode and reload the file that defines PI.
  313
  314current_predicate_help(M:Name/Arity) :-
  315    current_predicate(M:Name/Arity),
  316    pi_head(Name/Arity,Head),
  317    \+ predicate_property(M:Head, imported_from(_)),
  318    module_property(M, class(user)),
  319    (   mode(M:_, _)             % Some predicates are documented
  320    ->  true
  321    ;   \+ module_property(M, class(system)),
  322        main_source_file(M:Head, File),
  323	xref_source(File,[comments(store)])
  324    ),
  325    mode(M:Head, _).             % Test that our predicate is documented
  326
  327match_name(exact, Name, Name).
  328match_name(dwim,  Name, Fuzzy) :-
  329    freeze(Fuzzy, dwim_match(Fuzzy, Name)).
  330
  331%!  main_source_file(+Pred, -File) is semidet.
  332%
  333%   True when File is the main (not included) file that defines Pred.
  334
  335main_source_file(Pred, File) :-
  336    predicate_property(Pred, file(File0)),
  337    main_source(File0, File).
  338
  339main_source(File, Main) :-
  340    source_file(File),
  341    !,
  342    Main = File.
  343main_source(File, Main) :-
  344    source_file_property(File, included_in(Parent, _Time)),
  345    main_source(Parent, Main).
  346
  347
  348%!  with_pager(+Goal)
  349%
  350%   Send the current output of Goal through a  pager. If no pager can be
  351%   found we simply dump the output to the current output.
  352
  353with_pager(Goal) :-
  354    pager_ok(Pager, Options),
  355    !,
  356    Catch = error(io_error(_,_), _),
  357    current_output(OldIn),
  358    setup_call_cleanup(
  359	process_create(Pager, Options,
  360		       [stdin(pipe(In))]),
  361	( set_stream(In, tty(true)),
  362	  set_output(In),
  363	  catch(Goal, Catch, true)
  364	),
  365	( set_output(OldIn),
  366	  close(In, [force(true)])
  367	)).
  368with_pager(Goal) :-
  369    call(Goal).
  370
  371pager_ok(_Path, _Options) :-
  372    current_prolog_flag(help_pager, false),
  373    !,
  374    fail.
  375pager_ok(Path, Options) :-
  376    current_prolog_flag(help_pager, default),
  377    !,
  378    stream_property(current_output, tty(true)),
  379    \+ running_under_emacs,
  380    (   distinct((   getenv('PAGER', Pager)
  381		 ;   Pager = less
  382		 )),
  383	absolute_file_name(path(Pager), Path,
  384			   [ access(execute),
  385			     file_errors(fail)
  386			   ])
  387    ->  pager_options(Path, Options)
  388    ).
  389pager_ok(Path, Options) :-
  390    current_prolog_flag(help_pager, Term),
  391    callable(Term),
  392    compound_name_arguments(Term, Pager, Options),
  393    absolute_file_name(path(Pager), Path,
  394			   [ access(execute),
  395			     file_errors(fail)
  396			   ]).
  397
  398pager_options(Path, Options) :-
  399    file_base_name(Path, File),
  400    file_name_extension(Base, _, File),
  401    downcase_atom(Base, Id),
  402    pager_default_options(Id, Options).
  403
  404pager_default_options(less, ['-r']).
  405
  406
  407%!  running_under_emacs
  408%
  409%   True when we believe to be running  in Emacs. Unfortunately there is
  410%   no easy unambiguous way to tell.
  411
  412running_under_emacs :-
  413    current_prolog_flag(emacs_inferior_process, true),
  414    !.
  415running_under_emacs :-
  416    getenv('TERM', dumb),
  417    !.
  418running_under_emacs :-
  419    current_prolog_flag(toplevel_prompt, P),
  420    sub_atom(P, _, _, _, 'ediprolog'),
  421    !.
  422
  423
  424%!  apropos(+Query) is det.
  425%
  426%   Print objects from the  manual  whose   name  or  summary match with
  427%   Query. Query takes one of the following forms:
  428%
  429%     - Type:Text
  430%       Find objects matching Text and filter the results by Type.
  431%       Type matching is a case intensitive _prefix_ match.
  432%       Defined types are `section`, `cfunction`, `function`,
  433%       `iso_predicate`, `swi_builtin_predicate`, `library_predicate`,
  434%       `dcg` and aliases `chapter`, `arithmetic`, `c_function`,
  435%       `predicate`, `nonterminal` and `non_terminal`.  For example:
  436%
  437%           ?- apropos(c:close).
  438%           ?- apropos(f:min).
  439%
  440%     - Text
  441%       Text is broken into tokens.  A topic matches if all tokens
  442%       appear in the name or summary of the topic. Matching is
  443%	case insensitive.  Results are ordered depending on the
  444%	quality of the match.
  445
  446apropos(Query) :-
  447    notrace(apropos_no_trace(Query)).
  448
  449apropos_no_trace(Query) :-
  450    findall(Q-(Obj-Summary), apropos(Query, Obj, Summary, Q), Pairs),
  451    (   Pairs == []
  452    ->  print_message(warning, help(no_apropos_match(Query)))
  453    ;   sort(1, >=, Pairs, Sorted),
  454	length(Sorted, Len),
  455	(   Len > 20
  456	->  length(Truncated, 20),
  457	    append(Truncated, _, Sorted)
  458	;   Truncated = Sorted
  459	),
  460	pairs_values(Truncated, Matches),
  461	print_message(information, help(apropos_matches(Matches, Len)))
  462    ).
  463
  464apropos(Query, Obj, Summary, Q) :-
  465    parse_query(Query, Type, Words),
  466    man_object_property(Obj, summary(Summary)),
  467    apropos_match(Type, Words, Obj, Summary, Q).
  468
  469parse_query(Type:String, Type, Words) :-
  470    !,
  471    must_be(atom, Type),
  472    must_be(text, String),
  473    tokenize_atom(String, Words).
  474parse_query(String, _Type, Words) :-
  475    must_be(text, String),
  476    tokenize_atom(String, Words).
  477
  478apropos_match(Type, Query, Object, Summary, Q) :-
  479    maplist(amatch(Object, Summary), Query, Scores),
  480    match_object_type(Type, Object),
  481    sum_list(Scores, Q).
  482
  483amatch(Object, Summary, Query, Score) :-
  484    (   doc_object_identifier(Object, String)
  485    ;   String = Summary
  486    ),
  487    amatch(Query, String, Score),
  488    !.
  489
  490amatch(Query, To, Quality) :-
  491    doc_related_word(Query, Related, Distance),
  492    sub_atom_icasechk(To, _, Related),
  493    isub(Related, To, false, Quality0),
  494    Quality is Quality0*Distance.
  495
  496match_object_type(Type, _Object) :-
  497    var(Type),
  498    !.
  499match_object_type(Type, Object) :-
  500    downcase_atom(Type, LType),
  501    object_class(Object, Class),
  502    match_object_class(LType, Class).
  503
  504match_object_class(Type, Class) :-
  505    (   TheClass = Class
  506    ;   class_alias(Class, TheClass)
  507    ),
  508    sub_atom(TheClass, 0, _, _, Type),
  509    !.
  510
  511class_alias(section,               chapter).
  512class_alias(function,              arithmetic).
  513class_alias(cfunction,             c_function).
  514class_alias(iso_predicate,         predicate).
  515class_alias(swi_builtin_predicate, predicate).
  516class_alias(library_predicate,     predicate).
  517class_alias(dcg,                   predicate).
  518class_alias(dcg,                   nonterminal).
  519class_alias(dcg,                   non_terminal).
  520
  521class_tag(section,               'SEC').
  522class_tag(function,              '  F').
  523class_tag(iso_predicate,         'ISO').
  524class_tag(swi_builtin_predicate, 'SWI').
  525class_tag(library_predicate,     'LIB').
  526class_tag(dcg,                   'DCG').
  527
  528object_class(section(_Level, _Num, _Label, _File), section).
  529object_class(c(_Name), cfunction).
  530object_class(f(_Name/_Arity), function).
  531object_class(Name/Arity, Type) :-
  532    functor(Term, Name, Arity),
  533    (   current_predicate(system:Name/Arity),
  534	predicate_property(system:Term, built_in)
  535    ->  (   predicate_property(system:Term, iso)
  536	->  Type = iso_predicate
  537	;   Type = swi_builtin_predicate
  538	)
  539    ;   Type = library_predicate
  540    ).
  541object_class(_M:_Name/_Arity, library_predicate).
  542object_class(_Name//_Arity, dcg).
  543object_class(_M:_Name//_Arity, dcg).
  544
  545%! help_text(+Predicate:term, -HelpText:string) is semidet.
  546%
  547%  When =Predicate= is a term of the form =Name/Arity= for which
  548%  documentation exists, =HelpText= is the documentation in textual
  549%  format (parsed from the HTML help).
  550help_text(Pred, HelpText) :-
  551    help_objects(Pred, exact, Matches), !,
  552    catch(help_html(Matches, exact-exact, HtmlDoc), _, fail),
  553    setup_call_cleanup(open_string(HtmlDoc, In),
  554                       load_html(stream(In), Dom, []),
  555                       close(In)),
  556    with_output_to(string(HelpText), html_text(Dom, [])).
  557
  558		 /*******************************
  559		 *            MESSAGES		*
  560		 *******************************/
  561
  562:- multifile prolog:message//1.  563
  564prolog:message(help(not_found(What))) -->
  565    [ 'No help for ~p.'-[What], nl,
  566      'Use ?- apropos(query). to search for candidates.'-[]
  567    ].
  568prolog:message(help(no_apropos_match(Query))) -->
  569    [ 'No matches for ~p'-[Query] ].
  570prolog:message(help(apropos_matches(Pairs, Total))) -->
  571    { tty_width(W),
  572      Width is max(30,W),
  573      length(Pairs, Count)
  574    },
  575    matches(Pairs, Width),
  576    (   {Count =:= Total}
  577    ->  []
  578    ;   [ nl,
  579	  ansi(fg(red), 'Showing ~D of ~D matches', [Count,Total]), nl, nl,
  580	  'Use ?- apropos(Type:Query) or multiple words in Query '-[], nl,
  581	  'to restrict your search.  For example:'-[], nl, nl,
  582	  '  ?- apropos(iso:open).'-[], nl,
  583	  '  ?- apropos(\'open file\').'-[]
  584	]
  585    ).
  586
  587matches([], _) --> [].
  588matches([H|T], Width) -->
  589    match(H, Width),
  590    (   {T == []}
  591    ->  []
  592    ;   [nl],
  593	matches(T, Width)
  594    ).
  595
  596match(Obj-Summary, Width) -->
  597    { Left is min(40, max(20, round(Width/3))),
  598      Right is Width-Left-2,
  599      man_object_summary(Obj, ObjS, Tag),
  600      write_length(ObjS, LenObj, [portray(true), quoted(true)]),
  601      Spaces0 is Left - LenObj - 4,
  602      (   Spaces0 > 0
  603      ->  Spaces = Spaces0,
  604	  SummaryLen = Right
  605      ;   Spaces = 1,
  606	  SummaryLen is Right + Spaces0 - 1
  607      ),
  608      truncate(Summary, SummaryLen, SummaryE)
  609    },
  610    [ ansi([fg(default)], '~w ~p', [Tag, ObjS]),
  611      '~|~*+~w'-[Spaces, SummaryE]
  612%     '~*|~w'-[Spaces, SummaryE]		% Should eventually work
  613    ].
  614
  615truncate(Summary, Width, SummaryE) :-
  616    string_length(Summary, SL),
  617    SL > Width,
  618    !,
  619    Pre is Width-4,
  620    sub_string(Summary, 0, Pre, _, S1),
  621    string_concat(S1, " ...", SummaryE).
  622truncate(Summary, _, Summary).
  623
  624man_object_summary(section(_Level, _Num, Label, _File), Obj, 'SEC') :-
  625    atom_concat('sec:', Obj, Label),
  626    !.
  627man_object_summary(section(0, _Num, File, _Path), File, 'SEC') :- !.
  628man_object_summary(c(Name), Obj, '  C') :- !,
  629    compound_name_arguments(Obj, Name, []).
  630man_object_summary(f(Name/Arity), Name/Arity, '  F') :- !.
  631man_object_summary(Obj, Obj, Tag) :-
  632    (   object_class(Obj, Class),
  633	class_tag(Class, Tag)
  634    ->  true
  635    ;   Tag = '  ?'
  636    ).
  637
  638		 /*******************************
  639		 *            SANDBOX		*
  640		 *******************************/
  641
  642sandbox:safe_primitive(prolog_help:apropos(_)).
  643sandbox:safe_primitive(prolog_help:help(_))