summaryrefslogtreecommitdiff
path: root/sys/src/cmd/wikifs/tohtml.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /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-xsys/src/cmd/wikifs/tohtml.c825
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, "&lt;");
+ break;
+ case '>':
+ s = s_append(s, "&gt;");
+ break;
+ case '&':
+ s = s_append(s, "&amp;");
+ 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;
+}