cmark

My personal build of CMark ✏️

Commit
9370b2cfd9b6382164ab7bde36a59409d32ae498
Parent
47580cbda73fa6ad984dc4690625eb27b54bc563
Author
John MacFarlane <fiddlosopher@gmail.com>
Date

Merge branch 'api_tests' of https://github.com/nwellnhof/CommonMark into nwellnhof-api_tests

Diffstat

10 files changed, 362 insertions, 25 deletions

Status File Name N° Changes Insertions Deletions
Modified CMakeLists.txt 2 2 0
Added api_test/CMakeLists.txt 19 19 0
Added api_test/harness.c 102 102 0
Added api_test/harness.h 34 34 0
Added api_test/main.c 58 58 0
Modified src/chunk.h 39 28 11
Modified src/cmark.h 32 27 5
Modified src/html/html.c 14 7 7
Modified src/node.c 85 84 1
Modified src/node.h 2 1 1
diff --git a/CMakeLists.txt b/CMakeLists.txt
@@ -13,6 +13,7 @@ set(PROJECT_VERSION_PATCH 1)
 set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH} )
 
 add_subdirectory(src)
+add_subdirectory(api_test)
 
 if(UNIX)
   INSTALL(FILES man/man1/cmark.1 DESTINATION share/man/man1)
@@ -24,6 +25,7 @@ enable_testing()
 add_test(spectest
    perl "${CMAKE_SOURCE_DIR}/runtests.pl" "${CMAKE_SOURCE_DIR}/spec.txt" "${CMAKE_BINARY_DIR}/src/cmark"
 )
+add_test(NAME api_test COMMAND api_test)
 
 if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE "Release" CACHE STRING
