tohtml.c 15 KB

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