forb

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

commit d06930708777e4e052daa96f628529c86103b3eb
Author: Remy Noulin <loader2x@gmail.com>
Date:   Tue,  9 Jun 2020 20:43:17 +0200

first version

.gitignore                                         |  65 ++
LICENSE                                            |  21 +
README.md                                          |  20 +
forb.c                                             | 761 +++++++++++++++++++++
package.yml                                        |  22 +
shpPackages/preprocessor/LICENSE                   |  21 +
shpPackages/preprocessor/package.yml               |  15 +
shpPackages/preprocessor/pp.txt                    |  21 +
shpPackages/preprocessor/ppCommentConfig.txt       |  19 +
shpPackages/preprocessor/pp_inc.txt                |   3 +
shpPackages/preprocessor/preprocessor.c            | 296 ++++++++
shpPackages/preprocessor/preprocessor.h            |   7 +
shpPackages/preprocessor/preprocessorTest.c        |  18 +
shpPackages/short/main.c                           |  29 +
shpPackages/short/package.yml                      |  15 +
shpPackages/short/short.h                          |  12 +
shpPackages/simpleTemplates/LICENSE                |  21 +
shpPackages/simpleTemplates/file.txt               |  24 +
shpPackages/simpleTemplates/package.yml            |  15 +
shpPackages/simpleTemplates/simpleTemplates.c      |  73 ++
shpPackages/simpleTemplates/simpleTemplates.h      |  12 +
shpPackages/simpleTemplates/simpleTemplatesTest.c  |  22 +
shpPackages/simpleTemplates/templateFile.txt       |   3 +
template/_config.yml                               |  14 +
template/_includes/footer.html                     |  51 ++
template/_includes/head.html                       |  12 +
template/_includes/header.html                     |  23 +
template/_layouts/default.html                     |  20 +
template/_layouts/page.html                        |  14 +
template/_layouts/post.html                        |  15 +
.../_posts/2020-06-09-getting-started.markdown     |  11 +
template/about.md                                  |  10 +
template/css/main.css                              | 449 ++++++++++++
template/feed.xml                                  |  16 +
template/index.html                                |  15 +
35 files changed, 2165 insertions(+)