diff --git a/api_test/CMakeLists.txt b/api_test/CMakeLists.txt
@@ -0,0 +1,19 @@
+add_executable(api_test
+  harness.c
+  harness.h
+  main.c
+)
+include_directories(
+  ${PROJECT_SOURCE_DIR}/src
+  ${PROJECT_BINARY_DIR}/src
+)
+target_link_libraries(api_test libcmark)
+
+# Compiler flags
+if(CMAKE_COMPILER_IS_GNUCC OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
+  set_target_properties(api_test PROPERTIES COMPILE_FLAGS
+    "-std=c99 -Wall -Wextra"
+  )
+elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "MSVC")
+  set_target_properties(api_test PROPERTIES COMPILE_FLAGS "/TP /W4")
+endif()
diff --git a/api_test/harness.c b/api_test/harness.c
@@ -0,0 +1,102 @@
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "harness.h"
+
+test_batch_runner*
+test_batch_runner_new()
+{
+	return (test_batch_runner *)calloc(1, sizeof(test_batch_runner));
+}
+
+static void
+test_result(test_batch_runner *runner, int cond, const char *msg, va_list ap)
+{
+	++runner->test_num;
+
+	if (cond) {
+		++runner->num_passed;
+	}
+	else {
+		fprintf(stderr, "FAILED test %d: ", runner->test_num);
+		vfprintf(stderr, msg, ap);
+		fprintf(stderr, "\n");
+		++runner->num_failed;
+	}
+}
+
+void
+SKIP(test_batch_runner *runner, int num_tests)
+{
+	runner->test_num    += num_tests;
+	runner->num_skipped += num_tests;
+}
+
+void
+OK(test_batch_runner *runner, int cond, const char *msg, ...)
+{
+	va_list ap;
+	va_start(ap, msg);
+	test_result(runner, cond, msg, ap);
+	va_end(ap);
+}
+
+void
+INT_EQ(test_batch_runner *runner, int got, int expected, const char *msg, ...)
+{
+	int cond = got == expected;
+
+	va_list ap;
+	va_start(ap, msg);
+	test_result(runner, cond, msg, ap);
+	va_end(ap);
+
+	if (!cond) {
+		fprintf(stderr, "  Got:      %d\n", got);
+		fprintf(stderr, "  Expected: %d\n", expected);
+	}
+}
+
+void
+STR_EQ(test_batch_runner *runner, const char *got, const char *expected,
+       const char *msg, ...)
+{
+	int cond = strcmp(got, expected) == 0;
+
+	va_list ap;
+	va_start(ap, msg);
+	test_result(runner, cond, msg, ap);
+	va_end(ap);
+
+	if (!cond) {
+		fprintf(stderr, "  Got:      \"%s\"\n", got);
+		fprintf(stderr, "  Expected: \"%s\"\n", expected);
+	}
+}
+
+int
+test_ok(test_batch_runner *runner)
+{
+	return runner->num_failed == 0;
+}
+
+void
+test_print_summary(test_batch_runner *runner)
+{
+	int num_passed  = runner->num_passed;
+	int num_skipped = runner->num_skipped;
+	int num_failed  = runner->num_failed;
+
+	fprintf(stderr, "%d tests passed, %d failed, %d skipped\n",
+		num_passed, num_skipped, num_failed);
+
+	if (test_ok(runner)) {
+		fprintf(stderr, "PASS\n");
+	}
+	else {
+		fprintf(stderr, "FAIL\n");
+	}
+}
+
diff --git a/api_test/harness.h b/api_test/harness.h
@@ -0,0 +1,34 @@
+#ifndef CMARK_API_TEST_HARNESS_H
+#define CMARK_API_TEST_HARNESS_H
+
+typedef struct {
+	int test_num;
+	int num_passed;
+	int num_failed;
+	int num_skipped;
+} test_batch_runner;
+
+test_batch_runner*
+test_batch_runner_new();
+
+void
+SKIP(test_batch_runner *runner, int num_tests);
+
+void
+OK(test_batch_runner *runner, int cond, const char *msg, ...);
+
+void
+INT_EQ(test_batch_runner *runner, int got, int expected, const char *msg, ...);
+
+void
+STR_EQ(test_batch_runner *runner, const char *got, const char *expected,
+       const char *msg, ...);
+
+int
+test_ok(test_batch_runner *runner);
+
+void
+test_print_summary(test_batch_runner *runner);
+
+#endif
+
diff --git a/api_test/main.c b/api_test/main.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cmark.h"
+#include "node.h"
+
+#include "harness.h"
+
+static void
+create_tree(test_batch_runner *runner)
+{
+	cmark_node *doc = cmark_node_new(CMARK_NODE_DOCUMENT);
+
+	cmark_node *p = cmark_node_new(CMARK_NODE_PARAGRAPH);
+	OK(runner, cmark_node_append_child(doc, p), "append1");
+	INT_EQ(runner, cmark_node_check(doc), 0, "append1 consistent");
+
+	cmark_node *emph = cmark_node_new(CMARK_NODE_EMPH);
+	OK(runner, cmark_node_prepend_child(p, emph), "prepend1");
+	INT_EQ(runner, cmark_node_check(doc), 0, "prepend1 consistent");
+
+	cmark_node *str1 = cmark_node_new(CMARK_NODE_STRING);
+	cmark_node_set_content(str1, "Hello, ");
+	OK(runner, cmark_node_prepend_child(p, str1), "prepend2");
+	INT_EQ(runner, cmark_node_check(doc), 0, "prepend2 consistent");
+
+	cmark_node *str3 = cmark_node_new(CMARK_NODE_STRING);
+	cmark_node_set_content(str3, "!");
+	OK(runner, cmark_node_append_child(p, str3), "append2");
+	INT_EQ(runner, cmark_node_check(doc), 0, "append2 consistent");
+
+	cmark_node *str2 = cmark_node_new(CMARK_NODE_STRING);
+	cmark_node_set_content(str2, "world");
+	OK(runner, cmark_node_append_child(emph, str2), "append3");
+	INT_EQ(runner, cmark_node_check(doc), 0, "append3 consistent");
+
+	char *html = (char *)cmark_render_html(doc);
+	STR_EQ(runner, html, "<p>Hello, <em>world</em>!</p>\n",
+	       "render_html");
+	free(html);
+
+	cmark_node_destroy(doc);
+}
+
+int main() {
+	int retval;
+	test_batch_runner *runner = test_batch_runner_new();
+
+	create_tree(runner);
+
+	test_print_summary(runner);
+	retval =  test_ok(runner) ? 0 : 1;
+	free(runner);
+
+	return retval;
+}
+
diff --git a/src/chunk.h b/src/chunk.h
@@ -8,15 +8,15 @@
 #include "buffer.h"
 
 typedef struct {
-	const unsigned char *data;
+	unsigned char *data;
 	int len;
-	int alloc;
+	int alloc;  // also implies a NULL-terminated string
 } cmark_chunk;
 
 static inline void cmark_chunk_free(cmark_chunk *c)
 {
 	if (c->alloc)
-		free((char *)c->data);
+		free(c->data);
 
 	c->data = NULL;
 	c->alloc = 0;
@@ -55,21 +55,38 @@ static inline int cmark_chunk_strchr(cmark_chunk *ch, int c, int offset)
 	return p ? (int)(p - ch->data) : ch->len;
 }
 
