forb

Forb is static blog generator inspired by jekyll
git clone https://noulin.net/git/forb.git
Log | Files | Refs | README | LICENSE

commit 80c29d144cbb6ef2f34c5475f4cd296001a6e941
parent d3fac2986de28275d7613c85c54243c9ffc58dc6
Author: Remy Noulin <loader2x@gmail.com>
Date:   Sun,  2 Aug 2020 15:17:04 +0200

add new title command to create a draft, add publish command to publish a draft

forb.c                        | 197 +++++++++++++++++++++++++++++++++++++-----
package.yml                   |   2 +-
shpPackages/short/package.yml |   2 +-
shpPackages/short/short.h     |   5 +-
4 files changed, 180 insertions(+), 26 deletions(-)

Diffstat:
Mforb.c | 197+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mpackage.yml | 2+-
MshpPackages/short/package.yml | 2+-
MshpPackages/short/short.h | 5++++-
4 files changed, 180 insertions(+), 26 deletions(-)

diff --git a/forb.c b/forb.c @@ -3,6 +3,7 @@ #include "shpPackages/short/short.h" #include "shpPackages/preprocessor/preprocessor.h" #include "shpPackages/simpleTemplates/simpleTemplates.h" +#include "forb.h" // forb new: copy templates to current directory // forb: generate static blog in the _site directory @@ -25,6 +26,9 @@ #define configFile "_config.yml" #define indexFile "index.html" #define layoutDir "_layouts" +#define draftsDir "_drafts" +#define PostsDir "_posts" +#define publishedDir "_published" #define siteDir "_site" #define mainCss "css/main.css" #define imagesDir "images" @@ -35,6 +39,10 @@ /* #undef pLog */ /* #define pLog(...) */ +void help(void); + +void publish(const char *path); + bool readFileWithFrontMatter(char *filename, smallJsont *kv, smallArrayt *lines); bool generateAPageOrAPost(char *filename, smallJsont *cfg, smallArrayt *postsFeed); @@ -48,38 +56,92 @@ int main(int ARGC, char** ARGV) { //disableLibsheepyErrorLogs; // read arguments + #define checkInput(path) procbegin\ + if (not isPath(path)) {\ + logE(BLD"%s not found."RST"\n"\ + , path);\ + help();\ + XFailure;\ + }\ + procend + if (ARGC > 1) { - if (eqG(ARGV[1], "new")) { - bool c,a; - if ((c = isPath(configFile)) or (a = isPath("about.md"))) { - if (c) logC(configFile" already exists."); - if (a) logC("about.md already exists."); - logC("Stop."); + if (eqG(ARGV[1], "new") and not isPath(configFile)) { + // create directory structure when config file is not found + bool a; + if (a = isPath("about.md")) { + if (a) logE("about.md already exists."); + logE("Stop."); XFailure; } cleanCharP(progPath) = shDirname(getRealProgPath()); logCommandf("cp -R %s/template/* .", progPath); XSuccess; } + elif (eqG(ARGV[1], "new")) { + checkInput(draftsDir); + if (ARGC < 3) { + logE(BLD"Too few arguments"RST", post title missing.\n" + "forb new "BLD"TITLE"RST"\n" + ); + XFailure; + } + cleanListP(args) = dupG(ARGV); + delElemG(&args, 1); + delElemG(&args, 0); + cleanCharP(title) = joinG(args, ' '); + uniqG(&title, ' '); + //lv(title); + cleanCharP(draftName) = dupG(title); + char *tmp = draftName; + while(*tmp++) { + if (*tmp and not isalnum(*tmp)) { + *tmp = '-'; + } + } + lowerG(&draftName); + prependG(&draftName, draftsDir"/"); + pushG(&draftName, ".markdown"); + //lv(draftName); + cleanCharP(defaultDraft) = replaceG(draftTemplate, "$TITLE", title, 1); + writeFileG(defaultDraft, draftName); + logP(BLD GRN"Generated %s\n"RST + "Open %s in your text editor and write your post.", draftName, draftName); + XSuccess; + } + elif (eqG(ARGV[1], "-h") or eqG(ARGV[1], "--help") or eqG(ARGV[1], "help")) { + help(); + XSuccess; + } + elif (eqG(ARGV[1], "publish")) { + if (ARGC < 3) { + logE("publish failed. Missing path, usage:\n" + "forb publish PATH" + ); + XFailure; + } + rangeFrom(i, 2, ARGC) { + if (not isPath(ARGV[i])) { + logE("Path %d '%s' not found", i, ARGV[i]); + } + else { + checkInput(draftsDir); + checkInput(PostsDir); + publish(ARGV[i]); + } + } + XSuccess; + } else { - logC("Command not found.\n\n" - "forb new --- copy default template\n" - "forb --- generate site"); + logE("Command "BLD"'%s'"RST" not found or invalid parameters.\n\n" + , ARGV[1]); + help(); XFailure; } } // check inputs - #define checkInput(path) procbegin\ - if (not isPath(path)) {\ - logC("%s not found.\n\n"\ - "forb new --- copy default template\n"\ - "forb --- generate site"\ - , path);\ - XFailure;\ - }\ - procend checkInput(configFile); checkInput(indexFile); checkInput(layoutDir); @@ -112,7 +174,7 @@ int main(int ARGC, char** ARGV) { // read posts // list all md files in _posts - cleanSmallArrayP(postsDir) = readDirG(rtSmallArrayt, "_posts"); + cleanSmallArrayP(postsDir) = readDirG(rtSmallArrayt, PostsDir); iter(postsDir, L) { castS(l,L); if (not endsWithG(l, ".markdown")) { @@ -127,7 +189,7 @@ int main(int ARGC, char** ARGV) { castS(l,L); cleanCharP(postHtmlFile) = copyRngG(ssGet(l), 11, 0); replaceG(&postHtmlFile, ".markdown", ".html", 1); - prependG(l, "_posts/"); + prependG(l, PostsDir); setPG(postsDir, iterIndexG(postsDir), l); cleanAllocateSmallJson(postCfg); cleanAllocateSmallArray(postf); @@ -261,6 +323,95 @@ int main(int ARGC, char** ARGV) { if (isPath("tmp.md")) rmAll("tmp.md"); } +void help(void) { + logI(BLD GRN"Forb help\n"RST + "Argument convention: the arguments are words\n\n" + BLD YLW"\nFORB SUBCOMMANDS:\n\n"RST + " help, -h, --help --- print this help message\n" + " without argument --- generate the blog in the "siteDir" directory, the posts in "PostsDir" are moved to "publishedDir"\n" + " new [title] --- copy default template or when running in a forb directory, create a draft post\n" + " publish PATH --- publish a draft, the draft file is moved from the "draftsDir" directory to the "PostsDir" directory and today's date is added to the file name and in the post\n" + " update PATH --- move post in "publishedDir" to "PostsDir", edit the post and generate the blog. The update date is added to the post and the first publish date is kept\n" + ); +} + +/** + * publish a post in _drafts to _posts + */ +void publish(const char *path) { + // steps + // check extension + // read post + // create date string for filename + // create destination filename + // create date string for front matter + // add date string in front matter + // failed to find front matter or categories + // save posts directory + // remove path + + // check extension + if (not endsWithG(path, ".markdown")) { + logE("'%s' doesn't have the markdown extension. Stop."); + XFailure; + } + + // read post + cleanAllocateSmallArray(post); + readFileG(post, path); + + // create date string for filename + cleanCharP(date) = getCurrentDateYMD(); + date[10] = 0; + //lv(date); + //lv(path); + + // create destination filename + char *filename = basename(path); + cleanCharP(dest) = catS(PostsDir, "/", date, "-", filename); + //lv(dest); + + // create date string for front matter + date[10] = ' '; + prependG(&date, "date: "); + //lv(date); + + // add date string in front matter + char *status = "search front matter"; + iter(post, L) { + castS(l, L); + if (eqG(status, "stop at categories") and startsWithG(l, "categories:")) { + injectG(post, iI(post), date); + goto publishPost; + } + if (eqG(status, "stop at categories") and eqG(l, "---")) { + logE("categories not found in front matter, path is: %s", path); + ret; + } + if (eqG(status, "search front matter") and eqG(l, "---")) { + status = "stop at categories"; + } + } + + // failed to find front matter or categories + logE("front matter or categories not found, path is %s", path); + ret; + + publishPost: + // save posts directory + if (not writeFileG(post, dest)) { + logE("Could not write '%s', path is: %s", dest, path); + ret; + } + + // remove path + if (not rmAllG(path)) { + logE("Could not remove: %s", path); + } + + logP("Published draft post to %s", dest); +} + /** * read file filename with front matter and text (any type of text) @@ -330,7 +481,7 @@ bool readFileWithFrontMatter(char *filename, smallJsont *kv, smallArrayt *lines) * The url format is "/category/YYYY/MM/DD/filename.html" */ char *postToUrl(char *filename) { - cleanCharP(fn) = catS("_posts/",filename,".markdown"); + cleanCharP(fn) = catS(PostsDir,filename,".markdown"); cleanAllocateSmallArray(pf); cleanAllocateSmallJson(pCfg); @@ -704,7 +855,7 @@ bool generateAPageOrAPost(char *filename, smallJsont *cfg, smallArrayt *postsFee cleanCharP(dst) = null; char *pUrl = null; baset *description = null; - if (startsWithG(filename, "_posts/")) { + if (startsWithG(filename, PostsDir)) { cleanCharP(postHtmlFile) = copyRngG(filename, 18, 0); replaceG(&postHtmlFile, ".markdown", ".html", 1); @@ -746,7 +897,7 @@ bool generateAPageOrAPost(char *filename, smallJsont *cfg, smallArrayt *postsFee setNFreeG(pageValues, "_\%site.email", getNDupO(cfg, "email")); setNFreeG(pageValues, "_\%site.github_username", getNDupO(cfg, "github_username")); setNFreeG(pageValues, "_\%site.twitter_username", getNDupO(cfg, "twitter_username")); - if (startsWithG(filename, "_posts/")) { + if (startsWithG(filename, PostsDir)) { setG(pageValues, "_\%date", $(pCfg, "date")); } diff --git a/package.yml b/package.yml @@ -1,6 +1,6 @@ --- name: forb - version: 0.0.6 + version: 0.0.7 description: static blog generator with configuration files inspired by jekyll bin: ./forb.c repository: diff --git a/shpPackages/short/package.yml b/shpPackages/short/package.yml @@ -1,6 +1,6 @@ --- name: short - version: 0.0.2 + version: 0.0.3 description: "convenient defines for often used libsheepy function calls making the lines shorter and more readable" bin: ./short.h repository: diff --git a/shpPackages/short/short.h b/shpPackages/short/short.h @@ -1,8 +1,11 @@ #include "libsheepyObject.h" -#define lv logVarG +#define lv logTVarG #define i_ logI +#define iI iterIndexG +#define iK iterKeyG + // get string from array or dict #define $(object, key) getG(object, rtChar, key)