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; +}