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-2017, 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_http, 37 [ doc_enable/1, % +Boolean 38 doc_server/1, % ?Port 39 doc_server/2, % ?Port, +Options 40 doc_browser/0, 41 doc_browser/1 % +What 42 ]). 43:- use_module(library(pldoc)). 44:- if(exists_source(library(http/thread_httpd))). 45:- use_module(library(http/thread_httpd)). 46:- endif. 47:- use_module(library(http/http_parameters)). 48:- use_module(library(http/html_write)). 49:- use_module(library(http/mimetype)). 50:- use_module(library(dcg/basics)). 51:- use_module(library(http/http_dispatch)). 52:- use_module(library(http/http_hook)). 53:- use_module(library(http/http_path)). 54:- use_module(library(http/http_wrapper)). 55:- use_module(library(uri)). 56:- use_module(library(debug)). 57:- use_module(library(lists)). 58:- use_module(library(url)). 59:- use_module(library(socket)). 60:- use_module(library(option)). 61:- use_module(library(error)). 62:- use_module(library(www_browser)). 63:- use_module(pldoc(doc_process)). 64:- use_module(pldoc(doc_htmlsrc)). 65:- use_module(pldoc(doc_html)). 66:- use_module(pldoc(doc_index)). 67:- use_module(pldoc(doc_search)). 68:- use_module(pldoc(doc_man)). 69:- use_module(pldoc(doc_wiki)). 70:- use_module(pldoc(doc_util)). 71:- use_module(pldoc(doc_access)). 72:- use_module(pldoc(doc_pack)). 73:- use_module(pldoc(man_index)).
82:- dynamic 83 doc_server_port/1, 84 doc_enabled/0. 85 86httplocation(pldoc, root(pldoc), []). 87httplocation(pldoc_man, pldoc(refman), []). 88httplocation(pldoc_pkg, pldoc(package), []). 89httplocation(pldoc_resource, Path, []) :- 90 http_location_by_id(pldoc_resource, Path).
98doc_enable(true) :- 99 ( doc_enabled 100 -> true 101 ; assertz(doc_enabled) 102 ). 103doc_enable(false) :- 104 retractall(doc_enabled).
ip(A,B,C,D)
. See tcp_host_to_address/2 for
details.allow(HostOrIP)
.true
.The predicate doc_server/1 is defined as below, which provides a good default for development.
doc_server(Port) :- doc_server(Port, [ allow(localhost) ]).
140doc_server(Port) :- 141 doc_server(Port, 142 [ allow(localhost), 143 allow(ip(127,0,0,1)) % Windows ip-->host often fails 144 ]). 145 146doc_server(Port, _) :- 147 doc_enable(true), 148 catch(doc_current_server(Port), _, fail), 149 !. 150:- if(current_predicate(http_server/2)). 151doc_server(Port, Options) :- 152 doc_enable(true), 153 prepare_editor, 154 host_access_options(Options, ServerOptions), 155 http_absolute_location(pldoc('.'), Entry, []), 156 merge_options(ServerOptions, 157 [ port(Port), 158 entry_page(Entry) 159 ], HTTPOptions), 160 http_server(http_dispatch, HTTPOptions), 161 assertz(doc_server_port(Port)). 162:- endif.
175doc_current_server(Port) :- 176 ( doc_server_port(P) 177 -> Port = P 178 ; http_current_server(_:_, P) 179 -> Port = P 180 ; existence_error(http_server, pldoc) 181 ). 182 183:- if(\+current_predicate(http_current_server/2)). 184http_current_server(_,_) :- fail. 185:- endif.
192doc_browser :- 193 doc_browser([]). 194doc_browser(Spec) :- 195 catch(doc_current_server(Port), 196 error(existence_error(http_server, pldoc), _), 197 doc_server(Port)), 198 browser_url(Spec, Request), 199 format(string(URL), 'http://localhost:~w~w', [Port, Request]), 200 www_open_url(URL). 201 202browser_url([], Root) :- 203 !, 204 http_location_by_id(pldoc_root, Root). 205browser_url(Name, URL) :- 206 atom(Name), 207 !, 208 browser_url(Name/_, URL). 209browser_url(Name//Arity, URL) :- 210 must_be(atom, Name), 211 integer(Arity), 212 !, 213 PredArity is Arity+2, 214 browser_url(Name/PredArity, URL). 215browser_url(Name/Arity, URL) :- 216 !, 217 must_be(atom, Name), 218 ( man_object_property(Name/Arity, summary(_)) 219 -> format(string(S), '~q/~w', [Name, Arity]), 220 http_link_to_id(pldoc_man, [predicate=S], URL) 221 ; browser_url(_:Name/Arity, URL) 222 ). 223browser_url(Spec, URL) :- 224 !, 225 Spec = M:Name/Arity, 226 doc_comment(Spec, _Pos, _Summary, _Comment), 227 !, 228 ( var(M) 229 -> format(string(S), '~q/~w', [Name, Arity]) 230 ; format(string(S), '~q:~q/~w', [M, Name, Arity]) 231 ), 232 http_link_to_id(pldoc_object, [object=S], URL).
239prepare_editor :- 240 current_prolog_flag(editor, pce_emacs), 241 !, 242 start_emacs. 243prepare_editor. 244 245 246 /******************************* 247 * USER REPLIES * 248 *******************************/ 249 250:- http_handler(pldoc(.), pldoc_root, 251 [ prefix, 252 authentication(pldoc(read)), 253 condition(doc_enabled) 254 ]). 255:- http_handler(pldoc('index.html'), pldoc_index, []). 256:- http_handler(pldoc(file), pldoc_file, []). 257:- http_handler(pldoc(place), go_place, []). 258:- http_handler(pldoc(edit), pldoc_edit, 259 [authentication(pldoc(edit))]). 260:- http_handler(pldoc(doc), pldoc_doc, [prefix]). 261:- http_handler(pldoc(man), pldoc_man, []). 262:- http_handler(pldoc(doc_for), pldoc_object, [id(pldoc_doc_for)]). 263:- http_handler(pldoc(search), pldoc_search, []). 264:- http_handler(pldoc('res/'), pldoc_resource, [prefix]).
274pldoc_root(Request) :- 275 http_parameters(Request, 276 [ empty(Empty, [ oneof([true,false]), 277 default(false) 278 ]) 279 ]), 280 pldoc_root(Request, Empty). 281 282pldoc_root(Request, false) :- 283 http_location_by_id(pldoc_root, Root), 284 memberchk(path(Path), Request), 285 Root \== Path, 286 !, 287 existence_error(http_location, Path). 288pldoc_root(_Request, false) :- 289 working_directory(Dir0, Dir0), 290 allowed_directory(Dir0), 291 !, 292 ensure_slash_end(Dir0, Dir1), 293 doc_file_href(Dir1, Ref0), 294 atom_concat(Ref0, 'index.html', Index), 295 throw(http_reply(see_other(Index))). 296pldoc_root(Request, _) :- 297 pldoc_index(Request).
305pldoc_index(_Request) :-
306 reply_html_page(pldoc(index),
307 title('SWI-Prolog documentation'),
308 [ \doc_links('', []),
309 h1('SWI-Prolog documentation'),
310 \man_overview([])
311 ]).
318pldoc_file(Request) :-
319 http_parameters(Request,
320 [ file(File, [])
321 ]),
322 ( source_file(File)
323 -> true
324 ; throw(http_reply(forbidden(File)))
325 ),
326 doc_for_file(File, []).
localhost
. The call can
edit files using the file
attribute or a predicate if both
name
and arity
is given and optionally module
.336pldoc_edit(Request) :- 337 http:authenticate(pldoc(edit), Request, _), 338 http_parameters(Request, 339 [ file(File, 340 [ optional(true), 341 description('Name of the file to edit') 342 ]), 343 line(Line, 344 [ optional(true), 345 integer, 346 description('Line in the file') 347 ]), 348 name(Name, 349 [ optional(true), 350 description('Name of a Prolog predicate to edit') 351 ]), 352 arity(Arity, 353 [ integer, 354 optional(true), 355 description('Arity of a Prolog predicate to edit') 356 ]), 357 module(Module, 358 [ optional(true), 359 description('Name of a Prolog module to search for predicate') 360 ]) 361 ]), 362 ( atom(File) 363 -> allowed_file(File) 364 ; true 365 ), 366 ( atom(File), integer(Line) 367 -> Edit = file(File, line(Line)) 368 ; atom(File) 369 -> Edit = file(File) 370 ; atom(Name), integer(Arity) 371 -> ( atom(Module) 372 -> Edit = (Module:Name/Arity) 373 ; Edit = (Name/Arity) 374 ) 375 ), 376 edit(Edit), 377 format('Content-type: text/plain~n~n'), 378 format('Started ~q~n', [edit(Edit)]). 379pldoc_edit(_Request) :- 380 http_location_by_id(pldoc_edit, Location), 381 throw(http_reply(forbidden(Location))).
388go_place(Request) :- 389 http_parameters(Request, 390 [ place(Place, []) 391 ]), 392 places(Place). 393 394places(':packs:') :- 395 !, 396 http_link_to_id(pldoc_pack, [], HREF), 397 throw(http_reply(moved(HREF))). 398places(Dir0) :- 399 expand_alias(Dir0, Dir), 400 ( allowed_directory(Dir) 401 -> format(string(IndexFile), '~w/index.html', [Dir]), 402 doc_file_href(IndexFile, HREF), 403 throw(http_reply(moved(HREF))) 404 ; throw(http_reply(forbidden(Dir))) 405 ).
412allowed_directory(Dir) :- 413 source_directory(Dir), 414 !. 415allowed_directory(Dir) :- 416 working_directory(CWD, CWD), 417 same_file(CWD, Dir). 418allowed_directory(Dir) :- 419 prolog:doc_directory(Dir).
427allowed_file(File) :- 428 source_file(_, File), 429 !. 430allowed_file(File) :- 431 absolute_file_name(File, Canonical), 432 file_directory_name(Canonical, Dir), 433 allowed_directory(Dir).
440pldoc_resource(Request) :- 441 http_location_by_id(pldoc_resource, ResRoot), 442 memberchk(path(Path), Request), 443 atom_concat(ResRoot, File, Path), 444 file(File, Local), 445 http_reply_file(pldoc(Local), [], Request). 446 447file('pldoc.css', 'pldoc.css'). 448file('pllisting.css', 'pllisting.css'). 449file('pldoc.js', 'pldoc.js'). 450file('edit.png', 'edit.png'). 451file('editpred.png', 'editpred.png'). 452file('up.gif', 'up.gif'). 453file('source.png', 'source.png'). 454file('public.png', 'public.png'). 455file('private.png', 'private.png'). 456file('reload.png', 'reload.png'). 457file('favicon.ico', 'favicon.ico'). 458file('h1-bg.png', 'h1-bg.png'). 459file('h2-bg.png', 'h2-bg.png'). 460file('pub-bg.png', 'pub-bg.png'). 461file('priv-bg.png', 'priv-bg.png'). 462file('multi-bg.png', 'multi-bg.png').
Reply documentation of a file. Path is the absolute path of the file for which to return the documentation. Extension is either none, the Prolog extension or the HTML extension.
Note that we reply with pldoc.css if the file basename is pldoc.css to allow for a relative link from any directory.
476pldoc_doc(Request) :- 477 memberchk(path(ReqPath), Request), 478 http_location_by_id(pldoc_doc, Me), 479 atom_concat(Me, AbsFile0, ReqPath), 480 ( sub_atom(ReqPath, _, _, 0, /) 481 -> atom_concat(ReqPath, 'index.html', File), 482 throw(http_reply(moved(File))) 483 ; clean_path(AbsFile0, AbsFile1), 484 expand_alias(AbsFile1, AbsFile), 485 is_absolute_file_name(AbsFile) 486 -> documentation(AbsFile, Request) 487 ). 488 489documentation(Path, Request) :- 490 file_base_name(Path, Base), 491 file(_, Base), % serve pldoc.css, etc. 492 !, 493 http_reply_file(pldoc(Base), [], Request). 494documentation(Path, Request) :- 495 file_name_extension(_, Ext, Path), 496 autolink_extension(Ext, image), 497 http_reply_file(Path, [unsafe(true)], Request). 498documentation(Path, Request) :- 499 Index = '/index.html', 500 sub_atom(Path, _, _, 0, Index), 501 atom_concat(Dir, Index, Path), 502 exists_directory(Dir), % Directory index 503 !, 504 ( allowed_directory(Dir) 505 -> edit_options(Request, EditOptions), 506 doc_for_dir(Dir, EditOptions) 507 ; throw(http_reply(forbidden(Dir))) 508 ). 509documentation(File, Request) :- 510 wiki_file(File, WikiFile), 511 !, 512 ( allowed_file(WikiFile) 513 -> true 514 ; throw(http_reply(forbidden(File))) 515 ), 516 edit_options(Request, Options), 517 doc_for_wiki_file(WikiFile, Options). 518documentation(Path, Request) :- 519 pl_file(Path, File), 520 !, 521 ( allowed_file(File) 522 -> true 523 ; throw(http_reply(forbidden(File))) 524 ), 525 doc_reply_file(File, Request). 526documentation(Path, _) :- 527 throw(http_reply(not_found(Path))). 528 529:- public 530 doc_reply_file/2. 531 532doc_reply_file(File, Request) :- 533 http_parameters(Request, 534 [ public_only(Public), 535 reload(Reload), 536 show(Show), 537 format_comments(FormatComments) 538 ], 539 [ attribute_declarations(param) 540 ]), 541 ( exists_file(File) 542 -> true 543 ; throw(http_reply(not_found(File))) 544 ), 545 ( Reload == true, 546 source_file(File) 547 -> load_files(File, [if(changed), imports([])]) 548 ; true 549 ), 550 edit_options(Request, EditOptions), 551 ( Show == src 552 -> format('Content-type: text/html~n~n', []), 553 source_to_html(File, stream(current_output), 554 [ skin(src_skin(Request, Show, FormatComments)), 555 format_comments(FormatComments) 556 ]) 557 ; Show == raw 558 -> http_reply_file(File, 559 [ unsafe(true), % is already validated 560 mime_type(text/plain) 561 ], Request) 562 ; doc_for_file(File, 563 [ public_only(Public), 564 source_link(true) 565 | EditOptions 566 ]) 567 ). 568 569 570:- public src_skin/5. % called through source_to_html/3. 571 572src_skin(Request, _Show, FormatComments, header, Out) :- 573 memberchk(request_uri(ReqURI), Request), 574 negate(FormatComments, AltFormatComments), 575 replace_parameters(ReqURI, [show(raw)], RawLink), 576 replace_parameters(ReqURI, [format_comments(AltFormatComments)], CmtLink), 577 phrase(html(div(class(src_formats), 578 [ 'View source with ', 579 a(href(CmtLink), \alt_view(AltFormatComments)), 580 ' or as ', 581 a(href(RawLink), raw) 582 ])), Tokens), 583 print_html(Out, Tokens). 584 585alt_view(true) --> 586 html('formatted comments'). 587alt_view(false) --> 588 html('raw comments'). 589 590negate(true, false). 591negate(false, true). 592 593replace_parameters(ReqURI, Extra, URI) :- 594 uri_components(ReqURI, C0), 595 uri_data(search, C0, Search0), 596 ( var(Search0) 597 -> uri_query_components(Search, Extra) 598 ; uri_query_components(Search0, Form0), 599 merge_options(Extra, Form0, Form), 600 uri_query_components(Search, Form) 601 ), 602 uri_data(search, C0, Search, C), 603 uri_components(URI, C).
edit(true)
in Options if the connection is from the
localhost.611edit_options(Request, [edit(true)]) :- 612 catch(http:authenticate(pldoc(edit), Request, _), _, fail), 613 !. 614edit_options(_, []).
619pl_file(File, PlFile) :- 620 file_name_extension(Base, html, File), 621 !, 622 absolute_file_name(Base, 623 PlFile, 624 [ file_errors(fail), 625 file_type(prolog), 626 access(read) 627 ]). 628pl_file(File, File).
635wiki_file(File, TxtFile) :- 636 file_name_extension(_, Ext, File), 637 wiki_file_extension(Ext), 638 !, 639 TxtFile = File. 640wiki_file(File, TxtFile) :- 641 file_base_name(File, Base), 642 autolink_file(Base, wiki), 643 !, 644 TxtFile = File. 645wiki_file(File, TxtFile) :- 646 file_name_extension(Base, html, File), 647 wiki_file_extension(Ext), 648 file_name_extension(Base, Ext, TxtFile), 649 access_file(TxtFile, read). 650 651wiki_file_extension(md). 652wiki_file_extension(txt).
659clean_path(Path0, Path) :- 660 current_prolog_flag(windows, true), 661 sub_atom(Path0, 2, _, _, :), 662 !, 663 sub_atom(Path0, 1, _, 0, Path). 664clean_path(Path, Path).
678pldoc_man(Request) :- 679 http_parameters(Request, 680 [ predicate(PI, [optional(true)]), 681 function(Fun, [optional(true)]), 682 'CAPI'(F, [optional(true)]), 683 section(Sec, [optional(true)]) 684 ]), 685 ( ground(PI) 686 -> atom_pi(PI, Obj) 687 ; ground(Fun) 688 -> atomic_list_concat([Name,ArityAtom], /, Fun), 689 atom_number(ArityAtom, Arity), 690 Obj = f(Name/Arity) 691 ; ground(F) 692 -> Obj = c(F) 693 ; ground(Sec) 694 -> atom_concat('sec:', Sec, SecID), 695 Obj = section(SecID) 696 ), 697 man_title(Obj, Title), 698 reply_html_page( 699 pldoc(object(Obj)), 700 title(Title), 701 \man_page(Obj, [])). 702 703man_title(f(Obj), Title) :- 704 !, 705 format(atom(Title), 'SWI-Prolog -- function ~w', [Obj]). 706man_title(c(Obj), Title) :- 707 !, 708 format(atom(Title), 'SWI-Prolog -- API-function ~w', [Obj]). 709man_title(section(_Id), Title) :- 710 !, 711 format(atom(Title), 'SWI-Prolog -- Manual', []). 712man_title(Obj, Title) :- 713 format(atom(Title), 'SWI-Prolog -- ~w', [Obj]).
720pldoc_object(Request) :-
721 http_parameters(Request,
722 [ object(Atom, []),
723 header(Header, [default(true)])
724 ]),
725 ( catch(atom_to_term(Atom, Obj, _), error(_,_), fail)
726 -> true
727 ; atom_to_object(Atom, Obj)
728 ),
729 ( prolog:doc_object_title(Obj, Title)
730 -> true
731 ; Title = Atom
732 ),
733 edit_options(Request, EditOptions),
734 reply_html_page(
735 pldoc(object(Obj)),
736 title(Title),
737 \object_page(Obj, [header(Header)|EditOptions])).
744pldoc_search(Request) :- 745 http_parameters(Request, 746 [ for(For, 747 [ optional(true), 748 description('String to search for') 749 ]), 750 page(Page, 751 [ integer, 752 default(1), 753 description('Page of search results to view') 754 ]), 755 in(In, 756 [ oneof([all,app,noapp,man,lib,pack,wiki]), 757 default(all), 758 description('Search everying, application only or manual only') 759 ]), 760 match(Match, 761 [ oneof([name,summary]), 762 default(summary), 763 description('Match only the name or also the summary') 764 ]), 765 resultFormat(Format, 766 [ oneof(long,summary), 767 default(summary), 768 description('Return full documentation or summary-lines') 769 ]) 770 ]), 771 edit_options(Request, EditOptions), 772 format(string(Title), 'Prolog search -- ~w', [For]), 773 reply_html_page(pldoc(search(For)), 774 title(Title), 775 \search_reply(For, 776 [ resultFormat(Format), 777 search_in(In), 778 search_match(Match), 779 page(Page) 780 | EditOptions 781 ])). 782 783 784 /******************************* 785 * HTTP PARAMETER TYPES * 786 *******************************/ 787 788:- public 789 param/2. % used in pack documentation server 790 791param(public_only, 792 [ boolean, 793 default(true), 794 description('If true, hide private predicates') 795 ]). 796param(reload, 797 [ boolean, 798 default(false), 799 description('Reload the file and its documentation') 800 ]). 801param(show, 802 [ oneof([doc,src,raw]), 803 default(doc), 804 description('How to show the file') 805 ]). 806param(format_comments, 807 [ boolean, 808 default(true), 809 description('If true, use PlDoc for rendering structured comments') 810 ])
Documentation server
The module
library(pldoc/http)
provides an embedded HTTP documentation server that allows for browsing the documentation of all files loaded after library(pldoc) has been loaded. */