diff options
-rw-r--r-- | archive.c | 9 | ||||
-rw-r--r-- | archive.h | 12 | ||||
-rw-r--r-- | concat.c | 37 | ||||
-rw-r--r-- | export.c | 51 | ||||
-rw-r--r-- | html.c | 218 |
5 files changed, 217 insertions, 110 deletions
diff --git a/archive.c b/archive.c index 992b592..7edc6d6 100644 --- a/archive.c +++ b/archive.c @@ -62,9 +62,6 @@ int main(int argc, char *argv[]) { const char *algo = "REFERENCES"; const char *search = "ALL"; - const char *title = NULL; - const char *headPath = NULL; - for (int opt; 0 < (opt = getopt(argc, argv, "C:a:h:p:s:t:u:vw:"));) { switch (opt) { break; case 'C': { @@ -72,10 +69,10 @@ int main(int argc, char *argv[]) { if (error) err(EX_NOINPUT, "%s", optarg); } break; case 'a': algo = optarg; - break; case 'h': headPath = optarg; + break; case 'h': concatHead = optarg; break; case 'p': port = optarg; break; case 's': search = optarg; - break; case 't': title = optarg; + break; case 't': htmlTitle = optarg; break; case 'u': atomBaseURL = optarg; break; case 'v': imapVerbose = true; break; case 'w': passPath = optarg; @@ -87,7 +84,7 @@ int main(int argc, char *argv[]) { if (!host) errx(EX_USAGE, "host required"); if (!user) errx(EX_USAGE, "user required"); - if (!title) title = mailbox; + if (!htmlTitle) htmlTitle = mailbox; char *pass = NULL; if (passPath) { diff --git a/archive.h b/archive.h index c48d8e3..94db1a6 100644 --- a/archive.h +++ b/archive.h @@ -130,6 +130,7 @@ void parseBodyPart(struct BodyPart *part, struct List list); bool exportFetch(FILE *imap, enum Atom tag, struct List threads); bool exportData(FILE *imap, enum Atom tag, struct List items); +extern const char *concatHead; void concatFetch(FILE *imap, enum Atom tag, struct List threads); void concatData(struct List threads, struct List items); @@ -203,10 +204,11 @@ int atomEntryClose(FILE *file); int atomFeedOpen(FILE *file, const struct Envelope *envelope); int atomFeedClose(FILE *file); -int htmlMessageHead(FILE *file, const struct Envelope *envelope); -int htmlMessageTail(FILE *file); +extern const char *htmlTitle; +int htmlMessageOpen(FILE *file, const struct Envelope *envelope); +int htmlMessageClose(FILE *file); int htmlThreadHead(FILE *file, const struct Envelope *envelope); -int htmlThreadHeader(FILE *file, const struct Envelope *envelope); -int htmlThreadOpen(FILE *file); +int htmlThreadOpen(FILE *file, const struct Envelope *envelope); +int htmlSubthreadOpen(FILE *file); +int htmlSubthreadClose(FILE *file); int htmlThreadClose(FILE *file); -int htmlThreadTail(FILE *file); diff --git a/concat.c b/concat.c index a0f451e..04d2e27 100644 --- a/concat.c +++ b/concat.c @@ -80,22 +80,21 @@ static int concatHTML(FILE *file, struct List thread) { int error; for (size_t i = 0; i < thread.len; ++i) { if (thread.ptr[i].type == List) { - error = concatHTML(file, thread.ptr[i].list); + error = 0 + || htmlSubthreadOpen(file) + || concatHTML(file, thread.ptr[i].list) + || htmlSubthreadClose(file); } else { uint32_t uid = dataCheck(thread.ptr[i], Number).number; - error = htmlThreadOpen(file) - || concatFile(file, pathUID(uid, "html")); + error = concatFile(file, pathUID(uid, "html")); } if (error) return error; } - for (size_t i = 0; i < thread.len; ++i) { - if (thread.ptr[i].type == List) continue; - error = htmlThreadClose(file); - if (error) return error; - } return 0; } +const char *concatHead; + void concatData(struct List threads, struct List items) { uint32_t uid = 0; struct Envelope envelope = {0}; @@ -157,5 +156,27 @@ void concatData(struct List threads, struct List items) { if (error) err(EX_IOERR, "%s", path); } + path = pathThread(envelope.messageID, "html"); + error = stat(path, &status); + if (error || status.st_mtime < uidNewest(flat, "html")) { + FILE *file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + + error = htmlThreadHead(file, &envelope); + if (error) err(EX_IOERR, "%s", path); + + if (concatHead) { + error = concatFile(file, concatHead); + if (error) err(EX_IOERR, "%s", path); + } + + error = 0 + || htmlThreadOpen(file, &envelope) + || concatHTML(file, thread) + || htmlThreadClose(file) + || fclose(file); + if (error) err(EX_IOERR, "%s", path); + } + listFree(flat); } diff --git a/export.c b/export.c index fe166f8..3c6a31f 100644 --- a/export.c +++ b/export.c @@ -111,6 +111,55 @@ static void exportAtom( if (error) err(EX_IOERR, "%s", path); } +static int exportHTMLBody( + FILE *file, struct List *section, + const struct BodyPart *structure, struct Data body +) { + int error; + if (structure->multipart) { + // TODO: Choose a part from multipart/alternative. + for (size_t i = 0; i < structure->parts.len; ++i) { + struct Data part = { .type = Number, .number = 1 + i }; + listPush(section, part); + error = exportHTMLBody( + file, section, + &structure->parts.ptr[i], dataCheck(body, List).list.ptr[i] + ); + if (error) return error; + section->len--; + } + } else if (structure->message.envelope) { + error = 0 + || htmlMessageOpen(file, structure->message.envelope) + || exportHTMLBody(file, section, structure->message.structure, body) + || htmlMessageClose(file); + } else { + // TODO: Content. + error = 0; + } + return error; +} + +static void exportHTML( + uint32_t uid, const struct Envelope *envelope, + const struct BodyPart *structure, struct Data body +) { + const char *path = pathUID(uid, "html"); + FILE *file = fopen(path, "w"); + if (!file) err(EX_CANTCREAT, "%s", path); + + int error = htmlMessageOpen(file, envelope); + if (error) err(EX_IOERR, "%s", path); + + struct List section = {0}; + error = exportHTMLBody(file, §ion, structure, body); + if (error) err(EX_IOERR, "%s", path); + listFree(section); + + error = htmlMessageClose(file) || fclose(file); + if (error) err(EX_IOERR, "%s", path); +} + static void fetchParts( FILE *imap, struct List *section, const struct BodyPart *structure ) { @@ -232,8 +281,10 @@ bool exportData(FILE *imap, enum Atom tag, struct List items) { bool fetch = false; if (!structure.multipart) { exportAtom(uid, &envelope, &structure, bodyText); + exportHTML(uid, &envelope, &structure, bodyText); } else if (bodyParts.type == List) { exportAtom(uid, &envelope, &structure, bodyParts); + exportHTML(uid, &envelope, &structure, bodyParts); } else { fetch = true; fprintf( diff --git a/html.c b/html.c index d6bf723..090bfab 100644 --- a/html.c +++ b/html.c @@ -24,161 +24,197 @@ #include "archive.h" static int htmlAddress(FILE *file, const char *class, struct Address addr) { - struct Variable vars[] = { - { "class", class }, - { "name", addressName(addr) }, - { "mailbox", addr.mailbox }, - {0}, - }; + const char *template; if (addr.host) { - return templateRender( - file, - TEMPLATE(<li><address class="[class]">[name]</address></li>), - vars, - escapeXML + template = TEMPLATE( + <li><address class="[class]">[name]</address></li> ); } else if (addr.mailbox) { - return templateRender( - file, - TEMPLATE(<li><address class="[class] group">[mailbox]<ul>), - vars, - escapeXML + template = TEMPLATE( + <li> + <address class="[class] group">[mailbox]</address> + <ul class="group"> ); } else { - return templateRender( - file, - TEMPLATE(</ul></address></li>), - vars, - escapeXML + template = TEMPLATE( + </ul> + </li> ); } + struct Variable vars[] = { + { "class", class }, + { "name", addressName(addr) }, + { "mailbox", addr.mailbox }, + {0}, + }; + return templateRender(file, template, vars, escapeXML); } static int htmlAddressList(FILE *file, const char *class, struct AddressList list) { if (!list.len) return 0; + const char *template = TEMPLATE( + <ul class="[class]"> + ); struct Variable vars[] = { { "class", class }, {0}, }; - int error = templateRender( - file, TEMPLATE(<ul class="[class]">), vars, escapeXML - ); + int error = templateRender(file, template, vars, escapeXML); if (error) return error; for (size_t i = 0; i < list.len; ++i) { error = htmlAddress(file, class, list.addrs[i]); if (error) return error; } - return templateRender(file, TEMPLATE(</ul>), vars, escapeXML); + return templateRender(file, TEMPLATE(</ul>), NULL, NULL); } -int htmlMessageHead(FILE *file, const struct Envelope *envelope) { - struct Variable urlVars[] = { - { "mailbox", envelope->replyTo.mailbox }, - { "host", envelope->replyTo.host }, +static char *htmlMailto(const struct Envelope *envelope) { + const char *template = { + "mailto:[mailbox]@[host]?subject=[re][subject]&In-Reply-To=[messageID]" + }; + struct Variable vars[] = { + { "mailbox", envelope->from.mailbox }, + { "host", envelope->from.host }, { "re", (strncmp(envelope->subject, "Re: ", 4) ? "Re: " : "") }, { "subject", envelope->subject }, { "messageID", envelope->messageID }, - { "pathID", pathSafe(envelope->messageID) }, {0}, }; - char *fragment = templateURL("#[messageID]", urlVars); - char *mailto = templateURL( - "mailto:[mailbox]@[host]?subject=[re][subject]&In-Reply-To=[messageID]", - urlVars - ); - char *mbox = templateURL("../message/[pathID].mbox", urlVars); + return templateURL(template, vars); +} + +static char *htmlFragment(const struct Envelope *envelope) { + struct Variable vars[] = { + { "messageID", envelope->messageID }, + {0}, + }; + return templateURL("#[messageID]", vars); +} +static char *htmlMbox(const struct Envelope *envelope) { + struct Variable vars[] = { + { "name", pathSafe(envelope->messageID) }, + {0}, + }; + return templateURL("../message/[name].mbox", vars); +} + +int htmlMessageOpen(FILE *file, const struct Envelope *envelope) { + // TODO: Conditionally include mailto: link. + const char *template1 = TEMPLATE( + <article class="message" id="[messageID]"> + <header> + <h2>[subject]</h2> + <address class="from"> + <a href="[mailto]">[from]</a> + </address> + <time datetime="[utc]">[date]</time> + ); + const char *template2 = TEMPLATE( + <nav> + <ul> + <li><a href="[fragment]">permalink</a></li> + <li><a href="[mbox]">mbox</a></li> + </ul> + </nav> + </header> + ); + char *mailto = htmlMailto(envelope); char utc[sizeof("0000-00-00T00:00:00Z")]; strftime(utc, sizeof(utc), "%FT%TZ", gmtime(&envelope->time)); + char *fragment = htmlFragment(envelope); + char *mbox = htmlMbox(envelope); struct Variable vars[] = { { "messageID", envelope->messageID }, - { "fragment", fragment }, { "subject", envelope->subject }, { "mailto", mailto }, { "from", addressName(envelope->from) }, - { "date", envelope->date }, { "utc", utc }, + { "date", envelope->date }, + { "fragment", fragment }, { "mbox", mbox }, {0}, }; - const char *Summary = TEMPLATE( - <details class="message" id="[messageID]"> - <summary> - <a class="subject" href="[fragment]">[subject]</a> - <address class="from"><a href="[mailto]">[from]</a></address> - <time datetime="[utc]">[date]</time> - <a class="mbox" href="[mbox]">mbox</a> - </summary> - ); - int error = templateRender(file, Summary, vars, escapeXML); - free(fragment); + int error = 0 + || templateRender(file, template1, vars, escapeXML) + || htmlAddressList(file, "to", envelope->to) + || htmlAddressList(file, "cc", envelope->cc) + || templateRender(file, template2, vars, escapeXML); free(mailto); + free(fragment); free(mbox); - if (error) return error; - - return 0 - || htmlAddressList(file, "to", envelope->to) - || htmlAddressList(file, "cc", envelope->cc); + return error; } -int htmlMessageTail(FILE *file) { - int n = fprintf(file, "</details>\n"); - return (n < 0 ? n : 0); +int htmlMessageClose(FILE *file) { + return templateRender(file, TEMPLATE(</article>), NULL, NULL); } -int htmlThreadHead(FILE *file, const struct Envelope *envelope) { - struct Variable urlVars[] = { - { "pathID", pathSafe(envelope->messageID) }, - {0}, - }; - char *path = templateURL("[pathID]", urlVars); +const char *htmlTitle; +static char *htmlThreadURL(const struct Envelope *envelope) { struct Variable vars[] = { - { "subject", envelope->subject }, - { "path", path }, + { "name", pathSafe(envelope->messageID) }, {0}, }; - const char *Head = TEMPLATE( + return templateURL("[name]", vars); +} + +int htmlThreadHead(FILE *file, const struct Envelope *envelope) { + const char *template = TEMPLATE( <!DOCTYPE html> <meta charset="utf-8"> - <title>[subject]</title> - <link rel="alternate" type="application/atom+xml" href="[path].atom"> - <link rel="alternate" type="application/mbox" href="[path].mbox"> - <style> - address { display: inline; } - section.thread section.thread:not(:first-child) { - border-left: 1px solid gray; - padding-left: 2ch; - } - </style> + <title>[subject] · [title]</title> + <link rel="alternate" type="application/atom+xml" href="[url].atom"> + <link rel="alternate" type="application/mbox" href="[url].mbox"> ); - int error = templateRender(file, Head, vars, escapeXML); - free(path); + char *url = htmlThreadURL(envelope); + struct Variable vars[] = { + { "subject", envelope->subject }, + { "title", htmlTitle }, + { "url", url }, + {0}, + }; + int error = templateRender(file, template, vars, escapeXML); + free(url); return error; } -int htmlThreadHeader(FILE *file, const struct Envelope *envelope) { +int htmlThreadOpen(FILE *file, const struct Envelope *envelope) { + const char *template = TEMPLATE( + <header class="thread"> + <h1>[subject]</h1> + <nav> + <ul> + <li><a href="[url].atom">Atom</a></li> + <li><a href="[url].mbox">mbox</a></li> + </ul> + </nav> + </header> + <main class="thread"> + ); + char *url = htmlThreadURL(envelope); struct Variable vars[] = { { "subject", envelope->subject }, + { "url", url }, {0}, }; - const char *Header = TEMPLATE( - <h1>[subject]</h1> - ); - return templateRender(file, Header, vars, escapeXML); + int error = templateRender(file, template, vars, escapeXML); + free(url); + return error; } -int htmlThreadOpen(FILE *file) { - int n = fprintf(file, TEMPLATE(<section class="thread">)); - return (n < 0 ? n : 0); +int htmlSubthreadOpen(FILE *file) { + return templateRender( + file, TEMPLATE(<section class="subthread">), NULL, NULL + ); } -int htmlThreadClose(FILE *file) { - int n = fprintf(file, TEMPLATE(</section>)); - return (n < 0 ? n : 0); +int htmlSubthreadClose(FILE *file) { + return templateRender(file, TEMPLATE(</section>), NULL, NULL); } -int htmlThreadTail(FILE *file) { - return 0; +int htmlThreadClose(FILE *file) { + return templateRender(file, TEMPLATE(</main>), NULL, NULL); } |