dag

Djmoch's Auto Generator
git clone git://git.danielmoch.com/dag.git
Log | Files | Refs | README | LICENSE

commit aa554186f25b5ff77b63764b0a5108bbb033648f
parent 44ba8884823d8747df837fb7c161f4249c412628
Author: Daniel Moch <daniel@danielmoch.com>
Date:   Mon, 22 Nov 2021 11:33:42 -0500

xml: Add RSS feeds

Diffstat:
Mdagindex.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mxml.c | 188+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mxml.h | 3++-
3 files changed, 219 insertions(+), 47 deletions(-)

diff --git a/dagindex.c b/dagindex.c @@ -48,7 +48,8 @@ usage(int rv) fprintf(fp, "usage: %s -Vh\n", argv0); fprintf(fp, "\t%s -A -t title -s slug -p date_published [-a author]\n", argv0); fprintf(fp, "\t\t[-u date_updated] [-c category] [-d description]\n"); - fprintf(fp, "\t%s -G -o fmt [-f fqdn]\n", argv0); + fprintf(fp, "\t%s -G -o fmt [-t title] [-f fqdn] [-d description]\n", argv0); + fprintf(fp, "\t\t[-r rss_url] [-l language] [-c copyright]\n"); exit(rv); } @@ -110,13 +111,13 @@ main(int argc, char **argv) { struct db_entry *entry; struct db_index *index; - char ch, *author = NULL, *cat = NULL, *desc = NULL, - *fmt = NULL, *fqdn = NULL, *pub = NULL, *slug = NULL, - *title = NULL, *updated = NULL; + char ch, *author = NULL, *c = NULL, *desc = NULL, + *fmt = NULL, *fqdn = NULL, *lang = NULL, *pub = NULL, + *rss_url = NULL, *slug = NULL, *title = NULL, *updated = NULL; argv0 = basename(argv[0]); - while ((ch = getopt(argc, argv, "AGVa:c:d:f:ho:p:s:t:u:v")) != -1) { + while ((ch = getopt(argc, argv, "AGVa:c:d:f:hl:o:p:r:s:t:u:v")) != -1) { switch (ch) { case 'A': if (mode != NONE) { @@ -145,29 +146,24 @@ main(int argc, char **argv) author = optarg; break; case 'c': - if (mode != ADD) { - warnx("specifying -c without -A makes no sense"); - usage(ERR_ARGS); - } - cat = optarg; + c = optarg; break; case 'd': - if (mode != ADD) { - warnx("specifying -d without -A makes no sense"); - usage(ERR_ARGS); - } desc = optarg; break; case 'f': - if (mode != GENERATE) { - warnx("specifying -f without -G makes no sense"); - usage(ERR_ARGS); - } fqdn = optarg; break; case 'h': usage(ERR_NONE); break; + case 'l': + if (mode != GENERATE) { + warnx("specifying -l without -G makes no sense"); + usage(ERR_ARGS); + } + lang = optarg; + break; case 'o': if (mode != GENERATE) { warnx("specifying -o without -G makes no sense"); @@ -182,6 +178,13 @@ main(int argc, char **argv) } pub = optarg; break; + case 'r': + if (mode != GENERATE) { + warnx("specifying -r without -G makes no sense"); + usage(ERR_ARGS); + } + rss_url = optarg; + break; case 's': if (mode != ADD) { warnx("specifying -s without -A makes no sense"); @@ -190,10 +193,6 @@ main(int argc, char **argv) slug = optarg; break; case 't': - if (mode != ADD) { - warnx("specifying -t without -A makes no sense"); - usage(ERR_ARGS); - } title = optarg; break; case 'u': @@ -242,7 +241,7 @@ main(int argc, char **argv) if (verbose >= 2) { fputs("DEBUG: received options:\n", stderr); fprintf(stderr, "\tauthor = %s\n", author); - fprintf(stderr, "\tcategory = %s\n", cat); + fprintf(stderr, "\tcategory = %s\n", c); fprintf(stderr, "\tdescription = %s\n", desc); fprintf(stderr, "\tdate_published = %s\n", pub); fprintf(stderr, "\tslug = %s\n", slug); @@ -255,7 +254,7 @@ main(int argc, char **argv) switch (mode) { case ADD: entry->author = populate_str(author, ""); - entry->category = populate_str(cat, ""); + entry->category = populate_str(c, ""); entry->description = populate_str(desc, ""); entry->date_published = populate_time(pub); entry->slug = populate_str(slug, ""); @@ -287,6 +286,34 @@ main(int argc, char **argv) } xml_db_fmt_sitemap(fqdn, index); } + else if (strncmp(fmt, "rss", 4) == 0) { + if (title == NULL) { + warnx("rss requires title be set with -t"); + break; + } + if (fqdn == NULL) { + warnx("rss requires fqdn be set with -f"); + break; + } + if (desc == NULL) { + warnx("rss requires description be set with -d"); + break; + } + if (rss_url == NULL) { + warnx("rss requires RSS URL be set with -r"); + break; + } + if (lang == NULL) { + warnx("rss requires language be set with -c"); + break; + } + if (c == NULL) { + warnx("rss requires copyright be set with -c"); + break; + } + xml_db_fmt_rss(title, fqdn, desc, rss_url, + lang, c, index); + } else { warnx("unknown output format: %s", fmt); } diff --git a/xml.c b/xml.c @@ -12,27 +12,20 @@ static const int DATE_STRING_LENGTH = 30; /* TODO: use decl in db.c */ -static void xmlescape(char *s); +static char *xmlescape(char *s); +static void file_date_str(char mod[DATE_STRING_LENGTH], const char *path, const char *fmt); +static void time_t_date_str(char mod[DATE_STRING_LENGTH], time_t time, const char *fmt); void xml_db_fmt_sitemapindex(char *fqdn, struct db_index *index) { - struct stat sb; - struct tm *time_tm; - char mod[DATE_STRING_LENGTH], *xfqdn = strdup(fqdn); + char mod[DATE_STRING_LENGTH], *xfqdn; - if (stat(index->db_path, &sb) == -1) { - err(errno, "unable to stat %s", index->db_path); - } - if ((time_tm = gmtime(&(sb.st_mtim.tv_sec))) == NULL) { - errx(1, "call to gmtime failed"); - } - if (strftime(mod, DATE_STRING_LENGTH, "%FT%TZ", time_tm) == 0) { - errx(1, "call to strftime failed"); + file_date_str(mod, index->db_path, "%FT%TZ"); + if ((xfqdn = strdup(fqdn)) == NULL) { + err(errno, "strdup failed"); } - - xmlescape(mod); - xmlescape(xfqdn); + xfqdn = xmlescape(xfqdn); puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); puts("<sitemapindex"); @@ -47,6 +40,7 @@ xml_db_fmt_sitemapindex(char *fqdn, struct db_index *index) puts("</sitemap>"); puts("</sitemapindex>"); + free(mod); free(xfqdn); } @@ -54,11 +48,14 @@ void xml_db_fmt_sitemap(char *fqdn, struct db_index *index) { struct db_entry *entry = index->entries; - char *xfqdn = strdup(fqdn); + char *xfqdn; struct tm *tmp_tm; char upd[DATE_STRING_LENGTH], pub[DATE_STRING_LENGTH]; - xmlescape(xfqdn); + if ((xfqdn = strdup(fqdn)) == NULL) { + err(errno, "strdup failed"); + } + xfqdn = xmlescape(xfqdn); puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); puts("<urlset"); @@ -97,12 +94,125 @@ xml_db_fmt_sitemap(char *fqdn, struct db_index *index) } void -xml_db_fmt_rss(struct db_index *index) +xml_db_fmt_rss(char *title, char *fqdn, char *description, char *rss_url, + char *lang, char *copyright, struct db_index *index) { - index = NULL; + struct db_entry *entry = index->entries; + char *xcopyright, *xdescription, *xfqdn, *xlang, *xrss_url, *xtitle; + char mod[DATE_STRING_LENGTH]; + const char date_fmt[] = "%a, %d %b %Y %T GMT"; + + file_date_str(mod, index->db_path, date_fmt); + + if ((xcopyright = strdup(copyright)) == NULL) { + err(errno, "strdup failed"); + } + xcopyright = xmlescape(xcopyright); + + if ((xdescription = strdup(description)) == NULL) { + err(errno, "strdup failed"); + } + xdescription = xmlescape(xdescription); + + if ((xfqdn = strdup(fqdn)) == NULL) { + err(errno, "strdup failed"); + } + xfqdn = xmlescape(xfqdn); + + if ((xlang = strdup(lang)) == NULL) { + err(errno, "strdup failed"); + } + xlang = xmlescape(xlang); + + if ((xrss_url = strdup(rss_url)) == NULL) { + err(errno, "strdup failed"); + } + xrss_url = xmlescape(xrss_url); + + if ((xtitle = strdup(title)) == NULL) { + err(errno, "strdup failed"); + } + xtitle = xmlescape(xtitle); + + puts("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); + puts("<?xml-stylesheet type=\"text/xsl\" href=\"/assets/xml/rss.xsl\" media=\"all\"?>"); + puts("<rss version=\"2.0\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:atom=\"http://www.w3.org/2005/Atom\">"); + puts(" <channel>"); + printf(" <title>%s</title>\n", xtitle); + printf(" <link>%s</link>\n", xfqdn); + printf(" <description>%s</description>\n", xdescription); + printf(" <atom:link href=\"%s\" rel=\"self\" type=\"application/rss+xml\"></atom:link>\n", xrss_url); + printf(" <language>%s</language>\n", xlang); + printf(" <copyright>%s</copyright>\n", xcopyright); + printf(" <lastBuildDate>%s</lastBuildDate>\n", mod); + puts(" <generator>Dag (git.danielmoch.com/dag)</generator>"); + puts(" <docs>http://blogs.law.harvard.edu/tech/rss</docs>"); + + while (entry != NULL) { + char *etitle, *elink, *ecreator, *edescription, *eslug; + + if ((etitle = strdup(entry->title)) == NULL) { + err(errno, "strdup failed"); + } + etitle = xmlescape(etitle); + + if ((elink = strdup(xfqdn)) == NULL) { + err(errno, "strdup failed"); + } + if ((elink = realloc(elink, strlen(xfqdn) + strlen(entry->slug) + 1)) == NULL) { + err(errno, "realloc failed"); + } + elink = strcat(elink, "/"); + elink = strcat(elink, entry->slug); + elink = xmlescape(elink); + + if ((ecreator = strdup(entry->author)) == NULL) { + err(errno, "strdup failed"); + } + ecreator = xmlescape(ecreator); + + if ((edescription = strdup(entry->description)) == NULL) { + err(errno, "strdup failed"); + } + edescription = xmlescape(edescription); + + if ((eslug = strdup(entry->slug)) == NULL) { + err(errno, "strdup failed"); + } + eslug = xmlescape(eslug); + + time_t_date_str(mod, entry->date_published, date_fmt); + + puts(" <item>"); + printf(" <title>%s</title>\n", etitle); + printf(" <link>%s</link>\n", elink); + printf(" <dc:creator>%s</dc:creator>\n", ecreator); + printf(" <description>%s</description>\n", edescription); + printf(" <guid>%s</guid>\n", eslug); + printf(" <pubDate>%s</pubDate>\n", mod); + puts(" </item>"); + + free(etitle); + free(elink); + free(ecreator); + free(edescription); + free(eslug); + + entry = entry->next; + } + + puts(" </channel>"); + puts("</rss>"); + + free(xcopyright); + free(xdescription); + free(xfqdn); + free(xlang); + free(xrss_url); + free(xtitle); } -static void +static char * xmlescape(char *s) { size_t len = strlen(s), ct = 0, maxlen = 0; @@ -123,11 +233,13 @@ xmlescape(char *s) } if (ct == 0) { - return; + return s; } maxlen = len + (ct * strlen("&quot;")); - s = realloc(s, maxlen); + if ((s = realloc(s, maxlen)) == NULL) { + err(errno, "realloc failed"); + } strnswp(s, "'", "&#39;", maxlen); strnswp(s, "\"", "&quot;", maxlen); @@ -137,4 +249,36 @@ xmlescape(char *s) strnswp(s, "\t", "&#x9;", maxlen); strnswp(s, "\n", "&#xA;", maxlen); strnswp(s, "\r", "&#xD;", maxlen); + + return s; +} + +static void +file_date_str(char mod[DATE_STRING_LENGTH], const char *path, const char *fmt) +{ + struct stat sb; + struct tm *time_tm; + + if (stat(path, &sb) == -1) { + err(errno, "unable to stat %s", path); + } + if ((time_tm = gmtime(&(sb.st_mtim.tv_sec))) == NULL) { + errx(1, "call to gmtime failed"); + } + if (strftime(mod, DATE_STRING_LENGTH, fmt, time_tm) == 0) { + errx(1, "call to strftime failed"); + } +} + +static void +time_t_date_str(char mod[DATE_STRING_LENGTH], time_t time, const char *fmt) +{ + struct tm *time_tm; + + if ((time_tm = gmtime(&time)) == NULL) { + errx(1, "call to gmtime failed"); + } + if (strftime(mod, DATE_STRING_LENGTH, fmt, time_tm) == 0) { + errx(1, "call to strftime failed"); + } } diff --git a/xml.h b/xml.h @@ -4,6 +4,7 @@ void xml_db_fmt_sitemapindex(char *fqdn, struct db_index *index); void xml_db_fmt_sitemap(char *fqdn, struct db_index *index); -void xml_db_fmt_rss(char *fqdn, struct db_index *index); +void xml_db_fmt_rss(char *title, char *fqdn, char *description, + char *rss_url, char *lang, char *copyright, struct db_index *index); #endif