diff options
author | Erik K <erikk@previousplan.org> | 2022-05-13 17:24:26 +0000 |
---|---|---|
committer | Erik K <erikk@previousplan.org> | 2022-05-13 17:24:26 +0000 |
commit | b2fada1dd19211d71e04557653d08e697134a6ce (patch) | |
tree | 063b6d1280193046321db1e53506be5b72d2220f |
initial commit
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | Makefile | 24 | ||||
-rw-r--r-- | config.h | 94 | ||||
-rw-r--r-- | config.mk | 9 | ||||
-rw-r--r-- | genplugin.awk | 20 | ||||
-rw-r--r-- | posts.c | 93 | ||||
-rw-r--r-- | rss.c | 84 | ||||
-rw-r--r-- | skull.c | 82 | ||||
-rw-r--r-- | sm.c | 442 | ||||
-rw-r--r-- | sm.conf | 12 | ||||
-rw-r--r-- | sm.h | 22 | ||||
-rw-r--r-- | util.c | 83 | ||||
-rw-r--r-- | util.h | 9 |
13 files changed, 980 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74afe1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +content +export +sm.tsv +*.o +sm +plugins.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2dd9aca --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +include config.mk +SRC = sm.c util.c ${PLUGINS} +OBJ = ${SRC:.c=.o} + +sm: ${OBJ} + ${CC} ${LDFLAGS} -o sm ${OBJ} + +.c.o: + ${CC} ${CFLAGS} -c $< + +${OBJ}: config.h plugins.h + +plugins.h: ${PLUGINS} + printf '%s\n' ${PLUGINS:.c=} | awk -f genplugin.awk + +install: + install -dm 0755 ${DESTDIR}${PREFIX}/bin + install -m 0755 sm ${DESTDIR}${PREFIX}/bin + +uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/sm + +clean: + rm -f sm plugins.h ${OBJ} diff --git a/config.h b/config.h new file mode 100644 index 0000000..96a42b7 --- /dev/null +++ b/config.h @@ -0,0 +1,94 @@ +#ifndef CONFIG_H +#define CONFIG_H +#include <strings.h> + +#define CLEN 512 + +#define DUMPSTR(N, S) fprintf(stderr, "%-15s: %s\n", N, S); +#define DUMPCFG() \ + DUMPSTR("DbLoc", kdbloc); \ + DUMPSTR("ContentDir", kcontentdir); \ + DUMPSTR("LinkPreset", klinkpreset); \ + DUMPSTR("Autoedit", kautoedit); \ + DUMPSTR("Autoexport", kautoexport); \ + DUMPSTR("SkullExport", kskullexport); \ + DUMPSTR("RssTitle", krsstitle); \ + DUMPSTR("RssDescription", krssdescription); \ + DUMPSTR("RssLink", krsslink); \ + DUMPSTR("RssExport", krssexport); \ + DUMPSTR("PostsExport", kpostsexport); \ + +char *storepointer(const char *, int *); + +extern char kdbloc[]; +extern char kcontentdir[]; +extern char klinkpreset[]; +extern char kautoedit[]; +extern char kautoexport[]; + +extern char kskullexport[]; + +extern char krsstitle[]; +extern char krssdescription[]; +extern char krsslink[]; +extern char krssexport[]; + +extern char kpostsexport[]; + +#ifdef MAIN +char kdbloc[CLEN] = "sm.tsv"; +char kcontentdir[CLEN] = "content"; +char klinkpreset[CLEN] = "https://previousplan.org/%"; +char kautoedit[CLEN] = "True"; +char kautoexport[CLEN] = "False"; + +char kskullexport[CLEN] = "export"; + +char krsstitle[CLEN] = "Previous Plan!"; +char krssdescription[CLEN] = "Previous Plan! Blog"; +char krsslink[CLEN] = "https://previousplan.org/rss.xml"; +char krssexport[CLEN] = "export/rss.xml"; + +char kpostsexport[CLEN] = "export/posts.html"; + +char * +storepointer(const char *key, int *ispath) +{ + *ispath = 0; + if (!strcasecmp(key, "DbLoc")) { + *ispath = 1; + return kdbloc; + } + if (!strcasecmp(key, "ContentDir")) { + *ispath = 1; + return kcontentdir; + } + if (!strcasecmp(key, "LinkPreset")) + return klinkpreset; + if (!strcasecmp(key, "Autoedit")) + return kautoedit; + if (!strcasecmp(key, "Autoexport")) + return kautoexport; + if (!strcasecmp(key, "SkullExport")) { + *ispath = 1; + return kskullexport; + } + if (!strcasecmp(key, "RssTitle")) + return krsstitle; + if (!strcasecmp(key, "RssDescription")) + return krssdescription; + if (!strcasecmp(key, "RssLink")) + return krsslink; + if (!strcasecmp(key, "RssExport")) { + *ispath = 1; + return krssexport; + } + if (!strcasecmp(key, "PostsExport")) { + *ispath = 1; + return kpostsexport; + } + return NULL; +} +#endif + +#endif /* ! CONFIG_H */ diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..fe7e1b3 --- /dev/null +++ b/config.mk @@ -0,0 +1,9 @@ +PLUGINS = skull.c rss.c posts.c + +VERSION = 1.0 +PREFIX = /usr/local + +CPPFLAGS = -D_DEFAULT_SOURCE +CFLAGS = ${CPPFLAGS} -O0 -g -DVERSION=\"${VERSION}\" +# CFLAGS = ${CPPFLAGS} -O3 -pipe -march=native -DVERSION=\"${VERSION}\" +LDFLAGS = -static diff --git a/genplugin.awk b/genplugin.awk new file mode 100644 index 0000000..e6f99ac --- /dev/null +++ b/genplugin.awk @@ -0,0 +1,20 @@ +#!/usr/bin/awk -f + +BEGIN { + f = "plugins.h" + printf "#ifndef PLUGIN_H\n#define PLUGIN_H\n#include \"sm.h\"\n" >f +} + +# We read the list of plugins from stdin +{ + plugins[i++] = $0 + printf "extern Plugin %s;\n", $0 >f +} + +END { + printf "#define NPLUGINS %d\n#ifdef MAIN\nPlugin *plugins[%d] = {\n", NR, NR >f + for (i in plugins) { + printf "\t&%s,\n", plugins[i] >f + } + printf "};\n#endif /* MAIN */\n\n#endif /* ! PLUGIN_H */" >f +} @@ -0,0 +1,93 @@ +#include <stdlib.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "sm.h" +#include "util.h" + +extern char kcontentdir[]; +extern char kpostsexport[]; + +static void postsexport(Document **, int); + +Plugin posts = {"posts", postsexport, NULL, 0}; + +static int +creatcompar(const Document **d1, const Document **d2) +{ + return (*d2)->creat - (*d1)->creat; +} + +static void +postsexport(Document **doc, int ndoc) +{ + struct tm *tm, lasttm; + char dateheader[64]; + FILE *outf; + int i, j; + + static char header[] = "\ +<!DOCTYPE html>\n\ +<html>\n\ + <title>Posts</title>\n\ + <meta charset=\"utf-8\"/>\n\ + <style>\n\ + h1, nav { text-align: center }\n\ + p { margin-left: 6%; margin-right: 6% }\n\ + </style>\n\ +</head>\n\ +<body bgcolor=\"#000000\" background=\"/pix/crackblk.jpg\" text=\"#eeeeee\" \ +link=\"orange\" alink=\"red\" vlink=\"red\">\n\ + <a href=\"https://previousplan.org\"><img src=\"/pix/previousplan-button.gif\" alt=\"PreviousPlan! web button\" title=\"PreviousPlan! web button\"></a>\n\ + <nav>\n\ + <img src=\"/pix/s-spin.gif\"/>\n\ + <i><font size=\"6\" color=\"gold\">Previous Plan!</font></i>\n\ + -\n\ + <a href=\"/about.html\">Home</a>\n\ + -\n\ + <a href=\"/posts.html\">Posts</a>\n\ + -\n\ + <a href=\"/rss.xml\">RSS</a>\n\ + -\n\ + <a href=\"/donate.html\">Donate</a>\n\ + <img src=\"/pix/s-spin.gif\"/>\n\ + </nav>\n\ + <h2>List of posts</h2>\n\ +"; + + static char footer[] = "\ + <hr/>\n\ + <img src=\"pix/s-mail.gif\" alt=\"EMAIL\"/>\n\ + <p>Webmaster email: <a \ +href=\"mailto:erikk@previousplan.org\">erikk@previousplan.org</a> \ +<a href=\"/erikk.asc\">(PGP key)</a></p>\n\ +</body>\n\ +</html>\n\ +"; + + char title[128], link[CLEN]; + char *item[] = { " <li><a href=\"", link, "\">", title, "</a></li>\n" }; + + qsort(doc, ndoc, sizeof(*doc), (int (*)(const void *, const void *))creatcompar); + + outf = xfopen(kpostsexport, "w"); + xfputs(header, outf); + for (i = 0; i < ndoc; i++) { + my_strlcpy(title, doc[i]->title, sizeof(title)); + my_strlcpy(link, doc[i]->link, sizeof(link)); + + tm = gmtime(&doc[i]->creat); + if (i == 0 || tm->tm_mon != lasttm.tm_mon || tm->tm_year != lasttm.tm_year) { + strftime(dateheader, sizeof(dateheader), i != 0 ? "</ul>\n<h3>%B %Y</h3>\n<ul>\n" : "<h3>%B %Y</h3>\n<ul>\n", tm); + xfputs(dateheader, outf); + } + memcpy(&lasttm, tm, sizeof(lasttm)); + for (j = 0; j < LEN(item); j++) + xfputs(item[j], outf); + } + if (ndoc) + xfputs("</ul>\n", outf); + xfputs(footer, outf); + fclose(outf); +} @@ -0,0 +1,84 @@ +#include <stdlib.h> + +#include "config.h" +#include "sm.h" +#include "util.h" + +extern char kcontentdir[]; +extern char krssexport[]; +extern char krsstitle[]; +extern char krssdescription[]; +extern char krsslink[]; + +static void rssexport(Document **, int); + +Plugin rss = {"rss", rssexport, NULL, 0}; + +static int +creatcompar(const Document **d1, const Document **d2) +{ + return (*d2)->creat - (*d1)->creat; +} + +static void +rssexport(Document **doc, int ndoc) +{ + char inpath[512]; + FILE *outf; + int i, j; + + char *header[] = { "\ +<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\ +<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n\ +\n\ +<channel>\n\ +<title>", krsstitle, "</title>\n\ +<description>", krssdescription, "</description>\n\ +<language>en-us</language>\n\ +<link>", krsslink, "</link>\n\ +<generator>sm-"VERSION"</generator>\n\ +<atom:link href=\"", krsslink, "\" rel=\"self\" type=\"application/rss+xml\" />\n\ +" }; + + char *footer[] = { "\ +</channel>\n\ +</rss>\n\ +" }; + + char filename[64], title[128], creat[64], link[CLEN]; + char *itemheader[] = { "\ +<item>\n\ + <title>", title, "</title>\n\ + <guid isPermaLink=\"false\">", filename, "</guid>\n\ + <pubDate>", creat, "</pubDate>\n\ + <link>", link, "</link>\n\ + <description><![CDATA[" }; + + char *itemfooter[] = { "\ +]]></description>\n\ +</item>\n\ +" }; + + qsort(doc, ndoc, sizeof(*doc), (int (*)(const void *, const void *))creatcompar); + + outf = xfopen(krssexport, "w"); + for (j = 0; j < LEN(header); j++) + xfputs(header[j], outf); + for (i = 0; i < ndoc; i++) { + snprintf(inpath, sizeof(inpath), "%s/%s", kcontentdir, + doc[i]->filename); + my_strlcpy(filename, doc[i]->filename, sizeof(filename)); + my_strlcpy(title, doc[i]->title, sizeof(title)); + strftime(creat, sizeof(creat), "%a, %d %b %Y %H:%M:%S %z", + gmtime(&doc[i]->creat)); + my_strlcpy(link, doc[i]->link, sizeof(link)); + for (j = 0; j < LEN(itemheader); j++) + xfputs(itemheader[j], outf); + cat(inpath, outf); + for (j = 0; j < LEN(itemfooter); j++) + xfputs(itemfooter[j], outf); + } + for (j = 0; j < LEN(footer); j++) + xfputs(footer[j], outf); + fclose(outf); +} @@ -0,0 +1,82 @@ +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <time.h> + +#include "sm.h" +#include "util.h" + +extern char kcontentdir[]; +extern char kskullexport[]; + +static void skullexport(Document **, int); + +Plugin skull = {"skull", skullexport, NULL, 0}; + +static void +skullexport(Document **doc, int ndoc) +{ + char path[512], outpath[512]; + FILE *outf; + int i, j; + + char title[128], creat[64], mod[64]; + char *header[] = { "\ +<!DOCTYPE html>\n\ +<html>\n\ + <title>", title, "</title>\n\ + <meta charset=\"utf-8\"/>\n\ + <style>\n\ + h1, nav { text-align: center }\n\ + p { margin-left: 6%; margin-right: 6% }\n\ + </style>\n\ +</head>\n\ +<body bgcolor=\"#000000\" background=\"/pix/crackblk.jpg\" text=\"#eeeeee\" \ +link=\"orange\" alink=\"red\" vlink=\"red\">\n\ + <a href=\"https://previousplan.org\"><img src=\"/pix/previousplan-button.gif\" alt=\"PreviousPlan! web button\" title=\"PreviousPlan! web button\"></a>\n\ + <nav>\n\ + <img src=\"/pix/s-spin.gif\"/>\n\ + <i><font size=\"6\" color=\"gold\">Previous Plan!</font></i>\n\ + -\n\ + <a href=\"/about.html\">Home</a>\n\ + -\n\ + <a href=\"/posts.html\">Posts</a>\n\ + -\n\ + <a href=\"/rss.xml\">RSS</a>\n\ + -\n\ + <a href=\"/donate.html\">Donate</a>\n\ + <img src=\"/pix/s-spin.gif\"/>\n\ + </nav>\n\ +" }; + + char *footer[] = { "\ + <hr/>\n\ + <p>Written: ", creat, ", Last modified: ", mod, "</p>\n\ + <img src=\"pix/s-mail.gif\" alt=\"EMAIL\"/>\n\ + <p>Webmaster email: <a \ +href=\"mailto:erikk@previousplan.org\">erikk@previousplan.org</a> \ +<a href=\"/erikk.asc\">(PGP key)</a></p>\n\ +</body>\n\ +</html>\n\ +" }; + + for (i = 0; i < ndoc; i++) { + snprintf(path, sizeof(path), "%s/%s", kcontentdir, doc[i]->filename); + snprintf(outpath, sizeof(outpath), "%s/%s", kskullexport, doc[i]->filename); + + if ((outf = fopen(outpath, "w")) == NULL) { + fprintf(stderr, "%s: %s\n", outpath, strerror(errno)); + continue; + } + + my_strlcpy(title, doc[i]->title, sizeof(title)); + strftime(creat, sizeof(creat), "%a, %Y %b %d", gmtime(&doc[i]->creat)); + strftime(mod, sizeof(mod), "%a, %Y %b %d", gmtime(&doc[i]->mod)); + for (j = 0; j < LEN(header); j++) + xfputs(header[j], outf); + cat(path, outf); + for (j = 0; j < LEN(footer); j++) + xfputs(footer[j], outf); + fclose(outf); + } +} @@ -0,0 +1,442 @@ +#include <sys/wait.h> + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> + +#include "util.h" + +#define MAIN +#include "config.h" +#include "plugins.h" + +#define MIN(A, B) (A < B ? A : B) + +typedef struct { + char argv[10][CLEN]; + int argc; +} Cmd; + +int cat(const char *, FILE *); + +static int dbdel(const char *); +static Document *dbfind(const char *); +static void dbread(const char *); +static void dbwrite(const char *); +static void doautoexport(const char *); +static void cmdsplit(Cmd *, char *); +static void cfgread(const char *); +static void expandlink(Document *d); +static void expandpath(char *, size_t); +static int pladddoc(const char *, Document *); + +static int add(const char *); +static int edit(const char *); +static void list(const char *); + +static Document **docdb; +static int ndocdb; +static int autoedit; +static int autoexport; + +int +cat(const char *in, FILE *outf) +{ + char buf[BUFSIZ]; + size_t ret; + FILE *inf; + + if ((inf = fopen(in, "r")) == NULL) { + fprintf(stderr, "%s: %s\n", in, strerror(errno)); + return 0; + } + while ((ret = fread(buf, 1, sizeof(buf), inf))) { + if (!fwrite(buf, ret, 1, outf)) { + fprintf(stderr, "fwrite: %s\n", strerror(errno)); + exit(1); + } + } + if (ferror(inf)) { + fprintf(stderr, "fread: %s\n", strerror(errno)); + exit(1); + } + return 1; +} + + +static int +dbdel(const char *filename) +{ + char path[512]; + Document *d; + int i, j; + int di; + + if ((d = dbfind(filename)) == NULL) { + fprintf(stderr, "%s: no such file\n", filename); + return 0; + } + + for (i = 0; i < NPLUGINS; i++) { + for (j = 0; j < plugins[i]->nusedby; j++) { + if (plugins[i]->usedby[j] != d) + continue; + if (j + 1 < plugins[i]->nusedby) { + memmove(&plugins[i]->usedby[j], &plugins[i]->usedby[j + + 1], (plugins[i]->nusedby - j - 1) * + sizeof(*plugins[i]->usedby)); + } + plugins[i]->usedby = xrealloc(plugins[i]->usedby, + --plugins[i]->nusedby * + sizeof(*plugins[i]->usedby)); + } + } + + for (di = 0; di < ndocdb; di++) { + if (docdb[di] == d) + break; + } + if (di == ndocdb) + return 0; + if (di + 1 < ndocdb) { + memmove(&docdb[di], &docdb[di + 1], (ndocdb - di - 1) * + sizeof(*docdb)); + } + docdb = xrealloc(docdb, --ndocdb * sizeof(*docdb)); + + snprintf(path, sizeof(path), "%s/%s", kcontentdir, filename); + unlink(path); + free(d); + return 1; +} + +static Document * +dbfind(const char *filename) +{ + int i; + + for (i = 0; i < ndocdb; i++) { + if (!strcmp(docdb[i]->filename, filename)) { + return docdb[i]; + } + } + + return NULL; +} + +static void +dbread(const char *path) +{ + FILE *db; + char buf[1024], *field, *p; + Document *d; + int i; + + if ((db = fopen(path, "r")) == NULL) + return; + while (xfgets(buf, sizeof(buf), db)) { + if (buf[0] == '\0') + continue; + d = xmalloc(sizeof(*d)); + for (i = 0, p = buf; (field = strsep(&p, "\t")); i++) { + switch (i) { + case 0: my_strlcpy(d->filename, field, sizeof(d->filename)); break; + case 1: my_strlcpy(d->title, field, sizeof(d->title)); break; + case 2: d->creat = strtoll(field, NULL, 10); break; + case 3: d->mod = strtoll(field, NULL, 10); break; + default: pladddoc(field, d); + } + } + if (i > 3) { + expandlink(d); + docdb = xrealloc(docdb, ++ndocdb * sizeof(*docdb)); + docdb[ndocdb-1] = d; + } else { + free(d); + } + } + fclose(db); +} + +static void +dbwrite(const char *path) +{ + FILE *db; + int i, j, k; + + db = xfopen(path, "w"); + for (i = 0; i < ndocdb; i++) { + fprintf(db, "%s\t%s\t%ld\t%ld", docdb[i]->filename, + docdb[i]->title, docdb[i]->creat, + docdb[i]->mod); + for (j = 0; j < NPLUGINS; j++) { + for (k = 0; k < plugins[j]->nusedby; k++) { + if (plugins[j]->usedby[k] == docdb[i]) { + fprintf(db, "\t%s", plugins[j]->name); + break; + } + } + } + fputs("\n", db); + } + fclose(db); +} + +static void +doautoexport(const char *filename) +{ + Document *d; + int i, j; + + if ((d = dbfind(filename)) == NULL) + return; + for (i = 0; i < NPLUGINS; i++) { + for (j = 0; j < plugins[i]->nusedby; j++) { + if (plugins[i]->usedby[j] == d) { + plugins[i]->export(plugins[i]->usedby, + plugins[i]->nusedby); + } + } + } +} + +static void +cmdsplit(Cmd *cmd, char *line) +{ + char *p, *arg; + + p = line; + cmd->argc = 0; + while (cmd->argc < LEN(cmd->argv) && (arg = strsep(&p, " "))) { + if (!arg[0]) + continue; + my_strlcpy(cmd->argv[cmd->argc++], arg, sizeof(cmd->argv[cmd->argc])); + } +} + +static void +cfgread(const char *path) +{ + char buf[CLEN], *end, *value, *store; + int ispath; + FILE *cfg; + + cfg = xfopen(path, "r"); + while (xfgets(buf, sizeof(buf), cfg)) { + if (buf[0] == '#' || !buf[0]) + continue; + if ((value = strchr(buf, ' ')) == NULL || value == buf) + continue; + *value++ = '\0'; + for (end = value + strlen(value) - 1; end >= value + && isspace(*end); *end-- = '\0') { + continue; + } + if ((store = storepointer(buf, &ispath))) { + my_strlcpy(store, value, CLEN); + if (ispath) + expandpath(store, CLEN); + } + } + fclose(cfg); +} + +static void +expandlink(Document *d) +{ + int i, j; + + for (i = j = 0; j + 1 < sizeof(d->link) && klinkpreset[i]; i++) { + if (klinkpreset[i] == '%') { + my_strlcpy(&d->link[j], d->filename, sizeof(d->link) - j); + j += MIN(sizeof(d->link) - j - 1, strlen(d->filename)); + } else { + d->link[j++] = klinkpreset[i]; + } + } + d->link[j] = '\0'; +} + +static void +expandpath(char *str, size_t max) +{ + const char *home = getenv("HOME"); + char newpath[CLEN]; + + if (home == NULL) + return; + if (str[0] == '~') { + snprintf(newpath, sizeof(newpath), "%s%s", home, str + 1); + my_strlcpy(str, newpath, max); + } +} + +/* pladddoc -- e.g -- pluginadddoc */ +static int +pladddoc(const char *plname, Document *d) +{ + int i, j; + + for (i = 0; i < NPLUGINS; i++) { + if (strcmp(plugins[i]->name, plname) != 0) + continue; + for (j = 0; j < plugins[i]->nusedby; j++) { + if (plugins[i]->usedby[j] == d) + goto next; + } + plugins[i]->usedby = xrealloc(plugins[i]->usedby, + ++plugins[i]->nusedby * + sizeof(*plugins[i]->usedby)); + plugins[i]->usedby[plugins[i]->nusedby-1] = d; +next: + } +} + + +static int +add(const char *filename) +{ + Document *d; + char title[128]; + + if ((d = dbfind(filename))) { + fprintf(stderr, "%s: file exists\n", filename); + return 0; + } + + xfputs("Title: ", stdout); + fflush(stdout); + if (!xfgets(title, sizeof(title), stdin)) + return 0; + + d = xmalloc(sizeof(*d)); + my_strlcpy(d->filename, filename, sizeof(d->filename)); + my_strlcpy(d->title, title, sizeof(d->title)); + d->creat = d->mod = time(NULL); + + expandlink(d); + docdb = xrealloc(docdb, ++ndocdb * sizeof(*docdb)); + docdb[ndocdb-1] = d; + + return 1; +} + +static int +edit(const char *filename) +{ + Document *d; + char path[512]; + pid_t pid; + + if ((d = dbfind(filename)) == NULL) { + fprintf(stderr, "%s: no such file\n", filename); + return 0; + } + snprintf(path, sizeof(path), "%s/%s", kcontentdir, filename); + switch ((pid = fork())) { + case -1: + fprintf(stderr, "fork: %s\n", strerror(errno)); + exit(1); + case 0: + execl("/bin/sh", "sh", "-c", "$EDITOR \"$1\"", "sm-edit", path, NULL); + exit(1); + } + waitpid(pid, NULL, 0); + d->mod = time(NULL); + return 1; +} + +static void +list(const char *filename) +{ + char path[512]; + FILE *f; + int i; + + if (filename) { + snprintf(path, sizeof(path), "%s/%s", kcontentdir, filename); + cat(path, stdout); + } else for (i = 0; i < ndocdb; i++) { + printf("%-15s: %s\n", docdb[i]->filename, docdb[i]->title); + } +} + + + +int +main(int argc, char *argv[]) +{ + char *cfgpath = "sm.conf"; + char linebuf[CLEN]; + Document *d; + Cmd cmd; + int i; + + if (argc >= 2) + cfgpath = argv[1]; + + cfgread(cfgpath); + DUMPCFG(); + + if (!strcasecmp(kautoedit, "yes") || !strcasecmp(kautoedit, "true")) + autoedit = 1; + if (!strcasecmp(kautoexport, "yes") || !strcasecmp(kautoexport, "true")) + autoexport = 1; + + dbread(kdbloc); + for (;;) { + xfputs("[sm-" VERSION "] ", stdout); + fflush(stdout); + if (!xfgets(linebuf, sizeof(linebuf), stdin)) + break; + cmdsplit(&cmd, linebuf); + if (cmd.argc == 0) + continue; + for (i = 0; i < NPLUGINS; i++) { + if (!strcmp(plugins[i]->name, cmd.argv[0])) { + plugins[i]->export(plugins[i]->usedby, plugins[i]->nusedby); + break; + } + } + if (i != NPLUGINS) + continue; + if (!strcmp(cmd.argv[0], "quit") || !strcmp(cmd.argv[0], "exit")) + break; + if (!strcmp(cmd.argv[0], "list")) { + list(cmd.argc >= 2 ? cmd.argv[1] : NULL); + } else if (!strcmp(cmd.argv[0], "save")) { + dbwrite(kdbloc); + } else if (cmd.argc < 2) { + fprintf(stderr, "invalid command/needs more parameters\n"); + } else if (!strcmp(cmd.argv[0], "add")) { + if (add(cmd.argv[1])) + dbwrite(kdbloc); + if (autoedit) + goto editcmd; + } else if (!strcmp(cmd.argv[0], "edit")) { +editcmd: + if (edit(cmd.argv[1])) { + doautoexport(cmd.argv[1]); + dbwrite(kdbloc); + } + } else if (!strcmp(cmd.argv[0], "rm")) { + if (dbdel(cmd.argv[1])) + dbwrite(kdbloc); + } else if (cmd.argc < 3) { + fprintf(stderr, "invalid command/needs more parameters\n"); + } else if (!strcmp(cmd.argv[0], "set")) { + if ((d = dbfind(cmd.argv[1])) == NULL) { + fprintf(stderr, "%s: no such file\n", cmd.argv[1]); + continue; + } + if (pladddoc(cmd.argv[2], d)) + dbwrite(kdbloc); + } + } + fputc('\n', stdout); + return 0; +} @@ -0,0 +1,12 @@ +DbLoc ~/sites/sm/previousplan.org.tsv +ContentDir ~/sites/sm/previousplan.org +LinkPreset https://previousplan.org/% +Autoedit True +Autoexport True + +SkullExport ~/sites/previousplan.org +RssTitle Previous Plan! +RssDescription Previous Plan! Blog +RssLink https://previousplan.org/rss.xml +RssExport ~/sites/previousplan.org/rss.xml +PostsExport ~/sites/previousplan.org/posts.html @@ -0,0 +1,22 @@ +#include <stdio.h> +#include <time.h> + +#include "config.h" + +#define LEN(X) (sizeof(X)/sizeof(*X)) + +typedef struct { + char filename[64]; + char title[128]; + time_t creat, mod; + char link[CLEN]; +} Document; + +typedef struct { + const char name[16]; + void (*export)(Document **, int); + Document **usedby; + int nusedby; +} Plugin; + +int cat(const char *, FILE *); @@ -0,0 +1,83 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "util.h" + +/* This isn't a 1:1 wrapper around fgets. It also removes that annoying + * trailing \n automatically */ +char * +xfgets(char *s, size_t size, FILE *stream) +{ + char *p; + if ((s = fgets(s, size, stream))) { + if ((p = strchr(s, '\n'))) + *p = '\0'; + } else if (ferror(stream)) { + fprintf(stderr, "fgets: %s\n", strerror(errno)); + exit(1); + } + return s; +} + +FILE * +xfopen(const char *pathname, const char *mode) +{ + FILE *f; + if ((f = fopen(pathname, mode)) == NULL) { + fprintf(stderr, "%s: %s\n", pathname, strerror(errno)); + exit(1); + } + return f; +} + +int +xfputs(const char *s, FILE *stream) +{ + int d; + if ((d = fputs(s, stream)) == EOF) { + fprintf(stderr, "fputs: %s\n", strerror(errno)); + exit(1); + } + return d; +} + +void * +xmalloc(size_t size) +{ + void *n; + if ((n = malloc(size)) == NULL) { + fprintf(stderr, "malloc: %s\n", strerror(errno)); + exit(1); + } + return n; +} + +void * +xrealloc(void *ptr, size_t size) +{ + void *n; + if ((n = realloc(ptr, size)) == NULL) { + fprintf(stderr, "malloc: %s\n", strerror(errno)); + exit(1); + } + return n; +} + +/* strlcpy is unportable, and strncpy is a mess. So we define our own strlcpy + * instead. */ +size_t +my_strlcpy(char *dst, const char *src, size_t size) +{ + const char *p2; + char *p1, *stop; + + p1 = dst; + p2 = src; + stop = dst + size; + while (*p2 && p1 != stop) + *p1++ = *p2++; + *p1 = '\0'; + return (size_t)(p1 - dst); +} @@ -0,0 +1,9 @@ +#include <stdio.h> +#include <stdlib.h> + +char *xfgets(char *, size_t, FILE *); +FILE *xfopen(const char *, const char *); +int xfputs(const char *, FILE *); +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +size_t my_strlcpy(char *, const char *, size_t); |