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:
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)