Diffstat:
A.gitignore | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE | 21+++++++++++++++++++++
AREADME.md | 20++++++++++++++++++++
Aforb.c | 761+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackage.yml | 22++++++++++++++++++++++
AshpPackages/preprocessor/LICENSE | 21+++++++++++++++++++++
AshpPackages/preprocessor/package.yml | 15+++++++++++++++
AshpPackages/preprocessor/pp.txt | 21+++++++++++++++++++++
AshpPackages/preprocessor/ppCommentConfig.txt | 19+++++++++++++++++++
AshpPackages/preprocessor/pp_inc.txt | 3+++
AshpPackages/preprocessor/preprocessor.c | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AshpPackages/preprocessor/preprocessor.h | 7+++++++
AshpPackages/preprocessor/preprocessorTest.c | 18++++++++++++++++++
AshpPackages/short/main.c | 29+++++++++++++++++++++++++++++
AshpPackages/short/package.yml | 15+++++++++++++++
AshpPackages/short/short.h | 12++++++++++++
AshpPackages/simpleTemplates/LICENSE | 21+++++++++++++++++++++
AshpPackages/simpleTemplates/file.txt | 24++++++++++++++++++++++++
AshpPackages/simpleTemplates/package.yml | 15+++++++++++++++
AshpPackages/simpleTemplates/simpleTemplates.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AshpPackages/simpleTemplates/simpleTemplates.h | 12++++++++++++
AshpPackages/simpleTemplates/simpleTemplatesTest.c | 22++++++++++++++++++++++
AshpPackages/simpleTemplates/templateFile.txt | 3+++
Atemplate/_config.yml | 14++++++++++++++
Atemplate/_includes/footer.html | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Atemplate/_includes/head.html | 12++++++++++++
Atemplate/_includes/header.html | 23+++++++++++++++++++++++
Atemplate/_layouts/default.html | 20++++++++++++++++++++
Atemplate/_layouts/page.html | 14++++++++++++++
Atemplate/_layouts/post.html | 15+++++++++++++++
Atemplate/_posts/2020-06-09-getting-started.markdown | 11+++++++++++
Atemplate/about.md | 10++++++++++
Atemplate/css/main.css | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atemplate/feed.xml | 16++++++++++++++++
Atemplate/index.html | 15+++++++++++++++
35 files changed, 2165 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,65 @@ +md2html + +# Vim +*.sw* + +# Debug +.gdb_history + +# Coverage +*.gcov +*.gcda +*.gcno + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/LICENSE b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Remy Noulin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md @@ -0,0 +1,20 @@ +# Getting started with forb + +Forb is a blogging system using configurations similar to [jekyll](https://jekyllrb.com). + +- Run `forb new` to copy the default template to the current directory +- Run `forb` to generate the site in the `_site` directory + +The source code is available at [forb git](https://noulin.net/forb) + +# Configuration + +The site configuration is located in `_config.yml`. The default `_config.yml` is created in the directory where `forb new` was executed. + +# Posts + +Add your posts in the `_posts` directory, with filenames in the format: +`YYYY-MM-DD-TITLE.markdown` + +For example: +`2020-06-09-getting-started.markdown` diff --git a/forb.c b/forb.c @@ -0,0 +1,761 @@ +#! /usr/bin/env sheepy +#include "libsheepyObject.h" +#include "shpPackages/short/short.h" +#include "shpPackages/preprocessor/preprocessor.h" +#include "shpPackages/simpleTemplates/simpleTemplates.h" + +// forb new: copy templates to current directory +// forb: generate static blog in the _site directory + +// Steps +// read arguments +// check inputs +// load config +// copy main css +// copy images +// generate index + // read posts + // generate html code for each post for index.html + // create configuation for index +// generate about +// generate posts +// generate feed.xml +// clean tmp files + +#define configFile "_config.yml" +#define indexFile "index.html" +#define layoutDir "_layouts" +#define siteDir "_site" +#define mainCss "css/main.css" +#define imagesDir "images" + +#define content "{{ content }}" + +/* enable/disable logging */ +/* #undef pLog */ +/* #define pLog(...) */ + +bool readFileWithFrontMatter(char *filename, smallJsont *kv, smallArrayt *lines); + +bool generateAPageOrAPost(char *filename, smallJsont *cfg, smallArrayt *postsFeed); + +int main(int ARGC, char** ARGV) { + + initLibsheepy(ARGV[0]); + setLogMode(LOG_FUNC); + //openProgLogFile(); + setLogSymbols(LOG_UTF8); + //disableLibsheepyErrorLogs; + + // read arguments + 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."); + XFailure; + } + cleanCharP(progPath) = shDirname(getRealProgPath()); + logCommandf("cp -R %s/template/* .", progPath); + XSuccess; + } + else { + logC("Command not found.\n\n" + "forb new --- copy default template\n" + "forb --- generate site"); + 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); + + // load config + cleanAllocateSmallJson(cfg); + readFileG(cfg, configFile); + + if (not isPath(siteDir)) { + mkdirParents(siteDir); + } + + // copy main css + if (not isPath(siteDir"/css")) { + mkdirParents(siteDir"/css"); + } + copy(mainCss, siteDir"/"mainCss); + + // copy images + if (not isPath(siteDir"/"imagesDir)) { + mkdirParents(siteDir"/"imagesDir); + } + command("cp "imagesDir"/* "siteDir"/"imagesDir"/"); + + + // generate index + cleanAllocateSmallArray(indexf); + cleanAllocateSmallJson(indexCfg); + readFileWithFrontMatter(indexFile, indexCfg, indexf); + + // read posts + // list all md files in _posts + cleanSmallArrayP(postsDir) = readDirG(rtSmallArrayt, "_posts"); + iter(postsDir, L) { + castS(l,L); + if (not endsWithG(l, ".markdown")) { + delElemG(postsDir, iterIndexG(postsDir)); + } + } + //lv(postsDir); + + // generate html code for each post for index.html + cleanAllocateSmallArray(postsIndex); + iterLast(postsDir, L) { + castS(l,L); + cleanCharP(postHtmlFile) = copyRngG(ssGet(l), 11, 0); + replaceG(&postHtmlFile, ".markdown", ".html", 1); + prependG(l, "_posts/"); + setPG(postsDir, iterIndexG(postsDir), l); + cleanAllocateSmallJson(postCfg); + cleanAllocateSmallArray(postf); + readFileWithFrontMatter(ssGet(l), postCfg, postf); + cleanCharP(postDate) = copyRngG($(postCfg, "date"), 0, 10); + replaceG(&postDate, "-", "/", 0); + cleanSmallArrayP(postUrlA) = createSA($(cfg, "baseurl"), + $(postCfg, "categories"), postDate, postHtmlFile); + cleanCharP(postUrl) = joinSG(postUrlA, "/"); + $(postCfg, "date")[10] = 0; + cleanCharP(s) = formatS("<li>\n" + " <span class=\"post-meta\">%s</span>\n" + "\n" + " <h2>\n" + " <a class=\"post-link\" href=\"%s\">%s</a>\n" + " </h2>\n" + "</li>\n" + ,$(postCfg, "date"), postUrl, $(postCfg, "title")); + pushG(postsIndex, s); + } + //cleanCharP(postsIndexS) = joinSG(postsIndex, "\n"); + //lv(postsIndexS); + + iter(indexf, L) { + castS(l,L); + if (hasG(l, "{{ posts }}")) { + delElemG(indexf, iterIndexG(indexf)); + insertNFreeG(indexf, iterIndexG(indexf), dupG(postsIndex)); + break; + } + } + + cleanCharP(layoutFile) = catS(layoutDir,"/",$(indexCfg, "layout"),".html"); + + //lv(layoutFile); + + var index = preprocess(layoutFile); + + iter(index, L) { + castS(l,L); + if (hasG(l, content)) { + delElemG(index, iterIndexG(index)); + insertNFreeG(index, iterIndexG(index), dupG(indexf)); + break; + } + } + + //logG(index); + + // create configuation for index + + /* // TODO list all md files in dir (the pages) */ + /* cleanSmallArrayP(pages) = readDirG(rtSmallArrayt, "."); */ + /* iter(pages, L) { */ + /* castS(l,L); */ + /* if (not endsWithG(l, ".md")) { */ + /* delElemG(pages, iterIndexG(pages)); */ + /* } */ + /* } */ + /* lv(pages); */ + + cleanAllocateSmallArray(about); + cleanAllocateSmallJson(aboutCfg); + readFileWithFrontMatter("about.md", aboutCfg, about); + + // page list on first line on all pages + char *sitePages = catS("<a class=\"page-link\" href=\"", $(cfg, "baseurl"), $(aboutCfg, "permalink"), "\">", $(aboutCfg, "title"), "</a>"); + + cleanAllocateSmallDict(pageValues); + + setNFreeG(pageValues, "_\%title", getNDupO(cfg, "title")); + setNFreeG(pageValues, "_\%description", getNDupO(cfg, "description")); + setNFreeG(pageValues, "_\%site.description", getNDupO(cfg, "description")); + setNFreeG(pageValues, "_\%css", catS($(cfg, "baseurl"), "/css/main.css")); + setNFreeG(pageValues, "_\%url", catS($(cfg, "url"), $(cfg, "baseurl"))); + setNFreeG(pageValues, "_\%site.title", getNDupO(cfg, "title")); + setNFreeG(pageValues, "_\%feed", catS($(cfg, "url"), $(cfg, "baseurl"), "/feed.xml")); + setNFreeG(pageValues, "_\%baseurl", getNDupO(cfg, "baseurl")); + setNFreeG(pageValues, "_\%site.pages", sitePages); + 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")); + + //lv(pageValues); + + simpleTemplatesReplaceKeysWithValues(index, pageValues); + + //logG(index); + + // save index + writeFileG(index, siteDir"/index.html"); + + + // generate about page + generateAPageOrAPost("about.md", cfg, null/*postsFeed*/); + + // generate posts + cleanAllocateSmallArray(postsFeed); + + int count = 0; + iterLast(postsDir, L) { + castS(l,L); + // collect html code for 10 last post for feed.xml + generateAPageOrAPost(ssGet(l), cfg, count++ < 10 ? postsFeed : null); + } + + // generate feed.xml + cleanAllocateSmallArray(feed); + cleanAllocateSmallJson(feedCfg); + readFileWithFrontMatter("feed.xml", feedCfg, feed); + + //lv(feedCfg); + //lv(feed); + + iter(feed, L) { + castS(l,L); + if (hasG(l, "{{ posts }}")) { + delElemG(feed, iterIndexG(feed)); + insertNFreeG(feed, iterIndexG(feed), dupG(postsFeed)); + break; + } + } + + setNFreeG(pageValues, "_\%date", getCurrentDate()); + simpleTemplatesReplaceKeysWithValues(feed, pageValues); + + writeFileG(feed, siteDir"/feed.xml"); + + // clean tmp files + if (isPath("tmp.html")) rmAll("tmp.html"); + if (isPath("tmp.md")) rmAll("tmp.md"); +} + + +/** + * read file filename with front matter and text (any type of text) + * + * \return + * kv: keys and values for yml code in front matter + * lines: lines in file filename without front matter + */ +bool readFileWithFrontMatter(char *filename, smallJsont *kv, smallArrayt *lines) { + int frontMatterStart = -1, frontMatterEnd = -1; + + // Steps + // find front matter start and end + // parse yml code in front matter + // remove front matter from lines + + readFileG(lines, filename); + + // find front matter start and end + char *status = "start"; + iter(lines, L) { + castS(l,L); + if (eqS(status, "end") and (startsWithG(l, "---"))) { + frontMatterEnd = iterIndexG(lines); + break; + } + if (eqS(status, "start") and (startsWithG(l, "---"))) { + frontMatterStart = iterIndexG(lines)+1; + status = "end"; + } + } + + // parse yml code in front matter + var frontMatter = copyRngG(lines, frontMatterStart, frontMatterEnd); + + //lv(frontMatter); + + // TODO change to: parseYMLG(indexCfg, frontMatter); + // when parseYMLG accepts smallArrays + cleanCharP(fm) = joinSG(frontMatter, "\n"); + parseYMLG(kv, fm); + + //lv(indexCfg); + finishG(frontMatter); + + // remove front matter from lines + sliceG(lines, frontMatterEnd+1, 0); + //lv(lines); + + ret true; +} + +/** + * convert post filename ("2020-06-09-getting-started" without markdown) + * to url for linking posts easily in the blog. + * + * The url format is "/category/YYYY/MM/DD/filename.html" + */ +char *postToUrl(char *filename) { + cleanCharP(fn) = catS("_posts/",filename,".markdown"); + + cleanAllocateSmallArray(pf); + cleanAllocateSmallJson(pCfg); + readFileWithFrontMatter(fn, pCfg, pf); + + cleanCharP(postHtmlFile) = copyRngG(fn, 18, 0); + replaceG(&postHtmlFile, ".markdown", ".html", 1); + + cleanCharP(postDate) = copyRngG($(pCfg, "date"), 0, 10); + replaceG(&postDate, "-", "/", 0); + char *pUrl = catS("/",$(pCfg,"categories"),"/",postDate,"/",postHtmlFile); + + ret pUrl; +} + + +bool generateAPageOrAPost(char *filename, smallJsont *cfg, smallArrayt *postsFeed) { + + // Steps + // check if filename exists + // parse front matter yml + // convert markdown to html + // add class in <code> + // process {% post_url 2015-12-22-test %} + // add syntax highlighting + // insert html in layout template + // add layout and html code in sub layout + // TODO list pages on first line of all pages + // configure destination path, post url, date and description + // replace keys with values + // generate feed + // write post html page + + // check if filename exists + if (not isPath(filename)) { + logE("%s not found", filename); + ret false; + } + + // parse front matter yml + cleanAllocateSmallArray(pf); + cleanAllocateSmallJson(pCfg); + readFileWithFrontMatter(filename, pCfg, pf); + + //lv(pf); + + // convert markdown to html + writeFileG(pf, "tmp.md"); + + // TODO get path once and reuse + cleanCharP(progPath) = shDirname(getProgPath()); + commandf("%s/shpPackages/md2html/md2html --full-html --ftables --fstrikethrough tmp.md --output=tmp.html", progPath); + + cleanAllocateSmallArray(pHtml); + readFileG(pHtml, "tmp.html"); + + // keep only html code between the body tags + int bodyStart = -1, bodyEnd = -1; + iter(pHtml, L) { + castS(l,L); + if (hasG(l, "<body>")) bodyStart = iterIndexG(pHtml)+1; + if (hasG(l, "</body>")) { + bodyEnd = iterIndexG(pHtml); + break; + } + } + sliceG(pHtml, bodyStart, bodyEnd); + + // add class in <code> + // process {% post_url 2015-12-22-test %} + // add syntax highlighting + enum {searchCode, bash, javascript, python, html, coffeescript}; + int status = searchCode; + int lastCodeHighlightingLine = -1; + iter(pHtml, L) { + castS(l,L); + // process {% post_url 2015-12-22-running-rocket-chat %} + if (hasG(l, "{\% post_url ")) { + char *startp = hasG(l, "{\% post_url "); + char *endp = hasG(l, " \%}"); + int start = startp - ssGet(l); + int end = endp - ssGet(l); + // post filename + char *postFilemane = startp + strlen("{\% post_url "); + *endp = 0; + //lv(postFilemane); + cleanCharP(url) = postToUrl(postFilemane); + delG(l, start, end+3); + insertG(l, start, url); + //lv(l); + } + // code highlighting + replaceG(l, "<code>", "<code class=\"highlighter-rouge\">", 0); + if (status == searchCode and hasG(l, "<pre><code class=\"language-")) { + lastCodeHighlightingLine = iterIndexG(pHtml); + } + if (status == searchCode and hasG(l, "<pre><code class=\"language-bash\">")) { + status = bash; + } + if (status == searchCode and hasG(l, "<pre><code class=\"language-javascript\">")) { + status = javascript; + } + if (status == searchCode and hasG(l, "<pre><code class=\"language-python\">")) { + status = python; + } + if (status == searchCode and hasG(l, "<pre><code class=\"language-html\">")) { + status = html; + } + if (status == searchCode and hasG(l, "<pre><code class=\"language-coffeescript\">")) { + status = coffeescript; + } + if (hasG(l, "</code></pre>")) { + if (status == searchCode) { + if (lastCodeHighlightingLine == -1) { + logE("line %d in html for "BLD"%s"RST", code highlighting not found.", iterIndexG(pHtml), &filename[18]); + } + else { + logE("line %d in html for "BLD"%s"RST", code highlighting not recognize: '%s'", iterIndexG(pHtml), &filename[18], $(pHtml, lastCodeHighlightingLine)); + } + } + else replaceG(l, "</code></pre>", "</code></pre></figure>", 0); + status = searchCode; + } + + // bash highlight + if (status == bash) { + replaceManyG(l, "<pre><code class=\"language-bash\">", "<figure class=\"highlight\"><pre><code class=\"language-bash\" data-lang=\"bash\">", + "cd ", "<span class=\"nb\">cd </span>", + "echo ", "<span class=\"nb\">echo </span>", + "set ", "<span class=\"nb\">set </span>"); + // detect comment + if (hasG(l, "#")) { + replaceG(l, "#", "<span class=\"c\">#", 1); + pushG(l, "</span>"); + } + // strings + if (not hasG(l, "<figure class=\"highlight\"><pre><code class=\"language-")) { + var c = countG(l, '\''); + if (c != 0 and (c & 1) == 0) { + #define highlightQuotes(q, qS, qE) procbegin\ + var len = lenG(l);\ + var qL = lenG(q);\ + cleanCharP(str) = malloc(len + ((strlen(qS) + strlen(qE)) * c/2) + 1);\ + char *ps = str;\ + char *ln = ssGet(l);\ + enum {qSearch, q1};\ + int status = qSearch;\ + range(i, len) {\ + if (eqIS(ln, q, i)) {\ + if (status == qSearch) {\ + status = q1;\ + strcpy(ps, qS);\ + ps += strlen(qS);\ + *ps++ = ln[i];\ + }\ + elif (status == q1) {\ + status = qSearch;\ + rangeFrom(n, i, i+qL) {\ + *ps++ = ln[n];\ + }\ + i += qL;\ + strcpy(ps, qE);\ + ps += strlen(qE);\ + }\ + }\ + else {\ + *ps++ = ln[i];\ + }\ + }\ + *ps = 0;\ + setValG(l, str);\ + procend + highlightQuotes("'", "<span class=\"s1\">", "</span>"); + } + } + } + + // javascript highlight + elif (status == javascript) { + replaceManyG(l, "<pre><code class=\"language-javascript\">", "<figure class=\"highlight\"><pre><code class=\"language-javascript\" data-lang=\"javascript\">", + "*", "<span class=\"o\">*</span>", + "var", "<span class=\"kd\">var</span>", + "function", "<span class=\"kd\">function</span>", + "return", "<span class=\"kd\">return</span>", + "delete", "<span class=\"kd\">delete</span>", + "new", "<span class=\"kd\">new</span>", + "this", "<span class=\"kd\">this</span>", + "true", "<span class=\"kd\">true</span>", + "false", "<span class=\"kd\">false</span>", + "export", "<span class=\"kd\">export</span>", + "Object", "<span class=\"nb\">Object</span>" + ); + // detect comment + if (hasG(l, "//") and not hasG(l, "://")) { + replaceG(l, "//", "<span class=\"c\">//", 1); + pushG(l, "</span>"); + } + // strings + if (not hasG(l, "<figure class=\"highlight\"><pre><code class=\"language-")) { + var c = countG(l, '\''); + if (c != 0 and (c & 1) == 0) { + highlightQuotes("'", "<span class=\"s1\">", "</span>"); + } + } + } + + // python highlighting + elif (status == python) { + replaceManyG(l, "<pre><code class=\"language-python\">", "<figure class=\"highlight\"><pre><code class=\"language-python\" data-lang=\"python\">", + "*", "<span class=\"o\">*</span>", + "for", "<span class=\"kd\">for</span>", + "print", "<span class=\"kd\">print</span>", + "def", "<span class=\"kd\">def</span>", + "return", "<span class=\"kd\">return</span>", + "del", "<span class=\"kd\">del</span>", + "dict", "<span class=\"nb\">dict</span>", + "range", "<span class=\"nb\">range</span>", + "zip", "<span class=\"nb\">zip</span>" + ); + // detect comment + if (hasG(l, "#")) { + replaceG(l, "#", "<span class=\"c\">#", 1); + pushG(l, "</span>"); + } + // strings + if (not hasG(l, "<figure class=\"highlight\"><pre><code class=\"language-")) { + var c = countG(l, '\''); + if (c != 0 and (c & 1) == 0) { + highlightQuotes("'", "<span class=\"s\">", "</span>"); + } + } + } + + // html highlight + elif (status == html) { + // removed "class=", "<span class=\"na\">class=</span>", + // it causes conflicts + replaceManyG(l, "<pre><code class=\"language-html\">", "<figure class=\"highlight\"><pre><code class=\"language-html\" data-lang=\"html\">", + "&lt;script ", "<span class=\"nt\">&lt;script </span>", + "&lt;/script&gt;", "<span class=\"nt\">&lt;/script&gt;</span>", + "&lt;div", "<span class=\"nt\">&lt;div</span>", + "&lt;/div&gt;", "<span class=\"nt\">&lt;/div&gt;</span>", + "&lt;a", "<span class=\"nt\">&lt;a</span>", + "&lt;/a&gt;", "<span class=\"nt\">&lt;/a&gt;</span>", + "&lt;title&gt;", "<span class=\"nt\">&lt;title&gt;</span>", + "&lt;/title&gt;", "<span class=\"nt\">&lt;/title&gt;</span>", + "&lt;link", "<span class=\"nt\">&lt;link</span>", + "&lt;h1&gt;", "<span class=\"nt\">&lt;h1&gt;</span>", + "&lt;/h1&gt;", "<span class=\"nt\">&lt;/h1&gt;</span>", + "&lt;p&gt;", "<span class=\"nt\">&lt;p&gt;</span>", + "&lt;/p&gt;", "<span class=\"nt\">&lt;/p&gt;</span>", + "&lt;ul&gt;", "<span class=\"nt\">&lt;ul&gt;</span>", + "&lt;/ul&gt;", "<span class=\"nt\">&lt;/ul&gt;</span>", + "&lt;li&gt;", "<span class=\"nt\">&lt;li&gt;</span>", + "&lt;/li&gt;", "<span class=\"nt\">&lt;/li&gt;</span>", + "name=", "<span class=\"na\">name=</span>", + "type=", "<span class=\"na\">type=</span>", + "input=", "<span class=\"na\">input=</span>", + "style=", "<span class=\"na\">style=</span>", + "href=", "<span class=\"na\">href=</span>", + "src=", "<span class=\"na\">src=</span>" + ); + // strings + if (not hasG(l, "<figure class=\"highlight\"><pre><code class=\"language-")) { + var c = countG(l, "&quot;"); + if (c != 0 and (c & 1) == 0) { + highlightQuotes("&quot;", "<span class=\"s\">", "</span>"); + } + } + } + + // coffeescript highlight + elif (status == coffeescript) { + replaceManyG(l, "<pre><code class=\"language-coffeescript\">", "<figure class=\"highlight\"><pre><code class=\"language-coffeescript\" data-lang=\"coffeescript\">", + "*", "<span class=\"o\">*</span>", + "delete", "<span class=\"kd\">delete</span>", + "new", "<span class=\"kd\">new</span>", + "this", "<span class=\"kd\">this</span>", + "true", "<span class=\"kd\">true</span>", + "false", "<span class=\"kd\">false</span>", + "export", "<span class=\"kd\">export</span>", + "Object", "<span class=\"nb\">Object</span>" + ); + // detect comment + if (hasG(l, "#")) { + replaceG(l, "#", "<span class=\"c\">#", 1); + pushG(l, "</span>"); + } + // strings + if (not hasG(l, "<figure class=\"highlight\"><pre><code class=\"language-")) { + var c = countG(l, '\''); + if (c != 0 and (c & 1) == 0) { + highlightQuotes("'", "<span class=\"s1\">", "</span>"); + } + } + } + + setPG(pHtml, iterIndexG(pHtml), l); + } + + //lv(pHtml); + + // insert html in layout template + cleanCharP(layoutFile) = catS(layoutDir,"/",$(pCfg, "layout"),".html"); + + //lv(layoutFile); + + cleanAllocateSmallArray(layoutf); + cleanAllocateSmallJson(layoutCfg); + readFileWithFrontMatter(layoutFile, layoutCfg, layoutf); + + //lv(layoutf); + + // replace content with mardown generated html + + iter(layoutf, L) { + castS(l,L); + if (hasG(l, content)) { + delElemG(layoutf, iterIndexG(layoutf)); + insertNFreeG(layoutf, iterIndexG(layoutf), dupG(pHtml)); + break; + } + } + + // add layout and html code in sub layout + cleanCharP(subLayoutFile) = catS(layoutDir,"/",$(layoutCfg, "layout"),".html"); + + var p = preprocess(subLayoutFile); + + //logG(p); + + iter(p, L) { + castS(l,L); + if (hasG(l, content)) { + delElemG(p, iterIndexG(p)); + insertNFreeG(p, iterIndexG(p), dupG(layoutf)); + break; + } + } + + // TODO list pages on first line of all pages + cleanAllocateSmallArray(about); + cleanAllocateSmallJson(aboutCfg); + readFileWithFrontMatter("about.md", aboutCfg, about); + + char *sitePages = catS("<a class=\"page-link\" href=\"", $(cfg, "baseurl"), $(aboutCfg, "permalink"), "\">", $(aboutCfg, "title"), "</a>"); + + // configure destination path, post url, date and description + cleanCharP(dst) = null; + char *pUrl = null; + baset *description = null; + if (startsWithG(filename, "_posts/")) { + cleanCharP(postHtmlFile) = copyRngG(filename, 18, 0); + replaceG(&postHtmlFile, ".markdown", ".html", 1); + + cleanCharP(postDate) = copyRngG($(pCfg, "date"), 0, 10); + replaceG(&postDate, "-", "/", 0); + dst = catS(siteDir,"/",$(pCfg, "categories"),"/",postDate,"/",postHtmlFile); + pUrl = catS($(cfg, "url"),$(cfg, "baseurl"),"/", + $(pCfg,"categories"),"/",postDate,"/",postHtmlFile); + $(pCfg, "date")[10] = 0; + + iter(pf, L) { + castS(l,L); + if (not isBlankG(l)) { + description = (baset*) dupG(l); + break; + } + } + replaceManyG((smallStringt*)description, "\"", "", "'", "", "`", ""); + //lv(description); + } + else { + dst = catS(siteDir, $(pCfg, "permalink"), "index.html"); + pUrl = catS($(cfg, "url"), $(cfg, "baseurl"), $(pCfg, "permalink")); + description = getNDupO(cfg, "description"); + } + + // replace keys with values + cleanAllocateSmallDict(pageValues); + + setNFreeG(pageValues, "_\%title", getNDupO(pCfg, "title")); + setNFreeG(pageValues, "_\%description", description); + setNFreeG(pageValues, "_\%site.description", getNDupO(cfg, "description")); + setNFreeG(pageValues, "_\%css", catS($(cfg, "baseurl"), "/css/main.css")); + setNFreeG(pageValues, "_\%url", pUrl); + setNFreeG(pageValues, "_\%site.title", getNDupO(cfg, "title")); + setNFreeG(pageValues, "_\%feed", catS($(cfg, "url"), $(cfg, "baseurl"), "/feed.xml")); + setNFreeG(pageValues, "_\%baseurl", getNDupO(cfg, "baseurl")); + setNFreeG(pageValues, "_\%site.pages", sitePages); + 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/")) { + setG(pageValues, "_\%date", $(pCfg, "date")); + } + + //lv(pageValues); + + simpleTemplatesReplaceKeysWithValues(p, pageValues); + + // generate feed + if (postsFeed) { + cleanCharP(postContent) = joinSG(pHtml, '\n'); + iReplaceManyS(&postContent, "<", "&lt;", + ">", "&gt;", + "\"", "&quot;"); + pushNFreeG(postsFeed, formatS( + "<item>\n" + " <title>%s</title>\n" + " <description>%s</description>\n" + " <pubDate>%s</pubDate>\n" + " <link>%s</link>\n" + " <guid isPermaLink=\"true\">%s</guid>\n" + " <category>%s</category>\n" + "</item>\n", + $(pCfg, "title"), postContent, $(pCfg, "date"), $(pageValues, "_\%url"), $(pageValues, "_\%url"), + $(pCfg, "categories"))); + } + + //logG(p); + + // write post html page + cleanCharP(dstDir) = shDirname(dst); + + //lv(dst); + //lv(dstDir); + + if (not isPath(dstDir)) { + mkdirParents(dstDir); + } + + writeFileG(p, dst); + + ret true; +} + +// vim: set expandtab ts=2 sw=2: diff --git a/package.yml b/package.yml @@ -0,0 +1,22 @@ +--- + name: forb + version: 0.0.2 + description: static blog generator with configuration files inspired by jekyll + bin: ./forb.c + repository: + type: git + url: "git+https://noulin.net/git/forb.git" + keywords: + - command + - generator + - static + - html + author: Remy Noulin + license: MIT + homepage: "https://noulin.net/forb" + dependencies: + short: "" + preprocessor: "" + simpleTemplates: "" + md2html: "" + private: false diff --git a/shpPackages/preprocessor/LICENSE b/shpPackages/preprocessor/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Remy Noulin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/shpPackages/preprocessor/package.yml b/shpPackages/preprocessor/package.yml @@ -0,0 +1,15 @@ +--- + name: preprocessor + version: 0.0.6 + description: simple preprocessor for any type of file + bin: ./preprocessor.c + repository: + type: git + url: "git+https://noulin.net/git/preprocessor.git" + keywords: + - utility + author: Remy + license: MIT + bugs: + url: "https://noulin.net/preprocessor/log.html" + homepage: "https://noulin.net/preprocessor/file/README.md.html" diff --git a/shpPackages/preprocessor/pp.txt b/shpPackages/preprocessor/pp.txt @@ -0,0 +1,21 @@ +#:define a +asd +asd +#:end + +#:include 'pp_inc.txt' + +#:a + +#:b=1 +#:c=0 + +#:b +wewef +#:else +no output +#:end + +#:not c +c is 0 +#:end diff --git a/shpPackages/preprocessor/ppCommentConfig.txt b/shpPackages/preprocessor/ppCommentConfig.txt @@ -0,0 +1,19 @@ +/*: generator/pp comment config */ + +Define: +/*:define wefw*/ +wefwef +/*:end */ + +/*:include 'pp_inc.txt' */ + +/*:b=1*/ + +/*:not b */ +b=0 +/*:else*/ +b=1 +/*:end*/ + +Copy: +/*:wefw*/ diff --git a/shpPackages/preprocessor/pp_inc.txt b/shpPackages/preprocessor/pp_inc.txt @@ -0,0 +1,3 @@ +included txt + +line 3 diff --git a/shpPackages/preprocessor/preprocessor.c b/shpPackages/preprocessor/preprocessor.c @@ -0,0 +1,296 @@ + +#include "libsheepyObject.h" + +char *commentConfig = NULL; +char *commentEndConfig = NULL; + +/* ------------------------------------------------------------------------------------- + * set comment config + */ +internal void setCommentConfig(smallArrayt *input_lines) { + forEachSmallArray(input_lines, L) { + castS(l, L); + if (hasG(l, "generator/pp comment config")) { + smallStringt *lt = trimG(dupG(l)); + smallArrayt *la = splitG(lt, ": generator/pp comment config"); + commentConfig = getNDupG(la, rtChar, 0); + commentEndConfig = trimG(getNDupG(la, rtChar, 1)); + } + finishG(l); + } + if (not commentConfig) { + commentConfig = strdup("#"); + } +} + +internal smallArrayt *includePass(smallArrayt *input_lines, char *script_path) { + createAllocateSmallArray(lines); + + char *include = catS(commentConfig, ":include"); + + // Include files + forEachSmallArray(input_lines, L) { + castS(l, L); + smallStringt *l2 = dupG(l); + lowerG(l2); + if (hasG(l2, include) and hasG(l2, "'")) { + smallArrayt *a = splitG(l, "'"); + //putsG(l); + //logVarG(a); + char *file_to_include = catS(script_path, "/", getG(a, rtChar, 1)); + //logVarG(file_to_include); + createAllocateSmallArray(file); + readFileG(file, file_to_include); + appendNSmashG(lines, file); + terminateG(a); + } + else { + pushG(lines, l); + } + terminateG(l2); + finishG(l); + } + free(include); + smashG(input_lines); + return lines; +} + +// Search and replace defines +internal smallArrayt *definePass(smallArrayt *input_lines) { + createAllocateSmallArray(lines); + + char *define = catS(commentConfig, ":define "); + char *end = catS(commentConfig, ":end"); + char *ppMark = catS(commentConfig, ":"); + + // Assign values to defines + createAllocateSmallDict(defines); + char *status = NULL; + char *name = NULL; + forEachSmallArray(input_lines, L) { + castS(l, L); + //logVarG(l); + //logVarG(status); + if (eqG(status, "define code")) { + if (startsWithG(l, end)) { + status = "no define"; + } + else { + smallArrayt *def = getG(defines, rtSmallArrayt, name); + pushNFreeG(def, dupG(l)); + setPG(defines, name, def); + } + } + if (hasG(l, define)) { + smallArrayt *spl = splitG(l, define); + free(name); + name = getNDupG(spl, rtChar, 1); + replaceG(&name, commentEndConfig, "", 1); + terminateG(spl); + createAllocateSmallArray(defineCode); + setNFreeG(defines, name, defineCode); + status = "define code"; + } + finishG(l); + } + free(name); + //logVarG(defines); + + // cg: replace define with value. + char *statusDefine = "no define"; + forEachSmallArray(input_lines, L) { + castS(l, L); + //logVarG(l); + //logVarG(status); + //logVarG(statusDefine); + if (not startsWithG(l, ppMark)) { + pushG(lines, l); + } + else { + if (eqG(l, ppMark)) { + pushG(lines, l); + finishG(l); + continue; + } + status = "keep line"; + char *def = strdup(ssGet(l) + lenG(commentConfig) + 1); + replaceG(&def, commentEndConfig, "", 1); + trimG(&def); + if (hasG(defines, def)) { + status = "remove line"; + } + if (not hasG(l, define) and ((not startsWithG(l, end)) or (startsWithG(l, end) and (eqG(statusDefine, "no define")))) and eqG(status, "keep line")) { + pushG(lines, l); + } + if (hasG(defines, def)) { + status = "remove line"; + smallArrayt *defCode = getNDupG(defines, rtSmallArrayt, def); + //logVarG(def); + //logVarG(defCode); + appendNSmashG(lines, defCode); + } + if (startsWithG(l, end) and eqG(statusDefine, "define code")) { + statusDefine = "no define"; + } + if (startsWithG(l, define)) { + statusDefine = "define code"; + } + } + finishG(l); + } + smashG(input_lines); + terminateG(defines); + freeManyS(define, end, ppMark); + //logG(lines); + return lines; +} + +// Search tags +// Process tags +internal smallArrayt *tagPass(smallArrayt *input_lines) { + createAllocateSmallArray(lines); + + char *ppMark = catS(commentConfig, ":"); + + // Assign values to tags + createAllocateSmallDict(tags); + forEachSmallArray(input_lines, L) { + castS(l, L); + if (startsWithG(l, ppMark) and hasG(l, "=")) { + smallStringt *l2 = dupG(l); + replaceG(l2, commentEndConfig, "", 0); + delG(l2, 0, lenG(commentConfig)+1); + smallArrayt *spl = splitG(l2, "="); + terminateG(l2); + enumerateSmallArray(spl, T, i) { + castS(t, T); + setPG(spl, i, trimG(t)); + finishG(t); + } + char *s = getG(spl, rtChar, 1); + setG(tags, getG(spl, rtChar, 0), s); + terminateG(spl); + } + finishG(l); + } + //logVarG(tags); + + // cg: include or remove lines depending on tag value. + char *status = "no tag"; + char *Yes, *No, *changeStatus; + // support for inner tags in else closes + createAllocateSmallArray(innerTags); + forEachSmallArray(input_lines, L) { + castS(l, L); + //logVarG(status); + //logVarG(innerTags); + //logVarG(l); + //put; + if (startsWithG(l, ppMark) and (not hasG(l, "="))) { + smallStringt *tag = dupG(l); + delG(tag, 0, lenG(commentConfig)+1); + replaceG(tag, commentEndConfig, "", 1); + trimG(tag); + if (eqG(tag, "end")) { + delG(innerTags, -1, 0); + status = "no tag"; + } + else if (not hasG(l, "generator/pp comment config")) { + if (eqG(tag, "else")) { + if (eqG(status, "keep lines")) { + status = "remove lines"; + } + else { + if (lenG(innerTags) > 1) { + if (eqG(getG(innerTags, rtChar, 0), "remove lines")) { + status = "keep lines"; + } + } + else { + status = "keep lines"; + } + } + } + else { + if (startsWithG(tag, "not ")) { + delG(tag, 0, 4); + Yes = "remove lines"; + No = "keep lines"; + } + else { + Yes = "keep lines"; + No = "remove lines"; + } + + if (lenG(innerTags) > 0) { + if (eqG(getG(innerTags, rtChar, 0), "remove lines") and eqG(status, "keep lines")) { + changeStatus = "yes"; + } + else { + changeStatus = "no"; + } + } + else { + changeStatus = "yes"; + } + if (eqG(changeStatus, "yes")) { + if (hasG(tags, ssGet(tag))) { + if (eqG(getG(tags, rtChar, ssGet(tag)), "0")) { + status = No; + } + else { + status = Yes; + } + } + else { + // tag doesnt exit, same as tag = 0 + status = No; + } + } + pushG(innerTags, status); + } + } + } + if (not eqG(status, "remove lines") and not startsWithG(l, ppMark)) { + pushG(lines, l); + } + finishG(l); + } + //logVarG(lines); + terminateG(innerTags); + smashG(input_lines); + terminateG(tags); + free(ppMark); + return lines; +} + +smallArrayt *preprocess(char *filename) { + createAllocateSmallArray(lines); + + if (!fileExists(filename)) { + printf("File not found: %s", filename); + } + + char *dir = shDirnameG(filename); + //logVarG(dir); + + readFileG(lines, filename); + + setCommentConfig(lines); + //logVarG(commentConfig); + //logVarG(commentEndConfig); + + lines = includePass(lines, dir); + lines = definePass(lines); + lines = tagPass(lines); + + free(dir); + freen(commentConfig); + freen(commentEndConfig); + return lines; +} + +bool checkLibsheepyVersionPreprocessor(const char *currentLibsheepyVersion) { + return eqG(currentLibsheepyVersion, LIBSHEEPY_VERSION); +} + diff --git a/shpPackages/preprocessor/preprocessor.h b/shpPackages/preprocessor/preprocessor.h @@ -0,0 +1,7 @@ + +#include "libsheepyObject.h" + +smallArrayt *preprocess(char *filename); + +#define isPreprocessorCompiledWithCurrentLisheepyVersion checkLibsheepyVersionPreprocessor(LIBSHEEPY_VERSION) +bool checkLibsheepyVersionPreprocessor(const char *currentLibsheepyVersion); diff --git a/shpPackages/preprocessor/preprocessorTest.c b/shpPackages/preprocessor/preprocessorTest.c @@ -0,0 +1,18 @@ +#! /usr/bin/env sheepy + +#include "libsheepyObject.h" +#include "preprocessor.h" + +int main(int ARGC, char** ARGV) { + + smallArrayt *a; + puts(BLD WHT "pp.txt" RST); + a = preprocess("pp.txt"); + logG(a); + terminateG(a); + + puts(BLD WHT "ppCommentConfig.txt" RST); + a = preprocess("ppCommentConfig.txt"); + logG(a); + terminateG(a); +} diff --git a/shpPackages/short/main.c b/shpPackages/short/main.c @@ -0,0 +1,29 @@ +#! /usr/bin/env sheepy +/* or direct path to sheepy: #! /usr/local/bin/sheepy */ + +/* Libsheepy documentation: http://spartatek.se/libsheepy/ */ +#include "libsheepyObject.h" +#include "short.h" + +int argc; char **argv; + +/* enable/disable logging */ +/* #undef pLog */ +/* #define pLog(...) */ + +int main(int ARGC, char** ARGV) { + + argc = ARGC; argv = ARGV; + + initLibsheepy(ARGV[0]); + + createSmallDict(d); + + $(&d, "wer"); + + i_("de"); + + lv(&d); + +} +// vim: set expandtab ts=2 sw=2: diff --git a/shpPackages/short/package.yml b/shpPackages/short/package.yml @@ -0,0 +1,15 @@ +--- + name: short + version: 0.0.2 + description: "convenient defines for often used libsheepy function calls making the lines shorter and more readable" + bin: ./short.h + repository: + type: git + url: git+https://github.com/USER/short.git + keywords: + - utility + author: Remy + license: MIT + bugs: + url: https://github.com/USER/short/issues + homepage: https://github.com/USER/short#readme diff --git a/shpPackages/short/short.h b/shpPackages/short/short.h @@ -0,0 +1,12 @@ +#include "libsheepyObject.h" + +#define lv logVarG +#define i_ logI + +// get string from array or dict +#define $(object, key) getG(object, rtChar, key) + +// get unsigned int +#define u$(object, key) getG(object, rtU64, key) + +// vim: set expandtab ts=2 sw=2: diff --git a/shpPackages/simpleTemplates/LICENSE b/shpPackages/simpleTemplates/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Remy Noulin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/shpPackages/simpleTemplates/file.txt b/shpPackages/simpleTemplates/file.txt @@ -0,0 +1,24 @@ +test + +#! /usr/bin/env sheepy + +#include "libsheepyObject.h" +#include "simpleTemplates.h" + +int argc; char **argv; + +int main(int ARGC, char** ARGV) { + + argc = ARGC; argv = ARGV; + + initLibsheepy(argv[0]); + + createAllocateSmallDict(d); + + setG(d, "_INSERT_FILE_", "simpleTemplatesTest.c"); + + simpleTemplatesInsertFiles("templateFile.txt","file.txt", d); + + finalizeLibsheepy(); + +} diff --git a/shpPackages/simpleTemplates/package.yml b/shpPackages/simpleTemplates/package.yml @@ -0,0 +1,15 @@ +--- + name: simpleTemplates + version: 0.0.6 + description: Library for processing templates + bin: ./simpleTemplates.c + repository: + type: git + url: "git+https://noulin.net/git/simpleTemplates.git" + keywords: + - templates + author: Remy + license: MIT + bugs: + url: "https://noulin.net/simpleTemplates/log.html" + homepage: "https://noulin.net/simpleTemplates/file/README.md.html" diff --git a/shpPackages/simpleTemplates/simpleTemplates.c b/shpPackages/simpleTemplates/simpleTemplates.c @@ -0,0 +1,73 @@ +#include "libsheepyObject.h" + +bool simpleTemplatesInsertFiles(char *templ, char *dst, smallDictt *keyFiles) { + if (!fileExists(templ)) { + return false; + } + + // load templates + createAllocateSmallArray(template); + readFileG(template, templ); + //logVarG(template); + + forEachSmallDict(keyFiles, k, V) { + castS(v, V); + smallStringt *path = dupG(v); + expandHomeG(path); + if (!fileExistsG(path)) { + printf("File for key %s not found: %s\n", k, ssGet(v)); + return false; + } + createAllocateSmallString(s); + readFileG(s, ssGet(path)); + terminateG(path); + if (getG(s, unusedV , -1) == '\n') { + setG(s, -1, 0); + } + setG(keyFiles, k, s); + finishG(v); + } + listFreeS(libsheepyInternalKeys); + + char **keys = keysG(keyFiles); + + enumerateSmallArray(template, L, i) { + castS(l, L); + forEachS(keys, k) { + smallStringt *s = getG(keyFiles, rtSmallStringt, k); + replaceSO_max(l, k, ssGet(s)); + finishG(s); + } + setPG(template, i, l); + finishG(l); + } + freeG(keys); + + //logVarG(template); + + writeFileG(template, dst); + + terminateG(template); + return true; +} + + + + +bool simpleTemplatesReplaceKeysWithValues(smallArrayt *index, smallDictt *pageValues) { + iter(index, L) { + castS(l, L); + iterK(pageValues, k) { + replaceG(l, k, getG(pageValues, rtChar, k), 0); + } + setPG(index, iterIndexG(index), l); + } +} + + + + + +bool checkLibsheepyVersionSimpleTemplates(const char *currentLibsheepyVersion) { + return eqG(currentLibsheepyVersion, LIBSHEEPY_VERSION); +} diff --git a/shpPackages/simpleTemplates/simpleTemplates.h b/shpPackages/simpleTemplates/simpleTemplates.h @@ -0,0 +1,12 @@ + +#include "libsheepyObject.h" + + +// key values +bool simpleTemplatesInsertFiles(char *templ, char *dst, smallDictt *keyFiles); +// key file + +bool simpleTemplatesReplaceKeysWithValues(smallArrayt *index, smallDictt *pageValues); + +#define isSimpleTemplatesCompiledWithCurrentLisheepyVersion checkLibsheepyVersionSimpleTemplates(LIBSHEEPY_VERSION) +bool checkLibsheepyVersionSimpleTemplates(const char *currentLibsheepyVersion); diff --git a/shpPackages/simpleTemplates/simpleTemplatesTest.c b/shpPackages/simpleTemplates/simpleTemplatesTest.c @@ -0,0 +1,22 @@ +#! /usr/bin/env sheepy + +#include "libsheepyObject.h" +#include "simpleTemplates.h" + +int argc; char **argv; + +int main(int ARGC, char** ARGV) { + + argc = ARGC; argv = ARGV; + + initLibsheepy(argv[0]); + + createAllocateSmallDict(d); + + setG(d, "_INSERT_FILE_", "simpleTemplatesTest.c"); + + simpleTemplatesInsertFiles("templateFile.txt","file.txt", d); + + finalizeLibsheepy(); + +} diff --git a/shpPackages/simpleTemplates/templateFile.txt b/shpPackages/simpleTemplates/templateFile.txt @@ -0,0 +1,3 @@ +test + +_INSERT_FILE_ diff --git a/template/_config.yml b/template/_config.yml @@ -0,0 +1,14 @@ +# Site settings +title: Your awesome title +email: your-email@example.com +description: > # this means to ignore newlines until "baseurl:" + Write an awesome description for your new site here. You can edit this + line in _config.yml. It will appear in your document head meta (for + Google search results) and in your feed.xml site description. +baseurl: "/blog" # the subpath of your site, e.g. /blog +url: "http://example.com" # the base hostname & protocol for your site, e.g. https://example.com +twitter_username: your-twitter +github_username: your-github + +# Build settings +markdown: kramdown diff --git a/template/_includes/footer.html b/template/_includes/footer.html @@ -0,0 +1,51 @@ +<footer class="site-footer"> + + <div class="wrapper"> + + <h2 class="footer-heading">_%site.title</h2> + + <div class="footer-col-wrapper"> + <div class="footer-col footer-col-1"> + <ul class="contact-list"> + <li>_%site.title</li> + <li><a href="mailto:_%site.email">_%site.email</a></li> + </ul> + </div> + + <div class="footer-col footer-col-2"> + <ul class="social-media-list"> + <li> + <a href="https://github.com/_%site.github_username"> + <span class="icon icon--github"> + <svg viewBox="0 0 16 16"> + <path fill="#828282" d="M7.999,0.431c-4.285,0-7.76,3.474-7.76,7.761 c0,3.428,2.223,6.337,5.307,7.363c0.388,0.071,0.53-0.168,0.53-0.374c0-0.184-0.007-0.672-0.01-1.32 c-2.159,0.469-2.614-1.04-2.614-1.04c-0.353-0.896-0.862-1.135-0.862-1.135c-0.705-0.481,0.053-0.472,0.053-0.472 c0.779,0.055,1.189,0.8,1.189,0.8c0.692,1.186,1.816,0.843,2.258,0.645c0.071-0.502,0.271-0.843,0.493-1.037 C4.86,11.425,3.049,10.76,3.049,7.786c0-0.847,0.302-1.54,0.799-2.082C3.768,5.507,3.501,4.718,3.924,3.65 c0,0,0.652-0.209,2.134,0.796C6.677,4.273,7.34,4.187,8,4.184c0.659,0.003,1.323,0.089,1.943,0.261 c1.482-1.004,2.132-0.796,2.132-0.796c0.423,1.068,0.157,1.857,0.077,2.054c0.497,0.542,0.798,1.235,0.798,2.082 c0,2.981-1.814,3.637-3.543,3.829c0.279,0.24,0.527,0.713,0.527,1.437c0,1.037-0.01,1.874-0.01,2.129 c0,0.208,0.14,0.449,0.534,0.373c3.081-1.028,5.302-3.935,5.302-7.362C15.76,3.906,12.285,0.431,7.999,0.431z"/> + </svg> + </span> + + <span class="username">_%site.github_username</span> + </a> + </li> + + <li> + <a href="https://twitter.com/_%site.twitter_username"> + <span class="icon icon--twitter"> + <svg viewBox="0 0 16 16"> + <path fill="#828282" d="M15.969,3.058c-0.586,0.26-1.217,0.436-1.878,0.515c0.675-0.405,1.194-1.045,1.438-1.809 + c-0.632,0.375-1.332,0.647-2.076,0.793c-0.596-0.636-1.446-1.033-2.387-1.033c-1.806,0-3.27,1.464-3.27,3.27 c0,0.256,0.029,0.506,0.085,0.745C5.163,5.404,2.753,4.102,1.14,2.124C0.859,2.607,0.698,3.168,0.698,3.767 c0,1.134,0.577,2.135,1.455,2.722C1.616,6.472,1.112,6.325,0.671,6.08c0,0.014,0,0.027,0,0.041c0,1.584,1.127,2.906,2.623,3.206 C3.02,9.402,2.731,9.442,2.433,9.442c-0.211,0-0.416-0.021-0.615-0.059c0.416,1.299,1.624,2.245,3.055,2.271 c-1.119,0.877-2.529,1.4-4.061,1.4c-0.264,0-0.524-0.015-0.78-0.046c1.447,0.928,3.166,1.469,5.013,1.469 c6.015,0,9.304-4.983,9.304-9.304c0-0.142-0.003-0.283-0.009-0.423C14.976,4.29,15.531,3.714,15.969,3.058z"/> + </svg> + </span> + + <span class="username">_%site.twitter_username</span> + </a> + </li> + </ul> + </div> + + <div class="footer-col footer-col-3"> + <p class="text">_%site.description</p> + </div> + </div> + + </div> + +</footer> diff --git a/template/_includes/head.html b/template/_includes/head.html @@ -0,0 +1,12 @@ +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <title>_%title</title> + <meta name="description" content="_%description"> + + <link rel="stylesheet" href="_%css"> + <link rel="canonical" href="_%url"> + <link rel="alternate" type="application/rss+xml" title="_%site.title" href="_%feed" /> +</head> diff --git a/template/_includes/header.html b/template/_includes/header.html @@ -0,0 +1,23 @@ +<header class="site-header"> + + <div class="wrapper"> + + <a class="site-title" href="_%baseurl/">_%site.title</a> + + <nav class="site-nav"> + <a href="#" class="menu-icon"> + <svg viewBox="0 0 18 15"> + <path fill="#424242" d="M18,1.484c0,0.82-0.665,1.484-1.484,1.484H1.484C0.665,2.969,0,2.304,0,1.484l0,0C0,0.665,0.665,0,1.484,0 h15.031C17.335,0,18,0.665,18,1.484L18,1.484z"/> + <path fill="#424242" d="M18,7.516C18,8.335,17.335,9,16.516,9H1.484C0.665,9,0,8.335,0,7.516l0,0c0-0.82,0.665-1.484,1.484-1.484 h15.031C17.335,6.031,18,6.696,18,7.516L18,7.516z"/> + <path fill="#424242" d="M18,13.516C18,14.335,17.335,15,16.516,15H1.484C0.665,15,0,14.335,0,13.516l0,0 c0-0.82,0.665-1.484,1.484-1.484h15.031C17.335,12.031,18,12.696,18,13.516L18,13.516z"/> + </svg> + </a> + + <div class="trigger"> + _%site.pages + </div> + </nav> + + </div> + +</header> diff --git a/template/_layouts/default.html b/template/_layouts/default.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + + #:include '../_includes/head.html' + + <body> + + #:include '../_includes/header.html' + + <div class="page-content"> + <div class="wrapper"> + {{ content }} + </div> + </div> + + #:include '../_includes/footer.html' + + </body> + +</html> diff --git a/template/_layouts/page.html b/template/_layouts/page.html @@ -0,0 +1,14 @@ +--- +layout: default +--- +<div class="post"> + + <header class="post-header"> + <h1 class="post-title">_%title</h1> + </header> + + <article class="post-content"> + {{ content }} + </article> + +</div> diff --git a/template/_layouts/post.html b/template/_layouts/post.html @@ -0,0 +1,15 @@ +--- +layout: default +--- +<div class="post"> + + <header class="post-header"> + <h1 class="post-title">_%title</h1> + <p class="post-meta">_%date</p> + </header> + + <article class="post-content"> + {{ content }} + </article> + +</div> diff --git a/template/_posts/2020-06-09-getting-started.markdown b/template/_posts/2020-06-09-getting-started.markdown @@ -0,0 +1,11 @@ +--- +layout: post +title: "Getting started with forb" +date: 2020-06-09 13:00:10 +categories: forb +--- + +Forb is blogging system using configurations similar to [jekyll](https://jekyllrb.com). + +- Run `forb new` to copy the default template to the current directory +- Run `forb` to generate the site (saved under the `_site` directory) diff --git a/template/about.md b/template/about.md @@ -0,0 +1,10 @@ +--- +layout: page +title: About +permalink: /about/ +--- + +This is forb's default template. + +You can find the source code for forb at: +[forb git](https://noulin.net/forb) diff --git a/template/css/main.css b/template/css/main.css @@ -0,0 +1,449 @@ +/** + * Reset some basic elements + */ +body, h1, h2, h3, h4, h5, h6, +p, blockquote, pre, hr, +dl, dd, ol, ul, figure { + margin: 0; + padding: 0; } + +/** + * Basic styling + */ +body { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 1.5; + font-weight: 300; + color: #111; + background-color: #fdfdfd; + -webkit-text-size-adjust: 100%; } + +/** + * Set `margin-bottom` to maintain vertical rhythm + */ +h1, h2, h3, h4, h5, h6, +p, blockquote, pre, +ul, ol, dl, figure, +.highlight { + margin-bottom: 15px; } + +/** + * Images + */ +img { + max-width: 100%; + vertical-align: middle; } + +/** + * Figures + */ +figure > img { + display: block; } + +figcaption { + font-size: 14px; } + +/** + * Lists + */ +ul, ol { + margin-left: 30px; } + +li > ul, +li > ol { + margin-bottom: 0; } + +/** + * Headings + */ +h1, h2, h3, h4, h5, h6 { + font-weight: 300; } + +/** + * Links + */ +a { + color: #2a7ae2; + text-decoration: none; } + a:visited { + color: #1756a9; } + a:hover { + color: #111; + text-decoration: underline; } + +/** + * Blockquotes + */ +blockquote { + color: #828282; + border-left: 4px solid #e8e8e8; + padding-left: 15px; + font-size: 18px; + letter-spacing: -1px; + font-style: italic; } + blockquote > :last-child { + margin-bottom: 0; } + +/** + * Code formatting + */ +pre, +code { + font-size: 15px; + border: 1px solid #e8e8e8; + border-radius: 3px; + background-color: #eef; } + +code { + padding: 1px 5px; } + +pre { + padding: 8px 12px; + overflow-x: scroll; } + pre > code { + border: 0; + padding-right: 0; + padding-left: 0; } + +/** + * Wrapper + */ +.wrapper { + max-width: -webkit-calc(800px - (30px * 2)); + max-width: calc(800px - (30px * 2)); + margin-right: auto; + margin-left: auto; + padding-right: 30px; + padding-left: 30px; } + @media screen and (max-width: 800px) { + .wrapper { + max-width: -webkit-calc(800px - (30px)); + max-width: calc(800px - (30px)); + padding-right: 15px; + padding-left: 15px; } } + +/** + * Clearfix + */ +.wrapper:after, .footer-col-wrapper:after { + content: ""; + display: table; + clear: both; } + +/** + * Icons + */ +.icon > svg { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; } + .icon > svg path { + fill: #828282; } + +/** + * Site header + */ +.site-header { + border-top: 5px solid #424242; + border-bottom: 1px solid #e8e8e8; + min-height: 56px; + position: relative; } + +.site-title { + font-size: 26px; + line-height: 56px; + letter-spacing: -1px; + margin-bottom: 0; + float: left; } + .site-title, .site-title:visited { + color: #424242; } + +.site-nav { + float: right; + line-height: 56px; } + .site-nav .menu-icon { + display: none; } + .site-nav .page-link { + color: #111; + line-height: 1.5; } + .site-nav .page-link:not(:first-child) { + margin-left: 20px; } + @media screen and (max-width: 600px) { + .site-nav { + position: absolute; + top: 9px; + right: 30px; + background-color: #fdfdfd; + border: 1px solid #e8e8e8; + border-radius: 5px; + text-align: right; } + .site-nav .menu-icon { + display: block; + float: right; + width: 36px; + height: 26px; + line-height: 0; + padding-top: 10px; + text-align: center; } + .site-nav .menu-icon > svg { + width: 18px; + height: 15px; } + .site-nav .menu-icon > svg path { + fill: #424242; } + .site-nav .trigger { + clear: both; + display: none; } + .site-nav:hover .trigger { + display: block; + padding-bottom: 5px; } + .site-nav .page-link { + display: block; + padding: 5px 10px; } } + +/** + * Site footer + */ +.site-footer { + border-top: 1px solid #e8e8e8; + padding: 30px 0; } + +.footer-heading { + font-size: 18px; + margin-bottom: 15px; } + +.contact-list, +.social-media-list { + list-style: none; + margin-left: 0; } + +.footer-col-wrapper { + font-size: 15px; + color: #828282; + margin-left: -15px; } + +.footer-col { + float: left; + margin-bottom: 15px; + padding-left: 15px; } + +.footer-col-1 { + width: -webkit-calc(35% - (30px / 2)); + width: calc(35% - (30px / 2)); } + +.footer-col-2 { + width: -webkit-calc(20% - (30px / 2)); + width: calc(20% - (30px / 2)); } + +.footer-col-3 { + width: -webkit-calc(45% - (30px / 2)); + width: calc(45% - (30px / 2)); } + +@media screen and (max-width: 800px) { + .footer-col-1, + .footer-col-2 { + width: -webkit-calc(50% - (30px / 2)); + width: calc(50% - (30px / 2)); } + + .footer-col-3 { + width: -webkit-calc(100% - (30px / 2)); + width: calc(100% - (30px / 2)); } } +@media screen and (max-width: 600px) { + .footer-col { + float: none; + width: -webkit-calc(100% - (30px / 2)); + width: calc(100% - (30px / 2)); } } +/** + * Page content + */ +.page-content { + padding: 30px 0; } + +.page-heading { + font-size: 20px; } + +.post-list { + margin-left: 0; + list-style: none; } + .post-list > li { + margin-bottom: 30px; } + +.post-meta { + font-size: 14px; + color: #828282; } + +.post-link { + display: block; + font-size: 24px; } + +/** + * Posts + */ +.post-header { + margin-bottom: 30px; } + +.post-title { + font-size: 42px; + letter-spacing: -1px; + line-height: 1; } + @media screen and (max-width: 800px) { + .post-title { + font-size: 36px; } } + +.post-content { + margin-bottom: 30px; } + .post-content h2 { + font-size: 32px; } + @media screen and (max-width: 800px) { + .post-content h2 { + font-size: 28px; } } + .post-content h3 { + font-size: 26px; } + @media screen and (max-width: 800px) { + .post-content h3 { + font-size: 22px; } } + .post-content h4 { + font-size: 20px; } + @media screen and (max-width: 800px) { + .post-content h4 { + font-size: 18px; } } + +/** + * Syntax highlighting styles + */ +.highlight { + background: #fff; } + .highlight .c { + color: #998; + font-style: italic; } + .highlight .err { + color: #a61717; + background-color: #e3d2d2; } + .highlight .k { + font-weight: bold; } + .highlight .o { + font-weight: bold; } + .highlight .cm { + color: #998; + font-style: italic; } + .highlight .cp { + color: #999; + font-weight: bold; } + .highlight .c1 { + color: #998; + font-style: italic; } + .highlight .cs { + color: #999; + font-weight: bold; + font-style: italic; } + .highlight .gd { + color: #000; + background-color: #fdd; } + .highlight .gd .x { + color: #000; + background-color: #faa; } + .highlight .ge { + font-style: italic; } + .highlight .gr { + color: #a00; } + .highlight .gh { + color: #999; } + .highlight .gi { + color: #000; + background-color: #dfd; } + .highlight .gi .x { + color: #000; + background-color: #afa; } + .highlight .go { + color: #888; } + .highlight .gp { + color: #555; } + .highlight .gs { + font-weight: bold; } + .highlight .gu { + color: #aaa; } + .highlight .gt { + color: #a00; } + .highlight .kc { + font-weight: bold; } + .highlight .kd { + font-weight: bold; } + .highlight .kp { + font-weight: bold; } + .highlight .kr { + font-weight: bold; } + .highlight .kt { + color: #458; + font-weight: bold; } + .highlight .m { + color: #099; } + .highlight .s { + color: #d14; } + .highlight .na { + color: #008080; } + .highlight .nb { + color: #0086B3; } + .highlight .nc { + color: #458; + font-weight: bold; } + .highlight .no { + color: #008080; } + .highlight .ni { + color: #800080; } + .highlight .ne { + color: #900; + font-weight: bold; } + .highlight .nf { + color: #900; + font-weight: bold; } + .highlight .nn { + color: #555; } + .highlight .nt { + color: #000080; } + .highlight .nv { + color: #008080; } + .highlight .ow { + font-weight: bold; } + .highlight .w { + color: #bbb; } + .highlight .mf { + color: #099; } + .highlight .mh { + color: #099; } + .highlight .mi { + color: #099; } + .highlight .mo { + color: #099; } + .highlight .sb { + color: #d14; } + .highlight .sc { + color: #d14; } + .highlight .sd { + color: #d14; } + .highlight .s2 { + color: #d14; } + .highlight .se { + color: #d14; } + .highlight .sh { + color: #d14; } + .highlight .si { + color: #d14; } + .highlight .sx { + color: #d14; } + .highlight .sr { + color: #009926; } + .highlight .s1 { + color: #d14; } + .highlight .ss { + color: #990073; } + .highlight .bp { + color: #999; } + .highlight .vc { + color: #008080; } + .highlight .vg { + color: #008080; } + .highlight .vi { + color: #008080; } + .highlight .il { + color: #099; } diff --git a/template/feed.xml b/template/feed.xml @@ -0,0 +1,16 @@ +--- +layout: null +--- +<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <title>_%title</title> + <description>_%description</description> + <link>_%url/</link> + <atom:link href="_%url/feed.xml" rel="self" type="application/rss+xml"/> + <pubDate>_%date</pubDate> + <lastBuildDate>_%date</lastBuildDate> + <generator>Forb</generator> + {{ posts }} + </channel> +</rss> diff --git a/template/index.html b/template/index.html @@ -0,0 +1,15 @@ +--- +layout: default +--- + +<div class="home"> + + <h1 class="page-heading">Posts</h1> + + <ul class="post-list"> + {{ posts }} + </ul> + + <p class="rss-subscribe">subscribe <a href="_%feed">via RSS</a></p> + +</div>