From b2fada1dd19211d71e04557653d08e697134a6ce Mon Sep 17 00:00:00 2001 From: Erik K Date: Fri, 13 May 2022 17:24:26 +0000 Subject: initial commit --- .gitignore | 6 + Makefile | 24 ++++ config.h | 94 +++++++++++++ config.mk | 9 ++ genplugin.awk | 20 +++ posts.c | 93 ++++++++++++ rss.c | 84 +++++++++++ skull.c | 82 +++++++++++ sm.c | 442 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ sm.conf | 12 ++ sm.h | 22 +++ util.c | 83 +++++++++++ util.h | 9 ++ 13 files changed, 980 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 config.h create mode 100644 config.mk create mode 100644 genplugin.awk create mode 100644 posts.c create mode 100644 rss.c create mode 100644 skull.c create mode 100644 sm.c create mode 100644 sm.conf create mode 100644 sm.h create mode 100644 util.c create mode 100644 util.h 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 + +#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 +} diff --git a/posts.c b/posts.c new file mode 100644 index 0000000..239f118 --- /dev/null +++ b/posts.c @@ -0,0 +1,93 @@ +#include +#include +#include + +#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[] = "\ +\n\ +\n\ + Posts\n\ + \n\ + \n\ +\n\ +\n\ + \"PreviousPlan!\n\ + \n\ +

List of posts

\n\ +"; + + static char footer[] = "\ +
\n\ + \"EMAIL\"/\n\ +

Webmaster email: erikk@previousplan.org \ +(PGP key)

\n\ +\n\ +\n\ +"; + + char title[128], link[CLEN]; + char *item[] = { "
  • ", title, "
  • \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 ? "\n

    %B %Y

    \n
      \n" : "

      %B %Y

      \n
        \n", tm); + xfputs(dateheader, outf); + } + memcpy(&lasttm, tm, sizeof(lasttm)); + for (j = 0; j < LEN(item); j++) + xfputs(item[j], outf); + } + if (ndoc) + xfputs("
      \n", outf); + xfputs(footer, outf); + fclose(outf); +} diff --git a/rss.c b/rss.c new file mode 100644 index 0000000..e0f22fe --- /dev/null +++ b/rss.c @@ -0,0 +1,84 @@ +#include + +#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[] = { "\ +\n\ +\n\ +\n\ +\n\ +", krsstitle, "\n\ +", krssdescription, "\n\ +en-us\n\ +", krsslink, "\n\ +sm-"VERSION"\n\ +\n\ +" }; + + char *footer[] = { "\ +\n\ +\n\ +" }; + + char filename[64], title[128], creat[64], link[CLEN]; + char *itemheader[] = { "\ +\n\ + ", title, "\n\ + ", filename, "\n\ + ", creat, "\n\ + ", link, "\n\ + \n\ +\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); +} diff --git a/skull.c b/skull.c new file mode 100644 index 0000000..5c44f20 --- /dev/null +++ b/skull.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +#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[] = { "\ +\n\ +\n\ + ", title, "\n\ + \n\ + \n\ +\n\ +\n\ + \"PreviousPlan!\n\ + \n\ +" }; + + char *footer[] = { "\ +
      \n\ +

      Written: ", creat, ", Last modified: ", mod, "

      \n\ + \"EMAIL\"/\n\ +

      Webmaster email: erikk@previousplan.org \ +(PGP key)

      \n\ +\n\ +\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); + } +} diff --git a/sm.c b/sm.c new file mode 100644 index 0000000..d4064c7 --- /dev/null +++ b/sm.c @@ -0,0 +1,442 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/sm.conf b/sm.conf new file mode 100644 index 0000000..63a1352 --- /dev/null +++ b/sm.conf @@ -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 diff --git a/sm.h b/sm.h new file mode 100644 index 0000000..085b92f --- /dev/null +++ b/sm.h @@ -0,0 +1,22 @@ +#include +#include + +#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 *); diff --git a/util.c b/util.c new file mode 100644 index 0000000..3f4f51d --- /dev/null +++ b/util.c @@ -0,0 +1,83 @@ +#include +#include +#include +#include + +#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); +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..e1e49e7 --- /dev/null +++ b/util.h @@ -0,0 +1,9 @@ +#include +#include + +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); -- cgit v1.2.3