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:
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\">",
+ "<script ", "<span class=\"nt\"><script </span>",
+ "</script>", "<span class=\"nt\"></script></span>",
+ "<div", "<span class=\"nt\"><div</span>",
+ "</div>", "<span class=\"nt\"></div></span>",
+ "<a", "<span class=\"nt\"><a</span>",
+ "</a>", "<span class=\"nt\"></a></span>",
+ "<title>", "<span class=\"nt\"><title></span>",
+ "</title>", "<span class=\"nt\"></title></span>",
+ "<link", "<span class=\"nt\"><link</span>",
+ "<h1>", "<span class=\"nt\"><h1></span>",
+ "</h1>", "<span class=\"nt\"></h1></span>",
+ "<p>", "<span class=\"nt\"><p></span>",
+ "</p>", "<span class=\"nt\"></p></span>",
+ "<ul>", "<span class=\"nt\"><ul></span>",
+ "</ul>", "<span class=\"nt\"></ul></span>",
+ "<li>", "<span class=\"nt\"><li></span>",
+ "</li>", "<span class=\"nt\"></li></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, """);
+ if (c != 0 and (c & 1) == 0) {
+ highlightQuotes(""", "<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, "<", "<",
+ ">", ">",
+ "\"", """);
+ 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>