]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/wikifs/tohtml.c
kernel: make noswap flag exclude processes from killbig() if not eve, reset noswap...
[plan9front.git] / sys / src / cmd / wikifs / tohtml.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <String.h>
5 #include <thread.h>
6 #include "wiki.h"
7
8 /*
9  * Get HTML and text templates from underlying file system.
10  * Caches them, which means changes don't take effect for
11  * up to Tcache seconds after they are made.
12  * 
13  * If the files are deleted, we keep returning the last
14  * known copy.
15  */
16 enum {
17         WAIT = 60
18 };
19
20 static char *name[2*Ntemplate] = {
21  [Tpage]                "page.html",
22  [Tedit]                "edit.html",
23  [Tdiff]                "diff.html",
24  [Thistory]             "history.html",
25  [Tnew]         "new.html",
26  [Toldpage]     "oldpage.html",
27  [Twerror]              "werror.html",
28  [Ntemplate+Tpage]      "page.txt",
29  [Ntemplate+Tdiff]      "diff.txt",
30  [Ntemplate+Thistory]   "history.txt",
31  [Ntemplate+Toldpage]   "oldpage.txt",
32  [Ntemplate+Twerror]    "werror.txt",
33 };
34
35 static struct {
36         RWLock;
37         String *s;
38         ulong t;
39         Qid qid;
40 } cache[2*Ntemplate];
41
42 static void
43 cacheinit(void)
44 {
45         int i;
46         static int x;
47         static Lock l;
48
49         if(x)
50                 return;
51         lock(&l);
52         if(x){
53                 unlock(&l);
54                 return;
55         }
56
57         for(i=0; i<2*Ntemplate; i++)
58                 if(name[i])
59                         cache[i].s = s_copy("");
60         x = 1;
61         unlock(&l);
62 }
63
64 static String*
65 gettemplate(int type)
66 {
67         int n;
68         Biobuf *b;
69         Dir *d;
70         String *s, *ns;
71
72         if(name[type]==nil)
73                 return nil;
74
75         cacheinit();
76
77         rlock(&cache[type]);
78         if(0 && cache[type].t+Tcache >= time(0)){
79                 s = s_incref(cache[type].s);
80                 runlock(&cache[type]);
81                 return s;
82         }
83         runlock(&cache[type]);
84
85 //      d = nil;
86         wlock(&cache[type]);
87         if(0 && cache[type].t+Tcache >= time(0) || (d = wdirstat(name[type])) == nil)
88                 goto Return;
89
90         if(0 && d->qid.vers == cache[type].qid.vers && d->qid.path == cache[type].qid.path){
91                 cache[type].t = time(0);
92                 goto Return;
93         }
94
95         if((b = wBopen(name[type], OREAD)) == nil)
96                 goto Return;
97
98         ns = s_reset(nil);
99         do
100                 n = s_read(b, ns, Bsize);
101         while(n > 0);
102         Bterm(b);
103         if(n < 0) {
104                 s_free(ns);
105                 goto Return;
106         }
107
108         s_free(cache[type].s);
109         cache[type].s = ns;
110         cache[type].qid = d->qid;
111         cache[type].t = time(0);
112
113 Return:
114         free(d);
115         s = s_incref(cache[type].s);
116         wunlock(&cache[type]);
117         return s;
118 }
119
120         
121 /*
122  * Write wiki document in HTML.
123  */
124 static String*
125 s_escappend(String *s, char *p, int pre)
126 {
127         char *q;
128
129         while(q = strpbrk(p, pre ? "<>&" : " <>&")){
130                 s = s_nappend(s, p, q-p);
131                 switch(*q){
132                 case '<':
133                         s = s_append(s, "&lt;");
134                         break;
135                 case '>':
136                         s = s_append(s, "&gt;");
137                         break;
138                 case '&':
139                         s = s_append(s, "&amp;");
140                         break;
141                 case ' ':
142                         s = s_append(s, "\n");
143                 }
144                 p = q+1;
145         }
146         s = s_append(s, p);
147         return s;
148 }
149
150 static char*
151 mkurl(char *s, int ty)
152 {
153         char *p, *q;
154
155         if(strncmp(s, "http:", 5)==0
156         || strncmp(s, "https:", 6)==0
157         || strncmp(s, "#", 1)==0
158         || strncmp(s, "ftp:", 4)==0
159         || strncmp(s, "mailto:", 7)==0
160         || strncmp(s, "telnet:", 7)==0
161         || strncmp(s, "file:", 5)==0)
162                 return estrdup(s);
163
164         if(strchr(s, ' ')==nil && strchr(s, '@')!=nil){
165                 p = emalloc(strlen(s)+8);
166                 strcpy(p, "mailto:");
167                 strcat(p, s);
168                 return p;
169         }
170
171         if(ty == Toldpage)
172                 p = smprint("../../%s", s);
173         else
174                 p = smprint("../%s", s);
175
176         for(q=p; *q; q++)
177                 if(*q==' ')
178                         *q = '_';
179         return p;
180 }
181
182 int okayinlist[Nwtxt] =
183 {
184         [Wbullet]       1,
185         [Wlink] 1,
186         [Wman]  1,
187         [Wplain]        1,
188 };
189
190 int okayinpre[Nwtxt] =
191 {
192         [Wlink] 1,
193         [Wman]  1,
194         [Wpre]  1,
195 };
196
197 int okayinpara[Nwtxt] =
198 {
199         [Wpara] 1,
200         [Wlink] 1,
201         [Wman]  1,
202         [Wplain]        1,
203 };
204
205 char*
206 nospaces(char *s)
207 {
208         char *q;
209         s = strdup(s);
210         if(s == nil)
211                 return nil;
212         for(q=s; *q; q++)
213                 if(*q == ' ')
214                         *q = '_';
215         return s;
216 }
217         
218 String*
219 pagehtml(String *s, Wpage *wtxt, int ty)
220 {
221         char *p, tmp[40];
222         int inlist, inpara, inpre, t, tnext;
223         Wpage *w;
224
225         inlist = 0;
226         inpre = 0;
227         inpara = 0;
228
229         for(w=wtxt; w; w=w->next){
230                 t = w->type;
231                 tnext = Whr;
232                 if(w->next)
233                         tnext = w->next->type;
234
235                 if(inlist && !okayinlist[t]){
236                         inlist = 0;
237                         s = s_append(s, "\n</li>\n</ul>\n");
238                 }
239                 if(inpre && !okayinpre[t]){
240                         inpre = 0;
241                         s = s_append(s, "</pre>\n");
242                 }
243
244                 switch(t){
245                 case Wheading:
246                         p = nospaces(w->text);
247                         s = s_appendlist(s, 
248                                 "\n<a name=\"", p, "\" /><h3>", 
249                                 w->text, "</h3>\n", nil);
250                         free(p);
251                         break;
252
253                 case Wpara:
254                         if(inpara){
255                                 s = s_append(s, "\n</p>\n");
256                                 inpara = 0;
257                         }
258                         if(okayinpara[tnext]){
259                                 s = s_append(s, "\n<p class='para'>\n");
260                                 inpara = 1;
261                         }
262                         break;
263
264                 case Wbullet:
265                         if(!inlist){
266                                 inlist = 1;
267                                 s = s_append(s, "\n<ul>\n");
268                         }else
269                                 s = s_append(s, "\n</li>\n");
270                         s = s_append(s, "\n<li>\n");
271                         break;
272
273                 case Wlink:
274                         if(w->url == nil)
275                                 p = mkurl(w->text, ty);
276                         else
277                                 p = w->url;
278                         s = s_appendlist(s, "<a href=\"", p, "\">", nil);
279                         s = s_escappend(s, w->text, 0);
280                         s = s_append(s, "</a>");
281                         if(w->url == nil)
282                                 free(p);
283                         break;
284
285                 case Wman:
286                         sprint(tmp, "%d", w->section);
287                         s = s_appendlist(s, 
288                                 "<a href=\"http://plan9.bell-labs.com/magic/man2html/",
289                                 tmp, "/", w->text, "\"><i>", w->text, "</i>(",
290                                 tmp, ")</a>", nil);
291                         break;
292                         
293                 case Wpre:
294                         if(!inpre){
295                                 inpre = 1;
296                                 s = s_append(s, "\n<pre>\n");
297                         }
298                         s = s_escappend(s, w->text, 1);
299                         s = s_append(s, "\n");
300                         break;
301                 
302                 case Whr:
303                         s = s_append(s, "<hr />");
304                         break;
305
306                 case Wplain:
307                         s = s_escappend(s, w->text, 0);
308                         break;
309                 }
310         }
311         if(inlist)
312                 s = s_append(s, "\n</li>\n</ul>\n");
313         if(inpre)
314                 s = s_append(s, "</pre>\n");
315         if(inpara)
316                 s = s_append(s, "\n</p>\n");
317         return s;
318 }
319
320 static String*
321 copythru(String *s, char **newp, int *nlinep, int l)
322 {
323         char *oq, *q, *r;
324         int ol;
325
326         q = *newp;
327         oq = q;
328         ol = *nlinep;
329         while(ol < l){
330                 if(r = strchr(q, '\n'))
331                         q = r+1;
332                 else{
333                         q += strlen(q);
334                         break;
335                 }
336                 ol++;
337         }
338         if(*nlinep < l)
339                 *nlinep = l;
340         *newp = q;
341         return s_nappend(s, oq, q-oq);
342 }
343
344 static int
345 dodiff(char *f1, char *f2)
346 {
347         int p[2];
348
349         if(pipe(p) < 0){
350                 return -1;
351         }
352
353         switch(fork()){
354         case -1:
355                 return -1;
356
357         case 0:
358                 close(p[0]);
359                 dup(p[1], 1);
360                 execl("/bin/diff", "diff", f1, f2, nil);
361                 _exits(nil);
362         }
363         close(p[1]);
364         return p[0];
365 }
366
367
368 /* print document i grayed out, with only diffs relative to j in black */
369 static String*
370 s_diff(String *s, Whist *h, int i, int j)
371 {
372         char *p, *q, *pnew;
373         int fdiff, fd1, fd2, n1, n2;
374         Biobuf b;
375         char fn1[40], fn2[40];
376         String *new, *old;
377         int nline;
378
379         if(j < 0)
380                 return pagehtml(s, h->doc[i].wtxt, Tpage);
381
382         strcpy(fn1, "/tmp/wiki.XXXXXX");
383         strcpy(fn2, "/tmp/wiki.XXXXXX");
384         if((fd1 = opentemp(fn1)) < 0 || (fd2 = opentemp(fn2)) < 0){
385                 close(fd1);
386                 s = s_append(s, "\nopentemp failed; sorry\n");
387                 return s;
388         }
389
390         new = pagehtml(s_reset(nil), h->doc[i].wtxt, Tpage);
391         old = pagehtml(s_reset(nil), h->doc[j].wtxt, Tpage);
392         write(fd1, s_to_c(new), s_len(new));
393         write(fd2, s_to_c(old), s_len(old));
394
395         fdiff = dodiff(fn2, fn1);
396         if(fdiff < 0)
397                 s = s_append(s, "\ndiff failed; sorry\n");
398         else{
399                 nline = 0;
400                 pnew = s_to_c(new);
401                 Binit(&b, fdiff, OREAD);
402                 while(p = Brdline(&b, '\n')){
403                         if(p[0]=='<' || p[0]=='>' || p[0]=='-')
404                                 continue;
405                         p[Blinelen(&b)-1] = '\0';
406                         if((p = strpbrk(p, "acd")) == nil)
407                                 continue;
408                         n1 = atoi(p+1);
409                         if(q = strchr(p, ','))
410                                 n2 = atoi(q+1);
411                         else
412                                 n2 = n1;
413                         switch(*p){
414                         case 'a':
415                         case 'c':
416                                 s = s_append(s, "<span class='old_text'>");
417                                 s = copythru(s, &pnew, &nline, n1-1);
418                                 s = s_append(s, "</span><span class='new_text'>");
419                                 s = copythru(s, &pnew, &nline, n2);
420                                 s = s_append(s, "</span>");
421                                 break;
422                         }
423                 }
424                 close(fdiff);
425                 s = s_append(s, "<span class='old_text'>");
426                 s = s_append(s, pnew);
427                 s = s_append(s, "</span>");
428
429         }
430         s_free(new);
431         s_free(old);
432         close(fd1);
433         close(fd2);
434         return s;
435 }
436
437 static String*
438 diffhtml(String *s, Whist *h)
439 {
440         int i;
441         char tmp[50];
442         char *atime;
443
444         for(i=h->ndoc-1; i>=0; i--){
445                 s = s_append(s, "<hr /><div class='diff_head'>\n");
446                 if(i==h->current)
447                         sprint(tmp, "index.html");
448                 else
449                         sprint(tmp, "%lud", h->doc[i].time);
450                 atime = ctime(h->doc[i].time);
451                 atime[strlen(atime)-1] = '\0';
452                 s = s_appendlist(s, 
453                         "<a href=\"", tmp, "\">",
454                         atime, "</a>", nil);
455                 if(h->doc[i].author)
456                         s = s_appendlist(s, ", ", h->doc[i].author, nil);
457                 if(h->doc[i].conflict)
458                         s = s_append(s, ", conflicting write");
459                 s = s_append(s, "\n");
460                 if(h->doc[i].comment)
461                         s = s_appendlist(s, "<br /><i>", h->doc[i].comment, "</i>\n", nil);
462                 s = s_append(s, "</div><hr />");
463                 s = s_diff(s, h, i, i-1);
464         }
465         s = s_append(s, "<hr>");
466         return s;
467 }
468
469 static String*
470 historyhtml(String *s, Whist *h)
471 {
472         int i;
473         char tmp[40];
474         char *atime;
475
476         s = s_append(s, "<ul>\n");
477         for(i=h->ndoc-1; i>=0; i--){
478                 if(i==h->current)
479                         sprint(tmp, "index.html");
480                 else
481                         sprint(tmp, "%lud", h->doc[i].time);
482                 atime = ctime(h->doc[i].time);
483                 atime[strlen(atime)-1] = '\0';
484                 s = s_appendlist(s, 
485                         "<li><a href=\"", tmp, "\">",
486                         atime, "</a>", nil);
487                 if(h->doc[i].author)
488                         s = s_appendlist(s, ", ", h->doc[i].author, nil);
489                 if(h->doc[i].conflict)
490                         s = s_append(s, ", conflicting write");
491                 s = s_append(s, "\n");
492                 if(h->doc[i].comment)
493                         s = s_appendlist(s, "<br><i>", h->doc[i].comment, "</i>\n", nil);
494         }
495         s = s_append(s, "</ul>");
496         return s;               
497 }
498
499 String*
500 tohtml(Whist *h, Wdoc *d, int ty)
501 {
502         char *atime;
503         char *p, *q, ver[40];
504         int nsub;
505         Sub sub[3];
506         String *s, *t;
507
508         t = gettemplate(ty);
509         if(p = strstr(s_to_c(t), "PAGE"))
510                 q = p+4;
511         else{
512                 p = s_to_c(t)+s_len(t);
513                 q = nil;
514         }
515
516         nsub = 0;
517         if(h){
518                 sub[nsub] = (Sub){ "TITLE", h->title };
519                 nsub++;
520         }
521         if(d){
522                 sprint(ver, "%lud", d->time);
523                 sub[nsub] = (Sub){ "VERSION", ver };
524                 nsub++;
525                 atime = ctime(d->time);
526                 atime[strlen(atime)-1] = '\0';
527                 sub[nsub] = (Sub){ "DATE", atime };
528                 nsub++;
529         }
530
531         s = s_reset(nil);
532         s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub);
533         switch(ty){
534         case Tpage:
535         case Toldpage:
536                 s = pagehtml(s, d->wtxt, ty);
537                 break;
538         case Tedit:
539                 s = pagetext(s, d->wtxt, 0);
540                 break;
541         case Tdiff:
542                 s = diffhtml(s, h);
543                 break;
544         case Thistory:
545                 s = historyhtml(s, h);
546                 break;
547         case Tnew:
548         case Twerror:
549                 break;
550         }
551         if(q)
552                 s = s_appendsub(s, q, strlen(q), sub, nsub);
553         s_free(t);
554         return s;
555 }
556
557 enum {
558         LINELEN = 70,
559 };
560
561 static String*
562 s_appendbrk(String *s, char *p, char *prefix, int dosharp)
563 {
564         char *e, *w, *x;
565         int first, l;
566         Rune r;
567
568         first = 1;
569         while(*p){
570                 s = s_append(s, p);
571                 e = strrchr(s_to_c(s), '\n');
572                 if(e == nil)
573                         e = s_to_c(s);
574                 else
575                         e++;
576                 if(utflen(e) <= LINELEN)
577                         break;
578                 x = e; l=LINELEN;
579                 while(l--)
580                         x+=chartorune(&r, x);
581                 x = strchr(x, ' ');
582                 if(x){
583                         *x = '\0';
584                         w = strrchr(e, ' ');
585                         *x = ' ';
586                 }else
587                         w = strrchr(e, ' ');
588         
589                 if(w-s_to_c(s) < strlen(prefix))
590                         break;
591                 
592                 x = estrdup(w+1);
593                 *w = '\0';
594                 s->ptr = w;
595                 s_append(s, "\n");
596                 if(dosharp)
597                         s_append(s, "#");
598                 s_append(s, prefix);
599                 if(!first)
600                         free(p);
601                 first = 0;
602                 p = x;
603         }
604         if(!first)
605                 free(p);
606         return s;
607 }
608
609 static void
610 s_endline(String *s, int dosharp)
611 {
612         if(dosharp){
613                 if(s->ptr == s->base+1 && s->ptr[-1] == '#')
614                         return;
615
616                 if(s->ptr > s->base+1 && s->ptr[-1] == '#' && s->ptr[-2] == '\n')
617                         return;
618                 s_append(s, "\n#");
619         }else{
620                 if(s->ptr > s->base+1 && s->ptr[-1] == '\n')
621                         return;
622                 s_append(s, "\n");
623         }
624 }
625
626 String*
627 pagetext(String *s, Wpage *page, int dosharp)
628 {
629         int inlist, inpara;
630         char *prefix, *sharp, tmp[40];
631         String *t;
632         Wpage *w;
633
634         inlist = 0;
635         inpara = 0;
636         prefix = "";
637         sharp = dosharp ? "#" : "";
638         s = s_append(s, sharp);
639         for(w=page; w; w=w->next){
640                 switch(w->type){
641                 case Wheading:
642                         if(inlist){
643                                 prefix = "";
644                                 inlist = 0;
645                         }
646                         s_endline(s, dosharp);
647                         if(!inpara){
648                                 inpara = 1;
649                                 s = s_appendlist(s, "\n", sharp, nil);
650                         }
651                         s = s_appendlist(s, w->text, "\n", sharp, "\n", sharp, nil);
652                         break;
653
654                 case Wpara:
655                         s_endline(s, dosharp);
656                         if(inlist){
657                                 prefix = "";
658                                 inlist = 0;
659                         }
660                         if(!inpara){
661                                 inpara = 1;
662                                 s = s_appendlist(s, "\n", sharp, nil);
663                         }
664                         break;
665
666                 case Wbullet:
667                         s_endline(s, dosharp);
668                         if(!inlist)
669                                 inlist = 1;
670                         if(inpara)
671                                 inpara = 0;
672                         s = s_append(s, " *\t");
673                         prefix = "\t";
674                         break;
675
676                 case Wlink:
677                         if(inpara)
678                                 inpara = 0;
679                         t = s_append(s_copy("["), w->text);
680                         if(w->url == nil)
681                                 t = s_append(t, "]");
682                         else{
683                                 t = s_append(t, " | ");
684                                 t = s_append(t, w->url);
685                                 t = s_append(t, "]");
686                         }
687                         s = s_appendbrk(s, s_to_c(t), prefix, dosharp);
688                         s_free(t);
689                         break;
690
691                 case Wman:
692                         if(inpara)
693                                 inpara = 0;
694                         s = s_appendbrk(s, w->text, prefix, dosharp);
695                         sprint(tmp, "(%d)", w->section);
696                         s = s_appendbrk(s, tmp, prefix, dosharp);
697                         break;
698                         
699                 case Wpre:
700                         if(inlist){
701                                 prefix = "";
702                                 inlist = 0;
703                         }
704                         if(inpara)
705                                 inpara = 0;
706                         s_endline(s, dosharp);
707                         s = s_appendlist(s, "! ", w->text, "\n", sharp, nil);
708                         break;
709                 case Whr:
710                         s_endline(s, dosharp);
711                         s = s_appendlist(s, "------------------------------------------------------ \n", sharp, nil);
712                         break;
713
714                 case Wplain:
715                         if(inpara)
716                                 inpara = 0;
717                         s = s_appendbrk(s, w->text, prefix, dosharp);
718                         break;
719                 }
720         }
721         s_endline(s, dosharp);
722         s->ptr--;
723         *s->ptr = '\0';
724         return s;
725 }
726
727 static String*
728 historytext(String *s, Whist *h)
729 {
730         int i;
731         char tmp[40];
732         char *atime;
733
734         for(i=h->ndoc-1; i>=0; i--){
735                 if(i==h->current)
736                         sprint(tmp, "[current]");
737                 else
738                         sprint(tmp, "[%lud/]", h->doc[i].time);
739                 atime = ctime(h->doc[i].time);
740                 atime[strlen(atime)-1] = '\0';
741                 s = s_appendlist(s, " * ", tmp, " ", atime, nil);
742                 if(h->doc[i].author)
743                         s = s_appendlist(s, ", ", h->doc[i].author, nil);
744                 if(h->doc[i].conflict)
745                         s = s_append(s, ", conflicting write");
746                 s = s_append(s, "\n");
747                 if(h->doc[i].comment)
748                         s = s_appendlist(s, "<i>", h->doc[i].comment, "</i>\n", nil);
749         }
750         return s;               
751 }
752
753 String*
754 totext(Whist *h, Wdoc *d, int ty)
755 {
756         char *atime;
757         char *p, *q, ver[40];
758         int nsub;
759         Sub sub[3];
760         String *s, *t;
761
762         t = gettemplate(Ntemplate+ty);
763         if(p = strstr(s_to_c(t), "PAGE"))
764                 q = p+4;
765         else{
766                 p = s_to_c(t)+s_len(t);
767                 q = nil;
768         }
769
770         nsub = 0;
771         if(h){
772                 sub[nsub] = (Sub){ "TITLE", h->title };
773                 nsub++;
774         }
775         if(d){
776                 sprint(ver, "%lud", d->time);
777                 sub[nsub] = (Sub){ "VERSION", ver };
778                 nsub++;
779                 atime = ctime(d->time);
780                 atime[strlen(atime)-1] = '\0';
781                 sub[nsub] = (Sub){ "DATE", atime };
782                 nsub++;
783         }
784         
785         s = s_reset(nil);
786         s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub);
787         switch(ty){
788         case Tpage:
789         case Toldpage:
790                 s = pagetext(s, d->wtxt, 0);
791                 break;
792         case Thistory:
793                 s = historytext(s, h);
794                 break;
795         case Tnew:
796         case Twerror:
797                 break;
798         }
799         if(q)
800                 s = s_appendsub(s, q, strlen(q), sub, nsub);
801         s_free(t);
802         return s;
803 }
804
805 String*
806 doctext(String *s, Wdoc *d)
807 {
808         char tmp[40];
809
810         sprint(tmp, "D%lud", d->time);
811         s = s_append(s, tmp);
812         if(d->comment){
813                 s = s_append(s, "\nC");
814                 s = s_append(s, d->comment);
815         }
816         if(d->author){
817                 s = s_append(s, "\nA");
818                 s = s_append(s, d->author);
819         }
820         if(d->conflict)
821                 s = s_append(s, "\nX");
822         s = s_append(s, "\n");
823         s = pagetext(s, d->wtxt, 1);
824         return s;
825 }