-static inline unsigned char *cmark_chunk_to_cstr(cmark_chunk *c)
+static inline const char *cmark_chunk_to_cstr(cmark_chunk *c)
 {
 	unsigned char *str;
 
-	str = (unsigned char *)calloc(c->len + 1, sizeof(*str));
-    if(str != NULL) {
-    	memcpy(str, c->data, c->len);
-	    str[c->len] = 0;
-    }
-	return str;
+	if (c->alloc) {
+		return (char *)c->data;
+	}
+	str = (unsigned char *)malloc(c->len + 1);
+	if(str != NULL) {
+		memcpy(str, c->data, c->len);
+		str[c->len] = 0;
+	}
+	c->data  = str;
+	c->alloc = 1;
+
+	return (char *)str;
+}
+
+static inline void cmark_chunk_set_cstr(cmark_chunk *c, const char *str)
+{
+	if (c->alloc) {
+		free(c->data);
+	}
+	c->len   = strlen(str);
+	c->data  = (unsigned char *)malloc(c->len + 1);
+	c->alloc = 1;
+	memcpy(c->data, str, c->len + 1);
 }
 
 static inline cmark_chunk cmark_chunk_literal(const char *data)
 {
-	cmark_chunk c = {(const unsigned char *)data, data ? strlen(data) : 0, 0};
+	cmark_chunk c = {(unsigned char *)data, data ? strlen(data) : 0, 0};
 	return c;
 }
 
