diff --git a/README.md b/README.md
index b0c99a9..50761a5 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,76 @@
# nhtml
-Not html, a very simple template engine
\ No newline at end of file
+!html, a very simple template language
+
+Syntax
+------------
+As said above, the Template Language is simple.
+That also implies a few limitations:
+1. No Parametric instantiation (for now) since this would bloat the compiler too much
+ (I was in no need for that feature when writing this compiler).
+2. No language integration of any kind.
+ This makes this template compiler useful for static webcontent only!
+
+Sooo, how does it work?
+* text enclosed with `""` will be placed in the html output, but will be html escaped and `\n` will be replaced with `
`.
+
+* text enclosed with () will be placed *without any changes*.
+
+* One can include other files by writing either
+`@`, e.g. `@hi.nhtml` or
+`@""`, e.g. `@"filename with spaces.nhtml"`
+These files will pe pasted in place unless they end with `.nhtml`, in which case
+they get compiled before getting pasted.
+
+* There are comment blocks (`/*text*/`) and linecomments (`//text`) like there are in C.
+
+* A left curly brace (`{`) opens a tag, a coresponding right curly brace (`}`) closes it.
+
+* Finally, tag names are simply written as text and can be accompied by an attribute set
+enclosed in `[]`. So in order to e.g. produce
+`Hi
`, the corresponding nhtml would look something like
+`div[style=color:white]{"Hi"}`.
+
+* If the Attributes Key-Name is `class`, its pair can be written as `.` instead of `class=`.
+Note that multiple mentions of the same attribute key will result in the attribute values
+beeing concatenated with a space. So e.g.
+`p[.A class=hi]{}` will become ``.
+
+That about covers everything this little thing can do :)
+For a simple demo, look in example.
+
+How to compile
+--------------
+
+The Project used cmake to build. It has no hard dependencies but it requires unix-style pathnames. (`/path/to/file`) for includes to work.
+
+## on linux
+Simply install cmake for your distro
+### Ubuntu
+`apt-get install cmake`
+### Arch Linux
+`pacman -S cmake`
+
+The create a subdirectory for the build to reside in
+`mkdir build`
+`cd build`
+
+And run cmake
+Release: `cmake -G"Unix Makefiles" ..`
+Debug: `cmake -G"Unix Makefiles" .. -DCMAKE_BUILD_TYPE=DEBUG`
+
+after that run `make` and you should be left with a compiled version of the !html compiler `nhtmlc`
+
+How to use
+----------
+```
+./nhtmlc [-o ] file [file...]
+-I Add Path to include paths
+--output
+-o The output file to write the html to
+ When missing this option, stdout is used instead
+-v Enable verbose output
+--help Print this usage
+```
+
+When given multple input files, the resulting html is concatenated into output.
diff --git a/example/Makefile b/example/Makefile
new file mode 100644
index 0000000..cb17144
--- /dev/null
+++ b/example/Makefile
@@ -0,0 +1,15 @@
+
+all: entchen.html
+
+NHTMLC:=../build/nhtmlc
+
+%.html: %.nhtml
+ $(NHTMLC) $< -o $@
+
+index.html: head.nhtml entchen.nhtml
+ $(NHTMLC) entchen.nhtml -o $@
+
+clean:
+ rm -f entchen.html
+
+.PHONY: all clean
diff --git a/example/entchen.css b/example/entchen.css
new file mode 100644
index 0000000..f9efad5
--- /dev/null
+++ b/example/entchen.css
@@ -0,0 +1,9 @@
+.strophe {
+ border-width:1px;
+ border-type:solid;
+ border-color:black;
+ background-color:gray;
+ font-type:italic;
+ color: white;
+ padding: 0.5em;
+}
diff --git a/example/entchen.nhtml b/example/entchen.nhtml
new file mode 100644
index 0000000..d1a10f5
--- /dev/null
+++ b/example/entchen.nhtml
@@ -0,0 +1,35 @@
+/*
+* Generates the body of our document
+*
+* File created by Julian Daube
+* date: 08.08.2017
+*/
+
+@head.nhtml
+body {
+ style {
+ @entchen.css
+ }
+
+ h3{ "Alle meine Entchen" }
+ p[.strophe] {
+ "Alle meine Entchen schwimmen auf dem See,
+ schwimmen auf dem See,
+ Köpfchen in das Wasser, Schwänzchen in die Höh'."
+ }
+ p[.strophe] {
+ "Alle meine Täubchen gurren auf dem Dach,
+ gurren auf dem Dach,
+ eins fliegt in die Lüfte, fliegen alle nach."
+ }
+ p[.strophe] {
+ "Alle meine Hühner scharren in dem Stroh,
+ scharren in dem Stroh,
+ finden sie ein Körnchen, sind sie alle froh."
+ }
+ p[.strophe] {
+ "Alle meine Gänschen watscheln durch den Grund,
+ watscheln durch den Grund,
+ suchen in dem Tümpel, werden kugelrund."
+ }
+}
diff --git a/example/head.nhtml b/example/head.nhtml
new file mode 100644
index 0000000..83fff3b
--- /dev/null
+++ b/example/head.nhtml
@@ -0,0 +1,11 @@
+/*
+* Generates the document header
+*
+* file generated by Julian Daube
+* date: 08.08.2017
+*/
+!DOCTYPE[html]
+head {
+ title { "Alle meine Entchen" }
+ meta[charset=utf-8]
+}
diff --git a/inc/includes.h b/inc/includes.h
new file mode 100644
index 0000000..0b82d5f
--- /dev/null
+++ b/inc/includes.h
@@ -0,0 +1,18 @@
+/*
+ * includes.h
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#ifndef INC_INCLUDES_H_
+#define INC_INCLUDES_H_
+
+#include
+#include
+
+void include_add_path(const char * path);
+
+int include_file(string_t *filename, FILE * output);
+
+#endif /* INC_INCLUDES_H_ */
diff --git a/inc/nhtml_string.h b/inc/nhtml_string.h
index 13bc6e0..a9d51c9 100644
--- a/inc/nhtml_string.h
+++ b/inc/nhtml_string.h
@@ -24,10 +24,18 @@ typedef struct {
*/
int string_append(string_t *str, char c);
+/**
+ * \brief append src to dest
+ * will enlarge dest to accommodate src
+ * \return -1 on failure (check errno)
+ * \return 0 on success
+ */
+int string_concat(string_t * dest, string_t * src);
+
/**
* \brief Erase the String from memory
*/
-void string_destroy(string_t s);
+void string_destroy(string_t *s);
/**
* \brief copy the contents of a string
@@ -36,5 +44,10 @@ void string_destroy(string_t s);
*/
string_t string_copy(string_t old);
+/**
+ * \brief creates copy of str
+ * \return a copy of str
+ */
+string_t string_from_cstr(const char * str);
#endif /* NHTML_STRING_H_ */
diff --git a/inc/parser.h b/inc/parser.h
new file mode 100644
index 0000000..1a68e2d
--- /dev/null
+++ b/inc/parser.h
@@ -0,0 +1,17 @@
+/*
+ * parser.h
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#ifndef INC_PARSER_H_
+#define INC_PARSER_H_
+
+#include
+
+int strip(FILE * input);
+int parse_node(int current, FILE * stream, FILE * output);
+
+
+#endif /* INC_PARSER_H_ */
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 699928c..effd3f4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,4 +5,5 @@ set(SOURCE
${pwd}/nhtml_string.c
${pwd}/attribute.c
${pwd}/html.c
+ ${pwd}/includes.c
PARENT_SCOPE)
diff --git a/src/attribute.c b/src/attribute.c
index cc12446..86ffad8 100644
--- a/src/attribute.c
+++ b/src/attribute.c
@@ -31,9 +31,10 @@ int attr_set_append(attr_set_t * set, attr_t *new_entry) {
attr_t * new_ptr = attr_set_find(set, new_entry->name.c_str);
if (new_ptr != NULL) {
// already contained in set
- // just change entries value
- string_destroy(new_ptr->value);
- new_ptr->value = string_copy(new_entry->value);
+ // concatenate values
+ //new_ptr->value = string_copy(new_entry->value);
+ string_append(&new_ptr->value, ' ');
+ string_concat(&new_ptr->value, &new_entry->value);
return 0;
}
@@ -58,11 +59,8 @@ attr_t attr_copy(attr_t * attr) {
void attr_destroy(attr_t * attr) {
// clear memory
- string_destroy(attr->name);
- string_destroy(attr->value);
-
- // reset memory content
- memset(attr, 0, sizeof(attr_t));
+ string_destroy(&attr->name);
+ string_destroy(&attr->value);
}
diff --git a/src/includes.c b/src/includes.c
new file mode 100644
index 0000000..8b85174
--- /dev/null
+++ b/src/includes.c
@@ -0,0 +1,94 @@
+/*
+ * includes.c
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#include
+#include
+#include
+#include // needs: malloc, realloc
+#include // needs: memcpy
+
+string_t* include_paths;
+size_t num_includes = 0;
+
+void include_paths_init() {
+ include_paths = malloc(sizeof(string_t));
+ num_includes = 1;
+
+ include_paths[0] = string_from_cstr("."); // search in current dir by default
+}
+
+const char * extension(const string_t * path) {
+ int i;
+ for (i = path->len; i > 0; i--) {
+ if (path->c_str[i] == '/')
+ return NULL;
+ if (path->c_str[i] == '.')
+ break;
+ }
+
+ return path->c_str + i;
+}
+
+void include_add_path(const char * path) {
+ if (num_includes == 0) {
+ include_paths_init();
+ }
+
+ include_paths = realloc(include_paths, sizeof(const char*)*(num_includes+1));
+ include_paths[num_includes] = string_from_cstr(path);
+ num_includes++;
+}
+
+int include_file(string_t *filename, FILE * output) {
+ FILE * input = NULL;
+ char * tempPath = NULL;
+
+ if(num_includes == 0) {
+ include_paths_init();
+ }
+
+ for (size_t i = 0; i < num_includes; ++i) {
+ // actual length is length + 2 (2* end char)
+ tempPath = malloc(filename->len + include_paths[i].len);
+ if (tempPath == NULL) {
+ continue;
+ }
+
+ // Concatenate the two paths
+ // adding a safety slash to separate the two
+ memcpy(tempPath, include_paths[i].c_str, include_paths[i].len-1);
+ tempPath[include_paths->len-1] = '/';
+ memcpy(tempPath + include_paths->len, filename->c_str, filename->len);
+
+ input = fopen(tempPath, "r");
+ if (input != NULL) {
+ break;
+ }
+ }
+
+ if (input == NULL) {
+ return 0;
+ }
+ const char * ext = extension(filename);
+#ifdef DEBUG
+ printf("include %s, ext: %s\n", filename->c_str, ext);
+#endif
+
+ if (ext && !strcmp(ext, ".nhtml")) {
+ // (re)parse whole file
+ int current = strip(input);
+ while((current = parse_node(current, input, output)) != EOF);
+ } else {
+ // copy file 1:1
+ int current = 0;
+ while((current = fgetc(input)) != EOF) {
+ fputc(current, output);
+ }
+ }
+ fclose(input);
+ return 1;
+}
diff --git a/src/main.c b/src/main.c
index 435e0e5..d3eb575 100644
--- a/src/main.c
+++ b/src/main.c
@@ -7,9 +7,14 @@
#include // needs: getopt_long
// Project specific includes
-#include "html.h"
-#include "attribute.h"
-#include "nhtml_string.h"
+#include
+#include
+#include
+#include
+
+// globals
+
+int verbose = 0;
int strip(FILE * stream) {
int current = 0;
@@ -45,8 +50,7 @@ int parse_attr(FILE * stream, attr_set_t * output) {
}
if (isKey && buffer == '.') {
isKey = 0;
- current_attr.name.c_str = "class";
- current_attr.name.len = 6;
+ current_attr.name = string_from_cstr("class");
continue;
}
if (buffer == '=') {
@@ -70,7 +74,7 @@ int parse_attr(FILE * stream, attr_set_t * output) {
printf("parsed attr: %s=%s\n", current_attr.name.c_str, current_attr.value);
#endif
- //memset(output, 0, sizeof(attr_set_t));
+ attr_destroy(¤t_attr);
return strip(stream);
}
@@ -122,11 +126,102 @@ int readName(FILE *stream, node_t *node) {
return current;
}
+// Parse comments
+int parse_comment(int current, FILE * input) {
+ current = fgetc(input);
+ if (current == '/') {
+ // line comment
+ while((current = fgetc(input)) != EOF) {
+ if (current == '\n') break;
+ }
+ } else
+ if (current == '*') {
+ // block comment
+ unsigned char comment_done = 0;
+ while((current = fgetc(input)) != EOF) {
+ if (current == '*') {
+ comment_done = 1;
+ } else
+ if (comment_done && current == '/') {
+ break;
+ } else
+ comment_done = 0;
+ }
+ }
+
+ return strip(input);
+}
+
+int parse_include(FILE * input, FILE * output) {
+ // handle include
+ int current = fgetc(input);
+ string_t filename = {};
+
+ // two possibilities
+ // 1. include with "" <= supports arbitrary paths
+ // 2. include without ""
+ // reads till a nonpath, char (!{")
+ // then strips empty chars from start and end of path
+ // both methods then try to find the file in the include paths
+ if (current == EOF) return current;
+
+ if (current == '"') { // Method 1
+ char escaped = 0;
+ while((current = fgetc(input)) != EOF) {
+ if (!escaped && current == '\\') {
+ escaped = 1;
+ continue;
+ }
+ if (!escaped && current == '"') {
+ break;
+ }
+
+ escaped = 0;
+ string_append(&filename, current);
+ }
+ } else {
+ string_append(&filename, current);
+ while((current = fgetc(input)) != EOF) {
+ switch(current) {
+ case '{':
+ case '!':
+ case '"':
+ case '}':
+ break;
+ }
+ if (isspace(current)) {
+ break;
+ }
+
+ string_append(&filename, current);
+ }
+ }
+
+ if (filename.c_str == NULL) {
+ fprintf(stderr, "include command without filename!\n");
+ return strip(input);
+ }
+
+ // handle filename
+ if (verbose) printf("include file %s\n", filename.c_str);
+
+ if (!include_file(&filename, output)) {
+ fprintf(stderr, "could not include file %s\n", filename.c_str);
+ }
+
+ return strip(input);
+}
+
int parse_node(int current, FILE * stream, FILE * output) {
if (current == '"' || current == '(') {
if (current == '(') current = ')';
-
return parse_text(current, stream, output);
+ } else
+ if (current == '/') {
+ return parse_comment(current, stream);
+ } else
+ if (current == '@') {
+ return parse_include(stream, output);
}
// normal node
@@ -161,6 +256,7 @@ done:
}
+
// long options
static struct option long_options[] = {
{"output", required_argument, 0, 'o'},
@@ -168,13 +264,14 @@ static struct option long_options[] = {
{}
};
-int verbose = 1;
void usage(int argc, char ** args) {
printf("usage: %s [-o ] file [file...]\n", args[0]);
+ printf("-I\tAdd Path to include paths\n");
printf("--output\n");
printf("-o\tThe output file to write the html to\n");
printf("\tWhen missing this option, stdout is used instead\n");
+ printf("-v\tEnable verbose output\n");
printf("--help Print this usage\n");
}
@@ -183,16 +280,22 @@ int main(int argc, char ** args) {
FILE* output = NULL;
// parse arguments
- while((i = getopt_long(argc, args, "o:v", long_options, NULL)) != -1) {
+ while((i = getopt_long(argc, args, "o:I:v", long_options, NULL)) != -1) {
switch(i) {
case 'o':
- printf("output: %s\n", optarg);
output = fopen(optarg, "w");
if (output == NULL) {
- fprintf(stderr, "could not create file: %s\n", strerror(errno));
+ fprintf(stderr, "could not create output file: %s (%s)\n", optarg, strerror(errno));
return -1;
}
break;
+ case 'I':
+ // add include dir
+ include_add_path(optarg);
+ break;
+ case 'v':
+ verbose = 1;
+ break;
case '?':
usage(argc, args);
return -1;
@@ -204,6 +307,7 @@ int main(int argc, char ** args) {
if (output == NULL) {
output = stdout;
+ // disable output when printing to stdout
verbose = 0;
}
@@ -211,7 +315,7 @@ int main(int argc, char ** args) {
// parse all the files
for(i = optind;i < argc; i++) {
- filename = args[argc-1];
+ filename = args[i];
if (verbose) printf("starting conversion of %s\n", filename);
FILE * handle = fopen(filename, "r");
diff --git a/src/nhtml_string.c b/src/nhtml_string.c
index 5786f13..6ead993 100644
--- a/src/nhtml_string.c
+++ b/src/nhtml_string.c
@@ -7,7 +7,8 @@
#include "nhtml_string.h"
#include // needs: malloc, free
-#include // needs: memcpy
+#include // needs: memcpy, memset
+#include // needs: strlen, strdup
int string_append(string_t *str, char c) {
if (str->c_str == NULL) {
@@ -29,12 +30,35 @@ int string_append(string_t *str, char c) {
return 0;
}
-void string_destroy(string_t s) {
- free(s.c_str);
+int string_concat(string_t * dest, string_t *src) {
+ if (src->c_str == NULL) {
+ return 0;
+ }
+
+ if (dest->c_str == NULL) {
+ dest->len++;
+ }
+
+ char * new_ptr = realloc(dest->c_str, dest->len + src->len-1);
+ if (new_ptr == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ memcpy(dest->c_str + dest->len-1, src->c_str, src->len);
+ return 0;
+}
+
+void string_destroy(string_t *s) {
+ free(s->c_str);
+ memset(s, 0, sizeof(string_t));
}
string_t string_copy(string_t old) {
string_t tmp = {}; // initialize with 0
+ if (old.len == 0) {
+ return tmp;
+ }
tmp.c_str = malloc(old.len);
if (tmp.c_str == NULL) {
@@ -48,3 +72,9 @@ string_t string_copy(string_t old) {
return tmp;
}
+string_t string_from_cstr(const char * str) {
+ string_t temp;
+ temp.len = strlen(str)+1;
+ temp.c_str = strdup(str);
+ return temp;
+}