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