35
36:- module(pldoc_http,
37 [ doc_enable/1, 38 doc_server/1, 39 doc_server/2, 40 doc_browser/0,
41 doc_browser/1 42 ]). 43:- use_module(library(pldoc)). 44:- use_module(library(http/thread_httpd)). 45:- use_module(library(http/http_parameters)). 46:- use_module(library(http/html_write)). 47:- use_module(library(http/mimetype)). 48:- use_module(library(dcg/basics)). 49:- use_module(library(http/http_dispatch)). 50:- use_module(library(http/http_hook)). 51:- use_module(library(http/http_path)). 52:- use_module(library(http/http_wrapper)). 53:- use_module(library(uri)). 54:- use_module(library(debug)). 55:- use_module(library(lists)). 56:- use_module(library(url)). 57:- use_module(library(socket)). 58:- use_module(library(option)). 59:- use_module(library(error)). 60:- use_module(library(www_browser)). 61:- use_module(pldoc(doc_process)). 62:- use_module(pldoc(doc_htmlsrc)). 63:- use_module(pldoc(doc_html)). 64:- use_module(pldoc(doc_index)). 65:- use_module(pldoc(doc_search)). 66:- use_module(pldoc(doc_man)). 67:- use_module(pldoc(doc_wiki)). 68:- use_module(pldoc(doc_util)). 69:- use_module(pldoc(doc_access)). 70:- use_module(pldoc(doc_pack)). 71
78
79:- dynamic
80 doc_server_port/1,
81 doc_enabled/0. 82
83http:location(pldoc, root(pldoc), []).
84http:location(pldoc_man, pldoc(refman), []).
85http:location(pldoc_pkg, pldoc(package), []).
86http:location(pldoc_resource, Path, []) :-
87 http_location_by_id(pldoc_resource, Path).
88
94
95doc_enable(true) :-
96 ( doc_enabled
97 -> true
98 ; assertz(doc_enabled)
99 ).
100doc_enable(false) :-
101 retractall(doc_enabled).
102
136
137doc_server(Port) :-
138 doc_server(Port,
139 [ allow(localhost),
140 allow(ip(127,0,0,1)) 141 ]).
142
143doc_server(Port, _) :-
144 doc_enable(true),
145 catch(doc_current_server(Port), _, fail),
146 !.
147doc_server(Port, Options) :-
148 doc_enable(true),
149 prepare_editor,
150 host_access_options(Options, ServerOptions),
151 http_absolute_location(pldoc('.'), Entry, []),
152 merge_options(ServerOptions,
153 [ port(Port),
154 entry_page(Entry)
155 ], HTTPOptions),
156 http_server(http_dispatch, HTTPOptions),
157 assertz(doc_server_port(Port)).
158
169
170doc_current_server(Port) :-
171 ( doc_server_port(P)
172 -> Port = P
173 ; http_current_server(_:_, P)
174 -> Port = P
175 ; existence_error(http_server, pldoc)
176 ).
177
182
183doc_browser :-
184 doc_browser([]).
185doc_browser(Spec) :-
186 catch(doc_current_server(Port),
187 error(existence_error(http_server, pldoc), _),
188 doc_server(Port)),
189 browser_url(Spec, Request),
190 format(string(URL), 'http://localhost:~w~w', [Port, Request]),
191 www_open_url(URL).
192
193browser_url([], Root) :-
194 !,
195 http_location_by_id(pldoc_root, Root).
196browser_url(Name, URL) :-
197 atom(Name),
198 !,
199 browser_url(Name/_, URL).
200browser_url(Name//Arity, URL) :-
201 must_be(atom, Name),
202 integer(Arity),
203 !,
204 PredArity is Arity+2,
205 browser_url(Name/PredArity, URL).
206browser_url(Name/Arity, URL) :-
207 !,
208 must_be(atom, Name),
209 ( predicate(Name, Arity, _, _, _)
210 -> format(string(S), '~q/~w', [Name, Arity]),
211 http_link_to_id(pldoc_man, [predicate=S], URL)
212 ; browser_url(_:Name/Arity, URL)
213 ).
214browser_url(Spec, URL) :-
215 !,
216 Spec = M:Name/Arity,
217 doc_comment(Spec, _Pos, _Summary, _Comment),
218 !,
219 ( var(M)
220 -> format(string(S), '~q/~w', [Name, Arity])
221 ; format(string(S), '~q:~q/~w', [M, Name, Arity])
222 ),
223 http_link_to_id(pldoc_object, [object=S], URL).
224
229
230prepare_editor :-
231 current_prolog_flag(editor, pce_emacs),
232 !,
233 start_emacs.
234prepare_editor.
235
236
237 240
241:- http_handler(pldoc(.), pldoc_root,
242 [ prefix,
243 authentication(pldoc(read)),
244 condition(doc_enabled)
245 ]). 246:- http_handler(pldoc('index.html'), pldoc_index, []). 247:- http_handler(pldoc(file), pldoc_file, []). 248:- http_handler(pldoc(place), go_place, []). 249:- http_handler(pldoc(edit), pldoc_edit,
250 [authentication(pldoc(edit))]). 251:- http_handler(pldoc(doc), pldoc_doc, [prefix]). 252:- http_handler(pldoc(man), pldoc_man, []). 253:- http_handler(pldoc(doc_for), pldoc_object, [id(pldoc_doc_for)]). 254:- http_handler(pldoc(search), pldoc_search, []). 255:- http_handler(pldoc('res/'), pldoc_resource, [prefix]). 256
257
264
265pldoc_root(Request) :-
266 http_parameters(Request,
267 [ empty(Empty, [ oneof([true,false]),
268 default(false)
269 ])
270 ]),
271 pldoc_root(Request, Empty).
272
273pldoc_root(Request, false) :-
274 http_location_by_id(pldoc_root, Root),
275 memberchk(path(Path), Request),
276 Root \== Path,
277 !,
278 existence_error(http_location, Path).
279pldoc_root(_Request, false) :-
280 working_directory(Dir0, Dir0),
281 allowed_directory(Dir0),
282 !,
283 ensure_slash_end(Dir0, Dir1),
284 doc_file_href(Dir1, Ref0),
285 atom_concat(Ref0, 'index.html', Index),
286 throw(http_reply(see_other(Index))).
287pldoc_root(Request, _) :-
288 pldoc_index(Request).
289
290
295
296pldoc_index(_Request) :-
297 reply_html_page(pldoc(index),
298 title('SWI-Prolog documentation'),
299 [ \doc_links('', []),
300 h1('SWI-Prolog documentation'),
301 \man_overview([])
302 ]).
303
304
308
309pldoc_file(Request) :-
310 http_parameters(Request,
311 [ file(File, [])
312 ]),
313 ( source_file(File)
314 -> true
315 ; throw(http_reply(forbidden(File)))
316 ),
317 doc_for_file(File, []).
318
326
327pldoc_edit(Request) :-
328 http:authenticate(pldoc(edit), Request, _),
329 http_parameters(Request,
330 [ file(File,
331 [ optional(true),
332 description('Name of the file to edit')
333 ]),
334 line(Line,
335 [ optional(true),
336 integer,
337 description('Line in the file')
338 ]),
339 name(Name,
340 [ optional(true),
341 description('Name of a Prolog predicate to edit')
342 ]),
343 arity(Arity,
344 [ integer,
345 optional(true),
346 description('Arity of a Prolog predicate to edit')
347 ]),
348 module(Module,
349 [ optional(true),
350 description('Name of a Prolog module to search for predicate')
351 ])
352 ]),
353 ( atom(File)
354 -> allowed_file(File)
355 ; true
356 ),
357 ( atom(File), integer(Line)
358 -> Edit = file(File, line(Line))
359 ; atom(File)
360 -> Edit = file(File)
361 ; atom(Name), integer(Arity)
362 -> ( atom(Module)
363 -> Edit = (Module:Name/Arity)
364 ; Edit = (Name/Arity)
365 )
366 ),
367 edit(Edit),
368 format('Content-type: text/plain~n~n'),
369 format('Started ~q~n', [edit(Edit)]).
370pldoc_edit(_Request) :-
371 http_location_by_id(pldoc_edit, Location),
372 throw(http_reply(forbidden(Location))).
373
374
378
379go_place(Request) :-
380 http_parameters(Request,
381 [ place(Place, [])
382 ]),
383 places(Place).
384
385places(':packs:') :-
386 !,
387 http_link_to_id(pldoc_pack, [], HREF),
388 throw(http_reply(moved(HREF))).
389places(Dir0) :-
390 expand_alias(Dir0, Dir),
391 ( allowed_directory(Dir)
392 -> format(string(IndexFile), '~w/index.html', [Dir]),
393 doc_file_href(IndexFile, HREF),
394 throw(http_reply(moved(HREF)))
395 ; throw(http_reply(forbidden(Dir)))
396 ).
397
398
402
403allowed_directory(Dir) :-
404 source_directory(Dir),
405 !.
406allowed_directory(Dir) :-
407 working_directory(CWD, CWD),
408 same_file(CWD, Dir).
409allowed_directory(Dir) :-
410 prolog:doc_directory(Dir).
411
412
417
418allowed_file(File) :-
419 source_file(_, File),
420 !.
421allowed_file(File) :-
422 absolute_file_name(File, Canonical),
423 file_directory_name(Canonical, Dir),
424 allowed_directory(Dir).
425
426
430
431pldoc_resource(Request) :-
432 http_location_by_id(pldoc_resource, ResRoot),
433 memberchk(path(Path), Request),
434 atom_concat(ResRoot, File, Path),
435 file(File, Local),
436 http_reply_file(pldoc(Local), [], Request).
437
438file('pldoc.css', 'pldoc.css').
439file('pllisting.css', 'pllisting.css').
440file('pldoc.js', 'pldoc.js').
441file('edit.png', 'edit.png').
442file('editpred.png', 'editpred.png').
443file('up.gif', 'up.gif').
444file('source.png', 'source.png').
445file('public.png', 'public.png').
446file('private.png', 'private.png').
447file('reload.png', 'reload.png').
448file('favicon.ico', 'favicon.ico').
449file('h1-bg.png', 'h1-bg.png').
450file('h2-bg.png', 'h2-bg.png').
451file('pub-bg.png', 'pub-bg.png').
452file('priv-bg.png', 'priv-bg.png').
453file('multi-bg.png', 'multi-bg.png').
454
455
466
467pldoc_doc(Request) :-
468 memberchk(path(ReqPath), Request),
469 http_location_by_id(pldoc_doc, Me),
470 atom_concat(Me, AbsFile0, ReqPath),
471 ( sub_atom(ReqPath, _, _, 0, /)
472 -> atom_concat(ReqPath, 'index.html', File),
473 throw(http_reply(moved(File)))
474 ; clean_path(AbsFile0, AbsFile1),
475 expand_alias(AbsFile1, AbsFile),
476 is_absolute_file_name(AbsFile)
477 -> documentation(AbsFile, Request)
478 ).
479
480documentation(Path, Request) :-
481 file_base_name(Path, Base),
482 file(_, Base), 483 !,
484 http_reply_file(pldoc(Base), [], Request).
485documentation(Path, Request) :-
486 file_name_extension(_, Ext, Path),
487 autolink_extension(Ext, image),
488 http_reply_file(Path, [unsafe(true)], Request).
489documentation(Path, Request) :-
490 Index = '/index.html',
491 sub_atom(Path, _, _, 0, Index),
492 atom_concat(Dir, Index, Path),
493 exists_directory(Dir), 494 !,
495 ( allowed_directory(Dir)
496 -> edit_options(Request, EditOptions),
497 doc_for_dir(Dir, EditOptions)
498 ; throw(http_reply(forbidden(Dir)))
499 ).
500documentation(File, Request) :-
501 wiki_file(File, WikiFile),
502 !,
503 ( allowed_file(WikiFile)
504 -> true
505 ; throw(http_reply(forbidden(File)))
506 ),
507 edit_options(Request, Options),
508 doc_for_wiki_file(WikiFile, Options).
509documentation(Path, Request) :-
510 pl_file(Path, File),
511 !,
512 ( allowed_file(File)
513 -> true
514 ; throw(http_reply(forbidden(File)))
515 ),
516 doc_reply_file(File, Request).
517documentation(Path, _) :-
518 throw(http_reply(not_found(Path))).
519
520:- public
521 doc_reply_file/2. 522
523doc_reply_file(File, Request) :-
524 http_parameters(Request,
525 [ public_only(Public),
526 reload(Reload),
527 show(Show),
528 format_comments(FormatComments)
529 ],
530 [ attribute_declarations(param)
531 ]),
532 ( exists_file(File)
533 -> true
534 ; throw(http_reply(not_found(File)))
535 ),
536 ( Reload == true,
537 source_file(File)
538 -> load_files(File, [if(changed), imports([])])
539 ; true
540 ),
541 edit_options(Request, EditOptions),
542 ( Show == src
543 -> format('Content-type: text/html~n~n', []),
544 source_to_html(File, stream(current_output),
545 [ skin(src_skin(Request, Show, FormatComments)),
546 format_comments(FormatComments)
547 ])
548 ; Show == raw
549 -> http_reply_file(File,
550 [ unsafe(true), 551 mime_type(text/plain)
552 ], Request)
553 ; doc_for_file(File,
554 [ public_only(Public),
555 source_link(true)
556 | EditOptions
557 ])
558 ).
559
560
561:- public src_skin/5. 562
563src_skin(Request, _Show, FormatComments, header, Out) :-
564 memberchk(request_uri(ReqURI), Request),
565 negate(FormatComments, AltFormatComments),
566 replace_parameters(ReqURI, [show(raw)], RawLink),
567 replace_parameters(ReqURI, [format_comments(AltFormatComments)], CmtLink),
568 phrase(html(div(class(src_formats),
569 [ 'View source with ',
570 a(href(CmtLink), \alt_view(AltFormatComments)),
571 ' or as ',
572 a(href(RawLink), raw)
573 ])), Tokens),
574 print_html(Out, Tokens).
575
576alt_view(true) -->
577 html('formatted comments').
578alt_view(false) -->
579 html('raw comments').
580
581negate(true, false).
582negate(false, true).
583
584replace_parameters(ReqURI, Extra, URI) :-
585 uri_components(ReqURI, C0),
586 uri_data(search, C0, Search0),
587 ( var(Search0)
588 -> uri_query_components(Search, Extra)
589 ; uri_query_components(Search0, Form0),
590 merge_options(Extra, Form0, Form),
591 uri_query_components(Search, Form)
592 ),
593 uri_data(search, C0, Search, C),
594 uri_components(URI, C).
595
596
601
602edit_options(Request, [edit(true)]) :-
603 catch(http:authenticate(pldoc(edit), Request, _), _, fail),
604 !.
605edit_options(_, []).
606
607
609
610pl_file(File, PlFile) :-
611 file_name_extension(Base, html, File),
612 !,
613 absolute_file_name(Base,
614 PlFile,
615 [ file_errors(fail),
616 file_type(prolog),
617 access(read)
618 ]).
619pl_file(File, File).
620
625
626wiki_file(File, TxtFile) :-
627 file_name_extension(_, Ext, File),
628 wiki_file_extension(Ext),
629 !,
630 TxtFile = File.
631wiki_file(File, TxtFile) :-
632 file_base_name(File, Base),
633 autolink_file(Base, wiki),
634 !,
635 TxtFile = File.
636wiki_file(File, TxtFile) :-
637 file_name_extension(Base, html, File),
638 wiki_file_extension(Ext),
639 file_name_extension(Base, Ext, TxtFile),
640 access_file(TxtFile, read).
641
642wiki_file_extension(md).
643wiki_file_extension(txt).
644
645
649
650clean_path(Path0, Path) :-
651 current_prolog_flag(windows, true),
652 sub_atom(Path0, 2, _, _, :),
653 !,
654 sub_atom(Path0, 1, _, 0, Path).
655clean_path(Path, Path).
656
657
668
669pldoc_man(Request) :-
670 http_parameters(Request,
671 [ predicate(PI, [optional(true)]),
672 function(Fun, [optional(true)]),
673 'CAPI'(F, [optional(true)]),
674 section(Sec, [optional(true)])
675 ]),
676 ( ground(PI)
677 -> atom_pi(PI, Obj)
678 ; ground(Fun)
679 -> atomic_list_concat([Name,ArityAtom], /, Fun),
680 atom_number(ArityAtom, Arity),
681 Obj = f(Name/Arity)
682 ; ground(F)
683 -> Obj = c(F)
684 ; ground(Sec)
685 -> atom_concat('sec:', Sec, SecID),
686 Obj = section(SecID)
687 ),
688 man_title(Obj, Title),
689 reply_html_page(
690 pldoc(object(Obj)),
691 title(Title),
692 \man_page(Obj, [])).
693
694man_title(f(Obj), Title) :-
695 !,
696 format(atom(Title), 'SWI-Prolog -- function ~w', [Obj]).
697man_title(c(Obj), Title) :-
698 !,
699 format(atom(Title), 'SWI-Prolog -- API-function ~w', [Obj]).
700man_title(section(_Id), Title) :-
701 !,
702 format(atom(Title), 'SWI-Prolog -- Manual', []).
703man_title(Obj, Title) :-
704 format(atom(Title), 'SWI-Prolog -- ~w', [Obj]).
705
710
711pldoc_object(Request) :-
712 http_parameters(Request,
713 [ object(Atom, []),
714 header(Header, [default(true)])
715 ]),
716 atom_to_term(Atom, Obj, _),
717 ( prolog:doc_object_title(Obj, Title)
718 -> true
719 ; Title = Atom
720 ),
721 edit_options(Request, EditOptions),
722 reply_html_page(
723 pldoc(object(Obj)),
724 title(Title),
725 \object_page(Obj, [header(Header)|EditOptions])).
726
727
731
732pldoc_search(Request) :-
733 http_parameters(Request,
734 [ for(For,
735 [ optional(true),
736 description('String to search for')
737 ]),
738 page(Page,
739 [ integer,
740 default(1),
741 description('Page of search results to view')
742 ]),
743 in(In,
744 [ oneof([all,app,noapp,man,lib,pack,wiki]),
745 default(all),
746 description('Search everying, application only or manual only')
747 ]),
748 match(Match,
749 [ oneof([name,summary]),
750 default(summary),
751 description('Match only the name or also the summary')
752 ]),
753 resultFormat(Format,
754 [ oneof(long,summary),
755 default(summary),
756 description('Return full documentation or summary-lines')
757 ])
758 ]),
759 edit_options(Request, EditOptions),
760 format(string(Title), 'Prolog search -- ~w', [For]),
761 reply_html_page(pldoc(search(For)),
762 title(Title),
763 \search_reply(For,
764 [ resultFormat(Format),
765 search_in(In),
766 search_match(Match),
767 page(Page)
768 | EditOptions
769 ])).
770
771
772 775
776:- public
777 param/2. 778
779param(public_only,
780 [ boolean,
781 default(true),
782 description('If true, hide private predicates')
783 ]).
784param(reload,
785 [ boolean,
786 default(false),
787 description('Reload the file and its documentation')
788 ]).
789param(show,
790 [ oneof([doc,src,raw]),
791 default(doc),
792 description('How to show the file')
793 ]).
794param(format_comments,
795 [ boolean,
796 default(true),
797 description('If true, use PlDoc for rendering structured comments')
798 ])