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:           https://www.swi-prolog.org
    6    Copyright (c)  2006-2026, University of Amsterdam
    7			      VU University Amsterdam
    8			      CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(pldoc_html,
   39	  [ doc_for_file/2,             % +FileSpec, +Options
   40	    doc_write_html/3,           % +Stream, +Title, +Term
   41	    doc_for_wiki_file/2,        % +FileSpec, +Options
   42					% Support doc_index
   43	    doc_page_dom/3,             % +Title, +Body, -DOM
   44	    print_html_head/1,          % +Stream
   45	    predref//1,                 % +PI //
   46	    predref//2,                 % +PI, Options //
   47	    nopredref//1,               % +PI //
   48	    module_info/3,              % +File, +Options0, -Options
   49	    doc_hide_private/3,         % +Doc0, -Doc, +Options
   50	    edit_button//2,             % +File, +Options, //
   51	    source_button//2,           % +File, +Options, //
   52	    zoom_button//2,             % +File, +Options, //
   53            pred_edit_button//2,        % +PredInd, +Options, //
   54	    object_edit_button//2,      % +Obj, +Options, //
   55	    object_source_button//2,    % +Obj, +Options, //
   56	    doc_resources//1,           % +Options
   57	    ensure_doc_objects/1,       % +File
   58					% Support other backends
   59	    doc_file_objects/5,         % +FSpec, -File, -Objs, -FileOpts, +Opts
   60	    existing_linked_file/2,     % +FileSpec, -Path
   61	    unquote_filespec/2,         % +FileSpec, -Unquoted
   62	    doc_tag_title/2,            % +Tag, -Title
   63	    mode_anchor_name/2,         % +Mode, -Anchor
   64	    pred_anchor_name/3,         % +Head, -PI, -Anchor
   65	    private/2,                  % +Obj, +Options
   66	    (multifile)/2,              % +Obj, +Options
   67	    is_pi/1,                    % @Term
   68	    is_op_type/2,               % +Atom, ?Type
   69					% Output routines
   70	    file//1,                    % +File, //
   71	    file//2,                    % +File, +Options, //
   72	    include//3,                 % +File, +Type, +Options //
   73	    tags//1,                    % +Tags, //
   74	    term//3,                    % +Text, +Term, +Bindings, //
   75	    file_header//2,             % +File, +Options, //
   76	    flagref//1,                 % +Flag
   77	    objects//2,                 % +Objects, +Options, //
   78	    object_ref//2,              % +Object, +Options, //
   79	    object_name//2,             % +Object, +Object
   80	    object_href/2,              % +Object, -URL
   81	    localise_object/2,          % +Object, -Canonical
   82	    object_tree//3,             % +Tree, +Current, +Options
   83	    object_page//2,             % +Object, +Options, //
   84	    object_page_header//2,      % +File, +Options, //
   85	    object_synopsis//2,         % +Object, +Options, //
   86	    object_footer//2,           % +Object, +Options, //
   87	    object_page_footer//2,      % +Object, +Options, //
   88	    cite//1                     % +Citations
   89	  ]).   90% Get HTTP server infrastructure when available
   91:- if(exists_source(library(http/http_dispatch))).   92:- use_module(library(http/http_dispatch)).   93:- use_module(library(http/http_wrapper)).   94:- use_module(library(http/jquery)).   95:- use_module(library(apply)).   96:- use_module(library(debug)).   97:- use_module(library(error)).   98:- use_module(library(filesex)).   99:- use_module(library(lists)).  100:- use_module(library(occurs)).  101:- use_module(library(option)).  102:- use_module(library(pairs)).  103:- use_module(library(prolog_source)).  104:- use_module(library(prolog_xref)).  105:- use_module(library(readutil)).  106:- use_module(library(solution_sequences)).  107:- use_module(library(uri)).  108:- use_module(library(http/html_head)).  109:- use_module(library(http/html_write)).  110:- use_module(library(http/http_path)).  111:- use_module(library(http/term_html)).  112:- use_module(pldoc(doc_index)).  113:- use_module(pldoc(doc_man)).  114:- use_module(pldoc(doc_modes)).  115:- use_module(pldoc(doc_process)).  116:- use_module(pldoc(doc_search)).  117:- use_module(pldoc(doc_util)).  118:- use_module(pldoc(doc_wiki)).  119:- use_module(library(dcg/high_order)).  120:- use_module(library(dcg/basics)).  121
  122pldoc_server(true).
  123:- else.  124
  125:- multifile
  126    http:location/3.  127
  128http:location(pldoc_resource, '/pldoc/res', []).
  129
  130pldoc_server(false).
  131:- endif.  132
  133:- use_module(library(lists)).  134:- use_module(library(option)).  135:- use_module(library(uri)).  136:- use_module(library(readutil)).  137:- use_module(library(http/html_write)).  138:- use_module(library(http/http_path)).  139:- use_module(library(http/html_head)).  140:- use_module(library(http/term_html)).  141:- use_module(library(debug)).  142:- use_module(library(apply)).  143:- use_module(library(pairs)).  144:- use_module(library(filesex)).  145:- use_module(doc_process).  146:- use_module(doc_man).  147:- use_module(doc_modes).  148:- use_module(doc_wiki).  149:- use_module(doc_search).  150:- use_module(doc_index).  151:- use_module(doc_util).  152:- use_module(library(solution_sequences)).  153:- use_module(library(error)).  154:- use_module(library(occurs)).  155:- use_module(library(prolog_source)).  156:- use_module(library(prolog_xref)).  157
  158:- include(hooks).
  159
  160
  161/** <module> PlDoc HTML backend
  162
  163This  module  translates  the  Herbrand   term  from  the  documentation
  164extracting module doc_wiki.pl into HTML+CSS.
  165
  166@tbd    Split put generation from computation as computation is reusable
  167	in other backends.
  168*/
  169
  170:- public
  171    args//1,                        % Called from \Term output created
  172    pred_dt//3,                     % by the wiki renderer
  173    section//2,
  174    tag//2.  175
  176
  177:- predicate_options(doc_for_wiki_file/2, 2,
  178		     [ edit(boolean)
  179		     ]).  180:- predicate_options(doc_hide_private/3, 3,
  181		     [module(atom), public(list), public_only(boolean)]).  182:- predicate_options(edit_button//2, 2,
  183		     [ edit(boolean)
  184		     ]).  185:- predicate_options(file//2, 2,
  186		     [ label(any),
  187		       absolute_path(atom),
  188		       href(atom),
  189		       map_extension(list),
  190		       files(list),
  191		       edit_handler(atom)
  192		     ]).  193:- predicate_options(file_header//2, 2,
  194		     [ edit(boolean),
  195		       files(list),
  196		       public_only(boolean)
  197		     ]).  198:- predicate_options(include//3, 3,
  199		     [ absolute_path(atom),
  200		       class(atom),
  201		       files(list),
  202		       href(atom),
  203		       label(any),
  204		       map_extension(list)
  205		     ]).  206:- predicate_options(object_edit_button//2, 2,
  207		     [ edit(boolean),
  208		       pass_to(pred_edit_button//2, 2)
  209		     ]).  210:- predicate_options(object_page//2, 2,
  211		     [ for(any),
  212		       header(boolean),
  213		       links(boolean),
  214		       no_manual(boolean),
  215		       try_manual(boolean),
  216		       search_in(oneof([all,app,man])),
  217		       search_match(oneof([name,summary])),
  218		       search_options(boolean)
  219		     ]).  220:- predicate_options(object_ref//2, 2,
  221		     [ files(list),
  222		       qualify(boolean),
  223		       style(oneof([number,title,number_title])),
  224		       secref_style(oneof([number,title,number_title]))
  225		     ]).  226:- predicate_options(object_synopsis//2, 2,
  227		     [ href(atom)
  228		     ]).  229:- predicate_options(pred_dt//3, 3,
  230		     [ edit(boolean)
  231		     ]).  232:- predicate_options(pred_edit_button//2, 2,
  233		     [ edit(boolean)
  234		     ]).  235:- predicate_options(predref//2, 2,
  236		     [ files(list),
  237		       prefer(oneof([manual,app])),
  238		       pass_to(object_ref/4, 2)
  239		     ]).  240:- predicate_options(private/2, 2,
  241		     [ module(atom),
  242		       public(list)
  243		     ]).  244:- predicate_options(source_button//2, 2,
  245		     [ files(list)
  246		     ]).  247
  248
  249		 /*******************************
  250		 *           RESOURCES          *
  251		 *******************************/
  252
  253:- if(pldoc_server(true)).  254:- html_resource(pldoc_css,
  255		 [ virtual(true),
  256		   requires([ pldoc_resource('pldoc.css')
  257			    ])
  258		 ]).  259:- html_resource(pldoc_resource('pldoc.js'),
  260		 [ requires([ jquery
  261			    ])
  262		 ]).  263:- html_resource(pldoc_js,
  264		 [ virtual(true),
  265		   requires([ pldoc_resource('pldoc.js')
  266			    ])
  267		 ]).  268:- html_resource(pldoc,
  269		 [ virtual(true),
  270		   requires([ pldoc_css,
  271			      pldoc_js
  272			    ])
  273		 ]).  274:- else.  275:- html_resource(pldoc_css, [virtual(true)]).  276:- html_resource(pldoc_resource('pldoc.js'), [virtual(true)]).  277:- html_resource(pldoc_js, [virtual(true)]).  278:- html_resource(pldoc, [virtual(true)]).  279:- endif.  280
  281
  282		 /*******************************
  283		 *       FILE PROCESSING        *
  284		 *******************************/
  285
  286%!  doc_for_file(+File, +Options) is det
  287%
  288%   HTTP  handler  that  writes  documentation  for  File  as  HTML.
  289%   Options:
  290%
  291%           * public_only(+Bool)
  292%           If =true= (default), only emit documentation for
  293%           exported predicates.
  294%
  295%           * edit(Bool)
  296%           If =true=, provide edit buttons. Default, these buttons
  297%           are suppressed.
  298%
  299%           * title(+Title)
  300%           Specify the page title.  Default is the base name of the
  301%           file.
  302%
  303%   @param File     Prolog file specification or xref source id.
  304
  305doc_for_file(FileSpec, Options) :-
  306    doc_file_objects(FileSpec, File, Objects, FileOptions, Options),
  307    doc_file_title(File, Title, FileOptions, Options),
  308    doc_write_page(
  309	pldoc(file(File, Title)),
  310	title(Title),
  311	\prolog_file(File, Objects, FileOptions, Options),
  312	Options).
  313
  314doc_file_title(_, Title, _, Options) :-
  315    option(title(Title), Options),
  316    !.
  317doc_file_title(File, Title, FileOptions, _) :-
  318    memberchk(file(Title0, _Comment), FileOptions),
  319    !,
  320    file_base_name(File, Base),
  321    atomic_list_concat([Base, ' -- ', Title0], Title).
  322doc_file_title(File, Title, _, _) :-
  323    file_base_name(File, Title).
  324
  325:- html_meta doc_write_page(+, html, html, +).  326
  327doc_write_page(Style, Head, Body, Options) :-
  328    option(files(_), Options),
  329    !,
  330    phrase(page(Style, Head, Body), HTML),
  331    print_html(HTML).
  332doc_write_page(Style, Head, Body, _) :-
  333    reply_html_page(Style, Head, Body).
  334
  335
  336prolog_file(File, Objects, FileOptions, Options) -->
  337    { b_setval(pldoc_file, File),   % TBD: delete?
  338      file_directory_name(File, Dir)
  339    },
  340    html([ \doc_resources(Options),
  341	   \doc_links(Dir, FileOptions),
  342	   \file_header(File, FileOptions)
  343	 | \objects(Objects, FileOptions)
  344	 ]),
  345    undocumented(File, Objects, FileOptions).
  346
  347%!  doc_resources(+Options)// is det.
  348%
  349%   Include required resources (CSS, JS) into  the output. The first
  350%   clause supports doc_files.pl. A bit hacky ...
  351
  352doc_resources(Options) -->
  353    { option(resource_directory(ResDir), Options),
  354      nb_current(pldoc_output, OutputFile),
  355      !,
  356      directory_file_path(ResDir, 'pldoc.css', Res),
  357      relative_file_name(Res, OutputFile, Ref)
  358    },
  359    html_requires(Ref).
  360doc_resources(Options) -->
  361    { option(html_resources(Resoures), Options, pldoc)
  362    },
  363    html_requires(Resoures).
  364
  365
  366%!  doc_file_objects(+FileSpec, -File, -Objects, -FileOptions, +Options) is det.
  367%
  368%   Extracts  relevant  information  for  FileSpec  from  the  PlDoc
  369%   database.  FileOptions contains:
  370%
  371%           * file(Title:string, Comment:string)
  372%           * module(Module:atom)
  373%           * public(Public:list(predicate_indicator)
  374%
  375%   Objects contains
  376%
  377%           * doc(PI:predicate_indicator, File:Line, Comment)
  378%
  379%   We distinguish three different states for FileSpec:
  380%
  381%     1. File was cross-referenced with collection enabled.  All
  382%        information is in the xref database.
  383%     2. File was loaded. If comments are not loaded,
  384%        cross-reference the file, while _storing_ the comments
  385%        as the compiler would do.
  386%     3. Neither of the above.  In this case we cross-reference the
  387%        file.
  388%
  389%   @param FileSpec File specification as used for load_files/2.
  390%   @param File     Prolog canonical filename
  391
  392doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  393    xref_current_source(FileSpec),
  394    xref_option(FileSpec, comments(collect)),
  395    !,
  396    File = FileSpec,
  397    findall(Object, xref_doc_object(File, Object), Objects0),
  398    reply_file_objects(File, Objects0, Objects, FileOptions, Options).
  399doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  400    absolute_file_name(FileSpec, File,
  401		       [ file_type(prolog),
  402			 access(read)
  403		       ]),
  404    source_file(File),
  405    !,
  406    ensure_doc_objects(File),
  407    Pos = File:Line,
  408    findall(Line-doc(Obj,Pos,Comment),
  409	    doc_comment(Obj, Pos, _, Comment), Pairs),
  410    sort(Pairs, Pairs1),            % remove duplicates
  411    keysort(Pairs1, ByLine),
  412    pairs_values(ByLine, Objs0),
  413    reply_file_objects(File, Objs0, Objects, FileOptions, Options).
  414doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  415    absolute_file_name(FileSpec, File,
  416		       [ file_type(prolog),
  417			 access(read)
  418		       ]),
  419    xref_source(File, [silent(true)]),
  420    findall(Object, xref_doc_object(File, Object), Objects0),
  421    reply_file_objects(File, Objects0, Objects, FileOptions, Options).
  422
  423
  424reply_file_objects(File, Objs0, Objects, FileOptions, Options) :-
  425    module_info(File, ModuleOptions, Options),
  426    file_info(Objs0, Objs1, FileOptions, ModuleOptions),
  427    doc_hide_private(Objs1, ObjectsSelf, ModuleOptions),
  428    include_reexported(ObjectsSelf, Objects1, File, FileOptions),
  429    remove_doc_duplicates(Objects1, Objects, []).
  430
  431remove_doc_duplicates([], [], _).
  432remove_doc_duplicates([H|T0], [H|T], Seen) :-
  433    H = doc(_, _, Comment),
  434    \+ memberchk(Comment, Seen),
  435    !,
  436    remove_doc_duplicates(T0, T, [Comment|Seen]).
  437remove_doc_duplicates([_|T0], T, Seen) :-
  438    remove_doc_duplicates(T0, T, Seen).
  439
  440include_reexported(SelfObjects, Objects, File, Options) :-
  441    option(include_reexported(true), Options),
  442    option(module(Module), Options),
  443    option(public(Exports), Options),
  444    select_undocumented(Exports, Module, SelfObjects, Undoc),
  445    re_exported_doc(Undoc, File, Module, REObjs, _),
  446    REObjs \== [],
  447    !,
  448    append(SelfObjects, REObjs, Objects).
  449include_reexported(Objects, Objects, _, _).
  450
  451
  452%!  xref_doc_object(File, DocObject) is nondet.
  453
  454xref_doc_object(File, doc(M:module(Title),File:0,Comment)) :-
  455    xref_comment(File, Title, Comment),
  456    xref_module(File, M).
  457xref_doc_object(File, doc(M:Name/Arity,File:0,Comment)) :-
  458    xref_comment(File, Head, _Summary, Comment),
  459    xref_module(File, Module),
  460    strip_module(Module:Head, M, Plain),
  461    functor(Plain, Name, Arity).
  462xref_doc_object(File, doc(M:Object,File:0,Comment)) :-
  463    xref_object_comment(File, Object, _Summary, Comment),
  464    (   xref_module(File, M)
  465    ->  true
  466    ;   M = user
  467    ).
  468
  469%!  ensure_doc_objects(+File) is det.
  470%
  471%   Ensure we have documentation about File.  If we have no comments
  472%   for the file because it was loaded before comment collection was
  473%   enabled, run the cross-referencer on it  to collect the comments
  474%   and meta-information.
  475%
  476%   @param File is a canonical filename that is loaded.
  477
  478:- dynamic
  479    no_comments/2.  480
  481ensure_doc_objects(File) :-
  482    source_file(File),
  483    !,
  484    (   doc_file_has_comments(File)
  485    ->  true
  486    ;   no_comments(File, TimeChecked),
  487	time_file(File, TimeChecked)
  488    ->  true
  489    ;   xref_source(File, [silent(true), comments(store)]),
  490	retractall(no_comments(File, _)),
  491	(   doc_file_has_comments(File)
  492	->  true
  493	;   time_file(File, TimeChecked),
  494	    assertz(no_comments(File, TimeChecked))
  495	)
  496    ).
  497ensure_doc_objects(File) :-
  498    xref_source(File, [silent(true)]).
  499
  500%!  module_info(+File, -ModuleOptions, +OtherOptions) is det.
  501%
  502%   Add options module(Name),  public(Exports)   to  OtherOptions if
  503%   File is a module file.
  504
  505module_info(File, [module(Module), public(Exports)|Options], Options) :-
  506    module_property(Module, file(File)),
  507    !,
  508    module_property(Module, exports(Exports)).
  509module_info(File, [module(Module), public(Exports)|Options], Options) :-
  510    xref_module(File, Module),
  511    !,
  512    findall(PI, xref_exported_pi(File, PI), Exports).
  513module_info(_, Options, Options).
  514
  515xref_exported_pi(Src, Name/Arity) :-
  516    xref_exported(Src, Head),
  517    functor(Head, Name, Arity).
  518
  519%!  doc_hide_private(+Objs, +Public, +Options)
  520%
  521%   Remove the private objects from Objs according to Options.
  522
  523doc_hide_private(Objs, Objs, Options) :-
  524    option(public_only(false), Options, true),
  525    !.
  526doc_hide_private(Objs0, Objs, Options) :-
  527    hide_private(Objs0, Objs, Options).
  528
  529hide_private([], [], _).
  530hide_private([H|T0], T, Options) :-
  531    obj(H, Obj),
  532    private(Obj, Options),
  533    !,
  534    hide_private(T0, T, Options).
  535hide_private([H|T0], [H|T], Options) :-
  536    hide_private(T0, T, Options).
  537
  538%!  obj(+Term, -Object) is det.
  539%
  540%   Extract the documented  object  from   its  environment.  It  is
  541%   assumed to be the first term. Note  that if multiple objects are
  542%   described by the same comment Term is a list.
  543
  544obj(doc(Obj0, _Pos, _Summary), Obj) :-
  545    !,
  546    (   Obj0 = [Obj|_]
  547    ->  true
  548    ;   Obj = Obj0
  549    ).
  550obj(Obj0, Obj) :-
  551    (   Obj0 = [Obj|_]
  552    ->  true
  553    ;   Obj = Obj0
  554    ).
  555
  556
  557%!  private(+Obj, +Options) is semidet.
  558%
  559%   True if Obj is not  exported   from  Options. This means Options
  560%   defined a module and Obj is  not   member  of the exports of the
  561%   module.
  562
  563:- multifile
  564    prolog:doc_is_public_object/1.  565
  566private(Object, _Options) :-
  567    prolog:doc_is_public_object(Object), !, fail.
  568private(Module:PI, Options) :-
  569    multifile(Module:PI, Options), !, fail.
  570private(Module:PI, Options) :-
  571    public(Module:PI, Options), !, fail.
  572private(Module:PI, Options) :-
  573    option(module(Module), Options),
  574    option(public(Public), Options),
  575    !,
  576    \+ ( member(PI2, Public),
  577	 eq_pi(PI, PI2)
  578       ).
  579private(Module:PI, _Options) :-
  580    module_property(Module, file(_)),      % A loaded module
  581    !,
  582    module_property(Module, exports(Exports)),
  583    \+ ( member(PI2, Exports),
  584	 eq_pi(PI, PI2)
  585       ).
  586private(Module:PI, _Options) :-
  587    \+ (pi_to_head(PI, Head),
  588	xref_exported(Source, Head),
  589	xref_module(Source, Module)).
  590
  591%!  prolog:doc_is_public_object(+Object) is semidet.
  592%
  593%   Hook that allows objects  to  be   displayed  with  the  default
  594%   public-only view.
  595
  596%!  multifile(+Obj, +Options) is semidet.
  597%
  598%   True if Obj is a multifile predicate.
  599
  600multifile(Obj, _Options) :-
  601    strip_module(user:Obj, Module, PI),
  602    pi_to_head(PI, Head),
  603    (   predicate_property(Module:Head, multifile)
  604    ;   xref_module(Source, Module),
  605	xref_defined(Source, Head, multifile(_Line))
  606    ),
  607    !.
  608
  609%!  public(+Options, +Options)
  610%
  611%   True if Obj is declared using public/1.
  612
  613public(Obj, _Options) :-
  614    strip_module(user:Obj, Module, PI),
  615    pi_to_head(PI, Head),
  616    (   predicate_property(Module:Head, public)
  617    ;   xref_module(Source, Module),
  618	xref_defined(Source, Head, public(_Line))
  619    ),
  620    !.
  621
  622pi_to_head(Var, _) :-
  623    var(Var), !, fail.
  624pi_to_head(Name/Arity, Term) :-
  625    functor(Term, Name, Arity).
  626pi_to_head(Name//DCGArity, Term) :-
  627    Arity is DCGArity+2,
  628    functor(Term, Name, Arity).
  629
  630%!  file_info(+Comments, -RestComment, -FileOptions, +OtherOptions) is det.
  631%
  632%   Add options file(Title, Comment) to OtherOptions if available.
  633
  634file_info(Comments, RestComments, [file(Title, Comment)|Opts], Opts) :-
  635    select(doc(_:module(Title),_,Comment), Comments, RestComments),
  636    !.
  637file_info(Comments, Comments, Opts, Opts).
  638
  639
  640%!  file_header(+File, +Options)// is det.
  641%
  642%   Create the file header.
  643
  644file_header(File, Options) -->
  645    { memberchk(file(Title, Comment), Options),
  646      !,
  647      file_base_name(File, Base)
  648    },
  649    file_title([Base, ' -- ', Title], File, Options),
  650    { is_structured_comment(Comment, Prefixes),
  651      string_codes(Comment, Codes),
  652      indented_lines(Codes, Prefixes, Lines),
  653      section_comment_header(Lines, _Header, Lines1),
  654      wiki_lines_to_dom(Lines1, [], DOM)
  655    },
  656    html(DOM).
  657file_header(File, Options) -->
  658    { file_base_name(File, Base)
  659    },
  660    file_title([Base], File, Options).
  661
  662
  663%!  file_title(+Title:list, +File, +Options)// is det
  664%
  665%   Emit the file-header and manipulation buttons.
  666
  667file_title(Title, File, Options) -->
  668    prolog:doc_file_title(Title, File, Options),
  669    !.
  670file_title(Title, File, Options) -->
  671    { file_base_name(File, Base)
  672    },
  673    html(h1(class(file),
  674	    [ span(style('float:right'),
  675		   [ \reload_button(File, Base, Options),
  676		     \zoom_button(Base, Options),
  677		     \source_button(Base, Options),
  678		     \edit_button(File, Options)
  679		   ])
  680	    | Title
  681	    ])).
  682
  683
  684%!  reload_button(+File, +Base, +Options)// is det.
  685%
  686%   Create a button for  reloading  the   sources  and  updating the
  687%   documentation page. Note that the  button   is  not shown if the
  688%   file is not loaded because we do  not want to load files through
  689%   the documentation system.
  690
  691reload_button(File, _Base, Options) -->
  692    { \+ source_file(File),
  693      \+ option(files(_), Options)
  694    },
  695    !,
  696    html(span(class(file_anot), '[not loaded]')).
  697reload_button(_File, Base, Options) -->
  698    { option(edit(true), Options),
  699      !,
  700      option(public_only(Public), Options, true)
  701    },
  702    html(a(href(Base+[reload(true), public_only(Public)]),
  703	   img([ class(action),
  704		 alt('Reload'),
  705		 title('Make & Reload'),
  706		 src(location_by_id(pldoc_resource)+'reload.png')
  707	       ]))).
  708reload_button(_, _, _) --> [].
  709
  710%!  edit_button(+File, +Options)// is det.
  711%
  712%   Create an edit button  for  File.   If  the  button  is clicked,
  713%   JavaScript sends a message to the   server without modifying the
  714%   current page.  JavaScript code is in the file pldoc.js.
  715
  716edit_button(File, Options) -->
  717    { option(edit(true), Options)
  718    },
  719    !,
  720    html(a([ onClick('HTTPrequest(\'' +
  721		     location_by_id(pldoc_edit) + [file(File)] +
  722		     '\')')
  723	   ],
  724	   img([ class(action),
  725		 alt(edit),
  726		 title('Edit file'),
  727		 src(location_by_id(pldoc_resource)+'edit.png')
  728	     ]))).
  729edit_button(_, _) -->
  730    [].
  731
  732
  733%!  zoom_button(BaseName, +Options)// is det.
  734%
  735%   Add zoom in/out button to show/hide the private documentation.
  736
  737zoom_button(_, Options) -->
  738    { option(files(_Map), Options) },
  739    !.    % generating files
  740zoom_button(Base, Options) -->
  741    {   (   option(public_only(true), Options, true)
  742	->  Zoom = 'public.png',
  743	    Alt = 'Public',
  744	    Title = 'Click to include private',
  745	    PublicOnly = false
  746	;   Zoom = 'private.png',
  747	    Alt = 'All predicates',
  748	    Title = 'Click to show exports only',
  749	    PublicOnly = true
  750	)
  751    },
  752    html(a(href(Base+[public_only(PublicOnly)]),
  753	   img([ class(action),
  754		 alt(Alt),
  755		 title(Title),
  756		 src(location_by_id(pldoc_resource)+Zoom)
  757	       ]))).
  758
  759
  760%!  source_button(+File, +Options)// is det.
  761%
  762%   Add show-source button.
  763
  764source_button(_File, Options) -->
  765    { option(files(_Map), Options) },
  766    !.    % generating files
  767source_button(File, _Options) -->
  768    { (   is_absolute_file_name(File)
  769      ->  doc_file_href(File, HREF0)
  770      ;   HREF0 = File
  771      )
  772    },
  773    html(a(href(HREF0+[show(src)]),
  774	   img([ class(action),
  775		 alt('Show source'),
  776		 title('Show source'),
  777		 src(location_by_id(pldoc_resource)+'source.png')
  778	       ]))).
  779
  780
  781%!  objects(+Objects:list, +Options)// is det.
  782%
  783%   Emit the documentation body.  Options includes:
  784%
  785%     * navtree(+Boolean)
  786%     If =true=, provide a navitation tree.
  787
  788objects(Objects, Options) -->
  789    { option(navtree(true), Options),
  790      !,
  791      objects_nav_tree(Objects, Tree)
  792    },
  793    html([ div(class(navtree),
  794	       div(class(navwindow),
  795		   \nav_tree(Tree, Objects, Options))),
  796	   div(class(navcontent),
  797	       \objects_nt(Objects, Options))
  798	 ]).
  799objects(Objects, Options) -->
  800    objects_nt(Objects, Options).
  801
  802objects_nt(Objects, Options) -->
  803    objects(Objects, [body], Options).
  804
  805objects([], Mode, _) -->
  806    pop_mode(body, Mode, _).
  807objects([Obj|T], Mode, Options) -->
  808    object(Obj, Mode, Mode1, Options),
  809    objects(T, Mode1, Options).
  810
  811%!  object(+Spec, +ModeIn, -ModeOut, +Options)// is det.
  812%
  813%   Emit the documentation of a single object.
  814%
  815%   @param  Spec is one of doc(Obj,Pos,Comment), which is used
  816%           to list the objects documented in a file or a plain
  817%           Obj, used for documenting the object regardless of
  818%           its location.
  819
  820object(doc(Obj,Pos,Comment), Mode0, Mode, Options) -->
  821    !,
  822    object(Obj, [Pos-Comment], Mode0, Mode, [scope(file)|Options]).
  823object(Obj, Mode0, Mode, Options) -->
  824    { findall(Pos-Comment,
  825	      doc_comment(Obj, Pos, _Summary, Comment),
  826	      Pairs)
  827    },
  828    !,
  829    { b_setval(pldoc_object, Obj) },
  830    object(Obj, Pairs, Mode0, Mode, Options).
  831
  832object(Obj, Pairs, Mode0, Mode, Options) -->
  833    { is_pi(Obj),
  834      !,
  835      maplist(pred_dom(Obj, Options), Pairs, DOMS),
  836      append(DOMS, DOM)
  837    },
  838    need_mode(dl, Mode0, Mode),
  839    html(DOM).
  840object([Obj|_Same], Pairs, Mode0, Mode, Options) -->
  841    !,
  842    object(Obj, Pairs, Mode0, Mode, Options).
  843object(Obj, Pairs, Mode0, Mode, Options) -->
  844    { phrase(prolog:doc_object(Obj, Pairs, Options), Tokens) },
  845    !,
  846    need_mode(body, Mode0, Mode),
  847    tokens(Tokens).
  848object(Obj, _Pairs, Mode, Mode, _Options) -->
  849    { debug(pldoc, 'Skipped ~p', [Obj]) },
  850    [].
  851
  852%!  prolog:doc_object(+Object, +Pairs, +Options)// is semidet.
  853%
  854%   Hook to emit the documentation of an  Object that is not a predicate,
  855%   e.g., the members of an xpce class.  Pairs is a list Pos-Comment for
  856%   the comments that document Object.  The output is emitted outside
  857%   the description list of the predicates.
  858
  859%   tokens(+Tokens)// emits a list of HTML tokens.
  860
  861tokens(Tokens, List, Tail) :-
  862    append(Tokens, Tail, List).
  863
  864pred_dom(Obj, Options, Pos-Comment, DOM) :-
  865    is_structured_comment(Comment, Prefixes),
  866    string_codes(Comment, Codes),
  867    indented_lines(Codes, Prefixes, Lines),
  868    strip_module(user:Obj, Module, _),
  869    process_modes(Lines, Module, Pos, Modes, Args, Lines1),
  870    (   private(Obj, Options)
  871    ->  Class = privdef             % private definition
  872    ;   multifile(Obj, Options)
  873    ->  (   option(scope(file), Options)
  874	->  (   more_doc(Obj, Pos)
  875	    ->  Class = multidef(object(Obj))
  876	    ;   Class = multidef
  877	    )
  878	;   Class = multidef(file((Pos)))
  879	)
  880    ;   public(Obj, Options)
  881    ->  Class = publicdef           % :- public definition
  882    ;   Class = pubdef              % exported definition
  883    ),
  884    (   Obj = Module:_
  885    ->  POptions = [module(Module)|Options]
  886    ;   POptions = Options
  887    ),
  888    Pos = File:Line,
  889    DTOptions = [file(File),line(Line)|POptions],
  890    DOM = [\pred_dt(Modes, Class, DTOptions), dd(class=defbody, DOM1)],
  891    wiki_lines_to_dom(Lines1, Args, DOM0),
  892    strip_leading_par(DOM0, DOM1).
  893
  894more_doc(Obj, File:_) :-
  895    doc_comment(Obj, File2:_, _, _),
  896    File2 \== File,
  897    !.
  898
  899%!  need_mode(+Mode:atom, +Stack:list, -NewStack:list)// is det.
  900%
  901%   While predicates are part of a   description  list, sections are
  902%   not and we therefore  need  to   insert  <dl>...</dl>  into  the
  903%   output. We do so by demanding  an outer environment and push/pop
  904%   the required elements.
  905
  906need_mode(Mode, Stack, Stack) -->
  907    { Stack = [Mode|_] },
  908    !,
  909    [].
  910need_mode(Mode, Stack, Rest) -->
  911    { memberchk(Mode, Stack)
  912    },
  913    !,
  914    pop_mode(Mode, Stack, Rest).
  915need_mode(Mode, Stack, [Mode|Stack]) -->
  916    !,
  917    html_begin(Mode).
  918
  919pop_mode(Mode, Stack, Stack) -->
  920    { Stack = [Mode|_] },
  921    !,
  922    [].
  923pop_mode(Mode, [H|Rest0], Rest) -->
  924    html_end(H),
  925    pop_mode(Mode, Rest0, Rest).
  926
  927%!  undocumented(+File, +Objects, +Options)// is det.
  928%
  929%   Describe undocumented predicates if the file is a module file.
  930
  931undocumented(File, Objs, Options) -->
  932    { option(module(Module), Options),
  933      option(public(Exports), Options),
  934      select_undocumented(Exports, Module, Objs, Undoc),
  935      re_exported_doc(Undoc, File, Module, UREObjs, ReallyUnDoc),
  936      sort(2, @=<, UREObjs, REObjs) % UREObjs = doc(PI,Pos,Comment)
  937                                    % i.e., sort on Pos
  938    },
  939    !,
  940    re_exported_doc(REObjs, Options),
  941    undocumented(ReallyUnDoc, Options).
  942undocumented(_, _, _) -->
  943    [].
  944
  945re_exported_doc([], _) --> !.
  946re_exported_doc(Objs, Options) -->
  947    reexport_header(Objs, Options),
  948    objects(Objs, Options).
  949
  950reexport_header(_, Options) -->
  951    { option(reexport_header(true), Options, true)
  952    },
  953    !,
  954    html([ h2(class(wiki), 'Re-exported predicates'),
  955	   p([ "The following predicates are exported from this file \c
  956                while their implementation is defined in imported modules \c
  957                or non-module files loaded by this module."
  958	     ])
  959	 ]).
  960reexport_header(_, _) -->
  961    [].
  962
  963undocumented([], _) --> !.
  964undocumented(UnDoc, Options) -->
  965    html([ h2(class(undoc), 'Undocumented predicates'),
  966	   p(['The following predicates are exported, but not ',
  967	      'or incorrectly documented.'
  968	     ]),
  969	   dl(class(undoc),
  970	      \undocumented_predicates(UnDoc, Options))
  971	 ]).
  972
  973
  974undocumented_predicates([], _) -->
  975    [].
  976undocumented_predicates([H|T], Options) -->
  977    undocumented_pred(H, Options),
  978    undocumented_predicates(T, Options).
  979
  980undocumented_pred(Name/Arity, Options) -->
  981    { functor(Head, Name, Arity) },
  982    html(dt(class=undoc, \pred_mode(Head, [], _, Options))).
  983
  984select_undocumented([], _, _, []).
  985select_undocumented([PI|T0], M, Objs, [PI|T]) :-
  986    is_pi(PI),
  987    \+ in_doc(M:PI, Objs),
  988    !,
  989    select_undocumented(T0, M, Objs, T).
  990select_undocumented([_|T0], M, Objs, T) :-
  991    select_undocumented(T0, M, Objs, T).
  992
  993in_doc(PI, Objs) :-
  994    member(doc(O,_,_), Objs),
  995    (   is_list(O)
  996    ->  member(O2, O),
  997	eq_pi(PI, O2)
  998    ;   eq_pi(PI, O)
  999    ).
 1000
 1001
 1002%!  eq_pi(PI1, PI2) is semidet.
 1003%
 1004%   True if PI1 and PI2 refer to the same predicate.
 1005
 1006eq_pi(PI, PI) :- !.
 1007eq_pi(M:PI1, M:PI2) :-
 1008    atom(M),
 1009    !,
 1010    eq_pi(PI1, PI2).
 1011eq_pi(Name/A, Name//DCGA) :-
 1012    A =:= DCGA+2,
 1013    !.
 1014eq_pi(Name//DCGA, Name/A) :-
 1015    A =:= DCGA+2.
 1016
 1017%!  is_pi(@Term) is semidet.
 1018%
 1019%   True if Term is a predicate indicator.
 1020
 1021is_pi(Var) :-
 1022    var(Var),
 1023    !,
 1024    fail.
 1025is_pi(_:PI) :-
 1026    !,
 1027    is_pi(PI).
 1028is_pi(_/_).
 1029is_pi(_//_).
 1030
 1031
 1032%!  re_exported_doc(+Undoc:list(pi), +File:atom, +Module:atom,
 1033%!                  -ImportedDoc, -ReallyUnDoc:list(pi))
 1034
 1035re_exported_doc([], _, _, [], []).
 1036re_exported_doc([PI|T0], File, Module, [doc(Orig:PI,Pos,Comment)|ObjT], UnDoc) :-
 1037    pi_to_head(PI, Head),
 1038    (   predicate_property(Module:Head, imported_from(Orig))
 1039    ->  true
 1040    ;   predicate_property(Module:Head, exported)
 1041    ->  Orig = Module
 1042    ;   xref_defined(File, Head, imported(File2)),
 1043	ensure_doc_objects(File2),
 1044	xref_module(File2, Orig)
 1045    ),
 1046    doc_comment(Orig:PI, Pos, _, Comment),
 1047    !,
 1048    re_exported_doc(T0, File, Module, ObjT, UnDoc).
 1049re_exported_doc([PI|T0], File, Module, REObj, [PI|UnDoc]) :-
 1050    re_exported_doc(T0, File, Module, REObj, UnDoc).
 1051
 1052
 1053		 /*******************************
 1054		 *      SINGLE OBJECT PAGE      *
 1055		 *******************************/
 1056
 1057%!  object_page(+Obj, +Options)// is semidet.
 1058%
 1059%   Generate an HTML page describing Obj.  The top presents the file
 1060%   the object is documented in and a search-form.  Options:
 1061%
 1062%       * header(+Boolean)
 1063%       Show the navigation and search header.
 1064
 1065object_page(Obj, Options) -->
 1066    prolog:doc_object_page(Obj, Options),
 1067    !,
 1068    object_page_footer(Obj, Options).
 1069object_page(Obj, Options) -->
 1070    { doc_comment(Obj, File:_Line, _Summary, _Comment)
 1071    },
 1072    !,
 1073    (   { \+ ( doc_comment(Obj, File2:_, _, _),
 1074	       File2 \== File )
 1075	}
 1076    ->  html([ \html_requires(pldoc),
 1077	       \object_page_header(File, Options),
 1078	       \object_synopsis(Obj, []),
 1079	       \objects([Obj], Options)
 1080	     ])
 1081    ;   html([ \html_requires(pldoc),
 1082	       \object_page_header(-, Options),
 1083	       \objects([Obj], [synopsis(true)|Options])
 1084	     ])
 1085    ),
 1086    object_page_footer(Obj, Options).
 1087object_page(M:Name/Arity, Options) -->          % specified module, but public
 1088    { functor(Head, Name, Arity),
 1089      (   predicate_property(M:Head, exported)
 1090      ->  module_property(M, class(library))
 1091      ;   \+ predicate_property(M:Head, defined)
 1092      )
 1093    },
 1094    prolog:doc_object_page(Name/Arity, Options),
 1095    !,
 1096    object_page_footer(Name/Arity, Options).
 1097
 1098object_page_header(File, Options) -->
 1099    prolog:doc_page_header(file(File), Options),
 1100    !.
 1101object_page_header(File, Options) -->
 1102    { option(header(true), Options, true) },
 1103    !,
 1104    html(div(class(navhdr),
 1105	     [ div(class(jump), \file_link(File)),
 1106	       div(class(search), \search_form(Options)),
 1107	       br(clear(right))
 1108	     ])).
 1109object_page_header(_, _) --> [].
 1110
 1111file_link(-) -->
 1112    !,
 1113    places_menu(-).
 1114file_link(File) -->
 1115    { file_directory_name(File, Dir)
 1116    },
 1117    places_menu(Dir),
 1118    html([ div(a(href(location_by_id(pldoc_doc)+File), File))
 1119	 ]).
 1120
 1121%!  object_footer(+Obj, +Options)// is det.
 1122%
 1123%   Call the hook prolog:doc_object_footer//2.  This hook is used by
 1124%   the examples machinery.  When the changelog support is installed
 1125%   (cmake -DCHANGELOG=ON), a small block listing introduction and
 1126%   last change is appended after the hook output — see
 1127%   object_changelog//2.
 1128
 1129object_footer(Obj, Options) -->
 1130    object_changelog(Obj, Options),
 1131    (   prolog:doc_object_footer(Obj, Options)
 1132    ->  []
 1133    ;   []
 1134    ).
 1135
 1136%!  object_changelog(+Obj, +Options)// is det.
 1137%
 1138%   Emit a small footer noting when Obj (a Name/Arity, possibly module-
 1139%   or DCG-qualified, or a list of such, or `f(...)`) was introduced
 1140%   and when it was last changed.  Empty when no events are known or
 1141%   when the changelog support was not built (CHANGELOG cmake option
 1142%   off).  Each version is rendered as `Maj.Min.Patch` and linked to
 1143%   the underlying commit on GitHub.
 1144
 1145:- if(exists_source(library(pldoc/doc_changes))). 1146:- use_module(library(pldoc/doc_changes),
 1147              [doc_introduced/3, doc_last_changed/4]). 1148
 1149object_changelog(Objs, Options) -->
 1150    { is_list(Objs), !,
 1151      objs_pis(Objs, PIs)
 1152    },
 1153    object_changelog_pis(PIs, Options).
 1154object_changelog(Obj, Options) -->
 1155    { objs_pis([Obj], PIs)
 1156    },
 1157    object_changelog_pis(PIs, Options).
 1158
 1159object_changelog_pis(PIs, _Options) -->
 1160    { changelog_union(PIs, Events),
 1161      Events \== []
 1162    },
 1163    !,
 1164    html(div(class('pldoc-changes'),
 1165             [ div(class('pldoc-changes-header'), "History"),
 1166               ul(\changelog_events(Events)),
 1167               div(class('pldoc-changes-footer'),
 1168                   "Disclaimer: heuristically mined from GIT")
 1169             ])).
 1170object_changelog_pis(_, _) --> [].
 1171
 1172%!  objs_pis(+Objs, -PIs) is det.
 1173%
 1174%   Filter Objs (a list  of  documentation   objects)  to  the predicate
 1175%   indicators  stored  in  changelog_event/7.   Module  qualifiers  are
 1176%   stripped; DCG arity is folded to the underlying predicate arity (the
 1177%   extractor does not yet emit `//`   events  separately). Other object
 1178%   shapes (xpce(...), c(...), f(...),   library(...), section(...)) are
 1179%   dropped so the footer is suppressed for them.
 1180
 1181objs_pis(Objs, PIs) :-
 1182    convlist(obj_pi, Objs, PIs).
 1183
 1184obj_pi(_:O, PI) :- !, obj_pi(O, PI).
 1185obj_pi(Name/Arity,  Name/Arity)  :- atom(Name), integer(Arity).
 1186obj_pi(Name//Arity, Name/PArity) :-
 1187    atom(Name), integer(Arity),
 1188    PArity is Arity + 2.
 1189
 1190changelog_events(Events) -->
 1191    sequence(changelog_event, Events).
 1192
 1193changelog_event(Event) -->
 1194    html(li(\changelog_event_(Event))).
 1195
 1196changelog_event_(event(Type, Version, Hash, Subject, Repo)) -->
 1197    { clean_subject(Subject, Subject1)
 1198    },
 1199    html([ \event_type(Type), " in ",
 1200           \version_commit_link(Version, Hash, Repo),
 1201           " ", em(Subject1)
 1202         ]).
 1203
 1204clean_subject(Subject0, Subject) :-
 1205    string_codes(Subject0, Codes),
 1206    phrase((tag, string(Msg)), Codes),
 1207    !,
 1208    string_codes(Subject, Msg).
 1209clean_subject(Subject, Subject).
 1210
 1211tag -->
 1212    capitals, ":", whites.
 1213
 1214capitals -->
 1215    [C], { between(0'A, 0'Z, C) }, !,
 1216    capitals.
 1217capitals -->
 1218    [].
 1219
 1220event_type(introduced) ==> html(b(title("The predicate was added"),
 1221                                  "Introduced")).
 1222event_type(added)      ==> html(b(title("New functinality was added"),
 1223                                  "Extended")).
 1224event_type(enhanced)   ==> html(b(title("Implementation has been improved"),
 1225                                  "Enhanced")).
 1226event_type(fixed)      ==> html(b(title("A bug was fixed"),
 1227                                  "Fixed")).
 1228event_type(modified)   ==> html(b(title("The modification may break compatibility"),
 1229                                  "Modified")).
 1230event_type(Id)         ==> html(b(title("Unknow tag"),
 1231                                  Id)).
 1232
 1233%!  changelog_union_line(+PIs, -Line) is nondet.
 1234%
 1235%   Emit one paragraph (`Line`) for the introduction event (only if
 1236%   *every* PI in the block has an introduction event in the tracked
 1237%   range; otherwise the line is suppressed to avoid implying the whole
 1238%   family is newer than the older members really are), and one
 1239%   paragraph for the most recent change across any family member.
 1240
 1241changelog_union(PIs, [Event]) :-
 1242    all_introduced(PIs, Event).
 1243changelog_union(PIs, Events) :-
 1244    union_last_changed(PIs, Events).
 1245
 1246%!  all_introduced(+PIs, -Event) is semidet.
 1247%
 1248%   Succeeds when every PI has an introduction event in the tracked
 1249%   range.  Version is the highest such introduction version across
 1250%   the family — i.e. the version at which the family reached its
 1251%   currently-documented shape.  Hash and Repo identify a representative
 1252%   commit (the one at that highest version).
 1253
 1254all_introduced(PIs, Event) :-
 1255    maplist(any_introduction, PIs, Events),
 1256    E0 = event(introduced, V0, _, _, _),
 1257    aggregate_all(max(V0, E0),
 1258                  member(E0, Events),
 1259                  max(_, Event)).
 1260
 1261any_introduction(PI, Event) :-
 1262    doc_introduced(PI, _, Event), !.
 1263
 1264%!  union_last_changed(+PIs, -Events:list) is det.
 1265%
 1266%   Most recent change event across PIs, ranked by version.  Fails if
 1267%   no PI has a change event in the tracked range.
 1268
 1269union_last_changed(PIs, Events) :-
 1270    Ev = event(T0, V0, _H0, _S0, _R0),
 1271    findall(Ev,
 1272            ( member(PI, PIs),
 1273              doc_last_changed(PI, V0, T0, Ev)
 1274            ),
 1275            Events0),
 1276    sort(2, >=, Events0, Events).
 1277
 1278version_commit_link(V, Hash, Repo) -->
 1279    { version_string(V, VStr),
 1280      short_hash(Hash, Short),
 1281      commit_url(Repo, Hash, URL)
 1282    },
 1283    html([ b(VStr), ' (👉',
 1284           a([href(URL), target('_blank')], Short),
 1285           ')'
 1286         ]).
 1287
 1288version_string(N, VStr) :-
 1289    Major is N // 10000,
 1290    Minor is (N // 100) mod 100,
 1291    Patch is N mod 100,
 1292    format(string(VStr), "~d.~d.~d", [Major, Minor, Patch]).
 1293
 1294short_hash(Hash, Short) :-
 1295    string(Hash),
 1296    !,
 1297    sub_string(Hash, 0, 7, _, Short).
 1298short_hash(Hash, Short) :-
 1299    sub_atom(Hash, 0, 7, _, Short),
 1300    !.
 1301short_hash(Hash, Hash).
 1302
 1303commit_url(Repo, Hash, URL) :-
 1304    atom(Repo), Repo \== unknown,
 1305    !,
 1306    format(atom(URL), "https://github.com/~w/commit/~w", [Repo, Hash]).
 1307commit_url(_, Hash, URL) :-
 1308    format(atom(URL), "#~w", [Hash]).
 1309:- else.	% library(pldoc/doc_changes) is missing
 1310object_changelog(_, _) --> [].
 1311:- endif. 1312
 1313
 1314%!  object_page_footer(+Obj, +Options)// is det.
 1315%
 1316%   Call the hook prolog:doc_object_page_footer//2. This hook will
 1317%   be used to deal with annotations.
 1318
 1319object_page_footer(Obj, Options) -->
 1320    prolog:doc_object_page_footer(Obj, Options),
 1321    !.
 1322object_page_footer(_, _) --> [].
 1323
 1324
 1325%!  object_synopsis(Obj, Options)// is det.
 1326%
 1327%   Provide additional information  about  Obj.   Note  that  due to
 1328%   reexport facilities, predicates may be   available from multiple
 1329%   modules.
 1330%
 1331%   @tbd Currently we provide a synopsis   for the one where the
 1332%   definition resides. This is not   always  correct. Notably there
 1333%   are cases where multiple implementation modules are bundled in a
 1334%   larger interface that is the `preferred' module.
 1335
 1336object_synopsis(Name/Arity, _) -->
 1337    { functor(Head, Name, Arity),
 1338      predicate_property(system:Head, built_in)
 1339    },
 1340    synopsis([span(class(builtin), 'built-in')]).
 1341object_synopsis(Name/Arity, Options) -->
 1342    !,
 1343    object_synopsis(_:Name/Arity, Options).
 1344object_synopsis(M:Name/Arity, Options) -->
 1345    { functor(Head, Name, Arity),
 1346      (   option(source(Spec), Options)
 1347      ->  absolute_file_name(Spec, File,
 1348			     [ access(read),
 1349			       file_type(prolog),
 1350			       file_errors(fail)
 1351			     ])
 1352      ;   predicate_property(M:Head, exported),
 1353	  \+ predicate_property(M:Head, imported_from(_)),
 1354	  module_property(M, file(File)),
 1355	  file_name_on_path(File, Spec)
 1356      ),
 1357      !,
 1358      unquote_filespec(Spec, Unquoted0),
 1359      (   prolog:pldoc_synopsis_spec(Unquoted0, Unquoted)
 1360      ->  true
 1361      ;   Unquoted = Unquoted0
 1362      )
 1363    },
 1364    use_module_synopsis(Head, File, Unquoted, Options).
 1365object_synopsis(Name//Arity, Options) -->
 1366    !,
 1367    { DCGArity is Arity+2 },
 1368    object_synopsis(Name/DCGArity, Options).
 1369object_synopsis(Module:Name//Arity, Options) -->
 1370    !,
 1371    { DCGArity is Arity+2 },
 1372    object_synopsis(Module:Name/DCGArity, Options).
 1373object_synopsis(f(_/_), _) -->
 1374    synopsis(span(class(function),
 1375		  [ 'Arithmetic function (see ',
 1376		    \object_ref(is/2, []),
 1377		    ')'
 1378		  ])).
 1379object_synopsis(c(Func), _) -->
 1380    {   sub_atom(Func, 0, _, _, 'PL_')
 1381    ;   sub_atom(Func, 0, _, _, 'S')
 1382    },
 1383    !,
 1384    synopsis([span(class(cfunc), 'C-language interface function')]).
 1385object_synopsis(_, _) --> [].
 1386
 1387:- html_meta(synopsis(html,?,?)). 1388
 1389use_module_synopsis(Head, File, Unquoted, Options) -->
 1390    { Args = [class(copy), title('Click to copy')] },
 1391    (   { option(href(HREF), Options) }
 1392    ->  synopsis([ code(Args, [':- use_module(',a(href(HREF), '~q'-[Unquoted]),').'])
 1393                 | \can_autoload(Head, File)
 1394                 ])
 1395    ;   synopsis([ code(Args, ':- use_module(~q).'-[Unquoted])
 1396                 | \can_autoload(Head, File)
 1397                 ])
 1398    ).
 1399
 1400synopsis(HTML) -->
 1401    html(div(class(synopsis),
 1402	     [ span(class('synopsis-hdr'), 'Availability:')
 1403	     | HTML
 1404	     ])).
 1405
 1406can_autoload(Head, File) -->
 1407    { predicate_property(Head, autoload(FileBase)),
 1408      file_name_extension(FileBase, _Ext, File)
 1409    },
 1410    !,
 1411    html(span(class(autoload), \can_be_autoloaded)).
 1412can_autoload(_, _) -->
 1413    [].
 1414
 1415can_be_autoloaded -->
 1416    { catch(http_link_to_id(pldoc_man, [section(autoload)], HREF),
 1417            error(_,_), fail)
 1418    },
 1419    html(['(can be ', a(href(HREF), autoloaded), ')']).
 1420can_be_autoloaded -->
 1421    html('(can be autoloaded)').
 1422
 1423
 1424%!  unquote_filespec(+Spec, -Unquoted) is det.
 1425%
 1426%   Translate       e.g.       library('semweb/rdf_db')         into
 1427%   library(semweb/rdf_db).
 1428
 1429unquote_filespec(Spec, Unquoted) :-
 1430    compound(Spec),
 1431    Spec =.. [Alias,Path],
 1432    atom(Path),
 1433    atomic_list_concat(Parts, /, Path),
 1434    maplist(need_no_quotes, Parts),
 1435    !,
 1436    parts_to_path(Parts, UnquotedPath),
 1437    Unquoted =.. [Alias, UnquotedPath].
 1438unquote_filespec(Spec, Spec).
 1439
 1440need_no_quotes(Atom) :-
 1441    format(atom(A), '~q', [Atom]),
 1442    \+ sub_atom(A, 0, _, _, '\'').
 1443
 1444parts_to_path([One], One) :- !.
 1445parts_to_path(List, More/T) :-
 1446    (   append(H, [T], List)
 1447    ->  parts_to_path(H, More)
 1448    ).
 1449
 1450
 1451		 /*******************************
 1452		 *             PRINT            *
 1453		 *******************************/
 1454
 1455%!  doc_write_html(+Out:stream, +Title:atomic, +DOM) is det.
 1456%
 1457%   Write HTML for the documentation page DOM using Title to Out.
 1458
 1459doc_write_html(Out, Title, Doc) :-
 1460    doc_page_dom(Title, Doc, DOM),
 1461    phrase(html(DOM), Tokens),
 1462    print_html_head(Out),
 1463    print_html(Out, Tokens).
 1464
 1465%!  doc_page_dom(+Title, +Body, -DOM) is det.
 1466%
 1467%   Create the complete HTML DOM from the   Title  and Body. It adds
 1468%   links to the style-sheet and javaScript files.
 1469
 1470doc_page_dom(Title, Body, DOM) :-
 1471    DOM = html([ head([ title(Title),
 1472			link([ rel(stylesheet),
 1473			       type('text/css'),
 1474			       href(location_by_id(pldoc_resource)+'pldoc.css')
 1475			     ]),
 1476			script([ src(location_by_id(pldoc_resource)+'pldoc.js'),
 1477				 type('text/javascript')
 1478			       ], [])
 1479		      ]),
 1480		 body(Body)
 1481	       ]).
 1482
 1483%!  print_html_head(+Out:stream) is det.
 1484%
 1485%   Print the =DOCTYPE= line.
 1486
 1487print_html_head(Out) :-
 1488    format(Out,
 1489	   '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" \c
 1490	       "http://www.w3.org/TR/html4/strict.dtd">~n', []).
 1491
 1492% Rendering rules
 1493%
 1494% These rules translate \-terms produced by wiki.pl
 1495
 1496%!  tags(+Tags)// is det.
 1497%
 1498%   Emit the @tag tags of a description. Tags is produced by tags/3.
 1499%
 1500%   @see combine_tags/2.
 1501
 1502tags(Tags) -->
 1503    html(dl(class=tags, Tags)).
 1504
 1505%!  tag(+Tag, +Values:list)// is det.
 1506%
 1507%   Called from \tag(Name, Values) terms produced by doc_wiki.pl.
 1508
 1509tag(Tag, Values) -->
 1510    {   doc_tag_title(Tag, Title),
 1511	atom_concat('keyword-', Tag, Class)
 1512    },
 1513    html([ dt(class=Class, Title),
 1514	   \tag_values(Values, Class)
 1515	 ]).
 1516
 1517tag_values([], _) -->
 1518    [].
 1519tag_values([H|T], Class) -->
 1520    html(dd(class=Class, ['- '|H])),
 1521    tag_values(T, Class).
 1522
 1523
 1524%!  doc_tag_title(+Tag, -Title) is det.
 1525%
 1526%   Title is the name to use for Tag in the generated documentation.
 1527
 1528doc_tag_title(Tag, Title) :-
 1529    tag_title(Tag, Title),
 1530    !.
 1531doc_tag_title(Tag, Tag).
 1532
 1533tag_title(compat, 'Compatibility').
 1534tag_title(tbd,    'To be done').
 1535tag_title(see,    'See also').
 1536tag_title(error,  'Errors').
 1537tag_title(since,  'Since').
 1538
 1539%!  args(+Params:list) is det.
 1540%
 1541%   Called from \args(List) created by   doc_wiki.pl.  Params is a
 1542%   list of arg(Name, Descr).
 1543
 1544args(Params) -->
 1545    html([ dt(class=tag, 'Arguments:'),
 1546	   dd(table(class=arglist,
 1547		    \arg_list(Params)))
 1548	 ]).
 1549
 1550arg_list([]) -->
 1551    [].
 1552arg_list([H|T]) -->
 1553    argument(H),
 1554    arg_list(T).
 1555
 1556argument(arg(Name,Descr)) -->
 1557    html(tr([td(var(Name)), td(class=argdescr, ['- '|Descr])])).
 1558
 1559
 1560		 /*******************************
 1561		 *         NAVIGATION TREE      *
 1562		 *******************************/
 1563
 1564%!  objects_nav_tree(+Objects, -Tree) is det.
 1565%
 1566%   Provide a navigation tree showing the context of Object.  Tree
 1567%   is of the form node(Object, Children).
 1568
 1569objects_nav_tree(Objects, Tree) :-
 1570    maplist(object_nav_tree, Objects, Trees),
 1571    union_trees(Trees, Tree0),
 1572    remove_unique_root(Tree0, Tree).
 1573
 1574object_nav_tree(Obj, Tree) :-
 1575    Node = node(directory(Dir), FileNodes),
 1576    FileNode = node(file(File), Siblings),
 1577    doc_comment(Obj, File:_Line, _Summary, _Comment),
 1578    !,
 1579    file_directory_name(File, Dir),
 1580    sibling_file_nodes(Dir, FileNodes0),
 1581    selectchk(node(file(File),[]), FileNodes0, FileNode, FileNodes),
 1582    findall(Sibling, doc_comment(Sibling, File:_, _, _), Siblings0),
 1583    delete(Siblings0, _:module(_), Siblings1),
 1584    doc_hide_private(Siblings1, Siblings2, []),
 1585    flatten(Siblings2, Siblings),   % a comment may describe objects
 1586    embed_directories(Node, Tree).
 1587
 1588sibling_file_nodes(Dir, Nodes) :-
 1589    findall(node(file(File), []),
 1590	    (   source_file(File),
 1591		file_directory_name(File, Dir)
 1592	    ),
 1593	    Nodes).
 1594
 1595embed_directories(Node, Tree) :-
 1596    Node = node(file(File), _),
 1597    !,
 1598    file_directory_name(File, Dir),
 1599    Super = node(directory(Dir), [Node]),
 1600    embed_directories(Super, Tree).
 1601embed_directories(Node, Tree) :-
 1602    Node = node(directory(Dir), _),
 1603    file_directory_name(Dir, SuperDir),
 1604    SuperDir \== Dir,
 1605    !,
 1606    Super = node(directory(SuperDir), [Node]),
 1607    embed_directories(Super, Tree).
 1608embed_directories(Tree, Tree).
 1609
 1610
 1611union_trees([Tree], Tree) :- !.
 1612union_trees([T1,T2|Trees], Tree) :-
 1613    merge_trees(T1, T2, M1),
 1614    union_trees([M1|Trees], Tree).
 1615
 1616merge_trees(node(R, Ch1), node(R, Ch2), node(R, Ch)) :-
 1617    merge_nodes(Ch1, Ch2, Ch).
 1618
 1619merge_nodes([], Ch, Ch) :- !.
 1620merge_nodes(Ch, [], Ch) :- !.
 1621merge_nodes([node(Root, Ch1)|T1], N1, [T1|Nodes]) :-
 1622    selectchk(node(Root, Ch2), N1, N2),
 1623    !,
 1624    merge_trees(node(Root, Ch1), node(Root, Ch2), T1),
 1625    merge_nodes(T1, N2, Nodes).
 1626merge_nodes([Node|T1], N1, [Node|Nodes]) :-
 1627    merge_nodes(T1, N1, Nodes).
 1628
 1629%!  remove_unique_root(+TreeIn, -Tree)
 1630%
 1631%   Remove the root part that does not branch
 1632
 1633remove_unique_root(node(_, [node(R1, [R2])]), Tree) :-
 1634    !,
 1635    remove_unique_root(node(R1, [R2]), Tree).
 1636remove_unique_root(Tree, Tree).
 1637
 1638%!  nav_tree(+Tree, +Current, +Options)// is det.
 1639%
 1640%   Render the navigation tree
 1641
 1642nav_tree(Tree, Current, Options) -->
 1643    html(ul(class(nav),
 1644	    \object_tree(Tree, Current, Options))).
 1645
 1646%!  object_tree(+Tree, +Current, +Options)// is det.
 1647%
 1648%   Render a tree of objects used for navigation.
 1649
 1650object_tree(node(Id, []), Target, Options) -->
 1651    !,
 1652    { node_class(Id, Target, Class) },
 1653    html(li(class(Class),
 1654	    \node(Id, Options))).
 1655object_tree(node(Id, Children), Target, Options) -->
 1656    !,
 1657    { node_class(Id, Target, Class) },
 1658    html(li(class(Class),
 1659	    [ \node(Id, Options),
 1660	      ul(class(nav),
 1661		 \object_trees(Children, Target, Options))
 1662	    ])).
 1663object_tree(Id, Target, Options) -->
 1664    !,
 1665    { node_class(Id, Target, Class) },
 1666    html(li(class([obj|Class]), \node(Id, Options))).
 1667
 1668object_trees([], _, _) --> [].
 1669object_trees([H|T], Target, Options) -->
 1670    object_tree(H, Target, Options),
 1671    object_trees(T, Target, Options).
 1672
 1673node_class(Ids, Current, Class) :-
 1674    is_list(Ids),
 1675    !,
 1676    (   member(Id, Ids), memberchk(Id, Current)
 1677    ->  Class = [nav,current]
 1678    ;   Class = [nav]
 1679    ).
 1680node_class(Id, Current, Class) :-
 1681    (   memberchk(Id, Current)
 1682    ->  Class = [nav,current]
 1683    ;   Class = [nav]
 1684    ).
 1685
 1686node(file(File), Options) -->
 1687    !,
 1688    object_ref(file(File), [style(title)|Options]).
 1689node(Id, Options) -->
 1690    object_ref(Id, Options).
 1691
 1692
 1693		 /*******************************
 1694		 *            SECTIONS          *
 1695		 *******************************/
 1696
 1697section(Type, Title) -->
 1698    { string_codes(Title, Codes),
 1699      wiki_codes_to_dom(Codes, [], Content0),
 1700      strip_leading_par(Content0, Content),
 1701      make_section(Type, Content, HTML)
 1702    },
 1703    html(HTML).
 1704
 1705make_section(module,  Title, h1(class=module,  Title)).
 1706make_section(section, Title, h1(class=section, Title)).
 1707
 1708
 1709		 /*******************************
 1710		 *       PRED MODE HEADER       *
 1711		 *******************************/
 1712
 1713%!  pred_dt(+Modes, +Class, Options)// is det.
 1714%
 1715%   Emit the predicate header.
 1716%
 1717%   @param Modes    List as returned by process_modes/5.
 1718
 1719pred_dt(Modes, Class, Options) -->
 1720    pred_dt(Modes, Class, [], _Done, Options).
 1721
 1722pred_dt([], _, Done, Done, _) -->
 1723    [].
 1724pred_dt([H|T], Class, Done0, Done, Options) -->
 1725    { functor(Class, CSSClass, _) },
 1726    html(dt(class=CSSClass,
 1727	    [ \pred_mode(H, Done0, Done1, Options),
 1728	      \mode_anot(Class)
 1729	    ])),
 1730    pred_dt(T, Class, Done1, Done, Options).
 1731
 1732mode_anot(privdef) -->
 1733    !,
 1734    html(span([class(anot), style('float:right')],
 1735	      '[private]')).
 1736mode_anot(multidef(object(Obj))) -->
 1737    !,
 1738    { object_href(Obj, HREF) },
 1739    html(span([class(anot), style('float:right')],
 1740	      ['[', a(href(HREF), multifile), ']'
 1741	      ])).
 1742mode_anot(multidef(file(File:_))) -->
 1743    !,
 1744    { file_name_on_path(File, Spec),
 1745      unquote_filespec(Spec, Unquoted),
 1746      doc_file_href(File, HREF)
 1747    },
 1748    html(span([class(anot), style('float:right')],
 1749	      ['[multifile, ', a(href(HREF), '~q'-[Unquoted]), ']'
 1750	      ])).
 1751mode_anot(multidef) -->
 1752    !,
 1753    html(span([class(anot), style('float:right')],
 1754	      '[multifile]')).
 1755mode_anot(_) -->
 1756    [].
 1757
 1758pred_mode(mode(Head,Vars), Done0, Done, Options) -->
 1759    !,
 1760    { bind_vars(Head, Vars) },
 1761    pred_mode(Head, Done0, Done, Options).
 1762pred_mode(Head is Det, Done0, Done, Options) -->
 1763    !,
 1764    anchored_pred_head(Head, Done0, Done, Options),
 1765    pred_det(Det).
 1766pred_mode(Head, Done0, Done, Options) -->
 1767    anchored_pred_head(Head, Done0, Done, Options).
 1768
 1769bind_vars(Term, Bindings) :-
 1770    bind_vars(Bindings),
 1771    anon_vars(Term).
 1772
 1773bind_vars([]).
 1774bind_vars([Name=Var|T]) :-
 1775    Var = '$VAR'(Name),
 1776    bind_vars(T).
 1777
 1778%!  anon_vars(+Term) is det.
 1779%
 1780%   Bind remaining variables in Term to '$VAR'('_'), so they are
 1781%   printed as '_'.
 1782
 1783anon_vars(Var) :-
 1784    var(Var),
 1785    !,
 1786    Var = '$VAR'('_').
 1787anon_vars(Term) :-
 1788    compound(Term),
 1789    !,
 1790    Term =.. [_|Args],
 1791    maplist(anon_vars, Args).
 1792anon_vars(_).
 1793
 1794
 1795anchored_pred_head(Head, Done0, Done, Options) -->
 1796    { pred_anchor_name(Head, PI, Name) },
 1797    (   { memberchk(PI, Done0) }
 1798    ->  { Done = Done0 },
 1799	pred_head(Head)
 1800    ;   html([ span(style('float:right'),
 1801		    [ \pred_edit_or_source_button(Head, Options),
 1802		      &(nbsp)
 1803		    ]),
 1804	       a(name=Name, \pred_head(Head))
 1805	     ]),
 1806	{ Done = [PI|Done0] }
 1807    ).
 1808
 1809
 1810pred_edit_or_source_button(Head, Options) -->
 1811    { option(edit(true), Options) },
 1812    !,
 1813    pred_edit_button(Head, Options).
 1814pred_edit_or_source_button(Head, Options) -->
 1815    { option(source_link(true), Options) },
 1816    !,
 1817    pred_source_button(Head, Options).
 1818pred_edit_or_source_button(_, _) --> [].
 1819
 1820%!  pred_edit_button(+PredIndicator, +Options)// is det.
 1821%
 1822%   Create a button for editing the given predicate.  Options
 1823%   processed:
 1824%
 1825%       * module(M)
 1826%       Resolve to module M
 1827%       * file(F)
 1828%       For multi-file predicates: link to version in file.
 1829%       * line(L)
 1830%       Line to edit (in file)
 1831
 1832pred_edit_button(_, Options) -->
 1833    { \+ option(edit(true), Options) },
 1834    !.
 1835pred_edit_button(PI0, Options0) -->
 1836    { canonicalise_predref(PI0, PI, Options0, Options) },
 1837    pred_edit_button2(PI, Options).
 1838
 1839pred_edit_button2(Name/Arity, Options) -->
 1840    { \+ ( memberchk(file(_), Options), % always edit if file and line
 1841	   memberchk(line(_), Options)  % are given.
 1842	 ),
 1843      functor(Head, Name, Arity),
 1844      option(module(M), Options, _),
 1845      \+ ( current_module(M),
 1846	   source_file(M:Head, _File)
 1847	 )
 1848    },
 1849    !.
 1850pred_edit_button2(Name/Arity, Options) -->
 1851    { include(edit_param, Options, Extra),
 1852      http_link_to_id(pldoc_edit,
 1853		      [name(Name),arity(Arity)|Extra],
 1854		      EditHREF)
 1855    },
 1856    html(a(onClick('HTTPrequest(\'' + EditHREF + '\')'),
 1857	   img([ class(action),
 1858		 alt('Edit predicate'),
 1859		 title('Edit predicate'),
 1860		 src(location_by_id(pldoc_resource)+'editpred.png')
 1861	       ]))).
 1862pred_edit_button2(_, _) -->
 1863    !,
 1864    [].
 1865
 1866edit_param(module(_)).
 1867edit_param(file(_)).
 1868edit_param(line(_)).
 1869
 1870
 1871%!  object_edit_button(+Object, +Options)// is det.
 1872%
 1873%   Create a button for editing Object.
 1874
 1875object_edit_button(_, Options) -->
 1876    { \+ option(edit(true), Options) },
 1877    !.
 1878object_edit_button(PI, Options) -->
 1879    { is_pi(PI) },
 1880    !,
 1881    pred_edit_button(PI, Options).
 1882object_edit_button(_, _) -->
 1883    [].
 1884
 1885
 1886%!  pred_source_button(+PredIndicator, +Options)// is det.
 1887%
 1888%   Create a button for viewing the source of a predicate.
 1889
 1890pred_source_button(PI0, Options0) -->
 1891    { canonicalise_predref(PI0, PI, Options0, Options),
 1892      option(module(M), Options, _),
 1893      pred_source_href(PI, M, HREF), !
 1894    },
 1895    html(a([ href(HREF),
 1896             class(source)
 1897	   ],
 1898	   img([ class(action),
 1899		 alt('Source'),
 1900		 title('Show source'),
 1901		 src(location_by_id(pldoc_resource)+'source.png')
 1902	       ]))).
 1903pred_source_button(_, _) -->
 1904    [].
 1905
 1906
 1907%!  object_source_button(+Object, +Options)// is det.
 1908%
 1909%   Create a button for showing the source of Object.
 1910
 1911object_source_button(PI, Options) -->
 1912    { is_pi(PI),
 1913      option(source_link(true), Options, true)
 1914    },
 1915    !,
 1916    pred_source_button(PI, Options).
 1917object_source_button(_, _) -->
 1918    [].
 1919
 1920
 1921%!  canonicalise_predref(+PredRef, -PI:Name/Arity, +Options0, -Options) is det.
 1922%
 1923%   Canonicalise a predicate reference. A   possible module qualifier is
 1924%   added as module(M) to Options.
 1925
 1926canonicalise_predref(M:PI0, PI, Options0, [module(M)|Options]) :-
 1927    !,
 1928    canonicalise_predref(PI0, PI, Options0, Options).
 1929canonicalise_predref(//(Head), PI, Options0, Options) :-
 1930    !,
 1931    functor(Head, Name, Arity),
 1932    PredArity is Arity + 2,
 1933    canonicalise_predref(Name/PredArity, PI, Options0, Options).
 1934canonicalise_predref(Name//Arity, PI, Options0, Options) :-
 1935    integer(Arity), Arity >= 0,
 1936    !,
 1937    PredArity is Arity + 2,
 1938    canonicalise_predref(Name/PredArity, PI, Options0, Options).
 1939canonicalise_predref(PI, PI, Options, Options) :-
 1940    PI = Name/Arity,
 1941    atom(Name), integer(Arity), Arity >= 0,
 1942    !.
 1943canonicalise_predref(Head, PI, Options0, Options) :-
 1944    functor(Head, Name, Arity),
 1945    canonicalise_predref(Name/Arity, PI, Options0, Options).
 1946
 1947
 1948%!  pred_head(+Term) is det.
 1949%
 1950%   Emit a predicate head. The functor is  typeset as a =span= using
 1951%   class =pred= and the arguments and =var= using class =arglist=.
 1952
 1953pred_head(Var) -->
 1954    { var(Var),
 1955      !,
 1956      instantiation_error(Var)
 1957    }.
 1958pred_head(//(Head)) -->
 1959    !,
 1960    pred_head(Head),
 1961    html(//).
 1962pred_head(M:Head) -->
 1963    html([span(class=module, M), :]),
 1964    pred_head(Head).
 1965pred_head(Head) -->
 1966    { atom(Head) },
 1967    !,
 1968    html(b(class=pred, Head)).
 1969pred_head(Head) -->                     % Infix operators
 1970    { Head =.. [Functor,Left,Right],
 1971      is_op_type(Functor, infix)
 1972    },
 1973    !,
 1974    html([ var(class=arglist, \pred_arg(Left, 1)),
 1975	   ' ', b(class=pred, Functor), ' ',
 1976	   var(class=arglist, \pred_arg(Right, 2))
 1977	 ]).
 1978pred_head(Head) -->                     % Prefix operators
 1979    { Head =.. [Functor,Arg],
 1980      is_op_type(Functor, prefix)
 1981    },
 1982    !,
 1983    html([ b(class=pred, Functor), ' ',
 1984	   var(class=arglist, \pred_arg(Arg, 1))
 1985	 ]).
 1986pred_head(Head) -->                     % Postfix operators
 1987    { Head =.. [Functor,Arg],
 1988      is_op_type(Functor, postfix)
 1989    },
 1990    !,
 1991    html([ var(class=arglist, \pred_arg(Arg, 1)),
 1992	   ' ', b(class=pred, Functor)
 1993	 ]).
 1994pred_head({Head}) -->
 1995    !,
 1996    html([ b(class=pred, '{'),
 1997	   var(class=arglist,
 1998	       \pred_args([Head], 1)),
 1999	   b(class=pred, '}')
 2000	 ]).
 2001pred_head(Head) -->                     % Plain terms
 2002    { Head =.. [Functor|Args] },
 2003    html([ b(class=pred, Functor),
 2004	   var(class=arglist,
 2005	       [ '(', \pred_args(Args, 1), ')' ])
 2006	 ]).
 2007
 2008%!  is_op_type(+Atom, ?Type)
 2009%
 2010%   True if Atom is an operator of   Type.  Type is one of =prefix=,
 2011%   =infix= or =postfix=.
 2012
 2013is_op_type(Functor, Type) :-
 2014    current_op(_Pri, F, Functor),
 2015    op_type(F, Type).
 2016
 2017op_type(fx,  prefix).
 2018op_type(fy,  prefix).
 2019op_type(xf,  postfix).
 2020op_type(yf,  postfix).
 2021op_type(xfx, infix).
 2022op_type(xfy, infix).
 2023op_type(yfx, infix).
 2024op_type(yfy, infix).
 2025
 2026
 2027pred_args([], _) -->
 2028    [].
 2029pred_args([H|T], I) -->
 2030    pred_arg(H, I),
 2031    (   {T==[]}
 2032    ->  []
 2033    ;   html(', '),
 2034	{ I2 is I + 1 },
 2035	pred_args(T, I2)
 2036    ).
 2037
 2038pred_arg(Var, I) -->
 2039    { var(Var) },
 2040    !,
 2041    html(['Arg', I]).
 2042pred_arg(...(Term), I) -->
 2043    !,
 2044    pred_arg(Term, I),
 2045    html('...').
 2046pred_arg(Term, I) -->
 2047    { Term =.. [Ind,Arg],
 2048      mode_indicator(Ind)
 2049    },
 2050    !,
 2051    html([Ind, \pred_arg(Arg, I)]).
 2052pred_arg(Arg:Type, _) -->
 2053    !,
 2054    html([\argname(Arg), :, \argtype(Type)]).
 2055pred_arg(Arg, _) -->
 2056    argname(Arg).
 2057
 2058argname('$VAR'(Name)) -->
 2059    !,
 2060    html(Name).
 2061argname(Name) -->
 2062    !,
 2063    html(Name).
 2064
 2065argtype(Term) -->
 2066    { format(string(S), '~W',
 2067	     [ Term,
 2068	       [ quoted(true),
 2069		 numbervars(true)
 2070	       ]
 2071	     ]) },
 2072    html(S).
 2073
 2074pred_det(unknown) -->
 2075    [].
 2076pred_det(Det) -->
 2077    html([' is ', b(class=det, Det)]).
 2078
 2079
 2080%!  term(+Text, +Term, +Bindings)// is det.
 2081%
 2082%   Process the \term element as produced by doc_wiki.pl.
 2083%
 2084%   @tbd    Properly merge with pred_head//1
 2085
 2086term(_, Atom, []) -->
 2087    { atomic(Atom),
 2088      !,
 2089      format(string(S), '~W', [Atom,[quoted(true)]])
 2090    },
 2091    html(span(class=functor, S)).
 2092term(_, Key:Type, [TypeName=Type]) -->
 2093    { atomic(Key)
 2094    },
 2095    !,
 2096    html([span(class='pl-key', Key), :, span(class('pl-var'), TypeName)]).
 2097term(_, Term, Bindings) -->
 2098    { is_mode(Term is det),         % HACK. Bit too strict?
 2099      bind_vars(Bindings)
 2100    },
 2101    !,
 2102    pred_head(Term).
 2103term(_, Term, Bindings) -->
 2104    term(Term,
 2105	 [ variable_names(Bindings),
 2106	   quoued(true)
 2107	 ]).
 2108
 2109
 2110		 /*******************************
 2111		 *             PREDREF          *
 2112		 *******************************/
 2113
 2114%!  predref(+PI)// is det.
 2115%!  predref(+PI, +Options)// is det.
 2116%
 2117%   Create a reference to a predicate. The reference consists of the
 2118%   relative path to the  file  using   the  predicate  indicator as
 2119%   anchor.
 2120%
 2121%   Current file must  be  available   through  the  global variable
 2122%   =pldoc_file=. If this variable not  set   it  creates  a link to
 2123%   /doc/<file>#anchor.  Such links only work in the online browser.
 2124
 2125predref(Term) -->
 2126    { catch(nb_getval(pldoc_options, Options), _, Options = []) },
 2127    predref(Term, Options).
 2128
 2129predref(Obj, Options) -->
 2130    { Obj = _:_,
 2131      doc_comment(Obj, File:_Line, _, _),
 2132      (   (   option(files(Map), Options)
 2133	  ->  memberchk(file(File,_), Map)
 2134	  ;   true
 2135	  )
 2136      ->  object_href(Obj, HREF, Options)
 2137      ;   manref(Obj, HREF, Options)
 2138      )
 2139    },
 2140    !,
 2141    html(a(href(HREF), \object_name(Obj, [qualify(true)|Options]))).
 2142predref(M:Term, Options) -->
 2143    !,
 2144    predref(Term, M, Options).
 2145predref(Term, Options) -->
 2146    predref(Term, _, Options).
 2147
 2148predref(Name/Arity, _, Options) -->             % Builtin; cannot be overruled
 2149    { prolog:doc_object_summary(Name/Arity, manual, _, _),
 2150      !,
 2151      manref(Name/Arity, HREF, Options)
 2152    },
 2153    html(a([class=builtin, href=HREF], [Name, /, Arity])).
 2154predref(Name/Arity, _, Options) -->             % From packages
 2155    { option(prefer(manual), Options),
 2156      prolog:doc_object_summary(Name/Arity, Category, _, _),
 2157      !,
 2158      manref(Name/Arity, HREF, Options)
 2159    },
 2160    html(a([class=Category, href=HREF], [Name, /, Arity])).
 2161predref(Obj, Module, Options) -->               % Local
 2162    { doc_comment(Module:Obj, File:_Line, _, _),
 2163      (   option(files(Map), Options)
 2164      ->  memberchk(file(File,_), Map)
 2165      ;   true
 2166      )
 2167    },
 2168    !,
 2169    object_ref(Module:Obj, Options).
 2170predref(Name/Arity, Module, Options) -->
 2171    { \+ option(files(_), Options),
 2172      pred_href(Name/Arity, Module, HREF)
 2173    },
 2174    !,
 2175    html(a(href=HREF, [Name, /, Arity])).
 2176predref(Name//Arity, Module, Options) -->
 2177    { \+ option(files(_), Options),
 2178      PredArity is Arity + 2,
 2179      pred_href(Name/PredArity, Module, HREF)
 2180    },
 2181    !,
 2182    html(a(href=HREF, [Name, //, Arity])).
 2183predref(PI, _, Options) -->             % From packages
 2184    { canonical_pi(PI, CPI, HTML),
 2185      (   option(files(_), Options)
 2186      ->  Category = extmanual
 2187      ;   prolog:doc_object_summary(CPI, Category, _, _)
 2188      ),
 2189      manref(CPI, HREF, Options)
 2190    },
 2191    html(a([class=Category, href=HREF], HTML)).
 2192predref(PI, _, _Options) -->
 2193    { canonical_pi(PI, _CPI, HTML)
 2194    },
 2195    !,
 2196    html(span(class=undef, HTML)).
 2197predref(Callable, Module, Options) -->
 2198    { callable(Callable),
 2199      functor(Callable, Name, Arity)
 2200    },
 2201    predref(Name/Arity, Module, Options).
 2202
 2203canonical_pi(Name/Arity, Name/Arity, [Name, /, Arity]) :-
 2204    atom(Name), integer(Arity),
 2205    !.
 2206canonical_pi(Name//Arity, Name/Arity2, [Name, //, Arity]) :-
 2207    atom(Name), integer(Arity),
 2208    !,
 2209    Arity2 is Arity+2.
 2210
 2211%!  nopredref(+PI)//
 2212%
 2213%   Result of ``name/arity``, non-linking predicate indicator.
 2214
 2215nopredref(PI) -->
 2216    { canonical_pi(PI, _CPI, HTML)
 2217    },
 2218    !,
 2219    html(span(class=nopredref, HTML)).
 2220
 2221%!  flagref(+Flag)//
 2222%
 2223%   Reference to a Prolog flag.
 2224%
 2225%   @tbd generate a link to the Prolog website?
 2226
 2227flagref(Flag) -->
 2228    html(code(Flag)).
 2229
 2230%!  cite(+Citations)// is det.
 2231%
 2232%   Emit citations. This is indented to   allow  for [@cite1;@cite2] for
 2233%   generating LaTex.
 2234
 2235cite(Citations) -->
 2236    html('['), citations(Citations), html(']').
 2237
 2238citations([]) --> [].
 2239citations([H|T]) -->
 2240    citation(H),
 2241    (   {T==[]}
 2242    ->  []
 2243    ;   [';'],
 2244	citations(T)
 2245    ).
 2246
 2247citation(H) -->
 2248    html([@,H]).
 2249
 2250
 2251%!  manref(+NameArity, -HREF, +Options) is det.
 2252%
 2253%   Create reference to a manual page.  When generating files, this
 2254%   listens to the option man_server(+Server).
 2255
 2256manref(PI, HREF, Options) :-
 2257    predname(PI, PredName),
 2258    (   option(files(_Map), Options)
 2259    ->  option(man_server(Server), Options,
 2260	       'http://www.swi-prolog.org/pldoc'),
 2261	uri_components(Server, Comp0),
 2262	uri_data(path, Comp0, Path0),
 2263	directory_file_path(Path0, man, Path),
 2264	uri_data(path, Comp0, Path, Components),
 2265	uri_query_components(Query, [predicate=PredName]),
 2266	uri_data(search, Components, Query),
 2267	uri_components(HREF, Components)
 2268    ;   http_link_to_id(pldoc_man, [predicate=PredName], HREF)
 2269    ).
 2270
 2271predname(Name/Arity, PredName) :-
 2272    !,
 2273    format(atom(PredName), '~w/~d', [Name, Arity]).
 2274predname(Module:Name/Arity, PredName) :-
 2275    !,
 2276    format(atom(PredName), '~w:~w/~d', [Module, Name, Arity]).
 2277
 2278
 2279%!  pred_href(+NameArity, +Module, -HREF) is semidet.
 2280%
 2281%   Create reference.  Prefer:
 2282%
 2283%           1. Local definition
 2284%           2. If from package and documented: package documentation
 2285%           3. From any file
 2286%
 2287%   @bug    Should analyse import list to find where the predicate
 2288%           comes from.
 2289
 2290pred_href(Name/Arity, Module, HREF) :-
 2291    format(string(FragmentId), '~w/~d', [Name, Arity]),
 2292    uri_data(fragment, Components, FragmentId),
 2293    functor(Head, Name, Arity),
 2294    (   catch(relative_file(Module:Head, File), _, fail)
 2295    ->  uri_data(path, Components, File),
 2296	uri_components(HREF, Components)
 2297    ;   in_file(Module:Head, File)
 2298    ->  (   current_prolog_flag(home, SWI),
 2299	    sub_atom(File, 0, _, _, SWI),
 2300	    prolog:doc_object_summary(Name/Arity, packages, _, _)
 2301	->  http_link_to_id(pldoc_man, [predicate=FragmentId], HREF)
 2302	;   http_location_by_id(pldoc_doc, DocHandler),
 2303	    atom_concat(DocHandler, File, Path),
 2304	    uri_data(path, Components, Path),
 2305	    uri_components(HREF, Components)
 2306	)
 2307    ).
 2308
 2309relative_file(Head, '') :-
 2310    b_getval(pldoc_file, CurrentFile), CurrentFile \== [],
 2311    in_file(Head, CurrentFile),
 2312    !.
 2313relative_file(Head, RelFile) :-
 2314    b_getval(pldoc_file, CurrentFile), CurrentFile \== [],
 2315    in_file(Head, DefFile),
 2316    relative_file_name(DefFile, CurrentFile, RelFile).
 2317
 2318%!  pred_source_href(+Pred:predicate_indicator, +Module, -HREF) is semidet.
 2319%
 2320%   HREF is a URL to show the predicate source in its file.
 2321
 2322pred_source_href(Name/Arity, Module, HREF) :-
 2323    format(string(FragmentId), '~w/~d', [Name, Arity]),
 2324    uri_data(fragment, Components, FragmentId),
 2325    uri_query_components(Query, [show=src]),
 2326    uri_data(search, Components, Query),
 2327    functor(Head, Name, Arity),
 2328    (   catch(relative_file(Module:Head, File), _, fail)
 2329    ->  uri_data(path, Components, File),
 2330	uri_components(HREF, Components)
 2331    ;   in_file(Module:Head, File0)
 2332    ->  insert_alias(File0, File),
 2333	http_location_by_id(pldoc_doc, DocHandler),
 2334	atom_concat(DocHandler, File, Path),
 2335	uri_data(path, Components, Path),
 2336	uri_components(HREF, Components)
 2337    ).
 2338
 2339
 2340%!  object_ref(+Object, +Options)// is det.
 2341%
 2342%   Create a hyperlink to Object. Points to the /doc_for URL. Object
 2343%   is as the first argument of doc_comment/4.   Note  this can be a
 2344%   list of objects.
 2345
 2346object_ref([], _) -->
 2347    !,
 2348    [].
 2349object_ref([H|T], Options) -->
 2350    !,
 2351    object_ref(H, Options),
 2352    (   {T == []}
 2353    ->  html(', '),
 2354	object_ref(T, Options)
 2355    ;   []
 2356    ).
 2357object_ref(Obj, Options) -->
 2358    { object_href(Obj, HREF, Options)
 2359    },
 2360    html(a(href(HREF), \object_name(Obj, Options))).
 2361
 2362%!  object_href(+Object, -HREF) is det.
 2363%!  object_href(+Object, -HREF, +Options) is det.
 2364%
 2365%   HREF is the URL to access Object.
 2366
 2367object_href(Obj, HREF) :-
 2368    object_href(Obj, HREF, []).
 2369
 2370object_href(M:PI0, HREF, Options) :-
 2371    option(files(Map), Options),
 2372    (   module_property(M, file(File))
 2373    ->  true
 2374    ;   xref_module(File, M)
 2375    ),
 2376    memberchk(file(File, DocFile), Map),
 2377    !,
 2378    file_base_name(DocFile, LocalFile),     % TBD: proper directory index
 2379    expand_pi(PI0, PI),
 2380    term_to_string(PI, PIS),
 2381    uri_data(path, Components, LocalFile),
 2382    uri_data(fragment, Components, PIS),
 2383    uri_components(HREF, Components).
 2384object_href(file(File), HREF, _Options) :-
 2385    doc_file_href(File, HREF),
 2386    !.
 2387object_href(directory(Dir), HREF, _Options) :-
 2388    directory_file_path(Dir, 'index.html', Index),
 2389    doc_file_href(Index, HREF),
 2390    !.
 2391object_href(Obj, HREF, _Options) :-
 2392    prolog:doc_object_href(Obj, HREF),
 2393    !.
 2394object_href(Obj0, HREF, _Options) :-
 2395    localise_object(Obj0, Obj),
 2396    term_to_string(Obj, String),
 2397    http_link_to_id(pldoc_object, [object=String], HREF).
 2398
 2399expand_pi(Name//Arity0, Name/Arity) :-
 2400    !,
 2401    Arity is Arity0+2.
 2402expand_pi(PI, PI).
 2403
 2404
 2405%!  localise_object(+ObjIn, -ObjOut) is det.
 2406%
 2407%   Abstract  path-details  to  make  references  more  stable  over
 2408%   versions.
 2409
 2410localise_object(Obj0, Obj) :-
 2411    prolog:doc_canonical_object(Obj0, Obj),
 2412    !.
 2413localise_object(Obj, Obj).
 2414
 2415
 2416%!  term_to_string(+Term, -String) is det.
 2417%
 2418%   Convert Term, possibly  holding  variables,   into  a  canonical
 2419%   string using A, B, ... for variables and _ for singletons.
 2420
 2421term_to_string(Term, String) :-
 2422    State = state(-),
 2423    (   numbervars(Term, 0, _, [singletons(true)]),
 2424	with_output_to(string(String),
 2425		       write_term(Term,
 2426				  [ numbervars(true),
 2427				    quoted(true)
 2428				  ])),
 2429	nb_setarg(1, State, String),
 2430	fail
 2431    ;   arg(1, State, String)
 2432    ).
 2433
 2434%!  object_name(+Obj, +Options)// is det.
 2435%
 2436%   HTML description of documented Obj. Obj is as the first argument
 2437%   of doc_comment/4.  Options:
 2438%
 2439%     - style(+Style)
 2440%     One of =inline= or =title=
 2441%     - qualify(+Boolean)
 2442%     Qualify predicates by their module
 2443%     - secref_style(Style)
 2444%     One of =number=, =title= or =number_title=
 2445
 2446object_name(Obj, Options) -->
 2447    { option(style(Style), Options, inline)
 2448    },
 2449    object_name(Style, Obj, Options).
 2450
 2451object_name(title, Obj, Options) -->
 2452    { merge_options(Options, [secref_style(title)], Options1) },
 2453    prolog:doc_object_link(Obj, Options1),
 2454    !.
 2455object_name(inline, Obj, Options) -->
 2456    prolog:doc_object_link(Obj, Options),
 2457    !.
 2458object_name(title, f(Name/Arity), _Options) -->
 2459    !,
 2460    html(['Function ', Name, /, Arity]).
 2461object_name(inline, f(Name/Arity), _Options) -->
 2462    !,
 2463    html([Name, /, Arity]).
 2464object_name(Style, PI, Options) -->
 2465    { is_pi(PI) },
 2466    !,
 2467    pi(Style, PI, Options).
 2468object_name(inline, Module:module(_Title), _) -->
 2469    !,
 2470    { module_property(Module, file(File)),
 2471      file_base_name(File, Base)
 2472    },
 2473    !,
 2474    html(Base).
 2475object_name(title, Module:module(Title), _) -->
 2476    { module_property(Module, file(File)),
 2477      file_base_name(File, Base)
 2478    },
 2479    !,
 2480    html([Base, ' -- ', Title]).
 2481object_name(title, file(File), _) -->
 2482    { module_property(Module, file(File)),
 2483      doc_comment(Module:module(Title), _, _, _),
 2484      !,
 2485      file_base_name(File, Base)
 2486    },
 2487    html([Base, ' -- ', Title]).
 2488object_name(_, file(File), _) -->
 2489    { file_base_name(File, Base) },
 2490    html(Base).
 2491object_name(_, directory(Dir), _) -->
 2492    { file_base_name(Dir, Base) },
 2493    html(Base).
 2494object_name(_, module(Title), _Options) -->
 2495    { print_message(warning,
 2496		    pldoc(module_comment_outside_module(Title)))
 2497    }.
 2498
 2499pi(title, PI, Options) -->
 2500    pi_type(PI),
 2501    pi(PI, Options).
 2502pi(inline, PI, Options) -->
 2503    pi(PI, Options).
 2504
 2505pi(M:PI, Options) -->
 2506    !,
 2507    (   { option(qualify(true), Options) }
 2508    ->  html([span(class(module), M), :])
 2509    ;   []
 2510    ),
 2511    pi(PI, Options).
 2512pi(Name/Arity, _) -->
 2513    !,
 2514    html([Name, /, \arity(Arity)]).
 2515pi(Name//Arity, _) -->
 2516    html([Name, //, \arity(Arity)]).
 2517
 2518arity(Arity) -->
 2519    { var(Arity) },
 2520    !,
 2521    html('_').
 2522arity(Arity) -->
 2523    html(Arity).
 2524
 2525pi_type(_:PI) -->
 2526    !,
 2527    pi_type(PI).
 2528pi_type(_/_) -->
 2529    html(['Predicate ']).
 2530pi_type(_//_) -->
 2531    html(['Grammar rule ']).
 2532
 2533
 2534
 2535%!  in_file(+Head, ?File) is nondet.
 2536%
 2537%   File is the name of a file containing the Predicate Head.
 2538%   Head may be qualified with a module.
 2539%
 2540%   @tbd Prefer local, then imported, then `just anywhere'
 2541%   @tbd Look for documented and/or public predicates.
 2542
 2543in_file(Module:Head, File) :-
 2544    !,
 2545    distinct(File, in_file(Module, Head, File)).
 2546in_file(Head, File) :-
 2547    distinct(File, in_file(_, Head, File)).
 2548
 2549in_file(Module, Head, File) :-
 2550    var(Module),
 2551    (   predicate_property(system:Head, foreign)
 2552    ->  !,
 2553	fail
 2554    ;   predicate_property(system:Head, file(File)),
 2555	\+ system_arithmetic_function(Head)
 2556    ->  !
 2557    ;   predicate_property(Head, autoload(File0))
 2558    ->  !,
 2559	file_name_extension(File0, pl, File)
 2560    ;   exported_from(Module, Head, File),
 2561	module_property(Module, class(library))
 2562    ).
 2563in_file(Module, Head, File) :-
 2564    nonvar(Module),
 2565    predicate_property(Module:Head, file(File)),
 2566    \+ predicate_property(Module:Head, imported_from(_)).
 2567in_file(Module, Head, File) :-
 2568    xref_defined(File, Head, How),
 2569    xref_current_source(File),
 2570    atom(File),                     % only plain files
 2571    xref_module(File, Module),
 2572    How \= imported(_From).
 2573in_file(Module, Head, File) :-
 2574    exported_from(Module, Head, File).
 2575in_file(Module, Head, File) :-
 2576    predicate_property(Module:Head, file(File)),
 2577    \+ predicate_property(Module:Head, imported_from(_)).
 2578in_file(Module, Head, File) :-
 2579    current_module(Module),
 2580    source_file(Module:Head, File).
 2581
 2582exported_from(Module, Head, File) :-
 2583    distinct(Primary,
 2584	     (   predicate_property(Module:Head, exported),
 2585		 (   predicate_property(Module:Head, imported_from(Primary))
 2586		 ->  true
 2587		 ;   Primary = Module
 2588		 ))),
 2589    module_property(Primary, file(File)).
 2590
 2591:- multifile
 2592    arithmetic:evaluable/2. 2593
 2594system_arithmetic_function(Head) :-
 2595    functor(Head, Name, Arity),
 2596    FArith is Arity-1,
 2597    FArith >= 0,
 2598    functor(FHead, Name, FArith),
 2599    arithmetic:evaluable(FHead, system).
 2600
 2601%%     file(+FileName)// is det.
 2602%%     file(+FileName, +Options)// is det.
 2603%
 2604%      Create a link to another filename if   the file exists. Called by
 2605%      \file(File) terms in the DOM term generated by wiki.pl. Supported
 2606%      options are:
 2607%
 2608%          * label(+Label)
 2609%          Label to use for the link to the file.
 2610%
 2611%          * absolute_path(+Path)
 2612%          Absolute location of the referenced file.
 2613%
 2614%          * href(+HREF)
 2615%          Explicitely provided link; overrule link computation.
 2616%
 2617%          * map_extension(+Pairs)
 2618%          Map the final extension if OldExt-NewExt is in Pairs.
 2619%
 2620%          * files(+Map)
 2621%          List of file(Name, Link) that specifies that we must
 2622%          user Link for the given physical file Name.
 2623%
 2624%          * edit_handler(+Id)
 2625%          HTTP handler Id to call if the user clicks the edit button.
 2626%
 2627%       @tbd    Translation of files to HREFS is a mess.  How to relate
 2628%               these elegantly?
 2629
 2630file(File) -->
 2631    file(File, []).
 2632
 2633file(File, Options) -->
 2634    { catch(nb_getval(pldoc_options, GenOptions), _, GenOptions = []),
 2635      merge_options(Options, GenOptions, FinalOptions)
 2636    },
 2637    link_file(File, FinalOptions),
 2638    !.
 2639file(File, Options) -->
 2640    { option(edit_handler(Handler), Options),
 2641      http_current_request(Request),
 2642      memberchk(path(Path), Request),
 2643      absolute_file_name(File, Location,
 2644			 [ relative_to(Path)
 2645			 ]),
 2646      http_link_to_id(Handler, [location(Location)], HREF),
 2647      format(atom(Title), 'Click to create ~w', [File])
 2648    },
 2649    html(a([href(HREF), class(nofile), title(Title)], File)).
 2650file(File, _) -->
 2651    html(code(class(nofile), File)).
 2652
 2653link_file(File, Options) -->
 2654    { file_href(File, HREF, Options),
 2655      option(label(Label), Options, File),
 2656      option(class(Class), Options, file)
 2657    },
 2658    html(a([class(Class), href(HREF)], Label)).
 2659
 2660%!  file_href(+FilePath, -HREF, +Options) is det.
 2661%
 2662%   Find URL for refering to FilePath based on Options.
 2663
 2664file_href(_, HREF, Options) :-
 2665    option(href(HREF), Options),
 2666    !.
 2667file_href(File, HREF, Options) :-
 2668    file_href_real(File, HREF0, Options),
 2669    map_extension(HREF0, HREF, Options).
 2670
 2671%!  map_extension(+HREFIn, -HREFOut, Options) is det.
 2672%
 2673%   Replace extension using the option
 2674%
 2675%       * map_extension(+Pairs)
 2676
 2677map_extension(HREF0, HREF, Options) :-
 2678    option(map_extension(Map), Options),
 2679    file_name_extension(Base, Old, HREF0),
 2680    memberchk(Old-New, Map),
 2681    !,
 2682    file_name_extension(Base, New, HREF).
 2683map_extension(HREF, HREF, _).
 2684
 2685
 2686file_href_real(File, HREF, Options) :-
 2687    (   option(absolute_path(Path), Options)
 2688    ;   existing_linked_file(File, Path)
 2689    ),
 2690    !,
 2691    (   option(files(Map), Options),
 2692	memberchk(file(Path, LinkFile), Map)
 2693    ->  true
 2694    ;   LinkFile = Path
 2695    ),
 2696    file_href(LinkFile, HREF).
 2697file_href_real(File, HREF, _) :-
 2698    directory_alias(Alias),
 2699    Term =.. [Alias,File],
 2700    absolute_file_name(Term, _,
 2701		       [ access(read),
 2702			 file_errors(fail)
 2703		       ]),
 2704    !,
 2705    http_absolute_location(Term, HREF, []).
 2706
 2707directory_alias(icons).
 2708directory_alias(css).
 2709
 2710
 2711%!  file_href(+FilePath, -HREF) is det.
 2712%
 2713%   Create a relative URL from  the   current  location to the given
 2714%   absolute file name. It resolves  the   filename  relative to the
 2715%   file being processed  that  is   available  through  the  global
 2716%   variable =pldoc_file=.
 2717
 2718file_href(Path, HREF) :-                % a loaded Prolog file
 2719    source_file(Path),
 2720    !,
 2721    doc_file_href(Path, HREF).
 2722file_href(Path, HREF) :-
 2723    (   nb_current(pldoc_output, CFile)
 2724    ;   nb_current(pldoc_file, CFile)
 2725    ),
 2726    CFile \== [],
 2727    !,
 2728    relative_file_name(Path, CFile, HREF).
 2729file_href(Path, Path).
 2730
 2731
 2732%!  existing_linked_file(+File, -Path) is semidet.
 2733%
 2734%   True if File is a path to an existing file relative to the
 2735%   current file.  Path is the absolute location of File.
 2736
 2737existing_linked_file(File, Path) :-
 2738    catch(b_getval(pldoc_file, CurrentFile), _, fail),
 2739    CurrentFile \== [],
 2740    absolute_file_name(File, Path,
 2741		       [ relative_to(CurrentFile),
 2742			 access(read),
 2743			 file_errors(fail)
 2744		       ]).
 2745
 2746
 2747%!  include(+FileName, +Type, +Options)// is det.
 2748%
 2749%   Inline FileName. If this is an image file, show an inline image.
 2750%   Else we create a link  like   file//1.  Called by \include(File,
 2751%   Type)  terms  in  the  DOM  term  generated  by  wiki.pl  if  it
 2752%   encounters [[file.ext]].
 2753
 2754include(PI, predicate, _) -->
 2755    !,
 2756    (   html_tokens_for_predicates(PI, [])
 2757    ->  []
 2758    ;   html(['[[', \predref(PI), ']]'])
 2759    ).
 2760include(File, image, Options) -->
 2761    { file_name_extension(_, svg, File),
 2762      file_href(File, HREF, Options),
 2763      !,
 2764      include(image_attribute, Options, Attrs0),
 2765      merge_options(Attrs0,
 2766		    [ alt(File),
 2767		      data(HREF),
 2768		      type('image/svg+xml')
 2769		    ], Attrs)
 2770    },
 2771    (   { option(caption(Caption), Options) }
 2772    ->  html(div(class(figure),
 2773		 [ div(class(image), object(Attrs, [])),
 2774		   div(class(caption), Caption)
 2775		 ]))
 2776    ;   html(object(Attrs, []))
 2777    ).
 2778include(File, image, Options) -->
 2779    { file_href(File, HREF, Options),
 2780      !,
 2781      include(image_attribute, Options, Attrs0),
 2782      merge_options(Attrs0,
 2783		    [ alt(File),
 2784		      border(0),
 2785		      src(HREF)
 2786		    ], Attrs)
 2787    },
 2788    (   { option(caption(Caption), Options) }
 2789    ->  html(div(class(figure),
 2790		 [ div(class(image), img(Attrs)),
 2791		   div(class(caption), Caption)
 2792		 ]))
 2793    ;   html(img(Attrs))
 2794    ).
 2795include(File, wiki, _Options) -->       % [[file.txt]] is included
 2796    { access_file(File, read),
 2797      !,
 2798      read_file_to_codes(File, String, []),
 2799      wiki_codes_to_dom(String, [], DOM)
 2800    },
 2801    html(DOM).
 2802include(File, _Type, Options) -->
 2803    link_file(File, Options),
 2804    !.
 2805include(File, _, _) -->
 2806    html(code(class(nofile), ['[[',File,']]'])).
 2807
 2808image_attribute(src(_)).
 2809image_attribute(alt(_)).
 2810image_attribute(title(_)).
 2811image_attribute(align(_)).
 2812image_attribute(width(_)).
 2813image_attribute(height(_)).
 2814image_attribute(border(_)).
 2815image_attribute(class(_)).
 2816image_attribute(style(_)).
 2817
 2818
 2819%!  html_tokens_for_predicates(+PI, +Options)// is semidet.
 2820%
 2821%   Inline description for a predicate as produced by the text below
 2822%   from wiki processing.
 2823%
 2824%   ==
 2825%           * [[member/2]]
 2826%           * [[append/3]]
 2827%   ==
 2828
 2829html_tokens_for_predicates([], _Options) -->
 2830    [].
 2831html_tokens_for_predicates([H|T], Options) -->
 2832    !,
 2833    html_tokens_for_predicates(H, Options),
 2834    html_tokens_for_predicates(T, Options).
 2835html_tokens_for_predicates(PI, Options) -->
 2836    { PI = _:_/_,
 2837      !,
 2838      (   doc_comment(PI, Pos, _Summary, Comment)
 2839      ->  true
 2840      ;   Comment = ''
 2841      )
 2842    },
 2843    object(PI, [Pos-Comment], [dl], _, Options).
 2844html_tokens_for_predicates(Spec, Options) -->
 2845    { findall(PI, documented_pi(Spec, PI), List),
 2846      List \== [], !
 2847    },
 2848    html_tokens_for_predicates(List, Options).
 2849html_tokens_for_predicates(Spec, Options) -->
 2850    man_page(Spec,
 2851	     [ links(false),                % no header
 2852	       navtree(false),              % no navigation tree
 2853	       footer(false),               % no footer
 2854	       synopsis(false)              % no synopsis
 2855	     | Options
 2856	     ]).
 2857
 2858
 2859documented_pi(Spec, PI) :-
 2860    generalise_spec(Spec, PI),
 2861    doc_comment(PI, _Pos, _Summary, _Comment).
 2862
 2863generalise_spec(Name/Arity, _M:Name/Arity).
 2864generalise_spec(Name//Arity, _M:Name//Arity).
 2865
 2866
 2867		 /*******************************
 2868		 *           WIKI FILES         *
 2869		 *******************************/
 2870
 2871
 2872%!  doc_for_wiki_file(+File, +Options) is det.
 2873%
 2874%   Write HTML for the File containing wiki data.
 2875
 2876doc_for_wiki_file(FileSpec, Options) :-
 2877    absolute_file_name(FileSpec, File,
 2878		       [ access(read)
 2879		       ]),
 2880    read_file_to_codes(File, String, []),
 2881    b_setval(pldoc_file, File),
 2882    call_cleanup(reply_wiki_page(File, String, Options),
 2883		 nb_delete(pldoc_file)).
 2884
 2885reply_wiki_page(File, String, Options) :-
 2886    wiki_codes_to_dom(String, [], DOM0),
 2887    title(DOM0, File, Title),
 2888    insert_edit_button(DOM0, File, DOM, Options),
 2889    reply_html_page(pldoc(wiki),
 2890		    title(Title),
 2891		    [ \html_requires(pldoc)
 2892		    | DOM
 2893		    ]).
 2894
 2895title(DOM, _, Title) :-
 2896    sub_term(h1(_,Title), DOM),
 2897    !.
 2898title(_, File, Title) :-
 2899    file_base_name(File, Title).
 2900
 2901insert_edit_button(DOM, _, DOM, Options) :-
 2902    option(edit(false), Options, false),
 2903    !.
 2904insert_edit_button([h1(Attrs,Title)|DOM], File,
 2905		   [h1(Attrs,[ span(style('float:right'),
 2906				   \edit_button(File, [edit(true)]))
 2907			     | Title
 2908			     ])|DOM], _) :- !.
 2909insert_edit_button(DOM, File,
 2910		   [ h1(class(wiki),
 2911			[ span(style('float:right'),
 2912			       \edit_button(File, [edit(true)]))
 2913			])
 2914		   | DOM
 2915		   ], _).
 2916
 2917
 2918		 /*******************************
 2919		 *            ANCHORS           *
 2920		 *******************************/
 2921
 2922%!  mode_anchor_name(+Mode, -Anchor:atom) is det.
 2923%
 2924%   Get the anchor name for a mode.
 2925
 2926mode_anchor_name(Var, _) :-
 2927    var(Var),
 2928    !,
 2929    instantiation_error(Var).
 2930mode_anchor_name(mode(Head, _), Anchor) :-
 2931    !,
 2932    mode_anchor_name(Head, Anchor).
 2933mode_anchor_name(Head is _Det, Anchor) :-
 2934    !,
 2935    mode_anchor_name(Head, Anchor).
 2936mode_anchor_name(Head, Anchor) :-
 2937    pred_anchor_name(Head, _, Anchor).
 2938
 2939
 2940%!  pred_anchor_name(+Head, -PI:atom/integer, -Anchor:atom) is det.
 2941%
 2942%   Create an HTML anchor name from Head.
 2943
 2944pred_anchor_name(//(Head), Name/Arity, Anchor) :-
 2945    !,
 2946    functor(Head, Name, DCGArity),
 2947    Arity is DCGArity+2,
 2948    format(atom(Anchor), '~w/~d', [Name, Arity]).
 2949pred_anchor_name(Head, Name/Arity, Anchor) :-
 2950    functor(Head, Name, Arity),
 2951    format(atom(Anchor), '~w/~d', [Name, Arity]).
 2952
 2953:- multifile prolog:message//1. 2954
 2955prolog:message(pldoc(module_comment_outside_module(Title))) -->
 2956    [ 'PlDoc comment <module> ~w does not appear in a module'-[Title] ]