commit version 0.1

This commit is contained in:
Julian Daube 2017-08-08 22:24:40 +02:00
parent e3d663d6c3
commit 5378676596
17 changed files with 995 additions and 1 deletions

13
CMakeLists.txt Normal file
View File

@ -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)

View File

@ -1,3 +1,76 @@
# nhtml
Not html, a very simple template engine
!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 `<br/>`.
* text enclosed with () will be placed *without any changes*.
* One can include other files by writing either
`@<filename>`, e.g. `@hi.nhtml` or
`@"<filename>"`, 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
`<div style="color:white">Hi</div>`, 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 `.<value>` instead of `class=<value>`.
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 `<p class="A hi"></p>`.
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 <filename>] 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.

15
example/Makefile Normal file
View File

@ -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

9
example/entchen.css Normal file
View File

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

35
example/entchen.nhtml Normal file
View File

@ -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."
}
}

11
example/head.nhtml Normal file
View File

@ -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]
}

54
inc/attribute.h Normal file
View File

@ -0,0 +1,54 @@
/*
* attribute.h
*
* Created on: 08.08.2017
* Author: julian
*/
#ifndef ATTRIBUTE_H_
#define ATTRIBUTE_H_
#include <errno.h>
#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_ */

37
inc/html.h Normal file
View File

@ -0,0 +1,37 @@
/*
* html.h
*
* Created on: 07.08.2017
* Author: julian
*/
#ifndef HTML_H_
#define HTML_H_
#include <stdio.h> // 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_ */

18
inc/includes.h Normal file
View File

@ -0,0 +1,18 @@
/*
* includes.h
*
* Created on: 08.08.2017
* Author: julian
*/
#ifndef INC_INCLUDES_H_
#define INC_INCLUDES_H_
#include <nhtml_string.h>
#include <stdio.h>
void include_add_path(const char * path);
int include_file(string_t *filename, FILE * output);
#endif /* INC_INCLUDES_H_ */

53
inc/nhtml_string.h Normal file
View File

@ -0,0 +1,53 @@
/*
* nhtml_string.h
*
* Created on: 08.08.2017
* Author: julian
*/
#ifndef NHTML_STRING_H_
#define NHTML_STRING_H_
#include <stddef.h> // size_t
#include <errno.h> // 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_ */

17
inc/parser.h Normal file
View File

@ -0,0 +1,17 @@
/*
* parser.h
*
* Created on: 08.08.2017
* Author: julian
*/
#ifndef INC_PARSER_H_
#define INC_PARSER_H_
#include <stdio.h>
int strip(FILE * input);
int parse_node(int current, FILE * stream, FILE * output);
#endif /* INC_PARSER_H_ */

9
src/CMakeLists.txt Normal file
View File

@ -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)

66
src/attribute.c Normal file
View File

@ -0,0 +1,66 @@
/*
* attribute.c
*
* Created on: 08.08.2017
* Author: julian
*/
#include "attribute.h"
#include <string.h> // needs: strcmp
#include <stdlib.h> // 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);
}

71
src/html.c Normal file
View File

@ -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, "&lt;");
goto escaped;
case '>':
fprintf(output, "&gt;");
goto escaped;
case '&':
fprintf(output, "&amp;");
goto escaped;
case '"':
fprintf(output, "&quot;");
goto escaped;
/*case ' ':
fprintf(output, "&nbsp;");
goto escaped;*/
case '\n':
fprintf(output, "<br/>");
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);
}
}

94
src/includes.c Normal file
View File

@ -0,0 +1,94 @@
/*
* includes.c
*
* Created on: 08.08.2017
* Author: julian
*/
#include <includes.h>
#include <parser.h>
#include <nhtml_string.h>
#include <stdlib.h> // needs: malloc, realloc
#include <memory.h> // 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;
}

339
src/main.c Normal file
View File

@ -0,0 +1,339 @@
#include <stdio.h> // needs: fgetc, fputs, fopen, fprintf
#include <stdlib.h> // needs: abort
#include <errno.h>
#include <string.h> // needs: sterror
#include <ctype.h> // needs: isspace
#include <getopt.h> // needs: getopt_long
// Project specific includes
#include <html.h>
#include <attribute.h>
#include <nhtml_string.h>
#include <includes.h>
// 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, &current_attr);
#ifdef DEBUG
printf("parsed attr: %s=%s\n", current_attr.name.c_str, current_attr.value);
#endif
// reset attribute
attr_destroy(&current_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(&current_attr.name, buffer);
} else {
string_append(&current_attr.value, buffer);
}
}
// append last attribute
attr_set_append(output, &current_attr);
#ifdef DEBUG
printf("parsed attr: %s=%s\n", current_attr.name.c_str, current_attr.value);
#endif
attr_destroy(&current_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(&current_node.name, current);
current = readName(stream, &current_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, &current_node.attributes);
}
if (current != '{') {
// tag is selfclosing
open_node(&current_node, output);
return current;
}
current = strip(stream);
open_node(&current_node, output);
while(current != '}' && current != EOF) {
current = parse_node(current, stream, output);
}
close_node(&current_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 <filename>] 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;
}

80
src/nhtml_string.c Normal file
View File

@ -0,0 +1,80 @@
/*
* string.c
*
* Created on: 08.08.2017
* Author: julian
*/
#include "nhtml_string.h"
#include <stdlib.h> // needs: malloc, free
#include <memory.h> // needs: memcpy, memset
#include <string.h> // 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;
}