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