#include
#include
#include
#include
#include
#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)
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;
}
String*
pagehtml(String *s, Wpage *wtxt, int ty)
{
int inlist, inpre, inpara, t, tn;
char *p, *c, tmp[40];
Wpage *w;
inlist = 0;
inpre = 0;
inpara = 0;
for(w=wtxt; w; w=w->next){
t = w->type;
tn = Whr; // This is the end
if(w->next)
tn = w->next->type;
if(inlist && !(t == Wbullet || t == Wlink || t == Wman || t == Wplain)){
inlist = 0;
s = s_append(s, "\n\n\n");
}
if(inpre && (t == Wheading || t == Wpara || t == Wbullet || t == Whr || t == Wplain)){
inpre = 0;
s = s_append(s, "\n");
}
switch(t){
case Wheading:
// a name='' can't contain spaces
p = strdup(w->text);
for(c = p; *c != '\0'; c++)
if(*c == ' ')
*c = '_';
s = s_appendlist(s, "\n", w->text, "
\n", nil);
free(p);
break;
case Wpara:
if(inpara) {
s = s_append(s, "\n
\n");
inpara = 0;
}
if( w->next && (tn != Wheading && tn != Wbullet && tn != Wpre && tn != Whr)) {
s = s_append(s, "\n\n");
inpara = 1;
}
break;
case Wbullet:
if(!inlist) {
inlist = 1;
s = s_append(s, "\n
\n");
} else {
s = s_append(s, "\n\n");
}
s = s_append(s, "\n- \n");
break;
case Wlink:
if(w->url == nil)
p = mkurl(w->text, ty);
else
p = w->url;
s = s_appendlist(s, "", nil);
s = s_escappend(s, w->text, 0);
s = s_append(s, "");
if(w->url == nil)
free(p);
break;
case Wman:
sprint(tmp, "%d", w->section);
s = s_appendlist(s,
"text, "\">", w->text, "(",
tmp, ")", nil);
break;
case Wpre:
if(!inpre){
inpre = 1;
s = s_append(s, "\n
\n");
}
s = s_escappend(s, w->text, 1);
s = s_append(s, "\n");
break;
case Whr:
s = s_append(s, "
");
break;
case Wplain:
s = s_escappend(s, w->text, 0);
break;
}
}
if(inlist)
s = s_append(s, "\n
\n
\n");
if(inpre)
s = s_append(s, "\n");
if(inpara)
s = s_append(s, "\n\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((q = strpbrk(p, "acd")) == nil)
continue;
switch(*q){
case 'a':
case 'c':
n1 = atoi(q+1);
if(q = strchr(q, ','))
n2 = atoi(q+1);
else
n2 = n1;
s = s_append(s,"");
s = copythru(s, &pnew, &nline, n1-1);
s = s_append(s,"");
s = copythru(s, &pnew, &nline, n2);
s = s_append(s,"");
break;
/*
//This is not what we want...
case 'd':
n1 = atoi(q+1);
s = s_append(s,"");
s = copythru(s, &pnew, &nline, n1-1);
s = s_append(s,"");
s = copythru(s, &pnew, &nline, n1);
break;
*/
}
}
close(fdiff);
s = s_append(s,"");
s = s_append(s, pnew);
s = s_append(s,"");
}
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, "
\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,
"
",
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, "
", h->doc[i].comment, "\n", nil);
s = s_append(s, "
");
s = s_diff(s, h, i, i-1);
}
s = s_append(s, "
");
return s;
}
static String*
historyhtml(String *s, Whist *h)
{
int i;
char tmp[40];
char *atime;
s = s_append(s, "\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,
"- ",
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, "
", h->doc[i].comment, "\n", nil);
}
s = s_append(s, "
");
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, "", h->doc[i].comment, "\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;
}