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 |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/wikifs')
-rwxr-xr-x | sys/src/cmd/wikifs/fs.c | 880 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/io.c | 700 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/lookup.c | 12 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/map.c | 7 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/mkfile | 33 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/parse.c | 331 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/parsehist.c | 130 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/testwrite.c | 52 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/tohtml.c | 825 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/util.c | 133 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/wdir.c | 76 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/wiki.h | 121 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/wiki2html.c | 67 | ||||
-rwxr-xr-x | sys/src/cmd/wikifs/wiki2text.c | 50 |
14 files changed, 3417 insertions, 0 deletions
diff --git a/sys/src/cmd/wikifs/fs.c b/sys/src/cmd/wikifs/fs.c new file mode 100755 index 000000000..d67a2c0de --- /dev/null +++ b/sys/src/cmd/wikifs/fs.c @@ -0,0 +1,880 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +#include <auth.h> +#include <fcall.h> +#include <9p.h> + +enum { + Qindexhtml, + Qindextxt, + Qraw, + Qhistoryhtml, + Qhistorytxt, + Qdiffhtml, + Qedithtml, + Qwerrorhtml, + Qwerrortxt, + Qhttplogin, + Nfile, +}; + +static char *filelist[] = { + "index.html", + "index.txt", + "current", + "history.html", + "history.txt", + "diff.html", + "edit.html", + "werror.html", + "werror.txt", + ".httplogin", +}; + +static int needhist[Nfile] = { +[Qhistoryhtml] 1, +[Qhistorytxt] 1, +[Qdiffhtml] 1, +}; + +/* + * The qids are <8-bit type><16-bit page number><16-bit page version><8-bit file index>. + */ +enum { /* <8-bit type> */ + Droot = 1, + D1st, + D2nd, + Fnew, + Fmap, + F1st, + F2nd, +}; + +uvlong +mkqid(int type, int num, int vers, int file) +{ + return ((uvlong)type<<40) | ((uvlong)num<<24) | (vers<<8) | file; +} + +int +qidtype(uvlong path) +{ + return (path>>40)&0xFF; +} + +int +qidnum(uvlong path) +{ + return (path>>24)&0xFFFF; +} + +int +qidvers(uvlong path) +{ + return (path>>8)&0xFFFF; +} + +int +qidfile(uvlong path) +{ + return path&0xFF; +} + +typedef struct Aux Aux; +struct Aux { + String *name; + Whist *w; + int n; + ulong t; + String *s; + Map *map; +}; + +static void +fsattach(Req *r) +{ + Aux *a; + + if(r->ifcall.aname && r->ifcall.aname[0]){ + respond(r, "invalid attach specifier"); + return; + } + + a = emalloc(sizeof(Aux)); + r->fid->aux = a; + a->name = s_copy(r->ifcall.uname); + + r->ofcall.qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR}; + r->fid->qid = r->ofcall.qid; + respond(r, nil); +} + +static String * +httplogin(void) +{ + String *s=s_new(); + Biobuf *b; + + if((b = wBopen(".httplogin", OREAD)) == nil) + goto Return; + + while(s_read(b, s, Bsize) > 0) + ; + Bterm(b); + +Return: + return s; +} + +static char* +fswalk1(Fid *fid, char *name, Qid *qid) +{ + char *q; + int i, isdotdot, n, t; + uvlong path; + Aux *a; + Whist *wh; + String *s; + + isdotdot = strcmp(name, "..")==0; + n = strtoul(name, &q, 10); + path = fid->qid.path; + a = fid->aux; + + switch(qidtype(path)){ + case 0: + return "wikifs: bad path in server (bug)"; + + case Droot: + if(isdotdot){ + *qid = fid->qid; + return nil; + } + if(strcmp(name, "new")==0){ + *qid = (Qid){mkqid(Fnew, 0, 0, 0), 0, 0}; + return nil; + } + if(strcmp(name, "map")==0){ + *qid = (Qid){mkqid(Fmap, 0, 0, 0), 0, 0}; + return nil; + } + if((*q!='\0' || (wh=getcurrent(n))==nil) + && (wh=getcurrentbyname(name))==nil) + return "file does not exist"; + *qid = (Qid){mkqid(D1st, wh->n, 0, 0), wh->doc->time, QTDIR}; + a->w = wh; + return nil; + + case D1st: + if(isdotdot){ + *qid = (Qid){mkqid(Droot, 0, 0, 0), 0, QTDIR}; + return nil; + } + + /* handle history directories */ + if(*q == '\0'){ + if((wh = gethistory(qidnum(path))) == nil) + return "file does not exist"; + for(i=0; i<wh->ndoc; i++) + if(wh->doc[i].time == n) + break; + if(i==wh->ndoc){ + closewhist(wh); + return "file does not exist"; + } + closewhist(a->w); + a->w = wh; + a->n = i; + *qid = (Qid){mkqid(D2nd, qidnum(path), i, 0), wh->doc[i].time, QTDIR}; + return nil; + } + + /* handle files other than index */ + for(i=0; i<nelem(filelist); i++){ + if(strcmp(name, filelist[i])==0){ + if(needhist[i]){ + if((wh = gethistory(qidnum(path))) == nil) + return "file does not exist"; + closewhist(a->w); + a->w = wh; + } + *qid = (Qid){mkqid(F1st, qidnum(path), 0, i), a->w->doc->time, 0}; + goto Gotfile; + } + } + return "file does not exist"; + + case D2nd: + if(isdotdot){ + /* + * Can't use a->w[a->ndoc-1] because that + * might be a failed write rather than the real one. + */ + *qid = (Qid){mkqid(D1st, qidnum(path), 0, 0), 0, QTDIR}; + if((wh = getcurrent(qidnum(path))) == nil) + return "file does not exist"; + closewhist(a->w); + a->w = wh; + a->n = 0; + return nil; + } + for(i=0; i<=Qraw; i++){ + if(strcmp(name, filelist[i])==0){ + *qid = (Qid){mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc->time, 0}; + goto Gotfile; + } + } + return "file does not exist"; + + default: + return "bad programming"; + } + /* not reached */ + +Gotfile: + t = qidtype(qid->path); + switch(qidfile(qid->path)){ + case Qindexhtml: + s = tohtml(a->w, a->w->doc+a->n, + t==F1st? Tpage : Toldpage); + break; + case Qindextxt: + s = totext(a->w, a->w->doc+a->n, + t==F1st? Tpage : Toldpage); + break; + case Qraw: + s = s_copy(a->w->title); + s = s_append(s, "\n"); + s = doctext(s, &a->w->doc[a->n]); + break; + case Qhistoryhtml: + s = tohtml(a->w, a->w->doc+a->n, Thistory); + break; + case Qhistorytxt: + s = totext(a->w, a->w->doc+a->n, Thistory); + break; + case Qdiffhtml: + s = tohtml(a->w, a->w->doc+a->n, Tdiff); + break; + case Qedithtml: + s = tohtml(a->w, a->w->doc+a->n, Tedit); + break; + case Qwerrorhtml: + s = tohtml(a->w, a->w->doc+a->n, Twerror); + break; + case Qwerrortxt: + s = totext(a->w, a->w->doc+a->n, Twerror); + break; + case Qhttplogin: + s = httplogin(); + break; + default: + return "internal error"; + } + a->s = s; + return nil; +} + +static void +fsopen(Req *r) +{ + int t; + uvlong path; + Aux *a; + Fid *fid; + Whist *wh; + + fid = r->fid; + path = fid->qid.path; + t = qidtype(fid->qid.path); + if((r->ifcall.mode != OREAD && t != Fnew && t != Fmap) + || (r->ifcall.mode&ORCLOSE)){ + respond(r, "permission denied"); + return; + } + + a = fid->aux; + switch(t){ + case Droot: + currentmap(0); + rlock(&maplock); + a->map = map; + incref(map); + runlock(&maplock); + respond(r, nil); + break; + + case D1st: + if((wh = gethistory(qidnum(path))) == nil){ + respond(r, "file does not exist"); + return; + } + closewhist(a->w); + a->w = wh; + a->n = a->w->ndoc-1; + r->ofcall.qid.vers = wh->doc[a->n].time; + r->fid->qid = r->ofcall.qid; + respond(r, nil); + break; + + case D2nd: + respond(r, nil); + break; + + case Fnew: + a->s = s_copy(""); + respond(r, nil); + break; + + case Fmap: + case F1st: + case F2nd: + respond(r, nil); + break; + + default: + respond(r, "programmer error"); + break; + } +} + +static char* +fsclone(Fid *old, Fid *new) +{ + Aux *a; + + a = emalloc(sizeof(*a)); + *a = *(Aux*)old->aux; + if(a->s) + s_incref(a->s); + if(a->w) + incref(a->w); + if(a->map) + incref(a->map); + if(a->name) + s_incref(a->name); + new->aux = a; + new->qid = old->qid; + + return nil; +} + +static void +fsdestroyfid(Fid *fid) +{ + Aux *a; + + a = fid->aux; + if(a==nil) + return; + + if(a->name) + s_free(a->name); + if(a->map) + closemap(a->map); + if(a->s) + s_free(a->s); + if(a->w) + closewhist(a->w); + free(a); + fid->aux = nil; +} + +static void +fillstat(Dir *d, uvlong path, ulong tm, ulong length) +{ + char tmp[32], *p; + int type; + + memset(d, 0, sizeof(Dir)); + d->uid = estrdup9p("wiki"); + d->gid = estrdup9p("wiki"); + + switch(qidtype(path)){ + case Droot: + case D1st: + case D2nd: + type = QTDIR; + break; + default: + type = 0; + break; + } + d->qid = (Qid){path, tm, type}; + + d->atime = d->mtime = tm; + d->length = length; + if(qidfile(path) == Qedithtml) + d->atime = d->mtime = time(0); + + switch(qidtype(path)){ + case Droot: + d->name = estrdup("/"); + d->mode = DMDIR|0555; + break; + + case D1st: + d->name = numtoname(qidnum(path)); + if(d->name == nil) + d->name = estrdup("<dead>"); + for(p=d->name; *p; p++) + if(*p==' ') + *p = '_'; + d->mode = DMDIR|0555; + break; + + case D2nd: + snprint(tmp, sizeof tmp, "%lud", tm); + d->name = estrdup(tmp); + d->mode = DMDIR|0555; + break; + + case Fmap: + d->name = estrdup("map"); + d->mode = 0666; + break; + + case Fnew: + d->name = estrdup("new"); + d->mode = 0666; + break; + + case F1st: + d->name = estrdup(filelist[qidfile(path)]); + d->mode = 0444; + break; + + case F2nd: + d->name = estrdup(filelist[qidfile(path)]); + d->mode = 0444; + break; + + default: + print("bad qid path 0x%.8llux\n", path); + break; + } +} + +static void +fsstat(Req *r) +{ + Aux *a; + Fid *fid; + ulong t; + + t = 0; + fid = r->fid; + if((a = fid->aux) && a->w) + t = a->w->doc[a->n].time; + + fillstat(&r->d, fid->qid.path, t, a->s ? s_len(a->s) : 0); + respond(r, nil); +} + +typedef struct Bogus Bogus; +struct Bogus { + uvlong path; + Aux *a; +}; + +static int +rootgen(int i, Dir *d, void *aux) +{ + Aux *a; + Bogus *b; + + b = aux; + a = b->a; + switch(i){ + case 0: /* new */ + fillstat(d, mkqid(Fnew, 0, 0, 0), a->map->t, 0); + return 0; + case 1: /* map */ + fillstat(d, mkqid(Fmap, 0, 0, 0), a->map->t, 0); + return 0; + default: /* first-level directory */ + i -= 2; + if(i >= a->map->nel) + return -1; + fillstat(d, mkqid(D1st, a->map->el[i].n, 0, 0), a->map->t, 0); + return 0; + } +} + +static int +firstgen(int i, Dir *d, void *aux) +{ + ulong t; + Bogus *b; + int num; + Aux *a; + + b = aux; + num = qidnum(b->path); + a = b->a; + t = a->w->doc[a->n].time; + + if(i < Nfile){ /* file in first-level directory */ + fillstat(d, mkqid(F1st, num, 0, i), t, 0); + return 0; + } + i -= Nfile; + + if(i < a->w->ndoc){ /* second-level (history) directory */ + fillstat(d, mkqid(D2nd, num, i, 0), a->w->doc[i].time, 0); + return 0; + } + //i -= a->w->ndoc; + + return -1; +} + +static int +secondgen(int i, Dir *d, void *aux) +{ + Bogus *b; + uvlong path; + Aux *a; + + b = aux; + path = b->path; + a = b->a; + + if(i <= Qraw){ /* index.html, index.txt, raw */ + fillstat(d, mkqid(F2nd, qidnum(path), qidvers(path), i), a->w->doc[a->n].time, 0); + return 0; + } + //i -= Qraw; + + return -1; +} + +static void +fsread(Req *r) +{ + char *t, *s; + uvlong path; + Aux *a; + Bogus b; + + a = r->fid->aux; + path = r->fid->qid.path; + b.a = a; + b.path = path; + switch(qidtype(path)){ + default: + respond(r, "cannot happen (bad qid)"); + return; + + case Droot: + if(a == nil || a->map == nil){ + respond(r, "cannot happen (no map)"); + return; + } + dirread9p(r, rootgen, &b); + respond(r, nil); + return; + + case D1st: + if(a == nil || a->w == nil){ + respond(r, "cannot happen (no wh)"); + return; + } + dirread9p(r, firstgen, &b); + respond(r, nil); + return; + + case D2nd: + dirread9p(r, secondgen, &b); + respond(r, nil); + return; + + case Fnew: + if(a->s){ + respond(r, "protocol botch"); + return; + } + /* fall through */ + case Fmap: + t = numtoname(a->n); + if(t == nil){ + respond(r, "unknown name"); + return; + } + for(s=t; *s; s++) + if(*s == ' ') + *s = '_'; + readstr(r, t); + free(t); + respond(r, nil); + return; + + case F1st: + case F2nd: + if(a == nil || a->s == nil){ + respond(r, "cannot happen (no s)"); + return; + } + readbuf(r, s_to_c(a->s), s_len(a->s)); + respond(r, nil); + return; + } +} + +typedef struct Sread Sread; +struct Sread { + char *rp; +}; + +static char* +Srdline(void *v, int c) +{ + char *p, *rv; + Sread *s; + + s = v; + if(s->rp == nil) + rv = nil; + else if(p = strchr(s->rp, c)){ + *p = '\0'; + rv = s->rp; + s->rp = p+1; + }else{ + rv = s->rp; + s->rp = nil; + } + return rv; +} + +static void +responderrstr(Req *r) +{ + char buf[ERRMAX]; + + rerrstr(buf, sizeof buf); + if(buf[0] == '\0') + strcpy(buf, "unknown error"); + respond(r, buf); +} + +static void +fswrite(Req *r) +{ + char *author, *comment, *net, *err, *p, *title, tmp[40]; + int rv, n; + ulong t; + Aux *a; + Fid *fid; + Sread s; + String *stmp; + Whist *w; + + fid = r->fid; + a = fid->aux; + switch(qidtype(fid->qid.path)){ + case Fmap: + stmp = s_nappend(s_reset(nil), r->ifcall.data, r->ifcall.count); + a->n = nametonum(s_to_c(stmp)); + s_free(stmp); + if(a->n < 0) + respond(r, "name not found"); + else + respond(r, nil); + return; + case Fnew: + break; + default: + respond(r, "cannot happen"); + return; + } + + if(a->s == nil){ + respond(r, "protocol botch"); + return; + } + if(r->ifcall.count==0){ /* do final processing */ + s.rp = s_to_c(a->s); + w = nil; + err = "bad format"; + if((title = Srdline(&s, '\n')) == nil){ + Error: + if(w) + closewhist(w); + s_free(a->s); + a->s = nil; + respond(r, err); + return; + } + + w = emalloc(sizeof(*w)); + incref(w); + w->title = estrdup(title); + + t = 0; + author = estrdup(s_to_c(a->name)); + + comment = nil; + while(s.rp && *s.rp && *s.rp != '\n'){ + p = Srdline(&s, '\n'); + assert(p != nil); + switch(p[0]){ + case 'A': + free(author); + author = estrdup(p+1); + break; + case 'D': + t = strtoul(p+1, &p, 10); + if(*p != '\0') + goto Error; + break; + case 'C': + free(comment); + comment = estrdup(p+1); + break; + } + } + + w->doc = emalloc(sizeof(w->doc[0])); + w->doc->time = time(0); + w->doc->comment = comment; + + if(net = r->pool->srv->aux){ + p = emalloc(strlen(author)+10+strlen(net)); + strcpy(p, author); + strcat(p, " ("); + strcat(p, net); + strcat(p, ")"); + free(author); + author = p; + } + w->doc->author = author; + + if((w->doc->wtxt = Brdpage(Srdline, &s)) == nil){ + err = "empty document"; + goto Error; + } + + w->ndoc = 1; + if((n = allocnum(w->title, 0)) < 0) + goto Error; + sprint(tmp, "D%lud\n", w->doc->time); + a->s = s_reset(a->s); + a->s = doctext(a->s, w->doc); + rv = writepage(n, t, a->s, w->title); + s_free(a->s); + a->s = nil; + a->n = n; + closewhist(w); + if(rv < 0) + responderrstr(r); + else + respond(r, nil); + return; + } + + if(s_len(a->s)+r->ifcall.count > Maxfile){ + respond(r, "file too large"); + s_free(a->s); + a->s = nil; + return; + } + a->s = s_nappend(a->s, r->ifcall.data, r->ifcall.count); + r->ofcall.count = r->ifcall.count; + respond(r, nil); +} + +Srv wikisrv = { +.attach= fsattach, +.destroyfid= fsdestroyfid, +.clone= fsclone, +.walk1= fswalk1, +.open= fsopen, +.read= fsread, +.write= fswrite, +.stat= fsstat, +}; + +void +usage(void) +{ + fprint(2, "usage: wikifs [-D] [-a addr]... [-m mtpt] [-p perm] [-s service] dir\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char **addr; + int i, naddr; + char *buf; + char *service, *mtpt; + ulong perm; + Dir d, *dp; + Srv *s; + + naddr = 0; + addr = nil; + perm = 0; + service = nil; + mtpt = "/mnt/wiki"; + ARGBEGIN{ + case 'D': + chatty9p++; + break; + case 'a': + if(naddr%8 == 0) + addr = erealloc(addr, (naddr+8)*sizeof(addr[0])); + addr[naddr++] = EARGF(usage()); + break; + case 'm': + mtpt = EARGF(usage()); + break; + case 'M': + mtpt = nil; + break; + case 'p': + perm = strtoul(EARGF(usage()), nil, 8); + break; + case 's': + service = EARGF(usage()); + break; + default: + usage(); + break; + }ARGEND + + if(argc != 1) + usage(); + + if((dp = dirstat(argv[0])) == nil) + sysfatal("dirstat %s: %r", argv[0]); + if((dp->mode&DMDIR) == 0) + sysfatal("%s: not a directory", argv[0]); + free(dp); + wikidir = argv[0]; + + currentmap(0); + + for(i=0; i<naddr; i++) + listensrv(&wikisrv, addr[i]); + + s = emalloc(sizeof *s); + *s = wikisrv; + postmountsrv(s, service, mtpt, MREPL|MCREATE); + if(perm){ + buf = emalloc9p(5+strlen(service)+1); + strcpy(buf, "/srv/"); + strcat(buf, service); + nulldir(&d); + d.mode = perm; + if(dirwstat(buf, &d) < 0) + fprint(2, "wstat: %r\n"); + free(buf); + } + exits(nil); +} diff --git a/sys/src/cmd/wikifs/io.c b/sys/src/cmd/wikifs/io.c new file mode 100755 index 000000000..43743744e --- /dev/null +++ b/sys/src/cmd/wikifs/io.c @@ -0,0 +1,700 @@ +/* + * I/O for a Wiki document set. + * + * The files are kept in one flat directory. + * There are three files for each document: + * nnn - current version of the document + * nnn.hist - history (all old versions) of the document + * append-only + * L.nnn - write lock file for the document + * + * At the moment, since we don't have read/write locks + * in the file system, we use the L.nnn file as a read lock too. + * It's a hack but there aren't supposed to be many readers + * anyway. + * + * The nnn.hist file is in the format read by Brdwhist. + * The nnn file is in that format too, but only contains the + * last entry of the nnn.hist file. + * + * In addition to this set of files, there is an append-only + * map file that provides a mapping between numbers and titles. + * The map file is a sequence of lines of the form + * nnn Title Here + * The lock file L.map must be held to add to the end, to + * make sure that the numbers are allocated sequentially. + * + * We assume that writes to the map file will fit in one message, + * so that we don't have to read-lock the file. + */ + +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +enum { + Nhash = 64, + Mcache = 128, +}; + +typedef struct Wcache Wcache; +struct Wcache { + int n; + ulong use; + RWLock; + ulong tcurrent; + ulong thist; + Whist *hist; + Whist *current; + Qid qid; + Qid qidhist; + Wcache *hash; +}; + +static RWLock cachelock; +static Wcache *tab[Nhash]; +int ncache; + +void +closewhist(Whist *wh) +{ + int i; + + if(wh && decref(wh) == 0){ + free(wh->title); + for(i=0; i<wh->ndoc; i++){ + free(wh->doc[i].author); + free(wh->doc[i].comment); + freepage(wh->doc[i].wtxt); + } + free(wh->doc); + free(wh); + } +} + +void +freepage(Wpage *p) +{ + Wpage *next; + + for(; p; p=next){ + next = p->next; + free(p->text); + free(p->url); + free(p); + } +} + +static Wcache* +findcache(int n) +{ + Wcache *w; + + for(w=tab[n%Nhash]; w; w=w->hash) + if(w->n == n){ + w->use = time(0); + return w; + } + return nil; +} + +static int +getlock(char *lock) +{ + char buf[ERRMAX]; + int i, fd; + enum { SECS = 200 }; + + for(i=0; i<SECS*10; i++){ + fd = wcreate(lock, ORDWR, DMEXCL|0666); + if(fd >= 0) + return fd; + buf[0] = '\0'; + rerrstr(buf, sizeof buf); + if(strstr(buf, "locked") == nil) + break; + sleep(1000/10); + } + werrstr("couldn't acquire lock %s: %r", lock); + return -1; +} + +static Whist* +readwhist(char *file, char *lock, Qid *qid) +{ + int lfd; + Biobuf *b; + Dir *d; + Whist *wh; + + if((lfd=getlock(lock)) < 0) // LOG? + return nil; + + if(qid){ + if((d = wdirstat(file)) == nil){ + close(lfd); + return nil; + } + *qid = d->qid; + free(d); + } + + if((b = wBopen(file, OREAD)) == nil){ //LOG? + close(lfd); + return nil; + } + + wh = Brdwhist(b); + + Bterm(b); + close(lfd); + return wh; +} + +static void +gencurrent(Wcache *w, Qid *q, char *file, char *lock, ulong *t, Whist **wp, int n) +{ + Dir *d; + Whist *wh; + + if(*wp && *t+Tcache >= time(0)) + return; + + wlock(w); + if(*wp && *t+Tcache >= time(0)){ + wunlock(w); + return; + } + + if(((d = wdirstat(file)) == nil) || (d->qid.path==q->path && d->qid.vers==q->vers)){ + *t = time(0); + wunlock(w); + free(d); + return; + } + + free(d); + if(wh = readwhist(file, lock, q)){ + wh->n = n; + *t = time(0); + closewhist(*wp); + *wp = wh; + } +else fprint(2, "error file=%s lock=%s %r\n", file, lock); + wunlock(w); +} + +static void +current(Wcache *w) +{ + char tmp[40]; + char tmplock[40]; + + sprint(tmplock, "d/L.%d", w->n); + sprint(tmp, "d/%d", w->n); + gencurrent(w, &w->qid, tmp, tmplock, &w->tcurrent, &w->current, w->n); +} + +static void +currenthist(Wcache *w) +{ + char hist[40], lock[40]; + + sprint(hist, "d/%d.hist", w->n); + sprint(lock, "d/L.%d", w->n); + + gencurrent(w, &w->qidhist, hist, lock, &w->thist, &w->hist, w->n); +} + +void +voidcache(int n) +{ + Wcache *c; + + rlock(&cachelock); + if(c = findcache(n)){ + wlock(c); + c->tcurrent = 0; + c->thist = 0; + /* aggressively free memory */ + closewhist(c->hist); + c->hist = nil; + closewhist(c->current); + c->current = nil; + wunlock(c); + } + runlock(&cachelock); +} + +static Whist* +getcache(int n, int hist) +{ + int i, isw; + ulong t; + Wcache *c, **cp, **evict; + Whist *wh; + + isw = 0; + rlock(&cachelock); + if(c = findcache(n)){ + Found: + current(c); + if(hist) + currenthist(c); + rlock(c); + if(hist) + wh = c->hist; + else + wh = c->current; + if(wh) + incref(wh); + runlock(c); + if(isw) + wunlock(&cachelock); + else + runlock(&cachelock); + return wh; + } + runlock(&cachelock); + + wlock(&cachelock); + if(c = findcache(n)){ + isw = 1; /* better to downgrade lock but can't */ + goto Found; + } + + if(ncache < Mcache){ + Alloc: + c = emalloc(sizeof *c); + ncache++; + }else{ + /* find something to evict. */ + t = ~0; + evict = nil; + for(i=0; i<Nhash; i++){ + for(cp=&tab[i], c=*cp; c; cp=&c->hash, c=*cp){ + if(c->use < t + && (!c->hist || c->hist->ref==1) + && (!c->current || c->current->ref==1)){ + evict = cp; + t = c->use; + } + } + } + + if(evict == nil){ + fprint(2, "wikifs: nothing to evict\n"); + goto Alloc; + } + + c = *evict; + *evict = c->hash; + + closewhist(c->current); + closewhist(c->hist); + memset(c, 0, sizeof *c); + } + + c->n = n; + c->hash = tab[n%Nhash]; + tab[n%Nhash] = c; + isw = 1; + goto Found; +} + +Whist* +getcurrent(int n) +{ + return getcache(n, 0); +} + +Whist* +gethistory(int n) +{ + return getcache(n, 1); +} + +RWLock maplock; +Map *map; + +static int +mapcmp(const void *va, const void *vb) +{ + Mapel *a, *b; + + a = (Mapel*)va; + b = (Mapel*)vb; + + return strcmp(a->s, b->s); +} + +void +closemap(Map *m) +{ + if(decref(m)==0){ + free(m->buf); + free(m->el); + free(m); + } +} + +void +currentmap(int force) +{ + char *p, *q, *r; + int lfd, fd, m, n; + Dir *d; + Map *nmap; + char *err = nil; + + lfd = -1; + fd = -1; + d = nil; + nmap = nil; + if(!force && map && map->t+Tcache >= time(0)) + return; + + wlock(&maplock); + if(!force && map && map->t+Tcache >= time(0)) + goto Return; + + if((lfd = getlock("d/L.map")) < 0){ + err = "can't lock"; + goto Return; + } + + if((d = wdirstat("d/map")) == nil) + goto Return; + + if(map && d->qid.path == map->qid.path && d->qid.vers == map->qid.vers){ + map->t = time(0); + goto Return; + } + + if(d->length > Maxmap){ + //LOG + err = "too long"; + goto Return; + } + + if((fd = wopen("d/map", OREAD)) < 0) + goto Return; + + nmap = emalloc(sizeof *nmap); + nmap->buf = emalloc(d->length+1); + n = readn(fd, nmap->buf, d->length); + if(n != d->length){ + err = "bad length"; + goto Return; + } + nmap->buf[n] = '\0'; + + n = 0; + for(p=nmap->buf; p; p=strchr(p+1, '\n')) + n++; + nmap->el = emalloc(n*sizeof(nmap->el[0])); + + m = 0; + for(p=nmap->buf; p && *p && m < n; p=q){ + if(q = strchr(p+1, '\n')) + *q++ = '\0'; + nmap->el[m].n = strtol(p, &r, 10); + if(*r == ' ') + r++; + else + {}//LOG? + nmap->el[m].s = strcondense(r, 1); + m++; + } + //LOG if m != n + + nmap->qid = d->qid; + nmap->t = time(0); + nmap->nel = m; + qsort(nmap->el, nmap->nel, sizeof(nmap->el[0]), mapcmp); + if(map) + closemap(map); + map = nmap; + incref(map); + nmap = nil; + +Return: + free(d); + if(nmap){ + free(nmap->el); + free(nmap->buf); + free(nmap); + } + if(map == nil) + sysfatal("cannot get map: %s: %r", err); + + if(fd >= 0) + close(fd); + if(lfd >= 0) + close(lfd); + wunlock(&maplock); +} + +int +allocnum(char *title, int mustbenew) +{ + char *p, *q; + int lfd, fd, n; + Biobuf b; + + if(strcmp(title, "map")==0 || strcmp(title, "new")==0){ + werrstr("reserved title name"); + return -1; + } + + if(title[0]=='\0' || strpbrk(title, "/<>:?")){ + werrstr("invalid character in name"); + return -1; + } + if((n = nametonum(title)) >= 0){ + if(mustbenew){ + werrstr("duplicate title"); + return -1; + } + return n; + } + + title = estrdup(title); + strcondense(title, 1); + strlower(title); + if(strchr(title, '\n') || strlen(title) > 200){ + werrstr("bad title"); + free(title); + return -1; + } + + if((lfd = getlock("d/L.map")) < 0){ + free(title); + return -1; + } + + if((fd = wopen("d/map", ORDWR)) < 0){ // LOG? + close(lfd); + free(title); + return -1; + } + + /* + * What we really need to do here is make sure the + * map is up-to-date, then make sure the title isn't + * taken, and then add it, all without dropping the locks. + * + * This turns out to be a mess when you start adding + * all the necessary dolock flags, so instead we just + * read through the file ourselves, and let our + * map catch up on its own. + */ + Binit(&b, fd, OREAD); + n = 0; + while(p = Brdline(&b, '\n')){ + p[Blinelen(&b)-1] = '\0'; + n = atoi(p)+1; + q = strchr(p, ' '); + if(q == nil) + continue; + if(strcmp(q+1, title) == 0){ + free(title); + close(fd); + close(lfd); + if(mustbenew){ + werrstr("duplicate title"); + return -1; + }else + return n; + } + } + + seek(fd, 0, 2); /* just in case it's not append only */ + fprint(fd, "%d %s\n", n, title); + close(fd); + close(lfd); + free(title); + /* kick the map */ + currentmap(1); + return n; +} + +int +nametonum(char *s) +{ + char *p; + int i, lo, hi, m, rv; + + s = estrdup(s); + strlower(s); + for(p=s; *p; p++) + if(*p=='_') + *p = ' '; + + currentmap(0); + rlock(&maplock); + lo = 0; + hi = map->nel; + while(hi-lo > 1){ + m = (lo+hi)/2; + i = strcmp(s, map->el[m].s); + if(i < 0) + hi = m; + else + lo = m; + } + if(hi-lo == 1 && strcmp(s, map->el[lo].s)==0) + rv = map->el[lo].n; + else + rv = -1; + runlock(&maplock); + free(s); + return rv; +} + +char* +numtoname(int n) +{ + int i; + char *s; + + currentmap(0); + rlock(&maplock); + for(i=0; i<map->nel; i++){ + if(map->el[i].n==n) + break; + } + if(i==map->nel){ + runlock(&maplock); + return nil; + } + s = estrdup(map->el[i].s); + runlock(&maplock); + return s; +} + +Whist* +getcurrentbyname(char *s) +{ + int n; + + if((n = nametonum(s)) < 0) + return nil; + return getcache(n, 0); +} + +static String* +Brdstring(Biobuf *b) +{ + long len; + String *s; + Dir *d; + + d = dirfstat(Bfildes(b)); + if (d == nil) /* shouldn't happen, we just opened it */ + len = 0; + else + len = d->length; + free(d); + s = s_newalloc(len); + s_read(b, s, len); + return s; +} + +/* + * Attempt to install a new page. If t==0 we are creating. + * Otherwise, we are editing and t must be set to the current + * version (t is the version we started with) to avoid conflicting + * writes. + * + * If there is a conflicting write, we still write the page to + * the history file, but mark it as a failed write. + */ +int +writepage(int num, ulong t, String *s, char *title) +{ + char tmp[40], tmplock[40], err[ERRMAX], hist[40], *p; + int conflict, lfd, fd; + Biobuf *b; + String *os; + + sprint(tmp, "d/%d", num); + sprint(tmplock, "d/L.%d", num); + sprint(hist, "d/%d.hist", num); + if((lfd = getlock(tmplock)) < 0) + return -1; + + conflict = 0; + if(b = wBopen(tmp, OREAD)){ + Brdline(b, '\n'); /* title */ + if(p = Brdline(b, '\n')) /* version */ + p[Blinelen(b)-1] = '\0'; + if(p==nil || p[0] != 'D'){ + snprint(err, sizeof err, "bad format in extant file"); + conflict = 1; + }else if(strtoul(p+1, 0, 0) != t){ + os = Brdstring(b); /* why read the whole file? */ + p = strchr(s_to_c(s), '\n'); + if(p!=nil && strcmp(p+1, s_to_c(os))==0){ /* ignore dup write */ + close(lfd); + s_free(os); + Bterm(b); + return 0; + } + s_free(os); + snprint(err, sizeof err, "update conflict %lud != %s", t, p+1); + conflict = 1; + } + Bterm(b); + }else{ + if(t != 0){ + close(lfd); + werrstr("did not expect to create"); + return -1; + } + } + + if((fd = wopen(hist, OWRITE)) < 0){ + if((fd = wcreate(hist, OWRITE, 0666)) < 0){ + close(lfd); + return -1; + }else + fprint(fd, "%s\n", title); + } + if(seek(fd, 0, 2) < 0 + || (conflict && write(fd, "X\n", 2) != 2) + || write(fd, s_to_c(s), s_len(s)) != s_len(s)){ + close(fd); + close(lfd); + return -1; + } + close(fd); + + if(conflict){ + close(lfd); + voidcache(num); + werrstr(err); + return -1; + } + + if((fd = wcreate(tmp, OWRITE, 0666)) < 0){ + close(lfd); + voidcache(num); + return -1; + } + if(write(fd, title, strlen(title)) != strlen(title) + || write(fd, "\n", 1) != 1 + || write(fd, s_to_c(s), s_len(s)) != s_len(s)){ + close(fd); + close(lfd); + voidcache(num); + return -1; + } + close(fd); + close(lfd); + voidcache(num); + return 0; +} diff --git a/sys/src/cmd/wikifs/lookup.c b/sys/src/cmd/wikifs/lookup.c new file mode 100755 index 000000000..997766e92 --- /dev/null +++ b/sys/src/cmd/wikifs/lookup.c @@ -0,0 +1,12 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +void +main(int argc, char **argv) +{ + print("%d\n", nametonum(argv[1])); +} diff --git a/sys/src/cmd/wikifs/map.c b/sys/src/cmd/wikifs/map.c new file mode 100755 index 000000000..88c5953bc --- /dev/null +++ b/sys/src/cmd/wikifs/map.c @@ -0,0 +1,7 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + diff --git a/sys/src/cmd/wikifs/mkfile b/sys/src/cmd/wikifs/mkfile new file mode 100755 index 000000000..ed0d5ef2f --- /dev/null +++ b/sys/src/cmd/wikifs/mkfile @@ -0,0 +1,33 @@ +</$objtype/mkfile + +TARG=wikifs + +HFILES=wiki.h +COFILES=\ + io.$O\ + parse.$O\ + parsehist.$O\ + tohtml.$O\ + util.$O\ + wdir.$O\ + +OFILES=fs.$O $COFILES + +BIN=/$objtype/bin +LIB=/$objtype/lib/lib9p.a #/$objtype/lib/libdebugmalloc.a + +UPDATE=\ + mkfile\ + $HFILES\ + ${OFILES:%.$O=%.c}\ + +</sys/src/cmd/mkone + +$O.wiki2html: wiki2html.$O $COFILES + $LD -o $target $prereq + +$O.wiki2text: wiki2text.$O $COFILES + $LD -o $target $prereq + +$O.owikifs: ofs.$O $COFILES + $LD -o $target $prereq diff --git a/sys/src/cmd/wikifs/parse.c b/sys/src/cmd/wikifs/parse.c new file mode 100755 index 000000000..dc8924317 --- /dev/null +++ b/sys/src/cmd/wikifs/parse.c @@ -0,0 +1,331 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <ctype.h> +#include <thread.h> +#include "wiki.h" + +static Wpage* +mkwtxt(int type, char *text) +{ + Wpage *w; + + w = emalloc(sizeof(*w)); + w->type = type; + w->text = text; + setmalloctag(w, getcallerpc(&type)); + return w; +} + +/* + * turn runs of whitespace into single spaces, + * eliminate whitespace at beginning and end. + */ +char* +strcondense(char *s, int cutbegin) +{ + char *r, *w, *es; + int inspace; + + es = s+strlen(s); + inspace = cutbegin; + for(r=w=s; *r; r++){ + if(isspace(*r)){ + if(!inspace){ + inspace=1; + *w++ = ' '; + } + }else{ + inspace=0; + *w++ = *r; + } + } + assert(w <= es); + if(inspace && w>s){ + --w; + *w = '\0'; + } + else + *w = '\0'; + return s; +} + +/* + * turn runs of Wplain into single Wplain. + */ +static Wpage* +wcondense(Wpage *wtxt) +{ + Wpage *ow, *w; + + for(w=wtxt; w; ){ + if(w->type == Wplain) + strcondense(w->text, 1); + + if(w->type != Wplain || w->next==nil + || w->next->type != Wplain){ + w=w->next; + continue; + } + + w->text = erealloc(w->text, strlen(w->text)+1+strlen(w->next->text)+1); + strcat(w->text, " "); + strcat(w->text, w->next->text); + + ow = w->next; + w->next = ow->next; + ow->next = nil; + freepage(ow); + } + return wtxt; +} + +/* + * Parse a link, without the brackets. + */ +static Wpage* +mklink(char *s) +{ + char *q; + Wpage *w; + + for(q=s; *q && *q != '|'; q++) + ; + + if(*q == '\0'){ + w = mkwtxt(Wlink, estrdup(strcondense(s, 1))); + w->url = nil; + }else{ + *q = '\0'; + w = mkwtxt(Wlink, estrdup(strcondense(s, 1))); + w->url = estrdup(strcondense(q+1, 1)); + } + setmalloctag(w, getcallerpc(&s)); + return w; +} + +/* + * Parse Wplains, inserting Wlink nodes where appropriate. + */ +static Wpage* +wlink(Wpage *wtxt) +{ + char *p, *q, *r, *s; + Wpage *w, *nw; + + for(w=wtxt; w; w=nw){ + nw = w->next; + if(w->type != Wplain) + continue; + while(w->text[0]){ + p = w->text; + for(q=p; *q && *q != '['; q++) + ; + if(*q == '\0') + break; + for(r=q; *r && *r != ']'; r++) + ; + if(*r == '\0') + break; + *q = '\0'; + *r = '\0'; + s = w->text; + w->text = estrdup(w->text); + w->next = mklink(q+1); + w = w->next; + w->next = mkwtxt(Wplain, estrdup(r+1)); + free(s); + w = w->next; + w->next = nw; + } + assert(w->next == nw); + } + return wtxt; +} + +static int +ismanchar(int c) +{ + return ('a' <= c && c <= 'z') + || ('A' <= c && c <= 'Z') + || ('0' <= c && c <= '9') + || c=='_' || c=='-' || c=='.' || c=='/' + || (c < 0); /* UTF */ +} + +static Wpage* +findmanref(char *p, char **beginp, char **endp) +{ + char *q, *r; + Wpage *w; + + q=p; + for(;;){ + for(; q[0] && (q[0] != '(' || !isdigit(q[1]) || q[2] != ')'); q++) + ; + if(*q == '\0') + break; + for(r=q; r>p && ismanchar(r[-1]); r--) + ; + if(r==q){ + q += 3; + continue; + } + *q = '\0'; + w = mkwtxt(Wman, estrdup(r)); + *beginp = r; + *q = '('; + w->section = q[1]-'0'; + *endp = q+3; + setmalloctag(w, getcallerpc(&p)); + return w; + } + return nil; +} + +/* + * Parse Wplains, looking for man page references. + * This should be done by using a plumb(6)-style + * control file rather than hard-coding things here. + */ +static Wpage* +wman(Wpage *wtxt) +{ + char *q, *r; + Wpage *w, *mw, *nw; + + for(w=wtxt; w; w=nw){ + nw = w->next; + if(w->type != Wplain) + continue; + while(w->text[0]){ + if((mw = findmanref(w->text, &q, &r)) == nil) + break; + *q = '\0'; + w->next = mw; + w = w->next; + w->next = mkwtxt(Wplain, estrdup(r)); + w = w->next; + w->next = nw; + } + assert(w->next == nw); + } + return wtxt; +} + +static int isheading(char *p) { + Rune r; + int hasupper=0; + while(*p) { + p+=chartorune(&r,p); + if(isupperrune(r)) + hasupper=1; + else if(islowerrune(r)) + return 0; + } + return hasupper; +} + +Wpage* +Brdpage(char *(*rdline)(void*,int), void *b) +{ + char *p, *c; + int waspara; + Wpage *w, **pw; + + w = nil; + pw = &w; + waspara = 1; + while((p = rdline(b, '\n')) != nil){ + if(p[0] != '!') + p = strcondense(p, 1); + if(p[0] == '\0'){ + if(waspara==0){ + waspara=1; + *pw = mkwtxt(Wpara, nil); + pw = &(*pw)->next; + } + continue; + } + waspara = 0; + switch(p[0]){ + case '*': + *pw = mkwtxt(Wbullet, nil); + pw = &(*pw)->next; + *pw = mkwtxt(Wplain, estrdup(p+1)); + pw = &(*pw)->next; + break; + case '!': + *pw = mkwtxt(Wpre, estrdup(p[1]==' '?p+2:p+1)); + pw = &(*pw)->next; + break; + case '-': + for(c = p; *c != '\0'; c++) { + if(*c != '-') { + c = p; + break; + } + } + + if( (c-p) > 4) { + *pw = mkwtxt(Whr, nil); + pw = &(*pw)->next; + break; + } + /* else fall thru */ + default: + if(isheading(p)){ + *pw = mkwtxt(Wheading, estrdup(p)); + pw = &(*pw)->next; + continue; + } + *pw = mkwtxt(Wplain, estrdup(p)); + pw = &(*pw)->next; + break; + } + } + if(w == nil) + werrstr("empty page"); + + *pw = nil; + w = wcondense(w); + w = wlink(w); + w = wman(w); + setmalloctag(w, getcallerpc(&rdline)); + + return w; +} + +void +printpage(Wpage *w) +{ + for(; w; w=w->next){ + switch(w->type){ + case Wpara: + print("para\n"); + break; + case Wheading: + print("heading '%s'\n", w->text); + break; + case Wbullet: + print("bullet\n"); + break; + case Wlink: + print("link '%s' '%s'\n", w->text, w->url); + break; + case Wman: + print("man %d %s\n", w->section, w->text); + break; + case Wplain: + print("plain '%s'\n", w->text); + break; + case Whr: + print("hr\n"); + break; + case Wpre: + print("pre '%s'\n", w->text); + break; + } + } +} diff --git a/sys/src/cmd/wikifs/parsehist.c b/sys/src/cmd/wikifs/parsehist.c new file mode 100755 index 000000000..88b49dd7e --- /dev/null +++ b/sys/src/cmd/wikifs/parsehist.c @@ -0,0 +1,130 @@ +/* + * Read a Wiki history file. + * It's a title line then a sequence of Wiki files separated by headers. + * + * Ddate/time + * #body + * #... + * #... + * #... + * etc. + */ + +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +static char* +Brdwline(void *vb, int sep) +{ + Biobufhdr *b; + char *p; + + b = vb; + if(Bgetc(b) == '#'){ + if(p = Brdline(b, sep)) + p[Blinelen(b)-1] = '\0'; + return p; + }else{ + Bungetc(b); + return nil; + } +} + +Whist* +Brdwhist(Biobuf *b) +{ + int i, current, conflict, c, n; + char *author, *comment, *p, *title; + ulong t; + Wdoc *w; + Whist *h; + + if((p = Brdline(b, '\n')) == nil){ + werrstr("short read: %r"); + return nil; + } + + p[Blinelen(b)-1] = '\0'; + p = strcondense(p, 1); + title = estrdup(p); + + w = nil; + n = 0; + t = -1; + author = nil; + comment = nil; + conflict = 0; + current = 0; + while((c = Bgetc(b)) != Beof){ + if(c != '#'){ + p = Brdline(b, '\n'); + if(p == nil) + break; + p[Blinelen(b)-1] = '\0'; + + switch(c){ + case 'D': + t = strtoul(p, 0, 10); + break; + case 'A': + free(author); + author = estrdup(p); + break; + case 'C': + free(comment); + comment = estrdup(p); + break; + case 'X': + conflict = 1; + } + } else { /* c=='#' */ + Bungetc(b); + if(n%8 == 0) + w = erealloc(w, (n+8)*sizeof(w[0])); + w[n].time = t; + w[n].author = author; + w[n].comment = comment; + comment = nil; + author = nil; + w[n].wtxt = Brdpage(Brdwline, b); + w[n].conflict = conflict; + if(w[n].wtxt == nil) + goto Error; + if(!conflict) + current = n; + n++; + conflict = 0; + t = -1; + } + } + if(w==nil) + goto Error; + + free(comment); + free(author); + h = emalloc(sizeof *h); + h->title = title; + h->doc = w; + h->ndoc = n; + h->current = current; + incref(h); + setmalloctag(h, getcallerpc(&b)); + return h; + +Error: + free(title); + free(author); + free(comment); + for(i=0; i<n; i++){ + free(w[i].author); + free(w[i].comment); + freepage(w[i].wtxt); + } + free(w); + return nil; +} + diff --git a/sys/src/cmd/wikifs/testwrite.c b/sys/src/cmd/wikifs/testwrite.c new file mode 100755 index 000000000..86e418abb --- /dev/null +++ b/sys/src/cmd/wikifs/testwrite.c @@ -0,0 +1,52 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +char *wikidir = "."; + +void +usage(void) +{ + fprint(2, "usage: testwrite [-d dir] wikifile n\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + ulong t; + int i; + Biobuf *b; + String *h; + Whist *doc; + char tmp[20]; + + t = 0; + ARGBEGIN{ + case 't': + t = strtoul(EARGF(usage()), 0, 0); + break; + default: + usage(); + }ARGEND + + if(argc != 2) + usage(); + + if((b = Bopen(argv[0], OREAD)) == nil) + sysfatal("Bopen: %r"); + + if((doc = Brdwhist(b)) == nil) + sysfatal("Brdwtxt: %r"); + + sprint(tmp, "D%lud\n", time(0)); + if((h = pagetext(s_copy(tmp), (doc->doc+doc->ndoc-1)->wtxt, 1))==nil) + sysfatal("wiki2text: %r"); + + if(writepage(atoi(argv[1]), t, h, doc->title) <0) + sysfatal("writepage: %r"); + exits(0); +} 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; +} diff --git a/sys/src/cmd/wikifs/util.c b/sys/src/cmd/wikifs/util.c new file mode 100755 index 000000000..d108cfb83 --- /dev/null +++ b/sys/src/cmd/wikifs/util.c @@ -0,0 +1,133 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <ctype.h> +#include <thread.h> +#include "wiki.h" + +void* +erealloc(void *v, ulong n) +{ + v = realloc(v, n); + if(v == nil) + sysfatal("out of memory reallocating %lud", n); + setmalloctag(v, getcallerpc(&v)); + return v; +} + +void* +emalloc(ulong n) +{ + void *v; + + v = malloc(n); + if(v == nil) + sysfatal("out of memory allocating %lud", n); + memset(v, 0, n); + setmalloctag(v, getcallerpc(&n)); + return v; +} + +char* +estrdup(char *s) +{ + int l; + char *t; + + if (s == nil) + return nil; + l = strlen(s)+1; + t = emalloc(l); + memmove(t, s, l); + setmalloctag(t, getcallerpc(&s)); + return t; +} + +char* +estrdupn(char *s, int n) +{ + int l; + char *t; + + l = strlen(s); + if(l > n) + l = n; + t = emalloc(l+1); + memmove(t, s, l); + t[l] = '\0'; + setmalloctag(t, getcallerpc(&s)); + return t; +} + +char* +strlower(char *s) +{ + char *p; + + for(p=s; *p; p++) + if('A' <= *p && *p <= 'Z') + *p += 'a'-'A'; + return s; +} + +String* +s_appendsub(String *s, char *p, int n, Sub *sub, int nsub) +{ + int i, m; + char *q, *r, *ep; + + ep = p+n; + while(p<ep){ + q = ep; + m = -1; + for(i=0; i<nsub; i++){ + if(sub[i].sub && (r = strstr(p, sub[i].match)) && r < q){ + q = r; + m = i; + } + } + s = s_nappend(s, p, q-p); + p = q; + if(m >= 0){ + s = s_append(s, sub[m].sub); + p += strlen(sub[m].match); + } + } + return s; +} + +String* +s_appendlist(String *s, ...) +{ + char *x; + va_list arg; + + va_start(arg, s); + while(x = va_arg(arg, char*)) + s = s_append(s, x); + va_end(arg); + return s; +} + +int +opentemp(char *template) +{ + int fd, i; + char *p; + + p = estrdup(template); + fd = -1; + for(i=0; i<10; i++){ + mktemp(p); + if(access(p, 0) < 0 && (fd=create(p, ORDWR|ORCLOSE, 0444)) >= 0) + break; + strcpy(p, template); + } + if(fd >= 0) + strcpy(template, p); + free(p); + + return fd; +} + diff --git a/sys/src/cmd/wikifs/wdir.c b/sys/src/cmd/wikifs/wdir.c new file mode 100755 index 000000000..a2b4f6b26 --- /dev/null +++ b/sys/src/cmd/wikifs/wdir.c @@ -0,0 +1,76 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +/* open, create relative to wiki dir */ +char *wikidir; + +static char* +wname(char *s) +{ + char *t; + + t = emalloc(strlen(wikidir)+1+strlen(s)+1); + strcpy(t, wikidir); + strcat(t, "/"); + strcat(t, s); + return t; +} + +int +wopen(char *fn, int mode) +{ + int rv; + + fn = wname(fn); + rv = open(fn, mode); + free(fn); + return rv; +} + +int +wcreate(char *fn, int mode, long perm) +{ + int rv; + + fn = wname(fn); + rv = create(fn, mode, perm); + free(fn); + return rv; +} + +Biobuf* +wBopen(char *fn, int mode) +{ + Biobuf *rv; + + fn = wname(fn); + rv = Bopen(fn, mode); + free(fn); + return rv; +} + +int +waccess(char *fn, int mode) +{ + int rv; + + fn = wname(fn); + rv = access(fn, mode); + free(fn); + return rv; +} + +Dir* +wdirstat(char *fn) +{ + Dir *d; + + fn = wname(fn); + d = dirstat(fn); + free(fn); + return d; +} diff --git a/sys/src/cmd/wikifs/wiki.h b/sys/src/cmd/wikifs/wiki.h new file mode 100755 index 000000000..88cb53388 --- /dev/null +++ b/sys/src/cmd/wikifs/wiki.h @@ -0,0 +1,121 @@ +typedef struct Map Map; +typedef struct Mapel Mapel; +typedef struct Sub Sub; +typedef struct Wdoc Wdoc; +typedef struct Whist Whist; +typedef struct Wpage Wpage; + +enum { + Tcache = 5, /* seconds */ + Maxmap = 10*1024*1024, + Maxfile = 100*1024, +}; +enum { + Wpara, + Wheading, + Wbullet, + Wlink, + Wman, + Wplain, + Wpre, + Whr, + Nwtxt, +}; + +struct Wpage { + int type; + char *text; + int section; /* Wman */ + char *url; /* Wlink */ + Wpage *next; +}; + +struct Whist { + Ref; + int n; + char *title; + Wdoc *doc; + int ndoc; + int current; +}; + +struct Wdoc { + char *author; + char *comment; + int conflict; + ulong time; + Wpage *wtxt; +}; + +enum { + Tpage, + Tedit, + Tdiff, + Thistory, + Tnew, + Toldpage, + Twerror, + Ntemplate, +}; + +struct Sub { + char *match; + char *sub; +}; + +struct Mapel { + char *s; + int n; +}; + +struct Map { + Ref; + Mapel *el; + int nel; + ulong t; + char *buf; + Qid qid; +}; + +void *erealloc(void*, ulong); +void *emalloc(ulong); +char *estrdup(char*); +char *estrdupn(char*, int); +char *strcondense(char*, int); +char *strlower(char*); + +String *s_appendsub(String*, char*, int, Sub*, int); +String *s_appendlist(String*, ...); +Whist *Brdwhist(Biobuf*); +Wpage *Brdpage(char*(*)(void*,int), void*); + +void printpage(Wpage*); +String *pagehtml(String*, Wpage*, int); +String *pagetext(String*, Wpage*, int); +String *tohtml(Whist*, Wdoc*, int); +String *totext(Whist*, Wdoc*, int); +String *doctext(String*, Wdoc*); + +Whist *getcurrent(int); +Whist *getcurrentbyname(char*); +Whist *gethistory(int); +void closewhist(Whist*); +int allocnum(char*, int); +void freepage(Wpage*); +int nametonum(char*); +char *numtoname(int); +int writepage(int, ulong, String*, char*); +void voidcache(int); + +void closemap(Map*); +void currentmap(int); + +extern Map *map; +extern RWLock maplock; +extern char *wikidir; +Biobuf *wBopen(char*, int); +int wopen(char*, int); +int wcreate(char*, int, long); +int waccess(char*, int); +Dir *wdirstat(char*); +int opentemp(char*); diff --git a/sys/src/cmd/wikifs/wiki2html.c b/sys/src/cmd/wikifs/wiki2html.c new file mode 100755 index 000000000..902a1942f --- /dev/null +++ b/sys/src/cmd/wikifs/wiki2html.c @@ -0,0 +1,67 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +char *wikidir; + +void +usage(void) +{ + fprint(2, "usage: wiki2html [-hoDP ] [-d dir] wikifile\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int t; + int parse; + String *h; + Whist *doc; + + rfork(RFNAMEG); + + t = Tpage; + ARGBEGIN{ + default: + usage(); + case 'd': + wikidir = EARGF(usage()); + break; + case 'h': + t = Thistory; + break; + case 'o': + t = Toldpage; + break; + case 'D': + t = Tdiff; + break; + case 'P': + parse = 1; + }ARGEND + + if(argc != 1) + usage(); + + if(t == Thistory || t==Tdiff) + doc = gethistory(atoi(argv[0])); + else + doc = getcurrent(atoi(argv[0])); + + if(doc == nil) + sysfatal("doc: %r"); + + if(parse){ + printpage(doc->doc->wtxt); + exits(0); + } + if((h = tohtml(doc, doc->doc+doc->ndoc-1, t)) == nil) + sysfatal("wiki2html: %r"); + + write(1, s_to_c(h), s_len(h)); + exits(0); +} diff --git a/sys/src/cmd/wikifs/wiki2text.c b/sys/src/cmd/wikifs/wiki2text.c new file mode 100755 index 000000000..33b33fc69 --- /dev/null +++ b/sys/src/cmd/wikifs/wiki2text.c @@ -0,0 +1,50 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <String.h> +#include <thread.h> +#include "wiki.h" + +char *wikidir = "."; + +void +usage(void) +{ + fprint(2, "usage: wiki2text [-d dir] wikifile\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + int i; + Biobuf *b; + String *h; + Whist *doc; + + ARGBEGIN{ + default: + usage(); + case 'd': + wikidir = EARGF(usage()); + break; + }ARGEND + + if(argc != 1) + usage(); + + if((b = Bopen(argv[0], OREAD)) == nil) + sysfatal("Bopen: %r"); + + if((doc = Brdwhist(b)) == nil) + sysfatal("Brdwtxt: %r"); + + h = nil; + for(i=0; i<doc->ndoc; i++){ + print("__________________ %d ______________\n", i); + if((h = pagetext(s_reset(h), doc->doc[i].wtxt, 1)) == nil) + sysfatal("wiki2html: %r"); + write(1, s_to_c(h), s_len(h)); + } + exits(0); +} |