diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/wikifs/tohtml.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/wikifs/tohtml.c')
-rwxr-xr-x | sys/src/cmd/wikifs/tohtml.c | 825 |
1 files changed, 825 insertions, 0 deletions
diff --git a/sys/src/cmd/wikifs/tohtml.c b/sys/src/cmd/wikifs/tohtml.c new file mode 100755 index 000000000..716848951 --- /dev/null +++ b/sys/src/cmd/wikifs/tohtml.c @@ -0,0 +1,825 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +/* + * Get HTML and text templates from underlying file system. + * Caches them, which means changes don't take effect for + * up to Tcache seconds after they are made. + * + * If the files are deleted, we keep returning the last + * known copy. + */ +enum { + WAIT = 60 +}; + +static char *name[2*Ntemplate] = { + [Tpage] "page.html", + [Tedit] "edit.html", + [Tdiff] "diff.html", + [Thistory] "history.html", + [Tnew] "new.html", + [Toldpage] "oldpage.html", + [Twerror] "werror.html", + [Ntemplate+Tpage] "page.txt", + [Ntemplate+Tdiff] "diff.txt", + [Ntemplate+Thistory] "history.txt", + [Ntemplate+Toldpage] "oldpage.txt", + [Ntemplate+Twerror] "werror.txt", +}; + +static struct { + RWLock; + String *s; + ulong t; + Qid qid; +} cache[2*Ntemplate]; + +static void +cacheinit(void) +{ + int i; + static int x; + static Lock l; + + if(x) + return; + lock(&l); + if(x){ + unlock(&l); + return; + } + + for(i=0; i<2*Ntemplate; i++) + if(name[i]) + cache[i].s = s_copy(""); + x = 1; + unlock(&l); +} + +static String* +gettemplate(int type) +{ + int n; + Biobuf *b; + Dir *d; + String *s, *ns; + + if(name[type]==nil) + return nil; + + cacheinit(); + + rlock(&cache[type]); + if(0 && cache[type].t+Tcache >= time(0)){ + s = s_incref(cache[type].s); + runlock(&cache[type]); + return s; + } + runlock(&cache[type]); + +// d = nil; + wlock(&cache[type]); + if(0 && cache[type].t+Tcache >= time(0) || (d = wdirstat(name[type])) == nil) + goto Return; + + if(0 && d->qid.vers == cache[type].qid.vers && d->qid.path == cache[type].qid.path){ + cache[type].t = time(0); + goto Return; + } + + if((b = wBopen(name[type], OREAD)) == nil) + goto Return; + + ns = s_reset(nil); + do + n = s_read(b, ns, Bsize); + while(n > 0); + Bterm(b); + if(n < 0) { + s_free(ns); + goto Return; + } + + s_free(cache[type].s); + cache[type].s = ns; + cache[type].qid = d->qid; + cache[type].t = time(0); + +Return: + free(d); + s = s_incref(cache[type].s); + wunlock(&cache[type]); + return s; +} + + +/* + * Write wiki document in HTML. + */ +static String* +s_escappend(String *s, char *p, int pre) +{ + char *q; + + while(q = strpbrk(p, pre ? "<>&" : " <>&")){ + s = s_nappend(s, p, q-p); + switch(*q){ + case '<': + s = s_append(s, "<"); + break; + case '>': + s = s_append(s, ">"); + break; + case '&': + s = s_append(s, "&"); + break; + case ' ': + s = s_append(s, "\n"); + } + p = q+1; + } + s = s_append(s, p); + return s; +} + +static char* +mkurl(char *s, int ty) +{ + char *p, *q; + + if(strncmp(s, "http:", 5)==0 + || strncmp(s, "https:", 6)==0 + || strncmp(s, "#", 1)==0 + || strncmp(s, "ftp:", 4)==0 + || strncmp(s, "mailto:", 7)==0 + || strncmp(s, "telnet:", 7)==0 + || strncmp(s, "file:", 5)==0) + return estrdup(s); + + if(strchr(s, ' ')==nil && strchr(s, '@')!=nil){ + p = emalloc(strlen(s)+8); + strcpy(p, "mailto:"); + strcat(p, s); + return p; + } + + if(ty == Toldpage) + p = smprint("../../%s", s); + else + p = smprint("../%s", s); + + for(q=p; *q; q++) + if(*q==' ') + *q = '_'; + return p; +} + +int okayinlist[Nwtxt] = +{ + [Wbullet] 1, + [Wlink] 1, + [Wman] 1, + [Wplain] 1, +}; + +int okayinpre[Nwtxt] = +{ + [Wlink] 1, + [Wman] 1, + [Wpre] 1, +}; + +int okayinpara[Nwtxt] = +{ + [Wpara] 1, + [Wlink] 1, + [Wman] 1, + [Wplain] 1, +}; + +char* +nospaces(char *s) +{ + char *q; + s = strdup(s); + if(s == nil) + return nil; + for(q=s; *q; q++) + if(*q == ' ') + *q = '_'; + return s; +} + +String* +pagehtml(String *s, Wpage *wtxt, int ty) +{ + char *p, tmp[40]; + int inlist, inpara, inpre, t, tnext; + Wpage *w; + + inlist = 0; + inpre = 0; + inpara = 0; + + for(w=wtxt; w; w=w->next){ + t = w->type; + tnext = Whr; + if(w->next) + tnext = w->next->type; + + if(inlist && !okayinlist[t]){ + inlist = 0; + s = s_append(s, "\n</li>\n</ul>\n"); + } + if(inpre && !okayinpre[t]){ + inpre = 0; + s = s_append(s, "</pre>\n"); + } + + switch(t){ + case Wheading: + p = nospaces(w->text); + s = s_appendlist(s, + "\n<a name=\"", p, "\" /><h3>", + w->text, "</h3>\n", nil); + free(p); + break; + + case Wpara: + if(inpara){ + s = s_append(s, "\n</p>\n"); + inpara = 0; + } + if(okayinpara[tnext]){ + s = s_append(s, "\n<p class='para'>\n"); + inpara = 1; + } + break; + + case Wbullet: + if(!inlist){ + inlist = 1; + s = s_append(s, "\n<ul>\n"); + }else + s = s_append(s, "\n</li>\n"); + s = s_append(s, "\n<li>\n"); + break; + + case Wlink: + if(w->url == nil) + p = mkurl(w->text, ty); + else + p = w->url; + s = s_appendlist(s, "<a href=\"", p, "\">", nil); + s = s_escappend(s, w->text, 0); + s = s_append(s, "</a>"); + if(w->url == nil) + free(p); + break; + + case Wman: + sprint(tmp, "%d", w->section); + s = s_appendlist(s, + "<a href=\"http://plan9.bell-labs.com/magic/man2html/", + tmp, "/", w->text, "\"><i>", w->text, "</i>(", + tmp, ")</a>", nil); + break; + + case Wpre: + if(!inpre){ + inpre = 1; + s = s_append(s, "\n<pre>\n"); + } + s = s_escappend(s, w->text, 1); + s = s_append(s, "\n"); + break; + + case Whr: + s = s_append(s, "<hr />"); + break; + + case Wplain: + s = s_escappend(s, w->text, 0); + break; + } + } + if(inlist) + s = s_append(s, "\n</li>\n</ul>\n"); + if(inpre) + s = s_append(s, "</pre>\n"); + if(inpara) + s = s_append(s, "\n</p>\n"); + return s; +} + +static String* +copythru(String *s, char **newp, int *nlinep, int l) +{ + char *oq, *q, *r; + int ol; + + q = *newp; + oq = q; + ol = *nlinep; + while(ol < l){ + if(r = strchr(q, '\n')) + q = r+1; + else{ + q += strlen(q); + break; + } + ol++; + } + if(*nlinep < l) + *nlinep = l; + *newp = q; + return s_nappend(s, oq, q-oq); +} + +static int +dodiff(char *f1, char *f2) +{ + int p[2]; + + if(pipe(p) < 0){ + return -1; + } + + switch(fork()){ + case -1: + return -1; + + case 0: + close(p[0]); + dup(p[1], 1); + execl("/bin/diff", "diff", f1, f2, nil); + _exits(nil); + } + close(p[1]); + return p[0]; +} + + +/* print document i grayed out, with only diffs relative to j in black */ +static String* +s_diff(String *s, Whist *h, int i, int j) +{ + char *p, *q, *pnew; + int fdiff, fd1, fd2, n1, n2; + Biobuf b; + char fn1[40], fn2[40]; + String *new, *old; + int nline; + + if(j < 0) + return pagehtml(s, h->doc[i].wtxt, Tpage); + + strcpy(fn1, "/tmp/wiki.XXXXXX"); + strcpy(fn2, "/tmp/wiki.XXXXXX"); + if((fd1 = opentemp(fn1)) < 0 || (fd2 = opentemp(fn2)) < 0){ + close(fd1); + s = s_append(s, "\nopentemp failed; sorry\n"); + return s; + } + + new = pagehtml(s_reset(nil), h->doc[i].wtxt, Tpage); + old = pagehtml(s_reset(nil), h->doc[j].wtxt, Tpage); + write(fd1, s_to_c(new), s_len(new)); + write(fd2, s_to_c(old), s_len(old)); + + fdiff = dodiff(fn2, fn1); + if(fdiff < 0) + s = s_append(s, "\ndiff failed; sorry\n"); + else{ + nline = 0; + pnew = s_to_c(new); + Binit(&b, fdiff, OREAD); + while(p = Brdline(&b, '\n')){ + if(p[0]=='<' || p[0]=='>' || p[0]=='-') + continue; + p[Blinelen(&b)-1] = '\0'; + if((p = strpbrk(p, "acd")) == nil) + continue; + n1 = atoi(p+1); + if(q = strchr(p, ',')) + n2 = atoi(q+1); + else + n2 = n1; + switch(*p){ + case 'a': + case 'c': + s = s_append(s, "<span class='old_text'>"); + s = copythru(s, &pnew, &nline, n1-1); + s = s_append(s, "</span><span class='new_text'>"); + s = copythru(s, &pnew, &nline, n2); + s = s_append(s, "</span>"); + break; + } + } + close(fdiff); + s = s_append(s, "<span class='old_text'>"); + s = s_append(s, pnew); + s = s_append(s, "</span>"); + + } + s_free(new); + s_free(old); + close(fd1); + close(fd2); + return s; +} + +static String* +diffhtml(String *s, Whist *h) +{ + int i; + char tmp[50]; + char *atime; + + for(i=h->ndoc-1; i>=0; i--){ + s = s_append(s, "<hr /><div class='diff_head'>\n"); + if(i==h->current) + sprint(tmp, "index.html"); + else + sprint(tmp, "%lud", h->doc[i].time); + atime = ctime(h->doc[i].time); + atime[strlen(atime)-1] = '\0'; + s = s_appendlist(s, + "<a href=\"", tmp, "\">", + atime, "</a>", nil); + if(h->doc[i].author) + s = s_appendlist(s, ", ", h->doc[i].author, nil); + if(h->doc[i].conflict) + s = s_append(s, ", conflicting write"); + s = s_append(s, "\n"); + if(h->doc[i].comment) + s = s_appendlist(s, "<br /><i>", h->doc[i].comment, "</i>\n", nil); + s = s_append(s, "</div><hr />"); + s = s_diff(s, h, i, i-1); + } + s = s_append(s, "<hr>"); + return s; +} + +static String* +historyhtml(String *s, Whist *h) +{ + int i; + char tmp[40]; + char *atime; + + s = s_append(s, "<ul>\n"); + for(i=h->ndoc-1; i>=0; i--){ + if(i==h->current) + sprint(tmp, "index.html"); + else + sprint(tmp, "%lud", h->doc[i].time); + atime = ctime(h->doc[i].time); + atime[strlen(atime)-1] = '\0'; + s = s_appendlist(s, + "<li><a href=\"", tmp, "\">", + atime, "</a>", nil); + if(h->doc[i].author) + s = s_appendlist(s, ", ", h->doc[i].author, nil); + if(h->doc[i].conflict) + s = s_append(s, ", conflicting write"); + s = s_append(s, "\n"); + if(h->doc[i].comment) + s = s_appendlist(s, "<br><i>", h->doc[i].comment, "</i>\n", nil); + } + s = s_append(s, "</ul>"); + return s; +} + +String* +tohtml(Whist *h, Wdoc *d, int ty) +{ + char *atime; + char *p, *q, ver[40]; + int nsub; + Sub sub[3]; + String *s, *t; + + t = gettemplate(ty); + if(p = strstr(s_to_c(t), "PAGE")) + q = p+4; + else{ + p = s_to_c(t)+s_len(t); + q = nil; + } + + nsub = 0; + if(h){ + sub[nsub] = (Sub){ "TITLE", h->title }; + nsub++; + } + if(d){ + sprint(ver, "%lud", d->time); + sub[nsub] = (Sub){ "VERSION", ver }; + nsub++; + atime = ctime(d->time); + atime[strlen(atime)-1] = '\0'; + sub[nsub] = (Sub){ "DATE", atime }; + nsub++; + } + + s = s_reset(nil); + s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub); + switch(ty){ + case Tpage: + case Toldpage: + s = pagehtml(s, d->wtxt, ty); + break; + case Tedit: + s = pagetext(s, d->wtxt, 0); + break; + case Tdiff: + s = diffhtml(s, h); + break; + case Thistory: + s = historyhtml(s, h); + break; + case Tnew: + case Twerror: + break; + } + if(q) + s = s_appendsub(s, q, strlen(q), sub, nsub); + s_free(t); + return s; +} + +enum { + LINELEN = 70, +}; + +static String* +s_appendbrk(String *s, char *p, char *prefix, int dosharp) +{ + char *e, *w, *x; + int first, l; + Rune r; + + first = 1; + while(*p){ + s = s_append(s, p); + e = strrchr(s_to_c(s), '\n'); + if(e == nil) + e = s_to_c(s); + else + e++; + if(utflen(e) <= LINELEN) + break; + x = e; l=LINELEN; + while(l--) + x+=chartorune(&r, x); + x = strchr(x, ' '); + if(x){ + *x = '\0'; + w = strrchr(e, ' '); + *x = ' '; + }else + w = strrchr(e, ' '); + + if(w-s_to_c(s) < strlen(prefix)) + break; + + x = estrdup(w+1); + *w = '\0'; + s->ptr = w; + s_append(s, "\n"); + if(dosharp) + s_append(s, "#"); + s_append(s, prefix); + if(!first) + free(p); + first = 0; + p = x; + } + if(!first) + free(p); + return s; +} + +static void +s_endline(String *s, int dosharp) +{ + if(dosharp){ + if(s->ptr == s->base+1 && s->ptr[-1] == '#') + return; + + if(s->ptr > s->base+1 && s->ptr[-1] == '#' && s->ptr[-2] == '\n') + return; + s_append(s, "\n#"); + }else{ + if(s->ptr > s->base+1 && s->ptr[-1] == '\n') + return; + s_append(s, "\n"); + } +} + +String* +pagetext(String *s, Wpage *page, int dosharp) +{ + int inlist, inpara; + char *prefix, *sharp, tmp[40]; + String *t; + Wpage *w; + + inlist = 0; + inpara = 0; + prefix = ""; + sharp = dosharp ? "#" : ""; + s = s_append(s, sharp); + for(w=page; w; w=w->next){ + switch(w->type){ + case Wheading: + if(inlist){ + prefix = ""; + inlist = 0; + } + s_endline(s, dosharp); + if(!inpara){ + inpara = 1; + s = s_appendlist(s, "\n", sharp, nil); + } + s = s_appendlist(s, w->text, "\n", sharp, "\n", sharp, nil); + break; + + case Wpara: + s_endline(s, dosharp); + if(inlist){ + prefix = ""; + inlist = 0; + } + if(!inpara){ + inpara = 1; + s = s_appendlist(s, "\n", sharp, nil); + } + break; + + case Wbullet: + s_endline(s, dosharp); + if(!inlist) + inlist = 1; + if(inpara) + inpara = 0; + s = s_append(s, " *\t"); + prefix = "\t"; + break; + + case Wlink: + if(inpara) + inpara = 0; + t = s_append(s_copy("["), w->text); + if(w->url == nil) + t = s_append(t, "]"); + else{ + t = s_append(t, " | "); + t = s_append(t, w->url); + t = s_append(t, "]"); + } + s = s_appendbrk(s, s_to_c(t), prefix, dosharp); + s_free(t); + break; + + case Wman: + if(inpara) + inpara = 0; + s = s_appendbrk(s, w->text, prefix, dosharp); + sprint(tmp, "(%d)", w->section); + s = s_appendbrk(s, tmp, prefix, dosharp); + break; + + case Wpre: + if(inlist){ + prefix = ""; + inlist = 0; + } + if(inpara) + inpara = 0; + s_endline(s, dosharp); + s = s_appendlist(s, "! ", w->text, "\n", sharp, nil); + break; + case Whr: + s_endline(s, dosharp); + s = s_appendlist(s, "------------------------------------------------------ \n", sharp, nil); + break; + + case Wplain: + if(inpara) + inpara = 0; + s = s_appendbrk(s, w->text, prefix, dosharp); + break; + } + } + s_endline(s, dosharp); + s->ptr--; + *s->ptr = '\0'; + return s; +} + +static String* +historytext(String *s, Whist *h) +{ + int i; + char tmp[40]; + char *atime; + + for(i=h->ndoc-1; i>=0; i--){ + if(i==h->current) + sprint(tmp, "[current]"); + else + sprint(tmp, "[%lud/]", h->doc[i].time); + atime = ctime(h->doc[i].time); + atime[strlen(atime)-1] = '\0'; + s = s_appendlist(s, " * ", tmp, " ", atime, nil); + if(h->doc[i].author) + s = s_appendlist(s, ", ", h->doc[i].author, nil); + if(h->doc[i].conflict) + s = s_append(s, ", conflicting write"); + s = s_append(s, "\n"); + if(h->doc[i].comment) + s = s_appendlist(s, "<i>", h->doc[i].comment, "</i>\n", nil); + } + return s; +} + +String* +totext(Whist *h, Wdoc *d, int ty) +{ + char *atime; + char *p, *q, ver[40]; + int nsub; + Sub sub[3]; + String *s, *t; + + t = gettemplate(Ntemplate+ty); + if(p = strstr(s_to_c(t), "PAGE")) + q = p+4; + else{ + p = s_to_c(t)+s_len(t); + q = nil; + } + + nsub = 0; + if(h){ + sub[nsub] = (Sub){ "TITLE", h->title }; + nsub++; + } + if(d){ + sprint(ver, "%lud", d->time); + sub[nsub] = (Sub){ "VERSION", ver }; + nsub++; + atime = ctime(d->time); + atime[strlen(atime)-1] = '\0'; + sub[nsub] = (Sub){ "DATE", atime }; + nsub++; + } + + s = s_reset(nil); + s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub); + switch(ty){ + case Tpage: + case Toldpage: + s = pagetext(s, d->wtxt, 0); + break; + case Thistory: + s = historytext(s, h); + break; + case Tnew: + case Twerror: + break; + } + if(q) + s = s_appendsub(s, q, strlen(q), sub, nsub); + s_free(t); + return s; +} + +String* +doctext(String *s, Wdoc *d) +{ + char tmp[40]; + + sprint(tmp, "D%lud", d->time); + s = s_append(s, tmp); + if(d->comment){ + s = s_append(s, "\nC"); + s = s_append(s, d->comment); + } + if(d->author){ + s = s_append(s, "\nA"); + s = s_append(s, d->author); + } + if(d->conflict) + s = s_append(s, "\nX"); + s = s_append(s, "\n"); + s = pagetext(s, d->wtxt, 1); + return s; +} |