diff --git a/src/cmark.h b/src/cmark.h
@@ -56,8 +56,16 @@ typedef enum {
 typedef struct cmark_node cmark_node;
 typedef struct cmark_doc_parser cmark_doc_parser;
 
-CMARK_EXPORT cmark_node_type
-cmark_node_get_type(cmark_node *node);
+// Construction and destruction
+
+CMARK_EXPORT cmark_node*
+cmark_node_new(cmark_node_type type);
+
+CMARK_EXPORT void
+cmark_node_destroy(cmark_node *node);
+
+CMARK_EXPORT void
+cmark_free_nodes(cmark_node *e);
 
 // Tree traversal
 
@@ -76,6 +84,23 @@ cmark_node_first_child(cmark_node *node);
 CMARK_EXPORT cmark_node*
 cmark_node_last_child(cmark_node *node);
 
+// Accessors
+
+CMARK_EXPORT cmark_node_type
+cmark_node_get_type(cmark_node *node);
+
+CMARK_EXPORT const char*
+cmark_node_get_content(cmark_node *node);
+
+CMARK_EXPORT int
+cmark_node_set_content(cmark_node *node, const char *content);
+
+CMARK_EXPORT const char*
+cmark_node_get_url(cmark_node *node);
+
+CMARK_EXPORT int
+cmark_node_set_url(cmark_node *node, const char *url);
+
 // Tree manipulation
 
 CMARK_EXPORT void
@@ -124,9 +149,6 @@ unsigned char *cmark_render_html(cmark_node *root);
 CMARK_EXPORT
 unsigned char *cmark_markdown_to_html(unsigned char *text, int len);
 
-CMARK_EXPORT
-void cmark_free_nodes(cmark_node *e);
-
 #ifndef CMARK_NO_SHORT_NAMES
   #define NODE_DOCUMENT             CMARK_NODE_DOCUMENT
   #define NODE_BQUOTE               CMARK_NODE_BQUOTE
diff --git a/src/html/html.c b/src/html/html.c
@@ -152,11 +152,11 @@ static void inlines_to_plain_html(strbuf *html, cmark_node* ils)
 // Convert an inline list to HTML.  Returns 0 on success, and sets result.
 static void inlines_to_html(strbuf *html, cmark_node* ils)
 {
-	cmark_node* children;
+	bool visit_children;
 	render_stack* rstack = NULL;
 
 	while(ils != NULL) {
-	        children = NULL;
+	        visit_children = false;
 		switch(ils->type) {
 		case NODE_STRING:
 			escape_html(html, ils->as.literal.data, ils->as.literal.len);
@@ -193,7 +193,7 @@ static void inlines_to_html(strbuf *html, cmark_node* ils)
 			}
 
 			strbuf_puts(html, "\">");
-			children = ils->first_child;
+			visit_children = true;
 			rstack = push_inline(rstack, ils->next, "</a>");
 			break;
 
@@ -215,20 +215,20 @@ static void inlines_to_html(strbuf *html, cmark_node* ils)
 
 		case NODE_STRONG:
 			strbuf_puts(html, "<strong>");
-			children = ils->first_child;
+			visit_children = true;
 			rstack = push_inline(rstack, ils->next, "</strong>");
 			break;
 
 		case NODE_EMPH:
 			strbuf_puts(html, "<em>");
-			children = ils->first_child;
+			visit_children = true;
 			rstack = push_inline(rstack, ils->next, "</em>");
 			break;
 		default:
 			break;
 		}
-		if (children) {
-			ils = children;
+		if (visit_children) {
+			ils = ils->first_child;
 		} else {
 			ils = ils->next;
 		}
diff --git a/src/node.c b/src/node.c
@@ -1,8 +1,26 @@
-#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
 
 #include "config.h"
 #include "node.h"
 
+static void
+S_node_unlink(cmark_node *node);
+
+cmark_node*
+cmark_node_new(cmark_node_type type) {
+	cmark_node *node = (cmark_node *)calloc(1, sizeof(*node));
+	node->type = type;
+	return node;
+}
+
+void
+cmark_node_destroy(cmark_node *node) {
+	S_node_unlink(node);
+	node->next = NULL;
+	cmark_free_nodes(node);
+}
+
 cmark_node_type
 cmark_node_get_type(cmark_node *node)
 {
@@ -69,6 +87,71 @@ cmark_node_last_child(cmark_node *node)
 	return node->last_child;
 }
 
+static char*
+S_strdup(const char *str) {
+	size_t size = strlen(str) + 1;
+	char *dup = (char *)malloc(size);
+	memcpy(dup, str, size);
+	return dup;
+}
+
+const char*
+cmark_node_get_content(cmark_node *node) {
+	switch (node->type) {
+	case NODE_STRING:
+	case NODE_INLINE_HTML:
+	case NODE_INLINE_CODE:
+		return cmark_chunk_to_cstr(&node->as.literal);
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+int
+cmark_node_set_content(cmark_node *node, const char *content) {
+	switch (node->type) {
+	case NODE_STRING:
+	case NODE_INLINE_HTML:
+	case NODE_INLINE_CODE:
+		cmark_chunk_set_cstr(&node->as.literal, content);
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+const char*
+cmark_node_get_url(cmark_node *node) {
+	switch (node->type) {
+	case NODE_LINK:
+	case NODE_IMAGE:
+		return (char *)node->as.link.url;
+	default:
+		break;
+	}
+
+	return NULL;
+}
+
+int
+cmark_node_set_url(cmark_node *node, const char *url) {
+	switch (node->type) {
+	case NODE_LINK:
+	case NODE_IMAGE:
+		free(node->as.link.url);
+		node->as.link.url = (unsigned char *)S_strdup(url);
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
 static inline bool
 S_is_block(cmark_node *node) {
 	return node->type >= CMARK_NODE_FIRST_BLOCK
diff --git a/src/node.h b/src/node.h
@@ -61,7 +61,7 @@ struct cmark_node {
 	} as;
 };
 
-int
+CMARK_EXPORT int
 cmark_node_check(cmark_node *node);
 
 #ifdef __cplusplus