diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | api_test/CMakeLists.txt | 19 | ||||
-rw-r--r-- | api_test/harness.c | 102 | ||||
-rw-r--r-- | api_test/harness.h | 34 | ||||
-rw-r--r-- | api_test/main.c | 58 | ||||
-rw-r--r-- | src/chunk.h | 39 | ||||
-rw-r--r-- | src/cmark.h | 32 | ||||
-rw-r--r-- | src/html/html.c | 14 | ||||
-rw-r--r-- | src/node.c | 85 | ||||
-rw-r--r-- | src/node.h | 2 |
10 files changed, 362 insertions, 25 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 105c257..9e2adbd 100644 --- 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 new file mode 100644 index 0000000..b2ab2c8 --- /dev/null +++ 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 new file mode 100644 index 0000000..6b38d41 --- /dev/null +++ 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 new file mode 100644 index 0000000..5fdc7ae --- /dev/null +++ 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 new file mode 100644 index 0000000..1ff606b --- /dev/null +++ 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 index 9dd56b6..7a1dbc3 100644 --- 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 index c5ddd5b..522e77e 100644 --- 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 index 11db0de..8110f87 100644 --- 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; } @@ -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 @@ -61,7 +61,7 @@ struct cmark_node { } as; }; -int +CMARK_EXPORT int cmark_node_check(cmark_node *node); #ifdef __cplusplus |