From f5f04edbf832aa2e219c621b9895c33dce3e47ba Mon Sep 17 00:00:00 2001
From: Danny van Kooten <dannyvankooten@users.noreply.github.com>
Date: Tue, 17 Mar 2020 16:12:55 +0100
Subject: first stab at supporting template inheritance

---
 src/template.c           | 68 ++++++++++++++++++++++++++++++++++++++++++++----
 src/template.h           |  5 ++++
 tests/data/01/base.tmpl  |  9 +++++++
 tests/data/01/child.tmpl |  5 ++++
 tests/test_template.c    |  8 ++++++
 5 files changed, 90 insertions(+), 5 deletions(-)
 create mode 100644 tests/data/01/base.tmpl
 create mode 100644 tests/data/01/child.tmpl

diff --git a/src/template.c b/src/template.c
index 7aa3a75..c43827a 100644
--- a/src/template.c
+++ b/src/template.c
@@ -1,6 +1,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <err.h>
+#include <unistd.h>
+#include <dirent.h>
 #include "vendor/mpc.h"
 #include "template.h"
 
@@ -26,6 +28,11 @@ struct buffer {
     char *string;
 };
 
+struct env {
+    struct hashmap *templates;
+};
+
+/* ensure buffer has room for a string sized l, grows buffer capacity if needed */
 void buffer_reserve(struct buffer *buf, int l) {
     int req_size = buf->size + l;
     if (req_size >= buf->cap) {
@@ -40,14 +47,44 @@ void buffer_reserve(struct buffer *buf, int l) {
     }
 }
 
+struct env *env_new(char *dirname) {
+    DIR *dr = opendir(dirname); 
+    if (dr == NULL) { 
+        err(EXIT_FAILURE, "could not open directory %s", dirname); 
+    } 
+  
+    struct env *env = malloc(sizeof *env);
+    env->templates = hashmap_new();
+    chdir(dirname);
+
+    struct dirent *de;   
+    while ((de = readdir(dr)) != NULL) {
+        // skip files starting with a dot
+        if (de->d_name[0] == '.') {
+            continue;
+        }
+
+        char *name = de->d_name;
+        char *tmpl = read_file(name);
+        hashmap_insert(env->templates, name, tmpl);
+    }
+  
+    closedir(dr);     
+    return env;
+}
+
+void env_free(struct env *env) {
+    // TODO: Free template strings
+    free(env);
+}
+
 char *read_file(char *filename) {
     char *input = malloc(BUFSIZ);
     unsigned int size = 0;
 
     FILE *f = fopen(filename, "r");
     if (!f) {
-        printf("Could not open \"%s\" for reading", filename);
-        exit(1);
+        err(EXIT_FAILURE, "Could not open \"%s\" for reading", filename);
     }
 
     unsigned int read = 0;
@@ -217,6 +254,20 @@ struct unja_object *eval_expression(mpc_ast_t* expr, struct hashmap *ctx) {
 int eval(struct buffer *buf, mpc_ast_t* t, struct hashmap *ctx) {
     static int trim_whitespace = 0;
 
+
+    if (strstr(t->tag, "content|statement|extends")) {
+        char *template_name = t->children[2]->children[1]->contents;
+        // TODO: Render given template instead
+        // but replace blocks with blocks in current template
+        return 0;
+    }
+
+
+    if (strstr(t->tag, "content|statement|block")) {
+        char *block_name = t->children[2]->contents;
+        return eval(buf, t->children[4], ctx);
+    }
+
     // eval print statement
     if (strstr(t->tag, "content|print")) {
         // maybe eat whitespace going backward
@@ -275,8 +326,11 @@ int eval(struct buffer *buf, mpc_ast_t* t, struct hashmap *ctx) {
         return 0;
     }
 
-    for (int i=0; i < t->children_num; i++) {
-        eval(buf, t->children[i], ctx);
+
+    if (strstr(t->tag, ">")) {
+        for (int i=0; i < t->children_num; i++) {
+            eval(buf, t->children[i], ctx);
+        }
     }
     
     return 0;
@@ -318,7 +372,7 @@ mpc_parser_t *parser_init() {
         " statement_open: \"{%\" /-? */;"
         " statement_close: / *-?/ \"%}\";"
         " for       : <statement_open> \"for \" <symbol> \"in\" <symbol> <statement_close> <body> <statement_open> \"endfor\" <statement_close> ;"
-        " block     : <statement_open> \"block \" <symbol> <statement_close> <statement_open> \"endblock\" <statement_close>;"
+        " block     : <statement_open> \"block \" <symbol> <statement_close> <body> <statement_open> \"endblock\" <statement_close>;"
         " extends   : <statement_open> \"extends \" <string> <statement_close>;"
         " if        : <statement_open> \"if \" <expression> <statement_close> <body> (<statement_open> \"else\" <statement_close> <body>)? <statement_open> \"endif\" <statement_close> ;"
         " statement : <for> | <block> | <extends> | <if> ;"
@@ -375,3 +429,7 @@ char *template(char *tmpl, struct hashmap *ctx) {
     return buf.string;
 }
 
+char *render(struct env *env, char *template_name, struct hashmap *ctx) {
+    char *tmpl = hashmap_get(env->templates, template_name);
+    return template(tmpl, ctx);
+}
\ No newline at end of file
diff --git a/src/template.h b/src/template.h
index 36d540d..f65da47 100644
--- a/src/template.h
+++ b/src/template.h
@@ -3,3 +3,8 @@
 
 char *read_file(char *filename);
 char *template(char *tmpl, struct hashmap *ctx);
+
+struct env;
+struct env *env_new();
+void env_free(struct env *env);
+char *render(struct env *env, char *template_name, struct hashmap *ctx);
\ No newline at end of file
diff --git a/tests/data/01/base.tmpl b/tests/data/01/base.tmpl
new file mode 100644
index 0000000..6ca89cb
--- /dev/null
+++ b/tests/data/01/base.tmpl
@@ -0,0 +1,9 @@
+Header
+
+{% block content %}
+Content 
+{% endblock %}
+
+{% block footer %}
+Footer
+{% endblock %}
\ No newline at end of file
diff --git a/tests/data/01/child.tmpl b/tests/data/01/child.tmpl
new file mode 100644
index 0000000..05467a6
--- /dev/null
+++ b/tests/data/01/child.tmpl
@@ -0,0 +1,5 @@
+{% extends "base.tmpl" %}
+
+{% block content %}
+Child content
+{% endblock %}
\ No newline at end of file
diff --git a/tests/test_template.c b/tests/test_template.c
index c13ef4c..cc57ba5 100644
--- a/tests/test_template.c
+++ b/tests/test_template.c
@@ -215,6 +215,14 @@ TEST(buffer_alloc) {
     free(output);
 }
 
+TEST(directory) {
+    struct env *env = env_new("./tests/data/01/");
+    char *output = render(env, "child.tmpl", NULL);
+    assert_str(output, "Header\n\nChild content\n\nFooter");
+    free(output);
+    env_free(env);
+}
+
 
 
 END_TESTS 
\ No newline at end of file
-- 
cgit v1.2.3