diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..9528306
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 3.0.0)
+project(nhtml C)
+
+add_subdirectory(src)
+add_executable(nhtmlc ${SOURCE})
+target_include_directories(nhtmlc PUBLIC inc)
+
+if (CMAKE_BUILD_TYPE MATCHES "DEBUG")
+ message("debug")
+ target_compile_definitions(nhtmlc PUBLIC DEBUG)
+endif (CMAKE_BUILD_TYPE MATCHES "DEBUG")
+
+target_link_libraries(nhtmlc)
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/attribute.h b/inc/attribute.h
new file mode 100644
index 0000000..452f27c
--- /dev/null
+++ b/inc/attribute.h
@@ -0,0 +1,54 @@
+/*
+ * attribute.h
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#ifndef ATTRIBUTE_H_
+#define ATTRIBUTE_H_
+
+#include
+
+#include "nhtml_string.h"
+
+
+typedef struct {
+ string_t name, value;
+} attr_t;
+
+/**
+ * \brief copy the give attribute
+ * \return a copy of attr
+ */
+attr_t attr_copy(attr_t * attr);
+/**
+ * \brief reset the attribute
+ * Deletes all strings
+ */
+void attr_destroy(attr_t * attr);
+
+
+typedef struct {
+ attr_t * arr;
+ size_t len;
+} attr_set_t;
+
+/**
+ * \brief search in attribute set for key
+ * \param set The Attribute set
+ * \param key The Key to search
+ * \return the pointer to the attribute in the set mathching the key
+ * \return NULL if there is no matching attribute
+ */
+attr_t * attr_set_find(attr_set_t *set, const char * key);
+// Append new Attribute to set
+/**
+ * \brief Append new attribute pair to set
+ * \return 0 on success
+ * \return -1 on failure (error can be found to errno)
+ */
+int attr_set_append(attr_set_t * set, attr_t *new_entry);
+
+
+#endif /* ATTRIBUTE_H_ */
diff --git a/inc/html.h b/inc/html.h
new file mode 100644
index 0000000..15d73e6
--- /dev/null
+++ b/inc/html.h
@@ -0,0 +1,37 @@
+/*
+ * html.h
+ *
+ * Created on: 07.08.2017
+ * Author: julian
+ */
+
+#ifndef HTML_H_
+#define HTML_H_
+
+#include // needs FILE
+#include "nhtml_string.h"
+#include "attribute.h"
+
+extern int html_escape(int c, FILE* output);
+
+typedef struct node {
+ string_t name;
+ attr_set_t attributes;
+} node_t;
+
+
+/**
+ * \brief emit html opening tag for \node
+ * \param node the Node to create the opening tag for
+ * \param output the File to write to
+ */
+void open_node(node_t * node, FILE * output);
+
+/**
+ * \brief emit html closing tag for \node
+ * \param node the Node to create the closing tag for
+ * \param output the File to write to
+ */
+void close_node(node_t * node, FILE * output);
+
+#endif /* HTML_H_ */
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
new file mode 100644
index 0000000..a9d51c9
--- /dev/null
+++ b/inc/nhtml_string.h
@@ -0,0 +1,53 @@
+/*
+ * nhtml_string.h
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#ifndef NHTML_STRING_H_
+#define NHTML_STRING_H_
+
+#include // size_t
+#include // errno
+
+typedef struct {
+ char * c_str;
+ size_t len;
+} string_t;
+
+/**\brief append the char @c to @str
+ * \param str to the String to append to
+ * \param c the char to append
+ * \return -1 on error, errno will be set to errorcode
+ * \return 0 on success
+ */
+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);
+
+/**
+ * \brief copy the contents of a string
+ * \param old The String to copy
+ * \return returns the new string
+ */
+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
new file mode 100644
index 0000000..effd3f4
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,9 @@
+set(pwd ${CMAKE_CURRENT_SOURCE_DIR})
+set(SOURCE
+ ${SOURCE}
+ ${pwd}/main.c
+ ${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
new file mode 100644
index 0000000..86ffad8
--- /dev/null
+++ b/src/attribute.c
@@ -0,0 +1,66 @@
+/*
+ * attribute.c
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#include "attribute.h"
+#include // needs: strcmp
+#include // needs: realloc
+
+// linear search in set
+// returns NULL on failure to find entry
+attr_t * attr_set_find(attr_set_t *set, const char * name) {
+ size_t current = 0;
+
+ for (; current != set->len; ++current) {
+ if (strcmp(set->arr[current].name.c_str, name) == 0) {
+ return set->arr + current;
+ }
+ }
+
+ return NULL;
+}
+
+// Append new Attribute to set
+int attr_set_append(attr_set_t * set, attr_t *new_entry) {
+ if (new_entry->name.c_str== NULL) return -1; // reject empty entries
+
+ // search first
+ attr_t * new_ptr = attr_set_find(set, new_entry->name.c_str);
+ if (new_ptr != NULL) {
+ // already contained in set
+ // 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;
+ }
+
+ new_ptr = realloc(set->arr, (set->len+1)*sizeof(attr_t));
+ if (new_ptr == NULL) {
+ return -1;
+ }
+
+ // append and quit
+ set->arr = new_ptr;
+ set->arr[set->len] = attr_copy(new_entry);
+ set->len++;
+ return 0;
+}
+
+attr_t attr_copy(attr_t * attr) {
+ attr_t temp;
+ temp.name = string_copy(attr->name);
+ temp.value = string_copy(attr->value);
+ return temp;
+}
+
+void attr_destroy(attr_t * attr) {
+ // clear memory
+ string_destroy(&attr->name);
+ string_destroy(&attr->value);
+}
+
+
diff --git a/src/html.c b/src/html.c
new file mode 100644
index 0000000..ee595e0
--- /dev/null
+++ b/src/html.c
@@ -0,0 +1,71 @@
+/*
+ * html.c
+ *
+ * Created on: 07.08.2017
+ * Author: julian
+ */
+
+#include "html.h"
+
+int html_escape(int c, FILE * output) {
+ switch(c) {
+ case '<':
+ fprintf(output, "<");
+ goto escaped;
+ case '>':
+ fprintf(output, ">");
+ goto escaped;
+ case '&':
+ fprintf(output, "&");
+ goto escaped;
+ case '"':
+ fprintf(output, """);
+ goto escaped;
+ /*case ' ':
+ fprintf(output, " ");
+ goto escaped;*/
+ case '\n':
+ fprintf(output, "
");
+ goto escaped;
+ }
+
+ return 0;
+escaped:
+ return 1;
+}
+
+// write out node
+void open_node(node_t * node, FILE * output) {
+ // check for empty node (text mostly)
+ if (!node->name.c_str) return;
+
+ // start by writing tag
+ fputc('<', output);
+
+ // follow with tag name
+ fputs(node->name.c_str,output);
+
+ // add attributes
+ size_t i = 0;
+ attr_t * current = node->attributes.arr;
+ for (; i < node->attributes.len; i++,current++) {
+ if (current->value.c_str)
+ fprintf(output, " %s=\"%s\"", current->name.c_str, current->value.c_str);
+ else {
+ fputc(' ', output);
+ fputs(current->name.c_str, output);
+ }
+ }
+
+ // close tag
+ fputc('>', output);
+}
+
+void close_node(node_t * node, FILE * output) {
+ if (node->name.c_str) {
+ fprintf(output, "%s>", node->name.c_str);
+ }
+}
+
+
+
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
new file mode 100644
index 0000000..d3eb575
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,339 @@
+#include // needs: fgetc, fputs, fopen, fprintf
+#include // needs: abort
+#include
+#include // needs: sterror
+#include // needs: isspace
+
+#include // needs: getopt_long
+
+// Project specific includes
+#include
+#include
+#include
+#include
+
+// globals
+
+int verbose = 0;
+
+int strip(FILE * stream) {
+ int current = 0;
+ while((current = fgetc(stream)) != EOF) {
+ if (!isspace(current))
+ break;
+ }
+
+ return current;
+}
+
+int parse_attr(FILE * stream, attr_set_t * output) {
+ attr_t current_attr = {};
+#ifdef DEBUG
+ printf("parse_attr\n");
+#endif
+ int buffer = 0;
+ unsigned char isKey = 1;
+
+ while((buffer = fgetc(stream)) != EOF) {
+ // parse key=value pairs
+ // check for delim
+ //if (buffer == ' ' || buffer == '\t' || buffer == '\n'){
+ if (isspace(buffer)) {
+ attr_set_append(output, ¤t_attr);
+#ifdef DEBUG
+ printf("parsed attr: %s=%s\n", current_attr.name.c_str, current_attr.value);
+#endif
+ // reset attribute
+ attr_destroy(¤t_attr);
+ isKey = 1;
+ continue;
+ }
+ if (isKey && buffer == '.') {
+ isKey = 0;
+ current_attr.name = string_from_cstr("class");
+ continue;
+ }
+ if (buffer == '=') {
+ isKey = 0;
+ continue;
+ } else
+ if (buffer == ']') {
+ break;
+ }
+
+ if (isKey) {
+ string_append(¤t_attr.name, buffer);
+ } else {
+ string_append(¤t_attr.value, buffer);
+ }
+ }
+
+ // append last attribute
+ attr_set_append(output, ¤t_attr);
+#ifdef DEBUG
+ printf("parsed attr: %s=%s\n", current_attr.name.c_str, current_attr.value);
+#endif
+
+ attr_destroy(¤t_attr);
+ return strip(stream);
+}
+
+int parse_text(int end_char, FILE * stream, FILE * output) {
+#ifdef DEBUG
+ printf("parse text\n");
+#endif
+ int buffer = 0;
+ char escaped = 0;
+
+ while((buffer = fgetc(stream)) != EOF) {
+ if (!escaped && buffer == '\\') {
+ escaped = 1;
+ continue;
+ }
+ if (!escaped && buffer == end_char) {
+ break;
+ }
+ escaped = 0;
+ if (end_char == '"' && html_escape(buffer, output)) {
+ continue;
+ }
+
+ fputc(buffer, output);
+ }
+
+ return strip(stream);
+}
+
+int readName(FILE *stream, node_t *node) {
+ int current = strip(stream);
+ if (current == EOF) {
+ return current;
+ }
+
+ do {
+ if (current == '{' || current == '[') {
+ break;
+ }
+
+ if (current == ' ' || current == '\t' || current == '\n') {
+ current = strip(stream);
+ break;
+ }
+
+ string_append(&node->name, current);
+ } while((current = fgetc(stream)) != EOF);
+
+ 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
+ node_t current_node = {};
+ string_append(¤t_node.name, current);
+ current = readName(stream, ¤t_node);
+ if (current == EOF) {
+ goto done;
+ }
+#ifdef DEBUG
+ printf("parse_node: %s\n", current_node.name.c_str);
+#endif
+ if (current == '[') {
+ current = parse_attr(stream, ¤t_node.attributes);
+ }
+
+ if (current != '{') {
+ // tag is selfclosing
+ open_node(¤t_node, output);
+ return current;
+ }
+ current = strip(stream);
+
+ open_node(¤t_node, output);
+ while(current != '}' && current != EOF) {
+ current = parse_node(current, stream, output);
+ }
+
+ close_node(¤t_node, output);
+done:
+ return strip(stream);
+}
+
+
+
+// long options
+static struct option long_options[] = {
+ {"output", required_argument, 0, 'o'},
+ {"help", no_argument, 0, '?'},
+ {}
+};
+
+
+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");
+}
+
+int main(int argc, char ** args) {
+ int i = 0;
+ FILE* output = NULL;
+
+ // parse arguments
+ while((i = getopt_long(argc, args, "o:I:v", long_options, NULL)) != -1) {
+ switch(i) {
+ case 'o':
+ output = fopen(optarg, "w");
+ if (output == NULL) {
+ 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;
+ default:
+ // should not be reachable
+ abort();
+ }
+ }
+
+ if (output == NULL) {
+ output = stdout;
+ // disable output when printing to stdout
+ verbose = 0;
+ }
+
+ char * filename= NULL;
+
+ // parse all the files
+ for(i = optind;i < argc; i++) {
+ filename = args[i];
+ if (verbose) printf("starting conversion of %s\n", filename);
+
+ FILE * handle = fopen(filename, "r");
+
+ if (handle == NULL) {
+ fprintf(stderr, "could not open \"%s\": %s\n", filename, strerror(errno));
+ continue;
+ }
+
+ int current = strip(handle);
+
+ // parse the complete file
+ while((current = parse_node(current, handle, output)) != EOF);
+
+ fclose(handle);
+ }
+
+ if (output != stdout) fclose(output);
+ if (verbose) printf("done compiling\n");
+ return 0;
+}
diff --git a/src/nhtml_string.c b/src/nhtml_string.c
new file mode 100644
index 0000000..6ead993
--- /dev/null
+++ b/src/nhtml_string.c
@@ -0,0 +1,80 @@
+/*
+ * string.c
+ *
+ * Created on: 08.08.2017
+ * Author: julian
+ */
+
+#include "nhtml_string.h"
+#include // needs: malloc, free
+#include // needs: memcpy, memset
+#include // needs: strlen, strdup
+
+int string_append(string_t *str, char c) {
+ if (str->c_str == NULL) {
+ // new string, need to emit EOS
+ str->len++;
+ }
+
+ char * new_ptr = realloc(str->c_str, str->len+1);
+ if (new_ptr == NULL) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ // append char
+ str->c_str = new_ptr;
+ str->c_str[str->len-1] = c;
+ str->c_str[str->len] = 0; // make sure
+ str->len++;
+ return 0;
+}
+
+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) {
+ // The Application will have to handle a out of mem
+ // situation here
+ return tmp;
+ }
+
+ tmp.len = old.len;
+ memcpy(tmp.c_str, old.c_str, tmp.len);
+ 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;
+}